diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | bfc2cec7d43eb8eaa46dd3f91084932381257059 (patch) | |
| tree | 0857e3aac2cff922826d4871ff54536b26fad6fc /excalidraw-app/components/ExportToExcalidrawPlus.tsx | |
| parent | 225db4a7805befe009fe055fc2ef5daedd6c04f9 (diff) | |
refactor: excalidraw-app/
Diffstat (limited to 'excalidraw-app/components/ExportToExcalidrawPlus.tsx')
| -rw-r--r-- | excalidraw-app/components/ExportToExcalidrawPlus.tsx | 133 |
1 files changed, 133 insertions, 0 deletions
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<AppState>, + 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<FileId, BinaryFileData>(); + 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<AppState>; + files: BinaryFiles; + name: string; + onError: (error: Error) => void; + onSuccess: () => void; +}> = ({ elements, appState, files, name, onError, onSuccess }) => { + const { t } = useI18n(); + return ( + <Card color="primary"> + <div className="Card-icon"> + <ExcalidrawLogo + style={{ + [`--color-logo-icon` as any]: "#fff", + width: "2.8rem", + height: "2.8rem", + }} + /> + </div> + <h2>Excalidraw+</h2> + <div className="Card-details"> + {t("exportDialog.excalidrawplus_description")} + </div> + <ToolButton + className="Card-button" + type="button" + title={t("exportDialog.excalidrawplus_button")} + aria-label={t("exportDialog.excalidrawplus_button")} + showAriaLabel={true} + onClick={async () => { + 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"))); + } + } + }} + /> + </Card> + ); +}; |
