mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
Composer perf: Defer plugins & toolbar component rendering
This commit is contained in:
parent
dc558c889e
commit
9d327cfd84
|
@ -89,6 +89,7 @@ export default class AccountContactField extends React.Component {
|
|||
const { draft, session, accounts } = this.props;
|
||||
return (
|
||||
<InjectedComponentSet
|
||||
deferred
|
||||
className="dropdown-component"
|
||||
matching={{ role: 'Composer:FromFieldComponents' }}
|
||||
exposedProps={{
|
||||
|
|
|
@ -62,6 +62,7 @@ export default class ActionBarPlugins extends React.Component {
|
|||
<span className={className}>
|
||||
<div className="action-bar-cover" />
|
||||
<InjectedComponentSet
|
||||
deferred
|
||||
className="composer-action-bar-plugins"
|
||||
matching={{ role: ROLE }}
|
||||
exposedProps={{
|
||||
|
|
|
@ -249,6 +249,7 @@ export default class ComposerView extends React.Component {
|
|||
return (
|
||||
<div className="composer-footer-region">
|
||||
<InjectedComponentSet
|
||||
deferred
|
||||
matching={{ role: 'Composer:Footer' }}
|
||||
exposedProps={{
|
||||
draft: this.props.draft,
|
||||
|
@ -298,6 +299,7 @@ export default class ComposerView extends React.Component {
|
|||
_renderActionsWorkspaceRegion() {
|
||||
return (
|
||||
<InjectedComponentSet
|
||||
deferred
|
||||
matching={{ role: 'Composer:ActionBarWorkspace' }}
|
||||
exposedProps={{
|
||||
draft: this.props.draft,
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
font-size: 14px;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -80,6 +81,7 @@
|
|||
border-bottom: 1px solid @border-color-divider;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
min-height: 29px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
|
|
@ -1,27 +1,43 @@
|
|||
import React from 'react';
|
||||
|
||||
export default class ComposerEditorToolbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { visible: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
for (const el of document.querySelectorAll('.scroll-region-content')) {
|
||||
el.addEventListener('scroll', this._onScroll);
|
||||
}
|
||||
this._mounted = true;
|
||||
|
||||
const parentScrollRegion = this._el.closest('.scroll-region-content');
|
||||
if (parentScrollRegion) {
|
||||
this._topClip = parentScrollRegion.getBoundingClientRect().top;
|
||||
} else {
|
||||
this._topClip = 0;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!this._mounted) return;
|
||||
this.setState({ visible: true }, () => {
|
||||
if (!this._mounted) return;
|
||||
for (const el of document.querySelectorAll('.scroll-region-content')) {
|
||||
el.addEventListener('scroll', this._onScroll);
|
||||
}
|
||||
|
||||
this._el.style.height = `${this._innerEl.clientHeight}px`;
|
||||
const parentScrollRegion = this._el.closest('.scroll-region-content');
|
||||
if (parentScrollRegion) {
|
||||
this._topClip = parentScrollRegion.getBoundingClientRect().top;
|
||||
} else {
|
||||
this._topClip = 0;
|
||||
}
|
||||
|
||||
this._el.style.height = `${this._innerEl.clientHeight}px`;
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._el.style.height = `${this._innerEl.clientHeight}px`;
|
||||
this._onScroll();
|
||||
if (this._el) {
|
||||
this._el.style.height = `${this._innerEl.clientHeight}px`;
|
||||
this._onScroll();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
for (const el of document.querySelectorAll('.scroll-region-content')) {
|
||||
el.removeEventListener('scroll', this._onScroll);
|
||||
}
|
||||
|
@ -44,6 +60,14 @@ export default class ComposerEditorToolbar extends React.Component {
|
|||
const { value, onChange, plugins } = this.props;
|
||||
let sectionItems = [];
|
||||
|
||||
if (!this.state.visible) {
|
||||
return (
|
||||
<div className="RichEditor-toolbar">
|
||||
<div className="inner display-deferrable deferred" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const pluginsWithToolbars = plugins.filter(
|
||||
(p, idx) => p.toolbarComponents && p.toolbarComponents.length
|
||||
);
|
||||
|
@ -68,7 +92,7 @@ export default class ComposerEditorToolbar extends React.Component {
|
|||
|
||||
return (
|
||||
<div ref={el => (this._el = el)} className="RichEditor-toolbar">
|
||||
<div ref={el => (this._innerEl = el)} className="inner">
|
||||
<div ref={el => (this._innerEl = el)} className="inner display-deferrable">
|
||||
{sectionItems}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,9 @@ export default class InjectedComponentSet extends React.Component {
|
|||
item rendered into the set.
|
||||
- `containersRequired` (optional). Pass false to optionally remove the containers
|
||||
placed around injected components to isolate them from the rest of the app.
|
||||
|
||||
- `deferred` (optiona). Pass true to render an empty space and fill in the injected
|
||||
components after a few milliseconds. Useful for avoiding potential slowdowns in
|
||||
getting core (composer) components onscreen.
|
||||
- Any other props you provide, such as `direction`, `data-column`, etc.
|
||||
will be applied to the {Flexbox} rendered by the InjectedComponentSet.
|
||||
*/
|
||||
|
@ -57,6 +59,7 @@ export default class InjectedComponentSet extends React.Component {
|
|||
matchLimit: PropTypes.number,
|
||||
exposedProps: PropTypes.object,
|
||||
containersRequired: PropTypes.bool,
|
||||
deferred: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -68,10 +71,23 @@ export default class InjectedComponentSet extends React.Component {
|
|||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = this._getStateFromStores();
|
||||
this.state = !props.deferred ? this._getStateFromStores() : { components: [], visible: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._mounted = true;
|
||||
|
||||
if (!this.props.deferred) {
|
||||
this.listen();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.setState(this._getStateFromStores());
|
||||
this.listen();
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
listen() {
|
||||
this._componentUnlistener = ComponentRegistry.listen(() =>
|
||||
this.setState(this._getStateFromStores())
|
||||
);
|
||||
|
@ -84,6 +100,7 @@ export default class InjectedComponentSet extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
if (this._componentUnlistener) {
|
||||
this._componentUnlistener();
|
||||
}
|
||||
|
@ -118,13 +135,20 @@ export default class InjectedComponentSet extends React.Component {
|
|||
});
|
||||
|
||||
if (visible) {
|
||||
className += ' registered-region-visible';
|
||||
className += ' injected-region-visible';
|
||||
elements.unshift(
|
||||
<InjectedComponentLabel key="_label" matching={matching} {...exposedProps} />
|
||||
);
|
||||
elements.push(<span key="_clear" style={{ clear: 'both' }} />);
|
||||
}
|
||||
|
||||
if (this.props.deferred) {
|
||||
className += ' display-deferrable';
|
||||
if (!components.length) {
|
||||
className += ' deferred';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox className={className} {...flexboxProps}>
|
||||
{elements}
|
||||
|
|
|
@ -176,7 +176,7 @@ export default class InjectedComponent extends React.Component {
|
|||
});
|
||||
let className = this.props.className;
|
||||
if (this.state.visible) {
|
||||
className += ' registered-region-visible';
|
||||
className += ' injected-region-visible';
|
||||
}
|
||||
|
||||
const Component = this.state.component;
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { remote, clipboard } from 'electron';
|
||||
import { React, PropTypes, Utils, Contact, ContactStore, RegExpUtils } from 'mailspring-exports';
|
||||
import {
|
||||
TokenizingTextField,
|
||||
Menu,
|
||||
InjectedComponent,
|
||||
InjectedComponentSet,
|
||||
} from 'mailspring-component-kit';
|
||||
import { TokenizingTextField, Menu, InjectedComponentSet } from 'mailspring-component-kit';
|
||||
|
||||
const TokenRenderer = props => {
|
||||
const { email, name } = props.token;
|
||||
|
@ -214,35 +209,30 @@ export default class ParticipantsTextField extends React.Component {
|
|||
// injected region feels out of place
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<InjectedComponent
|
||||
<TokenizingTextField
|
||||
ref={el => {
|
||||
this._textfieldEl = el;
|
||||
}}
|
||||
matching={{ role: 'Composer:ParticipantsTextField' }}
|
||||
fallback={TokenizingTextField}
|
||||
requiredMethods={['focus']}
|
||||
exposedProps={{
|
||||
tokens: this.props.participants[this.props.field],
|
||||
tokenKey: p => p.email,
|
||||
tokenIsValid: p => ContactStore.isValidContact(p),
|
||||
tokenRenderer: TokenRenderer,
|
||||
onRequestCompletions: input => ContactStore.searchContacts(input),
|
||||
shouldBreakOnKeydown: this._shouldBreakOnKeydown,
|
||||
onInputTrySubmit: this._onInputTrySubmit,
|
||||
completionNode: this._completionNode,
|
||||
onAdd: this._add,
|
||||
onRemove: this._remove,
|
||||
onEdit: this._edit,
|
||||
onEmptied: this.props.onEmptied,
|
||||
onFocus: this.props.onFocus,
|
||||
onTokenAction: this._onShowContextMenu,
|
||||
menuClassSet: classSet,
|
||||
menuPrompt: this.props.field,
|
||||
field: this.props.field,
|
||||
draft: this.props.draft,
|
||||
headerMessageId: headerMessageId,
|
||||
session: this.props.session,
|
||||
}}
|
||||
tokens={this.props.participants[this.props.field]}
|
||||
tokenKey={p => p.email}
|
||||
tokenIsValid={p => ContactStore.isValidContact(p)}
|
||||
tokenRenderer={TokenRenderer}
|
||||
onRequestCompletions={input => ContactStore.searchContacts(input)}
|
||||
shouldBreakOnKeydown={this._shouldBreakOnKeydown}
|
||||
onInputTrySubmit={this._onInputTrySubmit}
|
||||
completionNode={this._completionNode}
|
||||
onAdd={this._add}
|
||||
onRemove={this._remove}
|
||||
onEdit={this._edit}
|
||||
onEmptied={this.props.onEmptied}
|
||||
onFocus={this.props.onFocus}
|
||||
onTokenAction={this._onShowContextMenu}
|
||||
menuClassSet={classSet}
|
||||
menuPrompt={this.props.field}
|
||||
field={this.props.field}
|
||||
draft={this.props.draft}
|
||||
headerMessageId={headerMessageId}
|
||||
session={this.props.session}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -332,7 +332,15 @@ body.is-blurred {
|
|||
}
|
||||
}
|
||||
|
||||
.registered-region-visible {
|
||||
.display-deferrable {
|
||||
opacity: 1;
|
||||
transition: opacity 220ms;
|
||||
&.deferred {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.injected-region-visible {
|
||||
border: 1px dashed rgba(255, 0, 0, 0.5);
|
||||
margin: 2px;
|
||||
position: relative;
|
||||
|
|
Loading…
Reference in a new issue