Disable UI rendering when history is skipped (#574)

When we are scrolling, resizing, or moving elements, we already disable the history. Since those actions do not change the state of the UI, we can also avoid re-drawing it and save ~10ms per frame.

I had to change all the forceUpdate() to setState({}), otherwise it would bypass shouldComponentUpdate.
This commit is contained in:
Christopher Chedeau 2020-01-26 19:08:46 +00:00 committed by GitHub
parent 263fef4eaa
commit 8ab176b9a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -180,7 +180,7 @@ export class App extends React.Component<any, AppState> {
private syncActionResult = (res: ActionResult) => { private syncActionResult = (res: ActionResult) => {
if (res.elements !== undefined) { if (res.elements !== undefined) {
elements = res.elements; elements = res.elements;
this.forceUpdate(); this.setState({});
} }
if (res.appState !== undefined) { if (res.appState !== undefined) {
@ -199,7 +199,7 @@ export class App extends React.Component<any, AppState> {
), ),
); );
elements = deleteSelectedElements(elements); elements = deleteSelectedElements(elements);
this.forceUpdate(); this.setState({});
e.preventDefault(); e.preventDefault();
}; };
private onCopy = (e: ClipboardEvent) => { private onCopy = (e: ClipboardEvent) => {
@ -226,6 +226,14 @@ export class App extends React.Component<any, AppState> {
this.saveDebounced.flush(); this.saveDebounced.flush();
}; };
public shouldComponentUpdate() {
if (!history.isRecording()) {
this.componentDidUpdate();
return false;
}
return true;
}
public async componentDidMount() { public async componentDidMount() {
document.addEventListener("copy", this.onCopy); document.addEventListener("copy", this.onCopy);
document.addEventListener("paste", this.onPaste); document.addEventListener("paste", this.onPaste);
@ -253,7 +261,7 @@ export class App extends React.Component<any, AppState> {
if (data.appState) { if (data.appState) {
this.setState(data.appState); this.setState(data.appState);
} else { } else {
this.forceUpdate(); this.setState({});
} }
} }
@ -275,7 +283,7 @@ export class App extends React.Component<any, AppState> {
public state: AppState = getDefaultAppState(); public state: AppState = getDefaultAppState();
private onResize = () => { private onResize = () => {
this.forceUpdate(); this.setState({});
}; };
private updateCurrentCursorPosition = (e: MouseEvent) => { private updateCurrentCursorPosition = (e: MouseEvent) => {
@ -286,7 +294,7 @@ export class App extends React.Component<any, AppState> {
private onKeyDown = (event: KeyboardEvent) => { private onKeyDown = (event: KeyboardEvent) => {
if (event.key === KEYS.ESCAPE && !this.state.draggingElement) { if (event.key === KEYS.ESCAPE && !this.state.draggingElement) {
elements = clearSelection(elements); elements = clearSelection(elements);
this.forceUpdate(); this.setState({});
this.setState({ elementType: "selection" }); this.setState({ elementType: "selection" });
if (window.document.activeElement instanceof HTMLElement) { if (window.document.activeElement instanceof HTMLElement) {
window.document.activeElement.blur(); window.document.activeElement.blur();
@ -320,7 +328,7 @@ export class App extends React.Component<any, AppState> {
} }
return el; return el;
}); });
this.forceUpdate(); this.setState({});
event.preventDefault(); event.preventDefault();
} else if ( } else if (
shapesShortcutKeys.includes(event.key.toLowerCase()) && shapesShortcutKeys.includes(event.key.toLowerCase()) &&
@ -505,7 +513,7 @@ export class App extends React.Component<any, AppState> {
elements = clearSelection(elements); elements = clearSelection(elements);
document.documentElement.style.cursor = document.documentElement.style.cursor =
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR; value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
this.forceUpdate(); this.setState({});
}} }}
></ToolButton> ></ToolButton>
); );
@ -695,7 +703,7 @@ export class App extends React.Component<any, AppState> {
if (!element.isSelected) { if (!element.isSelected) {
elements = clearSelection(elements); elements = clearSelection(elements);
element.isSelected = true; element.isSelected = true;
this.forceUpdate(); this.setState({});
} }
ContextMenu.push({ ContextMenu.push({
@ -737,6 +745,8 @@ export class App extends React.Component<any, AppState> {
let deltaY = lastY - e.clientY; let deltaY = lastY - e.clientY;
lastX = e.clientX; lastX = e.clientX;
lastY = e.clientY; lastY = e.clientY;
// We don't want to save history when panning around
history.skipRecording();
this.setState(state => ({ this.setState(state => ({
scrollX: state.scrollX - deltaX, scrollX: state.scrollX - deltaX,
scrollY: state.scrollY - deltaY, scrollY: state.scrollY - deltaY,
@ -941,6 +951,8 @@ export class App extends React.Component<any, AppState> {
if (isOverHorizontalScrollBar) { if (isOverHorizontalScrollBar) {
const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT; const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT;
const dx = x - lastX; const dx = x - lastX;
// We don't want to save history when scrolling
history.skipRecording();
this.setState(state => ({ scrollX: state.scrollX - dx })); this.setState(state => ({ scrollX: state.scrollX - dx }));
lastX = x; lastX = x;
return; return;
@ -949,6 +961,8 @@ export class App extends React.Component<any, AppState> {
if (isOverVerticalScrollBar) { if (isOverVerticalScrollBar) {
const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP; const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP;
const dy = y - lastY; const dy = y - lastY;
// We don't want to save history when scrolling
history.skipRecording();
this.setState(state => ({ scrollY: state.scrollY - dy })); this.setState(state => ({ scrollY: state.scrollY - dy }));
lastY = y; lastY = y;
return; return;
@ -1051,7 +1065,7 @@ export class App extends React.Component<any, AppState> {
lastY = y; lastY = y;
// We don't want to save history when resizing an element // We don't want to save history when resizing an element
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
return; return;
} }
} }
@ -1072,7 +1086,7 @@ export class App extends React.Component<any, AppState> {
lastY = y; lastY = y;
// We don't want to save history when dragging an element to initially size it // We don't want to save history when dragging an element to initially size it
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
return; return;
} }
} }
@ -1127,7 +1141,7 @@ export class App extends React.Component<any, AppState> {
} }
// We don't want to save history when moving an element // We don't want to save history when moving an element
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
}; };
const onMouseUp = (e: MouseEvent) => { const onMouseUp = (e: MouseEvent) => {
@ -1152,12 +1166,11 @@ export class App extends React.Component<any, AppState> {
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
}); });
this.forceUpdate();
return; return;
} }
if (normalizeDimensions(draggingElement)) { if (normalizeDimensions(draggingElement)) {
this.forceUpdate(); this.setState({});
} }
if (resizingElement && isInvisiblySmallElement(resizingElement)) { if (resizingElement && isInvisiblySmallElement(resizingElement)) {
@ -1188,7 +1201,7 @@ export class App extends React.Component<any, AppState> {
if (draggingElement === null) { if (draggingElement === null) {
// if no element is clicked, clear the selection and redraw // if no element is clicked, clear the selection and redraw
elements = clearSelection(elements); elements = clearSelection(elements);
this.forceUpdate(); this.setState({});
return; return;
} }
@ -1208,7 +1221,7 @@ export class App extends React.Component<any, AppState> {
} }
history.resumeRecording(); history.resumeRecording();
this.forceUpdate(); this.setState({});
}; };
lastMouseUp = onMouseUp; lastMouseUp = onMouseUp;
@ -1218,7 +1231,7 @@ export class App extends React.Component<any, AppState> {
// We don't want to save history on mouseDown, only on mouseUp when it's fully configured // We don't want to save history on mouseDown, only on mouseUp when it's fully configured
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
}} }}
onDoubleClick={e => { onDoubleClick={e => {
const { x, y } = viewportCoordsToSceneCoords(e, this.state); const { x, y } = viewportCoordsToSceneCoords(e, this.state);
@ -1253,7 +1266,7 @@ export class App extends React.Component<any, AppState> {
elements = elements.filter( elements = elements.filter(
element => element.id !== elementAtPosition.id, element => element.id !== elementAtPosition.id,
); );
this.forceUpdate(); this.setState({});
textX = textX =
this.state.scrollX + this.state.scrollX +
@ -1355,6 +1368,8 @@ export class App extends React.Component<any, AppState> {
private handleWheel = (e: WheelEvent) => { private handleWheel = (e: WheelEvent) => {
e.preventDefault(); e.preventDefault();
const { deltaX, deltaY } = e; const { deltaX, deltaY } = e;
// We don't want to save history when panning around
history.skipRecording();
this.setState(state => ({ this.setState(state => ({
scrollX: state.scrollX - deltaX, scrollX: state.scrollX - deltaX,
scrollY: state.scrollY - deltaY, scrollY: state.scrollY - deltaY,
@ -1412,7 +1427,7 @@ export class App extends React.Component<any, AppState> {
return duplicate; return duplicate;
}), }),
]; ];
this.forceUpdate(); this.setState({});
} }
}; };