|
|
|
@@ -183,6 +183,7 @@ import {
|
|
|
|
|
ExcalidrawIframeElement,
|
|
|
|
|
ExcalidrawEmbeddableElement,
|
|
|
|
|
Ordered,
|
|
|
|
|
OrderedExcalidrawElement,
|
|
|
|
|
} from "../element/types";
|
|
|
|
|
import { getCenter, getDistance } from "../gesture";
|
|
|
|
|
import {
|
|
|
|
@@ -194,7 +195,7 @@ import {
|
|
|
|
|
isSelectedViaGroup,
|
|
|
|
|
selectGroupsForSelectedElements,
|
|
|
|
|
} from "../groups";
|
|
|
|
|
import History from "../history";
|
|
|
|
|
import { History } from "../history";
|
|
|
|
|
import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
|
|
|
|
|
import {
|
|
|
|
|
CODES,
|
|
|
|
@@ -278,11 +279,12 @@ import {
|
|
|
|
|
muteFSAbortError,
|
|
|
|
|
isTestEnv,
|
|
|
|
|
easeOut,
|
|
|
|
|
arrayToMap,
|
|
|
|
|
updateStable,
|
|
|
|
|
addEventListener,
|
|
|
|
|
normalizeEOL,
|
|
|
|
|
getDateTime,
|
|
|
|
|
isShallowEqual,
|
|
|
|
|
arrayToMap,
|
|
|
|
|
} from "../utils";
|
|
|
|
|
import {
|
|
|
|
|
createSrcDoc,
|
|
|
|
@@ -410,6 +412,7 @@ import { ElementCanvasButton } from "./MagicButton";
|
|
|
|
|
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
|
|
|
|
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
|
|
|
|
import FollowMode from "./FollowMode/FollowMode";
|
|
|
|
|
import { IStore, Store, StoreAction } from "../store";
|
|
|
|
|
import { AnimationFrameHandler } from "../animation-frame-handler";
|
|
|
|
|
import { AnimatedTrail } from "../animated-trail";
|
|
|
|
|
import { LaserTrails } from "../laser-trails";
|
|
|
|
@@ -540,6 +543,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
public library: AppClassProperties["library"];
|
|
|
|
|
public libraryItemsFromStorage: LibraryItems | undefined;
|
|
|
|
|
public id: string;
|
|
|
|
|
private store: IStore;
|
|
|
|
|
private history: History;
|
|
|
|
|
private excalidrawContainerValue: {
|
|
|
|
|
container: HTMLDivElement | null;
|
|
|
|
@@ -665,6 +669,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.canvas = document.createElement("canvas");
|
|
|
|
|
this.rc = rough.canvas(this.canvas);
|
|
|
|
|
this.renderer = new Renderer(this.scene);
|
|
|
|
|
|
|
|
|
|
this.store = new Store();
|
|
|
|
|
this.history = new History();
|
|
|
|
|
|
|
|
|
|
if (excalidrawAPI) {
|
|
|
|
|
const api: ExcalidrawImperativeAPI = {
|
|
|
|
|
updateScene: this.updateScene,
|
|
|
|
@@ -714,10 +722,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
onSceneUpdated: this.onSceneUpdated,
|
|
|
|
|
});
|
|
|
|
|
this.history = new History();
|
|
|
|
|
this.actionManager.registerAll(actions);
|
|
|
|
|
|
|
|
|
|
this.actionManager.registerAction(createUndoAction(this.history));
|
|
|
|
|
this.actionManager.registerAction(createRedoAction(this.history));
|
|
|
|
|
this.actionManager.registerAll(actions);
|
|
|
|
|
this.actionManager.registerAction(
|
|
|
|
|
createUndoAction(this.history, this.store),
|
|
|
|
|
);
|
|
|
|
|
this.actionManager.registerAction(
|
|
|
|
|
createRedoAction(this.history, this.store),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onWindowMessage(event: MessageEvent) {
|
|
|
|
@@ -2092,12 +2104,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
if (shouldUpdateStrokeColor) {
|
|
|
|
|
this.syncActionResult({
|
|
|
|
|
appState: { ...this.state, currentItemStrokeColor: color },
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
storeAction: StoreAction.CAPTURE,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.syncActionResult({
|
|
|
|
|
appState: { ...this.state, currentItemBackgroundColor: color },
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
storeAction: StoreAction.CAPTURE,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
@@ -2111,6 +2123,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
return el;
|
|
|
|
|
}),
|
|
|
|
|
commitToStore: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
@@ -2135,10 +2148,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
editingElement = element;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.scene.replaceAllElements(actionResult.elements);
|
|
|
|
|
if (actionResult.commitToHistory) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
|
|
|
|
|
if (actionResult.storeAction === StoreAction.UPDATE) {
|
|
|
|
|
this.store.shouldUpdateSnapshot();
|
|
|
|
|
} else if (actionResult.storeAction === StoreAction.CAPTURE) {
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.scene.replaceAllElements(actionResult.elements);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actionResult.files) {
|
|
|
|
@@ -2149,8 +2166,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actionResult.appState || editingElement || this.state.contextMenu) {
|
|
|
|
|
if (actionResult.commitToHistory) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
if (actionResult.storeAction === StoreAction.UPDATE) {
|
|
|
|
|
this.store.shouldUpdateSnapshot();
|
|
|
|
|
} else if (actionResult.storeAction === StoreAction.CAPTURE) {
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
|
|
|
|
@@ -2180,34 +2199,24 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
editingElement = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState(
|
|
|
|
|
(state) => {
|
|
|
|
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
|
|
|
|
// regarding the resulting type as not containing undefined
|
|
|
|
|
// (which the following expression will never contain)
|
|
|
|
|
return Object.assign(actionResult.appState || {}, {
|
|
|
|
|
// NOTE this will prevent opening context menu using an action
|
|
|
|
|
// or programmatically from the host, so it will need to be
|
|
|
|
|
// rewritten later
|
|
|
|
|
contextMenu: null,
|
|
|
|
|
editingElement,
|
|
|
|
|
viewModeEnabled,
|
|
|
|
|
zenModeEnabled,
|
|
|
|
|
gridSize,
|
|
|
|
|
theme,
|
|
|
|
|
name,
|
|
|
|
|
errorMessage,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
if (actionResult.syncHistory) {
|
|
|
|
|
this.history.setCurrentState(
|
|
|
|
|
this.state,
|
|
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
this.setState((state) => {
|
|
|
|
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
|
|
|
|
// regarding the resulting type as not containing undefined
|
|
|
|
|
// (which the following expression will never contain)
|
|
|
|
|
return Object.assign(actionResult.appState || {}, {
|
|
|
|
|
// NOTE this will prevent opening context menu using an action
|
|
|
|
|
// or programmatically from the host, so it will need to be
|
|
|
|
|
// rewritten later
|
|
|
|
|
contextMenu: null,
|
|
|
|
|
editingElement,
|
|
|
|
|
viewModeEnabled,
|
|
|
|
|
zenModeEnabled,
|
|
|
|
|
gridSize,
|
|
|
|
|
theme,
|
|
|
|
|
name,
|
|
|
|
|
errorMessage,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
@@ -2231,6 +2240,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.history.clear();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private resetStore = () => {
|
|
|
|
|
this.store.clear();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resets scene & history.
|
|
|
|
|
* ! Do not use to clear scene user action !
|
|
|
|
@@ -2243,6 +2256,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
isLoading: opts?.resetLoadingState ? false : state.isLoading,
|
|
|
|
|
theme: this.state.theme,
|
|
|
|
|
}));
|
|
|
|
|
this.resetStore();
|
|
|
|
|
this.resetHistory();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
@@ -2327,10 +2341,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
// seems faster even in browsers that do fire the loadingdone event.
|
|
|
|
|
this.fonts.loadFontsForElements(scene.elements);
|
|
|
|
|
|
|
|
|
|
this.resetStore();
|
|
|
|
|
this.resetHistory();
|
|
|
|
|
this.syncActionResult({
|
|
|
|
|
...scene,
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
storeAction: StoreAction.UPDATE,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -2420,9 +2435,17 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: this.history,
|
|
|
|
|
},
|
|
|
|
|
store: {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: this.store,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.store.onStoreIncrementEmitter.on((increment) => {
|
|
|
|
|
this.history.record(increment.elementsChange, increment.appStateChange);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.addCallback(this.onSceneUpdated);
|
|
|
|
|
this.addEventListeners();
|
|
|
|
|
|
|
|
|
@@ -2479,6 +2502,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.laserTrails.stop();
|
|
|
|
|
this.eraserTrail.stop();
|
|
|
|
|
this.onChangeEmitter.clear();
|
|
|
|
|
this.store.onStoreIncrementEmitter.clear();
|
|
|
|
|
ShapeCache.destroy();
|
|
|
|
|
SnapCache.destroy();
|
|
|
|
|
clearTimeout(touchTimeout);
|
|
|
|
@@ -2623,7 +2647,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
|
|
|
|
this.updateEmbeddables();
|
|
|
|
|
const elements = this.scene.getElementsIncludingDeleted();
|
|
|
|
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
|
|
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
|
|
|
|
const nonDeletedElementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
|
|
|
|
|
|
|
if (!this.state.showWelcomeScreen && !elements.length) {
|
|
|
|
|
this.setState({ showWelcomeScreen: true });
|
|
|
|
@@ -2739,7 +2764,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.state.editingLinearElement &&
|
|
|
|
|
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]
|
|
|
|
|
) {
|
|
|
|
|
// defer so that the commitToHistory flag isn't reset via current update
|
|
|
|
|
// defer so that the storeAction flag isn't reset via current update
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
// execute only if the condition still holds when the deferred callback
|
|
|
|
|
// executes (it can be scheduled multiple times depending on how
|
|
|
|
@@ -2778,13 +2803,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
|
|
|
|
multiElement,
|
|
|
|
|
-1,
|
|
|
|
|
elementsMap,
|
|
|
|
|
nonDeletedElementsMap,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
this,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
this.history.record(this.state, elements);
|
|
|
|
|
|
|
|
|
|
this.store.capture(elementsMap, this.state);
|
|
|
|
|
|
|
|
|
|
// Do not notify consumers if we're still loading the scene. Among other
|
|
|
|
|
// potential issues, this fixes a case where the tab isn't focused during
|
|
|
|
@@ -3154,7 +3180,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.files = { ...this.files, ...opts.files };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
|
|
|
|
|
const nextElementsToSelect =
|
|
|
|
|
excludeElementsInFramesFromSelection(newElements);
|
|
|
|
@@ -3389,7 +3415,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
PLAIN_PASTE_TOAST_SHOWN = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"] = (
|
|
|
|
@@ -3657,10 +3683,51 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
elements?: SceneData["elements"];
|
|
|
|
|
appState?: Pick<AppState, K> | null;
|
|
|
|
|
collaborators?: SceneData["collaborators"];
|
|
|
|
|
commitToHistory?: SceneData["commitToHistory"];
|
|
|
|
|
commitToStore?: SceneData["commitToStore"];
|
|
|
|
|
}) => {
|
|
|
|
|
if (sceneData.commitToHistory) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
const nextElements = syncInvalidIndices(sceneData.elements ?? []);
|
|
|
|
|
|
|
|
|
|
if (sceneData.commitToStore) {
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sceneData.elements || sceneData.appState) {
|
|
|
|
|
let nextCommittedAppState = this.state;
|
|
|
|
|
let nextCommittedElements: Map<string, OrderedExcalidrawElement>;
|
|
|
|
|
|
|
|
|
|
if (sceneData.appState) {
|
|
|
|
|
nextCommittedAppState = {
|
|
|
|
|
...this.state,
|
|
|
|
|
...sceneData.appState, // Here we expect just partial appState
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const prevElements = this.scene.getElementsIncludingDeleted();
|
|
|
|
|
|
|
|
|
|
if (sceneData.elements) {
|
|
|
|
|
/**
|
|
|
|
|
* We need to schedule a snapshot update, as in case `commitToStore` is false (i.e. remote update),
|
|
|
|
|
* as it's essential for computing local changes after the async action is completed (i.e. not to include remote changes in the diff).
|
|
|
|
|
*
|
|
|
|
|
* This is also a breaking change for all local `updateScene` calls without set `commitToStore` to true,
|
|
|
|
|
* as it makes such updates impossible to undo (previously they were undone coincidentally with the switch to the whole snapshot captured by the history).
|
|
|
|
|
*
|
|
|
|
|
* WARN: be careful here as moving it elsewhere could break the history for remote client without noticing
|
|
|
|
|
* - we need to find a way to test two concurrent client updates simultaneously, while having access to both stores & histories.
|
|
|
|
|
*/
|
|
|
|
|
this.store.shouldUpdateSnapshot();
|
|
|
|
|
|
|
|
|
|
// TODO#7348: deprecate once exchanging just store increments between clients
|
|
|
|
|
nextCommittedElements = this.store.ignoreUncomittedElements(
|
|
|
|
|
arrayToMap(prevElements),
|
|
|
|
|
arrayToMap(nextElements),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
nextCommittedElements = arrayToMap(prevElements);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WARN: Performs deep clone of changed elements, for ephemeral remote updates (i.e. remote dragging, resizing, drawing) we might consider doing something smarter
|
|
|
|
|
this.store.capture(nextCommittedElements, nextCommittedAppState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sceneData.appState) {
|
|
|
|
@@ -3668,7 +3735,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sceneData.elements) {
|
|
|
|
|
this.scene.replaceAllElements(sceneData.elements);
|
|
|
|
|
this.scene.replaceAllElements(nextElements);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sceneData.collaborators) {
|
|
|
|
@@ -3896,7 +3963,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.state.editingLinearElement.elementId !==
|
|
|
|
|
selectedElements[0].id
|
|
|
|
|
) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
this.setState({
|
|
|
|
|
editingLinearElement: new LinearElementEditor(
|
|
|
|
|
selectedElement,
|
|
|
|
@@ -4308,7 +4375,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if (!isDeleted || isExistingElement) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
@@ -4793,7 +4860,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
(!this.state.editingLinearElement ||
|
|
|
|
|
this.state.editingLinearElement.elementId !== selectedElements[0].id)
|
|
|
|
|
) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
this.setState({
|
|
|
|
|
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
|
|
|
|
});
|
|
|
|
@@ -4818,6 +4885,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
getSelectedGroupIdForElement(hitElement, this.state.selectedGroupIds);
|
|
|
|
|
|
|
|
|
|
if (selectedGroupId) {
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
this.setState((prevState) => ({
|
|
|
|
|
...prevState,
|
|
|
|
|
...selectGroupsForSelectedElements(
|
|
|
|
@@ -6300,7 +6368,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
const ret = LinearElementEditor.handlePointerDown(
|
|
|
|
|
event,
|
|
|
|
|
this.state,
|
|
|
|
|
this.history,
|
|
|
|
|
this.store,
|
|
|
|
|
pointerDownState.origin,
|
|
|
|
|
linearElementEditor,
|
|
|
|
|
this,
|
|
|
|
@@ -7848,7 +7916,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
|
|
|
|
if (isLinearElement(draggingElement)) {
|
|
|
|
|
if (draggingElement!.points.length > 1) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
const pointerCoords = viewportCoordsToSceneCoords(
|
|
|
|
|
childEvent,
|
|
|
|
@@ -7917,14 +7985,16 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
isInvisiblySmallElement(draggingElement)
|
|
|
|
|
) {
|
|
|
|
|
// remove invisible element which was added in onPointerDown
|
|
|
|
|
this.scene.replaceAllElements(
|
|
|
|
|
this.scene
|
|
|
|
|
// update the store snapshot, so that invisible elements are not captured by the store
|
|
|
|
|
this.updateScene({
|
|
|
|
|
elements: this.scene
|
|
|
|
|
.getElementsIncludingDeleted()
|
|
|
|
|
.filter((el) => el.id !== draggingElement.id),
|
|
|
|
|
);
|
|
|
|
|
this.setState({
|
|
|
|
|
draggingElement: null,
|
|
|
|
|
appState: {
|
|
|
|
|
draggingElement: null,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -8086,15 +8156,16 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resizingElement) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
|
|
|
|
this.scene.replaceAllElements(
|
|
|
|
|
this.scene
|
|
|
|
|
// update the store snapshot, so that invisible elements are not captured by the store
|
|
|
|
|
this.updateScene({
|
|
|
|
|
elements: this.scene
|
|
|
|
|
.getElementsIncludingDeleted()
|
|
|
|
|
.filter((el) => el.id !== resizingElement.id),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle frame membership for resizing frames and/or selected elements
|
|
|
|
@@ -8395,9 +8466,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
activeTool.type !== "selection" ||
|
|
|
|
|
isSomeElementSelected(this.scene.getNonDeletedElements(), this.state)
|
|
|
|
|
isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) ||
|
|
|
|
|
!isShallowEqual(
|
|
|
|
|
this.state.previousSelectedElementIds,
|
|
|
|
|
this.state.selectedElementIds,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
|
|
|
@@ -8475,7 +8550,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.elementsPendingErasure = new Set();
|
|
|
|
|
|
|
|
|
|
if (didChange) {
|
|
|
|
|
this.history.resumeRecording();
|
|
|
|
|
this.store.shouldCaptureIncrement();
|
|
|
|
|
this.scene.replaceAllElements(elements);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
@@ -9038,7 +9113,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
isLoading: false,
|
|
|
|
|
},
|
|
|
|
|
replaceFiles: true,
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
storeAction: StoreAction.CAPTURE,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
@@ -9118,12 +9193,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
) => {
|
|
|
|
|
file = await normalizeFile(file);
|
|
|
|
|
try {
|
|
|
|
|
const elements = this.scene.getElementsIncludingDeleted();
|
|
|
|
|
let ret;
|
|
|
|
|
try {
|
|
|
|
|
ret = await loadSceneOrLibraryFromBlob(
|
|
|
|
|
file,
|
|
|
|
|
this.state,
|
|
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
|
|
|
elements,
|
|
|
|
|
fileHandle,
|
|
|
|
|
);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
@@ -9152,6 +9228,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ret.type === MIME_TYPES.excalidraw) {
|
|
|
|
|
// Restore the fractional indices by mutating elements and update the
|
|
|
|
|
// store snapshot, otherwise we would end up with duplicate indices
|
|
|
|
|
syncInvalidIndices(elements.concat(ret.data.elements));
|
|
|
|
|
this.store.snapshot = this.store.snapshot.clone(
|
|
|
|
|
arrayToMap(elements),
|
|
|
|
|
this.state,
|
|
|
|
|
);
|
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
|
this.syncActionResult({
|
|
|
|
|
...ret.data,
|
|
|
|
@@ -9160,7 +9243,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
isLoading: false,
|
|
|
|
|
},
|
|
|
|
|
replaceFiles: true,
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
storeAction: StoreAction.CAPTURE,
|
|
|
|
|
});
|
|
|
|
|
} else if (ret.type === MIME_TYPES.excalidrawlib) {
|
|
|
|
|
await this.library
|
|
|
|
@@ -9770,6 +9853,7 @@ declare global {
|
|
|
|
|
setState: React.Component<any, AppState>["setState"];
|
|
|
|
|
app: InstanceType<typeof App>;
|
|
|
|
|
history: History;
|
|
|
|
|
store: Store;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|