diff --git a/excalidraw-app/tests/MobileMenu.test.tsx b/excalidraw-app/tests/MobileMenu.test.tsx index b97cafaf06..59ee66177d 100644 --- a/excalidraw-app/tests/MobileMenu.test.tsx +++ b/excalidraw-app/tests/MobileMenu.test.tsx @@ -30,7 +30,7 @@ describe("Test MobileMenu", () => { { "canFitSidebar": false, "desktopUIMode": "full", - "formFactor": "phone", + "formFactor": "desktop", "isLandscape": true, "isTouchScreen": false, "userAgent": { diff --git a/packages/common/src/editorInterface.ts b/packages/common/src/editorInterface.ts index 5473a085b4..4e7c248fac 100644 --- a/packages/common/src/editorInterface.ts +++ b/packages/common/src/editorInterface.ts @@ -14,7 +14,7 @@ export type EditorInterface = Readonly<{ }>; // storage key -export const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode"; +const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode"; // breakpoints // mobile: up to 699px @@ -185,3 +185,41 @@ export const createUserAgentDescriptor = ( platform, } as const; }; + +export const loadDesktopUIModePreference = () => { + if (typeof window === "undefined") { + return null; + } + + try { + const stored = window.localStorage.getItem(DESKTOP_UI_MODE_STORAGE_KEY); + if (stored === "compact" || stored === "full") { + return stored as EditorInterface["desktopUIMode"]; + } + } catch (error) { + // ignore storage access issues (e.g., Safari private mode) + } + + return null; +}; + +const persistDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => { + if (typeof window === "undefined") { + return; + } + try { + window.localStorage.setItem(DESKTOP_UI_MODE_STORAGE_KEY, mode); + } catch (error) { + // ignore storage access issues (e.g., Safari private mode) + } +}; + +export const setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => { + if (mode !== "compact" && mode !== "full") { + return; + } + + persistDesktopUIMode(mode); + + return mode; +}; diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 64a111197f..2b4beacdb8 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -96,7 +96,6 @@ import { Emitter, MINIMUM_ARROW_SIZE, DOUBLE_TAP_POSITION_THRESHOLD, - DESKTOP_UI_MODE_STORAGE_KEY, createUserAgentDescriptor, getFormFactor, deriveStylesPanelMode, @@ -105,6 +104,8 @@ import { isSafari, type EditorInterface, type StylesPanelMode, + loadDesktopUIModePreference, + setDesktopUIMode, } from "@excalidraw/common"; import { @@ -583,48 +584,6 @@ class App extends React.Component { private stylesPanelMode: StylesPanelMode = deriveStylesPanelMode( editorInterfaceContextInitialValue, ); - private loadDesktopUIModePreference = () => { - if (typeof window === "undefined") { - return null; - } - - try { - const stored = window.localStorage.getItem(DESKTOP_UI_MODE_STORAGE_KEY); - if (stored === "compact" || stored === "full") { - return stored as EditorInterface["desktopUIMode"]; - } - } catch (error) { - // ignore storage access issues (e.g., Safari private mode) - } - - return null; - }; - - private persistDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => { - if (typeof window === "undefined") { - return; - } - try { - window.localStorage.setItem(DESKTOP_UI_MODE_STORAGE_KEY, mode); - } catch (error) { - // ignore storage access issues (e.g., Safari private mode) - } - }; - - public setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => { - if (mode !== "compact" && mode !== "full") { - return; - } - if (mode === this.editorInterface.desktopUIMode) { - return; - } - - this.editorInterface = updateObject(this.editorInterface, { - desktopUIMode: mode, - }); - this.persistDesktopUIMode(mode); - this.reconcileStylesPanelMode(this.editorInterface); - }; private excalidrawContainerRef = React.createRef(); @@ -740,7 +699,7 @@ class App extends React.Component { height: window.innerHeight, }; - const storedDesktopUIMode = this.loadDesktopUIModePreference(); + const storedDesktopUIMode = loadDesktopUIModePreference(); const userAgentDescriptor = createUserAgentDescriptor( typeof navigator !== "undefined" ? navigator.userAgent : "", ); @@ -750,9 +709,7 @@ class App extends React.Component { props.UIOptions.desktopUIMode ?? storedDesktopUIMode ?? this.editorInterface.desktopUIMode, - formFactor: - props.UIOptions.formFactor ?? - getFormFactor(this.state.width, this.state.height), + formFactor: this.getFormFactor(), userAgent: userAgentDescriptor, }); this.stylesPanelMode = deriveStylesPanelMode(this.editorInterface); @@ -2496,6 +2453,13 @@ class App extends React.Component { } }; + private getFormFactor = () => { + const { width, height } = this.state; + return this.props.UIOptions.formFactor ?? isTestEnv() + ? "desktop" + : getFormFactor(width, height); + }; + private refreshEditorInterface = () => { const container = this.excalidrawContainerRef.current; if (!container) { @@ -2511,9 +2475,7 @@ class App extends React.Component { : MQ_RIGHT_SIDEBAR_MIN_WIDTH; const nextEditorInterface = updateObject(this.editorInterface, { - formFactor: - this.props.UIOptions.formFactor ?? - getFormFactor(editorWidth, editorHeight), + formFactor: this.getFormFactor(), canFitSidebar: editorWidth > sidebarBreakpoint, isLandscape: editorWidth > editorHeight, }); @@ -2544,6 +2506,14 @@ class App extends React.Component { } }; + private setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => { + const nextMode = setDesktopUIMode(mode); + this.editorInterface = updateObject(this.editorInterface, { + desktopUIMode: nextMode, + }); + this.reconcileStylesPanelMode(this.editorInterface); + }; + private clearImageShapeCache(filesMap?: BinaryFiles) { const files = filesMap ?? this.files; this.scene.getNonDeletedElements().forEach((element) => { diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 127a600c32..9b2c7e9043 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -741,7 +741,6 @@ export type AppClassProperties = { setActiveTool: App["setActiveTool"]; setOpenDialog: App["setOpenDialog"]; insertEmbeddableElement: App["insertEmbeddableElement"]; - setDesktopUIMode: App["setDesktopUIMode"]; onMagicframeToolSelect: App["onMagicframeToolSelect"]; getName: App["getName"]; dismissLinearEditor: App["dismissLinearEditor"];