ExportToExcalidrawPlus.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import React from "react";
  2. import { Card } from "../../src/components/Card";
  3. import { ToolButton } from "../../src/components/ToolButton";
  4. import { serializeAsJSON } from "../../src/data/json";
  5. import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
  6. import { FileId, NonDeletedExcalidrawElement } from "../../src/element/types";
  7. import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
  8. import { nanoid } from "nanoid";
  9. import { useI18n } from "../../src/i18n";
  10. import { encryptData, generateEncryptionKey } from "../../src/data/encryption";
  11. import { isInitializedImageElement } from "../../src/element/typeChecks";
  12. import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
  13. import { encodeFilesForUpload } from "../data/FileManager";
  14. import { MIME_TYPES } from "../../src/constants";
  15. import { trackEvent } from "../../src/analytics";
  16. import { getFrame } from "../../src/utils";
  17. import { ExcalidrawLogo } from "../../src/components/ExcalidrawLogo";
  18. export const exportToExcalidrawPlus = async (
  19. elements: readonly NonDeletedExcalidrawElement[],
  20. appState: Partial<AppState>,
  21. files: BinaryFiles,
  22. ) => {
  23. const firebase = await loadFirebaseStorage();
  24. const id = `${nanoid(12)}`;
  25. const encryptionKey = (await generateEncryptionKey())!;
  26. const encryptedData = await encryptData(
  27. encryptionKey,
  28. serializeAsJSON(elements, appState, files, "database"),
  29. );
  30. const blob = new Blob(
  31. [encryptedData.iv, new Uint8Array(encryptedData.encryptedBuffer)],
  32. {
  33. type: MIME_TYPES.binary,
  34. },
  35. );
  36. await firebase
  37. .storage()
  38. .ref(`/migrations/scenes/${id}`)
  39. .put(blob, {
  40. customMetadata: {
  41. data: JSON.stringify({ version: 2, name: appState.name }),
  42. created: Date.now().toString(),
  43. },
  44. });
  45. const filesMap = new Map<FileId, BinaryFileData>();
  46. for (const element of elements) {
  47. if (isInitializedImageElement(element) && files[element.fileId]) {
  48. filesMap.set(element.fileId, files[element.fileId]);
  49. }
  50. }
  51. if (filesMap.size) {
  52. const filesToUpload = await encodeFilesForUpload({
  53. files: filesMap,
  54. encryptionKey,
  55. maxBytes: FILE_UPLOAD_MAX_BYTES,
  56. });
  57. await saveFilesToFirebase({
  58. prefix: `/migrations/files/scenes/${id}`,
  59. files: filesToUpload,
  60. });
  61. }
  62. window.open(
  63. `${
  64. import.meta.env.VITE_APP_PLUS_APP
  65. }/import?excalidraw=${id},${encryptionKey}`,
  66. );
  67. };
  68. export const ExportToExcalidrawPlus: React.FC<{
  69. elements: readonly NonDeletedExcalidrawElement[];
  70. appState: Partial<AppState>;
  71. files: BinaryFiles;
  72. onError: (error: Error) => void;
  73. onSuccess: () => void;
  74. }> = ({ elements, appState, files, onError, onSuccess }) => {
  75. const { t } = useI18n();
  76. return (
  77. <Card color="primary">
  78. <div className="Card-icon">
  79. <ExcalidrawLogo
  80. style={{
  81. [`--color-logo-icon` as any]: "#fff",
  82. width: "2.8rem",
  83. height: "2.8rem",
  84. }}
  85. />
  86. </div>
  87. <h2>Excalidraw+</h2>
  88. <div className="Card-details">
  89. {t("exportDialog.excalidrawplus_description")}
  90. </div>
  91. <ToolButton
  92. className="Card-button"
  93. type="button"
  94. title={t("exportDialog.excalidrawplus_button")}
  95. aria-label={t("exportDialog.excalidrawplus_button")}
  96. showAriaLabel={true}
  97. onClick={async () => {
  98. try {
  99. trackEvent("export", "eplus", `ui (${getFrame()})`);
  100. await exportToExcalidrawPlus(elements, appState, files);
  101. onSuccess();
  102. } catch (error: any) {
  103. console.error(error);
  104. if (error.name !== "AbortError") {
  105. onError(new Error(t("exportDialog.excalidrawplus_exportError")));
  106. }
  107. }
  108. }}
  109. />
  110. </Card>
  111. );
  112. };