1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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>
);
};
|