mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: exporting frame-overlapping elements belonging to other frames (#7584)
This commit is contained in:
parent
3b0593baa7
commit
46da032626
5 changed files with 92 additions and 19 deletions
|
@ -348,6 +348,7 @@ import {
|
|||
updateFrameMembershipOfSelectedElements,
|
||||
isElementInFrame,
|
||||
getFrameLikeTitle,
|
||||
getElementsOverlappingFrame,
|
||||
} from "../frame";
|
||||
import {
|
||||
excludeElementsInFramesFromSelection,
|
||||
|
@ -395,7 +396,7 @@ import {
|
|||
import { Emitter } from "../emitter";
|
||||
import { ElementCanvasButtons } from "../element/ElementCanvasButtons";
|
||||
import { MagicCacheData, diagramToHTML } from "../data/magic";
|
||||
import { elementsOverlappingBBox, exportToBlob } from "../../utils/export";
|
||||
import { exportToBlob } from "../../utils/export";
|
||||
import { COLOR_PALETTE } from "../colors";
|
||||
import { ElementCanvasButton } from "./MagicButton";
|
||||
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
||||
|
@ -1803,11 +1804,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const magicFrameChildren = elementsOverlappingBBox({
|
||||
elements: this.scene.getNonDeletedElements(),
|
||||
bounds: magicFrame,
|
||||
type: "overlap",
|
||||
}).filter((el) => !isMagicFrameElement(el));
|
||||
const magicFrameChildren = getElementsOverlappingFrame(
|
||||
this.scene.getNonDeletedElements(),
|
||||
magicFrame,
|
||||
).filter((el) => !isMagicFrameElement(el));
|
||||
|
||||
if (!magicFrameChildren.length) {
|
||||
if (source === "button") {
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { elementsOverlappingBBox } from "../../utils/export";
|
||||
import { isSomeElementSelected, getSelectedElements } from "../scene";
|
||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||
import { ExportType } from "../scene/types";
|
||||
|
@ -20,6 +19,7 @@ import { cloneJSON } from "../utils";
|
|||
import { canvasToBlob } from "./blob";
|
||||
import { fileSave, FileSystemHandle } from "./filesystem";
|
||||
import { serializeAsJSON } from "./json";
|
||||
import { getElementsOverlappingFrame } from "../frame";
|
||||
|
||||
export { loadFromBlob } from "./blob";
|
||||
export { loadFromJSON, saveAsJSON } from "./json";
|
||||
|
@ -56,11 +56,7 @@ export const prepareElementsForExport = (
|
|||
isFrameLikeElement(exportedElements[0])
|
||||
) {
|
||||
exportingFrame = exportedElements[0];
|
||||
exportedElements = elementsOverlappingBBox({
|
||||
elements,
|
||||
bounds: exportingFrame,
|
||||
type: "overlap",
|
||||
});
|
||||
exportedElements = getElementsOverlappingFrame(elements, exportingFrame);
|
||||
} else if (exportedElements.length > 1) {
|
||||
exportedElements = getSelectedElements(
|
||||
elements,
|
||||
|
|
|
@ -21,7 +21,10 @@ import { getElementsWithinSelection, getSelectedElements } from "./scene";
|
|||
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
||||
import Scene, { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
||||
import { getElementLineSegments } from "./element/bounds";
|
||||
import { doLineSegmentsIntersect } from "../utils/export";
|
||||
import {
|
||||
doLineSegmentsIntersect,
|
||||
elementsOverlappingBBox,
|
||||
} from "../utils/export";
|
||||
import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
|
||||
|
||||
// --------------------------- Frame State ------------------------------------
|
||||
|
@ -664,3 +667,19 @@ export const getFrameLikeTitle = (
|
|||
// TODO name frames AI only is specific to AI frames
|
||||
return isFrameElement(element) ? `Frame ${frameIdx}` : `AI Frame ${frameIdx}`;
|
||||
};
|
||||
|
||||
export const getElementsOverlappingFrame = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
frame: ExcalidrawFrameLikeElement,
|
||||
) => {
|
||||
return (
|
||||
elementsOverlappingBBox({
|
||||
elements,
|
||||
bounds: frame,
|
||||
type: "overlap",
|
||||
})
|
||||
// removes elements who are overlapping, but are in a different frame,
|
||||
// and thus invisible in target frame
|
||||
.filter((el) => !el.frameId || el.frameId === frame.id)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,8 +26,8 @@ import {
|
|||
getInitializedImageElements,
|
||||
updateImageCache,
|
||||
} from "../element/image";
|
||||
import { elementsOverlappingBBox } from "../../utils/export";
|
||||
import {
|
||||
getElementsOverlappingFrame,
|
||||
getFrameLikeElements,
|
||||
getFrameLikeTitle,
|
||||
getRootElements,
|
||||
|
@ -168,11 +168,7 @@ const prepareElementsForRender = ({
|
|||
let nextElements: readonly ExcalidrawElement[];
|
||||
|
||||
if (exportingFrame) {
|
||||
nextElements = elementsOverlappingBBox({
|
||||
elements,
|
||||
bounds: exportingFrame,
|
||||
type: "overlap",
|
||||
});
|
||||
nextElements = getElementsOverlappingFrame(elements, exportingFrame);
|
||||
} else if (frameRendering.enabled && frameRendering.name) {
|
||||
nextElements = addFrameLabelsAsTextElements(elements, {
|
||||
exportWithDarkMode,
|
||||
|
|
|
@ -406,5 +406,67 @@ describe("exporting frames", () => {
|
|||
(frame.height + getFrameNameHeight("svg")).toString(),
|
||||
);
|
||||
});
|
||||
|
||||
it("should not export frame-overlapping elements belonging to different frame", async () => {
|
||||
const frame1 = API.createElement({
|
||||
type: "frame",
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
const frame2 = API.createElement({
|
||||
type: "frame",
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 200,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const frame1Child = API.createElement({
|
||||
type: "rectangle",
|
||||
width: 150,
|
||||
height: 100,
|
||||
x: 0,
|
||||
y: 50,
|
||||
frameId: frame1.id,
|
||||
});
|
||||
const frame2Child = API.createElement({
|
||||
type: "rectangle",
|
||||
width: 150,
|
||||
height: 100,
|
||||
x: 50,
|
||||
y: 0,
|
||||
frameId: frame2.id,
|
||||
});
|
||||
|
||||
// low-level exportToSvg api expects elements to be pre-filtered, so let's
|
||||
// use the filter we use in the editor
|
||||
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
||||
[frame1Child, frame1, frame2Child, frame2],
|
||||
{
|
||||
selectedElementIds: { [frame1.id]: true },
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const svg = await exportToSvg({
|
||||
elements: exportedElements,
|
||||
files: null,
|
||||
exportPadding: 0,
|
||||
exportingFrame,
|
||||
});
|
||||
|
||||
// frame shouldn't be exported
|
||||
expect(svg.querySelector(`[data-id="${frame1.id}"]`)).toBeNull();
|
||||
// frame1 child should be epxorted
|
||||
expect(svg.querySelector(`[data-id="${frame1Child.id}"]`)).not.toBeNull();
|
||||
// frame2 child should not be exported even if it physically overlaps with
|
||||
// frame1
|
||||
expect(svg.querySelector(`[data-id="${frame2Child.id}"]`)).toBeNull();
|
||||
|
||||
expect(svg.getAttribute("width")).toBe(frame1.width.toString());
|
||||
expect(svg.getAttribute("height")).toBe(frame1.height.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue