Add NonDeleted<ExcalidrawElement> (#1068)

* add NonDeleted

* make test:all script run tests without prompt

* rename helper

* replace with helper

* make element contructors return nonDeleted elements

* cache filtered elements where appliacable for better perf

* rename manager element getter

* remove unnecessary assertion

* fix test

* make element types in resizeElement into nonDeleted

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Pete Hunt 2020-04-08 09:49:52 -07:00 committed by GitHub
parent c714c778ab
commit df0613d8ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 260 additions and 189 deletions

View file

@ -20,7 +20,6 @@ import {
getElementMap,
getDrawingVersion,
getSyncableElements,
hasNonDeletedElements,
newLinearElement,
ResizeArrowFnType,
resizeElements,
@ -185,7 +184,7 @@ export class App extends React.Component<any, AppState> {
this.actionManager = new ActionManager(
this.syncActionResult,
() => this.state,
() => globalSceneState.getAllElements(),
() => globalSceneState.getElementsIncludingDeleted(),
);
this.actionManager.registerAll(actions);
@ -209,10 +208,7 @@ export class App extends React.Component<any, AppState> {
appState={this.state}
setAppState={this.setAppState}
actionManager={this.actionManager}
elements={globalSceneState.getAllElements().filter((element) => {
return !element.isDeleted;
})}
setElements={this.setElements}
elements={globalSceneState.getElements()}
onRoomCreate={this.openPortal}
onRoomDestroy={this.closePortal}
onLockToggle={this.toggleLock}
@ -310,7 +306,7 @@ export class App extends React.Component<any, AppState> {
try {
await Promise.race([
document.fonts?.ready?.then(() => {
globalSceneState.getAllElements().forEach((element) => {
globalSceneState.getElementsIncludingDeleted().forEach((element) => {
if (isTextElement(element)) {
invalidateShapeForElement(element);
}
@ -431,7 +427,7 @@ export class App extends React.Component<any, AppState> {
}
private onResize = withBatchedUpdates(() => {
globalSceneState
.getAllElements()
.getElementsIncludingDeleted()
.forEach((element) => invalidateShapeForElement(element));
this.setState({});
});
@ -439,7 +435,7 @@ export class App extends React.Component<any, AppState> {
private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => {
if (
this.state.isCollaborating &&
hasNonDeletedElements(globalSceneState.getAllElements())
globalSceneState.getElements().length > 0
) {
event.preventDefault();
// NOTE: modern browsers no longer allow showing a custom message here
@ -484,8 +480,9 @@ export class App extends React.Component<any, AppState> {
);
cursorButton[socketID] = user.button;
});
const elements = globalSceneState.getElements();
const { atLeastOneVisibleElement, scrollBars } = renderScene(
globalSceneState.getAllElements().filter((element) => {
elements.filter((element) => {
// don't render text element that's being currently edited (it's
// rendered on remote only)
return (
@ -517,22 +514,20 @@ export class App extends React.Component<any, AppState> {
if (scrollBars) {
currentScrollBars = scrollBars;
}
const scrolledOutside =
!atLeastOneVisibleElement &&
hasNonDeletedElements(globalSceneState.getAllElements());
const scrolledOutside = !atLeastOneVisibleElement && elements.length > 0;
if (this.state.scrolledOutside !== scrolledOutside) {
this.setState({ scrolledOutside: scrolledOutside });
}
this.saveDebounced();
if (
getDrawingVersion(globalSceneState.getAllElements()) >
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) >
this.lastBroadcastedOrReceivedSceneVersion
) {
this.broadcastScene("SCENE_UPDATE");
}
history.record(this.state, globalSceneState.getAllElements());
history.record(this.state, globalSceneState.getElementsIncludingDeleted());
}
// Copy/paste
@ -543,7 +538,7 @@ export class App extends React.Component<any, AppState> {
}
this.copyAll();
const { elements: nextElements, appState } = deleteSelectedElements(
globalSceneState.getAllElements(),
globalSceneState.getElementsIncludingDeleted(),
this.state,
);
globalSceneState.replaceAllElements(nextElements);
@ -561,19 +556,16 @@ export class App extends React.Component<any, AppState> {
});
private copyAll = () => {
copyToAppClipboard(globalSceneState.getAllElements(), this.state);
copyToAppClipboard(globalSceneState.getElements(), this.state);
};
private copyToClipboardAsPng = () => {
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
this.state,
);
const elements = globalSceneState.getElements();
const selectedElements = getSelectedElements(elements, this.state);
exportCanvas(
"clipboard",
selectedElements.length
? selectedElements
: globalSceneState.getAllElements(),
selectedElements.length ? selectedElements : elements,
this.state,
this.canvas!,
this.state,
@ -582,14 +574,14 @@ export class App extends React.Component<any, AppState> {
private copyToClipboardAsSvg = () => {
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
globalSceneState.getElements(),
this.state,
);
exportCanvas(
"clipboard-svg",
selectedElements.length
? selectedElements
: globalSceneState.getAllElements(),
: globalSceneState.getElements(),
this.state,
this.canvas!,
this.state,
@ -669,7 +661,7 @@ export class App extends React.Component<any, AppState> {
);
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...globalSceneState.getElementsIncludingDeleted(),
...newElements,
]);
history.resumeRecording();
@ -703,7 +695,7 @@ export class App extends React.Component<any, AppState> {
});
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...globalSceneState.getElementsIncludingDeleted(),
element,
]);
this.setState({ selectedElementIds: { [element.id]: true } });
@ -789,15 +781,15 @@ export class App extends React.Component<any, AppState> {
// elements with more staler versions than ours, ignore them
// and keep ours.
if (
globalSceneState.getAllElements() == null ||
globalSceneState.getAllElements().length === 0
globalSceneState.getElementsIncludingDeleted() == null ||
globalSceneState.getElementsIncludingDeleted().length === 0
) {
globalSceneState.replaceAllElements(remoteElements);
} else {
// create a map of ids so we don't have to iterate
// over the array more than once.
const localElementMap = getElementMap(
globalSceneState.getAllElements(),
globalSceneState.getElementsIncludingDeleted(),
);
// Reconcile
@ -982,12 +974,14 @@ export class App extends React.Component<any, AppState> {
const data: SocketUpdateDataSource[typeof sceneType] = {
type: sceneType,
payload: {
elements: getSyncableElements(globalSceneState.getAllElements()),
elements: getSyncableElements(
globalSceneState.getElementsIncludingDeleted(),
),
},
};
this.lastBroadcastedOrReceivedSceneVersion = Math.max(
this.lastBroadcastedOrReceivedSceneVersion,
getDrawingVersion(globalSceneState.getAllElements()),
getDrawingVersion(globalSceneState.getElementsIncludingDeleted()),
);
return this._broadcastSocketData(
data as typeof data & { _brand: "socketUpdateData" },
@ -1063,7 +1057,7 @@ export class App extends React.Component<any, AppState> {
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
: ELEMENT_TRANSLATE_AMOUNT;
globalSceneState.replaceAllElements(
globalSceneState.getAllElements().map((el) => {
globalSceneState.getElementsIncludingDeleted().map((el) => {
if (this.state.selectedElementIds[el.id]) {
const update: { x?: number; y?: number } = {};
if (event.key === KEYS.ARROW_LEFT) {
@ -1083,7 +1077,7 @@ export class App extends React.Component<any, AppState> {
event.preventDefault();
} else if (event.key === KEYS.ENTER) {
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
globalSceneState.getElements(),
this.state,
);
@ -1188,7 +1182,7 @@ export class App extends React.Component<any, AppState> {
const deleteElement = () => {
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements().map((_element) => {
...globalSceneState.getElementsIncludingDeleted().map((_element) => {
if (_element.id === element.id) {
return newElementWith(_element, { isDeleted: true });
}
@ -1199,7 +1193,7 @@ export class App extends React.Component<any, AppState> {
const updateElement = (text: string) => {
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements().map((_element) => {
...globalSceneState.getElementsIncludingDeleted().map((_element) => {
if (_element.id === element.id) {
return newTextElement({
...(_element as ExcalidrawTextElement),
@ -1271,7 +1265,7 @@ export class App extends React.Component<any, AppState> {
centerIfPossible?: boolean;
}) => {
const elementAtPosition = getElementAtPosition(
globalSceneState.getAllElements(),
globalSceneState.getElements(),
this.state,
x,
y,
@ -1326,7 +1320,7 @@ export class App extends React.Component<any, AppState> {
});
} else {
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...globalSceneState.getElementsIncludingDeleted(),
element,
]);
@ -1503,13 +1497,12 @@ export class App extends React.Component<any, AppState> {
return;
}
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
this.state,
);
const elements = globalSceneState.getElements();
const selectedElements = getSelectedElements(elements, this.state);
if (selectedElements.length === 1 && !isOverScrollBar) {
const elementWithResizeHandler = getElementWithResizeHandler(
globalSceneState.getAllElements(),
elements,
this.state,
{ x, y },
this.state.zoom,
@ -1538,7 +1531,7 @@ export class App extends React.Component<any, AppState> {
}
}
const hitElement = getElementAtPosition(
globalSceneState.getAllElements(),
elements,
this.state,
x,
y,
@ -1737,13 +1730,11 @@ export class App extends React.Component<any, AppState> {
let hitElement: ExcalidrawElement | null = null;
let hitElementWasAddedToSelection = false;
if (this.state.elementType === "selection") {
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
this.state,
);
const elements = globalSceneState.getElements();
const selectedElements = getSelectedElements(elements, this.state);
if (selectedElements.length === 1) {
const elementWithResizeHandler = getElementWithResizeHandler(
globalSceneState.getAllElements(),
elements,
this.state,
{ x, y },
this.state.zoom,
@ -1781,7 +1772,7 @@ export class App extends React.Component<any, AppState> {
}
if (!isResizingElements) {
hitElement = getElementAtPosition(
globalSceneState.getAllElements(),
elements,
this.state,
x,
y,
@ -1809,7 +1800,7 @@ export class App extends React.Component<any, AppState> {
},
}));
globalSceneState.replaceAllElements(
globalSceneState.getAllElements(),
globalSceneState.getElementsIncludingDeleted(),
);
hitElementWasAddedToSelection = true;
}
@ -1820,7 +1811,7 @@ export class App extends React.Component<any, AppState> {
// put the duplicates where the selected elements used to be.
const nextElements = [];
const elementsToAppend = [];
for (const element of globalSceneState.getAllElements()) {
for (const element of globalSceneState.getElementsIncludingDeleted()) {
if (
this.state.selectedElementIds[element.id] ||
(element.id === hitElement.id && hitElementWasAddedToSelection)
@ -1930,7 +1921,7 @@ export class App extends React.Component<any, AppState> {
points: [...element.points, [0, 0]],
});
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...globalSceneState.getElementsIncludingDeleted(),
element,
]);
this.setState({
@ -1958,7 +1949,7 @@ export class App extends React.Component<any, AppState> {
});
} else {
globalSceneState.replaceAllElements([
...globalSceneState.getAllElements(),
...globalSceneState.getElementsIncludingDeleted(),
element,
]);
this.setState({
@ -2047,7 +2038,7 @@ export class App extends React.Component<any, AppState> {
// if elements should be deselected on pointerup
draggingOccurred = true;
const selectedElements = getSelectedElements(
globalSceneState.getAllElements(),
globalSceneState.getElements(),
this.state,
);
if (selectedElements.length > 0) {
@ -2123,14 +2114,12 @@ export class App extends React.Component<any, AppState> {
}
if (this.state.elementType === "selection") {
if (
!event.shiftKey &&
isSomeElementSelected(globalSceneState.getAllElements(), this.state)
) {
const elements = globalSceneState.getElements();
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
this.setState({ selectedElementIds: {} });
}
const elementsWithinSelection = getElementsWithinSelection(
globalSceneState.getAllElements(),
elements,
draggingElement,
);
this.setState((prevState) => ({
@ -2223,7 +2212,7 @@ export class App extends React.Component<any, AppState> {
) {
// remove invisible element which was added in onPointerDown
globalSceneState.replaceAllElements(
globalSceneState.getAllElements().slice(0, -1),
globalSceneState.getElementsIncludingDeleted().slice(0, -1),
);
this.setState({
draggingElement: null,
@ -2240,7 +2229,7 @@ export class App extends React.Component<any, AppState> {
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
globalSceneState.replaceAllElements(
globalSceneState
.getAllElements()
.getElementsIncludingDeleted()
.filter((el) => el.id !== resizingElement.id),
);
}
@ -2285,7 +2274,7 @@ export class App extends React.Component<any, AppState> {
if (
elementType !== "selection" ||
isSomeElementSelected(globalSceneState.getAllElements(), this.state)
isSomeElementSelected(globalSceneState.getElements(), this.state)
) {
history.resumeRecording();
}
@ -2366,8 +2355,9 @@ export class App extends React.Component<any, AppState> {
window.devicePixelRatio,
);
const elements = globalSceneState.getElements();
const element = getElementAtPosition(
globalSceneState.getAllElements(),
elements,
this.state,
x,
y,
@ -2381,12 +2371,12 @@ export class App extends React.Component<any, AppState> {
action: () => this.pasteFromClipboard(null),
},
probablySupportsClipboardBlob &&
hasNonDeletedElements(globalSceneState.getAllElements()) && {
elements.length > 0 && {
label: t("labels.copyAsPng"),
action: this.copyToClipboardAsPng,
},
probablySupportsClipboardWriteText &&
hasNonDeletedElements(globalSceneState.getAllElements()) && {
elements.length > 0 && {
label: t("labels.copyAsSvg"),
action: this.copyToClipboardAsSvg,
},
@ -2468,7 +2458,7 @@ export class App extends React.Component<any, AppState> {
scale: number,
) {
const elementClickedInside = getElementContainingPosition(
globalSceneState.getAllElements(),
globalSceneState.getElementsIncludingDeleted(),
x,
y,
);
@ -2522,7 +2512,10 @@ export class App extends React.Component<any, AppState> {
}, 300);
private saveDebounced = debounce(() => {
saveToLocalStorage(globalSceneState.getAllElements(), this.state);
saveToLocalStorage(
globalSceneState.getElementsIncludingDeleted(),
this.state,
);
}, 300);
}
@ -2548,7 +2541,7 @@ if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") {
Object.defineProperties(window.h, {
elements: {
get() {
return globalSceneState.getAllElements();
return globalSceneState.getElementsIncludingDeleted();
},
set(elements: ExcalidrawElement[]) {
return globalSceneState.replaceAllElements(elements);