remove shared global scene and attach it to every instance (#1706)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
54f8d8f820
commit
20500b7822
@ -9,7 +9,6 @@ import {
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { globalSceneState } from "../scene";
|
|
||||||
|
|
||||||
export class ActionManager implements ActionsManagerInterface {
|
export class ActionManager implements ActionsManagerInterface {
|
||||||
actions = {} as ActionsManagerInterface["actions"];
|
actions = {} as ActionsManagerInterface["actions"];
|
||||||
@ -23,9 +22,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
constructor(
|
constructor(
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
getAppState: () => AppState,
|
getAppState: () => AppState,
|
||||||
getElementsIncludingDeleted: () => ReturnType<
|
getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
|
||||||
typeof globalSceneState["getElementsIncludingDeleted"]
|
|
||||||
>,
|
|
||||||
) {
|
) {
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
this.getAppState = getAppState;
|
this.getAppState = getAppState;
|
||||||
|
@ -39,7 +39,6 @@ import {
|
|||||||
getElementContainingPosition,
|
getElementContainingPosition,
|
||||||
getNormalizedZoom,
|
getNormalizedZoom,
|
||||||
getSelectedElements,
|
getSelectedElements,
|
||||||
globalSceneState,
|
|
||||||
isSomeElementSelected,
|
isSomeElementSelected,
|
||||||
calculateScrollCenter,
|
calculateScrollCenter,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
@ -137,7 +136,6 @@ import { generateCollaborationLink, getCollaborationLinkData } from "../data";
|
|||||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
import { SceneStateCallbackRemover } from "../scene/globalScene";
|
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
import { actionFinalize, actionDeleteSelected } from "../actions";
|
import { actionFinalize, actionDeleteSelected } from "../actions";
|
||||||
import {
|
import {
|
||||||
@ -155,6 +153,7 @@ import {
|
|||||||
getSelectedGroupIdForElement,
|
getSelectedGroupIdForElement,
|
||||||
} from "../groups";
|
} from "../groups";
|
||||||
import { Library } from "../data/library";
|
import { Library } from "../data/library";
|
||||||
|
import Scene from "../scene/Scene";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param func handler taking at most single parameter (event).
|
* @param func handler taking at most single parameter (event).
|
||||||
@ -243,7 +242,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
portal: Portal = new Portal(this);
|
portal: Portal = new Portal(this);
|
||||||
lastBroadcastedOrReceivedSceneVersion: number = -1;
|
lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||||
broadcastedElementVersions: Map<string, number> = new Map();
|
broadcastedElementVersions: Map<string, number> = new Map();
|
||||||
removeSceneCallback: SceneStateCallbackRemover | null = null;
|
|
||||||
unmounted: boolean = false;
|
unmounted: boolean = false;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
private excalidrawRef: any;
|
private excalidrawRef: any;
|
||||||
@ -252,6 +250,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
};
|
};
|
||||||
|
private scene: Scene;
|
||||||
|
|
||||||
constructor(props: ExcalidrawProps) {
|
constructor(props: ExcalidrawProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -266,11 +265,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
...this.getCanvasOffsets(),
|
...this.getCanvasOffsets(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.scene = new Scene();
|
||||||
this.excalidrawRef = React.createRef();
|
this.excalidrawRef = React.createRef();
|
||||||
this.actionManager = new ActionManager(
|
this.actionManager = new ActionManager(
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
() => this.state,
|
() => this.state,
|
||||||
() => globalSceneState.getElementsIncludingDeleted(),
|
() => this.scene.getElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
this.actionManager.registerAll(actions);
|
this.actionManager.registerAll(actions);
|
||||||
|
|
||||||
@ -308,7 +308,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
appState={this.state}
|
appState={this.state}
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
actionManager={this.actionManager}
|
actionManager={this.actionManager}
|
||||||
elements={globalSceneState.getElements()}
|
elements={this.scene.getElements()}
|
||||||
onRoomCreate={this.openPortal}
|
onRoomCreate={this.openPortal}
|
||||||
onRoomDestroy={this.closePortal}
|
onRoomDestroy={this.closePortal}
|
||||||
onUsernameChange={(username) => {
|
onUsernameChange={(username) => {
|
||||||
@ -368,7 +368,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
editingElement = element;
|
editingElement = element;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
globalSceneState.replaceAllElements(actionResult.elements);
|
this.scene.replaceAllElements(actionResult.elements);
|
||||||
if (actionResult.commitToHistory) {
|
if (actionResult.commitToHistory) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
}
|
}
|
||||||
@ -394,7 +394,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
if (actionResult.syncHistory) {
|
if (actionResult.syncHistory) {
|
||||||
history.setCurrentState(
|
history.setCurrentState(
|
||||||
this.state,
|
this.state,
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -421,7 +421,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onFontLoaded = () => {
|
private onFontLoaded = () => {
|
||||||
globalSceneState.getElementsIncludingDeleted().forEach((element) => {
|
this.scene.getElementsIncludingDeleted().forEach((element) => {
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
invalidateShapeForElement(element);
|
invalidateShapeForElement(element);
|
||||||
}
|
}
|
||||||
@ -562,9 +562,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removeSceneCallback = globalSceneState.addCallback(
|
this.scene.addCallback(this.onSceneUpdated);
|
||||||
this.onSceneUpdated,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
this.setState(this.getCanvasOffsets(), () => {
|
this.setState(this.getCanvasOffsets(), () => {
|
||||||
@ -574,14 +572,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.removeSceneCallback!();
|
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
|
this.scene.destroy();
|
||||||
clearTimeout(touchTimeout);
|
clearTimeout(touchTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onResize = withBatchedUpdates(() => {
|
private onResize = withBatchedUpdates(() => {
|
||||||
globalSceneState
|
this.scene
|
||||||
.getElementsIncludingDeleted()
|
.getElementsIncludingDeleted()
|
||||||
.forEach((element) => invalidateShapeForElement(element));
|
.forEach((element) => invalidateShapeForElement(element));
|
||||||
this.setState({});
|
this.setState({});
|
||||||
@ -682,10 +679,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
if (
|
if (this.state.isCollaborating && this.scene.getElements().length > 0) {
|
||||||
this.state.isCollaborating &&
|
|
||||||
globalSceneState.getElements().length > 0
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// NOTE: modern browsers no longer allow showing a custom message here
|
// NOTE: modern browsers no longer allow showing a custom message here
|
||||||
event.returnValue = "";
|
event.returnValue = "";
|
||||||
@ -753,7 +747,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
);
|
);
|
||||||
cursorButton[socketID] = user.button;
|
cursorButton[socketID] = user.button;
|
||||||
});
|
});
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
const { atLeastOneVisibleElement, scrollBars } = renderScene(
|
const { atLeastOneVisibleElement, scrollBars } = renderScene(
|
||||||
elements.filter((element) => {
|
elements.filter((element) => {
|
||||||
// don't render text element that's being currently edited (it's
|
// don't render text element that's being currently edited (it's
|
||||||
@ -798,14 +792,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.saveDebounced();
|
this.saveDebounced();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) >
|
getDrawingVersion(this.scene.getElementsIncludingDeleted()) >
|
||||||
this.lastBroadcastedOrReceivedSceneVersion
|
this.lastBroadcastedOrReceivedSceneVersion
|
||||||
) {
|
) {
|
||||||
this.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
|
this.broadcastScene(SCENE.UPDATE, /* syncAll */ false);
|
||||||
this.queueBroadcastAllElements();
|
this.queueBroadcastAllElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
history.record(this.state, globalSceneState.getElementsIncludingDeleted());
|
history.record(this.state, this.scene.getElementsIncludingDeleted());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy/paste
|
// Copy/paste
|
||||||
@ -828,11 +822,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
private copyAll = () => {
|
private copyAll = () => {
|
||||||
copyToAppClipboard(globalSceneState.getElements(), this.state);
|
copyToAppClipboard(this.scene.getElements(), this.state);
|
||||||
};
|
};
|
||||||
|
|
||||||
private copyToClipboardAsPng = () => {
|
private copyToClipboardAsPng = () => {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(elements, this.state);
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
exportCanvas(
|
exportCanvas(
|
||||||
@ -846,14 +840,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
private copyToClipboardAsSvg = () => {
|
private copyToClipboardAsSvg = () => {
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
exportCanvas(
|
exportCanvas(
|
||||||
"clipboard-svg",
|
"clipboard-svg",
|
||||||
selectedElements.length
|
selectedElements.length ? selectedElements : this.scene.getElements(),
|
||||||
? selectedElements
|
|
||||||
: globalSceneState.getElements(),
|
|
||||||
this.state,
|
this.state,
|
||||||
this.canvas!,
|
this.canvas!,
|
||||||
this.state,
|
this.state,
|
||||||
@ -958,15 +950,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const dy = y - elementsCenterY;
|
const dy = y - elementsCenterY;
|
||||||
const groupIdMap = new Map();
|
const groupIdMap = new Map();
|
||||||
|
|
||||||
const newElements = clipboardElements.map((element) =>
|
const newElements = clipboardElements.map((element) => {
|
||||||
duplicateElement(this.state.editingGroupId, groupIdMap, element, {
|
return duplicateElement(this.state.editingGroupId, groupIdMap, element, {
|
||||||
x: element.x + dx - minX,
|
x: element.x + dx - minX,
|
||||||
y: element.y + dy - minY,
|
y: element.y + dy - minY,
|
||||||
}),
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
...newElements,
|
...newElements,
|
||||||
]);
|
]);
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
@ -1004,8 +996,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
element,
|
element,
|
||||||
]);
|
]);
|
||||||
this.setState({ selectedElementIds: { [element.id]: true } });
|
this.setState({ selectedElementIds: { [element.id]: true } });
|
||||||
@ -1116,15 +1108,15 @@ class App extends React.Component<ExcalidrawProps, 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 (
|
||||||
globalSceneState.getElementsIncludingDeleted() == null ||
|
this.scene.getElementsIncludingDeleted() == null ||
|
||||||
globalSceneState.getElementsIncludingDeleted().length === 0
|
this.scene.getElementsIncludingDeleted().length === 0
|
||||||
) {
|
) {
|
||||||
globalSceneState.replaceAllElements(remoteElements);
|
this.scene.replaceAllElements(remoteElements);
|
||||||
} 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(
|
const localElementMap = getElementMap(
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reconcile
|
// Reconcile
|
||||||
@ -1183,7 +1175,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
newElements,
|
newElements,
|
||||||
);
|
);
|
||||||
|
|
||||||
globalSceneState.replaceAllElements(newElements);
|
this.scene.replaceAllElements(newElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -1317,7 +1309,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let syncableElements = getSyncableElements(
|
let syncableElements = getSyncableElements(
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!syncAll) {
|
if (!syncAll) {
|
||||||
@ -1340,7 +1332,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
};
|
};
|
||||||
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
|
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
|
||||||
this.lastBroadcastedOrReceivedSceneVersion,
|
this.lastBroadcastedOrReceivedSceneVersion,
|
||||||
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()),
|
getDrawingVersion(this.scene.getElementsIncludingDeleted()),
|
||||||
);
|
);
|
||||||
for (const syncableElement of syncableElements) {
|
for (const syncableElement of syncableElements) {
|
||||||
this.broadcastedElementVersions.set(
|
this.broadcastedElementVersions.set(
|
||||||
@ -1427,8 +1419,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
(event.shiftKey
|
(event.shiftKey
|
||||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||||
: ELEMENT_TRANSLATE_AMOUNT);
|
: ELEMENT_TRANSLATE_AMOUNT);
|
||||||
globalSceneState.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
globalSceneState.getElementsIncludingDeleted().map((el) => {
|
this.scene.getElementsIncludingDeleted().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) {
|
||||||
@ -1448,7 +1440,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
} else if (event.key === KEYS.ENTER) {
|
} else if (event.key === KEYS.ENTER) {
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1462,7 +1454,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
this.setState({
|
this.setState({
|
||||||
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
editingLinearElement: new LinearElementEditor(
|
||||||
|
selectedElements[0],
|
||||||
|
this.scene,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@ -1558,7 +1553,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
private setElements = (elements: readonly ExcalidrawElement[]) => {
|
private setElements = (elements: readonly ExcalidrawElement[]) => {
|
||||||
globalSceneState.replaceAllElements(elements);
|
this.scene.replaceAllElements(elements);
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleTextWysiwyg(
|
private handleTextWysiwyg(
|
||||||
@ -1570,8 +1565,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const updateElement = (text: string, isDeleted = false) => {
|
const updateElement = (text: string, isDeleted = false) => {
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted().map((_element) => {
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
||||||
if (_element.id === element.id && isTextElement(_element)) {
|
if (_element.id === element.id && isTextElement(_element)) {
|
||||||
return updateTextElement(_element, {
|
return updateTextElement(_element, {
|
||||||
text,
|
text,
|
||||||
@ -1624,6 +1619,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
setCursorForShape(this.state.elementType);
|
setCursorForShape(this.state.elementType);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
element,
|
||||||
});
|
});
|
||||||
// deselect all other elements when inserting text
|
// deselect all other elements when inserting text
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -1642,7 +1638,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
y: number,
|
y: number,
|
||||||
): NonDeleted<ExcalidrawTextElement> | null {
|
): NonDeleted<ExcalidrawTextElement> | null {
|
||||||
const element = getElementAtPosition(
|
const element = getElementAtPosition(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -1715,8 +1711,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN });
|
mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
element,
|
element,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -1752,7 +1748,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1763,7 +1759,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
this.setState({
|
this.setState({
|
||||||
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
editingLinearElement: new LinearElementEditor(
|
||||||
|
selectedElements[0],
|
||||||
|
this.scene,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -1781,7 +1780,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const selectedGroupIds = getSelectedGroupIds(this.state);
|
const selectedGroupIds = getSelectedGroupIds(this.state);
|
||||||
|
|
||||||
if (selectedGroupIds.length > 0) {
|
if (selectedGroupIds.length > 0) {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
const hitElement = getElementAtPosition(
|
const hitElement = getElementAtPosition(
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
@ -1803,7 +1802,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
selectedElementIds: { [hitElement!.id]: true },
|
selectedElementIds: { [hitElement!.id]: true },
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
},
|
},
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -1960,7 +1959,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(elements, this.state);
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
if (
|
if (
|
||||||
@ -2262,7 +2261,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
window.devicePixelRatio,
|
window.devicePixelRatio,
|
||||||
);
|
);
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
|
const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
|
||||||
@ -2377,7 +2376,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
const selectedElements = getSelectedElements(elements, this.state);
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||||
const elementWithResizeHandler = getElementWithResizeHandler(
|
const elementWithResizeHandler = getElementWithResizeHandler(
|
||||||
@ -2491,12 +2490,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
[hitElement!.id]: true,
|
[hitElement!.id]: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// TODO: this is strange...
|
// TODO: this is strange...
|
||||||
globalSceneState.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
);
|
);
|
||||||
pointerDownState.hit.wasAddedToSelection = true;
|
pointerDownState.hit.wasAddedToSelection = true;
|
||||||
}
|
}
|
||||||
@ -2610,8 +2609,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
points: [...element.points, [0, 0]],
|
points: [...element.points, [0, 0]],
|
||||||
});
|
});
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
element,
|
element,
|
||||||
]);
|
]);
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -2649,8 +2648,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
draggingElement: element,
|
draggingElement: element,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...globalSceneState.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
element,
|
element,
|
||||||
]);
|
]);
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -2672,7 +2671,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
if (pointerDownState.drag.offset === null) {
|
if (pointerDownState.drag.offset === null) {
|
||||||
pointerDownState.drag.offset = tupleToCoors(
|
pointerDownState.drag.offset = tupleToCoors(
|
||||||
getDragOffsetXY(
|
getDragOffsetXY(
|
||||||
getSelectedElements(globalSceneState.getElements(), this.state),
|
getSelectedElements(this.scene.getElements(), this.state),
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
),
|
),
|
||||||
@ -2735,7 +2734,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
if (pointerDownState.resize.isResizing) {
|
if (pointerDownState.resize.isResizing) {
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
const resizeHandle = pointerDownState.resize.handle;
|
const resizeHandle = pointerDownState.resize.handle;
|
||||||
@ -2796,7 +2795,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
// if elements should be deselected on pointerup
|
// if elements should be deselected on pointerup
|
||||||
pointerDownState.drag.hasOccurred = true;
|
pointerDownState.drag.hasOccurred = true;
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
if (selectedElements.length > 0) {
|
if (selectedElements.length > 0) {
|
||||||
@ -2818,7 +2817,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
const nextElements = [];
|
const nextElements = [];
|
||||||
const elementsToAppend = [];
|
const elementsToAppend = [];
|
||||||
const groupIdMap = new Map();
|
const groupIdMap = new Map();
|
||||||
for (const element of globalSceneState.getElementsIncludingDeleted()) {
|
for (const element of this.scene.getElementsIncludingDeleted()) {
|
||||||
if (
|
if (
|
||||||
this.state.selectedElementIds[element.id] ||
|
this.state.selectedElementIds[element.id] ||
|
||||||
// case: the state.selectedElementIds might not have been
|
// case: the state.selectedElementIds might not have been
|
||||||
@ -2846,7 +2845,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
nextElements.push(element);
|
nextElements.push(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalSceneState.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...nextElements,
|
...nextElements,
|
||||||
...elementsToAppend,
|
...elementsToAppend,
|
||||||
]);
|
]);
|
||||||
@ -2925,7 +2924,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
@ -2949,7 +2948,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}, {} as any),
|
}, {} as any),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
globalSceneState.getElements(),
|
this.scene.getElements(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3065,8 +3064,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
isInvisiblySmallElement(draggingElement)
|
isInvisiblySmallElement(draggingElement)
|
||||||
) {
|
) {
|
||||||
// remove invisible element which was added in onPointerDown
|
// remove invisible element which was added in onPointerDown
|
||||||
globalSceneState.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
globalSceneState.getElementsIncludingDeleted().slice(0, -1),
|
this.scene.getElementsIncludingDeleted().slice(0, -1),
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
@ -3086,8 +3085,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
||||||
globalSceneState.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
globalSceneState
|
this.scene
|
||||||
.getElementsIncludingDeleted()
|
.getElementsIncludingDeleted()
|
||||||
.filter((el) => el.id !== resizingElement.id),
|
.filter((el) => el.id !== resizingElement.id),
|
||||||
);
|
);
|
||||||
@ -3143,7 +3142,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
elementType !== "selection" ||
|
elementType !== "selection" ||
|
||||||
isSomeElementSelected(globalSceneState.getElements(), this.state)
|
isSomeElementSelected(this.scene.getElements(), this.state)
|
||||||
) {
|
) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
}
|
}
|
||||||
@ -3283,7 +3282,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
window.devicePixelRatio,
|
window.devicePixelRatio,
|
||||||
);
|
);
|
||||||
|
|
||||||
const elements = globalSceneState.getElements();
|
const elements = this.scene.getElements();
|
||||||
const element = getElementAtPosition(
|
const element = getElementAtPosition(
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
@ -3409,7 +3408,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
scale: number,
|
scale: number,
|
||||||
) {
|
) {
|
||||||
const elementClickedInside = getElementContainingPosition(
|
const elementClickedInside = getElementContainingPosition(
|
||||||
globalSceneState
|
this.scene
|
||||||
.getElementsIncludingDeleted()
|
.getElementsIncludingDeleted()
|
||||||
.filter((element) => !isTextElement(element)),
|
.filter((element) => !isTextElement(element)),
|
||||||
x,
|
x,
|
||||||
@ -3467,10 +3466,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
private saveDebounced = debounce(() => {
|
private saveDebounced = debounce(() => {
|
||||||
saveToLocalStorage(
|
saveToLocalStorage(this.scene.getElementsIncludingDeleted(), this.state);
|
||||||
globalSceneState.getElementsIncludingDeleted(),
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
private getCanvasOffsets() {
|
private getCanvasOffsets() {
|
||||||
@ -3515,10 +3511,10 @@ if (
|
|||||||
Object.defineProperties(window.h, {
|
Object.defineProperties(window.h, {
|
||||||
elements: {
|
elements: {
|
||||||
get() {
|
get() {
|
||||||
return globalSceneState.getElementsIncludingDeleted();
|
return this.app.scene.getElementsIncludingDeleted();
|
||||||
},
|
},
|
||||||
set(elements: ExcalidrawElement[]) {
|
set(elements: ExcalidrawElement[]) {
|
||||||
return globalSceneState.replaceAllElements(elements);
|
return this.app.scene.replaceAllElements(elements);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
history: {
|
history: {
|
||||||
|
@ -9,7 +9,8 @@ import { getElementPointsCoords } from "./bounds";
|
|||||||
import { Point, AppState } from "../types";
|
import { Point, AppState } from "../types";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { SceneHistory } from "../history";
|
import { SceneHistory } from "../history";
|
||||||
import { globalSceneState } from "../scene";
|
|
||||||
|
import Scene from "../scene/Scene";
|
||||||
|
|
||||||
export class LinearElementEditor {
|
export class LinearElementEditor {
|
||||||
public elementId: ExcalidrawElement["id"] & {
|
public elementId: ExcalidrawElement["id"] & {
|
||||||
@ -19,12 +20,13 @@ export class LinearElementEditor {
|
|||||||
public draggingElementPointIndex: number | null;
|
public draggingElementPointIndex: number | null;
|
||||||
public lastUncommittedPoint: Point | null;
|
public lastUncommittedPoint: Point | null;
|
||||||
|
|
||||||
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) {
|
||||||
LinearElementEditor.normalizePoints(element);
|
|
||||||
|
|
||||||
this.elementId = element.id as string & {
|
this.elementId = element.id as string & {
|
||||||
_brand: "excalidrawLinearElementId";
|
_brand: "excalidrawLinearElementId";
|
||||||
};
|
};
|
||||||
|
Scene.mapElementToScene(this.elementId, scene);
|
||||||
|
LinearElementEditor.normalizePoints(element);
|
||||||
|
|
||||||
this.activePointIndex = null;
|
this.activePointIndex = null;
|
||||||
this.lastUncommittedPoint = null;
|
this.lastUncommittedPoint = null;
|
||||||
this.draggingElementPointIndex = null;
|
this.draggingElementPointIndex = null;
|
||||||
@ -41,7 +43,7 @@ export class LinearElementEditor {
|
|||||||
* statically guarantee this method returns an ExcalidrawLinearElement)
|
* statically guarantee this method returns an ExcalidrawLinearElement)
|
||||||
*/
|
*/
|
||||||
static getElement(id: InstanceType<typeof LinearElementEditor>["elementId"]) {
|
static getElement(id: InstanceType<typeof LinearElementEditor>["elementId"]) {
|
||||||
const element = globalSceneState.getNonDeletedElement(id);
|
const element = Scene.getScene(id)?.getNonDeletedElement(id);
|
||||||
if (element) {
|
if (element) {
|
||||||
return element as NonDeleted<ExcalidrawLinearElement>;
|
return element as NonDeleted<ExcalidrawLinearElement>;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ExcalidrawElement } from "./types";
|
import { ExcalidrawElement } from "./types";
|
||||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||||
import { globalSceneState } from "../scene";
|
import Scene from "../scene/Scene";
|
||||||
import { getSizeFromPoints } from "../points";
|
import { getSizeFromPoints } from "../points";
|
||||||
import { randomInteger } from "../random";
|
import { randomInteger } from "../random";
|
||||||
import { Point } from "../types";
|
import { Point } from "../types";
|
||||||
@ -81,8 +81,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
|
|
||||||
element.version++;
|
element.version++;
|
||||||
element.versionNonce = randomInteger();
|
element.versionNonce = randomInteger();
|
||||||
|
Scene.getScene(element)?.informMutation();
|
||||||
globalSceneState.informMutation();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||||
|
@ -125,7 +125,6 @@ export const newTextElement = (
|
|||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
return textElement;
|
return textElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isWritableElement, getFontString } from "../utils";
|
import { isWritableElement, getFontString } from "../utils";
|
||||||
import { globalSceneState } from "../scene";
|
import Scene from "../scene/Scene";
|
||||||
import { isTextElement } from "./typeChecks";
|
import { isTextElement } from "./typeChecks";
|
||||||
import { CLASSES } from "../constants";
|
import { CLASSES } from "../constants";
|
||||||
import { ExcalidrawElement } from "./types";
|
import { ExcalidrawElement } from "./types";
|
||||||
@ -37,16 +37,18 @@ export const textWysiwyg = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
getViewportCoords,
|
getViewportCoords,
|
||||||
|
element,
|
||||||
}: {
|
}: {
|
||||||
id: ExcalidrawElement["id"];
|
id: ExcalidrawElement["id"];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
onChange?: (text: string) => void;
|
onChange?: (text: string) => void;
|
||||||
onSubmit: (text: string) => void;
|
onSubmit: (text: string) => void;
|
||||||
getViewportCoords: (x: number, y: number) => [number, number];
|
getViewportCoords: (x: number, y: number) => [number, number];
|
||||||
|
element: ExcalidrawElement;
|
||||||
}) => {
|
}) => {
|
||||||
function updateWysiwygStyle() {
|
function updateWysiwygStyle() {
|
||||||
const updatedElement = globalSceneState.getElement(id);
|
const updatedElement = Scene.getScene(element)?.getElement(id);
|
||||||
if (isTextElement(updatedElement)) {
|
if (updatedElement && isTextElement(updatedElement)) {
|
||||||
const [viewportX, viewportY] = getViewportCoords(
|
const [viewportX, viewportY] = getViewportCoords(
|
||||||
updatedElement.x,
|
updatedElement.x,
|
||||||
updatedElement.y,
|
updatedElement.y,
|
||||||
@ -183,7 +185,7 @@ export const textWysiwyg = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// handle updates of textElement properties of editing element
|
// handle updates of textElement properties of editing element
|
||||||
const unbindUpdate = globalSceneState.addCallback(() => {
|
const unbindUpdate = Scene.getScene(element)!.addCallback(() => {
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
editable.focus();
|
editable.focus();
|
||||||
});
|
});
|
||||||
|
121
src/scene/Scene.ts
Normal file
121
src/scene/Scene.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
ExcalidrawElement,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
NonDeleted,
|
||||||
|
} from "../element/types";
|
||||||
|
import { getNonDeletedElements, isNonDeletedElement } from "../element";
|
||||||
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
|
||||||
|
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
||||||
|
type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||||
|
|
||||||
|
type SceneStateCallback = () => void;
|
||||||
|
type SceneStateCallbackRemover = () => void;
|
||||||
|
|
||||||
|
const isIdKey = (elementKey: ElementKey): elementKey is ElementIdKey => {
|
||||||
|
if (typeof elementKey === "string") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// static methods/props
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static sceneMapByElement = new WeakMap<ExcalidrawElement, Scene>();
|
||||||
|
private static sceneMapById = new Map<string, Scene>();
|
||||||
|
|
||||||
|
static mapElementToScene(elementKey: ElementKey, scene: Scene) {
|
||||||
|
if (isIdKey(elementKey)) {
|
||||||
|
this.sceneMapById.set(elementKey, scene);
|
||||||
|
} else {
|
||||||
|
this.sceneMapByElement.set(elementKey, scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getScene(elementKey: ElementKey): Scene | null {
|
||||||
|
if (isIdKey(elementKey)) {
|
||||||
|
return this.sceneMapById.get(elementKey) || null;
|
||||||
|
}
|
||||||
|
return this.sceneMapByElement.get(elementKey) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// instance methods/props
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private callbacks: Set<SceneStateCallback> = new Set();
|
||||||
|
|
||||||
|
private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = [];
|
||||||
|
private elements: readonly ExcalidrawElement[] = [];
|
||||||
|
private elementsMap = new Map<ExcalidrawElement["id"], ExcalidrawElement>();
|
||||||
|
|
||||||
|
getElementsIncludingDeleted() {
|
||||||
|
return this.elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
getElements(): readonly NonDeletedExcalidrawElement[] {
|
||||||
|
return this.nonDeletedElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
getElement(id: ExcalidrawElement["id"]): ExcalidrawElement | null {
|
||||||
|
return this.elementsMap.get(id) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNonDeletedElement(
|
||||||
|
id: ExcalidrawElement["id"],
|
||||||
|
): NonDeleted<ExcalidrawElement> | null {
|
||||||
|
const element = this.getElement(id);
|
||||||
|
if (element && isNonDeletedElement(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceAllElements(nextElements: readonly ExcalidrawElement[]) {
|
||||||
|
this.elements = nextElements;
|
||||||
|
this.elementsMap.clear();
|
||||||
|
nextElements.forEach((element) => {
|
||||||
|
this.elementsMap.set(element.id, element);
|
||||||
|
Scene.mapElementToScene(element, this);
|
||||||
|
});
|
||||||
|
this.nonDeletedElements = getNonDeletedElements(this.elements);
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
Scene.sceneMapById.forEach((scene, elementKey) => {
|
||||||
|
if (scene === this) {
|
||||||
|
Scene.sceneMapById.delete(elementKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// done not for memory leaks, but to guard against possible late fires
|
||||||
|
// (I guess?)
|
||||||
|
this.callbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Scene;
|
@ -1,80 +0,0 @@
|
|||||||
import {
|
|
||||||
ExcalidrawElement,
|
|
||||||
NonDeletedExcalidrawElement,
|
|
||||||
NonDeleted,
|
|
||||||
} from "../element/types";
|
|
||||||
import {
|
|
||||||
getNonDeletedElements,
|
|
||||||
isNonDeletedElement,
|
|
||||||
getElementMap,
|
|
||||||
} from "../element";
|
|
||||||
|
|
||||||
export interface SceneStateCallback {
|
|
||||||
(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SceneStateCallbackRemover {
|
|
||||||
(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GlobalScene {
|
|
||||||
private callbacks: Set<SceneStateCallback> = new Set();
|
|
||||||
|
|
||||||
private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = [];
|
|
||||||
private elements: readonly ExcalidrawElement[] = [];
|
|
||||||
private elementsMap: {
|
|
||||||
[id: string]: ExcalidrawElement;
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
getElementsIncludingDeleted() {
|
|
||||||
return this.elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
getElements(): readonly NonDeletedExcalidrawElement[] {
|
|
||||||
return this.nonDeletedElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
getElement(id: ExcalidrawElement["id"]): ExcalidrawElement | null {
|
|
||||||
return this.elementsMap[id] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNonDeletedElement(
|
|
||||||
id: ExcalidrawElement["id"],
|
|
||||||
): NonDeleted<ExcalidrawElement> | null {
|
|
||||||
const element = this.getElement(id);
|
|
||||||
if (element && isNonDeletedElement(element)) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceAllElements(nextElements: readonly ExcalidrawElement[]) {
|
|
||||||
this.elements = nextElements;
|
|
||||||
this.elementsMap = getElementMap(nextElements);
|
|
||||||
this.nonDeletedElements = getNonDeletedElements(this.elements);
|
|
||||||
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 GlobalScene();
|
|
@ -15,4 +15,3 @@ export {
|
|||||||
hasText,
|
hasText,
|
||||||
} from "./comparisons";
|
} from "./comparisons";
|
||||||
export { getZoomOrigin, getNormalizedZoom } from "./zoom";
|
export { getZoomOrigin, getNormalizedZoom } from "./zoom";
|
||||||
export { globalSceneState } from "./globalScene";
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user