[feat] serialize export options when embedding scene in an image
This commit is contained in:
parent
120c8f373c
commit
d7f0d4ee21
290
src/appState.ts
290
src/appState.ts
@ -101,90 +101,228 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
Values extends {
|
Values extends {
|
||||||
/** whether to keep when storing to browser storage (localStorage/IDB) */
|
/** whether to keep when storing to browser storage (localStorage/IDB) */
|
||||||
browser: boolean;
|
browser: boolean;
|
||||||
/** whether to keep when exporting to file/database */
|
/** whether to keep when exporting to text file */
|
||||||
export: boolean;
|
text: boolean;
|
||||||
|
/** whether to keep when exporting to an image file */
|
||||||
|
image: boolean;
|
||||||
/** server (shareLink/collab/...) */
|
/** server (shareLink/collab/...) */
|
||||||
server: boolean;
|
server: boolean;
|
||||||
},
|
},
|
||||||
T extends Record<keyof AppState, Values>,
|
T extends Record<keyof AppState, Values>,
|
||||||
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
|
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
|
||||||
config)({
|
config)({
|
||||||
theme: { browser: true, export: false, server: false },
|
theme: { browser: true, text: false, image: false, server: false },
|
||||||
collaborators: { browser: false, export: false, server: false },
|
collaborators: { browser: false, text: false, image: false, server: false },
|
||||||
currentChartType: { browser: true, export: false, server: false },
|
currentChartType: { browser: true, text: false, image: false, server: false },
|
||||||
currentItemBackgroundColor: { browser: true, export: false, server: false },
|
currentItemBackgroundColor: {
|
||||||
currentItemEndArrowhead: { browser: true, export: false, server: false },
|
|
||||||
currentItemFillStyle: { browser: true, export: false, server: false },
|
|
||||||
currentItemFontFamily: { browser: true, export: false, server: false },
|
|
||||||
currentItemFontSize: { browser: true, export: false, server: false },
|
|
||||||
currentItemLinearStrokeSharpness: {
|
|
||||||
browser: true,
|
browser: true,
|
||||||
export: false,
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemEndArrowhead: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemFillStyle: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemFontFamily: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemFontSize: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemLinearStrokeSharpness: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemOpacity: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemRoughness: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemStartArrowhead: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemStrokeColor: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemStrokeSharpness: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemStrokeStyle: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemStrokeWidth: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
currentItemTextAlign: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
cursorButton: { browser: true, text: false, image: false, server: false },
|
||||||
|
draggingElement: { browser: false, text: false, image: false, server: false },
|
||||||
|
editingElement: { browser: false, text: false, image: false, server: false },
|
||||||
|
editingGroupId: { browser: true, text: false, image: false, server: false },
|
||||||
|
editingLinearElement: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
activeTool: { browser: true, text: false, image: false, server: false },
|
||||||
|
penMode: { browser: true, text: false, image: false, server: false },
|
||||||
|
penDetected: { browser: true, text: false, image: false, server: false },
|
||||||
|
errorMessage: { browser: false, text: false, image: false, server: false },
|
||||||
|
exportBackground: { browser: true, text: false, image: true, server: false },
|
||||||
|
exportEmbedScene: { browser: true, text: false, image: true, server: false },
|
||||||
|
exportScale: { browser: true, text: false, image: true, server: false },
|
||||||
|
exportWithDarkMode: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: true,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
fileHandle: { browser: false, text: false, image: false, server: false },
|
||||||
|
gridSize: { browser: true, text: true, image: true, server: true },
|
||||||
|
height: { browser: false, text: false, image: false, server: false },
|
||||||
|
isBindingEnabled: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
isLibraryOpen: { browser: true, text: false, image: false, server: false },
|
||||||
|
isLibraryMenuDocked: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
isLoading: { browser: false, text: false, image: false, server: false },
|
||||||
|
isResizing: { browser: false, text: false, image: false, server: false },
|
||||||
|
isRotating: { browser: false, text: false, image: false, server: false },
|
||||||
|
lastPointerDownWith: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
multiElement: { browser: false, text: false, image: false, server: false },
|
||||||
|
name: { browser: true, text: false, image: false, server: false },
|
||||||
|
offsetLeft: { browser: false, text: false, image: false, server: false },
|
||||||
|
offsetTop: { browser: false, text: false, image: false, server: false },
|
||||||
|
openMenu: { browser: true, text: false, image: false, server: false },
|
||||||
|
openPopup: { browser: false, text: false, image: false, server: false },
|
||||||
|
pasteDialog: { browser: false, text: false, image: false, server: false },
|
||||||
|
previousSelectedElementIds: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
resizingElement: { browser: false, text: false, image: false, server: false },
|
||||||
|
scrolledOutside: { browser: true, text: false, image: false, server: false },
|
||||||
|
scrollX: { browser: true, text: false, image: false, server: false },
|
||||||
|
scrollY: { browser: true, text: false, image: false, server: false },
|
||||||
|
selectedElementIds: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
selectedGroupIds: { browser: true, text: false, image: false, server: false },
|
||||||
|
selectionElement: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
shouldCacheIgnoreZoom: {
|
||||||
|
browser: true,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
showHelpDialog: { browser: false, text: false, image: false, server: false },
|
||||||
|
showStats: { browser: true, text: false, image: false, server: false },
|
||||||
|
startBoundElement: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
suggestedBindings: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
toastMessage: { browser: false, text: false, image: false, server: false },
|
||||||
|
viewBackgroundColor: {
|
||||||
|
browser: true,
|
||||||
|
text: true,
|
||||||
|
image: true,
|
||||||
|
server: true,
|
||||||
|
},
|
||||||
|
width: { browser: false, text: false, image: false, server: false },
|
||||||
|
zenModeEnabled: { browser: true, text: false, image: false, server: false },
|
||||||
|
zoom: { browser: true, text: false, image: false, server: false },
|
||||||
|
viewModeEnabled: { browser: false, text: false, image: false, server: false },
|
||||||
|
pendingImageElementId: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
showHyperlinkPopup: {
|
||||||
|
browser: false,
|
||||||
|
text: false,
|
||||||
|
image: false,
|
||||||
server: false,
|
server: false,
|
||||||
},
|
},
|
||||||
currentItemOpacity: { browser: true, export: false, server: false },
|
|
||||||
currentItemRoughness: { browser: true, export: false, server: false },
|
|
||||||
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
|
||||||
currentItemStrokeColor: { browser: true, export: false, server: false },
|
|
||||||
currentItemStrokeSharpness: { browser: true, export: false, server: false },
|
|
||||||
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
|
||||||
currentItemStrokeWidth: { browser: true, export: false, server: false },
|
|
||||||
currentItemTextAlign: { browser: true, export: false, server: false },
|
|
||||||
cursorButton: { browser: true, export: false, server: false },
|
|
||||||
draggingElement: { browser: false, export: false, server: false },
|
|
||||||
editingElement: { browser: false, export: false, server: false },
|
|
||||||
editingGroupId: { browser: true, export: false, server: false },
|
|
||||||
editingLinearElement: { browser: false, export: false, server: false },
|
|
||||||
activeTool: { browser: true, export: false, server: false },
|
|
||||||
penMode: { browser: true, export: false, server: false },
|
|
||||||
penDetected: { browser: true, export: false, server: false },
|
|
||||||
errorMessage: { browser: false, export: false, server: false },
|
|
||||||
exportBackground: { browser: true, export: false, server: false },
|
|
||||||
exportEmbedScene: { browser: true, export: false, server: false },
|
|
||||||
exportScale: { browser: true, export: false, server: false },
|
|
||||||
exportWithDarkMode: { browser: true, export: false, server: false },
|
|
||||||
fileHandle: { browser: false, export: false, server: false },
|
|
||||||
gridSize: { browser: true, export: true, server: true },
|
|
||||||
height: { browser: false, export: false, server: false },
|
|
||||||
isBindingEnabled: { browser: false, export: false, server: false },
|
|
||||||
isLibraryOpen: { browser: true, export: false, server: false },
|
|
||||||
isLibraryMenuDocked: { browser: true, export: false, server: false },
|
|
||||||
isLoading: { browser: false, export: false, server: false },
|
|
||||||
isResizing: { browser: false, export: false, server: false },
|
|
||||||
isRotating: { browser: false, export: false, server: false },
|
|
||||||
lastPointerDownWith: { browser: true, export: false, server: false },
|
|
||||||
multiElement: { browser: false, export: false, server: false },
|
|
||||||
name: { browser: true, export: false, server: false },
|
|
||||||
offsetLeft: { browser: false, export: false, server: false },
|
|
||||||
offsetTop: { browser: false, export: false, server: false },
|
|
||||||
openMenu: { browser: true, export: false, server: false },
|
|
||||||
openPopup: { browser: false, export: false, server: false },
|
|
||||||
pasteDialog: { browser: false, export: false, server: false },
|
|
||||||
previousSelectedElementIds: { browser: true, export: false, server: false },
|
|
||||||
resizingElement: { browser: false, export: false, server: false },
|
|
||||||
scrolledOutside: { browser: true, export: false, server: false },
|
|
||||||
scrollX: { browser: true, export: false, server: false },
|
|
||||||
scrollY: { browser: true, export: false, server: false },
|
|
||||||
selectedElementIds: { browser: true, export: false, server: false },
|
|
||||||
selectedGroupIds: { browser: true, export: false, server: false },
|
|
||||||
selectionElement: { browser: false, export: false, server: false },
|
|
||||||
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
|
|
||||||
showHelpDialog: { browser: false, export: false, server: false },
|
|
||||||
showStats: { browser: true, export: false, server: false },
|
|
||||||
startBoundElement: { browser: false, export: false, server: false },
|
|
||||||
suggestedBindings: { browser: false, export: false, server: false },
|
|
||||||
toastMessage: { browser: false, export: false, server: false },
|
|
||||||
viewBackgroundColor: { browser: true, export: true, server: true },
|
|
||||||
width: { browser: false, export: false, server: false },
|
|
||||||
zenModeEnabled: { browser: true, export: false, server: false },
|
|
||||||
zoom: { browser: true, export: false, server: false },
|
|
||||||
viewModeEnabled: { browser: false, export: false, server: false },
|
|
||||||
pendingImageElementId: { browser: false, export: false, server: false },
|
|
||||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
ExportType extends "export" | "browser" | "server",
|
ExportType extends "image" | "text" | "browser" | "server",
|
||||||
>(
|
>(
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
exportType: ExportType,
|
exportType: ExportType,
|
||||||
@ -211,8 +349,12 @@ export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
|
|||||||
return _clearAppStateForStorage(appState, "browser");
|
return _clearAppStateForStorage(appState, "browser");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanAppStateForExport = (appState: Partial<AppState>) => {
|
export const cleanAppStateForTextExport = (appState: Partial<AppState>) => {
|
||||||
return _clearAppStateForStorage(appState, "export");
|
return _clearAppStateForStorage(appState, "text");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cleanAppStateForImageExport = (appState: Partial<AppState>) => {
|
||||||
|
return _clearAppStateForStorage(appState, "image");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
|
export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForTextExport } from "../appState";
|
||||||
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { ExcalidrawElement, FileId } from "../element/types";
|
import { ExcalidrawElement, FileId } from "../element/types";
|
||||||
@ -143,7 +143,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
appState: {
|
appState: {
|
||||||
theme: localAppState?.theme,
|
theme: localAppState?.theme,
|
||||||
fileHandle: fileHandle || blob.handle || null,
|
fileHandle: fileHandle || blob.handle || null,
|
||||||
...cleanAppStateForExport(data.appState || {}),
|
...cleanAppStateForTextExport(data.appState || {}),
|
||||||
...(localAppState
|
...(localAppState
|
||||||
? calculateScrollCenter(
|
? calculateScrollCenter(
|
||||||
data.elements || [],
|
data.elements || [],
|
||||||
|
@ -82,7 +82,7 @@ export const exportCanvas = async (
|
|||||||
await import(/* webpackChunkName: "image" */ "./image")
|
await import(/* webpackChunkName: "image" */ "./image")
|
||||||
).encodePngMetadata({
|
).encodePngMetadata({
|
||||||
blob,
|
blob,
|
||||||
metadata: serializeAsJSON(elements, appState, files, "local"),
|
metadata: serializeAsJSON(elements, appState, files, "image"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { fileOpen, fileSave } from "./filesystem";
|
import { fileOpen, fileSave } from "./filesystem";
|
||||||
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
import {
|
||||||
|
cleanAppStateForImageExport,
|
||||||
|
cleanAppStateForTextExport,
|
||||||
|
clearAppStateForDatabase,
|
||||||
|
} from "../appState";
|
||||||
import {
|
import {
|
||||||
EXPORT_DATA_TYPES,
|
EXPORT_DATA_TYPES,
|
||||||
EXPORT_SOURCE,
|
EXPORT_SOURCE,
|
||||||
@ -43,25 +47,32 @@ export const serializeAsJSON = (
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
type: "local" | "database",
|
destination: "text" | "image" | "database",
|
||||||
): string => {
|
): string => {
|
||||||
|
const cleanAppState = () => {
|
||||||
|
switch (destination) {
|
||||||
|
case "database":
|
||||||
|
return clearAppStateForDatabase(appState);
|
||||||
|
case "text":
|
||||||
|
return cleanAppStateForTextExport(appState);
|
||||||
|
case "image":
|
||||||
|
return cleanAppStateForImageExport(appState);
|
||||||
|
}
|
||||||
|
};
|
||||||
const data: ExportedDataState = {
|
const data: ExportedDataState = {
|
||||||
type: EXPORT_DATA_TYPES.excalidraw,
|
type: EXPORT_DATA_TYPES.excalidraw,
|
||||||
version: VERSIONS.excalidraw,
|
version: VERSIONS.excalidraw,
|
||||||
source: EXPORT_SOURCE,
|
source: EXPORT_SOURCE,
|
||||||
elements:
|
elements:
|
||||||
type === "local"
|
destination === "database"
|
||||||
? clearElementsForExport(elements)
|
? clearElementsForDatabase(elements)
|
||||||
: clearElementsForDatabase(elements),
|
: clearElementsForExport(elements),
|
||||||
appState:
|
appState: cleanAppState(),
|
||||||
type === "local"
|
|
||||||
? cleanAppStateForExport(appState)
|
|
||||||
: clearAppStateForDatabase(appState),
|
|
||||||
files:
|
files:
|
||||||
type === "local"
|
destination === "database"
|
||||||
? filterOutDeletedFiles(elements, files)
|
? // will be stripped from JSON
|
||||||
: // will be stripped from JSON
|
undefined
|
||||||
undefined,
|
: filterOutDeletedFiles(elements, files),
|
||||||
};
|
};
|
||||||
|
|
||||||
return JSON.stringify(data, null, 2);
|
return JSON.stringify(data, null, 2);
|
||||||
@ -72,7 +83,7 @@ export const saveAsJSON = async (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const serialized = serializeAsJSON(elements, appState, files, "local");
|
const serialized = serializeAsJSON(elements, appState, files, "text");
|
||||||
const blob = new Blob([serialized], {
|
const blob = new Blob([serialized], {
|
||||||
type: MIME_TYPES.excalidraw,
|
type: MIME_TYPES.excalidraw,
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
LibraryItems,
|
LibraryItems,
|
||||||
LibraryItems_anyVersion,
|
LibraryItems_anyVersion,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import type { cleanAppStateForExport } from "../appState";
|
import type { cleanAppStateForTextExport } from "../appState";
|
||||||
import { VERSIONS } from "../constants";
|
import { VERSIONS } from "../constants";
|
||||||
|
|
||||||
export interface ExportedDataState {
|
export interface ExportedDataState {
|
||||||
@ -13,7 +13,7 @@ export interface ExportedDataState {
|
|||||||
version: number;
|
version: number;
|
||||||
source: string;
|
source: string;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
appState: ReturnType<typeof cleanAppStateForExport>;
|
appState: ReturnType<typeof cleanAppStateForTextExport>;
|
||||||
files: BinaryFiles | undefined;
|
files: BinaryFiles | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ export const exportToBlob = async (
|
|||||||
opts.elements,
|
opts.elements,
|
||||||
opts.appState,
|
opts.appState,
|
||||||
opts.files || {},
|
opts.files || {},
|
||||||
"local",
|
"image",
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ export const exportToSvg = async (
|
|||||||
metadata = await (
|
metadata = await (
|
||||||
await import(/* webpackChunkName: "image" */ "../../src/data/image")
|
await import(/* webpackChunkName: "image" */ "../../src/data/image")
|
||||||
).encodeSvgMetadata({
|
).encodeSvgMetadata({
|
||||||
text: serializeAsJSON(elements, appState, files || {}, "local"),
|
text: serializeAsJSON(elements, appState, files || {}, "image"),
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user