|
@@ -3,7 +3,6 @@ import React from "react";
|
|
import socketIOClient from "socket.io-client";
|
|
import socketIOClient from "socket.io-client";
|
|
import rough from "roughjs/bin/rough";
|
|
import rough from "roughjs/bin/rough";
|
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
|
-import { Point } from "roughjs/bin/geometry";
|
|
|
|
|
|
|
|
import {
|
|
import {
|
|
newElement,
|
|
newElement,
|
|
@@ -95,9 +94,9 @@ import {
|
|
} from "../constants";
|
|
} from "../constants";
|
|
import { LayerUI } from "./LayerUI";
|
|
import { LayerUI } from "./LayerUI";
|
|
import { ScrollBars } from "../scene/types";
|
|
import { ScrollBars } from "../scene/types";
|
|
-import { invalidateShapeForElement } from "../renderer/renderElement";
|
|
|
|
import { generateCollaborationLink, getCollaborationLinkData } from "../data";
|
|
import { generateCollaborationLink, getCollaborationLinkData } from "../data";
|
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
|
|
+import { invalidateShapeForElement } from "../renderer/renderElement";
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
// TEST HOOKS
|
|
// TEST HOOKS
|
|
@@ -106,27 +105,24 @@ import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
declare global {
|
|
declare global {
|
|
interface Window {
|
|
interface Window {
|
|
__TEST__: {
|
|
__TEST__: {
|
|
- elements: typeof elements;
|
|
|
|
|
|
+ elements: readonly ExcalidrawElement[];
|
|
appState: AppState;
|
|
appState: AppState;
|
|
};
|
|
};
|
|
- // TEMPORARY until we have a UI to support this
|
|
|
|
- generateCollaborationLink: () => Promise<string>;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (process.env.NODE_ENV === "test") {
|
|
if (process.env.NODE_ENV === "test") {
|
|
window.__TEST__ = {} as Window["__TEST__"];
|
|
window.__TEST__ = {} as Window["__TEST__"];
|
|
}
|
|
}
|
|
-window.generateCollaborationLink = generateCollaborationLink;
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
-let { elements } = createScene();
|
|
|
|
|
|
+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 elements;
|
|
|
|
|
|
+ return scene.getAllElements();
|
|
},
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
@@ -173,7 +169,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,
|
|
- () => elements,
|
|
|
|
|
|
+ () => scene.getAllElements(),
|
|
);
|
|
);
|
|
this.actionManager.registerAll(actions);
|
|
this.actionManager.registerAll(actions);
|
|
|
|
|
|
@@ -181,6 +177,11 @@ 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,
|
|
@@ -189,11 +190,10 @@ export class App extends React.Component<any, AppState> {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
if (res.elements) {
|
|
if (res.elements) {
|
|
- elements = res.elements;
|
|
|
|
|
|
+ this.replaceElements(res.elements);
|
|
if (commitToHistory) {
|
|
if (commitToHistory) {
|
|
history.resumeRecording();
|
|
history.resumeRecording();
|
|
}
|
|
}
|
|
- this.setState({});
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if (res.appState) {
|
|
if (res.appState) {
|
|
@@ -212,12 +212,12 @@ export class App extends React.Component<any, AppState> {
|
|
if (isWritableElement(event.target)) {
|
|
if (isWritableElement(event.target)) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- copyToAppClipboard(elements, this.state);
|
|
|
|
|
|
+ copyToAppClipboard(scene.getAllElements(), this.state);
|
|
const { elements: nextElements, appState } = deleteSelectedElements(
|
|
const { elements: nextElements, appState } = deleteSelectedElements(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
);
|
|
);
|
|
- elements = nextElements;
|
|
|
|
|
|
+ this.replaceElements(nextElements);
|
|
history.resumeRecording();
|
|
history.resumeRecording();
|
|
this.setState({ ...appState });
|
|
this.setState({ ...appState });
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
@@ -226,7 +226,7 @@ export class App extends React.Component<any, AppState> {
|
|
if (isWritableElement(event.target)) {
|
|
if (isWritableElement(event.target)) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- copyToAppClipboard(elements, this.state);
|
|
|
|
|
|
+ copyToAppClipboard(scene.getAllElements(), this.state);
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
};
|
|
};
|
|
|
|
|
|
@@ -291,70 +291,75 @@ export class App extends React.Component<any, AppState> {
|
|
// Perform reconciliation - in collaboration, if we encounter
|
|
// Perform reconciliation - in collaboration, if we encounter
|
|
// 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 (elements == null || elements.length === 0) {
|
|
|
|
- elements = restoredState.elements;
|
|
|
|
|
|
+ if (
|
|
|
|
+ scene.getAllElements() == null ||
|
|
|
|
+ scene.getAllElements().length === 0
|
|
|
|
+ ) {
|
|
|
|
+ this.replaceElements(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(elements);
|
|
|
|
|
|
+ const localElementMap = getElementMap(scene.getAllElements());
|
|
|
|
|
|
// Reconcile
|
|
// Reconcile
|
|
- elements = restoredState.elements
|
|
|
|
- .reduce((elements, element) => {
|
|
|
|
- // if the remote element references one that's currently
|
|
|
|
- // edited on local, skip it (it'll be added in the next
|
|
|
|
- // step)
|
|
|
|
- if (
|
|
|
|
- element.id === this.state.editingElement?.id ||
|
|
|
|
- element.id === this.state.resizingElement?.id ||
|
|
|
|
- element.id === this.state.draggingElement?.id
|
|
|
|
- ) {
|
|
|
|
- return elements;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (
|
|
|
|
- localElementMap.hasOwnProperty(element.id) &&
|
|
|
|
- localElementMap[element.id].version > element.version
|
|
|
|
- ) {
|
|
|
|
- elements.push(localElementMap[element.id]);
|
|
|
|
- delete localElementMap[element.id];
|
|
|
|
- } else if (
|
|
|
|
- localElementMap.hasOwnProperty(element.id) &&
|
|
|
|
- localElementMap[element.id].version === element.version &&
|
|
|
|
- localElementMap[element.id].versionNonce !==
|
|
|
|
- element.versionNonce
|
|
|
|
- ) {
|
|
|
|
- // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
|
|
|
|
|
+ this.replaceElements(
|
|
|
|
+ restoredState.elements
|
|
|
|
+ .reduce((elements, element) => {
|
|
|
|
+ // if the remote element references one that's currently
|
|
|
|
+ // edited on local, skip it (it'll be added in the next
|
|
|
|
+ // step)
|
|
if (
|
|
if (
|
|
- localElementMap[element.id].versionNonce <
|
|
|
|
- element.versionNonce
|
|
|
|
|
|
+ element.id === this.state.editingElement?.id ||
|
|
|
|
+ element.id === this.state.resizingElement?.id ||
|
|
|
|
+ element.id === this.state.draggingElement?.id
|
|
|
|
+ ) {
|
|
|
|
+ return elements;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (
|
|
|
|
+ localElementMap.hasOwnProperty(element.id) &&
|
|
|
|
+ localElementMap[element.id].version > element.version
|
|
) {
|
|
) {
|
|
elements.push(localElementMap[element.id]);
|
|
elements.push(localElementMap[element.id]);
|
|
|
|
+ delete localElementMap[element.id];
|
|
|
|
+ } else if (
|
|
|
|
+ localElementMap.hasOwnProperty(element.id) &&
|
|
|
|
+ localElementMap[element.id].version ===
|
|
|
|
+ element.version &&
|
|
|
|
+ localElementMap[element.id].versionNonce !==
|
|
|
|
+ element.versionNonce
|
|
|
|
+ ) {
|
|
|
|
+ // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
|
|
|
+ if (
|
|
|
|
+ localElementMap[element.id].versionNonce <
|
|
|
|
+ element.versionNonce
|
|
|
|
+ ) {
|
|
|
|
+ elements.push(localElementMap[element.id]);
|
|
|
|
+ } else {
|
|
|
|
+ // it should be highly unlikely that the two versionNonces are the same. if we are
|
|
|
|
+ // really worried about this, we can replace the versionNonce with the socket id.
|
|
|
|
+ elements.push(element);
|
|
|
|
+ }
|
|
|
|
+ delete localElementMap[element.id];
|
|
} else {
|
|
} else {
|
|
- // it should be highly unlikely that the two versionNonces are the same. if we are
|
|
|
|
- // really worried about this, we can replace the versionNonce with the socket id.
|
|
|
|
elements.push(element);
|
|
elements.push(element);
|
|
|
|
+ delete localElementMap[element.id];
|
|
}
|
|
}
|
|
- delete localElementMap[element.id];
|
|
|
|
- } else {
|
|
|
|
- elements.push(element);
|
|
|
|
- delete localElementMap[element.id];
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return elements;
|
|
|
|
- }, [] as any)
|
|
|
|
- // add local elements that weren't deleted or on remote
|
|
|
|
- .concat(...Object.values(localElementMap));
|
|
|
|
|
|
+
|
|
|
|
+ return elements;
|
|
|
|
+ }, [] as any)
|
|
|
|
+ // add local elements that weren't deleted or on remote
|
|
|
|
+ .concat(...Object.values(localElementMap)),
|
|
|
|
+ );
|
|
}
|
|
}
|
|
this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
|
|
this.lastBroadcastedOrReceivedSceneVersion = getDrawingVersion(
|
|
- elements,
|
|
|
|
|
|
+ scene.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
|
|
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
|
|
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
|
|
// right now we think this is the right tradeoff.
|
|
// right now we think this is the right tradeoff.
|
|
history.clear();
|
|
history.clear();
|
|
- this.setState({});
|
|
|
|
if (this.socketInitialized === false) {
|
|
if (this.socketInitialized === false) {
|
|
this.socketInitialized = true;
|
|
this.socketInitialized = true;
|
|
}
|
|
}
|
|
@@ -423,12 +428,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(elements),
|
|
|
|
|
|
+ elements: getSyncableElements(scene.getAllElements()),
|
|
},
|
|
},
|
|
};
|
|
};
|
|
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
|
|
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
|
|
this.lastBroadcastedOrReceivedSceneVersion,
|
|
this.lastBroadcastedOrReceivedSceneVersion,
|
|
- getDrawingVersion(elements),
|
|
|
|
|
|
+ getDrawingVersion(scene.getAllElements()),
|
|
);
|
|
);
|
|
return this._broadcastSocketData(
|
|
return this._broadcastSocketData(
|
|
data as typeof data & { _brand: "socketUpdateData" },
|
|
data as typeof data & { _brand: "socketUpdateData" },
|
|
@@ -553,7 +558,9 @@ export class App extends React.Component<any, AppState> {
|
|
public state: AppState = getDefaultAppState();
|
|
public state: AppState = getDefaultAppState();
|
|
|
|
|
|
private onResize = () => {
|
|
private onResize = () => {
|
|
- elements.forEach(element => invalidateShapeForElement(element));
|
|
|
|
|
|
+ scene
|
|
|
|
+ .getAllElements()
|
|
|
|
+ .forEach(element => invalidateShapeForElement(element));
|
|
this.setState({});
|
|
this.setState({});
|
|
};
|
|
};
|
|
|
|
|
|
@@ -587,23 +594,24 @@ 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;
|
|
- elements = elements.map(el => {
|
|
|
|
- if (this.state.selectedElementIds[el.id]) {
|
|
|
|
- const update: { x?: number; y?: number } = {};
|
|
|
|
- if (event.key === KEYS.ARROW_LEFT) {
|
|
|
|
- update.x = el.x - step;
|
|
|
|
- } else if (event.key === KEYS.ARROW_RIGHT) {
|
|
|
|
- update.x = el.x + step;
|
|
|
|
- } else if (event.key === KEYS.ARROW_UP) {
|
|
|
|
- update.y = el.y - step;
|
|
|
|
- } else if (event.key === KEYS.ARROW_DOWN) {
|
|
|
|
- update.y = el.y + step;
|
|
|
|
|
|
+ this.replaceElements(
|
|
|
|
+ scene.getAllElements().map(el => {
|
|
|
|
+ if (this.state.selectedElementIds[el.id]) {
|
|
|
|
+ const update: { x?: number; y?: number } = {};
|
|
|
|
+ if (event.key === KEYS.ARROW_LEFT) {
|
|
|
|
+ update.x = el.x - step;
|
|
|
|
+ } else if (event.key === KEYS.ARROW_RIGHT) {
|
|
|
|
+ update.x = el.x + step;
|
|
|
|
+ } else if (event.key === KEYS.ARROW_UP) {
|
|
|
|
+ update.y = el.y - step;
|
|
|
|
+ } else if (event.key === KEYS.ARROW_DOWN) {
|
|
|
|
+ update.y = el.y + step;
|
|
|
|
+ }
|
|
|
|
+ return newElementWith(el, update);
|
|
}
|
|
}
|
|
- return newElementWith(el, update);
|
|
|
|
- }
|
|
|
|
- return el;
|
|
|
|
- });
|
|
|
|
- this.setState({});
|
|
|
|
|
|
+ return el;
|
|
|
|
+ }),
|
|
|
|
+ );
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
} else if (
|
|
} else if (
|
|
shapesShortcutKeys.includes(event.key.toLowerCase()) &&
|
|
shapesShortcutKeys.includes(event.key.toLowerCase()) &&
|
|
@@ -635,14 +643,17 @@ export class App extends React.Component<any, AppState> {
|
|
};
|
|
};
|
|
|
|
|
|
private copyToAppClipboard = () => {
|
|
private copyToAppClipboard = () => {
|
|
- copyToAppClipboard(elements, this.state);
|
|
|
|
|
|
+ copyToAppClipboard(scene.getAllElements(), this.state);
|
|
};
|
|
};
|
|
|
|
|
|
private copyToClipboardAsPng = () => {
|
|
private copyToClipboardAsPng = () => {
|
|
- const selectedElements = getSelectedElements(elements, this.state);
|
|
|
|
|
|
+ const selectedElements = getSelectedElements(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ this.state,
|
|
|
|
+ );
|
|
exportCanvas(
|
|
exportCanvas(
|
|
"clipboard",
|
|
"clipboard",
|
|
- selectedElements.length ? selectedElements : elements,
|
|
|
|
|
|
+ selectedElements.length ? selectedElements : scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
this.canvas!,
|
|
this.canvas!,
|
|
this.state,
|
|
this.state,
|
|
@@ -686,7 +697,7 @@ export class App extends React.Component<any, AppState> {
|
|
this.state.currentItemFont,
|
|
this.state.currentItemFont,
|
|
);
|
|
);
|
|
|
|
|
|
- elements = [...elements, element];
|
|
|
|
|
|
+ this.replaceElements([...scene.getAllElements(), element]);
|
|
this.setState({ selectedElementIds: { [element.id]: true } });
|
|
this.setState({ selectedElementIds: { [element.id]: true } });
|
|
history.resumeRecording();
|
|
history.resumeRecording();
|
|
}
|
|
}
|
|
@@ -729,11 +740,6 @@ export class App extends React.Component<any, AppState> {
|
|
this.setState(obj);
|
|
this.setState(obj);
|
|
};
|
|
};
|
|
|
|
|
|
- setElements = (elements_: readonly ExcalidrawElement[]) => {
|
|
|
|
- elements = elements_;
|
|
|
|
- this.setState({});
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
removePointer = (event: React.PointerEvent<HTMLElement>) => {
|
|
removePointer = (event: React.PointerEvent<HTMLElement>) => {
|
|
gesture.pointers.delete(event.pointerId);
|
|
gesture.pointers.delete(event.pointerId);
|
|
};
|
|
};
|
|
@@ -768,8 +774,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={elements}
|
|
|
|
- setElements={this.setElements}
|
|
|
|
|
|
+ elements={scene.getAllElements()}
|
|
|
|
+ setElements={this.replaceElements}
|
|
language={getLanguage()}
|
|
language={getLanguage()}
|
|
onRoomCreate={this.createRoom}
|
|
onRoomCreate={this.createRoom}
|
|
onRoomDestroy={this.destroyRoom}
|
|
onRoomDestroy={this.destroyRoom}
|
|
@@ -810,7 +816,7 @@ export class App extends React.Component<any, AppState> {
|
|
);
|
|
);
|
|
|
|
|
|
const element = getElementAtPosition(
|
|
const element = getElementAtPosition(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
x,
|
|
x,
|
|
y,
|
|
y,
|
|
@@ -824,7 +830,7 @@ export class App extends React.Component<any, AppState> {
|
|
action: () => this.pasteFromClipboard(null),
|
|
action: () => this.pasteFromClipboard(null),
|
|
},
|
|
},
|
|
probablySupportsClipboardBlob &&
|
|
probablySupportsClipboardBlob &&
|
|
- hasNonDeletedElements(elements) && {
|
|
|
|
|
|
+ hasNonDeletedElements(scene.getAllElements()) && {
|
|
label: t("labels.copyAsPng"),
|
|
label: t("labels.copyAsPng"),
|
|
action: this.copyToClipboardAsPng,
|
|
action: this.copyToClipboardAsPng,
|
|
},
|
|
},
|
|
@@ -908,7 +914,7 @@ export class App extends React.Component<any, AppState> {
|
|
);
|
|
);
|
|
|
|
|
|
const elementAtPosition = getElementAtPosition(
|
|
const elementAtPosition = getElementAtPosition(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
x,
|
|
x,
|
|
y,
|
|
y,
|
|
@@ -940,10 +946,11 @@ export class App extends React.Component<any, AppState> {
|
|
let textY = event.clientY;
|
|
let textY = event.clientY;
|
|
|
|
|
|
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
|
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
|
- elements = elements.filter(
|
|
|
|
- element => element.id !== elementAtPosition.id,
|
|
|
|
|
|
+ this.replaceElements(
|
|
|
|
+ scene
|
|
|
|
+ .getAllElements()
|
|
|
|
+ .filter(element => element.id !== elementAtPosition.id),
|
|
);
|
|
);
|
|
- this.setState({});
|
|
|
|
|
|
|
|
const centerElementX = elementAtPosition.x + elementAtPosition.width / 2;
|
|
const centerElementX = elementAtPosition.x + elementAtPosition.width / 2;
|
|
const centerElementY = elementAtPosition.y + elementAtPosition.height / 2;
|
|
const centerElementY = elementAtPosition.y + elementAtPosition.height / 2;
|
|
@@ -998,14 +1005,14 @@ export class App extends React.Component<any, AppState> {
|
|
zoom: this.state.zoom,
|
|
zoom: this.state.zoom,
|
|
onSubmit: text => {
|
|
onSubmit: text => {
|
|
if (text) {
|
|
if (text) {
|
|
- elements = [
|
|
|
|
- ...elements,
|
|
|
|
|
|
+ this.replaceElements([
|
|
|
|
+ ...scene.getAllElements(),
|
|
{
|
|
{
|
|
// we need to recreate the element to update dimensions &
|
|
// we need to recreate the element to update dimensions &
|
|
// position
|
|
// position
|
|
...newTextElement(element, text, element.font),
|
|
...newTextElement(element, text, element.font),
|
|
},
|
|
},
|
|
- ];
|
|
|
|
|
|
+ ]);
|
|
}
|
|
}
|
|
this.setState(prevState => ({
|
|
this.setState(prevState => ({
|
|
selectedElementIds: {
|
|
selectedElementIds: {
|
|
@@ -1083,11 +1090,11 @@ export class App extends React.Component<any, AppState> {
|
|
const originX = multiElement.x;
|
|
const originX = multiElement.x;
|
|
const originY = multiElement.y;
|
|
const originY = multiElement.y;
|
|
const points = multiElement.points;
|
|
const points = multiElement.points;
|
|
- const pnt = points[points.length - 1];
|
|
|
|
- pnt[0] = x - originX;
|
|
|
|
- pnt[1] = y - originY;
|
|
|
|
- mutateElement(multiElement);
|
|
|
|
- invalidateShapeForElement(multiElement);
|
|
|
|
|
|
+
|
|
|
|
+ mutateElement(multiElement, {
|
|
|
|
+ points: [...points.slice(0, -1), [x - originX, y - originY]],
|
|
|
|
+ });
|
|
|
|
+
|
|
this.setState({});
|
|
this.setState({});
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -1097,10 +1104,13 @@ export class App extends React.Component<any, AppState> {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- const selectedElements = getSelectedElements(elements, this.state);
|
|
|
|
|
|
+ const selectedElements = getSelectedElements(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ this.state,
|
|
|
|
+ );
|
|
if (selectedElements.length === 1 && !isOverScrollBar) {
|
|
if (selectedElements.length === 1 && !isOverScrollBar) {
|
|
const resizeElement = getElementWithResizeHandler(
|
|
const resizeElement = getElementWithResizeHandler(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
{ x, y },
|
|
{ x, y },
|
|
this.state.zoom,
|
|
this.state.zoom,
|
|
@@ -1114,7 +1124,7 @@ export class App extends React.Component<any, AppState> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const hitElement = getElementAtPosition(
|
|
const hitElement = getElementAtPosition(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
x,
|
|
x,
|
|
y,
|
|
y,
|
|
@@ -1306,14 +1316,17 @@ 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(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
{ x, y },
|
|
{ x, y },
|
|
this.state.zoom,
|
|
this.state.zoom,
|
|
event.pointerType,
|
|
event.pointerType,
|
|
);
|
|
);
|
|
|
|
|
|
- const selectedElements = getSelectedElements(elements, this.state);
|
|
|
|
|
|
+ const selectedElements = getSelectedElements(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ this.state,
|
|
|
|
+ );
|
|
if (selectedElements.length === 1 && resizeElement) {
|
|
if (selectedElements.length === 1 && resizeElement) {
|
|
this.setState({
|
|
this.setState({
|
|
resizingElement: resizeElement ? resizeElement.element : null,
|
|
resizingElement: resizeElement ? resizeElement.element : null,
|
|
@@ -1326,7 +1339,7 @@ export class App extends React.Component<any, AppState> {
|
|
isResizingElements = true;
|
|
isResizingElements = true;
|
|
} else {
|
|
} else {
|
|
hitElement = getElementAtPosition(
|
|
hitElement = getElementAtPosition(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
x,
|
|
x,
|
|
y,
|
|
y,
|
|
@@ -1353,7 +1366,7 @@ export class App extends React.Component<any, AppState> {
|
|
[hitElement!.id]: true,
|
|
[hitElement!.id]: true,
|
|
},
|
|
},
|
|
}));
|
|
}));
|
|
- elements = elements.slice();
|
|
|
|
|
|
+ this.replaceElements(scene.getAllElements());
|
|
elementIsAddedToSelection = true;
|
|
elementIsAddedToSelection = true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1363,7 +1376,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 elements) {
|
|
|
|
|
|
+ for (const element of scene.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);
|
|
@@ -1371,7 +1384,7 @@ export class App extends React.Component<any, AppState> {
|
|
nextElements.push(element);
|
|
nextElements.push(element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- elements = [...nextElements, ...elementsToAppend];
|
|
|
|
|
|
+ this.replaceElements([...nextElements, ...elementsToAppend]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1421,12 +1434,12 @@ export class App extends React.Component<any, AppState> {
|
|
zoom: this.state.zoom,
|
|
zoom: this.state.zoom,
|
|
onSubmit: text => {
|
|
onSubmit: text => {
|
|
if (text) {
|
|
if (text) {
|
|
- elements = [
|
|
|
|
- ...elements,
|
|
|
|
|
|
+ this.replaceElements([
|
|
|
|
+ ...scene.getAllElements(),
|
|
{
|
|
{
|
|
...newTextElement(element, text, this.state.currentItemFont),
|
|
...newTextElement(element, text, this.state.currentItemFont),
|
|
},
|
|
},
|
|
- ];
|
|
|
|
|
|
+ ]);
|
|
}
|
|
}
|
|
this.setState(prevState => ({
|
|
this.setState(prevState => ({
|
|
selectedElementIds: {
|
|
selectedElementIds: {
|
|
@@ -1469,9 +1482,9 @@ export class App extends React.Component<any, AppState> {
|
|
[multiElement.id]: true,
|
|
[multiElement.id]: true,
|
|
},
|
|
},
|
|
}));
|
|
}));
|
|
- multiElement.points.push([x - rx, y - ry]);
|
|
|
|
- mutateElement(multiElement);
|
|
|
|
- invalidateShapeForElement(multiElement);
|
|
|
|
|
|
+ mutateElement(multiElement, {
|
|
|
|
+ points: [...multiElement.points, [x - rx, y - ry]],
|
|
|
|
+ });
|
|
} else {
|
|
} else {
|
|
this.setState(prevState => ({
|
|
this.setState(prevState => ({
|
|
selectedElementIds: {
|
|
selectedElementIds: {
|
|
@@ -1479,10 +1492,10 @@ export class App extends React.Component<any, AppState> {
|
|
[element.id]: false,
|
|
[element.id]: false,
|
|
},
|
|
},
|
|
}));
|
|
}));
|
|
- element.points.push([0, 0]);
|
|
|
|
- mutateElement(element);
|
|
|
|
- invalidateShapeForElement(element);
|
|
|
|
- elements = [...elements, element];
|
|
|
|
|
|
+ mutateElement(element, {
|
|
|
|
+ points: [...element.points, [0, 0]],
|
|
|
|
+ });
|
|
|
|
+ this.replaceElements([...scene.getAllElements(), element]);
|
|
this.setState({
|
|
this.setState({
|
|
draggingElement: element,
|
|
draggingElement: element,
|
|
editingElement: element,
|
|
editingElement: element,
|
|
@@ -1494,7 +1507,7 @@ export class App extends React.Component<any, AppState> {
|
|
draggingElement: element,
|
|
draggingElement: element,
|
|
});
|
|
});
|
|
} else {
|
|
} else {
|
|
- elements = [...elements, element];
|
|
|
|
|
|
+ this.replaceElements([...scene.getAllElements(), element]);
|
|
this.setState({
|
|
this.setState({
|
|
multiElement: null,
|
|
multiElement: null,
|
|
draggingElement: element,
|
|
draggingElement: element,
|
|
@@ -1505,7 +1518,7 @@ export class App extends React.Component<any, AppState> {
|
|
let resizeArrowFn:
|
|
let resizeArrowFn:
|
|
| ((
|
|
| ((
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- p1: Point,
|
|
|
|
|
|
+ pointIndex: number,
|
|
deltaX: number,
|
|
deltaX: number,
|
|
deltaY: number,
|
|
deltaY: number,
|
|
pointerX: number,
|
|
pointerX: number,
|
|
@@ -1516,13 +1529,14 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
const arrowResizeOrigin = (
|
|
const arrowResizeOrigin = (
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- p1: Point,
|
|
|
|
|
|
+ pointIndex: number,
|
|
deltaX: number,
|
|
deltaX: number,
|
|
deltaY: number,
|
|
deltaY: number,
|
|
pointerX: number,
|
|
pointerX: number,
|
|
pointerY: number,
|
|
pointerY: number,
|
|
perfect: boolean,
|
|
perfect: boolean,
|
|
) => {
|
|
) => {
|
|
|
|
+ const p1 = element.points[pointIndex];
|
|
if (perfect) {
|
|
if (perfect) {
|
|
const absPx = p1[0] + element.x;
|
|
const absPx = p1[0] + element.x;
|
|
const absPy = p1[1] + element.y;
|
|
const absPy = p1[1] + element.y;
|
|
@@ -1535,44 +1549,52 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
const dx = element.x + width + p1[0];
|
|
const dx = element.x + width + p1[0];
|
|
const dy = element.y + height + p1[1];
|
|
const dy = element.y + height + p1[1];
|
|
- p1[0] = absPx - element.x;
|
|
|
|
- p1[1] = absPy - element.y;
|
|
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
x: dx,
|
|
x: dx,
|
|
y: dy,
|
|
y: dy,
|
|
|
|
+ points: element.points.map((point, i) =>
|
|
|
|
+ i === pointIndex ? [absPx - element.x, absPy - element.y] : point,
|
|
|
|
+ ),
|
|
});
|
|
});
|
|
} else {
|
|
} else {
|
|
- p1[0] -= deltaX;
|
|
|
|
- p1[1] -= deltaY;
|
|
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
x: element.x + deltaX,
|
|
x: element.x + deltaX,
|
|
y: element.y + deltaY,
|
|
y: element.y + deltaY,
|
|
|
|
+ points: element.points.map((point, i) =>
|
|
|
|
+ i === pointIndex ? [p1[0] - deltaX, p1[1] - deltaY] : point,
|
|
|
|
+ ),
|
|
});
|
|
});
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
const arrowResizeEnd = (
|
|
const arrowResizeEnd = (
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- p1: Point,
|
|
|
|
|
|
+ pointIndex: number,
|
|
deltaX: number,
|
|
deltaX: number,
|
|
deltaY: number,
|
|
deltaY: number,
|
|
pointerX: number,
|
|
pointerX: number,
|
|
pointerY: number,
|
|
pointerY: number,
|
|
perfect: boolean,
|
|
perfect: boolean,
|
|
) => {
|
|
) => {
|
|
|
|
+ const p1 = element.points[pointIndex];
|
|
if (perfect) {
|
|
if (perfect) {
|
|
const { width, height } = getPerfectElementSize(
|
|
const { width, height } = getPerfectElementSize(
|
|
element.type,
|
|
element.type,
|
|
pointerX - element.x,
|
|
pointerX - element.x,
|
|
pointerY - element.y,
|
|
pointerY - element.y,
|
|
);
|
|
);
|
|
- p1[0] = width;
|
|
|
|
- p1[1] = height;
|
|
|
|
|
|
+ mutateElement(element, {
|
|
|
|
+ points: element.points.map((point, i) =>
|
|
|
|
+ i === pointIndex ? [width, height] : point,
|
|
|
|
+ ),
|
|
|
|
+ });
|
|
} else {
|
|
} else {
|
|
- p1[0] += deltaX;
|
|
|
|
- p1[1] += deltaY;
|
|
|
|
|
|
+ mutateElement(element, {
|
|
|
|
+ points: element.points.map((point, i) =>
|
|
|
|
+ i === pointIndex ? [p1[0] + deltaX, p1[1] + deltaY] : point,
|
|
|
|
+ ),
|
|
|
|
+ });
|
|
}
|
|
}
|
|
- mutateElement(element);
|
|
|
|
};
|
|
};
|
|
|
|
|
|
const onPointerMove = (event: PointerEvent) => {
|
|
const onPointerMove = (event: PointerEvent) => {
|
|
@@ -1623,7 +1645,10 @@ export class App extends React.Component<any, AppState> {
|
|
if (isResizingElements && this.state.resizingElement) {
|
|
if (isResizingElements && this.state.resizingElement) {
|
|
this.setState({ isResizing: true });
|
|
this.setState({ isResizing: true });
|
|
const el = this.state.resizingElement;
|
|
const el = this.state.resizingElement;
|
|
- const selectedElements = getSelectedElements(elements, this.state);
|
|
|
|
|
|
+ const selectedElements = getSelectedElements(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ this.state,
|
|
|
|
+ );
|
|
if (selectedElements.length === 1) {
|
|
if (selectedElements.length === 1) {
|
|
const { x, y } = viewportCoordsToSceneCoords(
|
|
const { x, y } = viewportCoordsToSceneCoords(
|
|
event,
|
|
event,
|
|
@@ -1646,15 +1671,7 @@ export class App extends React.Component<any, AppState> {
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- resizeArrowFn(
|
|
|
|
- element,
|
|
|
|
- p1,
|
|
|
|
- deltaX,
|
|
|
|
- deltaY,
|
|
|
|
- x,
|
|
|
|
- y,
|
|
|
|
- event.shiftKey,
|
|
|
|
- );
|
|
|
|
|
|
+ resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
} else {
|
|
} else {
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
x: element.x + deltaX,
|
|
x: element.x + deltaX,
|
|
@@ -1678,15 +1695,7 @@ export class App extends React.Component<any, AppState> {
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- resizeArrowFn(
|
|
|
|
- element,
|
|
|
|
- p1,
|
|
|
|
- deltaX,
|
|
|
|
- deltaY,
|
|
|
|
- x,
|
|
|
|
- y,
|
|
|
|
- event.shiftKey,
|
|
|
|
- );
|
|
|
|
|
|
+ resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
} else {
|
|
} else {
|
|
const nextWidth = element.width + deltaX;
|
|
const nextWidth = element.width + deltaX;
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
@@ -1708,15 +1717,7 @@ export class App extends React.Component<any, AppState> {
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- resizeArrowFn(
|
|
|
|
- element,
|
|
|
|
- p1,
|
|
|
|
- deltaX,
|
|
|
|
- deltaY,
|
|
|
|
- x,
|
|
|
|
- y,
|
|
|
|
- event.shiftKey,
|
|
|
|
- );
|
|
|
|
|
|
+ resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
} else {
|
|
} else {
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
x: element.x + deltaX,
|
|
x: element.x + deltaX,
|
|
@@ -1737,15 +1738,7 @@ export class App extends React.Component<any, AppState> {
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
resizeArrowFn = arrowResizeOrigin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- resizeArrowFn(
|
|
|
|
- element,
|
|
|
|
- p1,
|
|
|
|
- deltaX,
|
|
|
|
- deltaY,
|
|
|
|
- x,
|
|
|
|
- y,
|
|
|
|
- event.shiftKey,
|
|
|
|
- );
|
|
|
|
|
|
+ resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
} else {
|
|
} else {
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
width: element.width + deltaX,
|
|
width: element.width + deltaX,
|
|
@@ -1756,9 +1749,13 @@ export class App extends React.Component<any, AppState> {
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
case "n": {
|
|
case "n": {
|
|
|
|
+ let points;
|
|
if (element.points.length > 0) {
|
|
if (element.points.length > 0) {
|
|
const len = element.points.length;
|
|
const len = element.points.length;
|
|
- const points = [...element.points].sort((a, b) => a[1] - b[1]);
|
|
|
|
|
|
+ points = [...element.points].sort((a, b) => a[1] - b[1]) as [
|
|
|
|
+ number,
|
|
|
|
+ number,
|
|
|
|
+ ][];
|
|
|
|
|
|
for (let i = 1; i < points.length; ++i) {
|
|
for (let i = 1; i < points.length; ++i) {
|
|
const pnt = points[i];
|
|
const pnt = points[i];
|
|
@@ -1769,13 +1766,18 @@ export class App extends React.Component<any, AppState> {
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
height: element.height - deltaY,
|
|
height: element.height - deltaY,
|
|
y: element.y + deltaY,
|
|
y: element.y + deltaY,
|
|
|
|
+ points,
|
|
});
|
|
});
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
case "w": {
|
|
case "w": {
|
|
|
|
+ let points;
|
|
if (element.points.length > 0) {
|
|
if (element.points.length > 0) {
|
|
const len = element.points.length;
|
|
const len = element.points.length;
|
|
- const points = [...element.points].sort((a, b) => a[0] - b[0]);
|
|
|
|
|
|
+ points = [...element.points].sort((a, b) => a[0] - b[0]) as [
|
|
|
|
+ number,
|
|
|
|
+ number,
|
|
|
|
+ ][];
|
|
|
|
|
|
for (let i = 0; i < points.length; ++i) {
|
|
for (let i = 0; i < points.length; ++i) {
|
|
const pnt = points[i];
|
|
const pnt = points[i];
|
|
@@ -1786,13 +1788,19 @@ export class App extends React.Component<any, AppState> {
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
width: element.width - deltaX,
|
|
width: element.width - deltaX,
|
|
x: element.x + deltaX,
|
|
x: element.x + deltaX,
|
|
|
|
+ points,
|
|
});
|
|
});
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
case "s": {
|
|
case "s": {
|
|
|
|
+ let points;
|
|
|
|
+
|
|
if (element.points.length > 0) {
|
|
if (element.points.length > 0) {
|
|
const len = element.points.length;
|
|
const len = element.points.length;
|
|
- const points = [...element.points].sort((a, b) => a[1] - b[1]);
|
|
|
|
|
|
+ points = [...element.points].sort((a, b) => a[1] - b[1]) as [
|
|
|
|
+ number,
|
|
|
|
+ number,
|
|
|
|
+ ][];
|
|
|
|
|
|
for (let i = 1; i < points.length; ++i) {
|
|
for (let i = 1; i < points.length; ++i) {
|
|
const pnt = points[i];
|
|
const pnt = points[i];
|
|
@@ -1802,14 +1810,18 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
height: element.height + deltaY,
|
|
height: element.height + deltaY,
|
|
- points: element.points, // no-op, but signifies that we mutated points in-place above
|
|
|
|
|
|
+ points,
|
|
});
|
|
});
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
case "e": {
|
|
case "e": {
|
|
|
|
+ let points;
|
|
if (element.points.length > 0) {
|
|
if (element.points.length > 0) {
|
|
const len = element.points.length;
|
|
const len = element.points.length;
|
|
- const points = [...element.points].sort((a, b) => a[0] - b[0]);
|
|
|
|
|
|
+ points = [...element.points].sort((a, b) => a[0] - b[0]) as [
|
|
|
|
+ number,
|
|
|
|
+ number,
|
|
|
|
+ ][];
|
|
|
|
|
|
for (let i = 1; i < points.length; ++i) {
|
|
for (let i = 1; i < points.length; ++i) {
|
|
const pnt = points[i];
|
|
const pnt = points[i];
|
|
@@ -1819,7 +1831,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
mutateElement(element, {
|
|
mutateElement(element, {
|
|
width: element.width + deltaX,
|
|
width: element.width + deltaX,
|
|
- points: element.points, // no-op, but signifies that we mutated points in-place above
|
|
|
|
|
|
+ points,
|
|
});
|
|
});
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -1838,7 +1850,6 @@ export class App extends React.Component<any, AppState> {
|
|
x: element.x,
|
|
x: element.x,
|
|
y: element.y,
|
|
y: element.y,
|
|
});
|
|
});
|
|
- invalidateShapeForElement(el);
|
|
|
|
|
|
|
|
lastX = x;
|
|
lastX = x;
|
|
lastY = y;
|
|
lastY = y;
|
|
@@ -1851,7 +1862,10 @@ export class App extends React.Component<any, AppState> {
|
|
// Marking that click was used for dragging to check
|
|
// Marking that click was used for dragging to check
|
|
// if elements should be deselected on pointerup
|
|
// if elements should be deselected on pointerup
|
|
draggingOccurred = true;
|
|
draggingOccurred = true;
|
|
- const selectedElements = getSelectedElements(elements, this.state);
|
|
|
|
|
|
+ const selectedElements = getSelectedElements(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ this.state,
|
|
|
|
+ );
|
|
if (selectedElements.length > 0) {
|
|
if (selectedElements.length > 0) {
|
|
const { x, y } = viewportCoordsToSceneCoords(
|
|
const { x, y } = viewportCoordsToSceneCoords(
|
|
event,
|
|
event,
|
|
@@ -1906,14 +1920,12 @@ export class App extends React.Component<any, AppState> {
|
|
}
|
|
}
|
|
|
|
|
|
if (points.length === 1) {
|
|
if (points.length === 1) {
|
|
- points.push([dx, dy]);
|
|
|
|
|
|
+ mutateElement(draggingElement, { points: [...points, [dx, dy]] });
|
|
} else if (points.length > 1) {
|
|
} else if (points.length > 1) {
|
|
- const pnt = points[points.length - 1];
|
|
|
|
- pnt[0] = dx;
|
|
|
|
- pnt[1] = dy;
|
|
|
|
|
|
+ mutateElement(draggingElement, {
|
|
|
|
+ points: [...points.slice(0, -1), [dx, dy]],
|
|
|
|
+ });
|
|
}
|
|
}
|
|
-
|
|
|
|
- mutateElement(draggingElement, { points });
|
|
|
|
} else {
|
|
} else {
|
|
if (event.shiftKey) {
|
|
if (event.shiftKey) {
|
|
({ width, height } = getPerfectElementSize(
|
|
({ width, height } = getPerfectElementSize(
|
|
@@ -1935,14 +1947,15 @@ export class App extends React.Component<any, AppState> {
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
- invalidateShapeForElement(draggingElement);
|
|
|
|
-
|
|
|
|
if (this.state.elementType === "selection") {
|
|
if (this.state.elementType === "selection") {
|
|
- if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
|
|
|
|
|
+ if (
|
|
|
|
+ !event.shiftKey &&
|
|
|
|
+ isSomeElementSelected(scene.getAllElements(), this.state)
|
|
|
|
+ ) {
|
|
this.setState({ selectedElementIds: {} });
|
|
this.setState({ selectedElementIds: {} });
|
|
}
|
|
}
|
|
const elementsWithinSelection = getElementsWithinSelection(
|
|
const elementsWithinSelection = getElementsWithinSelection(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
draggingElement,
|
|
draggingElement,
|
|
);
|
|
);
|
|
this.setState(prevState => ({
|
|
this.setState(prevState => ({
|
|
@@ -1990,12 +2003,12 @@ export class App extends React.Component<any, AppState> {
|
|
this.state,
|
|
this.state,
|
|
this.canvas,
|
|
this.canvas,
|
|
);
|
|
);
|
|
- draggingElement.points.push([
|
|
|
|
- x - draggingElement.x,
|
|
|
|
- y - draggingElement.y,
|
|
|
|
- ]);
|
|
|
|
- mutateElement(draggingElement);
|
|
|
|
- invalidateShapeForElement(draggingElement);
|
|
|
|
|
|
+ mutateElement(draggingElement, {
|
|
|
|
+ points: [
|
|
|
|
+ ...draggingElement.points,
|
|
|
|
+ [x - draggingElement.x, y - draggingElement.y],
|
|
|
|
+ ],
|
|
|
|
+ });
|
|
this.setState({
|
|
this.setState({
|
|
multiElement: this.state.draggingElement,
|
|
multiElement: this.state.draggingElement,
|
|
editingElement: this.state.draggingElement,
|
|
editingElement: this.state.draggingElement,
|
|
@@ -2030,7 +2043,7 @@ 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
|
|
- elements = elements.slice(0, -1);
|
|
|
|
|
|
+ this.replaceElements(scene.getAllElements().slice(0, -1));
|
|
this.setState({
|
|
this.setState({
|
|
draggingElement: null,
|
|
draggingElement: null,
|
|
});
|
|
});
|
|
@@ -2047,7 +2060,9 @@ export class App extends React.Component<any, AppState> {
|
|
}
|
|
}
|
|
|
|
|
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
|
- elements = elements.filter(el => el.id !== resizingElement.id);
|
|
|
|
|
|
+ this.replaceElements(
|
|
|
|
+ scene.getAllElements().filter(el => el.id !== resizingElement.id),
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
// If click occurred on already selected element
|
|
// If click occurred on already selected element
|
|
@@ -2090,7 +2105,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
if (
|
|
if (
|
|
elementType !== "selection" ||
|
|
elementType !== "selection" ||
|
|
- isSomeElementSelected(elements, this.state)
|
|
|
|
|
|
+ isSomeElementSelected(scene.getAllElements(), this.state)
|
|
) {
|
|
) {
|
|
history.resumeRecording();
|
|
history.resumeRecording();
|
|
}
|
|
}
|
|
@@ -2163,7 +2178,7 @@ export class App extends React.Component<any, AppState> {
|
|
return duplicate;
|
|
return duplicate;
|
|
});
|
|
});
|
|
|
|
|
|
- elements = [...elements, ...newElements];
|
|
|
|
|
|
+ this.replaceElements([...scene.getAllElements(), ...newElements]);
|
|
history.resumeRecording();
|
|
history.resumeRecording();
|
|
this.setState({
|
|
this.setState({
|
|
selectedElementIds: newElements.reduce((map, element) => {
|
|
selectedElementIds: newElements.reduce((map, element) => {
|
|
@@ -2174,7 +2189,11 @@ export class App extends React.Component<any, AppState> {
|
|
};
|
|
};
|
|
|
|
|
|
private getTextWysiwygSnappedToCenterPosition(x: number, y: number) {
|
|
private getTextWysiwygSnappedToCenterPosition(x: number, y: number) {
|
|
- const elementClickedInside = getElementContainingPosition(elements, x, y);
|
|
|
|
|
|
+ const elementClickedInside = getElementContainingPosition(
|
|
|
|
+ scene.getAllElements(),
|
|
|
|
+ x,
|
|
|
|
+ y,
|
|
|
|
+ );
|
|
if (elementClickedInside) {
|
|
if (elementClickedInside) {
|
|
const elementCenterX =
|
|
const elementCenterX =
|
|
elementClickedInside.x + elementClickedInside.width / 2;
|
|
elementClickedInside.x + elementClickedInside.width / 2;
|
|
@@ -2209,7 +2228,7 @@ export class App extends React.Component<any, AppState> {
|
|
};
|
|
};
|
|
|
|
|
|
private saveDebounced = debounce(() => {
|
|
private saveDebounced = debounce(() => {
|
|
- saveToLocalStorage(elements, this.state);
|
|
|
|
|
|
+ saveToLocalStorage(scene.getAllElements(), this.state);
|
|
}, 300);
|
|
}, 300);
|
|
|
|
|
|
componentDidUpdate() {
|
|
componentDidUpdate() {
|
|
@@ -2233,7 +2252,7 @@ export class App extends React.Component<any, AppState> {
|
|
);
|
|
);
|
|
});
|
|
});
|
|
const { atLeastOneVisibleElement, scrollBars } = renderScene(
|
|
const { atLeastOneVisibleElement, scrollBars } = renderScene(
|
|
- elements,
|
|
|
|
|
|
+ scene.getAllElements(),
|
|
this.state,
|
|
this.state,
|
|
this.state.selectionElement,
|
|
this.state.selectionElement,
|
|
window.devicePixelRatio,
|
|
window.devicePixelRatio,
|
|
@@ -2254,20 +2273,22 @@ export class App extends React.Component<any, AppState> {
|
|
currentScrollBars = scrollBars;
|
|
currentScrollBars = scrollBars;
|
|
}
|
|
}
|
|
const scrolledOutside =
|
|
const scrolledOutside =
|
|
- !atLeastOneVisibleElement && hasNonDeletedElements(elements);
|
|
|
|
|
|
+ !atLeastOneVisibleElement &&
|
|
|
|
+ hasNonDeletedElements(scene.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(elements) > this.lastBroadcastedOrReceivedSceneVersion
|
|
|
|
|
|
+ getDrawingVersion(scene.getAllElements()) >
|
|
|
|
+ this.lastBroadcastedOrReceivedSceneVersion
|
|
) {
|
|
) {
|
|
this.broadcastSceneUpdate();
|
|
this.broadcastSceneUpdate();
|
|
}
|
|
}
|
|
|
|
|
|
if (history.isRecording()) {
|
|
if (history.isRecording()) {
|
|
- history.pushEntry(this.state, elements);
|
|
|
|
|
|
+ history.pushEntry(this.state, scene.getAllElements());
|
|
history.skipRecording();
|
|
history.skipRecording();
|
|
}
|
|
}
|
|
}
|
|
}
|