Refactor to minimize re-rendering of certain aspects

This commit is contained in:
Gregory Schier 2017-08-09 17:13:59 -07:00
parent 32ef288c6e
commit 10a241b600
8 changed files with 102 additions and 95 deletions

View File

@ -12,6 +12,25 @@ export const SYNC_MODE_OFF = 'paused';
export const SYNC_MODE_ON = 'active'; export const SYNC_MODE_ON = 'active';
export const SYNC_MODE_NEVER = 'never'; export const SYNC_MODE_NEVER = 'never';
export const SYNC_MODE_UNSET = 'unset'; export const SYNC_MODE_UNSET = 'unset';
let changeListeners = [];
export function onChange (callback) {
changeListeners.push(callback);
}
export function offChange (callback) {
changeListeners = changeListeners.filter(l => l !== callback);
}
let _changeTimeout = null;
function _notifyChange () {
clearTimeout(_changeTimeout);
_changeTimeout = setTimeout(() => {
for (const fn of changeListeners) {
fn();
}
}, 200);
}
export function allActiveResources (resourceGroupId = null) { export function allActiveResources (resourceGroupId = null) {
if (resourceGroupId) { if (resourceGroupId) {
@ -86,6 +105,7 @@ export function findResourcesByDocId (id) {
export async function removeResourceGroup (resourceGroupId) { export async function removeResourceGroup (resourceGroupId) {
await _execDB(TYPE_RESOURCE, 'remove', {resourceGroupId}, {multi: true}); await _execDB(TYPE_RESOURCE, 'remove', {resourceGroupId}, {multi: true});
await _execDB(TYPE_CONFIG, 'remove', {resourceGroupId}, {multi: true}); await _execDB(TYPE_CONFIG, 'remove', {resourceGroupId}, {multi: true});
_notifyChange();
} }
export async function insertResource (resource) { export async function insertResource (resource) {
@ -94,17 +114,20 @@ export async function insertResource (resource) {
h.update(resource.id); h.update(resource.id);
const newResource = Object.assign({}, resource, {_id: `rs_${h.digest('hex')}`}); const newResource = Object.assign({}, resource, {_id: `rs_${h.digest('hex')}`});
await _execDB(TYPE_RESOURCE, 'insert', newResource); await _execDB(TYPE_RESOURCE, 'insert', newResource);
_notifyChange();
return newResource; return newResource;
} }
export async function updateResource (resource, ...patches) { export async function updateResource (resource, ...patches) {
const newDoc = Object.assign({}, resource, ...patches); const newDoc = Object.assign({}, resource, ...patches);
await _execDB(TYPE_RESOURCE, 'update', {_id: resource._id}, newDoc, {multi: true}); await _execDB(TYPE_RESOURCE, 'update', {_id: resource._id}, newDoc, {multi: true});
_notifyChange();
return newDoc; return newDoc;
} }
export function removeResource (resource) { export async function removeResource (resource) {
return _execDB(TYPE_RESOURCE, 'remove', {_id: resource._id}, {multi: true}); await _execDB(TYPE_RESOURCE, 'remove', {_id: resource._id}, {multi: true});
_notifyChange();
} }
// ~~~~~~ // // ~~~~~~ //
@ -196,6 +219,7 @@ export function initDB (config, forceReset) {
// ~~~~~~~ // // ~~~~~~~ //
let _database = null; let _database = null;
function _getDB (type, config = {}) { function _getDB (type, config = {}) {
initDB(config); initDB(config);
return _database[type]; return _database[type];

View File

@ -111,24 +111,17 @@ class Modal extends PureComponent {
this.props.onHide && this.props.onHide(); this.props.onHide && this.props.onHide();
} }
componentDidMount () {
this._node.addEventListener('keydown', this._handleKeyDown);
}
componentWillUnmount () {
if (this._node) {
this._node.removeEventListener('keydown', this._handleKeyDown);
}
}
render () { render () {
const {tall, wide, noEscape, className, children} = this.props; const {tall, wide, noEscape, className, children} = this.props;
const {open, zIndex, forceRefreshCounter} = this.state; const {open, zIndex, forceRefreshCounter} = this.state;
if (!open) {
return null;
}
const classes = classnames( const classes = classnames(
'modal', 'modal',
className, className,
{'modal--open': open},
{'modal--fixed-height': tall}, {'modal--fixed-height': tall},
{'modal--noescape': noEscape}, {'modal--noescape': noEscape},
{'modal--wide': wide}, {'modal--wide': wide},
@ -141,6 +134,7 @@ class Modal extends PureComponent {
return ( return (
<div ref={this._setModalRef} <div ref={this._setModalRef}
onKeyDown={this._handleKeyDown}
tabIndex="-1" tabIndex="-1"
className={classes} className={classes}
style={styles} style={styles}

View File

@ -27,7 +27,7 @@ const BASE_CODEMIRROR_OPTIONS = {
placeholder: 'Start Typing...', placeholder: 'Start Typing...',
foldGutter: true, foldGutter: true,
height: 'auto', height: 'auto',
autoRefresh: 500, autoRefresh: 2000,
lineWrapping: true, lineWrapping: true,
scrollbarStyle: 'native', scrollbarStyle: 'native',
lint: true, lint: true,

View File

@ -19,8 +19,11 @@ class SyncDropdown extends PureComponent {
this.state = { this.state = {
loggedIn: null, loggedIn: null,
syncData: null, loading: false,
loading: false resourceGroupId: null,
syncMode: null,
syncPercent: 0,
workspaceName: ''
}; };
} }
@ -33,8 +36,7 @@ class SyncDropdown extends PureComponent {
} }
async _handleSyncResourceGroupId () { async _handleSyncResourceGroupId () {
const {syncData} = this.state; const resourceGroupId = this.state;
const resourceGroupId = syncData.resourceGroupId;
// Set loading state // Set loading state
this.setState({loading: true}); this.setState({loading: true});
@ -73,15 +75,13 @@ class SyncDropdown extends PureComponent {
const numClean = all.length - dirty.length; const numClean = all.length - dirty.length;
const syncPercent = all.length === 0 ? 100 : parseInt(numClean / all.length * 1000) / 10; const syncPercent = all.length === 0 ? 100 : parseInt(numClean / all.length * 1000) / 10;
const syncData = { if (this._isMounted) {
this.setState({
resourceGroupId, resourceGroupId,
syncPercent, syncPercent,
syncMode: config.syncMode, syncMode: config.syncMode,
name: workspace.name workspaceName: workspace.name
}; });
if (this._isMounted) {
this.setState({syncData});
} }
} }
@ -90,29 +90,26 @@ class SyncDropdown extends PureComponent {
await this._reloadData(); await this._reloadData();
} }
async componentWillMount () { async componentDidMount () {
this._interval = setInterval(this._reloadData, 500); this._isMounted = true;
syncStorage.onChange(this._reloadData);
await this._reloadData(); await this._reloadData();
} }
componentDidMount () {
this._isMounted = true;
}
componentWillUnmount () { componentWillUnmount () {
clearInterval(this._interval); syncStorage.offChange(this._reloadData);
this._isMounted = false; this._isMounted = false;
} }
async componentDidUpdate () { async componentDidUpdate () {
const {syncData} = this.state; const {resourceGroupId, syncMode} = this.state;
if (!syncData) { if (!resourceGroupId) {
return; return;
} }
// Sync has not yet been configured for this workspace, so prompt the user to do so // Sync has not yet been configured for this workspace, so prompt the user to do so
const isModeUnset = !syncData.syncMode || syncData.syncMode === syncStorage.SYNC_MODE_UNSET; const isModeUnset = !syncMode || syncMode === syncStorage.SYNC_MODE_UNSET;
if (isModeUnset && !this._hasPrompted) { if (isModeUnset && !this._hasPrompted) {
this._hasPrompted = true; this._hasPrompted = true;
await this._handleShowSyncModePrompt(); await this._handleShowSyncModePrompt();
@ -138,14 +135,14 @@ class SyncDropdown extends PureComponent {
render () { render () {
const {className} = this.props; const {className} = this.props;
const {syncData, loading, loggedIn} = this.state; const {resourceGroupId, loading, loggedIn} = this.state;
// Don't show the sync menu unless we're logged in // Don't show the sync menu unless we're logged in
if (!loggedIn) { if (!loggedIn) {
return null; return null;
} }
if (!syncData) { if (!resourceGroupId) {
return ( return (
<div className={className}> <div className={className}>
<button className="btn btn--compact wide" disabled> <button className="btn btn--compact wide" disabled>
@ -154,7 +151,7 @@ class SyncDropdown extends PureComponent {
</div> </div>
); );
} else { } else {
const {syncMode, syncPercent} = syncData; const {syncMode, syncPercent} = this.state;
return ( return (
<div className={className}> <div className={className}>
<Dropdown wide className="wide tall"> <Dropdown wide className="wide tall">

View File

@ -25,6 +25,25 @@ class RequestSwitcherModal extends PureComponent {
}; };
} }
_handleKeydown (e) {
const keyCode = e.keyCode;
if (keyCode === 38 || (keyCode === 9 && e.shiftKey)) {
// Up or Shift+Tab
this._setActiveIndex(this.state.activeIndex - 1);
} else if (keyCode === 40 || keyCode === 9) {
// Down or Tab
this._setActiveIndex(this.state.activeIndex + 1);
} else if (keyCode === 13) {
// Enter
this._activateCurrentIndex();
} else {
return;
}
e.preventDefault();
}
_setModalRef (n) { _setModalRef (n) {
this.modal = n; this.modal = n;
} }
@ -186,27 +205,6 @@ class RequestSwitcherModal extends PureComponent {
} }
} }
componentDidMount () {
ReactDOM.findDOMNode(this).addEventListener('keydown', e => {
const keyCode = e.keyCode;
if (keyCode === 38 || (keyCode === 9 && e.shiftKey)) {
// Up or Shift+Tab
this._setActiveIndex(this.state.activeIndex - 1);
} else if (keyCode === 40 || keyCode === 9) {
// Down or Tab
this._setActiveIndex(this.state.activeIndex + 1);
} else if (keyCode === 13) {
// Enter
this._activateCurrentIndex();
} else {
return;
}
e.preventDefault();
});
}
render () { render () {
const { const {
searchString, searchString,
@ -219,7 +217,7 @@ class RequestSwitcherModal extends PureComponent {
const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type); const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type);
return ( return (
<Modal ref={this._setModalRef} dontFocus tall> <Modal ref={this._setModalRef} dontFocus tall onKeyDown={this._handleKeydown}>
<ModalHeader hideCloseButton> <ModalHeader hideCloseButton>
<div className="pull-right txt-sm pad-right"> <div className="pull-right txt-sm pad-right">
<span className="monospace">tab</span> or <span className="monospace">tab</span> or

View File

@ -7,7 +7,6 @@ class ResponseTimer extends PureComponent {
super(props); super(props);
this._interval = null; this._interval = null;
this.state = { this.state = {
show: false,
elapsedTime: 0 elapsedTime: 0
}; };
} }
@ -16,24 +15,27 @@ class ResponseTimer extends PureComponent {
clearInterval(this._interval); clearInterval(this._interval);
} }
componentDidMount () { componentWillReceiveProps (nextProps) {
const {loadStartTime} = nextProps;
if (loadStartTime <= 0) {
clearInterval(this._interval);
return;
}
clearInterval(this._interval); // Just to be sure
this._interval = setInterval(() => { this._interval = setInterval(() => {
const {loadStartTime} = this.props;
if (loadStartTime > 0) {
// Show and update if needed
const millis = Date.now() - loadStartTime - 200; const millis = Date.now() - loadStartTime - 200;
const elapsedTime = Math.round(millis / 100) / 10; const elapsedTime = Math.round(millis / 100) / 10;
this.setState({show: true, elapsedTime}); this.setState({elapsedTime});
} else if (this.state.show) {
// Hide if needed after a small delay (so it doesn't disappear too quickly)
setTimeout(() => this.setState({show: false}), 200);
}
}, 100); }, 100);
} }
render () { render () {
const {handleCancel} = this.props; const {handleCancel, loadStartTime} = this.props;
const {show, elapsedTime} = this.state; const {elapsedTime} = this.state;
const show = loadStartTime > 0;
return ( return (
<div className={classnames('overlay theme--overlay', {'overlay--hidden': !show})}> <div className={classnames('overlay theme--overlay', {'overlay--hidden': !show})}>

View File

@ -23,6 +23,8 @@ class Plugins extends React.PureComponent {
isRefreshingPlugins: boolean isRefreshingPlugins: boolean
}; };
_isMounted: boolean;
constructor (props: any) { constructor (props: any) {
super(props); super(props);
this.state = { this.state = {
@ -75,14 +77,22 @@ class Plugins extends React.PureComponent {
const delta = Date.now() - start; const delta = Date.now() - start;
await delay(500 - delta); await delay(500 - delta);
this.setState({plugins, isRefreshingPlugins: false});
trackEvent('Plugins', 'Refresh'); trackEvent('Plugins', 'Refresh');
if (this._isMounted) {
this.setState({plugins, isRefreshingPlugins: false});
}
} }
componentDidMount () { componentDidMount () {
this._isMounted = true;
this._handleRefreshPlugins(); this._handleRefreshPlugins();
} }
componentWillUnmount () {
this._isMounted = false;
}
render () { render () {
const {plugins, error, isInstallingFromNpm, isRefreshingPlugins} = this.state; const {plugins, error, isInstallingFromNpm, isRefreshingPlugins} = this.state;

View File

@ -3,22 +3,13 @@
.modal { .modal {
// Hidden state // Hidden state
opacity: 0;
z-index: -999999; // Component updates this manually
transition: opacity 250ms;
position: absolute; position: absolute;
top: 0; top: 0;
left: -99999px; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
padding: @padding-lg; padding: @padding-lg;
&.modal--open {
opacity: 1;
left: 0;
}
&:focus { &:focus {
outline: 0; outline: 0;
} }
@ -33,15 +24,11 @@
} }
.modal__content__wrapper { .modal__content__wrapper {
top: 1rem;
width: @modal-width; width: @modal-width;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
margin: auto; margin: auto;
position: relative;
transform: scale(0.95);
transition: transform 150ms ease-out, top 200ms ease-out;
// We want pointer events to pass through to the backdrop so we can close it // We want pointer events to pass through to the backdrop so we can close it
pointer-events: none; pointer-events: none;
@ -51,11 +38,6 @@
height: 100%; height: 100%;
} }
&.modal--open .modal__content__wrapper {
transform: scale(1);
top: 0;
}
&.modal--wide .modal__content__wrapper { &.modal--wide .modal__content__wrapper {
width: @modal-width-wide; width: @modal-width-wide;
} }