blob.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { cleanAppStateForExport } from "../appState";
  2. import { EXPORT_DATA_TYPES } from "../constants";
  3. import { clearElementsForExport } from "../element";
  4. import { ExcalidrawElement } from "../element/types";
  5. import { CanvasError } from "../errors";
  6. import { t } from "../i18n";
  7. import { calculateScrollCenter } from "../scene";
  8. import { AppState } from "../types";
  9. import { isValidExcalidrawData } from "./json";
  10. import { restore } from "./restore";
  11. import { ImportedLibraryData } from "./types";
  12. const parseFileContents = async (blob: Blob | File) => {
  13. let contents: string;
  14. if (blob.type === "image/png") {
  15. try {
  16. return await (
  17. await import(/* webpackChunkName: "image" */ "./image")
  18. ).decodePngMetadata(blob);
  19. } catch (error) {
  20. if (error.message === "INVALID") {
  21. throw new Error(t("alerts.imageDoesNotContainScene"));
  22. } else {
  23. throw new Error(t("alerts.cannotRestoreFromImage"));
  24. }
  25. }
  26. } else {
  27. if ("text" in Blob) {
  28. contents = await blob.text();
  29. } else {
  30. contents = await new Promise((resolve) => {
  31. const reader = new FileReader();
  32. reader.readAsText(blob, "utf8");
  33. reader.onloadend = () => {
  34. if (reader.readyState === FileReader.DONE) {
  35. resolve(reader.result as string);
  36. }
  37. };
  38. });
  39. }
  40. if (blob.type === "image/svg+xml") {
  41. try {
  42. return await (
  43. await import(/* webpackChunkName: "image" */ "./image")
  44. ).decodeSvgMetadata({
  45. svg: contents,
  46. });
  47. } catch (error) {
  48. if (error.message === "INVALID") {
  49. throw new Error(t("alerts.imageDoesNotContainScene"));
  50. } else {
  51. throw new Error(t("alerts.cannotRestoreFromImage"));
  52. }
  53. }
  54. }
  55. }
  56. return contents;
  57. };
  58. export const getMimeType = (blob: Blob | string): string => {
  59. let name: string;
  60. if (typeof blob === "string") {
  61. name = blob;
  62. } else {
  63. if (blob.type) {
  64. return blob.type;
  65. }
  66. name = blob.name || "";
  67. }
  68. if (/\.(excalidraw|json)$/.test(name)) {
  69. return "application/json";
  70. } else if (/\.png$/.test(name)) {
  71. return "image/png";
  72. } else if (/\.jpe?g$/.test(name)) {
  73. return "image/jpeg";
  74. } else if (/\.svg$/.test(name)) {
  75. return "image/svg+xml";
  76. }
  77. return "";
  78. };
  79. export const loadFromBlob = async (
  80. blob: Blob,
  81. /** @see restore.localAppState */
  82. localAppState: AppState | null,
  83. localElements: readonly ExcalidrawElement[] | null,
  84. ) => {
  85. const contents = await parseFileContents(blob);
  86. try {
  87. const data = JSON.parse(contents);
  88. if (!isValidExcalidrawData(data)) {
  89. throw new Error(t("alerts.couldNotLoadInvalidFile"));
  90. }
  91. const result = restore(
  92. {
  93. elements: clearElementsForExport(data.elements || []),
  94. appState: {
  95. theme: localAppState?.theme,
  96. fileHandle: (!blob.type.startsWith("image/") && blob.handle) || null,
  97. ...cleanAppStateForExport(data.appState || {}),
  98. ...(localAppState
  99. ? calculateScrollCenter(data.elements || [], localAppState, null)
  100. : {}),
  101. },
  102. },
  103. localAppState,
  104. localElements,
  105. );
  106. return result;
  107. } catch (error) {
  108. console.error(error.message);
  109. throw new Error(t("alerts.couldNotLoadInvalidFile"));
  110. }
  111. };
  112. export const loadLibraryFromBlob = async (blob: Blob) => {
  113. const contents = await parseFileContents(blob);
  114. const data: ImportedLibraryData = JSON.parse(contents);
  115. if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) {
  116. throw new Error(t("alerts.couldNotLoadInvalidFile"));
  117. }
  118. return data;
  119. };
  120. export const canvasToBlob = async (
  121. canvas: HTMLCanvasElement,
  122. ): Promise<Blob> => {
  123. return new Promise((resolve, reject) => {
  124. try {
  125. canvas.toBlob((blob) => {
  126. if (!blob) {
  127. return reject(
  128. new CanvasError(
  129. t("canvasError.canvasTooBig"),
  130. "CANVAS_POSSIBLY_TOO_BIG",
  131. ),
  132. );
  133. }
  134. resolve(blob);
  135. });
  136. } catch (error) {
  137. reject(error);
  138. }
  139. });
  140. };