remove most setState({}) (#959)
This commit is contained in:
parent
e1e2249f57
commit
35ce1729cc
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>(
|
||||||
|
@ -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
47
src/scene/globalScene.ts
Normal 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();
|
@ -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";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user