|
@@ -3014,7 +3014,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
!event.ctrlKey &&
|
|
|
!event.altKey &&
|
|
|
!event.metaKey &&
|
|
|
- this.state.draggingElement === null
|
|
|
+ !this.state.draggingElement &&
|
|
|
+ !this.state.selectionElement
|
|
|
) {
|
|
|
const shape = findShapeByKey(event.key);
|
|
|
if (shape) {
|
|
@@ -3343,6 +3344,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
this.setState({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
editingElement: null,
|
|
|
});
|
|
|
if (this.state.activeTool.locked) {
|
|
@@ -4421,8 +4423,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
// finger is lifted
|
|
|
if (
|
|
|
event.pointerType === "touch" &&
|
|
|
- this.state.draggingElement &&
|
|
|
- this.state.draggingElement.type === "freedraw"
|
|
|
+ this.state.draggingElement?.type === "freedraw"
|
|
|
) {
|
|
|
const element = this.state.draggingElement as ExcalidrawFreeDrawElement;
|
|
|
this.updateScene({
|
|
@@ -4434,6 +4435,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
}
|
|
|
: {}),
|
|
|
appState: {
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: null,
|
|
|
editingElement: null,
|
|
|
startBoundElement: null,
|
|
@@ -4561,13 +4563,16 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
// retrieve the latest element as the state may be stale
|
|
|
const pendingImageElement =
|
|
|
this.state.pendingImageElementId &&
|
|
|
- this.scene.getElement(this.state.pendingImageElementId);
|
|
|
+ this.scene.getElement<ExcalidrawImageElement>(
|
|
|
+ this.state.pendingImageElementId,
|
|
|
+ );
|
|
|
|
|
|
if (!pendingImageElement) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
this.setState({
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: pendingImageElement,
|
|
|
editingElement: pendingImageElement,
|
|
|
pendingImageElementId: null,
|
|
@@ -5374,6 +5379,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
);
|
|
|
this.scene.addNewElement(element);
|
|
|
this.setState({
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: element,
|
|
|
editingElement: element,
|
|
|
startBoundElement: boundElement,
|
|
@@ -5593,6 +5599,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
this.scene.addNewElement(element);
|
|
|
this.setState({
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: element,
|
|
|
editingElement: element,
|
|
|
startBoundElement: boundElement,
|
|
@@ -5667,12 +5674,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (element.type === "selection") {
|
|
|
this.setState({
|
|
|
selectionElement: element,
|
|
|
- draggingElement: element,
|
|
|
+ draggingElement: null,
|
|
|
});
|
|
|
} else {
|
|
|
this.scene.addNewElement(element);
|
|
|
this.setState({
|
|
|
multiElement: null,
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: element,
|
|
|
editingElement: element,
|
|
|
});
|
|
@@ -5705,6 +5713,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
this.setState({
|
|
|
multiElement: null,
|
|
|
+ selectionElement: null,
|
|
|
draggingElement: frame,
|
|
|
editingElement: frame,
|
|
|
});
|
|
@@ -5763,7 +5772,9 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (this.maybeHandleResize(pointerDownState, event)) {
|
|
|
return;
|
|
|
}
|
|
|
- this.maybeDragNewGenericElement(pointerDownState, event);
|
|
|
+ if (!this.maybeUpdateSelectionElement(pointerDownState, event)) {
|
|
|
+ this.maybeDragNewGenericElement(pointerDownState, event);
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -5776,7 +5787,9 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (this.maybeHandleResize(pointerDownState, event)) {
|
|
|
return;
|
|
|
}
|
|
|
- this.maybeDragNewGenericElement(pointerDownState, event);
|
|
|
+ if (!this.maybeUpdateSelectionElement(pointerDownState, event)) {
|
|
|
+ this.maybeDragNewGenericElement(pointerDownState, event);
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -6132,6 +6145,13 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ pointerDownState.lastCoords.x = pointerCoords.x;
|
|
|
+ pointerDownState.lastCoords.y = pointerCoords.y;
|
|
|
+
|
|
|
+ if (this.maybeHandleBoxSelection(pointerDownState, event)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// It is very important to read this.state within each move event,
|
|
|
// otherwise we would read a stale one!
|
|
|
const draggingElement = this.state.draggingElement;
|
|
@@ -6199,105 +6219,6 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
pointerDownState.lastCoords.y = pointerCoords.y;
|
|
|
this.maybeDragNewGenericElement(pointerDownState, event);
|
|
|
}
|
|
|
-
|
|
|
- if (this.state.activeTool.type === "selection") {
|
|
|
- pointerDownState.boxSelection.hasOccurred = true;
|
|
|
-
|
|
|
- const elements = this.scene.getNonDeletedElements();
|
|
|
-
|
|
|
- // box-select line editor points
|
|
|
- if (this.state.editingLinearElement) {
|
|
|
- LinearElementEditor.handleBoxSelection(
|
|
|
- event,
|
|
|
- this.state,
|
|
|
- this.setState.bind(this),
|
|
|
- );
|
|
|
- // regular box-select
|
|
|
- } else {
|
|
|
- let shouldReuseSelection = true;
|
|
|
-
|
|
|
- if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
|
|
- if (
|
|
|
- pointerDownState.withCmdOrCtrl &&
|
|
|
- pointerDownState.hit.element
|
|
|
- ) {
|
|
|
- this.setState((prevState) =>
|
|
|
- selectGroupsForSelectedElements(
|
|
|
- {
|
|
|
- ...prevState,
|
|
|
- selectedElementIds: {
|
|
|
- [pointerDownState.hit.element!.id]: true,
|
|
|
- },
|
|
|
- },
|
|
|
- this.scene.getNonDeletedElements(),
|
|
|
- prevState,
|
|
|
- this,
|
|
|
- ),
|
|
|
- );
|
|
|
- } else {
|
|
|
- shouldReuseSelection = false;
|
|
|
- }
|
|
|
- }
|
|
|
- const elementsWithinSelection = getElementsWithinSelection(
|
|
|
- elements,
|
|
|
- draggingElement,
|
|
|
- );
|
|
|
-
|
|
|
- this.setState((prevState) => {
|
|
|
- const nextSelectedElementIds = {
|
|
|
- ...(shouldReuseSelection && prevState.selectedElementIds),
|
|
|
- ...elementsWithinSelection.reduce(
|
|
|
- (acc: Record<ExcalidrawElement["id"], true>, element) => {
|
|
|
- acc[element.id] = true;
|
|
|
- return acc;
|
|
|
- },
|
|
|
- {},
|
|
|
- ),
|
|
|
- };
|
|
|
-
|
|
|
- if (pointerDownState.hit.element) {
|
|
|
- // if using ctrl/cmd, select the hitElement only if we
|
|
|
- // haven't box-selected anything else
|
|
|
- if (!elementsWithinSelection.length) {
|
|
|
- nextSelectedElementIds[pointerDownState.hit.element.id] = true;
|
|
|
- } else {
|
|
|
- delete nextSelectedElementIds[pointerDownState.hit.element.id];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- prevState = !shouldReuseSelection
|
|
|
- ? { ...prevState, selectedGroupIds: {}, editingGroupId: null }
|
|
|
- : prevState;
|
|
|
-
|
|
|
- return {
|
|
|
- ...selectGroupsForSelectedElements(
|
|
|
- {
|
|
|
- editingGroupId: prevState.editingGroupId,
|
|
|
- selectedElementIds: nextSelectedElementIds,
|
|
|
- },
|
|
|
- this.scene.getNonDeletedElements(),
|
|
|
- prevState,
|
|
|
- this,
|
|
|
- ),
|
|
|
- // select linear element only when we haven't box-selected anything else
|
|
|
- selectedLinearElement:
|
|
|
- elementsWithinSelection.length === 1 &&
|
|
|
- isLinearElement(elementsWithinSelection[0])
|
|
|
- ? new LinearElementEditor(
|
|
|
- elementsWithinSelection[0],
|
|
|
- this.scene,
|
|
|
- )
|
|
|
- : null,
|
|
|
- showHyperlinkPopup:
|
|
|
- elementsWithinSelection.length === 1 &&
|
|
|
- (elementsWithinSelection[0].link ||
|
|
|
- isEmbeddableElement(elementsWithinSelection[0]))
|
|
|
- ? "info"
|
|
|
- : false,
|
|
|
- };
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -6558,6 +6479,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
resetCursor(this.interactiveCanvas);
|
|
|
this.setState((prevState) => ({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
activeTool: updateActiveTool(this.state, {
|
|
|
type: "selection",
|
|
|
}),
|
|
@@ -6576,6 +6498,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
} else {
|
|
|
this.setState((prevState) => ({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
}));
|
|
|
}
|
|
|
}
|
|
@@ -6595,140 +6518,137 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
);
|
|
|
this.setState({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (draggingElement) {
|
|
|
- if (pointerDownState.drag.hasOccurred) {
|
|
|
- const sceneCoords = viewportCoordsToSceneCoords(
|
|
|
- childEvent,
|
|
|
- this.state,
|
|
|
- );
|
|
|
+ if (pointerDownState.drag.hasOccurred) {
|
|
|
+ const sceneCoords = viewportCoordsToSceneCoords(childEvent, this.state);
|
|
|
|
|
|
- // when editing the points of a linear element, we check if the
|
|
|
- // linear element still is in the frame afterwards
|
|
|
- // if not, the linear element will be removed from its frame (if any)
|
|
|
- if (
|
|
|
- this.state.selectedLinearElement &&
|
|
|
- this.state.selectedLinearElement.isDragging
|
|
|
- ) {
|
|
|
- const linearElement = this.scene.getElement(
|
|
|
- this.state.selectedLinearElement.elementId,
|
|
|
- );
|
|
|
+ // when editing the points of a linear element, we check if the
|
|
|
+ // linear element still is in the frame afterwards
|
|
|
+ // if not, the linear element will be removed from its frame (if any)
|
|
|
+ if (
|
|
|
+ this.state.selectedLinearElement &&
|
|
|
+ this.state.selectedLinearElement.isDragging
|
|
|
+ ) {
|
|
|
+ const linearElement = this.scene.getElement(
|
|
|
+ this.state.selectedLinearElement.elementId,
|
|
|
+ );
|
|
|
|
|
|
- if (linearElement?.frameId) {
|
|
|
- const frame = getContainingFrame(linearElement);
|
|
|
+ if (linearElement?.frameId) {
|
|
|
+ const frame = getContainingFrame(linearElement);
|
|
|
|
|
|
- if (frame && linearElement) {
|
|
|
- if (!elementOverlapsWithFrame(linearElement, frame)) {
|
|
|
- // remove the linear element from all groups
|
|
|
- // before removing it from the frame as well
|
|
|
- mutateElement(linearElement, {
|
|
|
- groupIds: [],
|
|
|
- });
|
|
|
+ if (frame && linearElement) {
|
|
|
+ if (!elementOverlapsWithFrame(linearElement, frame)) {
|
|
|
+ // remove the linear element from all groups
|
|
|
+ // before removing it from the frame as well
|
|
|
+ mutateElement(linearElement, {
|
|
|
+ groupIds: [],
|
|
|
+ });
|
|
|
|
|
|
- this.scene.replaceAllElements(
|
|
|
- removeElementsFromFrame(
|
|
|
- this.scene.getElementsIncludingDeleted(),
|
|
|
- [linearElement],
|
|
|
- this.state,
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
+ this.scene.replaceAllElements(
|
|
|
+ removeElementsFromFrame(
|
|
|
+ this.scene.getElementsIncludingDeleted(),
|
|
|
+ [linearElement],
|
|
|
+ this.state,
|
|
|
+ ),
|
|
|
+ );
|
|
|
}
|
|
|
}
|
|
|
- } else {
|
|
|
- // update the relationships between selected elements and frames
|
|
|
- const topLayerFrame =
|
|
|
- this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
|
|
-
|
|
|
- const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
- let nextElements = this.scene.getElementsIncludingDeleted();
|
|
|
-
|
|
|
- const updateGroupIdsAfterEditingGroup = (
|
|
|
- elements: ExcalidrawElement[],
|
|
|
- ) => {
|
|
|
- if (elements.length > 0) {
|
|
|
- for (const element of elements) {
|
|
|
- const index = element.groupIds.indexOf(
|
|
|
- this.state.editingGroupId!,
|
|
|
- );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // update the relationships between selected elements and frames
|
|
|
+ const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
|
|
+
|
|
|
+ const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
+ let nextElements = this.scene.getElementsIncludingDeleted();
|
|
|
+
|
|
|
+ const updateGroupIdsAfterEditingGroup = (
|
|
|
+ elements: ExcalidrawElement[],
|
|
|
+ ) => {
|
|
|
+ if (elements.length > 0) {
|
|
|
+ for (const element of elements) {
|
|
|
+ const index = element.groupIds.indexOf(
|
|
|
+ this.state.editingGroupId!,
|
|
|
+ );
|
|
|
+
|
|
|
+ mutateElement(
|
|
|
+ element,
|
|
|
+ {
|
|
|
+ groupIds: element.groupIds.slice(0, index),
|
|
|
+ },
|
|
|
+ false,
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
+ nextElements.forEach((element) => {
|
|
|
+ if (
|
|
|
+ element.groupIds.length &&
|
|
|
+ getElementsInGroup(
|
|
|
+ nextElements,
|
|
|
+ element.groupIds[element.groupIds.length - 1],
|
|
|
+ ).length < 2
|
|
|
+ ) {
|
|
|
mutateElement(
|
|
|
element,
|
|
|
{
|
|
|
- groupIds: element.groupIds.slice(0, index),
|
|
|
+ groupIds: [],
|
|
|
},
|
|
|
false,
|
|
|
);
|
|
|
}
|
|
|
+ });
|
|
|
|
|
|
- nextElements.forEach((element) => {
|
|
|
- if (
|
|
|
- element.groupIds.length &&
|
|
|
- getElementsInGroup(
|
|
|
- nextElements,
|
|
|
- element.groupIds[element.groupIds.length - 1],
|
|
|
- ).length < 2
|
|
|
- ) {
|
|
|
- mutateElement(
|
|
|
- element,
|
|
|
- {
|
|
|
- groupIds: [],
|
|
|
- },
|
|
|
- false,
|
|
|
- );
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- this.setState({
|
|
|
- editingGroupId: null,
|
|
|
- });
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- if (
|
|
|
- topLayerFrame &&
|
|
|
- !this.state.selectedElementIds[topLayerFrame.id]
|
|
|
- ) {
|
|
|
- const elementsToAdd = selectedElements.filter(
|
|
|
- (element) =>
|
|
|
- element.frameId !== topLayerFrame.id &&
|
|
|
- isElementInFrame(element, nextElements, this.state),
|
|
|
- );
|
|
|
-
|
|
|
- if (this.state.editingGroupId) {
|
|
|
- updateGroupIdsAfterEditingGroup(elementsToAdd);
|
|
|
- }
|
|
|
+ this.setState({
|
|
|
+ editingGroupId: null,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- nextElements = addElementsToFrame(
|
|
|
- nextElements,
|
|
|
- elementsToAdd,
|
|
|
- topLayerFrame,
|
|
|
- );
|
|
|
- } else if (!topLayerFrame) {
|
|
|
- if (this.state.editingGroupId) {
|
|
|
- const elementsToRemove = selectedElements.filter(
|
|
|
- (element) =>
|
|
|
- element.frameId &&
|
|
|
- !isElementInFrame(element, nextElements, this.state),
|
|
|
- );
|
|
|
+ if (
|
|
|
+ topLayerFrame &&
|
|
|
+ !this.state.selectedElementIds[topLayerFrame.id]
|
|
|
+ ) {
|
|
|
+ const elementsToAdd = selectedElements.filter(
|
|
|
+ (element) =>
|
|
|
+ element.frameId !== topLayerFrame.id &&
|
|
|
+ isElementInFrame(element, nextElements, this.state),
|
|
|
+ );
|
|
|
|
|
|
- updateGroupIdsAfterEditingGroup(elementsToRemove);
|
|
|
- }
|
|
|
+ if (this.state.editingGroupId) {
|
|
|
+ updateGroupIdsAfterEditingGroup(elementsToAdd);
|
|
|
}
|
|
|
|
|
|
- nextElements = updateFrameMembershipOfSelectedElements(
|
|
|
+ nextElements = addElementsToFrame(
|
|
|
nextElements,
|
|
|
- this.state,
|
|
|
- this,
|
|
|
+ elementsToAdd,
|
|
|
+ topLayerFrame,
|
|
|
);
|
|
|
+ } else if (!topLayerFrame) {
|
|
|
+ if (this.state.editingGroupId) {
|
|
|
+ const elementsToRemove = selectedElements.filter(
|
|
|
+ (element) =>
|
|
|
+ element.frameId &&
|
|
|
+ !isElementInFrame(element, nextElements, this.state),
|
|
|
+ );
|
|
|
|
|
|
- this.scene.replaceAllElements(nextElements);
|
|
|
+ updateGroupIdsAfterEditingGroup(elementsToRemove);
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ nextElements = updateFrameMembershipOfSelectedElements(
|
|
|
+ nextElements,
|
|
|
+ this.state,
|
|
|
+ this,
|
|
|
+ );
|
|
|
+
|
|
|
+ this.scene.replaceAllElements(nextElements);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ if (draggingElement) {
|
|
|
if (draggingElement.type === "frame") {
|
|
|
const elementsInsideFrame = getElementsInNewFrame(
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
@@ -7032,8 +6952,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (
|
|
|
!activeTool.locked &&
|
|
|
activeTool.type !== "freedraw" &&
|
|
|
- draggingElement &&
|
|
|
- draggingElement.type !== "selection"
|
|
|
+ draggingElement
|
|
|
) {
|
|
|
this.setState((prevState) => ({
|
|
|
selectedElementIds: makeNextSelectedElementIds(
|
|
@@ -7072,12 +6991,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
resetCursor(this.interactiveCanvas);
|
|
|
this.setState({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
suggestedBindings: [],
|
|
|
activeTool: updateActiveTool(this.state, { type: "selection" }),
|
|
|
});
|
|
|
} else {
|
|
|
this.setState({
|
|
|
draggingElement: null,
|
|
|
+ selectionElement: null,
|
|
|
suggestedBindings: [],
|
|
|
});
|
|
|
}
|
|
@@ -7876,6 +7797,139 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+ private maybeUpdateSelectionElement = (
|
|
|
+ pointerDownState: PointerDownState,
|
|
|
+ event: PointerEvent | KeyboardEvent,
|
|
|
+ ): boolean => {
|
|
|
+ const { selectionElement } = this.state;
|
|
|
+
|
|
|
+ if (!selectionElement || this.state.activeTool.type !== "selection") {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const pointerCoords = pointerDownState.lastCoords;
|
|
|
+
|
|
|
+ dragNewElement(
|
|
|
+ selectionElement,
|
|
|
+ this.state.activeTool.type,
|
|
|
+ pointerDownState.origin.x,
|
|
|
+ pointerDownState.origin.y,
|
|
|
+ pointerCoords.x,
|
|
|
+ pointerCoords.y,
|
|
|
+ distance(pointerDownState.origin.x, pointerCoords.x),
|
|
|
+ distance(pointerDownState.origin.y, pointerCoords.y),
|
|
|
+ shouldMaintainAspectRatio(event),
|
|
|
+ shouldResizeFromCenter(event),
|
|
|
+ );
|
|
|
+
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ private maybeHandleBoxSelection = (
|
|
|
+ pointerDownState: PointerDownState,
|
|
|
+ event: PointerEvent,
|
|
|
+ ): boolean => {
|
|
|
+ const { selectionElement } = this.state;
|
|
|
+
|
|
|
+ if (!selectionElement || this.state.activeTool.type !== "selection") {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.maybeUpdateSelectionElement(pointerDownState, event);
|
|
|
+
|
|
|
+ pointerDownState.boxSelection.hasOccurred = true;
|
|
|
+
|
|
|
+ const elements = this.scene.getNonDeletedElements();
|
|
|
+
|
|
|
+ // box-select line editor points
|
|
|
+ if (this.state.editingLinearElement) {
|
|
|
+ LinearElementEditor.handleBoxSelection(
|
|
|
+ event,
|
|
|
+ this.state,
|
|
|
+ this.setState.bind(this),
|
|
|
+ );
|
|
|
+ // regular box-select
|
|
|
+ } else {
|
|
|
+ let shouldReuseSelection = true;
|
|
|
+
|
|
|
+ if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
|
|
+ if (pointerDownState.withCmdOrCtrl && pointerDownState.hit.element) {
|
|
|
+ this.setState((prevState) =>
|
|
|
+ selectGroupsForSelectedElements(
|
|
|
+ {
|
|
|
+ ...prevState,
|
|
|
+ selectedElementIds: {
|
|
|
+ [pointerDownState.hit.element!.id]: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ this.scene.getNonDeletedElements(),
|
|
|
+ prevState,
|
|
|
+ this,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ shouldReuseSelection = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const elementsWithinSelection = getElementsWithinSelection(
|
|
|
+ elements,
|
|
|
+ selectionElement,
|
|
|
+ );
|
|
|
+
|
|
|
+ this.setState((prevState) => {
|
|
|
+ const nextSelectedElementIds = {
|
|
|
+ ...(shouldReuseSelection && prevState.selectedElementIds),
|
|
|
+ ...elementsWithinSelection.reduce(
|
|
|
+ (acc: Record<ExcalidrawElement["id"], true>, element) => {
|
|
|
+ acc[element.id] = true;
|
|
|
+ return acc;
|
|
|
+ },
|
|
|
+ {},
|
|
|
+ ),
|
|
|
+ };
|
|
|
+
|
|
|
+ if (pointerDownState.hit.element) {
|
|
|
+ // if using ctrl/cmd, select the hitElement only if we
|
|
|
+ // haven't box-selected anything else
|
|
|
+ if (!elementsWithinSelection.length) {
|
|
|
+ nextSelectedElementIds[pointerDownState.hit.element.id] = true;
|
|
|
+ } else {
|
|
|
+ delete nextSelectedElementIds[pointerDownState.hit.element.id];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ prevState = !shouldReuseSelection
|
|
|
+ ? { ...prevState, selectedGroupIds: {}, editingGroupId: null }
|
|
|
+ : prevState;
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...selectGroupsForSelectedElements(
|
|
|
+ {
|
|
|
+ editingGroupId: prevState.editingGroupId,
|
|
|
+ selectedElementIds: nextSelectedElementIds,
|
|
|
+ },
|
|
|
+ this.scene.getNonDeletedElements(),
|
|
|
+ prevState,
|
|
|
+ this,
|
|
|
+ ),
|
|
|
+ // select linear element only when we haven't box-selected anything else
|
|
|
+ selectedLinearElement:
|
|
|
+ elementsWithinSelection.length === 1 &&
|
|
|
+ isLinearElement(elementsWithinSelection[0])
|
|
|
+ ? new LinearElementEditor(elementsWithinSelection[0], this.scene)
|
|
|
+ : null,
|
|
|
+ showHyperlinkPopup:
|
|
|
+ elementsWithinSelection.length === 1 &&
|
|
|
+ (elementsWithinSelection[0].link ||
|
|
|
+ isEmbeddableElement(elementsWithinSelection[0]))
|
|
|
+ ? "info"
|
|
|
+ : false,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
private maybeDragNewGenericElement = (
|
|
|
pointerDownState: PointerDownState,
|
|
|
event: MouseEvent | KeyboardEvent,
|
|
@@ -7885,93 +7939,73 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
if (!draggingElement) {
|
|
|
return;
|
|
|
}
|
|
|
- if (
|
|
|
- draggingElement.type === "selection" &&
|
|
|
- this.state.activeTool.type !== "eraser"
|
|
|
- ) {
|
|
|
- dragNewElement(
|
|
|
- draggingElement,
|
|
|
- this.state.activeTool.type,
|
|
|
- pointerDownState.origin.x,
|
|
|
- pointerDownState.origin.y,
|
|
|
- pointerCoords.x,
|
|
|
- pointerCoords.y,
|
|
|
- distance(pointerDownState.origin.x, pointerCoords.x),
|
|
|
- distance(pointerDownState.origin.y, pointerCoords.y),
|
|
|
- shouldMaintainAspectRatio(event),
|
|
|
- shouldResizeFromCenter(event),
|
|
|
- );
|
|
|
- } else {
|
|
|
- let [gridX, gridY] = getGridPoint(
|
|
|
- pointerCoords.x,
|
|
|
- pointerCoords.y,
|
|
|
- event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
|
|
- );
|
|
|
+ let [gridX, gridY] = getGridPoint(
|
|
|
+ pointerCoords.x,
|
|
|
+ pointerCoords.y,
|
|
|
+ event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
|
|
|
+ );
|
|
|
|
|
|
- const image =
|
|
|
- isInitializedImageElement(draggingElement) &&
|
|
|
- this.imageCache.get(draggingElement.fileId)?.image;
|
|
|
- const aspectRatio =
|
|
|
- image && !(image instanceof Promise)
|
|
|
- ? image.width / image.height
|
|
|
- : null;
|
|
|
+ const image =
|
|
|
+ isInitializedImageElement(draggingElement) &&
|
|
|
+ this.imageCache.get(draggingElement.fileId)?.image;
|
|
|
+ const aspectRatio =
|
|
|
+ image && !(image instanceof Promise) ? image.width / image.height : null;
|
|
|
|
|
|
- this.maybeCacheReferenceSnapPoints(event, [draggingElement]);
|
|
|
+ this.maybeCacheReferenceSnapPoints(event, [draggingElement]);
|
|
|
|
|
|
- const { snapOffset, snapLines } = snapNewElement(
|
|
|
- draggingElement,
|
|
|
- this.state,
|
|
|
- event,
|
|
|
- {
|
|
|
- x:
|
|
|
- pointerDownState.originInGrid.x +
|
|
|
- (this.state.originSnapOffset?.x ?? 0),
|
|
|
- y:
|
|
|
- pointerDownState.originInGrid.y +
|
|
|
- (this.state.originSnapOffset?.y ?? 0),
|
|
|
- },
|
|
|
- {
|
|
|
- x: gridX - pointerDownState.originInGrid.x,
|
|
|
- y: gridY - pointerDownState.originInGrid.y,
|
|
|
- },
|
|
|
- );
|
|
|
+ const { snapOffset, snapLines } = snapNewElement(
|
|
|
+ draggingElement,
|
|
|
+ this.state,
|
|
|
+ event,
|
|
|
+ {
|
|
|
+ x:
|
|
|
+ pointerDownState.originInGrid.x +
|
|
|
+ (this.state.originSnapOffset?.x ?? 0),
|
|
|
+ y:
|
|
|
+ pointerDownState.originInGrid.y +
|
|
|
+ (this.state.originSnapOffset?.y ?? 0),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: gridX - pointerDownState.originInGrid.x,
|
|
|
+ y: gridY - pointerDownState.originInGrid.y,
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
- gridX += snapOffset.x;
|
|
|
- gridY += snapOffset.y;
|
|
|
+ gridX += snapOffset.x;
|
|
|
+ gridY += snapOffset.y;
|
|
|
|
|
|
- this.setState({
|
|
|
- snapLines,
|
|
|
- });
|
|
|
+ this.setState({
|
|
|
+ snapLines,
|
|
|
+ });
|
|
|
|
|
|
- dragNewElement(
|
|
|
- draggingElement,
|
|
|
- this.state.activeTool.type,
|
|
|
- pointerDownState.originInGrid.x,
|
|
|
- pointerDownState.originInGrid.y,
|
|
|
- gridX,
|
|
|
- gridY,
|
|
|
- distance(pointerDownState.originInGrid.x, gridX),
|
|
|
- distance(pointerDownState.originInGrid.y, gridY),
|
|
|
- isImageElement(draggingElement)
|
|
|
- ? !shouldMaintainAspectRatio(event)
|
|
|
- : shouldMaintainAspectRatio(event),
|
|
|
- shouldResizeFromCenter(event),
|
|
|
- aspectRatio,
|
|
|
- this.state.originSnapOffset,
|
|
|
- );
|
|
|
+ dragNewElement(
|
|
|
+ draggingElement,
|
|
|
+ this.state.activeTool.type,
|
|
|
+ pointerDownState.originInGrid.x,
|
|
|
+ pointerDownState.originInGrid.y,
|
|
|
+ gridX,
|
|
|
+ gridY,
|
|
|
+ distance(pointerDownState.originInGrid.x, gridX),
|
|
|
+ distance(pointerDownState.originInGrid.y, gridY),
|
|
|
+ isImageElement(draggingElement)
|
|
|
+ ? !shouldMaintainAspectRatio(event)
|
|
|
+ : shouldMaintainAspectRatio(event),
|
|
|
+ shouldResizeFromCenter(event),
|
|
|
+ aspectRatio,
|
|
|
+ this.state.originSnapOffset,
|
|
|
+ );
|
|
|
|
|
|
- this.maybeSuggestBindingForAll([draggingElement]);
|
|
|
+ this.maybeSuggestBindingForAll([draggingElement]);
|
|
|
|
|
|
- // highlight elements that are to be added to frames on frames creation
|
|
|
- if (this.state.activeTool.type === "frame") {
|
|
|
- this.setState({
|
|
|
- elementsToHighlight: getElementsInResizingFrame(
|
|
|
- this.scene.getNonDeletedElements(),
|
|
|
- draggingElement as ExcalidrawFrameElement,
|
|
|
- this.state,
|
|
|
- ),
|
|
|
- });
|
|
|
- }
|
|
|
+ // highlight elements that are to be added to frames on frames creation
|
|
|
+ if (this.state.activeTool.type === "frame") {
|
|
|
+ this.setState({
|
|
|
+ elementsToHighlight: getElementsInResizingFrame(
|
|
|
+ this.scene.getNonDeletedElements(),
|
|
|
+ draggingElement as ExcalidrawFrameElement,
|
|
|
+ this.state,
|
|
|
+ ),
|
|
|
+ });
|
|
|
}
|
|
|
};
|
|
|
|