appState.ts 8.4 KB

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