mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-01 10:14:26 +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 = () => {
|
||||
this._lastComputedHeight = 0;
|
||||
const domNode = ReactDOM.findDOMNode(this);
|
||||
const doc = domNode.contentDocument;
|
||||
const iframeNode = ReactDOM.findDOMNode(this.refs.iframe);
|
||||
const doc = iframeNode.contentDocument;
|
||||
if (!doc) { return; }
|
||||
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`)
|
||||
// so it can attach event listeners again.
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -90,31 +95,42 @@ export default class EmailFrame extends React.Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const domNode = ReactDOM.findDOMNode(this);
|
||||
const height = this._getFrameHeight(domNode.contentDocument);
|
||||
// Q: What's up with this holder?
|
||||
// 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
|
||||
// tracking pixels beneath that. In these scenarios, the scrollHeight of the
|
||||
// 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.
|
||||
if (Math.abs(height - this._lastComputedHeight) > 5) {
|
||||
domNode.height = `${height}px`;
|
||||
this.refs.iframe.setHeightQuietly(height);
|
||||
holderNode.height = `${height}px`;
|
||||
this._lastComputedHeight = height;
|
||||
}
|
||||
|
||||
if (domNode.contentDocument.readyState !== 'complete') {
|
||||
if (iframeNode.contentDocument.readyState !== 'complete') {
|
||||
_.defer(()=> this._setFrameHeight());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EventedIFrame
|
||||
ref="iframe"
|
||||
seamless="seamless"
|
||||
searchable
|
||||
onResize={this._setFrameHeight}
|
||||
/>
|
||||
<div ref="iframeHeightHolder" style={{height: this._lastComputedHeight}}>
|
||||
<EventedIFrame
|
||||
ref="iframe"
|
||||
seamless="seamless"
|
||||
searchable
|
||||
onResize={this._onResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ React = require 'react'
|
|||
_ = require 'underscore'
|
||||
EmailFrame = require './email-frame'
|
||||
{Utils,
|
||||
CanvasUtils,
|
||||
NylasAPI,
|
||||
MessageUtils,
|
||||
MessageBodyProcessor,
|
||||
|
@ -25,7 +26,8 @@ class MessageItemBody extends React.Component
|
|||
error: null
|
||||
|
||||
componentWillMount: =>
|
||||
@_unsub = MessageBodyProcessor.subscribe(@props.message, @_onBodyProcessed)
|
||||
@_unsub = MessageBodyProcessor.subscribe @props.message, (processedBody) =>
|
||||
@setState({processedBody})
|
||||
|
||||
componentDidMount: =>
|
||||
@_onFetchBody() if not _.isString(@props.message.body)
|
||||
|
@ -33,7 +35,8 @@ class MessageItemBody extends React.Component
|
|||
componentWillReceiveProps: (nextProps) ->
|
||||
if nextProps.message.id isnt @props.message.id
|
||||
@_unsub?()
|
||||
@_unsub = MessageBodyProcessor.subscribe(nextProps.message, @_onBodyProcessed)
|
||||
@_unsub = MessageBodyProcessor.subscribe nextProps.message, (processedBody) =>
|
||||
@setState({processedBody})
|
||||
|
||||
componentWillUnmount: =>
|
||||
@_unmounted = true
|
||||
|
@ -52,7 +55,9 @@ class MessageItemBody extends React.Component
|
|||
|
||||
_renderBody: =>
|
||||
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
|
||||
<div className="message-body-error">
|
||||
Sorry, this message could not be loaded. (Status code {@state.error.statusCode})
|
||||
|
@ -90,9 +95,7 @@ class MessageItemBody extends React.Component
|
|||
return if @_unmounted
|
||||
@setState({error})
|
||||
|
||||
_onBodyProcessed: (body) =>
|
||||
downloadingSpinnerPath = Utils.imageNamed('inline-loading-spinner.gif')
|
||||
|
||||
_mergeBodyWithFiles: (body) =>
|
||||
# Replace cid:// references with the paths to downloaded files
|
||||
for file in @props.message.files
|
||||
download = @props.downloads[file.id]
|
||||
|
@ -101,7 +104,8 @@ class MessageItemBody extends React.Component
|
|||
if download and download.state isnt 'finished'
|
||||
# Render a spinner and inject a `style` tag that injects object-position / object-fit
|
||||
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
|
||||
# Render the completed download
|
||||
body = body.replace cidRegexp, (text, quoteCharacter) ->
|
||||
|
@ -112,7 +116,6 @@ class MessageItemBody extends React.Component
|
|||
# no "missing image" region shown, just a space.
|
||||
body = body.replace(MessageUtils.cidRegex, "src=\"#{TransparentPixel}\"")
|
||||
|
||||
@setState
|
||||
processedBody: body
|
||||
return body
|
||||
|
||||
module.exports = MessageItemBody
|
||||
|
|
|
@ -5,6 +5,11 @@ DragCanvas = document.createElement("canvas")
|
|||
DragCanvas.style.position = "absolute"
|
||||
document.body.appendChild(DragCanvas)
|
||||
|
||||
PercentLoadedCache = {}
|
||||
PercentLoadedCanvas = document.createElement("canvas")
|
||||
PercentLoadedCanvas.style.position = "absolute"
|
||||
document.body.appendChild(PercentLoadedCanvas)
|
||||
|
||||
SystemTrayCanvas = document.createElement("canvas")
|
||||
|
||||
CanvasUtils =
|
||||
|
@ -23,6 +28,28 @@ CanvasUtils =
|
|||
ctx.stroke() if stroke
|
||||
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) ->
|
||||
canvas = DragCanvas
|
||||
|
||||
|
@ -33,7 +60,6 @@ CanvasUtils =
|
|||
canvas.style.width = "58px"
|
||||
canvas.style.height = "55px"
|
||||
|
||||
# necessary for setDragImage to work
|
||||
ctx = canvas.getContext('2d')
|
||||
|
||||
# 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.
|
||||
This allows {EventedIframe} to re-attach it's event listeners.
|
||||
###
|
||||
documentWasReplaced: =>
|
||||
didReplaceDocument: =>
|
||||
@_unsubscribeFromIFrameEvents()
|
||||
@_subscribeToIFrameEvents()
|
||||
|
||||
setHeightQuietly: (height) =>
|
||||
@_ignoreNextResize = true
|
||||
ReactDOM.findDOMNode(@).height = "#{height}px"
|
||||
|
||||
_onSearchableStoreChange: =>
|
||||
return unless @props.searchable
|
||||
node = ReactDOM.findDOMNode(@)
|
||||
|
@ -116,6 +120,9 @@ class EventedIFrame extends React.Component
|
|||
window.getSelection().empty()
|
||||
|
||||
_onIFrameResize: (event) =>
|
||||
if @_ignoreNextResize
|
||||
@_ignoreNextResize = false
|
||||
return
|
||||
@props.onResize?(event)
|
||||
|
||||
# The iFrame captures events that take place over it, which causes some
|
||||
|
|
Loading…
Add table
Reference in a new issue