diff --git a/internal_packages/composer/lib/composer-header.jsx b/internal_packages/composer/lib/composer-header.jsx index 59e0f97f1..5c1f84d69 100644 --- a/internal_packages/composer/lib/composer-header.jsx +++ b/internal_packages/composer/lib/composer-header.jsx @@ -27,6 +27,7 @@ export default class ComposerHeader extends React.Component { static propTypes = { draft: React.PropTypes.object.isRequired, session: React.PropTypes.object.isRequired, + initiallyFocused: React.PropTypes.bool, } static contextTypes = { @@ -35,12 +36,12 @@ export default class ComposerHeader extends React.Component { constructor(props = {}) { super(props) - this.state = this._initialStateForDraft(this.props.draft); + this.state = this._initialStateForDraft(this.props.draft, props); } componentWillReceiveProps(nextProps) { if (this.props.session !== nextProps.session) { - this.setState(this._initialStateForDraft(nextProps.draft)); + this.setState(this._initialStateForDraft(nextProps.draft, nextProps)); } else { this._ensureFilledFieldsEnabled(nextProps.draft); } @@ -80,7 +81,7 @@ export default class ComposerHeader extends React.Component { } } - _initialStateForDraft(draft) { + _initialStateForDraft(draft, props) { const enabledFields = [Fields.To]; if (!_.isEmpty(draft.cc)) { enabledFields.push(Fields.Cc); @@ -94,8 +95,8 @@ export default class ComposerHeader extends React.Component { } return { - enabledFields: enabledFields, - participantsFocused: false, + enabledFields, + participantsFocused: props.initiallyFocused, }; } diff --git a/internal_packages/composer/lib/composer-view.jsx b/internal_packages/composer/lib/composer-view.jsx index 9bc4d1465..de2841f0f 100644 --- a/internal_packages/composer/lib/composer-view.jsx +++ b/internal_packages/composer/lib/composer-view.jsx @@ -145,6 +145,7 @@ export default class ComposerView extends React.Component { ref="header" draft={this.props.draft} session={this.props.session} + initiallyFocused={this.props.draft.to.length === 0} />
{ + componentWillUnmount() { + if (this._usub) {this._usub()} + } + + componentDidUpdate() { + this.refs.composer.focus() + } + + _onDraftReady = () => { this.refs.composer.focus().then(() => { NylasEnv.displayWindow(); + if (this.state.errorMessage) { this._showInitialErrorDialog(this.state.errorMessage); } - // Give the composer some time to render before hitting another wall - // of javascript. - window.setTimeout(() => { - NylasEnv.getCurrentWindow().updateLoadSettings({ - windowType: "composer", + // This will start loading the rest of the composer's plugins. This + // may take a while (hundreds of ms) depending on how many plugins + // you have installed. For some reason it takes two frames to + // reliably get the basic composer (Send button, etc) painted + // properly. + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + NylasEnv.getCurrentWindow().updateLoadSettings({ + windowType: "composer", + }) }) - - // The call to updateLoadSettings will start loading the remaining - // packages. Once those packages load it'll cause a change in the - // root Sheet-level InjectedComponentSet, which will cause - // everything to re-render losing our focus. We have to manually - // refocus it but defer it so the event loop of the package - // activation happens first. - window.setTimeout(() => { - this.refs.composer.focus() - }, 32) - }, 32) + }) }); } @@ -65,7 +69,7 @@ class ComposerWithWindowProps extends React.Component { return ( diff --git a/src/component-registry.coffee b/src/component-registry.coffee index a057ba89c..68e4aa8ac 100644 --- a/src/component-registry.coffee +++ b/src/component-registry.coffee @@ -160,7 +160,19 @@ class ComponentRegistry return [].concat(results) - triggerDebounced: _.debounce(( -> @trigger(@)), 1) + # We debounce because a single plugin may activate many components in + # their `activate` methods. Furthermore, when the window loads several + # plugins may load in sequence. Plugin loading takes a while (dozens of + # ms) since javascript is being read and `require` trees are being + # traversed. + # + # Triggering the ComponentRegistry is fairly expensive since many very + # high-level components (like the ) listen and re-render when + # this triggers. + # + # We set the debouce interval to 2 "frames" (33ms) to balance + # responsiveness and efficient batching. + triggerDebounced: _.debounce(( -> @trigger(@)), 33) _removeDeprecatedRoles: (displayName, roles) -> newRoles = _.clone(roles)