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_NEVER = 'never';
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) {
if (resourceGroupId) {
@ -86,6 +105,7 @@ export function findResourcesByDocId (id) {
export async function removeResourceGroup (resourceGroupId) {
await _execDB(TYPE_RESOURCE, 'remove', {resourceGroupId}, {multi: true});
await _execDB(TYPE_CONFIG, 'remove', {resourceGroupId}, {multi: true});
_notifyChange();
}
export async function insertResource (resource) {
@ -94,17 +114,20 @@ export async function insertResource (resource) {
h.update(resource.id);
const newResource = Object.assign({}, resource, {_id: `rs_${h.digest('hex')}`});
await _execDB(TYPE_RESOURCE, 'insert', newResource);
_notifyChange();
return newResource;
}
export async function updateResource (resource, ...patches) {
const newDoc = Object.assign({}, resource, ...patches);
await _execDB(TYPE_RESOURCE, 'update', {_id: resource._id}, newDoc, {multi: true});
_notifyChange();
return newDoc;
}
export function removeResource (resource) {
return _execDB(TYPE_RESOURCE, 'remove', {_id: resource._id}, {multi: true});
export async function removeResource (resource) {
await _execDB(TYPE_RESOURCE, 'remove', {_id: resource._id}, {multi: true});
_notifyChange();
}
// ~~~~~~ //
@ -196,6 +219,7 @@ export function initDB (config, forceReset) {
// ~~~~~~~ //
let _database = null;
function _getDB (type, config = {}) {
initDB(config);
return _database[type];

View File

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

View File

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

View File

@ -19,8 +19,11 @@ class SyncDropdown extends PureComponent {
this.state = {
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 () {
const {syncData} = this.state;
const resourceGroupId = syncData.resourceGroupId;
const resourceGroupId = this.state;
// Set loading state
this.setState({loading: true});
@ -73,15 +75,13 @@ class SyncDropdown extends PureComponent {
const numClean = all.length - dirty.length;
const syncPercent = all.length === 0 ? 100 : parseInt(numClean / all.length * 1000) / 10;
const syncData = {
if (this._isMounted) {
this.setState({
resourceGroupId,
syncPercent,
syncMode: config.syncMode,
name: workspace.name
};
if (this._isMounted) {
this.setState({syncData});
workspaceName: workspace.name
});
}
}
@ -90,29 +90,26 @@ class SyncDropdown extends PureComponent {
await this._reloadData();
}
async componentWillMount () {
this._interval = setInterval(this._reloadData, 500);
async componentDidMount () {
this._isMounted = true;
syncStorage.onChange(this._reloadData);
await this._reloadData();
}
componentDidMount () {
this._isMounted = true;
}
componentWillUnmount () {
clearInterval(this._interval);
syncStorage.offChange(this._reloadData);
this._isMounted = false;
}
async componentDidUpdate () {
const {syncData} = this.state;
const {resourceGroupId, syncMode} = this.state;
if (!syncData) {
if (!resourceGroupId) {
return;
}
// 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) {
this._hasPrompted = true;
await this._handleShowSyncModePrompt();
@ -138,14 +135,14 @@ class SyncDropdown extends PureComponent {
render () {
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
if (!loggedIn) {
return null;
}
if (!syncData) {
if (!resourceGroupId) {
return (
<div className={className}>
<button className="btn btn--compact wide" disabled>
@ -154,7 +151,7 @@ class SyncDropdown extends PureComponent {
</div>
);
} else {
const {syncMode, syncPercent} = syncData;
const {syncMode, syncPercent} = this.state;
return (
<div className={className}>
<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) {
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 () {
const {
searchString,
@ -219,7 +217,7 @@ class RequestSwitcherModal extends PureComponent {
const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type);
return (
<Modal ref={this._setModalRef} dontFocus tall>
<Modal ref={this._setModalRef} dontFocus tall onKeyDown={this._handleKeydown}>
<ModalHeader hideCloseButton>
<div className="pull-right txt-sm pad-right">
<span className="monospace">tab</span> or

View File

@ -7,7 +7,6 @@ class ResponseTimer extends PureComponent {
super(props);
this._interval = null;
this.state = {
show: false,
elapsedTime: 0
};
}
@ -16,24 +15,27 @@ class ResponseTimer extends PureComponent {
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(() => {
const {loadStartTime} = this.props;
if (loadStartTime > 0) {
// Show and update if needed
const millis = Date.now() - loadStartTime - 200;
const elapsedTime = Math.round(millis / 100) / 10;
this.setState({show: true, 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);
}
this.setState({elapsedTime});
}, 100);
}
render () {
const {handleCancel} = this.props;
const {show, elapsedTime} = this.state;
const {handleCancel, loadStartTime} = this.props;
const {elapsedTime} = this.state;
const show = loadStartTime > 0;
return (
<div className={classnames('overlay theme--overlay', {'overlay--hidden': !show})}>

View File

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

View File

@ -3,22 +3,13 @@
.modal {
// Hidden state
opacity: 0;
z-index: -999999; // Component updates this manually
transition: opacity 250ms;
position: absolute;
top: 0;
left: -99999px;
left: 0;
right: 0;
bottom: 0;
padding: @padding-lg;
&.modal--open {
opacity: 1;
left: 0;
}
&:focus {
outline: 0;
}
@ -33,15 +24,11 @@
}
.modal__content__wrapper {
top: 1rem;
width: @modal-width;
height: 100%;
max-width: 100%;
max-height: 100%;
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
pointer-events: none;
@ -51,11 +38,6 @@
height: 100%;
}
&.modal--open .modal__content__wrapper {
transform: scale(1);
top: 0;
}
&.modal--wide .modal__content__wrapper {
width: @modal-width-wide;
}