mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Fix alt-drag binding
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
b47d7a9f88
commit
86629b06b0
2 changed files with 205 additions and 3 deletions
|
@ -55,6 +55,7 @@ import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isBindableElement,
|
isBindableElement,
|
||||||
|
isBindingElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFixedPointBinding,
|
isFixedPointBinding,
|
||||||
|
@ -1422,7 +1423,7 @@ const getLinearElementEdgeCoors = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fixBindingsAfterDuplication = (
|
export const fixDuplicatedBindingsAfterDuplication = (
|
||||||
newElements: ExcalidrawElement[],
|
newElements: ExcalidrawElement[],
|
||||||
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
duplicatedElementsMap: NonDeletedSceneElementsMap,
|
duplicatedElementsMap: NonDeletedSceneElementsMap,
|
||||||
|
@ -1493,6 +1494,196 @@ export const fixBindingsAfterDuplication = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fixReversedBindingsForBindables = (
|
||||||
|
original: ExcalidrawBindableElement,
|
||||||
|
duplicate: ExcalidrawBindableElement,
|
||||||
|
originalElements: Map<string, ExcalidrawElement>,
|
||||||
|
elementsWithClones: ExcalidrawElement[],
|
||||||
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
|
) => {
|
||||||
|
original.boundElements?.forEach((binding, idx) => {
|
||||||
|
if (binding.type !== "arrow") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldArrow = elementsWithClones.find((el) => el.id === binding.id);
|
||||||
|
|
||||||
|
if (!isBindingElement(oldArrow)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalElements.has(binding.id)) {
|
||||||
|
// Linked arrow is in the selection, so find the duplicate pair
|
||||||
|
const newArrowId = oldIdToDuplicatedId.get(binding.id) ?? binding.id;
|
||||||
|
const newArrow = elementsWithClones.find(
|
||||||
|
(el) => el.id === newArrowId,
|
||||||
|
)! as ExcalidrawArrowElement;
|
||||||
|
|
||||||
|
mutateElement(newArrow, {
|
||||||
|
startBinding:
|
||||||
|
oldArrow.startBinding?.elementId === binding.id
|
||||||
|
? {
|
||||||
|
...oldArrow.startBinding,
|
||||||
|
elementId: duplicate.id,
|
||||||
|
}
|
||||||
|
: newArrow.startBinding,
|
||||||
|
endBinding:
|
||||||
|
oldArrow.endBinding?.elementId === binding.id
|
||||||
|
? {
|
||||||
|
...oldArrow.endBinding,
|
||||||
|
elementId: duplicate.id,
|
||||||
|
}
|
||||||
|
: newArrow.endBinding,
|
||||||
|
});
|
||||||
|
mutateElement(duplicate, {
|
||||||
|
boundElements: [
|
||||||
|
...(duplicate.boundElements ?? []).filter(
|
||||||
|
(el) => el.id !== binding.id && el.id !== newArrowId,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
type: "arrow",
|
||||||
|
id: newArrowId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Linked arrow is outside the selection,
|
||||||
|
// so we move the binding to the duplicate
|
||||||
|
mutateElement(oldArrow, {
|
||||||
|
startBinding:
|
||||||
|
oldArrow.startBinding?.elementId === original.id
|
||||||
|
? {
|
||||||
|
...oldArrow.startBinding,
|
||||||
|
elementId: duplicate.id,
|
||||||
|
}
|
||||||
|
: oldArrow.startBinding,
|
||||||
|
endBinding:
|
||||||
|
oldArrow.endBinding?.elementId === original.id
|
||||||
|
? {
|
||||||
|
...oldArrow.endBinding,
|
||||||
|
elementId: duplicate.id,
|
||||||
|
}
|
||||||
|
: oldArrow.endBinding,
|
||||||
|
});
|
||||||
|
mutateElement(duplicate, {
|
||||||
|
boundElements: [
|
||||||
|
...(duplicate.boundElements ?? []),
|
||||||
|
{
|
||||||
|
type: "arrow",
|
||||||
|
id: oldArrow.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
mutateElement(original, {
|
||||||
|
boundElements:
|
||||||
|
original.boundElements?.filter((_, i) => i !== idx) ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixReversedBindingsForArrows = (
|
||||||
|
original: ExcalidrawArrowElement,
|
||||||
|
duplicate: ExcalidrawArrowElement,
|
||||||
|
originalElements: Map<string, ExcalidrawElement>,
|
||||||
|
bindingProp: "startBinding" | "endBinding",
|
||||||
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
|
elementsWithClones: ExcalidrawElement[],
|
||||||
|
) => {
|
||||||
|
const oldBindableId = original[bindingProp]?.elementId;
|
||||||
|
|
||||||
|
if (oldBindableId) {
|
||||||
|
if (originalElements.has(oldBindableId)) {
|
||||||
|
// Linked element is in the selection
|
||||||
|
const newBindableId =
|
||||||
|
oldIdToDuplicatedId.get(oldBindableId) ?? oldBindableId;
|
||||||
|
const newBindable = elementsWithClones.find(
|
||||||
|
(el) => el.id === newBindableId,
|
||||||
|
) as ExcalidrawBindableElement;
|
||||||
|
mutateElement(duplicate, {
|
||||||
|
[bindingProp]: {
|
||||||
|
...original[bindingProp],
|
||||||
|
elementId: newBindableId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mutateElement(newBindable, {
|
||||||
|
boundElements: [
|
||||||
|
...(newBindable.boundElements ?? []).filter(
|
||||||
|
(el) => el.id !== original.id && el.id !== duplicate.id,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: duplicate.id,
|
||||||
|
type: "arrow",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Linked element is outside the selection
|
||||||
|
const originalBindable = elementsWithClones.find(
|
||||||
|
(el) => el.id === oldBindableId,
|
||||||
|
);
|
||||||
|
if (originalBindable) {
|
||||||
|
mutateElement(duplicate, {
|
||||||
|
[bindingProp]: original[bindingProp],
|
||||||
|
});
|
||||||
|
mutateElement(original, {
|
||||||
|
[bindingProp]: null,
|
||||||
|
});
|
||||||
|
mutateElement(originalBindable, {
|
||||||
|
boundElements: [
|
||||||
|
...(originalBindable.boundElements?.filter(
|
||||||
|
(el) => el.id !== original.id,
|
||||||
|
) ?? []),
|
||||||
|
{
|
||||||
|
id: duplicate.id,
|
||||||
|
type: "arrow",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fixReversedBindings = (
|
||||||
|
originalElements: Map<string, ExcalidrawElement>,
|
||||||
|
elementsWithClones: ExcalidrawElement[],
|
||||||
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
|
) => {
|
||||||
|
for (const original of originalElements.values()) {
|
||||||
|
const duplicate = elementsWithClones.find(
|
||||||
|
(el) => el.id === oldIdToDuplicatedId.get(original.id),
|
||||||
|
)!;
|
||||||
|
|
||||||
|
if (isBindableElement(original) && isBindableElement(duplicate)) {
|
||||||
|
fixReversedBindingsForBindables(
|
||||||
|
original,
|
||||||
|
duplicate,
|
||||||
|
originalElements,
|
||||||
|
elementsWithClones,
|
||||||
|
oldIdToDuplicatedId,
|
||||||
|
);
|
||||||
|
} else if (isArrowElement(original) && isArrowElement(duplicate)) {
|
||||||
|
fixReversedBindingsForArrows(
|
||||||
|
original,
|
||||||
|
duplicate,
|
||||||
|
originalElements,
|
||||||
|
"startBinding",
|
||||||
|
oldIdToDuplicatedId,
|
||||||
|
elementsWithClones,
|
||||||
|
);
|
||||||
|
fixReversedBindingsForArrows(
|
||||||
|
original,
|
||||||
|
duplicate,
|
||||||
|
originalElements,
|
||||||
|
"endBinding",
|
||||||
|
oldIdToDuplicatedId,
|
||||||
|
elementsWithClones,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fixBindingsAfterDeletion = (
|
export const fixBindingsAfterDeletion = (
|
||||||
sceneElements: readonly ExcalidrawElement[],
|
sceneElements: readonly ExcalidrawElement[],
|
||||||
deletedElements: readonly ExcalidrawElement[],
|
deletedElements: readonly ExcalidrawElement[],
|
||||||
|
|
|
@ -36,7 +36,10 @@ import {
|
||||||
|
|
||||||
import { getBoundTextElement, getContainerElement } from "./textElement";
|
import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||||
|
|
||||||
import { fixBindingsAfterDuplication } from "./binding";
|
import {
|
||||||
|
fixDuplicatedBindingsAfterDuplication,
|
||||||
|
fixReversedBindings,
|
||||||
|
} from "./binding";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
|
@ -381,12 +384,20 @@ export const duplicateElements = (
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
fixBindingsAfterDuplication(
|
fixDuplicatedBindingsAfterDuplication(
|
||||||
newElements,
|
newElements,
|
||||||
oldIdToDuplicatedId,
|
oldIdToDuplicatedId,
|
||||||
duplicatedElementsMap as NonDeletedSceneElementsMap,
|
duplicatedElementsMap as NonDeletedSceneElementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (reverseOrder) {
|
||||||
|
fixReversedBindings(
|
||||||
|
_idsOfElementsToDuplicate,
|
||||||
|
elementsWithClones,
|
||||||
|
oldIdToDuplicatedId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bindElementsToFramesAfterDuplication(
|
bindElementsToFramesAfterDuplication(
|
||||||
elementsWithClones,
|
elementsWithClones,
|
||||||
oldElements,
|
oldElements,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue