appState.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import oc from "open-color";
  2. import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
  3. import { getDateTime } from "./utils";
  4. import { t } from "./i18n";
  5. import {
  6. DEFAULT_FONT_SIZE,
  7. DEFAULT_FONT_FAMILY,
  8. DEFAULT_TEXT_ALIGN,
  9. } from "./constants";
  10. export const getDefaultAppState = (): Omit<
  11. AppState,
  12. "offsetTop" | "offsetLeft"
  13. > => {
  14. return {
  15. appearance: "light",
  16. isLoading: false,
  17. errorMessage: null,
  18. draggingElement: null,
  19. resizingElement: null,
  20. multiElement: null,
  21. editingElement: null,
  22. startBoundElement: null,
  23. editingLinearElement: null,
  24. elementType: "selection",
  25. elementLocked: false,
  26. exportBackground: true,
  27. exportEmbedScene: false,
  28. shouldAddWatermark: false,
  29. currentItemStrokeColor: oc.black,
  30. currentItemBackgroundColor: "transparent",
  31. currentItemFillStyle: "hachure",
  32. currentItemStrokeWidth: 1,
  33. currentItemStrokeStyle: "solid",
  34. currentItemRoughness: 1,
  35. currentItemOpacity: 100,
  36. currentItemFontSize: DEFAULT_FONT_SIZE,
  37. currentItemFontFamily: DEFAULT_FONT_FAMILY,
  38. currentItemTextAlign: DEFAULT_TEXT_ALIGN,
  39. currentItemStrokeSharpness: "sharp",
  40. currentItemLinearStrokeSharpness: "round",
  41. viewBackgroundColor: oc.white,
  42. scrollX: 0 as FlooredNumber,
  43. scrollY: 0 as FlooredNumber,
  44. cursorX: 0,
  45. cursorY: 0,
  46. cursorButton: "up",
  47. scrolledOutside: false,
  48. name: `${t("labels.untitled")}-${getDateTime()}`,
  49. isBindingEnabled: true,
  50. isResizing: false,
  51. isRotating: false,
  52. selectionElement: null,
  53. zoom: {
  54. value: 1 as NormalizedZoomValue,
  55. translation: { x: 0, y: 0 },
  56. },
  57. openMenu: null,
  58. lastPointerDownWith: "mouse",
  59. selectedElementIds: {},
  60. previousSelectedElementIds: {},
  61. shouldCacheIgnoreZoom: false,
  62. showShortcutsDialog: false,
  63. suggestedBindings: [],
  64. zenModeEnabled: false,
  65. gridSize: null,
  66. editingGroupId: null,
  67. selectedGroupIds: {},
  68. width: window.innerWidth,
  69. height: window.innerHeight,
  70. isLibraryOpen: false,
  71. fileHandle: null,
  72. collaborators: new Map(),
  73. };
  74. };
  75. /**
  76. * Config containing all AppState keys. Used to determine whether given state
  77. * prop should be stripped when exporting to given storage type.
  78. */
  79. const APP_STATE_STORAGE_CONF = (<
  80. Values extends {
  81. /** whether to keep when storing to browser storage (localStorage/IDB) */
  82. browser: boolean;
  83. /** whether to keep when exporting to file/database */
  84. export: boolean;
  85. },
  86. T extends Record<keyof AppState, Values>
  87. >(
  88. config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
  89. ) => config)({
  90. appearance: { browser: true, export: false },
  91. currentItemBackgroundColor: { browser: true, export: false },
  92. currentItemFillStyle: { browser: true, export: false },
  93. currentItemFontFamily: { browser: true, export: false },
  94. currentItemFontSize: { browser: true, export: false },
  95. currentItemOpacity: { browser: true, export: false },
  96. currentItemRoughness: { browser: true, export: false },
  97. currentItemStrokeColor: { browser: true, export: false },
  98. currentItemStrokeStyle: { browser: true, export: false },
  99. currentItemStrokeWidth: { browser: true, export: false },
  100. currentItemTextAlign: { browser: true, export: false },
  101. currentItemStrokeSharpness: { browser: true, export: false },
  102. currentItemLinearStrokeSharpness: { browser: true, export: false },
  103. cursorButton: { browser: true, export: false },
  104. cursorX: { browser: true, export: false },
  105. cursorY: { browser: true, export: false },
  106. draggingElement: { browser: false, export: false },
  107. editingElement: { browser: false, export: false },
  108. startBoundElement: { browser: false, export: false },
  109. editingGroupId: { browser: true, export: false },
  110. editingLinearElement: { browser: false, export: false },
  111. elementLocked: { browser: true, export: false },
  112. elementType: { browser: true, export: false },
  113. errorMessage: { browser: false, export: false },
  114. exportBackground: { browser: true, export: false },
  115. exportEmbedScene: { browser: true, export: false },
  116. gridSize: { browser: true, export: true },
  117. height: { browser: false, export: false },
  118. isBindingEnabled: { browser: false, export: false },
  119. isLibraryOpen: { browser: false, export: false },
  120. isLoading: { browser: false, export: false },
  121. isResizing: { browser: false, export: false },
  122. isRotating: { browser: false, export: false },
  123. lastPointerDownWith: { browser: true, export: false },
  124. multiElement: { browser: false, export: false },
  125. name: { browser: true, export: false },
  126. openMenu: { browser: true, export: false },
  127. previousSelectedElementIds: { browser: true, export: false },
  128. resizingElement: { browser: false, export: false },
  129. scrolledOutside: { browser: true, export: false },
  130. scrollX: { browser: true, export: false },
  131. scrollY: { browser: true, export: false },
  132. selectedElementIds: { browser: true, export: false },
  133. selectedGroupIds: { browser: true, export: false },
  134. selectionElement: { browser: false, export: false },
  135. shouldAddWatermark: { browser: true, export: false },
  136. shouldCacheIgnoreZoom: { browser: true, export: false },
  137. showShortcutsDialog: { browser: false, export: false },
  138. suggestedBindings: { browser: false, export: false },
  139. viewBackgroundColor: { browser: true, export: true },
  140. width: { browser: false, export: false },
  141. zenModeEnabled: { browser: true, export: false },
  142. zoom: { browser: true, export: false },
  143. offsetTop: { browser: false, export: false },
  144. offsetLeft: { browser: false, export: false },
  145. fileHandle: { browser: false, export: false },
  146. collaborators: { browser: false, export: false },
  147. });
  148. const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
  149. appState: Partial<AppState>,
  150. exportType: ExportType,
  151. ) => {
  152. type ExportableKeys = {
  153. [K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
  154. ? K
  155. : never;
  156. }[keyof typeof APP_STATE_STORAGE_CONF];
  157. const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
  158. for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
  159. const propConfig = APP_STATE_STORAGE_CONF[key];
  160. if (!propConfig) {
  161. console.error(
  162. `_clearAppStateForStorage: appState key "${key}" config doesn't exist for "${exportType}" export type`,
  163. );
  164. }
  165. if (propConfig?.[exportType]) {
  166. // @ts-ignore see https://github.com/microsoft/TypeScript/issues/31445
  167. stateForExport[key] = appState[key];
  168. }
  169. }
  170. return stateForExport;
  171. };
  172. export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
  173. return _clearAppStateForStorage(appState, "browser");
  174. };
  175. export const cleanAppStateForExport = (appState: Partial<AppState>) => {
  176. return _clearAppStateForStorage(appState, "export");
  177. };