revert: remove bound-arrows from frames (#7190)
This commit is contained in:
parent
71ad3c5356
commit
104f64f1dc
@ -20,13 +20,3 @@ Frames should be ordered where frame children come first, followed by the frame
|
|||||||
```
|
```
|
||||||
|
|
||||||
If not oredered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.
|
If not oredered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.
|
||||||
|
|
||||||
# Arrows
|
|
||||||
|
|
||||||
An arrow can be a child of a frame only if it has no binding (either start or end) to any other element, regardless of whether the bound element is inside the frame or not.
|
|
||||||
|
|
||||||
This ensures that when an arrow is bound to an element outside the frame, it's rendered and behaves correctly.
|
|
||||||
|
|
||||||
Therefore, when an arrow (that's a child of a frame) gets bound to an element, it's automatically removed from the frame.
|
|
||||||
|
|
||||||
Bound-arrow is duplicated alongside a frame only if the arrow start is bound to an element within that frame.
|
|
||||||
|
@ -155,12 +155,7 @@ const duplicateElements = (
|
|||||||
groupId,
|
groupId,
|
||||||
).flatMap((element) =>
|
).flatMap((element) =>
|
||||||
isFrameElement(element)
|
isFrameElement(element)
|
||||||
? [
|
? [...getFrameElements(elements, element.id), element]
|
||||||
...getFrameElements(elements, element.id, {
|
|
||||||
includeBoundArrows: true,
|
|
||||||
}),
|
|
||||||
element,
|
|
||||||
]
|
|
||||||
: [element],
|
: [element],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -186,9 +181,7 @@ const duplicateElements = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isElementAFrame) {
|
if (isElementAFrame) {
|
||||||
const elementsInFrame = getFrameElements(sortedElements, element.id, {
|
const elementsInFrame = getFrameElements(sortedElements, element.id);
|
||||||
includeBoundArrows: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
elementsWithClones.push(
|
elementsWithClones.push(
|
||||||
...markAsProcessed([
|
...markAsProcessed([
|
||||||
|
@ -2554,12 +2554,18 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
||||||
if (text.length) {
|
if (text.length) {
|
||||||
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||||
|
x,
|
||||||
|
y: currentY,
|
||||||
|
});
|
||||||
|
|
||||||
const element = newTextElement({
|
const element = newTextElement({
|
||||||
...textElementProps,
|
...textElementProps,
|
||||||
x,
|
x,
|
||||||
y: currentY,
|
y: currentY,
|
||||||
text,
|
text,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
});
|
});
|
||||||
acc.push(element);
|
acc.push(element);
|
||||||
currentY += element.height + LINE_GAP;
|
currentY += element.height + LINE_GAP;
|
||||||
@ -3574,11 +3580,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return getElementsAtPosition(elements, (element) =>
|
return getElementsAtPosition(elements, (element) =>
|
||||||
hitTest(element, this.state, this.frameNameBoundsCache, x, y),
|
hitTest(element, this.state, this.frameNameBoundsCache, x, y),
|
||||||
).filter((element) => {
|
).filter((element) => {
|
||||||
// arrows don't clip even if they're children of frames,
|
|
||||||
// so always allow hitbox regardless of beinging contained in frame
|
|
||||||
if (isArrowElement(element)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// hitting a frame's element from outside the frame is not considered a hit
|
// hitting a frame's element from outside the frame is not considered a hit
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element);
|
||||||
return containingFrame &&
|
return containingFrame &&
|
||||||
|
@ -43,7 +43,6 @@ import {
|
|||||||
measureBaseline,
|
measureBaseline,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { normalizeLink } from "./url";
|
import { normalizeLink } from "./url";
|
||||||
import { isValidFrameChild } from "../frame";
|
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
@ -397,7 +396,7 @@ const repairBoundElement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* resets `frameId` if no longer applicable.
|
* Remove an element's frameId if its containing frame is non-existent
|
||||||
*
|
*
|
||||||
* NOTE mutates elements.
|
* NOTE mutates elements.
|
||||||
*/
|
*/
|
||||||
@ -405,16 +404,12 @@ const repairFrameMembership = (
|
|||||||
element: Mutable<ExcalidrawElement>,
|
element: Mutable<ExcalidrawElement>,
|
||||||
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
||||||
) => {
|
) => {
|
||||||
if (!element.frameId) {
|
if (element.frameId) {
|
||||||
return;
|
const containingFrame = elementsMap.get(element.frameId);
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (!containingFrame) {
|
||||||
!isValidFrameChild(element) ||
|
element.frameId = null;
|
||||||
// target frame not exists
|
}
|
||||||
!elementsMap.get(element.frameId)
|
|
||||||
) {
|
|
||||||
element.frameId = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -458,8 +453,6 @@ export const restoreElements = (
|
|||||||
// repair binding. Mutates elements.
|
// repair binding. Mutates elements.
|
||||||
const restoredElementsMap = arrayToMap(restoredElements);
|
const restoredElementsMap = arrayToMap(restoredElements);
|
||||||
for (const element of restoredElements) {
|
for (const element of restoredElements) {
|
||||||
// repair frame membership *after* bindings we do in restoreElement()
|
|
||||||
// since we rely on bindings to be correct
|
|
||||||
if (element.frameId) {
|
if (element.frameId) {
|
||||||
repairFrameMembership(element, restoredElementsMap);
|
repairFrameMembership(element, restoredElementsMap);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import { LinearElementEditor } from "./linearElementEditor";
|
|||||||
import { arrayToMap, tupleToCoors } from "../utils";
|
import { arrayToMap, tupleToCoors } from "../utils";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||||
import { isValidFrameChild } from "../frame";
|
|
||||||
|
|
||||||
export type SuggestedBinding =
|
export type SuggestedBinding =
|
||||||
| NonDeleted<ExcalidrawBindableElement>
|
| NonDeleted<ExcalidrawBindableElement>
|
||||||
@ -212,15 +211,6 @@ export const bindLinearElement = (
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (linearElement.frameId && !isValidFrameChild(linearElement)) {
|
|
||||||
mutateElement(
|
|
||||||
linearElement,
|
|
||||||
{
|
|
||||||
frameId: null,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't bind both ends of a simple segment
|
// Don't bind both ends of a simple segment
|
||||||
|
30
src/frame.ts
30
src/frame.ts
@ -323,24 +323,7 @@ export const groupByFrames = (elements: readonly ExcalidrawElement[]) => {
|
|||||||
export const getFrameElements = (
|
export const getFrameElements = (
|
||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
allElements: ExcalidrawElementsIncludingDeleted,
|
||||||
frameId: string,
|
frameId: string,
|
||||||
opts?: { includeBoundArrows?: boolean },
|
) => allElements.filter((element) => element.frameId === frameId);
|
||||||
) => {
|
|
||||||
return allElements.filter((element) => {
|
|
||||||
if (element.frameId === frameId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (opts?.includeBoundArrows && element.type === "arrow") {
|
|
||||||
const bindingId = element.startBinding?.elementId;
|
|
||||||
if (bindingId) {
|
|
||||||
const boundElement = Scene.getScene(element)?.getElement(bindingId);
|
|
||||||
if (boundElement?.frameId === frameId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getElementsInResizingFrame = (
|
export const getElementsInResizingFrame = (
|
||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
allElements: ExcalidrawElementsIncludingDeleted,
|
||||||
@ -468,14 +451,6 @@ export const getContainingFrame = (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidFrameChild = (element: ExcalidrawElement) => {
|
|
||||||
return (
|
|
||||||
element.type !== "frame" &&
|
|
||||||
// arrows that are bound to elements cannot be frame children
|
|
||||||
(element.type !== "arrow" || (!element.startBinding && !element.endBinding))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------- Frame Operations -------------------------------
|
// --------------------------- Frame Operations -------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -514,9 +489,6 @@ export const addElementsToFrame = (
|
|||||||
elementsToAdd,
|
elementsToAdd,
|
||||||
)) {
|
)) {
|
||||||
if (!currTargetFrameChildrenMap.has(element.id)) {
|
if (!currTargetFrameChildrenMap.has(element.id)) {
|
||||||
if (!isValidFrameChild(element)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
finalElementsToAdd.push(element);
|
finalElementsToAdd.push(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ import {
|
|||||||
} from "../element/Hyperlink";
|
} from "../element/Hyperlink";
|
||||||
import { renderSnaps } from "./renderSnaps";
|
import { renderSnaps } from "./renderSnaps";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
|
||||||
isEmbeddableElement,
|
isEmbeddableElement,
|
||||||
isFrameElement,
|
isFrameElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
@ -985,10 +984,7 @@ const _renderStaticScene = ({
|
|||||||
|
|
||||||
// TODO do we need to check isElementInFrame here?
|
// TODO do we need to check isElementInFrame here?
|
||||||
if (frame && isElementInFrame(element, elements, appState)) {
|
if (frame && isElementInFrame(element, elements, appState)) {
|
||||||
// do not clip arrows
|
frameClip(frame, context, renderConfig, appState);
|
||||||
if (!isArrowElement(element)) {
|
|
||||||
frameClip(frame, context, renderConfig, appState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
renderElement(element, rc, context, renderConfig, appState);
|
renderElement(element, rc, context, renderConfig, appState);
|
||||||
context.restore();
|
context.restore();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user