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:
David Laban 2021-07-15 09:54:26 -04:00 committed by GitHub
parent 9581c45522
commit 685abac81a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 15 deletions

View File

@ -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: {

View File

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

View File

@ -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 (

View File

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

View File

@ -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" }), {
return await fileSave(
new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }),
{
fileName: `${name}.svg`,
extensions: [".svg"],
});
return;
},
fileHandle,
);
} else if (type === "clipboard-svg") {
await copyTextToSystemClipboard(tempSvg.outerHTML);
return;
@ -76,10 +81,14 @@ export const exportCanvas = async (
});
}
await fileSave(blob, {
return await fileSave(
blob,
{
fileName,
extensions: [".png"],
});
},
fileHandle,
);
} else if (type === "clipboard") {
try {
await copyBlobToClipboardAsPng(blob);

View File

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