mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-05 04:04:38 +08:00
fix(inline): radial progress, merge body with download data on render
This commit is contained in:
parent
6faa7d5e12
commit
78681f6ed1
4 changed files with 77 additions and 25 deletions
|
@ -47,9 +47,8 @@ export default class EmailFrame extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeContent = () => {
|
_writeContent = () => {
|
||||||
this._lastComputedHeight = 0;
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
||||||
const domNode = ReactDOM.findDOMNode(this);
|
const doc = iframeNode.contentDocument;
|
||||||
const doc = domNode.contentDocument;
|
|
||||||
if (!doc) { return; }
|
if (!doc) { return; }
|
||||||
doc.open();
|
doc.open();
|
||||||
|
|
||||||
|
@ -71,7 +70,13 @@ export default class EmailFrame extends React.Component {
|
||||||
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
// Notify the EventedIFrame that we've replaced it's document (with `open`)
|
||||||
// so it can attach event listeners again.
|
// so it can attach event listeners again.
|
||||||
this.refs.iframe.documentWasReplaced();
|
this.refs.iframe.documentWasReplaced();
|
||||||
domNode.height = '0px';
|
this._onMustRecalculateFrameHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMustRecalculateFrameHeight = () => {
|
||||||
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
||||||
|
iframeNode.height = `0px`;
|
||||||
|
this._lastComputedHeight = 0;
|
||||||
this._setFrameHeight();
|
this._setFrameHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,31 +95,42 @@ export default class EmailFrame extends React.Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domNode = ReactDOM.findDOMNode(this);
|
// Q: What's up with this holder?
|
||||||
const height = this._getFrameHeight(domNode.contentDocument);
|
// A: If you resize the window, or do something to trigger setFrameHeight
|
||||||
|
// on an already-loaded message view, all the heights go to zero for a brief
|
||||||
|
// second while the heights are recomputed. This causes the ScrollRegion to
|
||||||
|
// reset it's scrollTop to ~0 (the new combined heiht of all children).
|
||||||
|
// To prevent this, the holderNode holds the last computed height until
|
||||||
|
// the new height is computed.
|
||||||
|
const holderNode = ReactDOM.findDOMNode(this.refs.iframeHeightHolder);
|
||||||
|
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
||||||
|
const height = this._getFrameHeight(iframeNode.contentDocument);
|
||||||
|
|
||||||
// Why 5px? Some emails have elements with a height of 100%, and then put
|
// Why 5px? Some emails have elements with a height of 100%, and then put
|
||||||
// tracking pixels beneath that. In these scenarios, the scrollHeight of the
|
// tracking pixels beneath that. In these scenarios, the scrollHeight of the
|
||||||
// message is always <100% + 1px>, which leads us to resize them constantly.
|
// message is always <100% + 1px>, which leads us to resize them constantly.
|
||||||
// This is a hack, but I'm not sure of a better solution.
|
// This is a hack, but I'm not sure of a better solution.
|
||||||
if (Math.abs(height - this._lastComputedHeight) > 5) {
|
if (Math.abs(height - this._lastComputedHeight) > 5) {
|
||||||
domNode.height = `${height}px`;
|
this.refs.iframe.setHeightQuietly(height);
|
||||||
|
holderNode.height = `${height}px`;
|
||||||
this._lastComputedHeight = height;
|
this._lastComputedHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domNode.contentDocument.readyState !== 'complete') {
|
if (iframeNode.contentDocument.readyState !== 'complete') {
|
||||||
_.defer(()=> this._setFrameHeight());
|
_.defer(()=> this._setFrameHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<EventedIFrame
|
<div ref="iframeHeightHolder" style={{height: this._lastComputedHeight}}>
|
||||||
ref="iframe"
|
<EventedIFrame
|
||||||
seamless="seamless"
|
ref="iframe"
|
||||||
searchable
|
seamless="seamless"
|
||||||
onResize={this._setFrameHeight}
|
searchable
|
||||||
/>
|
onResize={this._onResize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ React = require 'react'
|
||||||
_ = require 'underscore'
|
_ = require 'underscore'
|
||||||
EmailFrame = require './email-frame'
|
EmailFrame = require './email-frame'
|
||||||
{Utils,
|
{Utils,
|
||||||
|
CanvasUtils,
|
||||||
NylasAPI,
|
NylasAPI,
|
||||||
MessageUtils,
|
MessageUtils,
|
||||||
MessageBodyProcessor,
|
MessageBodyProcessor,
|
||||||
|
@ -25,7 +26,8 @@ class MessageItemBody extends React.Component
|
||||||
error: null
|
error: null
|
||||||
|
|
||||||
componentWillMount: =>
|
componentWillMount: =>
|
||||||
@_unsub = MessageBodyProcessor.subscribe(@props.message, @_onBodyProcessed)
|
@_unsub = MessageBodyProcessor.subscribe @props.message, (processedBody) =>
|
||||||
|
@setState({processedBody})
|
||||||
|
|
||||||
componentDidMount: =>
|
componentDidMount: =>
|
||||||
@_onFetchBody() if not _.isString(@props.message.body)
|
@_onFetchBody() if not _.isString(@props.message.body)
|
||||||
|
@ -33,7 +35,8 @@ class MessageItemBody extends React.Component
|
||||||
componentWillReceiveProps: (nextProps) ->
|
componentWillReceiveProps: (nextProps) ->
|
||||||
if nextProps.message.id isnt @props.message.id
|
if nextProps.message.id isnt @props.message.id
|
||||||
@_unsub?()
|
@_unsub?()
|
||||||
@_unsub = MessageBodyProcessor.subscribe(nextProps.message, @_onBodyProcessed)
|
@_unsub = MessageBodyProcessor.subscribe nextProps.message, (processedBody) =>
|
||||||
|
@setState({processedBody})
|
||||||
|
|
||||||
componentWillUnmount: =>
|
componentWillUnmount: =>
|
||||||
@_unmounted = true
|
@_unmounted = true
|
||||||
|
@ -52,7 +55,9 @@ class MessageItemBody extends React.Component
|
||||||
|
|
||||||
_renderBody: =>
|
_renderBody: =>
|
||||||
if _.isString(@props.message.body) and _.isString(@state.processedBody)
|
if _.isString(@props.message.body) and _.isString(@state.processedBody)
|
||||||
<EmailFrame showQuotedText={@state.showQuotedText} content={@state.processedBody}/>
|
finalBody = @_mergeBodyWithFiles(@state.processedBody)
|
||||||
|
|
||||||
|
<EmailFrame showQuotedText={@state.showQuotedText} content={finalBody}/>
|
||||||
else if @state.error
|
else if @state.error
|
||||||
<div className="message-body-error">
|
<div className="message-body-error">
|
||||||
Sorry, this message could not be loaded. (Status code {@state.error.statusCode})
|
Sorry, this message could not be loaded. (Status code {@state.error.statusCode})
|
||||||
|
@ -90,9 +95,7 @@ class MessageItemBody extends React.Component
|
||||||
return if @_unmounted
|
return if @_unmounted
|
||||||
@setState({error})
|
@setState({error})
|
||||||
|
|
||||||
_onBodyProcessed: (body) =>
|
_mergeBodyWithFiles: (body) =>
|
||||||
downloadingSpinnerPath = Utils.imageNamed('inline-loading-spinner.gif')
|
|
||||||
|
|
||||||
# Replace cid:// references with the paths to downloaded files
|
# Replace cid:// references with the paths to downloaded files
|
||||||
for file in @props.message.files
|
for file in @props.message.files
|
||||||
download = @props.downloads[file.id]
|
download = @props.downloads[file.id]
|
||||||
|
@ -101,7 +104,8 @@ class MessageItemBody extends React.Component
|
||||||
if download and download.state isnt 'finished'
|
if download and download.state isnt 'finished'
|
||||||
# Render a spinner and inject a `style` tag that injects object-position / object-fit
|
# Render a spinner and inject a `style` tag that injects object-position / object-fit
|
||||||
body = body.replace cidRegexp, (text, quoteCharacter) ->
|
body = body.replace cidRegexp, (text, quoteCharacter) ->
|
||||||
"#{downloadingSpinnerPath}#{quoteCharacter} style=#{quoteCharacter} object-position: 50% 50%; object-fit: none; "
|
dataUri = CanvasUtils.dataURIForLoadedPercent(download.percent)
|
||||||
|
"#{dataUri}#{quoteCharacter} style=#{quoteCharacter} object-position: 50% 50%; object-fit: none; "
|
||||||
else
|
else
|
||||||
# Render the completed download
|
# Render the completed download
|
||||||
body = body.replace cidRegexp, (text, quoteCharacter) ->
|
body = body.replace cidRegexp, (text, quoteCharacter) ->
|
||||||
|
@ -112,7 +116,6 @@ class MessageItemBody extends React.Component
|
||||||
# no "missing image" region shown, just a space.
|
# no "missing image" region shown, just a space.
|
||||||
body = body.replace(MessageUtils.cidRegex, "src=\"#{TransparentPixel}\"")
|
body = body.replace(MessageUtils.cidRegex, "src=\"#{TransparentPixel}\"")
|
||||||
|
|
||||||
@setState
|
return body
|
||||||
processedBody: body
|
|
||||||
|
|
||||||
module.exports = MessageItemBody
|
module.exports = MessageItemBody
|
||||||
|
|
|
@ -5,6 +5,11 @@ DragCanvas = document.createElement("canvas")
|
||||||
DragCanvas.style.position = "absolute"
|
DragCanvas.style.position = "absolute"
|
||||||
document.body.appendChild(DragCanvas)
|
document.body.appendChild(DragCanvas)
|
||||||
|
|
||||||
|
PercentLoadedCache = {}
|
||||||
|
PercentLoadedCanvas = document.createElement("canvas")
|
||||||
|
PercentLoadedCanvas.style.position = "absolute"
|
||||||
|
document.body.appendChild(PercentLoadedCanvas)
|
||||||
|
|
||||||
SystemTrayCanvas = document.createElement("canvas")
|
SystemTrayCanvas = document.createElement("canvas")
|
||||||
|
|
||||||
CanvasUtils =
|
CanvasUtils =
|
||||||
|
@ -23,6 +28,28 @@ CanvasUtils =
|
||||||
ctx.stroke() if stroke
|
ctx.stroke() if stroke
|
||||||
ctx.fill() if fill
|
ctx.fill() if fill
|
||||||
|
|
||||||
|
dataURIForLoadedPercent: (percent) ->
|
||||||
|
percent = Math.floor(percent / 5.0) * 5.0
|
||||||
|
cacheKey = "#{percent}%"
|
||||||
|
if not PercentLoadedCache[cacheKey]
|
||||||
|
canvas = PercentLoadedCanvas
|
||||||
|
scale = window.devicePixelRatio
|
||||||
|
canvas.width = 20 * scale
|
||||||
|
canvas.height = 20 * scale
|
||||||
|
canvas.style.width = "30px"
|
||||||
|
canvas.style.height = "30px"
|
||||||
|
|
||||||
|
half = 10 * scale
|
||||||
|
ctx = canvas.getContext('2d')
|
||||||
|
ctx.strokeStyle = "#AAA"
|
||||||
|
ctx.lineWidth = 3 * scale
|
||||||
|
ctx.clearRect(0, 0, 20 * scale, 20 * scale)
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(half, half, half - ctx.lineWidth, -0.5 * Math.PI, (-0.5 * Math.PI) + (2 * Math.PI) * percent / 100.0)
|
||||||
|
ctx.stroke()
|
||||||
|
PercentLoadedCache[cacheKey] = canvas.toDataURL()
|
||||||
|
return PercentLoadedCache[cacheKey]
|
||||||
|
|
||||||
canvasWithThreadDragImage: (count) ->
|
canvasWithThreadDragImage: (count) ->
|
||||||
canvas = DragCanvas
|
canvas = DragCanvas
|
||||||
|
|
||||||
|
@ -33,7 +60,6 @@ CanvasUtils =
|
||||||
canvas.style.width = "58px"
|
canvas.style.width = "58px"
|
||||||
canvas.style.height = "55px"
|
canvas.style.height = "55px"
|
||||||
|
|
||||||
# necessary for setDragImage to work
|
|
||||||
ctx = canvas.getContext('2d')
|
ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
# mail background image
|
# mail background image
|
||||||
|
|
|
@ -56,10 +56,14 @@ class EventedIFrame extends React.Component
|
||||||
Public: Call this method if you replace the contents of the iframe's document.
|
Public: Call this method if you replace the contents of the iframe's document.
|
||||||
This allows {EventedIframe} to re-attach it's event listeners.
|
This allows {EventedIframe} to re-attach it's event listeners.
|
||||||
###
|
###
|
||||||
documentWasReplaced: =>
|
didReplaceDocument: =>
|
||||||
@_unsubscribeFromIFrameEvents()
|
@_unsubscribeFromIFrameEvents()
|
||||||
@_subscribeToIFrameEvents()
|
@_subscribeToIFrameEvents()
|
||||||
|
|
||||||
|
setHeightQuietly: (height) =>
|
||||||
|
@_ignoreNextResize = true
|
||||||
|
ReactDOM.findDOMNode(@).height = "#{height}px"
|
||||||
|
|
||||||
_onSearchableStoreChange: =>
|
_onSearchableStoreChange: =>
|
||||||
return unless @props.searchable
|
return unless @props.searchable
|
||||||
node = ReactDOM.findDOMNode(@)
|
node = ReactDOM.findDOMNode(@)
|
||||||
|
@ -116,6 +120,9 @@ class EventedIFrame extends React.Component
|
||||||
window.getSelection().empty()
|
window.getSelection().empty()
|
||||||
|
|
||||||
_onIFrameResize: (event) =>
|
_onIFrameResize: (event) =>
|
||||||
|
if @_ignoreNextResize
|
||||||
|
@_ignoreNextResize = false
|
||||||
|
return
|
||||||
@props.onResize?(event)
|
@props.onResize?(event)
|
||||||
|
|
||||||
# The iFrame captures events that take place over it, which causes some
|
# The iFrame captures events that take place over it, which causes some
|
||||||
|
|
Loading…
Add table
Reference in a new issue