Compare commits
1 Commits
master
...
dwelle/dra
Author | SHA1 | Date | |
---|---|---|---|
![]() |
add575a419 |
@ -170,6 +170,7 @@ export const actionFinalize = register({
|
||||
: activeTool,
|
||||
activeEmbeddable: null,
|
||||
draggingElement: null,
|
||||
selectionElement: null,
|
||||
multiElement: null,
|
||||
editingElement: null,
|
||||
startBoundElement: null,
|
||||
@ -196,7 +197,9 @@ export const actionFinalize = register({
|
||||
keyTest: (event, appState) =>
|
||||
(event.key === KEYS.ESCAPE &&
|
||||
(appState.editingLinearElement !== null ||
|
||||
(!appState.draggingElement && appState.multiElement === null))) ||
|
||||
(!appState.selectionElement &&
|
||||
!appState.draggingElement &&
|
||||
appState.multiElement === null))) ||
|
||||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||
appState.multiElement !== null),
|
||||
PanelComponent: ({ appState, updateData, data }) => (
|
||||
|
@ -21,6 +21,7 @@ const writeData = (
|
||||
!appState.multiElement &&
|
||||
!appState.resizingElement &&
|
||||
!appState.editingElement &&
|
||||
!appState.selectionElement &&
|
||||
!appState.draggingElement
|
||||
) {
|
||||
const data = updater();
|
||||
|
@ -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);
|
||||
}
|
||||
} 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 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!,
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
nextElements = addElementsToFrame(
|
||||
nextElements,
|
||||
elementsToAdd,
|
||||
topLayerFrame,
|
||||
);
|
||||
} else if (!topLayerFrame) {
|
||||
if (this.state.editingGroupId) {
|
||||
const elementsToRemove = selectedElements.filter(
|
||||
(element) =>
|
||||
element.frameId &&
|
||||
!isElementInFrame(element, nextElements, this.state),
|
||||
);
|
||||
|
||||
updateGroupIdsAfterEditingGroup(elementsToRemove);
|
||||
}
|
||||
this.setState({
|
||||
editingGroupId: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
nextElements = updateFrameMembershipOfSelectedElements(
|
||||
nextElements,
|
||||
this.state,
|
||||
this,
|
||||
if (
|
||||
topLayerFrame &&
|
||||
!this.state.selectedElementIds[topLayerFrame.id]
|
||||
) {
|
||||
const elementsToAdd = selectedElements.filter(
|
||||
(element) =>
|
||||
element.frameId !== topLayerFrame.id &&
|
||||
isElementInFrame(element, nextElements, this.state),
|
||||
);
|
||||
|
||||
this.scene.replaceAllElements(nextElements);
|
||||
}
|
||||
}
|
||||
if (this.state.editingGroupId) {
|
||||
updateGroupIdsAfterEditingGroup(elementsToAdd);
|
||||
}
|
||||
|
||||
nextElements = addElementsToFrame(
|
||||
nextElements,
|
||||
elementsToAdd,
|
||||
topLayerFrame,
|
||||
);
|
||||
} else if (!topLayerFrame) {
|
||||
if (this.state.editingGroupId) {
|
||||
const elementsToRemove = selectedElements.filter(
|
||||
(element) =>
|
||||
element.frameId &&
|
||||
!isElementInFrame(element, nextElements, this.state),
|
||||
);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
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]);
|
||||
|
||||
// highlight elements that are to be added to frames on frames creation
|
||||
if (this.state.activeTool.type === "frame") {
|
||||
this.setState({
|
||||
snapLines,
|
||||
elementsToHighlight: getElementsInResizingFrame(
|
||||
this.scene.getNonDeletedElements(),
|
||||
draggingElement as ExcalidrawFrameElement,
|
||||
this.state,
|
||||
),
|
||||
});
|
||||
|
||||
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]);
|
||||
|
||||
// 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,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -82,8 +82,9 @@ const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
|
||||
|
||||
if (activeTool.type === "selection") {
|
||||
if (
|
||||
appState.draggingElement?.type === "selection" &&
|
||||
appState.selectionElement &&
|
||||
!selectedElements.length &&
|
||||
!appState.draggingElement &&
|
||||
!appState.editingElement &&
|
||||
!appState.editingLinearElement
|
||||
) {
|
||||
|
@ -210,6 +210,7 @@ export const Hyperlink = ({
|
||||
};
|
||||
const { x, y } = getCoordsForPopover(element, appState);
|
||||
if (
|
||||
appState.selectionElement ||
|
||||
appState.draggingElement ||
|
||||
appState.resizingElement ||
|
||||
appState.isRotating ||
|
||||
|
@ -134,10 +134,7 @@ export class LinearElementEditor {
|
||||
appState: AppState,
|
||||
setState: React.Component<any, AppState>["setState"],
|
||||
) {
|
||||
if (
|
||||
!appState.editingLinearElement ||
|
||||
appState.draggingElement?.type !== "selection"
|
||||
) {
|
||||
if (!appState.editingLinearElement || !appState.selectionElement) {
|
||||
return false;
|
||||
}
|
||||
const { editingLinearElement } = appState;
|
||||
@ -149,7 +146,7 @@ export class LinearElementEditor {
|
||||
}
|
||||
|
||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||
getElementAbsoluteCoords(appState.draggingElement);
|
||||
getElementAbsoluteCoords(appState.selectionElement);
|
||||
|
||||
const pointsSceneCoords =
|
||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
||||
|
@ -5152,35 +5152,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "down",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": "id3",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"seed": 400692809,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "selection",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 0,
|
||||
"x": 500,
|
||||
"y": 500,
|
||||
},
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
@ -5449,35 +5421,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "up",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": "id3",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"seed": 400692809,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "selection",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 0,
|
||||
"x": 50,
|
||||
"y": 50,
|
||||
},
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
@ -5718,35 +5662,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "down",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": "id1",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"seed": 2019559783,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "selection",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 0,
|
||||
"x": 110,
|
||||
"y": 110,
|
||||
},
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
@ -16262,35 +16178,7 @@ exports[`regression tests > switches from group of selected elements to another
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "down",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": "id4",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"seed": 493213705,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "selection",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
@ -16662,35 +16550,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "down",
|
||||
"defaultSidebarDockedPreference": false,
|
||||
"draggingElement": {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"id": "id2",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"seed": 238820263,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "selection",
|
||||
"updated": 1,
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingFrame": null,
|
||||
"editingGroupId": null,
|
||||
|
29
src/types.ts
29
src/types.ts
@ -17,6 +17,7 @@ import {
|
||||
StrokeRoundness,
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawEmbeddableElement,
|
||||
ExcalidrawSelectionElement,
|
||||
} from "./element/types";
|
||||
import { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||
import { LinearElementEditor } from "./element/linearElementEditor";
|
||||
@ -182,10 +183,25 @@ export type AppState = {
|
||||
element: NonDeletedExcalidrawElement;
|
||||
state: "hover" | "active";
|
||||
} | null;
|
||||
draggingElement: NonDeletedExcalidrawElement | null;
|
||||
/** element that's being dragged or created */
|
||||
draggingElement: Exclude<
|
||||
NonDeletedExcalidrawElement,
|
||||
ExcalidrawSelectionElement
|
||||
> | null;
|
||||
/**
|
||||
* Element that's being resized.
|
||||
* NOTE not set when resizing a group or linear element
|
||||
*/
|
||||
resizingElement: NonDeletedExcalidrawElement | null;
|
||||
/** multi-point linear element when it's being created */
|
||||
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
|
||||
selectionElement: NonDeletedExcalidrawElement | null;
|
||||
/**
|
||||
* The selection box (we currently use an excalidraw element).
|
||||
*
|
||||
* Checking for this attribute is a good way to determine whether the user is
|
||||
* selecting.
|
||||
*/
|
||||
selectionElement: ExcalidrawSelectionElement | null;
|
||||
isBindingEnabled: boolean;
|
||||
startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
|
||||
suggestedBindings: SuggestedBinding[];
|
||||
@ -198,9 +214,14 @@ export type AppState = {
|
||||
};
|
||||
editingFrame: string | null;
|
||||
elementsToHighlight: NonDeleted<ExcalidrawElement>[] | null;
|
||||
// element being edited, but not necessarily added to elements array yet
|
||||
// (e.g. text element when typing into the input)
|
||||
/**
|
||||
* Text that's being element, or new element being created.
|
||||
*/
|
||||
editingElement: NonDeletedExcalidrawElement | null;
|
||||
/**
|
||||
* Linear element that's being edited (when in the linear element editor).
|
||||
* Not set when creating multi-point linear element.
|
||||
*/
|
||||
editingLinearElement: LinearElementEditor | null;
|
||||
activeTool: {
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user