feat: resave to png/svg with metadata if you loaded your scene from a png/svg file (#3645)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
9581c45522
commit
685abac81a
@ -7,6 +7,7 @@ import "../components/ToolIcon.scss";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
|
||||
import { loadFromJSON, saveAsJSON } from "../data";
|
||||
import { resaveAsImageWithScene } from "../data/resave";
|
||||
import { t } from "../i18n";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { KEYS } from "../keys";
|
||||
@ -18,6 +19,7 @@ import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES } from "../constants";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { ActiveFile } from "../components/ActiveFile";
|
||||
import { isImageFileHandle } from "../data/blob";
|
||||
|
||||
export const actionChangeProjectName = register({
|
||||
name: "changeProjectName",
|
||||
@ -128,8 +130,12 @@ export const actionSaveToActiveFile = register({
|
||||
name: "saveToActiveFile",
|
||||
perform: async (elements, appState, value) => {
|
||||
const fileHandleExists = !!appState.fileHandle;
|
||||
|
||||
try {
|
||||
const { fileHandle } = await saveAsJSON(elements, appState);
|
||||
const { fileHandle } = isImageFileHandle(appState.fileHandle)
|
||||
? await resaveAsImageWithScene(elements, appState)
|
||||
: await saveAsJSON(elements, appState);
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
appState: {
|
||||
|
@ -3827,6 +3827,17 @@ class App extends React.Component<AppProps, AppState> {
|
||||
try {
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (file?.type === "image/png" || file?.type === "image/svg+xml") {
|
||||
if (fsSupported) {
|
||||
try {
|
||||
// This will only work as of Chrome 86,
|
||||
// but can be safely ignored on older releases.
|
||||
const item = event.dataTransfer.items[0];
|
||||
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
||||
} catch (error) {
|
||||
console.warn(error.name, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const { elements, appState } = await loadFromBlob(
|
||||
file,
|
||||
this.state,
|
||||
|
@ -48,6 +48,7 @@ import { UserList } from "./UserList";
|
||||
import Library from "../data/library";
|
||||
import { JSONExportDialog } from "./JSONExportDialog";
|
||||
import { LibraryButton } from "./LibraryButton";
|
||||
import { isImageFileHandle } from "../data/blob";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@ -407,7 +408,7 @@ const LayerUI = ({
|
||||
const createExporter = (type: ExportType): ExportCB => async (
|
||||
exportedElements,
|
||||
) => {
|
||||
await exportCanvas(type, exportedElements, appState, {
|
||||
const fileHandle = await exportCanvas(type, exportedElements, appState, {
|
||||
exportBackground: appState.exportBackground,
|
||||
name: appState.name,
|
||||
viewBackgroundColor: appState.viewBackgroundColor,
|
||||
@ -417,6 +418,14 @@ const LayerUI = ({
|
||||
console.error(error);
|
||||
setAppState({ errorMessage: error.message });
|
||||
});
|
||||
|
||||
if (
|
||||
appState.exportEmbedScene &&
|
||||
fileHandle &&
|
||||
isImageFileHandle(fileHandle)
|
||||
) {
|
||||
setAppState({ fileHandle });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FileSystemHandle } from "browser-fs-access";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { EXPORT_DATA_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
@ -80,6 +81,25 @@ export const getMimeType = (blob: Blob | string): string => {
|
||||
return "";
|
||||
};
|
||||
|
||||
export const getFileHandleType = (handle: FileSystemHandle | null) => {
|
||||
if (!handle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return handle.name.match(/\.(json|excalidraw|png|svg)$/)?.[1] || null;
|
||||
};
|
||||
|
||||
export const isImageFileHandleType = (
|
||||
type: string | null,
|
||||
): type is "png" | "svg" => {
|
||||
return type === "png" || type === "svg";
|
||||
};
|
||||
|
||||
export const isImageFileHandle = (handle: FileSystemHandle | null) => {
|
||||
const type = getFileHandleType(handle);
|
||||
return type === "png" || type === "svg";
|
||||
};
|
||||
|
||||
export const loadFromBlob = async (
|
||||
blob: Blob,
|
||||
/** @see restore.localAppState */
|
||||
@ -97,7 +117,7 @@ export const loadFromBlob = async (
|
||||
elements: clearElementsForExport(data.elements || []),
|
||||
appState: {
|
||||
theme: localAppState?.theme,
|
||||
fileHandle: (!blob.type.startsWith("image/") && blob.handle) || null,
|
||||
fileHandle: blob.handle || null,
|
||||
...cleanAppStateForExport(data.appState || {}),
|
||||
...(localAppState
|
||||
? calculateScrollCenter(data.elements || [], localAppState, null)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fileSave } from "browser-fs-access";
|
||||
import { fileSave, FileSystemHandle } from "browser-fs-access";
|
||||
import {
|
||||
copyBlobToClipboardAsPng,
|
||||
copyTextToSystemClipboard,
|
||||
@ -24,11 +24,13 @@ export const exportCanvas = async (
|
||||
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||
viewBackgroundColor,
|
||||
name,
|
||||
fileHandle = null,
|
||||
}: {
|
||||
exportBackground: boolean;
|
||||
exportPadding?: number;
|
||||
viewBackgroundColor: string;
|
||||
name: string;
|
||||
fileHandle?: FileSystemHandle | null;
|
||||
},
|
||||
) => {
|
||||
if (elements.length === 0) {
|
||||
@ -44,11 +46,14 @@ export const exportCanvas = async (
|
||||
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
||||
});
|
||||
if (type === "svg") {
|
||||
await fileSave(new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }), {
|
||||
fileName: `${name}.svg`,
|
||||
extensions: [".svg"],
|
||||
});
|
||||
return;
|
||||
return await fileSave(
|
||||
new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }),
|
||||
{
|
||||
fileName: `${name}.svg`,
|
||||
extensions: [".svg"],
|
||||
},
|
||||
fileHandle,
|
||||
);
|
||||
} else if (type === "clipboard-svg") {
|
||||
await copyTextToSystemClipboard(tempSvg.outerHTML);
|
||||
return;
|
||||
@ -76,10 +81,14 @@ export const exportCanvas = async (
|
||||
});
|
||||
}
|
||||
|
||||
await fileSave(blob, {
|
||||
fileName,
|
||||
extensions: [".png"],
|
||||
});
|
||||
return await fileSave(
|
||||
blob,
|
||||
{
|
||||
fileName,
|
||||
extensions: [".png"],
|
||||
},
|
||||
fileHandle,
|
||||
);
|
||||
} else if (type === "clipboard") {
|
||||
try {
|
||||
await copyBlobToClipboardAsPng(blob);
|
||||
|
@ -4,7 +4,7 @@ import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { loadFromBlob } from "./blob";
|
||||
import { isImageFileHandle, loadFromBlob } from "./blob";
|
||||
|
||||
import {
|
||||
ExportedDataState,
|
||||
@ -44,7 +44,7 @@ export const saveAsJSON = async (
|
||||
description: "Excalidraw file",
|
||||
extensions: [".excalidraw"],
|
||||
},
|
||||
appState.fileHandle,
|
||||
isImageFileHandle(appState.fileHandle) ? null : appState.fileHandle,
|
||||
);
|
||||
return { fileHandle };
|
||||
};
|
||||
|
38
src/data/resave.ts
Normal file
38
src/data/resave.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { exportCanvas } from ".";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { getFileHandleType, isImageFileHandleType } from "./blob";
|
||||
|
||||
export const resaveAsImageWithScene = async (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
const { exportBackground, viewBackgroundColor, name, fileHandle } = appState;
|
||||
|
||||
const fileHandleType = getFileHandleType(fileHandle);
|
||||
|
||||
if (!fileHandle || !isImageFileHandleType(fileHandleType)) {
|
||||
throw new Error(
|
||||
"fileHandle should exist and should be of type svg or png when resaving",
|
||||
);
|
||||
}
|
||||
appState = {
|
||||
...appState,
|
||||
exportEmbedScene: true,
|
||||
};
|
||||
|
||||
await exportCanvas(
|
||||
fileHandleType,
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
{
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
name,
|
||||
fileHandle,
|
||||
},
|
||||
);
|
||||
|
||||
return { fileHandle };
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user