|
|
|
@ -20,6 +20,34 @@ const LOCAL_STORAGE_KEY_STATE = "excalidraw-state";
|
|
|
|
|
|
|
|
|
|
const elements = Array.of<ExcalidrawElement>();
|
|
|
|
|
|
|
|
|
|
let skipHistory = false;
|
|
|
|
|
const stateHistory: string[] = [];
|
|
|
|
|
function generateHistoryCurrentEntry() {
|
|
|
|
|
return JSON.stringify(
|
|
|
|
|
elements.map(element => ({ ...element, isSelected: false }))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function pushHistoryEntry(newEntry: string) {
|
|
|
|
|
if (
|
|
|
|
|
stateHistory.length > 0 &&
|
|
|
|
|
stateHistory[stateHistory.length - 1] === newEntry
|
|
|
|
|
) {
|
|
|
|
|
// If the last entry is the same as this one, ignore it
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
stateHistory.push(newEntry);
|
|
|
|
|
}
|
|
|
|
|
function restoreHistoryEntry(entry: string) {
|
|
|
|
|
const newElements = JSON.parse(entry);
|
|
|
|
|
elements.splice(0, elements.length);
|
|
|
|
|
newElements.forEach((newElement: ExcalidrawElement) => {
|
|
|
|
|
generateDraw(newElement);
|
|
|
|
|
elements.push(newElement);
|
|
|
|
|
});
|
|
|
|
|
// When restoring, we shouldn't add an history entry otherwise we'll be stuck with it and can't go back
|
|
|
|
|
skipHistory = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript/47593316#47593316
|
|
|
|
|
const LCG = (seed: number) => () =>
|
|
|
|
|
((2 ** 31 - 1) & (seed = Math.imul(48271, seed))) / 2 ** 31;
|
|
|
|
@ -901,6 +929,17 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
} else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {
|
|
|
|
|
this.setState({ elementType: findElementByKey(event.key) });
|
|
|
|
|
} else if (event.metaKey && event.code === "KeyZ") {
|
|
|
|
|
let lastEntry = stateHistory.pop();
|
|
|
|
|
// If nothing was changed since last, take the previous one
|
|
|
|
|
if (generateHistoryCurrentEntry() === lastEntry) {
|
|
|
|
|
lastEntry = stateHistory.pop();
|
|
|
|
|
}
|
|
|
|
|
if (lastEntry !== undefined) {
|
|
|
|
|
restoreHistoryEntry(lastEntry);
|
|
|
|
|
}
|
|
|
|
|
this.forceUpdate();
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -1301,6 +1340,8 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
});
|
|
|
|
|
lastX = x;
|
|
|
|
|
lastY = y;
|
|
|
|
|
// We don't want to save history when resizing an element
|
|
|
|
|
skipHistory = true;
|
|
|
|
|
this.forceUpdate();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@ -1319,6 +1360,8 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
});
|
|
|
|
|
lastX = x;
|
|
|
|
|
lastY = y;
|
|
|
|
|
// We don't want to save history when dragging an element to initially size it
|
|
|
|
|
skipHistory = true;
|
|
|
|
|
this.forceUpdate();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@ -1347,6 +1390,8 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
if (this.state.elementType === "selection") {
|
|
|
|
|
setSelection(draggingElement);
|
|
|
|
|
}
|
|
|
|
|
// We don't want to save history when moving an element
|
|
|
|
|
skipHistory = true;
|
|
|
|
|
this.forceUpdate();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -1384,6 +1429,8 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
window.addEventListener("mousemove", onMouseMove);
|
|
|
|
|
window.addEventListener("mouseup", onMouseUp);
|
|
|
|
|
|
|
|
|
|
// We don't want to save history on mouseDown, only on mouseUp when it's fully configured
|
|
|
|
|
skipHistory = true;
|
|
|
|
|
this.forceUpdate();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
@ -1407,6 +1454,10 @@ class App extends React.Component<{}, AppState> {
|
|
|
|
|
viewBackgroundColor: this.state.viewBackgroundColor
|
|
|
|
|
});
|
|
|
|
|
save(this.state);
|
|
|
|
|
if (!skipHistory) {
|
|
|
|
|
pushHistoryEntry(generateHistoryCurrentEntry());
|
|
|
|
|
}
|
|
|
|
|
skipHistory = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|