aboutsummaryrefslogtreecommitdiffstats
path: root/excalidraw-app/components/ExportToExcalidrawPlus.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'excalidraw-app/components/ExportToExcalidrawPlus.tsx')
-rw-r--r--excalidraw-app/components/ExportToExcalidrawPlus.tsx133
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>
+ );
+};