| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 | import React from "react";import { uploadBytes, ref } from "firebase/storage";import { nanoid } from "nanoid";import { trackEvent } from "@excalidraw/excalidraw/analytics";import { Card } from "@excalidraw/excalidraw/components/Card";import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton";import { MIME_TYPES, getFrame } from "@excalidraw/common";import {  encryptData,  generateEncryptionKey,} from "@excalidraw/excalidraw/data/encryption";import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";import { isInitializedImageElement } from "@excalidraw/element";import { useI18n } from "@excalidraw/excalidraw/i18n";import type {  FileId,  NonDeletedExcalidrawElement,} from "@excalidraw/element/types";import type {  AppState,  BinaryFileData,  BinaryFiles,} from "@excalidraw/excalidraw/types";import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";import { encodeFilesForUpload } from "../data/FileManager";import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";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>  );};
 |