|
@@ -5,7 +5,7 @@ import {
|
|
} from "./element";
|
|
} from "./element";
|
|
import {
|
|
import {
|
|
ExcalidrawElement,
|
|
ExcalidrawElement,
|
|
- ExcalidrawFrameElement,
|
|
|
|
|
|
+ ExcalidrawFrameLikeElement,
|
|
NonDeleted,
|
|
NonDeleted,
|
|
NonDeletedExcalidrawElement,
|
|
NonDeletedExcalidrawElement,
|
|
} from "./element/types";
|
|
} from "./element/types";
|
|
@@ -18,11 +18,11 @@ import { arrayToMap } from "./utils";
|
|
import { mutateElement } from "./element/mutateElement";
|
|
import { mutateElement } from "./element/mutateElement";
|
|
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
|
|
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
|
|
import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
|
import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
|
-import { isFrameElement } from "./element";
|
|
|
|
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
|
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
|
import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
|
import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
|
import { getElementLineSegments } from "./element/bounds";
|
|
import { getElementLineSegments } from "./element/bounds";
|
|
import { doLineSegmentsIntersect } from "./packages/utils";
|
|
import { doLineSegmentsIntersect } from "./packages/utils";
|
|
|
|
+import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
|
|
|
|
|
|
// --------------------------- Frame State ------------------------------------
|
|
// --------------------------- Frame State ------------------------------------
|
|
export const bindElementsToFramesAfterDuplication = (
|
|
export const bindElementsToFramesAfterDuplication = (
|
|
@@ -58,7 +58,7 @@ export const bindElementsToFramesAfterDuplication = (
|
|
|
|
|
|
export function isElementIntersectingFrame(
|
|
export function isElementIntersectingFrame(
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) {
|
|
) {
|
|
const frameLineSegments = getElementLineSegments(frame);
|
|
const frameLineSegments = getElementLineSegments(frame);
|
|
|
|
|
|
@@ -75,20 +75,20 @@ export function isElementIntersectingFrame(
|
|
|
|
|
|
export const getElementsCompletelyInFrame = (
|
|
export const getElementsCompletelyInFrame = (
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) =>
|
|
) =>
|
|
- omitGroupsContainingFrames(
|
|
|
|
|
|
+ omitGroupsContainingFrameLikes(
|
|
getElementsWithinSelection(elements, frame, false),
|
|
getElementsWithinSelection(elements, frame, false),
|
|
).filter(
|
|
).filter(
|
|
(element) =>
|
|
(element) =>
|
|
- (element.type !== "frame" && !element.frameId) ||
|
|
|
|
|
|
+ (!isFrameLikeElement(element) && !element.frameId) ||
|
|
element.frameId === frame.id,
|
|
element.frameId === frame.id,
|
|
);
|
|
);
|
|
|
|
|
|
export const isElementContainingFrame = (
|
|
export const isElementContainingFrame = (
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
return getElementsWithinSelection(elements, element).some(
|
|
return getElementsWithinSelection(elements, element).some(
|
|
(e) => e.id === frame.id,
|
|
(e) => e.id === frame.id,
|
|
@@ -97,12 +97,12 @@ export const isElementContainingFrame = (
|
|
|
|
|
|
export const getElementsIntersectingFrame = (
|
|
export const getElementsIntersectingFrame = (
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => elements.filter((element) => isElementIntersectingFrame(element, frame));
|
|
) => elements.filter((element) => isElementIntersectingFrame(element, frame));
|
|
|
|
|
|
export const elementsAreInFrameBounds = (
|
|
export const elementsAreInFrameBounds = (
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
|
getElementAbsoluteCoords(frame);
|
|
getElementAbsoluteCoords(frame);
|
|
@@ -120,7 +120,7 @@ export const elementsAreInFrameBounds = (
|
|
|
|
|
|
export const elementOverlapsWithFrame = (
|
|
export const elementOverlapsWithFrame = (
|
|
element: ExcalidrawElement,
|
|
element: ExcalidrawElement,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
return (
|
|
return (
|
|
elementsAreInFrameBounds([element], frame) ||
|
|
elementsAreInFrameBounds([element], frame) ||
|
|
@@ -134,7 +134,7 @@ export const isCursorInFrame = (
|
|
x: number;
|
|
x: number;
|
|
y: number;
|
|
y: number;
|
|
},
|
|
},
|
|
- frame: NonDeleted<ExcalidrawFrameElement>,
|
|
|
|
|
|
+ frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
|
) => {
|
|
) => {
|
|
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame);
|
|
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame);
|
|
|
|
|
|
@@ -148,7 +148,7 @@ export const isCursorInFrame = (
|
|
export const groupsAreAtLeastIntersectingTheFrame = (
|
|
export const groupsAreAtLeastIntersectingTheFrame = (
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
groupIds: readonly string[],
|
|
groupIds: readonly string[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
|
getElementsInGroup(elements, groupId),
|
|
getElementsInGroup(elements, groupId),
|
|
@@ -168,7 +168,7 @@ export const groupsAreAtLeastIntersectingTheFrame = (
|
|
export const groupsAreCompletelyOutOfFrame = (
|
|
export const groupsAreCompletelyOutOfFrame = (
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
groupIds: readonly string[],
|
|
groupIds: readonly string[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
|
getElementsInGroup(elements, groupId),
|
|
getElementsInGroup(elements, groupId),
|
|
@@ -192,14 +192,14 @@ export const groupsAreCompletelyOutOfFrame = (
|
|
/**
|
|
/**
|
|
* Returns a map of frameId to frame elements. Includes empty frames.
|
|
* Returns a map of frameId to frame elements. Includes empty frames.
|
|
*/
|
|
*/
|
|
-export const groupByFrames = (elements: readonly ExcalidrawElement[]) => {
|
|
|
|
|
|
+export const groupByFrameLikes = (elements: readonly ExcalidrawElement[]) => {
|
|
const frameElementsMap = new Map<
|
|
const frameElementsMap = new Map<
|
|
ExcalidrawElement["id"],
|
|
ExcalidrawElement["id"],
|
|
ExcalidrawElement[]
|
|
ExcalidrawElement[]
|
|
>();
|
|
>();
|
|
|
|
|
|
for (const element of elements) {
|
|
for (const element of elements) {
|
|
- const frameId = isFrameElement(element) ? element.id : element.frameId;
|
|
|
|
|
|
+ const frameId = isFrameLikeElement(element) ? element.id : element.frameId;
|
|
if (frameId && !frameElementsMap.has(frameId)) {
|
|
if (frameId && !frameElementsMap.has(frameId)) {
|
|
frameElementsMap.set(frameId, getFrameChildren(elements, frameId));
|
|
frameElementsMap.set(frameId, getFrameChildren(elements, frameId));
|
|
}
|
|
}
|
|
@@ -213,12 +213,12 @@ export const getFrameChildren = (
|
|
frameId: string,
|
|
frameId: string,
|
|
) => allElements.filter((element) => element.frameId === frameId);
|
|
) => allElements.filter((element) => element.frameId === frameId);
|
|
|
|
|
|
-export const getFrameElements = (
|
|
|
|
|
|
+export const getFrameLikeElements = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
-): ExcalidrawFrameElement[] => {
|
|
|
|
- return allElements.filter((element) =>
|
|
|
|
- isFrameElement(element),
|
|
|
|
- ) as ExcalidrawFrameElement[];
|
|
|
|
|
|
+): ExcalidrawFrameLikeElement[] => {
|
|
|
|
+ return allElements.filter((element): element is ExcalidrawFrameLikeElement =>
|
|
|
|
+ isFrameLikeElement(element),
|
|
|
|
+ );
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -232,7 +232,7 @@ export const getFrameElements = (
|
|
export const getRootElements = (
|
|
export const getRootElements = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
) => {
|
|
) => {
|
|
- const frameElements = arrayToMap(getFrameElements(allElements));
|
|
|
|
|
|
+ const frameElements = arrayToMap(getFrameLikeElements(allElements));
|
|
return allElements.filter(
|
|
return allElements.filter(
|
|
(element) =>
|
|
(element) =>
|
|
frameElements.has(element.id) ||
|
|
frameElements.has(element.id) ||
|
|
@@ -243,7 +243,7 @@ export const getRootElements = (
|
|
|
|
|
|
export const getElementsInResizingFrame = (
|
|
export const getElementsInResizingFrame = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
appState: AppState,
|
|
appState: AppState,
|
|
): ExcalidrawElement[] => {
|
|
): ExcalidrawElement[] => {
|
|
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
|
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
|
@@ -336,9 +336,9 @@ export const getElementsInResizingFrame = (
|
|
|
|
|
|
export const getElementsInNewFrame = (
|
|
export const getElementsInNewFrame = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
- return omitGroupsContainingFrames(
|
|
|
|
|
|
+ return omitGroupsContainingFrameLikes(
|
|
allElements,
|
|
allElements,
|
|
getElementsCompletelyInFrame(allElements, frame),
|
|
getElementsCompletelyInFrame(allElements, frame),
|
|
);
|
|
);
|
|
@@ -356,12 +356,12 @@ export const getContainingFrame = (
|
|
if (element.frameId) {
|
|
if (element.frameId) {
|
|
if (elementsMap) {
|
|
if (elementsMap) {
|
|
return (elementsMap.get(element.frameId) ||
|
|
return (elementsMap.get(element.frameId) ||
|
|
- null) as null | ExcalidrawFrameElement;
|
|
|
|
|
|
+ null) as null | ExcalidrawFrameLikeElement;
|
|
}
|
|
}
|
|
return (
|
|
return (
|
|
(Scene.getScene(element)?.getElement(
|
|
(Scene.getScene(element)?.getElement(
|
|
element.frameId,
|
|
element.frameId,
|
|
- ) as ExcalidrawFrameElement) || null
|
|
|
|
|
|
+ ) as ExcalidrawFrameLikeElement) || null
|
|
);
|
|
);
|
|
}
|
|
}
|
|
return null;
|
|
return null;
|
|
@@ -377,7 +377,7 @@ export const getContainingFrame = (
|
|
export const addElementsToFrame = (
|
|
export const addElementsToFrame = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
elementsToAdd: NonDeletedExcalidrawElement[],
|
|
elementsToAdd: NonDeletedExcalidrawElement[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
) => {
|
|
) => {
|
|
const { currTargetFrameChildrenMap } = allElements.reduce(
|
|
const { currTargetFrameChildrenMap } = allElements.reduce(
|
|
(acc, element, index) => {
|
|
(acc, element, index) => {
|
|
@@ -397,7 +397,7 @@ export const addElementsToFrame = (
|
|
|
|
|
|
// - add bound text elements if not already in the array
|
|
// - add bound text elements if not already in the array
|
|
// - filter out elements that are already in the frame
|
|
// - filter out elements that are already in the frame
|
|
- for (const element of omitGroupsContainingFrames(
|
|
|
|
|
|
+ for (const element of omitGroupsContainingFrameLikes(
|
|
allElements,
|
|
allElements,
|
|
elementsToAdd,
|
|
elementsToAdd,
|
|
)) {
|
|
)) {
|
|
@@ -438,7 +438,7 @@ export const removeElementsFromFrame = (
|
|
>();
|
|
>();
|
|
|
|
|
|
const toRemoveElementsByFrame = new Map<
|
|
const toRemoveElementsByFrame = new Map<
|
|
- ExcalidrawFrameElement["id"],
|
|
|
|
|
|
+ ExcalidrawFrameLikeElement["id"],
|
|
ExcalidrawElement[]
|
|
ExcalidrawElement[]
|
|
>();
|
|
>();
|
|
|
|
|
|
@@ -474,7 +474,7 @@ export const removeElementsFromFrame = (
|
|
|
|
|
|
export const removeAllElementsFromFrame = (
|
|
export const removeAllElementsFromFrame = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
appState: AppState,
|
|
appState: AppState,
|
|
) => {
|
|
) => {
|
|
const elementsInFrame = getFrameChildren(allElements, frame.id);
|
|
const elementsInFrame = getFrameChildren(allElements, frame.id);
|
|
@@ -484,7 +484,7 @@ export const removeAllElementsFromFrame = (
|
|
export const replaceAllElementsInFrame = (
|
|
export const replaceAllElementsInFrame = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
nextElementsInFrame: ExcalidrawElement[],
|
|
nextElementsInFrame: ExcalidrawElement[],
|
|
- frame: ExcalidrawFrameElement,
|
|
|
|
|
|
+ frame: ExcalidrawFrameLikeElement,
|
|
appState: AppState,
|
|
appState: AppState,
|
|
) => {
|
|
) => {
|
|
return addElementsToFrame(
|
|
return addElementsToFrame(
|
|
@@ -524,7 +524,7 @@ export const updateFrameMembershipOfSelectedElements = (
|
|
elementsToFilter.forEach((element) => {
|
|
elementsToFilter.forEach((element) => {
|
|
if (
|
|
if (
|
|
element.frameId &&
|
|
element.frameId &&
|
|
- !isFrameElement(element) &&
|
|
|
|
|
|
+ !isFrameLikeElement(element) &&
|
|
!isElementInFrame(element, allElements, appState)
|
|
!isElementInFrame(element, allElements, appState)
|
|
) {
|
|
) {
|
|
elementsToRemove.add(element);
|
|
elementsToRemove.add(element);
|
|
@@ -540,7 +540,7 @@ export const updateFrameMembershipOfSelectedElements = (
|
|
* filters out elements that are inside groups that contain a frame element
|
|
* filters out elements that are inside groups that contain a frame element
|
|
* anywhere in the group tree
|
|
* anywhere in the group tree
|
|
*/
|
|
*/
|
|
-export const omitGroupsContainingFrames = (
|
|
|
|
|
|
+export const omitGroupsContainingFrameLikes = (
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
allElements: ExcalidrawElementsIncludingDeleted,
|
|
/** subset of elements you want to filter. Optional perf optimization so we
|
|
/** subset of elements you want to filter. Optional perf optimization so we
|
|
* don't have to filter all elements unnecessarily
|
|
* don't have to filter all elements unnecessarily
|
|
@@ -558,7 +558,9 @@ export const omitGroupsContainingFrames = (
|
|
const rejectedGroupIds = new Set<string>();
|
|
const rejectedGroupIds = new Set<string>();
|
|
for (const groupId of uniqueGroupIds) {
|
|
for (const groupId of uniqueGroupIds) {
|
|
if (
|
|
if (
|
|
- getElementsInGroup(allElements, groupId).some((el) => isFrameElement(el))
|
|
|
|
|
|
+ getElementsInGroup(allElements, groupId).some((el) =>
|
|
|
|
+ isFrameLikeElement(el),
|
|
|
|
+ )
|
|
) {
|
|
) {
|
|
rejectedGroupIds.add(groupId);
|
|
rejectedGroupIds.add(groupId);
|
|
}
|
|
}
|
|
@@ -636,7 +638,7 @@ export const isElementInFrame = (
|
|
}
|
|
}
|
|
|
|
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
- if (isFrameElement(elementInGroup)) {
|
|
|
|
|
|
+ if (isFrameLikeElement(elementInGroup)) {
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -650,3 +652,15 @@ export const isElementInFrame = (
|
|
|
|
|
|
return false;
|
|
return false;
|
|
};
|
|
};
|
|
|
|
+
|
|
|
|
+export const getFrameLikeTitle = (
|
|
|
|
+ element: ExcalidrawFrameLikeElement,
|
|
|
|
+ frameIdx: number,
|
|
|
|
+) => {
|
|
|
|
+ const existingName = element.name?.trim();
|
|
|
|
+ if (existingName) {
|
|
|
|
+ return existingName;
|
|
|
|
+ }
|
|
|
|
+ // TODO name frames AI only is specific to AI frames
|
|
|
|
+ return isFrameElement(element) ? `Frame ${frameIdx}` : `AI Frame ${frameIdx}`;
|
|
|
|
+};
|