mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-31 10:54:33 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			136 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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>
 | |
|   );
 | |
| };
 | 
