excalidraw/src/excalidraw-app/components/ExportToExcalidrawPlus.tsx

107 lines
3.2 KiB
TypeScript

import React from "react";
import { Card } from "../../components/Card";
import { ToolButton } from "../../components/ToolButton";
import { serializeAsJSON } from "../../data/json";
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
import { nanoid } from "nanoid";
import { t } from "../../i18n";
import { excalidrawPlusIcon } from "./icons";
import { encryptData, generateEncryptionKey } from "../../data/encryption";
import { isInitializedImageElement } from "../../element/typeChecks";
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
import { encodeFilesForUpload } from "../data/FileManager";
import { MIME_TYPES } from "../../constants";
const exportToExcalidrawPlus = async (
elements: readonly NonDeletedExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
) => {
const firebase = 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,
},
);
await firebase
.storage()
.ref(`/migrations/scenes/${id}`)
.put(blob, {
customMetadata: {
data: JSON.stringify({ version: 2, name: appState.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(
`https://plus.excalidraw.com/import?excalidraw=${id},${encryptionKey}`,
);
};
export const ExportToExcalidrawPlus: React.FC<{
elements: readonly NonDeletedExcalidrawElement[];
appState: AppState;
files: BinaryFiles;
onError: (error: Error) => void;
}> = ({ elements, appState, files, onError }) => {
return (
<Card color="indigo">
<div className="Card-icon">{excalidrawPlusIcon}</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 {
await exportToExcalidrawPlus(elements, appState, files);
} catch (error: any) {
console.error(error);
if (error.name !== "AbortError") {
onError(new Error(t("exportDialog.excalidrawplus_exportError")));
}
}
}}
/>
</Card>
);
};