feat: change boundElementIdsboundElements (#4404)

This commit is contained in:
David Luzar 2021-12-14 16:07:01 +01:00 committed by GitHub
parent 104664cb9e
commit 390da3fd0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 589 additions and 555 deletions

View file

@ -8,7 +8,11 @@ import {
} from "./types";
import { getElementAtPosition } from "../scene";
import { AppState } from "../types";
import { isBindableElement, isBindingElement } from "./typeChecks";
import {
isBindableElement,
isBindingElement,
isLinearElement,
} from "./typeChecks";
import {
bindingBorderTest,
distanceToBindableElement,
@ -20,7 +24,7 @@ import {
import { mutateElement } from "./mutateElement";
import Scene from "../scene/Scene";
import { LinearElementEditor } from "./linearElementEditor";
import { tupleToCoors } from "../utils";
import { arrayToMap, tupleToCoors } from "../utils";
import { KEYS } from "../keys";
export type SuggestedBinding =
@ -74,8 +78,9 @@ export const bindOrUnbindLinearElement = (
.getNonDeletedElements(onlyUnbound)
.forEach((element) => {
mutateElement(element, {
boundElementIds: element.boundElementIds?.filter(
(id) => id !== linearElement.id,
boundElements: element.boundElements?.filter(
(element) =>
element.type !== "arrow" || element.id !== linearElement.id,
),
});
});
@ -180,11 +185,16 @@ const bindLinearElement = (
...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd),
} as PointBinding,
});
mutateElement(hoveredElement, {
boundElementIds: Array.from(
new Set([...(hoveredElement.boundElementIds ?? []), linearElement.id]),
),
});
const boundElementsMap = arrayToMap(hoveredElement.boundElements || []);
if (!boundElementsMap.has(linearElement.id)) {
mutateElement(hoveredElement, {
boundElements: (hoveredElement.boundElements || []).concat({
id: linearElement.id,
type: "arrow",
}),
});
}
};
// Don't bind both ends of a simple segment
@ -284,52 +294,56 @@ export const updateBoundElements = (
newSize?: { width: number; height: number };
},
) => {
const boundElementIds = changedElement.boundElementIds ?? [];
if (boundElementIds.length === 0) {
const boundLinearElements = (changedElement.boundElements ?? []).filter(
(el) => el.type === "arrow",
);
if (boundLinearElements.length === 0) {
return;
}
const { newSize, simultaneouslyUpdated } = options ?? {};
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
simultaneouslyUpdated,
);
(
Scene.getScene(changedElement)!.getNonDeletedElements(
boundElementIds,
) as NonDeleted<ExcalidrawLinearElement>[]
).forEach((linearElement) => {
const bindableElement = changedElement as ExcalidrawBindableElement;
// In case the boundElementIds are stale
if (!doesNeedUpdate(linearElement, bindableElement)) {
return;
}
const startBinding = maybeCalculateNewGapWhenScaling(
bindableElement,
linearElement.startBinding,
newSize,
);
const endBinding = maybeCalculateNewGapWhenScaling(
bindableElement,
linearElement.endBinding,
newSize,
);
// `linearElement` is being moved/scaled already, just update the binding
if (simultaneouslyUpdatedElementIds.has(linearElement.id)) {
mutateElement(linearElement, { startBinding, endBinding });
return;
}
updateBoundPoint(
linearElement,
"start",
startBinding,
changedElement as ExcalidrawBindableElement,
);
updateBoundPoint(
linearElement,
"end",
endBinding,
changedElement as ExcalidrawBindableElement,
);
});
Scene.getScene(changedElement)!
.getNonDeletedElements(boundLinearElements.map((el) => el.id))
.forEach((element) => {
if (!isLinearElement(element)) {
return;
}
const bindableElement = changedElement as ExcalidrawBindableElement;
// In case the boundElements are stale
if (!doesNeedUpdate(element, bindableElement)) {
return;
}
const startBinding = maybeCalculateNewGapWhenScaling(
bindableElement,
element.startBinding,
newSize,
);
const endBinding = maybeCalculateNewGapWhenScaling(
bindableElement,
element.endBinding,
newSize,
);
// `linearElement` is being moved/scaled already, just update the binding
if (simultaneouslyUpdatedElementIds.has(element.id)) {
mutateElement(element, { startBinding, endBinding });
return;
}
updateBoundPoint(
element,
"start",
startBinding,
changedElement as ExcalidrawBindableElement,
);
updateBoundPoint(
element,
"end",
endBinding,
changedElement as ExcalidrawBindableElement,
);
});
};
const doesNeedUpdate = (
@ -559,11 +573,11 @@ export const fixBindingsAfterDuplication = (
const allBindableElementIds: Set<ExcalidrawElement["id"]> = new Set();
const shouldReverseRoles = duplicatesServeAsOld === "duplicatesServeAsOld";
oldElements.forEach((oldElement) => {
const { boundElementIds } = oldElement;
if (boundElementIds != null && boundElementIds.length > 0) {
boundElementIds.forEach((boundElementId) => {
if (shouldReverseRoles && !oldIdToDuplicatedId.has(boundElementId)) {
allBoundElementIds.add(boundElementId);
const { boundElements } = oldElement;
if (boundElements != null && boundElements.length > 0) {
boundElements.forEach((boundElement) => {
if (shouldReverseRoles && !oldIdToDuplicatedId.has(boundElement.id)) {
allBoundElementIds.add(boundElement.id);
}
});
allBindableElementIds.add(oldIdToDuplicatedId.get(oldElement.id)!);
@ -607,12 +621,16 @@ export const fixBindingsAfterDuplication = (
sceneElements
.filter(({ id }) => allBindableElementIds.has(id))
.forEach((bindableElement) => {
const { boundElementIds } = bindableElement;
if (boundElementIds != null && boundElementIds.length > 0) {
const { boundElements } = bindableElement;
if (boundElements != null && boundElements.length > 0) {
mutateElement(bindableElement, {
boundElementIds: boundElementIds.map(
(boundElementId) =>
oldIdToDuplicatedId.get(boundElementId) ?? boundElementId,
boundElements: boundElements.map((boundElement) =>
oldIdToDuplicatedId.has(boundElement.id)
? {
id: oldIdToDuplicatedId.get(boundElement.id)!,
type: boundElement.type,
}
: boundElement,
),
});
}
@ -645,9 +663,9 @@ export const fixBindingsAfterDeletion = (
const boundElementIds: Set<ExcalidrawElement["id"]> = new Set();
deletedElements.forEach((deletedElement) => {
if (isBindableElement(deletedElement)) {
deletedElement.boundElementIds?.forEach((id) => {
if (!deletedElementIds.has(id)) {
boundElementIds.add(id);
deletedElement.boundElements?.forEach((element) => {
if (!deletedElementIds.has(element.id)) {
boundElementIds.add(element.id);
}
});
}

View file

@ -27,7 +27,7 @@ type ElementConstructorOpts = MarkOptional<
| "height"
| "angle"
| "groupIds"
| "boundElementIds"
| "boundElements"
| "seed"
| "version"
| "versionNonce"
@ -50,7 +50,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
angle = 0,
groupIds = [],
strokeSharpness,
boundElementIds = null,
boundElements = null,
...rest
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
) => ({
@ -74,7 +74,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
version: rest.version || 1,
versionNonce: rest.versionNonce ?? 0,
isDeleted: false as false,
boundElementIds,
boundElements,
updated: getUpdatedTimestamp(),
});

View file

@ -43,8 +43,13 @@ type _ExcalidrawElementBase = Readonly<{
/** List of groups the element belongs to.
Ordered from deepest to shallowest. */
groupIds: readonly GroupId[];
/** Ids of (linear) elements that are bound to this element. */
boundElementIds: readonly ExcalidrawLinearElement["id"][] | null;
/** other elements that are bound to this element */
boundElements:
| readonly Readonly<{
id: ExcalidrawLinearElement["id"];
type: "arrow" | "text";
}>[]
| null;
/** epoch (ms) timestamp of last element update */
updated: number;
}>;