2
0
Эх сурвалжийг харах

purify getDefaultAppState by removing `name`

dwelle 4 жил өмнө
parent
commit
fe973e3513

+ 2 - 1
src/actions/actionCanvas.tsx

@@ -15,7 +15,7 @@ import { getNormalizedZoom, getSelectedElements } from "../scene";
 import { centerScrollOn } from "../scene/scroll";
 import { centerScrollOn } from "../scene/scroll";
 import { getNewZoom } from "../scene/zoom";
 import { getNewZoom } from "../scene/zoom";
 import { AppState, NormalizedZoomValue } from "../types";
 import { AppState, NormalizedZoomValue } from "../types";
-import { getShortcutKey } from "../utils";
+import { getNewSceneName, getShortcutKey } from "../utils";
 import { register } from "./register";
 import { register } from "./register";
 
 
 export const actionChangeViewBackgroundColor = register({
 export const actionChangeViewBackgroundColor = register({
@@ -59,6 +59,7 @@ export const actionClearCanvas = register({
       ),
       ),
       appState: {
       appState: {
         ...getDefaultAppState(),
         ...getDefaultAppState(),
+        name: getNewSceneName(),
         appearance: appState.appearance,
         appearance: appState.appearance,
         elementLocked: appState.elementLocked,
         elementLocked: appState.elementLocked,
         exportBackground: appState.exportBackground,
         exportBackground: appState.exportBackground,

+ 2 - 1
src/actions/actionExport.tsx

@@ -12,6 +12,7 @@ import { KEYS } from "../keys";
 import { muteFSAbortError } from "../utils";
 import { muteFSAbortError } from "../utils";
 import { register } from "./register";
 import { register } from "./register";
 import "../components/ToolIcon.scss";
 import "../components/ToolIcon.scss";
+import { SCENE_NAME_FALLBACK } from "../constants";
 
 
 export const actionChangeProjectName = register({
 export const actionChangeProjectName = register({
   name: "changeProjectName",
   name: "changeProjectName",
@@ -22,7 +23,7 @@ export const actionChangeProjectName = register({
   PanelComponent: ({ appState, updateData }) => (
   PanelComponent: ({ appState, updateData }) => (
     <ProjectName
     <ProjectName
       label={t("labels.fileTitle")}
       label={t("labels.fileTitle")}
-      value={appState.name || "Unnamed"}
+      value={appState.name || SCENE_NAME_FALLBACK}
       onChange={(name: string) => updateData(name)}
       onChange={(name: string) => updateData(name)}
     />
     />
   ),
   ),

+ 15 - 7
src/appState.ts

@@ -2,16 +2,20 @@ import oc from "open-color";
 import {
 import {
   DEFAULT_FONT_FAMILY,
   DEFAULT_FONT_FAMILY,
   DEFAULT_FONT_SIZE,
   DEFAULT_FONT_SIZE,
+  SCENE_NAME_FALLBACK,
   DEFAULT_TEXT_ALIGN,
   DEFAULT_TEXT_ALIGN,
 } from "./constants";
 } from "./constants";
-import { t } from "./i18n";
 import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
 import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
-import { getDateTime } from "./utils";
 
 
-export const getDefaultAppState = (): Omit<
-  AppState,
-  "offsetTop" | "offsetLeft"
-> => {
+type DefaultAppState = Omit<AppState, "offsetTop" | "offsetLeft" | "name"> & {
+  /**
+   * You should override this with current appState.name, or whatever is
+   * applicable at a given place where you get default appState.
+   */
+  name: undefined;
+};
+
+export const getDefaultAppState = (): DefaultAppState => {
   return {
   return {
     appearance: "light",
     appearance: "light",
     collaborators: new Map(),
     collaborators: new Map(),
@@ -50,7 +54,11 @@ export const getDefaultAppState = (): Omit<
     isRotating: false,
     isRotating: false,
     lastPointerDownWith: "mouse",
     lastPointerDownWith: "mouse",
     multiElement: null,
     multiElement: null,
-    name: `${t("labels.untitled")}-${getDateTime()}`,
+    // for safety (because TS mostly doesn't distinguish optional types and
+    //  undefined values), we set `name` to the fallback name, but we cast it to
+    // `undefined` so that TS forces us to explicitly specify it wherever
+    // possible
+    name: (SCENE_NAME_FALLBACK as unknown) as undefined,
     openMenu: null,
     openMenu: null,
     pasteDialog: { shown: false, data: null },
     pasteDialog: { shown: false, data: null },
     previousSelectedElementIds: {},
     previousSelectedElementIds: {},

+ 3 - 0
src/components/App.tsx

@@ -148,6 +148,7 @@ import {
 import {
 import {
   debounce,
   debounce,
   distance,
   distance,
+  getNewSceneName,
   isInputLike,
   isInputLike,
   isToolIcon,
   isToolIcon,
   isWritableElement,
   isWritableElement,
@@ -281,6 +282,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     } = props;
     } = props;
     this.state = {
     this.state = {
       ...defaultAppState,
       ...defaultAppState,
+      name: getNewSceneName(),
       isLoading: true,
       isLoading: true,
       width,
       width,
       height,
       height,
@@ -528,6 +530,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       this.scene.replaceAllElements([]);
       this.scene.replaceAllElements([]);
       this.setState((state) => ({
       this.setState((state) => ({
         ...getDefaultAppState(),
         ...getDefaultAppState(),
+        name: getNewSceneName(),
         isLoading: opts?.resetLoadingState ? false : state.isLoading,
         isLoading: opts?.resetLoadingState ? false : state.isLoading,
         appearance: this.state.appearance,
         appearance: this.state.appearance,
       }));
       }));

+ 2 - 0
src/constants.ts

@@ -88,3 +88,5 @@ export const STORAGE_KEYS = {
 export const TAP_TWICE_TIMEOUT = 300;
 export const TAP_TWICE_TIMEOUT = 300;
 export const TOUCH_CTX_MENU_TIMEOUT = 500;
 export const TOUCH_CTX_MENU_TIMEOUT = 500;
 export const TITLE_TIMEOUT = 10000;
 export const TITLE_TIMEOUT = 10000;
+
+export const SCENE_NAME_FALLBACK = "Untitled";

+ 2 - 0
src/data/restore.ts

@@ -15,6 +15,7 @@ import {
   DEFAULT_VERTICAL_ALIGN,
   DEFAULT_VERTICAL_ALIGN,
 } from "../constants";
 } from "../constants";
 import { getDefaultAppState } from "../appState";
 import { getDefaultAppState } from "../appState";
+import { getNewSceneName } from "../utils";
 
 
 const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
 const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
   for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
   for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
@@ -166,6 +167,7 @@ const restoreAppState = (
 
 
   return {
   return {
     ...nextAppState,
     ...nextAppState,
+    name: appState.name ?? localAppState?.name ?? getNewSceneName(),
     offsetLeft: appState.offsetLeft || 0,
     offsetLeft: appState.offsetLeft || 0,
     offsetTop: appState.offsetTop || 0,
     offsetTop: appState.offsetTop || 0,
     // Migrates from previous version where appState.zoom was a number
     // Migrates from previous version where appState.zoom was a number

+ 2 - 1
src/excalidraw-app/data/localStorage.ts

@@ -6,6 +6,7 @@ import {
 } from "../../appState";
 } from "../../appState";
 import { clearElementsForLocalStorage } from "../../element";
 import { clearElementsForLocalStorage } from "../../element";
 import { STORAGE_KEYS as APP_STORAGE_KEYS } from "../../constants";
 import { STORAGE_KEYS as APP_STORAGE_KEYS } from "../../constants";
+import { ImportedDataState } from "../../data/types";
 
 
 export const STORAGE_KEYS = {
 export const STORAGE_KEYS = {
   LOCAL_STORAGE_ELEMENTS: "excalidraw",
   LOCAL_STORAGE_ELEMENTS: "excalidraw",
@@ -81,7 +82,7 @@ export const importFromLocalStorage = () => {
     }
     }
   }
   }
 
 
-  let appState = null;
+  let appState: ImportedDataState["appState"] = null;
   if (savedState) {
   if (savedState) {
     try {
     try {
       appState = {
       appState = {

+ 2 - 0
src/index-node.ts

@@ -1,5 +1,6 @@
 import { exportToCanvas } from "./scene/export";
 import { exportToCanvas } from "./scene/export";
 import { getDefaultAppState } from "./appState";
 import { getDefaultAppState } from "./appState";
+import { SCENE_NAME_FALLBACK } from "./constants";
 
 
 const { registerFont, createCanvas } = require("canvas");
 const { registerFont, createCanvas } = require("canvas");
 
 
@@ -61,6 +62,7 @@ const canvas = exportToCanvas(
   elements as any,
   elements as any,
   {
   {
     ...getDefaultAppState(),
     ...getDefaultAppState(),
+    name: SCENE_NAME_FALLBACK,
     offsetTop: 0,
     offsetTop: 0,
     offsetLeft: 0,
     offsetLeft: 0,
   },
   },

+ 3 - 2
src/packages/utils.ts

@@ -6,6 +6,7 @@ import { getDefaultAppState } from "../appState";
 import { AppState } from "../types";
 import { AppState } from "../types";
 import { ExcalidrawElement } from "../element/types";
 import { ExcalidrawElement } from "../element/types";
 import { getNonDeletedElements } from "../element";
 import { getNonDeletedElements } from "../element";
+import { SCENE_NAME_FALLBACK } from "../constants";
 
 
 type ExportOpts = {
 type ExportOpts = {
   elements: readonly ExcalidrawElement[];
   elements: readonly ExcalidrawElement[];
@@ -18,7 +19,7 @@ type ExportOpts = {
 
 
 export const exportToCanvas = ({
 export const exportToCanvas = ({
   elements,
   elements,
-  appState = getDefaultAppState(),
+  appState = { ...getDefaultAppState(), name: SCENE_NAME_FALLBACK },
   getDimensions = (width, height) => ({ width, height, scale: 1 }),
   getDimensions = (width, height) => ({ width, height, scale: 1 }),
 }: ExportOpts) => {
 }: ExportOpts) => {
   return _exportToCanvas(
   return _exportToCanvas(
@@ -74,7 +75,7 @@ export const exportToBlob = (
 
 
 export const exportToSvg = ({
 export const exportToSvg = ({
   elements,
   elements,
-  appState = getDefaultAppState(),
+  appState = { ...getDefaultAppState(), name: SCENE_NAME_FALLBACK },
   exportPadding,
   exportPadding,
   metadata,
   metadata,
 }: ExportOpts & {
 }: ExportOpts & {

+ 5 - 0
src/utils.ts

@@ -8,6 +8,7 @@ import { FontFamily, FontString } from "./element/types";
 import { Zoom } from "./types";
 import { Zoom } from "./types";
 import { unstable_batchedUpdates } from "react-dom";
 import { unstable_batchedUpdates } from "react-dom";
 import { isDarwin } from "./keys";
 import { isDarwin } from "./keys";
+import { t } from "./i18n";
 
 
 export const SVG_NS = "http://www.w3.org/2000/svg";
 export const SVG_NS = "http://www.w3.org/2000/svg";
 
 
@@ -32,6 +33,10 @@ export const getDateTime = () => {
   return `${year}-${month}-${day}-${hr}${min}`;
   return `${year}-${month}-${day}-${hr}${min}`;
 };
 };
 
 
+export const getNewSceneName = () => {
+  return `${t("labels.untitled")}-${getDateTime()}`;
+};
+
 export const capitalizeString = (str: string) =>
 export const capitalizeString = (str: string) =>
   str.charAt(0).toUpperCase() + str.slice(1);
   str.charAt(0).toUpperCase() + str.slice(1);