remove most setState({}) (#959)

This commit is contained in:
Pete Hunt 2020-03-15 10:06:41 -07:00 committed by GitHub
parent e1e2249f57
commit 35ce1729cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 95 deletions

View File

@ -27,10 +27,10 @@ import {
getElementsWithinSelection, getElementsWithinSelection,
isOverScrollBars, isOverScrollBars,
getElementAtPosition, getElementAtPosition,
createScene,
getElementContainingPosition, getElementContainingPosition,
getNormalizedZoom, getNormalizedZoom,
getSelectedElements, getSelectedElements,
globalSceneState,
isSomeElementSelected, isSomeElementSelected,
} from "../scene"; } from "../scene";
import { import {
@ -117,12 +117,10 @@ if (process.env.NODE_ENV === "test") {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const scene = createScene();
if (process.env.NODE_ENV === "test") { if (process.env.NODE_ENV === "test") {
Object.defineProperty(window.__TEST__, "elements", { Object.defineProperty(window.__TEST__, "elements", {
get() { get() {
return scene.getAllElements(); return globalSceneState.getAllElements();
}, },
}); });
} }
@ -169,7 +167,7 @@ export class App extends React.Component<any, AppState> {
this.actionManager = new ActionManager( this.actionManager = new ActionManager(
this.syncActionResult, this.syncActionResult,
() => this.state, () => this.state,
() => scene.getAllElements(), () => globalSceneState.getAllElements(),
); );
this.actionManager.registerAll(actions); this.actionManager.registerAll(actions);
@ -177,11 +175,6 @@ export class App extends React.Component<any, AppState> {
this.actionManager.registerAction(createRedoAction(history)); this.actionManager.registerAction(createRedoAction(history));
} }
private replaceElements = (nextElements: readonly ExcalidrawElement[]) => {
scene.replaceAllElements(nextElements);
this.setState({});
};
private syncActionResult = ( private syncActionResult = (
res: ActionResult, res: ActionResult,
commitToHistory: boolean = true, commitToHistory: boolean = true,
@ -190,7 +183,7 @@ export class App extends React.Component<any, AppState> {
return; return;
} }
if (res.elements) { if (res.elements) {
this.replaceElements(res.elements); globalSceneState.replaceAllElements(res.elements);
if (commitToHistory) { if (commitToHistory) {
history.resumeRecording(); history.resumeRecording();
} }
@ -212,12 +205,12 @@ export class App extends React.Component<any, AppState> {
if (isWritableElement(event.target)) { if (isWritableElement(event.target)) {
return; return;
} }
copyToAppClipboard(scene.getAllElements(), this.state); copyToAppClipboard(globalSceneState.getAllElements(), this.state);
const { elements: nextElements, appState } = deleteSelectedElements( const { elements: nextElements, appState } = deleteSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
this.replaceElements(nextElements); globalSceneState.replaceAllElements(nextElements);
history.resumeRecording(); history.resumeRecording();
this.setState({ ...appState }); this.setState({ ...appState });
event.preventDefault(); event.preventDefault();
@ -226,7 +219,7 @@ export class App extends React.Component<any, AppState> {
if (isWritableElement(event.target)) { if (isWritableElement(event.target)) {
return; return;
} }
copyToAppClipboard(scene.getAllElements(), this.state); copyToAppClipboard(globalSceneState.getAllElements(), this.state);
event.preventDefault(); event.preventDefault();
}; };
@ -292,17 +285,19 @@ export class App extends React.Component<any, AppState> {
// elements with more staler versions than ours, ignore them // elements with more staler versions than ours, ignore them
// and keep ours. // and keep ours.
if ( if (
scene.getAllElements() == null || globalSceneState.getAllElements() == null ||
scene.getAllElements().length === 0 globalSceneState.getAllElements().length === 0
) { ) {
this.replaceElements(restoredState.elements); globalSceneState.replaceAllElements(restoredState.elements);
} else { } else {
// create a map of ids so we don't have to iterate // create a map of ids so we don't have to iterate
// over the array more than once. // over the array more than once.
const localElementMap = getElementMap(scene.getAllElements()); const localElementMap = getElementMap(
globalSceneState.getAllElements(),
);
// Reconcile // Reconcile
this.replaceElements( globalSceneState.replaceAllElements(
restoredState.elements restoredState.elements
.reduce((elements, element) => { .reduce((elements, element) => {
// if the remote element references one that's currently // if the remote element references one that's currently
@ -353,7 +348,7 @@ export class App extends React.Component<any, AppState> {
); );
} }
this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion( this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
scene.getAllElements(), globalSceneState.getAllElements(),
); );
// We haven't yet implemented multiplayer undo functionality, so we clear the undo stack // We haven't yet implemented multiplayer undo functionality, so we clear the undo stack
// when we receive any messages from another peer. This UX can be pretty rough -- if you // when we receive any messages from another peer. This UX can be pretty rough -- if you
@ -428,12 +423,12 @@ export class App extends React.Component<any, AppState> {
const data: SocketUpdateDataSource["SCENE_UPDATE"] = { const data: SocketUpdateDataSource["SCENE_UPDATE"] = {
type: "SCENE_UPDATE", type: "SCENE_UPDATE",
payload: { payload: {
elements: getSyncableElements(scene.getAllElements()), elements: getSyncableElements(globalSceneState.getAllElements()),
}, },
}; };
this.lastBroadcastedOrReceivedSceneVersion = Math.max( this.lastBroadcastedOrReceivedSceneVersion = Math.max(
this.lastBroadcastedOrReceivedSceneVersion, this.lastBroadcastedOrReceivedSceneVersion,
getDrawingVersion(scene.getAllElements()), getDrawingVersion(globalSceneState.getAllElements()),
); );
return this._broadcastSocketData( return this._broadcastSocketData(
data as typeof data & { _brand: "socketUpdateData" }, data as typeof data & { _brand: "socketUpdateData" },
@ -459,6 +454,10 @@ export class App extends React.Component<any, AppState> {
} }
} }
private handleSceneCallback = () => {
this.setState({});
};
private unmounted = false; private unmounted = false;
public async componentDidMount() { public async componentDidMount() {
if (process.env.NODE_ENV === "test") { if (process.env.NODE_ENV === "test") {
@ -470,6 +469,8 @@ export class App extends React.Component<any, AppState> {
}); });
} }
globalSceneState.addCallback(this.handleSceneCallback);
document.addEventListener("copy", this.onCopy); document.addEventListener("copy", this.onCopy);
document.addEventListener("paste", this.pasteFromClipboard); document.addEventListener("paste", this.pasteFromClipboard);
document.addEventListener("cut", this.onCut); document.addEventListener("cut", this.onCut);
@ -558,7 +559,7 @@ export class App extends React.Component<any, AppState> {
public state: AppState = getDefaultAppState(); public state: AppState = getDefaultAppState();
private onResize = () => { private onResize = () => {
scene globalSceneState
.getAllElements() .getAllElements()
.forEach(element => invalidateShapeForElement(element)); .forEach(element => invalidateShapeForElement(element));
this.setState({}); this.setState({});
@ -594,8 +595,8 @@ export class App extends React.Component<any, AppState> {
const step = event.shiftKey const step = event.shiftKey
? ELEMENT_SHIFT_TRANSLATE_AMOUNT ? ELEMENT_SHIFT_TRANSLATE_AMOUNT
: ELEMENT_TRANSLATE_AMOUNT; : ELEMENT_TRANSLATE_AMOUNT;
this.replaceElements( globalSceneState.replaceAllElements(
scene.getAllElements().map(el => { globalSceneState.getAllElements().map(el => {
if (this.state.selectedElementIds[el.id]) { if (this.state.selectedElementIds[el.id]) {
const update: { x?: number; y?: number } = {}; const update: { x?: number; y?: number } = {};
if (event.key === KEYS.ARROW_LEFT) { if (event.key === KEYS.ARROW_LEFT) {
@ -643,17 +644,19 @@ export class App extends React.Component<any, AppState> {
}; };
private copyToAppClipboard = () => { private copyToAppClipboard = () => {
copyToAppClipboard(scene.getAllElements(), this.state); copyToAppClipboard(globalSceneState.getAllElements(), this.state);
}; };
private copyToClipboardAsPng = () => { private copyToClipboardAsPng = () => {
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
exportCanvas( exportCanvas(
"clipboard", "clipboard",
selectedElements.length ? selectedElements : scene.getAllElements(), selectedElements.length
? selectedElements
: globalSceneState.getAllElements(),
this.state, this.state,
this.canvas!, this.canvas!,
this.state, this.state,
@ -697,7 +700,10 @@ export class App extends React.Component<any, AppState> {
this.state.currentItemFont, this.state.currentItemFont,
); );
this.replaceElements([...scene.getAllElements(), element]); globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
element,
]);
this.setState({ selectedElementIds: { [element.id]: true } }); this.setState({ selectedElementIds: { [element.id]: true } });
history.resumeRecording(); history.resumeRecording();
} }
@ -758,6 +764,10 @@ export class App extends React.Component<any, AppState> {
this.destroySocketClient(); this.destroySocketClient();
}; };
private setElements = (elements: readonly ExcalidrawElement[]) => {
globalSceneState.replaceAllElements(elements);
};
public render() { public render() {
const canvasDOMWidth = window.innerWidth; const canvasDOMWidth = window.innerWidth;
const canvasDOMHeight = window.innerHeight; const canvasDOMHeight = window.innerHeight;
@ -774,8 +784,8 @@ export class App extends React.Component<any, AppState> {
appState={this.state} appState={this.state}
setAppState={this.setAppState} setAppState={this.setAppState}
actionManager={this.actionManager} actionManager={this.actionManager}
elements={scene.getAllElements()} elements={globalSceneState.getAllElements()}
setElements={this.replaceElements} setElements={this.setElements}
language={getLanguage()} language={getLanguage()}
onRoomCreate={this.createRoom} onRoomCreate={this.createRoom}
onRoomDestroy={this.destroyRoom} onRoomDestroy={this.destroyRoom}
@ -816,7 +826,7 @@ export class App extends React.Component<any, AppState> {
); );
const element = getElementAtPosition( const element = getElementAtPosition(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
x, x,
y, y,
@ -830,7 +840,9 @@ export class App extends React.Component<any, AppState> {
action: () => this.pasteFromClipboard(null), action: () => this.pasteFromClipboard(null),
}, },
probablySupportsClipboardBlob && probablySupportsClipboardBlob &&
hasNonDeletedElements(scene.getAllElements()) && { hasNonDeletedElements(
globalSceneState.getAllElements(),
) && {
label: t("labels.copyAsPng"), label: t("labels.copyAsPng"),
action: this.copyToClipboardAsPng, action: this.copyToClipboardAsPng,
}, },
@ -914,7 +926,7 @@ export class App extends React.Component<any, AppState> {
); );
const elementAtPosition = getElementAtPosition( const elementAtPosition = getElementAtPosition(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
x, x,
y, y,
@ -946,8 +958,8 @@ export class App extends React.Component<any, AppState> {
let textY = event.clientY; let textY = event.clientY;
if (elementAtPosition && isTextElement(elementAtPosition)) { if (elementAtPosition && isTextElement(elementAtPosition)) {
this.replaceElements( globalSceneState.replaceAllElements(
scene globalSceneState
.getAllElements() .getAllElements()
.filter(element => element.id !== elementAtPosition.id), .filter(element => element.id !== elementAtPosition.id),
); );
@ -1005,8 +1017,8 @@ export class App extends React.Component<any, AppState> {
zoom: this.state.zoom, zoom: this.state.zoom,
onSubmit: text => { onSubmit: text => {
if (text) { if (text) {
this.replaceElements([ globalSceneState.replaceAllElements([
...scene.getAllElements(), ...globalSceneState.getAllElements(),
{ {
// we need to recreate the element to update dimensions & // we need to recreate the element to update dimensions &
// position // position
@ -1094,8 +1106,6 @@ export class App extends React.Component<any, AppState> {
mutateElement(multiElement, { mutateElement(multiElement, {
points: [...points.slice(0, -1), [x - originX, y - originY]], points: [...points.slice(0, -1), [x - originX, y - originY]],
}); });
this.setState({});
return; return;
} }
@ -1105,12 +1115,12 @@ export class App extends React.Component<any, AppState> {
} }
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
if (selectedElements.length === 1 && !isOverScrollBar) { if (selectedElements.length === 1 && !isOverScrollBar) {
const resizeElement = getElementWithResizeHandler( const resizeElement = getElementWithResizeHandler(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
{ x, y }, { x, y },
this.state.zoom, this.state.zoom,
@ -1124,7 +1134,7 @@ export class App extends React.Component<any, AppState> {
} }
} }
const hitElement = getElementAtPosition( const hitElement = getElementAtPosition(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
x, x,
y, y,
@ -1316,7 +1326,7 @@ export class App extends React.Component<any, AppState> {
let elementIsAddedToSelection = false; let elementIsAddedToSelection = false;
if (this.state.elementType === "selection") { if (this.state.elementType === "selection") {
const resizeElement = getElementWithResizeHandler( const resizeElement = getElementWithResizeHandler(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
{ x, y }, { x, y },
this.state.zoom, this.state.zoom,
@ -1324,7 +1334,7 @@ export class App extends React.Component<any, AppState> {
); );
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
if (selectedElements.length === 1 && resizeElement) { if (selectedElements.length === 1 && resizeElement) {
@ -1339,7 +1349,7 @@ export class App extends React.Component<any, AppState> {
isResizingElements = true; isResizingElements = true;
} else { } else {
hitElement = getElementAtPosition( hitElement = getElementAtPosition(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
x, x,
y, y,
@ -1366,7 +1376,9 @@ export class App extends React.Component<any, AppState> {
[hitElement!.id]: true, [hitElement!.id]: true,
}, },
})); }));
this.replaceElements(scene.getAllElements()); globalSceneState.replaceAllElements(
globalSceneState.getAllElements(),
);
elementIsAddedToSelection = true; elementIsAddedToSelection = true;
} }
@ -1376,7 +1388,7 @@ export class App extends React.Component<any, AppState> {
// put the duplicates where the selected elements used to be. // put the duplicates where the selected elements used to be.
const nextElements = []; const nextElements = [];
const elementsToAppend = []; const elementsToAppend = [];
for (const element of scene.getAllElements()) { for (const element of globalSceneState.getAllElements()) {
if (this.state.selectedElementIds[element.id]) { if (this.state.selectedElementIds[element.id]) {
nextElements.push(duplicateElement(element)); nextElements.push(duplicateElement(element));
elementsToAppend.push(element); elementsToAppend.push(element);
@ -1384,7 +1396,10 @@ export class App extends React.Component<any, AppState> {
nextElements.push(element); nextElements.push(element);
} }
} }
this.replaceElements([...nextElements, ...elementsToAppend]); globalSceneState.replaceAllElements([
...nextElements,
...elementsToAppend,
]);
} }
} }
} }
@ -1434,8 +1449,8 @@ export class App extends React.Component<any, AppState> {
zoom: this.state.zoom, zoom: this.state.zoom,
onSubmit: text => { onSubmit: text => {
if (text) { if (text) {
this.replaceElements([ globalSceneState.replaceAllElements([
...scene.getAllElements(), ...globalSceneState.getAllElements(),
{ {
...newTextElement(element, text, this.state.currentItemFont), ...newTextElement(element, text, this.state.currentItemFont),
}, },
@ -1495,7 +1510,10 @@ export class App extends React.Component<any, AppState> {
mutateElement(element, { mutateElement(element, {
points: [...element.points, [0, 0]], points: [...element.points, [0, 0]],
}); });
this.replaceElements([...scene.getAllElements(), element]); globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
element,
]);
this.setState({ this.setState({
draggingElement: element, draggingElement: element,
editingElement: element, editingElement: element,
@ -1507,7 +1525,10 @@ export class App extends React.Component<any, AppState> {
draggingElement: element, draggingElement: element,
}); });
} else { } else {
this.replaceElements([...scene.getAllElements(), element]); globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
element,
]);
this.setState({ this.setState({
multiElement: null, multiElement: null,
draggingElement: element, draggingElement: element,
@ -1646,7 +1667,7 @@ export class App extends React.Component<any, AppState> {
this.setState({ isResizing: true }); this.setState({ isResizing: true });
const el = this.state.resizingElement; const el = this.state.resizingElement;
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
if (selectedElements.length === 1) { if (selectedElements.length === 1) {
@ -1853,7 +1874,6 @@ export class App extends React.Component<any, AppState> {
lastX = x; lastX = x;
lastY = y; lastY = y;
this.setState({});
return; return;
} }
} }
@ -1863,7 +1883,7 @@ export class App extends React.Component<any, AppState> {
// if elements should be deselected on pointerup // if elements should be deselected on pointerup
draggingOccurred = true; draggingOccurred = true;
const selectedElements = getSelectedElements( const selectedElements = getSelectedElements(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
); );
if (selectedElements.length > 0) { if (selectedElements.length > 0) {
@ -1881,7 +1901,6 @@ export class App extends React.Component<any, AppState> {
}); });
lastX = x; lastX = x;
lastY = y; lastY = y;
this.setState({});
return; return;
} }
} }
@ -1950,12 +1969,12 @@ export class App extends React.Component<any, AppState> {
if (this.state.elementType === "selection") { if (this.state.elementType === "selection") {
if ( if (
!event.shiftKey && !event.shiftKey &&
isSomeElementSelected(scene.getAllElements(), this.state) isSomeElementSelected(globalSceneState.getAllElements(), this.state)
) { ) {
this.setState({ selectedElementIds: {} }); this.setState({ selectedElementIds: {} });
} }
const elementsWithinSelection = getElementsWithinSelection( const elementsWithinSelection = getElementsWithinSelection(
scene.getAllElements(), globalSceneState.getAllElements(),
draggingElement, draggingElement,
); );
this.setState(prevState => ({ this.setState(prevState => ({
@ -1968,7 +1987,6 @@ export class App extends React.Component<any, AppState> {
}, },
})); }));
} }
this.setState({});
}; };
const onPointerUp = (event: PointerEvent) => { const onPointerUp = (event: PointerEvent) => {
@ -1995,7 +2013,6 @@ export class App extends React.Component<any, AppState> {
if (elementType === "arrow" || elementType === "line") { if (elementType === "arrow" || elementType === "line") {
if (draggingElement!.points.length > 1) { if (draggingElement!.points.length > 1) {
history.resumeRecording(); history.resumeRecording();
this.setState({});
} }
if (!draggingOccurred && draggingElement && !multiElement) { if (!draggingOccurred && draggingElement && !multiElement) {
const { x, y } = viewportCoordsToSceneCoords( const { x, y } = viewportCoordsToSceneCoords(
@ -2043,25 +2060,26 @@ export class App extends React.Component<any, AppState> {
isInvisiblySmallElement(draggingElement) isInvisiblySmallElement(draggingElement)
) { ) {
// remove invisible element which was added in onPointerDown // remove invisible element which was added in onPointerDown
this.replaceElements(scene.getAllElements().slice(0, -1)); globalSceneState.replaceAllElements(
globalSceneState.getAllElements().slice(0, -1),
);
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
}); });
return; return;
} }
if (normalizeDimensions(draggingElement)) { normalizeDimensions(draggingElement);
this.setState({});
}
if (resizingElement) { if (resizingElement) {
history.resumeRecording(); history.resumeRecording();
this.setState({});
} }
if (resizingElement && isInvisiblySmallElement(resizingElement)) { if (resizingElement && isInvisiblySmallElement(resizingElement)) {
this.replaceElements( globalSceneState.replaceAllElements(
scene.getAllElements().filter(el => el.id !== resizingElement.id), globalSceneState
.getAllElements()
.filter(el => el.id !== resizingElement.id),
); );
} }
@ -2105,7 +2123,7 @@ export class App extends React.Component<any, AppState> {
if ( if (
elementType !== "selection" || elementType !== "selection" ||
isSomeElementSelected(scene.getAllElements(), this.state) isSomeElementSelected(globalSceneState.getAllElements(), this.state)
) { ) {
history.resumeRecording(); history.resumeRecording();
} }
@ -2178,7 +2196,10 @@ export class App extends React.Component<any, AppState> {
return duplicate; return duplicate;
}); });
this.replaceElements([...scene.getAllElements(), ...newElements]); globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...newElements,
]);
history.resumeRecording(); history.resumeRecording();
this.setState({ this.setState({
selectedElementIds: newElements.reduce((map, element) => { selectedElementIds: newElements.reduce((map, element) => {
@ -2190,7 +2211,7 @@ export class App extends React.Component<any, AppState> {
private getTextWysiwygSnappedToCenterPosition(x: number, y: number) { private getTextWysiwygSnappedToCenterPosition(x: number, y: number) {
const elementClickedInside = getElementContainingPosition( const elementClickedInside = getElementContainingPosition(
scene.getAllElements(), globalSceneState.getAllElements(),
x, x,
y, y,
); );
@ -2228,7 +2249,7 @@ export class App extends React.Component<any, AppState> {
}; };
private saveDebounced = debounce(() => { private saveDebounced = debounce(() => {
saveToLocalStorage(scene.getAllElements(), this.state); saveToLocalStorage(globalSceneState.getAllElements(), this.state);
}, 300); }, 300);
componentDidUpdate() { componentDidUpdate() {
@ -2252,7 +2273,7 @@ export class App extends React.Component<any, AppState> {
); );
}); });
const { atLeastOneVisibleElement, scrollBars } = renderScene( const { atLeastOneVisibleElement, scrollBars } = renderScene(
scene.getAllElements(), globalSceneState.getAllElements(),
this.state, this.state,
this.state.selectionElement, this.state.selectionElement,
window.devicePixelRatio, window.devicePixelRatio,
@ -2274,21 +2295,21 @@ export class App extends React.Component<any, AppState> {
} }
const scrolledOutside = const scrolledOutside =
!atLeastOneVisibleElement && !atLeastOneVisibleElement &&
hasNonDeletedElements(scene.getAllElements()); hasNonDeletedElements(globalSceneState.getAllElements());
if (this.state.scrolledOutside !== scrolledOutside) { if (this.state.scrolledOutside !== scrolledOutside) {
this.setState({ scrolledOutside: scrolledOutside }); this.setState({ scrolledOutside: scrolledOutside });
} }
this.saveDebounced(); this.saveDebounced();
if ( if (
getDrawingVersion(scene.getAllElements()) > getDrawingVersion(globalSceneState.getAllElements()) >
this.lastBroadcastedOrReceivedSceneVersion this.lastBroadcastedOrReceivedSceneVersion
) { ) {
this.broadcastSceneUpdate(); this.broadcastSceneUpdate();
} }
if (history.isRecording()) { if (history.isRecording()) {
history.pushEntry(this.state, scene.getAllElements()); history.pushEntry(this.state, globalSceneState.getAllElements());
history.skipRecording(); history.skipRecording();
} }
} }

View File

@ -1,6 +1,7 @@
import { ExcalidrawElement } from "./types"; import { ExcalidrawElement } from "./types";
import { randomSeed } from "roughjs/bin/math"; import { randomSeed } from "roughjs/bin/math";
import { invalidateShapeForElement } from "../renderer/renderElement"; import { invalidateShapeForElement } from "../renderer/renderElement";
import { globalSceneState } from "../scene";
type ElementUpdate<TElement extends ExcalidrawElement> = Omit< type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
Partial<TElement>, Partial<TElement>,
@ -33,6 +34,8 @@ export function mutateElement<TElement extends ExcalidrawElement>(
mutableElement.version++; mutableElement.version++;
mutableElement.versionNonce = randomSeed(); mutableElement.versionNonce = randomSeed();
globalSceneState.informMutation();
} }
export function newElementWith<TElement extends ExcalidrawElement>( export function newElementWith<TElement extends ExcalidrawElement>(

View File

@ -1,17 +0,0 @@
import { ExcalidrawElement } from "../element/types";
class SceneState {
constructor(private _elements: readonly ExcalidrawElement[] = []) {}
getAllElements() {
return this._elements;
}
replaceAllElements(nextElements: readonly ExcalidrawElement[]) {
this._elements = nextElements;
}
}
export const createScene = () => {
return new SceneState();
};

47
src/scene/globalScene.ts Normal file
View File

@ -0,0 +1,47 @@
import { ExcalidrawElement } from "../element/types";
export interface SceneStateCallback {
(): void;
}
export interface SceneStateCallbackRemover {
(): void;
}
class SceneState {
private callbacks: Set<SceneStateCallback> = new Set();
constructor(private _elements: readonly ExcalidrawElement[] = []) {}
getAllElements() {
return this._elements;
}
replaceAllElements(nextElements: readonly ExcalidrawElement[]) {
this._elements = nextElements;
this.informMutation();
}
informMutation() {
for (const callback of Array.from(this.callbacks)) {
callback();
}
}
addCallback(cb: SceneStateCallback): SceneStateCallbackRemover {
if (this.callbacks.has(cb)) {
throw new Error();
}
this.callbacks.add(cb);
return () => {
if (!this.callbacks.has(cb)) {
throw new Error();
}
this.callbacks.delete(cb);
};
}
}
export const globalSceneState = new SceneState();

View File

@ -16,5 +16,5 @@ export {
getElementContainingPosition, getElementContainingPosition,
hasText, hasText,
} from "./comparisons"; } from "./comparisons";
export { createScene } from "./createScene";
export { getZoomOrigin, getNormalizedZoom } from "./zoom"; export { getZoomOrigin, getNormalizedZoom } from "./zoom";
export { globalSceneState } from "./globalScene";