|
@@ -1536,6 +1536,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
<Hyperlink
|
|
|
key={firstSelectedElement.id}
|
|
|
element={firstSelectedElement}
|
|
|
+ elementsMap={allElementsMap}
|
|
|
setAppState={this.setAppState}
|
|
|
onLinkOpen={this.props.onLinkOpen}
|
|
|
setToast={this.setToast}
|
|
@@ -1549,6 +1550,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
isMagicFrameElement(firstSelectedElement) && (
|
|
|
<ElementCanvasButtons
|
|
|
element={firstSelectedElement}
|
|
|
+ elementsMap={elementsMap}
|
|
|
>
|
|
|
<ElementCanvasButton
|
|
|
title={t("labels.convertToCode")}
|
|
@@ -1569,6 +1571,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
?.status === "done" && (
|
|
|
<ElementCanvasButtons
|
|
|
element={firstSelectedElement}
|
|
|
+ elementsMap={elementsMap}
|
|
|
>
|
|
|
<ElementCanvasButton
|
|
|
title={t("labels.copySource")}
|
|
@@ -2599,10 +2602,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
|
|
this.updateEmbeddables();
|
|
|
- if (
|
|
|
- !this.state.showWelcomeScreen &&
|
|
|
- !this.scene.getElementsIncludingDeleted().length
|
|
|
- ) {
|
|
|
+ const elements = this.scene.getElementsIncludingDeleted();
|
|
|
+ const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
|
|
+
|
|
|
+ if (!this.state.showWelcomeScreen && !elements.length) {
|
|
|
this.setState({ showWelcomeScreen: true });
|
|
|
}
|
|
|
|
|
@@ -2756,27 +2759,21 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
|
|
multiElement,
|
|
|
-1,
|
|
|
+ elementsMap,
|
|
|
),
|
|
|
),
|
|
|
+ elementsMap,
|
|
|
);
|
|
|
}
|
|
|
- this.history.record(this.state, this.scene.getElementsIncludingDeleted());
|
|
|
+ this.history.record(this.state, elements);
|
|
|
|
|
|
// 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
|
|
|
// init, which would trigger onChange with empty elements, which would then
|
|
|
// override whatever is in localStorage currently.
|
|
|
if (!this.state.isLoading) {
|
|
|
- this.props.onChange?.(
|
|
|
- this.scene.getElementsIncludingDeleted(),
|
|
|
- this.state,
|
|
|
- this.files,
|
|
|
- );
|
|
|
- this.onChangeEmitter.trigger(
|
|
|
- this.scene.getElementsIncludingDeleted(),
|
|
|
- this.state,
|
|
|
- this.files,
|
|
|
- );
|
|
|
+ this.props.onChange?.(elements, this.state, this.files);
|
|
|
+ this.onChangeEmitter.trigger(elements, this.state, this.files);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -3126,7 +3123,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
newElement,
|
|
|
this.scene.getElementsMapIncludingDeleted(),
|
|
|
);
|
|
|
- redrawTextBoundingBox(newElement, container);
|
|
|
+ redrawTextBoundingBox(
|
|
|
+ newElement,
|
|
|
+ container,
|
|
|
+ this.scene.getElementsMapIncludingDeleted(),
|
|
|
+ );
|
|
|
}
|
|
|
});
|
|
|
|
|
@@ -3836,7 +3837,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
y: element.y + offsetY,
|
|
|
});
|
|
|
|
|
|
- updateBoundElements(element, {
|
|
|
+ updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
|
|
simultaneouslyUpdated: selectedElements,
|
|
|
});
|
|
|
});
|
|
@@ -4010,9 +4011,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
}
|
|
|
if (isArrowKey(event.key)) {
|
|
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
isBindingEnabled(this.state)
|
|
|
- ? bindOrUnbindSelectedElements(selectedElements)
|
|
|
- : unbindLinearElements(selectedElements);
|
|
|
+ ? bindOrUnbindSelectedElements(selectedElements, elementsMap)
|
|
|
+ : unbindLinearElements(selectedElements, elementsMap);
|
|
|
this.setState({ suggestedBindings: [] });
|
|
|
}
|
|
|
});
|
|
@@ -4193,20 +4195,21 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
isExistingElement?: boolean;
|
|
|
},
|
|
|
) {
|
|
|
+ const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
|
|
+
|
|
|
const updateElement = (
|
|
|
text: string,
|
|
|
originalText: string,
|
|
|
isDeleted: boolean,
|
|
|
) => {
|
|
|
this.scene.replaceAllElements([
|
|
|
+ // Not sure why we include deleted elements as well hence using deleted elements map
|
|
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
|
|
if (_element.id === element.id && isTextElement(_element)) {
|
|
|
return updateTextElement(
|
|
|
_element,
|
|
|
- getContainerElement(
|
|
|
- _element,
|
|
|
- this.scene.getElementsMapIncludingDeleted(),
|
|
|
- ),
|
|
|
+ getContainerElement(_element, elementsMap),
|
|
|
+ elementsMap,
|
|
|
{
|
|
|
text,
|
|
|
isDeleted,
|
|
@@ -4238,7 +4241,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
onChange: withBatchedUpdates((text) => {
|
|
|
updateElement(text, text, false);
|
|
|
if (isNonDeletedElement(element)) {
|
|
|
- updateBoundElements(element);
|
|
|
+ updateBoundElements(element, elementsMap);
|
|
|
}
|
|
|
}),
|
|
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
|
@@ -4377,6 +4380,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
!(isTextElement(element) && element.containerId)),
|
|
|
);
|
|
|
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
return getElementsAtPosition(elements, (element) =>
|
|
|
hitTest(
|
|
|
element,
|
|
@@ -4384,7 +4388,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.frameNameBoundsCache,
|
|
|
x,
|
|
|
y,
|
|
|
- this.scene.getNonDeletedElementsMap(),
|
|
|
+ elementsMap,
|
|
|
),
|
|
|
).filter((element) => {
|
|
|
// hitting a frame's element from outside the frame is not considered a hit
|
|
@@ -4392,7 +4396,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
return containingFrame &&
|
|
|
this.state.frameRendering.enabled &&
|
|
|
this.state.frameRendering.clip
|
|
|
- ? isCursorInFrame({ x, y }, containingFrame)
|
|
|
+ ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
|
|
|
: true;
|
|
|
});
|
|
|
}
|
|
@@ -4637,6 +4641,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.state,
|
|
|
sceneX,
|
|
|
sceneY,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
if (container) {
|
|
@@ -4648,6 +4653,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.state,
|
|
|
this.frameNameBoundsCache,
|
|
|
[sceneX, sceneY],
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
)
|
|
|
) {
|
|
|
const midPoint = getContainerCenter(
|
|
@@ -4688,6 +4694,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
index <= hitElementIndex &&
|
|
|
isPointHittingLink(
|
|
|
element,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
this.state,
|
|
|
[scenePointer.x, scenePointer.y],
|
|
|
this.device.editor.isMobile,
|
|
@@ -4718,8 +4725,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.lastPointerDownEvent!,
|
|
|
this.state,
|
|
|
);
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
const lastPointerDownHittingLinkIcon = isPointHittingLink(
|
|
|
this.hitLinkElement,
|
|
|
+ elementsMap,
|
|
|
this.state,
|
|
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
|
|
this.device.editor.isMobile,
|
|
@@ -4730,6 +4739,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
);
|
|
|
const lastPointerUpHittingLinkIcon = isPointHittingLink(
|
|
|
this.hitLinkElement,
|
|
|
+ elementsMap,
|
|
|
this.state,
|
|
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
|
|
this.device.editor.isMobile,
|
|
@@ -4766,10 +4776,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
x: number;
|
|
|
y: number;
|
|
|
}) => {
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
const frames = this.scene
|
|
|
.getNonDeletedFramesLikes()
|
|
|
.filter((frame): frame is ExcalidrawFrameLikeElement =>
|
|
|
- isCursorInFrame(sceneCoords, frame),
|
|
|
+ isCursorInFrame(sceneCoords, frame, elementsMap),
|
|
|
);
|
|
|
|
|
|
return frames.length ? frames[frames.length - 1] : null;
|
|
@@ -4873,6 +4884,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
y: scenePointerY,
|
|
|
},
|
|
|
event,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
this.setState((prevState) => {
|
|
@@ -4912,6 +4924,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
scenePointerX,
|
|
|
scenePointerY,
|
|
|
this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
if (
|
|
@@ -5062,6 +5075,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
scenePointerY,
|
|
|
this.state.zoom,
|
|
|
event.pointerType,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
if (
|
|
|
elementWithTransformHandleType &&
|
|
@@ -5109,7 +5123,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
|
|
) {
|
|
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
|
|
- showHyperlinkTooltip(this.hitLinkElement, this.state);
|
|
|
+ showHyperlinkTooltip(
|
|
|
+ this.hitLinkElement,
|
|
|
+ this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
+ );
|
|
|
} else {
|
|
|
hideHyperlinkToolip();
|
|
|
if (
|
|
@@ -5305,10 +5323,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.state,
|
|
|
this.frameNameBoundsCache,
|
|
|
[scenePointerX, scenePointerY],
|
|
|
+ elementsMap,
|
|
|
)
|
|
|
) {
|
|
|
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
|
|
element,
|
|
|
+ elementsMap,
|
|
|
this.state.zoom,
|
|
|
scenePointerX,
|
|
|
scenePointerY,
|
|
@@ -5738,10 +5758,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (
|
|
|
clicklength < 300 &&
|
|
|
isIframeLikeElement(this.hitLinkElement) &&
|
|
|
- !isPointHittingLinkIcon(this.hitLinkElement, this.state, [
|
|
|
- scenePointer.x,
|
|
|
- scenePointer.y,
|
|
|
- ])
|
|
|
+ !isPointHittingLinkIcon(
|
|
|
+ this.hitLinkElement,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
+ this.state,
|
|
|
+ [scenePointer.x, scenePointer.y],
|
|
|
+ )
|
|
|
) {
|
|
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
|
|
} else {
|
|
@@ -6039,7 +6061,9 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
): boolean => {
|
|
|
if (this.state.activeTool.type === "selection") {
|
|
|
const elements = this.scene.getNonDeletedElements();
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
+
|
|
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
|
|
const elementWithTransformHandleType =
|
|
|
getElementWithTransformHandleType(
|
|
@@ -6049,6 +6073,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
pointerDownState.origin.y,
|
|
|
this.state.zoom,
|
|
|
event.pointerType,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
if (elementWithTransformHandleType != null) {
|
|
|
this.setState({
|
|
@@ -6072,6 +6097,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
getResizeOffsetXY(
|
|
|
pointerDownState.resize.handleType,
|
|
|
selectedElements,
|
|
|
+ elementsMap,
|
|
|
pointerDownState.origin.x,
|
|
|
pointerDownState.origin.y,
|
|
|
),
|
|
@@ -6352,6 +6378,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.state,
|
|
|
sceneX,
|
|
|
sceneY,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
if (hasBoundTextElement(element)) {
|
|
@@ -6846,6 +6873,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.scene.getNonDeletedElements(),
|
|
|
selectedElements,
|
|
|
this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
@@ -6869,6 +6897,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.scene.getNonDeletedElements(),
|
|
|
selectedElements,
|
|
|
this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
@@ -6985,6 +7014,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
pointerCoords,
|
|
|
this.state,
|
|
|
!event[KEYS.CTRL_OR_CMD],
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
if (!ret) {
|
|
|
return;
|
|
@@ -7143,10 +7173,11 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
|
|
|
|
|
const { snapOffset, snapLines } = snapDraggedElements(
|
|
|
- getSelectedElements(originalElements, this.state),
|
|
|
+ originalElements,
|
|
|
dragOffset,
|
|
|
this.state,
|
|
|
event,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
this.setState({ snapLines });
|
|
@@ -7330,6 +7361,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
event,
|
|
|
this.state,
|
|
|
this.setState.bind(this),
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
// regular box-select
|
|
|
} else {
|
|
@@ -7360,6 +7392,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
const elementsWithinSelection = getElementsWithinSelection(
|
|
|
elements,
|
|
|
draggingElement,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
this.setState((prevState) => {
|
|
@@ -7491,7 +7524,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.setState({
|
|
|
selectedElementsAreBeingDragged: false,
|
|
|
});
|
|
|
-
|
|
|
+ const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
// Handle end of dragging a point of a linear element, might close a loop
|
|
|
// and sets binding element
|
|
|
if (this.state.editingLinearElement) {
|
|
@@ -7506,6 +7539,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
childEvent,
|
|
|
this.state.editingLinearElement,
|
|
|
this.state,
|
|
|
+ elementsMap,
|
|
|
);
|
|
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
|
|
this.setState({
|
|
@@ -7529,6 +7563,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
childEvent,
|
|
|
this.state.selectedLinearElement,
|
|
|
this.state,
|
|
|
+ elementsMap,
|
|
|
);
|
|
|
|
|
|
const { startBindingElement, endBindingElement } =
|
|
@@ -7539,6 +7574,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
element,
|
|
|
startBindingElement,
|
|
|
endBindingElement,
|
|
|
+ elementsMap,
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -7678,6 +7714,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.state,
|
|
|
this.scene,
|
|
|
pointerCoords,
|
|
|
+ elementsMap,
|
|
|
);
|
|
|
}
|
|
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
|
@@ -7748,7 +7785,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
const frame = getContainingFrame(linearElement);
|
|
|
|
|
|
if (frame && linearElement) {
|
|
|
- if (!elementOverlapsWithFrame(linearElement, frame)) {
|
|
|
+ if (
|
|
|
+ !elementOverlapsWithFrame(
|
|
|
+ linearElement,
|
|
|
+ frame,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
+ )
|
|
|
+ ) {
|
|
|
// remove the linear element from all groups
|
|
|
// before removing it from the frame as well
|
|
|
mutateElement(linearElement, {
|
|
@@ -7859,6 +7902,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
const elementsInsideFrame = getElementsInNewFrame(
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
|
draggingElement,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
this.scene.replaceAllElements(
|
|
@@ -7909,6 +7953,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
|
frame,
|
|
|
this.state,
|
|
|
+ elementsMap,
|
|
|
),
|
|
|
frame,
|
|
|
this,
|
|
@@ -8189,7 +8234,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
|
|
(isBindingEnabled(this.state)
|
|
|
? bindOrUnbindSelectedElements
|
|
|
- : unbindLinearElements)(this.scene.getSelectedElements(this.state));
|
|
|
+ : unbindLinearElements)(
|
|
|
+ this.scene.getSelectedElements(this.state),
|
|
|
+ elementsMap,
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
if (activeTool.type === "laser") {
|
|
@@ -8719,7 +8767,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (selectedElements.length > 50) {
|
|
|
return;
|
|
|
}
|
|
|
- const suggestedBindings = getEligibleElementsForBinding(selectedElements);
|
|
|
+ const suggestedBindings = getEligibleElementsForBinding(
|
|
|
+ selectedElements,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
+ );
|
|
|
this.setState({ suggestedBindings });
|
|
|
}
|
|
|
|
|
@@ -9058,6 +9109,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
x: gridX - pointerDownState.originInGrid.x,
|
|
|
y: gridY - pointerDownState.originInGrid.y,
|
|
|
},
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
);
|
|
|
|
|
|
gridX += snapOffset.x;
|
|
@@ -9096,6 +9148,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.scene.getNonDeletedElements(),
|
|
|
draggingElement as ExcalidrawFrameLikeElement,
|
|
|
this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
),
|
|
|
});
|
|
|
}
|
|
@@ -9215,6 +9268,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
this.scene.getNonDeletedElements(),
|
|
|
frame,
|
|
|
this.state,
|
|
|
+ this.scene.getNonDeletedElementsMap(),
|
|
|
).forEach((element) => elementsToHighlight.add(element));
|
|
|
});
|
|
|
|