blob.ts 4.0 KB

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