[feat] serialize export options when embedding scene in an image

This commit is contained in:
pomdtr 2022-06-27 20:31:05 +00:00
parent 120c8f373c
commit d7f0d4ee21
No known key found for this signature in database
GPG Key ID: 125AC29703080DF9
7 changed files with 248 additions and 95 deletions

View File

@ -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>) => {

View File

@ -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 || [],

View File

@ -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"),
}); });
} }

View File

@ -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,
}); });

View File

@ -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;
} }

View File

@ -131,7 +131,7 @@ export const exportToBlob = async (
opts.elements, opts.elements,
opts.appState, opts.appState,
opts.files || {}, opts.files || {},
"local", "image",
), ),
}); });
} }

View File

@ -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);