fix(inline): radial progress, merge body with download data on render

This commit is contained in:
Ben Gotow 2016-03-31 12:13:46 -07:00
parent 6faa7d5e12
commit 78681f6ed1
4 changed files with 77 additions and 25 deletions

View file

@ -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>
);
}
}

View file

@ -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

View file

@ -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

View file

@ -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