restore.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {
  2. ExcalidrawElement,
  3. FontFamily,
  4. ExcalidrawSelectionElement,
  5. } from "../element/types";
  6. import { AppState } from "../types";
  7. import { DataState } from "./types";
  8. import { isInvisiblySmallElement, getNormalizedDimensions } from "../element";
  9. import { calculateScrollCenter } from "../scene";
  10. import { randomId } from "../random";
  11. import { DEFAULT_TEXT_ALIGN, DEFAULT_FONT_FAMILY } from "../appState";
  12. import { FONT_FAMILY } from "../constants";
  13. const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
  14. for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
  15. if (fontFamilyString.includes(fontFamilyName)) {
  16. return parseInt(id) as FontFamily;
  17. }
  18. }
  19. return DEFAULT_FONT_FAMILY;
  20. };
  21. function migrateElementWithProperties<T extends ExcalidrawElement>(
  22. element: T,
  23. extra: Omit<T, keyof ExcalidrawElement>,
  24. ): T {
  25. const base: Pick<T, keyof ExcalidrawElement> = {
  26. type: element.type,
  27. // all elements must have version > 0 so getDrawingVersion() will pick up
  28. // newly added elements
  29. version: element.version || 1,
  30. versionNonce: element.versionNonce ?? 0,
  31. isDeleted: false,
  32. id: element.id || randomId(),
  33. fillStyle: element.fillStyle || "hachure",
  34. strokeWidth: element.strokeWidth || 1,
  35. strokeStyle: element.strokeStyle ?? "solid",
  36. roughness: element.roughness ?? 1,
  37. opacity: element.opacity == null ? 100 : element.opacity,
  38. angle: element.angle || 0,
  39. x: element.x || 0,
  40. y: element.y || 0,
  41. strokeColor: element.strokeColor,
  42. backgroundColor: element.backgroundColor,
  43. width: element.width || 0,
  44. height: element.height || 0,
  45. seed: element.seed ?? 1,
  46. groupIds: element.groupIds || [],
  47. };
  48. return {
  49. ...base,
  50. ...getNormalizedDimensions(base),
  51. ...extra,
  52. } as T;
  53. }
  54. const migrateElement = (
  55. element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
  56. ): typeof element => {
  57. switch (element.type) {
  58. case "text":
  59. let fontSize = element.fontSize;
  60. let fontFamily = element.fontFamily;
  61. if ("font" in element) {
  62. const [fontPx, _fontFamily]: [
  63. string,
  64. string,
  65. ] = (element as any).font.split(" ");
  66. fontSize = parseInt(fontPx, 10);
  67. fontFamily = getFontFamilyByName(_fontFamily);
  68. }
  69. return migrateElementWithProperties(element, {
  70. fontSize,
  71. fontFamily,
  72. text: element.text ?? "",
  73. baseline: element.baseline,
  74. textAlign: element.textAlign ?? DEFAULT_TEXT_ALIGN,
  75. });
  76. case "draw":
  77. case "line":
  78. case "arrow": {
  79. return migrateElementWithProperties(element, {
  80. points:
  81. // migrate old arrow model to new one
  82. !Array.isArray(element.points) || element.points.length < 2
  83. ? [
  84. [0, 0],
  85. [element.width, element.height],
  86. ]
  87. : element.points,
  88. });
  89. }
  90. // generic elements
  91. case "ellipse":
  92. case "rectangle":
  93. case "diamond":
  94. return migrateElementWithProperties(element, {});
  95. // don't use default case so as to catch a missing an element type case
  96. // (we also don't want to throw, but instead return void so we
  97. // filter out these unsupported elements from the restored array)
  98. }
  99. };
  100. export const restore = (
  101. savedElements: readonly ExcalidrawElement[],
  102. savedState: AppState | null,
  103. opts?: { scrollToContent: boolean },
  104. ): DataState => {
  105. const elements = savedElements.reduce((elements, element) => {
  106. // filtering out selection, which is legacy, no longer kept in elements,
  107. // and causing issues if retained
  108. if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
  109. const migratedElement = migrateElement(element);
  110. if (migratedElement) {
  111. elements.push(migratedElement);
  112. }
  113. }
  114. return elements;
  115. }, [] as ExcalidrawElement[]);
  116. if (opts?.scrollToContent && savedState) {
  117. savedState = {
  118. ...savedState,
  119. ...calculateScrollCenter(elements, savedState, null),
  120. };
  121. }
  122. return {
  123. elements: elements,
  124. appState: savedState,
  125. };
  126. };