appState.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. showWelcomeScreen: false,
  21. theme: THEME.LIGHT,
  22. collaborators: new Map(),
  23. currentChartType: "bar",
  24. currentItemBackgroundColor: "transparent",
  25. currentItemEndArrowhead: "arrow",
  26. currentItemFillStyle: "hachure",
  27. currentItemFontFamily: DEFAULT_FONT_FAMILY,
  28. currentItemFontSize: DEFAULT_FONT_SIZE,
  29. currentItemOpacity: 100,
  30. currentItemRoughness: 1,
  31. currentItemStartArrowhead: null,
  32. currentItemStrokeColor: oc.black,
  33. currentItemRoundness: "round",
  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. activeTool: {
  43. type: "selection",
  44. customType: null,
  45. locked: false,
  46. lastActiveTool: null,
  47. },
  48. penMode: false,
  49. penDetected: false,
  50. errorMessage: null,
  51. exportBackground: true,
  52. exportScale: defaultExportScale,
  53. exportEmbedScene: false,
  54. exportWithDarkMode: false,
  55. fileHandle: null,
  56. gridSize: null,
  57. isBindingEnabled: true,
  58. isSidebarDocked: false,
  59. isLoading: false,
  60. isResizing: false,
  61. isRotating: false,
  62. lastPointerDownWith: "mouse",
  63. multiElement: null,
  64. name: `${t("labels.untitled")}-${getDateTime()}`,
  65. contextMenu: null,
  66. openMenu: null,
  67. openPopup: null,
  68. openSidebar: null,
  69. openDialog: null,
  70. pasteDialog: { shown: false, data: null },
  71. previousSelectedElementIds: {},
  72. resizingElement: null,
  73. scrolledOutside: false,
  74. scrollX: 0,
  75. scrollY: 0,
  76. selectedElementIds: {},
  77. selectedGroupIds: {},
  78. selectionElement: null,
  79. shouldCacheIgnoreZoom: false,
  80. showStats: false,
  81. startBoundElement: null,
  82. suggestedBindings: [],
  83. toast: null,
  84. viewBackgroundColor: oc.white,
  85. zenModeEnabled: false,
  86. zoom: {
  87. value: 1 as NormalizedZoomValue,
  88. },
  89. viewModeEnabled: false,
  90. pendingImageElementId: null,
  91. showHyperlinkPopup: false,
  92. selectedLinearElement: null,
  93. };
  94. };
  95. /**
  96. * Config containing all AppState keys. Used to determine whether given state
  97. * prop should be stripped when exporting to given storage type.
  98. */
  99. const APP_STATE_STORAGE_CONF = (<
  100. Values extends {
  101. /** whether to keep when storing to browser storage (localStorage/IDB) */
  102. browser: boolean;
  103. /** whether to keep when exporting to file/database */
  104. export: boolean;
  105. /** server (shareLink/collab/...) */
  106. server: boolean;
  107. },
  108. T extends Record<keyof AppState, Values>,
  109. >(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
  110. config)({
  111. showWelcomeScreen: { browser: true, export: false, server: false },
  112. theme: { browser: true, export: false, server: false },
  113. collaborators: { browser: false, export: false, server: false },
  114. currentChartType: { browser: true, export: false, server: false },
  115. currentItemBackgroundColor: { browser: true, export: false, server: false },
  116. currentItemEndArrowhead: { browser: true, export: false, server: false },
  117. currentItemFillStyle: { browser: true, export: false, server: false },
  118. currentItemFontFamily: { browser: true, export: false, server: false },
  119. currentItemFontSize: { browser: true, export: false, server: false },
  120. currentItemRoundness: {
  121. browser: true,
  122. export: false,
  123. server: false,
  124. },
  125. currentItemOpacity: { browser: true, export: false, server: false },
  126. currentItemRoughness: { browser: true, export: false, server: false },
  127. currentItemStartArrowhead: { browser: true, export: false, server: false },
  128. currentItemStrokeColor: { browser: true, export: false, server: false },
  129. currentItemStrokeStyle: { browser: true, export: false, server: false },
  130. currentItemStrokeWidth: { browser: true, export: false, server: false },
  131. currentItemTextAlign: { browser: true, export: false, server: false },
  132. cursorButton: { browser: true, export: false, server: false },
  133. draggingElement: { browser: false, export: false, server: false },
  134. editingElement: { browser: false, export: false, server: false },
  135. editingGroupId: { browser: true, export: false, server: false },
  136. editingLinearElement: { browser: false, export: false, server: false },
  137. activeTool: { browser: true, export: false, server: false },
  138. penMode: { browser: true, export: false, server: false },
  139. penDetected: { browser: true, export: false, server: false },
  140. errorMessage: { browser: false, export: false, server: false },
  141. exportBackground: { browser: true, export: false, server: false },
  142. exportEmbedScene: { browser: true, export: false, server: false },
  143. exportScale: { browser: true, export: false, server: false },
  144. exportWithDarkMode: { browser: true, export: false, server: false },
  145. fileHandle: { browser: false, export: false, server: false },
  146. gridSize: { browser: true, export: true, server: true },
  147. height: { browser: false, export: false, server: false },
  148. isBindingEnabled: { browser: false, export: false, server: false },
  149. isSidebarDocked: { browser: true, export: false, server: false },
  150. isLoading: { browser: false, export: false, server: false },
  151. isResizing: { browser: false, export: false, server: false },
  152. isRotating: { browser: false, export: false, server: false },
  153. lastPointerDownWith: { browser: true, export: false, server: false },
  154. multiElement: { browser: false, export: false, server: false },
  155. name: { browser: true, export: false, server: false },
  156. offsetLeft: { browser: false, export: false, server: false },
  157. offsetTop: { browser: false, export: false, server: false },
  158. contextMenu: { browser: false, export: false, server: false },
  159. openMenu: { browser: true, export: false, server: false },
  160. openPopup: { browser: false, export: false, server: false },
  161. openSidebar: { browser: true, export: false, server: false },
  162. openDialog: { browser: false, export: false, server: false },
  163. pasteDialog: { browser: false, export: false, server: false },
  164. previousSelectedElementIds: { browser: true, export: false, server: false },
  165. resizingElement: { browser: false, export: false, server: false },
  166. scrolledOutside: { browser: true, export: false, server: false },
  167. scrollX: { browser: true, export: false, server: false },
  168. scrollY: { browser: true, export: false, server: false },
  169. selectedElementIds: { browser: true, export: false, server: false },
  170. selectedGroupIds: { browser: true, export: false, server: false },
  171. selectionElement: { browser: false, export: false, server: false },
  172. shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
  173. showStats: { browser: true, export: false, server: false },
  174. startBoundElement: { browser: false, export: false, server: false },
  175. suggestedBindings: { browser: false, export: false, server: false },
  176. toast: { browser: false, export: false, server: false },
  177. viewBackgroundColor: { browser: true, export: true, server: true },
  178. width: { browser: false, export: false, server: false },
  179. zenModeEnabled: { browser: true, export: false, server: false },
  180. zoom: { browser: true, export: false, server: false },
  181. viewModeEnabled: { browser: false, export: false, server: false },
  182. pendingImageElementId: { browser: false, export: false, server: false },
  183. showHyperlinkPopup: { browser: false, export: false, server: false },
  184. selectedLinearElement: { browser: true, export: false, server: false },
  185. });
  186. const _clearAppStateForStorage = <
  187. ExportType extends "export" | "browser" | "server",
  188. >(
  189. appState: Partial<AppState>,
  190. exportType: ExportType,
  191. ) => {
  192. type ExportableKeys = {
  193. [K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
  194. ? K
  195. : never;
  196. }[keyof typeof APP_STATE_STORAGE_CONF];
  197. const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
  198. for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
  199. const propConfig = APP_STATE_STORAGE_CONF[key];
  200. if (propConfig?.[exportType]) {
  201. const nextValue = appState[key];
  202. // https://github.com/microsoft/TypeScript/issues/31445
  203. (stateForExport as any)[key] = nextValue;
  204. }
  205. }
  206. return stateForExport;
  207. };
  208. export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
  209. return _clearAppStateForStorage(appState, "browser");
  210. };
  211. export const cleanAppStateForExport = (appState: Partial<AppState>) => {
  212. return _clearAppStateForStorage(appState, "export");
  213. };
  214. export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
  215. return _clearAppStateForStorage(appState, "server");
  216. };
  217. export const isEraserActive = ({
  218. activeTool,
  219. }: {
  220. activeTool: AppState["activeTool"];
  221. }) => activeTool.type === "eraser";
  222. export const isHandToolActive = ({
  223. activeTool,
  224. }: {
  225. activeTool: AppState["activeTool"];
  226. }) => {
  227. return activeTool.type === "hand";
  228. };