From bfc2cec7d43eb8eaa46dd3f91084932381257059 Mon Sep 17 00:00:00 2001 From: kj_sh604 Date: Sun, 15 Mar 2026 16:19:35 -0400 Subject: refactor: excalidraw-app/ --- .../components/ExportToExcalidrawPlus.tsx | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 excalidraw-app/components/ExportToExcalidrawPlus.tsx (limited to 'excalidraw-app/components/ExportToExcalidrawPlus.tsx') diff --git a/excalidraw-app/components/ExportToExcalidrawPlus.tsx b/excalidraw-app/components/ExportToExcalidrawPlus.tsx new file mode 100644 index 0000000..782ecd9 --- /dev/null +++ b/excalidraw-app/components/ExportToExcalidrawPlus.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import { Card } from "@excalidraw/excalidraw/components/Card"; +import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton"; +import { serializeAsJSON } from "@excalidraw/excalidraw/data/json"; +import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase"; +import type { + FileId, + NonDeletedExcalidrawElement, +} from "@excalidraw/excalidraw/element/types"; +import type { + AppState, + BinaryFileData, + BinaryFiles, +} from "@excalidraw/excalidraw/types"; +import { nanoid } from "nanoid"; +import { useI18n } from "@excalidraw/excalidraw/i18n"; +import { + encryptData, + generateEncryptionKey, +} from "@excalidraw/excalidraw/data/encryption"; +import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks"; +import { FILE_UPLOAD_MAX_BYTES } from "../app_constants"; +import { encodeFilesForUpload } from "../data/FileManager"; +import { uploadBytes, ref } from "firebase/storage"; +import { MIME_TYPES } from "@excalidraw/excalidraw/constants"; +import { trackEvent } from "@excalidraw/excalidraw/analytics"; +import { getFrame } from "@excalidraw/excalidraw/utils"; +import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo"; + +export const exportToExcalidrawPlus = async ( + elements: readonly NonDeletedExcalidrawElement[], + appState: Partial, + files: BinaryFiles, + name: string, +) => { + const storage = await loadFirebaseStorage(); + + const id = `${nanoid(12)}`; + + const encryptionKey = (await generateEncryptionKey())!; + const encryptedData = await encryptData( + encryptionKey, + serializeAsJSON(elements, appState, files, "database"), + ); + + const blob = new Blob( + [encryptedData.iv, new Uint8Array(encryptedData.encryptedBuffer)], + { + type: MIME_TYPES.binary, + }, + ); + + const storageRef = ref(storage, `/migrations/scenes/${id}`); + await uploadBytes(storageRef, blob, { + customMetadata: { + data: JSON.stringify({ version: 2, name }), + created: Date.now().toString(), + }, + }); + + const filesMap = new Map(); + for (const element of elements) { + if (isInitializedImageElement(element) && files[element.fileId]) { + filesMap.set(element.fileId, files[element.fileId]); + } + } + + if (filesMap.size) { + const filesToUpload = await encodeFilesForUpload({ + files: filesMap, + encryptionKey, + maxBytes: FILE_UPLOAD_MAX_BYTES, + }); + + await saveFilesToFirebase({ + prefix: `/migrations/files/scenes/${id}`, + files: filesToUpload, + }); + } + + window.open( + `${ + import.meta.env.VITE_APP_PLUS_APP + }/import?excalidraw=${id},${encryptionKey}`, + ); +}; + +export const ExportToExcalidrawPlus: React.FC<{ + elements: readonly NonDeletedExcalidrawElement[]; + appState: Partial; + files: BinaryFiles; + name: string; + onError: (error: Error) => void; + onSuccess: () => void; +}> = ({ elements, appState, files, name, onError, onSuccess }) => { + const { t } = useI18n(); + return ( + +
+ +
+

Excalidraw+

+
+ {t("exportDialog.excalidrawplus_description")} +
+ { + try { + trackEvent("export", "eplus", `ui (${getFrame()})`); + await exportToExcalidrawPlus(elements, appState, files, name); + onSuccess(); + } catch (error: any) { + console.error(error); + if (error.name !== "AbortError") { + onError(new Error(t("exportDialog.excalidrawplus_exportError"))); + } + } + }} + /> +
+ ); +}; -- cgit v1.2.3