mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: render frames on export (#7210)
This commit is contained in:
parent
a9a6f8eafb
commit
864c0b3ea8
30 changed files with 989 additions and 332 deletions
|
@ -3,11 +3,19 @@ import {
|
|||
copyTextToSystemClipboard,
|
||||
} from "../clipboard";
|
||||
import { DEFAULT_EXPORT_PADDING, isFirefox, MIME_TYPES } from "../constants";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { getNonDeletedElements, isFrameElement } from "../element";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawFrameElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { elementsOverlappingBBox } from "../packages/withinBounds";
|
||||
import { isSomeElementSelected, getSelectedElements } from "../scene";
|
||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||
import { ExportType } from "../scene/types";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { cloneJSON } from "../utils";
|
||||
import { canvasToBlob } from "./blob";
|
||||
import { fileSave, FileSystemHandle } from "./filesystem";
|
||||
import { serializeAsJSON } from "./json";
|
||||
|
@ -15,9 +23,61 @@ import { serializeAsJSON } from "./json";
|
|||
export { loadFromBlob } from "./blob";
|
||||
export { loadFromJSON, saveAsJSON } from "./json";
|
||||
|
||||
export type ExportedElements = readonly NonDeletedExcalidrawElement[] & {
|
||||
_brand: "exportedElements";
|
||||
};
|
||||
|
||||
export const prepareElementsForExport = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
{ selectedElementIds }: Pick<AppState, "selectedElementIds">,
|
||||
exportSelectionOnly: boolean,
|
||||
) => {
|
||||
elements = getNonDeletedElements(elements);
|
||||
|
||||
const isExportingSelection =
|
||||
exportSelectionOnly &&
|
||||
isSomeElementSelected(elements, { selectedElementIds });
|
||||
|
||||
let exportingFrame: ExcalidrawFrameElement | null = null;
|
||||
let exportedElements = isExportingSelection
|
||||
? getSelectedElements(
|
||||
elements,
|
||||
{ selectedElementIds },
|
||||
{
|
||||
includeBoundTextElement: true,
|
||||
},
|
||||
)
|
||||
: elements;
|
||||
|
||||
if (isExportingSelection) {
|
||||
if (exportedElements.length === 1 && isFrameElement(exportedElements[0])) {
|
||||
exportingFrame = exportedElements[0];
|
||||
exportedElements = elementsOverlappingBBox({
|
||||
elements,
|
||||
bounds: exportingFrame,
|
||||
type: "overlap",
|
||||
});
|
||||
} else if (exportedElements.length > 1) {
|
||||
exportedElements = getSelectedElements(
|
||||
elements,
|
||||
{ selectedElementIds },
|
||||
{
|
||||
includeBoundTextElement: true,
|
||||
includeElementsInFrames: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exportingFrame,
|
||||
exportedElements: cloneJSON(exportedElements) as ExportedElements,
|
||||
};
|
||||
};
|
||||
|
||||
export const exportCanvas = async (
|
||||
type: Omit<ExportType, "backend">,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elements: ExportedElements,
|
||||
appState: AppState,
|
||||
files: BinaryFiles,
|
||||
{
|
||||
|
@ -26,12 +86,14 @@ export const exportCanvas = async (
|
|||
viewBackgroundColor,
|
||||
name,
|
||||
fileHandle = null,
|
||||
exportingFrame = null,
|
||||
}: {
|
||||
exportBackground: boolean;
|
||||
exportPadding?: number;
|
||||
viewBackgroundColor: string;
|
||||
name: string;
|
||||
fileHandle?: FileSystemHandle | null;
|
||||
exportingFrame: ExcalidrawFrameElement | null;
|
||||
},
|
||||
) => {
|
||||
if (elements.length === 0) {
|
||||
|
@ -49,6 +111,7 @@ export const exportCanvas = async (
|
|||
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
||||
},
|
||||
files,
|
||||
{ exportingFrame },
|
||||
);
|
||||
if (type === "svg") {
|
||||
return await fileSave(
|
||||
|
@ -70,6 +133,7 @@ export const exportCanvas = async (
|
|||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
exportPadding,
|
||||
exportingFrame,
|
||||
});
|
||||
|
||||
if (type === "png") {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
LIBRARY_SIDEBAR_TAB,
|
||||
} from "../constants";
|
||||
import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
|
||||
import { cloneJSON } from "../utils";
|
||||
|
||||
export const libraryItemsAtom = atom<{
|
||||
status: "loading" | "loaded";
|
||||
|
@ -31,7 +32,7 @@ export const libraryItemsAtom = atom<{
|
|||
}>({ status: "loaded", isInitialized: true, libraryItems: [] });
|
||||
|
||||
const cloneLibraryItems = (libraryItems: LibraryItems): LibraryItems =>
|
||||
JSON.parse(JSON.stringify(libraryItems));
|
||||
cloneJSON(libraryItems);
|
||||
|
||||
/**
|
||||
* checks if library item does not exist already in current library
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { exportCanvas } from ".";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { exportCanvas, prepareElementsForExport } from ".";
|
||||
import { getFileHandleType, isImageFileHandleType } from "./blob";
|
||||
|
||||
export const resaveAsImageWithScene = async (
|
||||
|
@ -23,18 +22,19 @@ export const resaveAsImageWithScene = async (
|
|||
exportEmbedScene: true,
|
||||
};
|
||||
|
||||
await exportCanvas(
|
||||
fileHandleType,
|
||||
getNonDeletedElements(elements),
|
||||
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
||||
elements,
|
||||
appState,
|
||||
files,
|
||||
{
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
name,
|
||||
fileHandle,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
await exportCanvas(fileHandleType, exportedElements, appState, files, {
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
name,
|
||||
fileHandle,
|
||||
exportingFrame,
|
||||
});
|
||||
|
||||
return { fileHandle };
|
||||
};
|
||||
|
|
|
@ -40,7 +40,7 @@ import {
|
|||
VerticalAlign,
|
||||
} from "../element/types";
|
||||
import { MarkOptional } from "../utility-types";
|
||||
import { assertNever, getFontString } from "../utils";
|
||||
import { assertNever, cloneJSON, getFontString } from "../utils";
|
||||
import { getSizeFromPoints } from "../points";
|
||||
import { randomId } from "../random";
|
||||
|
||||
|
@ -368,7 +368,8 @@ const bindLinearElementToElement = (
|
|||
// Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.
|
||||
const endPointIndex = linearElement.points.length - 1;
|
||||
const delta = 0.5;
|
||||
const newPoints = JSON.parse(JSON.stringify(linearElement.points));
|
||||
|
||||
const newPoints = cloneJSON(linearElement.points) as [number, number][];
|
||||
// left to right so shift the arrow towards right
|
||||
if (
|
||||
linearElement.points[endPointIndex][0] >
|
||||
|
@ -439,9 +440,7 @@ export const convertToExcalidrawElements = (
|
|||
if (!elementsSkeleton) {
|
||||
return [];
|
||||
}
|
||||
const elements: ExcalidrawElementSkeleton[] = JSON.parse(
|
||||
JSON.stringify(elementsSkeleton),
|
||||
);
|
||||
const elements = cloneJSON(elementsSkeleton);
|
||||
const elementStore = new ElementStore();
|
||||
const elementsWithIds = new Map<string, ExcalidrawElementSkeleton>();
|
||||
const oldToNewElementIdMap = new Map<string, string>();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue