|
|
|
@@ -176,7 +176,9 @@ import {
|
|
|
|
|
isValidTextContainer,
|
|
|
|
|
redrawTextBoundingBox,
|
|
|
|
|
hasBoundingBox,
|
|
|
|
|
getCommonFrameId,
|
|
|
|
|
getFrameChildren,
|
|
|
|
|
getFrameChildrenInsertionIndex,
|
|
|
|
|
isCursorInFrame,
|
|
|
|
|
addElementsToFrame,
|
|
|
|
|
replaceAllElementsInFrame,
|
|
|
|
@@ -259,6 +261,7 @@ import {
|
|
|
|
|
maybeHandleArrowPointlikeDrag,
|
|
|
|
|
getUncroppedWidthAndHeight,
|
|
|
|
|
getActiveTextElement,
|
|
|
|
|
isEligibleFrameChildType,
|
|
|
|
|
} from "@excalidraw/element";
|
|
|
|
|
|
|
|
|
|
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
|
|
|
@@ -2092,20 +2095,30 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
|
|
const { renderTopRightUI, renderTopLeftUI, renderCustomStats } = this.props;
|
|
|
|
|
|
|
|
|
|
const sceneNonce = this.scene.getSceneNonce();
|
|
|
|
|
const { elementsMap, visibleElements } =
|
|
|
|
|
this.renderer.getRenderableElements({
|
|
|
|
|
sceneNonce,
|
|
|
|
|
zoom: this.state.zoom,
|
|
|
|
|
offsetLeft: this.state.offsetLeft,
|
|
|
|
|
offsetTop: this.state.offsetTop,
|
|
|
|
|
scrollX: this.state.scrollX,
|
|
|
|
|
scrollY: this.state.scrollY,
|
|
|
|
|
height: this.state.height,
|
|
|
|
|
width: this.state.width,
|
|
|
|
|
editingTextElement: this.state.editingTextElement,
|
|
|
|
|
newElementId: this.state.newElement?.id,
|
|
|
|
|
});
|
|
|
|
|
const {
|
|
|
|
|
elementsMap,
|
|
|
|
|
visibleElements,
|
|
|
|
|
canvasNonce,
|
|
|
|
|
/**
|
|
|
|
|
* element to draw on the <NewElementCanvas> for optimization purposes.
|
|
|
|
|
* Can be null even if this.state.newElement defined
|
|
|
|
|
* (e.g. when its zIndex isn't on top) */
|
|
|
|
|
newElementCanvasElement,
|
|
|
|
|
} = this.renderer.getRenderableElements({
|
|
|
|
|
zoom: this.state.zoom,
|
|
|
|
|
offsetLeft: this.state.offsetLeft,
|
|
|
|
|
offsetTop: this.state.offsetTop,
|
|
|
|
|
scrollX: this.state.scrollX,
|
|
|
|
|
scrollY: this.state.scrollY,
|
|
|
|
|
height: this.state.height,
|
|
|
|
|
width: this.state.width,
|
|
|
|
|
editingTextElement: this.state.editingTextElement,
|
|
|
|
|
newElement: this.state.newElement,
|
|
|
|
|
selectedElements,
|
|
|
|
|
selectedElementsAreBeingDragged:
|
|
|
|
|
this.state.selectedElementsAreBeingDragged,
|
|
|
|
|
frameToHighlight: this.state.frameToHighlight,
|
|
|
|
|
});
|
|
|
|
|
this.visibleElements = visibleElements;
|
|
|
|
|
|
|
|
|
|
const allElementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
|
@@ -2324,7 +2337,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
elementsMap={elementsMap}
|
|
|
|
|
allElementsMap={allElementsMap}
|
|
|
|
|
visibleElements={visibleElements}
|
|
|
|
|
sceneNonce={sceneNonce}
|
|
|
|
|
canvasNonce={canvasNonce}
|
|
|
|
|
selectionNonce={
|
|
|
|
|
this.state.selectionElement?.versionNonce
|
|
|
|
|
}
|
|
|
|
@@ -2345,9 +2358,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
theme: this.state.theme,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{this.state.newElement && (
|
|
|
|
|
{newElementCanvasElement && (
|
|
|
|
|
<NewElementCanvas
|
|
|
|
|
appState={this.state}
|
|
|
|
|
newElement={newElementCanvasElement}
|
|
|
|
|
scale={window.devicePixelRatio}
|
|
|
|
|
rc={this.rc}
|
|
|
|
|
elementsMap={elementsMap}
|
|
|
|
@@ -2375,7 +2389,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
visibleElements={visibleElements}
|
|
|
|
|
allElementsMap={allElementsMap}
|
|
|
|
|
selectedElements={selectedElements}
|
|
|
|
|
sceneNonce={sceneNonce}
|
|
|
|
|
canvasNonce={canvasNonce}
|
|
|
|
|
selectionNonce={
|
|
|
|
|
this.state.selectionElement?.versionNonce
|
|
|
|
|
}
|
|
|
|
@@ -2687,7 +2701,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
locked: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(frame);
|
|
|
|
|
this.insertNewElement(frame);
|
|
|
|
|
|
|
|
|
|
for (const child of selectedElements) {
|
|
|
|
|
this.scene.mutateElement(child, { frameId: frame.id });
|
|
|
|
@@ -3765,6 +3779,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
position:
|
|
|
|
|
this.editorInterface.formFactor === "desktop" ? "cursor" : "center",
|
|
|
|
|
retainSeed: isPlainPaste,
|
|
|
|
|
preserveFrameChildrenOrder: true,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@@ -3908,6 +3923,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
position: { clientX: number; clientY: number } | "cursor" | "center";
|
|
|
|
|
retainSeed?: boolean;
|
|
|
|
|
fitToContent?: boolean;
|
|
|
|
|
preserveFrameChildrenOrder?: boolean;
|
|
|
|
|
}) => {
|
|
|
|
|
const elements = restoreElements(opts.elements, null, {
|
|
|
|
|
deleteInvisibleElements: true,
|
|
|
|
@@ -3949,6 +3965,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
randomizeSeed: !opts.retainSeed,
|
|
|
|
|
preserveFrameChildrenOrder: opts.preserveFrameChildrenOrder,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const prevElements = this.scene.getElementsIncludingDeleted();
|
|
|
|
@@ -3970,11 +3987,10 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
duplicatedElements,
|
|
|
|
|
topLayerFrame,
|
|
|
|
|
);
|
|
|
|
|
addElementsToFrame(
|
|
|
|
|
nextElements = addElementsToFrame(
|
|
|
|
|
nextElements,
|
|
|
|
|
eligibleElements,
|
|
|
|
|
topLayerFrame,
|
|
|
|
|
this.state,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -4200,7 +4216,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.scene.insertElements(textElements);
|
|
|
|
|
this.insertNewElements(textElements);
|
|
|
|
|
this.store.scheduleCapture();
|
|
|
|
|
this.setState({
|
|
|
|
|
selectedElementIds: makeNextSelectedElementIds(
|
|
|
|
@@ -5456,7 +5472,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
if (!event[KEYS.CTRL_OR_CMD]) {
|
|
|
|
|
if (this.flowChartCreator.isCreatingChart) {
|
|
|
|
|
if (this.flowChartCreator.pendingNodes?.length) {
|
|
|
|
|
this.scene.insertElements(this.flowChartCreator.pendingNodes);
|
|
|
|
|
this.insertNewElements(this.flowChartCreator.pendingNodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const firstNode = this.flowChartCreator.pendingNodes?.[0];
|
|
|
|
@@ -5554,6 +5570,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
selectedLinearElement: isSelectionLikeTool(nextActiveTool.type)
|
|
|
|
|
? prevState.selectedLinearElement
|
|
|
|
|
: null,
|
|
|
|
|
frameToHighlight: null,
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
if (nextActiveTool.type === "freedraw") {
|
|
|
|
@@ -6151,6 +6168,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
hitElement = elements[index];
|
|
|
|
|
break;
|
|
|
|
|
} else if (x1 < x && x < x2 && y1 < y && y < y2) {
|
|
|
|
|
// to allow binding to containers within frames,
|
|
|
|
|
// ignore frames in hit testing
|
|
|
|
|
if (isFrameLikeElement(elements[index])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hitElement = elements[index];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@@ -6240,11 +6263,6 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
|
|
|
|
x: sceneX,
|
|
|
|
|
y: sceneY,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const textCreationGridPoint = this.getTextCreationGridPoint(sceneX, sceneY);
|
|
|
|
|
|
|
|
|
|
const newTextElementPosition = parentCenterPosition
|
|
|
|
@@ -6266,6 +6284,20 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
y: sceneY,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
|
|
|
|
x: newTextElementPosition.x,
|
|
|
|
|
y: newTextElementPosition.y,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// container has higher priority. Only add to frame if container is in the same frame.
|
|
|
|
|
const frameId =
|
|
|
|
|
topLayerFrame &&
|
|
|
|
|
(!shouldBindToContainer ||
|
|
|
|
|
!container ||
|
|
|
|
|
container.frameId === topLayerFrame.id)
|
|
|
|
|
? topLayerFrame.id
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const element =
|
|
|
|
|
existingTextElement ||
|
|
|
|
|
newTextElement({
|
|
|
|
@@ -6295,7 +6327,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
? (0 as Radians)
|
|
|
|
|
: container.angle
|
|
|
|
|
: (0 as Radians),
|
|
|
|
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
|
|
|
|
frameId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!existingTextElement && shouldBindToContainer && container) {
|
|
|
|
@@ -6311,9 +6343,12 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
if (!existingTextElement) {
|
|
|
|
|
if (container && shouldBindToContainer) {
|
|
|
|
|
const containerIndex = this.scene.getElementIndex(container.id);
|
|
|
|
|
this.scene.insertElementAtIndex(element, containerIndex + 1);
|
|
|
|
|
// TODO should use insertNewElement, after we update it to handle
|
|
|
|
|
// elements with containerId + frameId at the same time (containerId
|
|
|
|
|
// should take precedence when it comes to z-index)
|
|
|
|
|
this.scene.insertElementsAtIndex([element], containerIndex + 1);
|
|
|
|
|
} else {
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -6695,19 +6730,161 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private getTopLayerFrameAtSceneCoords = (sceneCoords: {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
}) => {
|
|
|
|
|
/**
|
|
|
|
|
* finds candidate frame under cursor (when dragging frame children/elements
|
|
|
|
|
* inside frames)
|
|
|
|
|
*/
|
|
|
|
|
private getTopLayerFrameAtSceneCoords = (
|
|
|
|
|
/**
|
|
|
|
|
* should be already grid aligned (basically should be what the call site
|
|
|
|
|
* sets the element's coords to, if applicable)
|
|
|
|
|
*/
|
|
|
|
|
sceneCoords: {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
},
|
|
|
|
|
opts?: {
|
|
|
|
|
/** to exclude selected elements when dragging, etc. */
|
|
|
|
|
excludeElementIds?: AppState["selectedElementIds"];
|
|
|
|
|
currentFrameId?: ExcalidrawElement["frameId"];
|
|
|
|
|
},
|
|
|
|
|
) => {
|
|
|
|
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
|
|
|
const frames = this.scene
|
|
|
|
|
const framesUnderCursor = this.scene
|
|
|
|
|
.getNonDeletedFramesLikes()
|
|
|
|
|
.filter(
|
|
|
|
|
(frame): frame is ExcalidrawFrameLikeElement =>
|
|
|
|
|
!frame.locked && isCursorInFrame(sceneCoords, frame, elementsMap),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return frames.length ? frames[frames.length - 1] : null;
|
|
|
|
|
if (!framesUnderCursor.length) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const topLayerFrame = framesUnderCursor.at(-1)!;
|
|
|
|
|
|
|
|
|
|
const hitElement = this.getElementsAtPosition(
|
|
|
|
|
sceneCoords.x,
|
|
|
|
|
sceneCoords.y,
|
|
|
|
|
{
|
|
|
|
|
includeLockedElements: true,
|
|
|
|
|
},
|
|
|
|
|
).findLast((element) => !opts?.excludeElementIds?.[element.id]);
|
|
|
|
|
|
|
|
|
|
if (hitElement) {
|
|
|
|
|
if (
|
|
|
|
|
isFrameLikeElement(hitElement) &&
|
|
|
|
|
// case: we're hitting a locked frame itself (frame's outline
|
|
|
|
|
// or later its bg once implemented)
|
|
|
|
|
!hitElement.locked
|
|
|
|
|
) {
|
|
|
|
|
return topLayerFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hitElementIndex = this.scene.getElementIndex(hitElement.id);
|
|
|
|
|
const topLayerFrameIndex = this.scene.getElementIndex(topLayerFrame.id);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
hitElementIndex !== -1 &&
|
|
|
|
|
topLayerFrameIndex !== -1 &&
|
|
|
|
|
hitElementIndex <= topLayerFrameIndex
|
|
|
|
|
) {
|
|
|
|
|
return topLayerFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// to support a case of dragging a pre-existing frame child underneath
|
|
|
|
|
// a non-frame element covering the cursor
|
|
|
|
|
const currentFrame = opts?.currentFrameId
|
|
|
|
|
? framesUnderCursor.find((frame) => frame.id === opts.currentFrameId) ??
|
|
|
|
|
null
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (currentFrame) {
|
|
|
|
|
return currentFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hitElement.frameId
|
|
|
|
|
? framesUnderCursor.find((frame) => frame.id === hitElement.frameId) ??
|
|
|
|
|
null
|
|
|
|
|
: null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return topLayerFrame;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private updateFrameToHighlight = (
|
|
|
|
|
frameToHighlight: AppState["frameToHighlight"],
|
|
|
|
|
) => {
|
|
|
|
|
if (this.state.frameToHighlight !== frameToHighlight) {
|
|
|
|
|
this.setState({ frameToHighlight });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private maybeUpdateFrameToHighlightOnPointerMove = (
|
|
|
|
|
sceneCoords: { x: number; y: number },
|
|
|
|
|
isOverScrollBar: boolean,
|
|
|
|
|
) => {
|
|
|
|
|
// currently this function is being called even during pointerdown so we
|
|
|
|
|
// need to make sure we don't re-set the state when dragging and similar
|
|
|
|
|
//
|
|
|
|
|
// But, we still want to reset on pointermove in case the state is stale
|
|
|
|
|
// so we updte even for non-eligible tool types
|
|
|
|
|
if (
|
|
|
|
|
this.state.newElement ||
|
|
|
|
|
this.state.multiElement ||
|
|
|
|
|
this.state.selectionElement ||
|
|
|
|
|
this.state.selectedElementsAreBeingDragged
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.updateFrameToHighlight(
|
|
|
|
|
!isOverScrollBar && isEligibleFrameChildType(this.state.activeTool.type)
|
|
|
|
|
? this.getTopLayerFrameAtSceneCoords(sceneCoords)
|
|
|
|
|
: null,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private insertNewElements = (elements: readonly ExcalidrawElement[]) => {
|
|
|
|
|
if (!elements.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chunkedElements: ExcalidrawElement[][] = [];
|
|
|
|
|
|
|
|
|
|
for (const element of elements) {
|
|
|
|
|
const currentChunk = chunkedElements[chunkedElements.length - 1];
|
|
|
|
|
|
|
|
|
|
if (currentChunk?.[0].frameId === element.frameId) {
|
|
|
|
|
currentChunk.push(element);
|
|
|
|
|
} else {
|
|
|
|
|
chunkedElements.push([element]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const chunk of chunkedElements) {
|
|
|
|
|
const frameId = chunk[0].frameId;
|
|
|
|
|
|
|
|
|
|
const insertionIndex = frameId
|
|
|
|
|
? getFrameChildrenInsertionIndex(
|
|
|
|
|
this.scene.getElementsIncludingDeleted(),
|
|
|
|
|
frameId,
|
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
this.scene.insertElementsAtIndex(chunk, insertionIndex);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private insertNewElement = (element: ExcalidrawElement) => {
|
|
|
|
|
this.insertNewElements([element]);
|
|
|
|
|
|
|
|
|
|
const frame = element.frameId
|
|
|
|
|
? this.scene.getNonDeletedElement(element.frameId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
this.updateFrameToHighlight(
|
|
|
|
|
frame && isFrameLikeElement(frame) ? frame : null,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private handleCanvasPointerMove = (
|
|
|
|
@@ -6809,6 +6986,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.maybeUpdateFrameToHighlightOnPointerMove(
|
|
|
|
|
{
|
|
|
|
|
x: scenePointerX,
|
|
|
|
|
y: scenePointerY,
|
|
|
|
|
},
|
|
|
|
|
isOverScrollBar,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!this.state.newElement &&
|
|
|
|
|
isActiveToolNonLinearSnappable(this.state.activeTool.type)
|
|
|
|
@@ -8863,7 +9048,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
pressures: simulatePressure ? [] : [event.pressure],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
|
|
|
|
|
this.setState((prevState) => {
|
|
|
|
|
const nextSelectedElementIds = {
|
|
|
|
@@ -8920,7 +9105,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
height,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
@@ -8974,7 +9159,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
link,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
};
|
|
|
|
@@ -9218,7 +9403,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
points: [pointFrom<LocalPoint>(0, 0), pointFrom<LocalPoint>(0, 0)],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
|
|
|
|
|
if (isBindingElement(element)) {
|
|
|
|
|
// Do the initial binding so the binding strategy has the initial state
|
|
|
|
@@ -9376,7 +9561,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
selectionElement: element,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.scene.insertElement(element);
|
|
|
|
|
this.insertNewElement(element);
|
|
|
|
|
this.setState({
|
|
|
|
|
multiElement: null,
|
|
|
|
|
newElement: element,
|
|
|
|
@@ -9409,7 +9594,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
? newMagicFrameElement(constructorOpts)
|
|
|
|
|
: newFrameElement(constructorOpts);
|
|
|
|
|
|
|
|
|
|
this.scene.insertElement(frame);
|
|
|
|
|
this.insertNewElement(frame);
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
multiElement: null,
|
|
|
|
@@ -9777,18 +9962,17 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedElementsHasAFrame = selectedElements.find((e) =>
|
|
|
|
|
const selectedElementsHasAFrame = selectedElements.some((e) =>
|
|
|
|
|
isFrameLikeElement(e),
|
|
|
|
|
);
|
|
|
|
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(pointerCoords);
|
|
|
|
|
const frameToHighlight =
|
|
|
|
|
topLayerFrame && !selectedElementsHasAFrame ? topLayerFrame : null;
|
|
|
|
|
const frameToHighlight = selectedElementsHasAFrame
|
|
|
|
|
? null
|
|
|
|
|
: this.getTopLayerFrameAtSceneCoords(pointerCoords, {
|
|
|
|
|
currentFrameId: getCommonFrameId(selectedElements),
|
|
|
|
|
excludeElementIds: this.state.selectedElementIds,
|
|
|
|
|
});
|
|
|
|
|
// Only update the state if there is a difference
|
|
|
|
|
if (this.state.frameToHighlight !== frameToHighlight) {
|
|
|
|
|
flushSync(() => {
|
|
|
|
|
this.setState({ frameToHighlight });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
this.updateFrameToHighlight(frameToHighlight);
|
|
|
|
|
|
|
|
|
|
// Marking that click was used for dragging to check
|
|
|
|
|
// if elements should be deselected on pointerup
|
|
|
|
@@ -10817,7 +11001,6 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
this.scene.getElementsMapIncludingDeleted(),
|
|
|
|
|
elementsInsideFrame,
|
|
|
|
|
newElement,
|
|
|
|
|
this.state,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
@@ -10877,9 +11060,14 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// update the relationships between selected elements and frames
|
|
|
|
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
|
|
|
|
|
|
|
|
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
|
|
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(
|
|
|
|
|
sceneCoords,
|
|
|
|
|
{
|
|
|
|
|
currentFrameId: getCommonFrameId(selectedElements),
|
|
|
|
|
excludeElementIds: this.state.selectedElementIds,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let nextElements = this.scene.getElementsMapIncludingDeleted();
|
|
|
|
|
|
|
|
|
|
const updateGroupIdsAfterEditingGroup = (
|
|
|
|
@@ -10928,10 +11116,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
topLayerFrame &&
|
|
|
|
|
!this.state.selectedElementIds[topLayerFrame.id]
|
|
|
|
|
) {
|
|
|
|
|
const elementsToAdd = selectedElements.filter(
|
|
|
|
|
(element) =>
|
|
|
|
|
element.frameId !== topLayerFrame.id &&
|
|
|
|
|
isElementInFrame(element, nextElements, this.state),
|
|
|
|
|
const elementsToAdd = selectedElements.filter((element) =>
|
|
|
|
|
isElementInFrame(element, nextElements, this.state),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (this.state.editingGroupId) {
|
|
|
|
@@ -10942,7 +11128,6 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
nextElements,
|
|
|
|
|
elementsToAdd,
|
|
|
|
|
topLayerFrame,
|
|
|
|
|
this.state,
|
|
|
|
|
);
|
|
|
|
|
} else if (!topLayerFrame) {
|
|
|
|
|
if (this.state.editingGroupId) {
|
|
|
|
@@ -11004,7 +11189,6 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
elementsMap,
|
|
|
|
|
),
|
|
|
|
|
frame,
|
|
|
|
|
this,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -11820,7 +12004,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
sceneY,
|
|
|
|
|
gridPadding,
|
|
|
|
|
);
|
|
|
|
|
placeholders.forEach((el) => this.scene.insertElement(el));
|
|
|
|
|
this.insertNewElements(placeholders);
|
|
|
|
|
|
|
|
|
|
// Create, position, insert and select initialized (replacing placeholders)
|
|
|
|
|
const initialized = await Promise.all(
|
|
|
|
@@ -11948,6 +12132,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
type: "everything",
|
|
|
|
|
elements: item.elements,
|
|
|
|
|
randomizeSeed: true,
|
|
|
|
|
preserveFrameChildrenOrder: true,
|
|
|
|
|
}).duplicatedElements,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|