From 319e28387f46cfad9caacac31d1e32a28fe5ef96 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 6 Nov 2025 15:44:09 +0200 Subject: [PATCH] refactor(react/type_widgets): separate persistence from canvas --- apps/client/src/widgets/note_types.tsx | 2 +- .../type_widgets/{ => canvas}/Canvas.css | 0 .../widgets/type_widgets/canvas/Canvas.tsx | 73 ++++++++++++++++ .../{Canvas.tsx => canvas/persistence.ts} | 85 ++----------------- 4 files changed, 83 insertions(+), 77 deletions(-) rename apps/client/src/widgets/type_widgets/{ => canvas}/Canvas.css (100%) create mode 100644 apps/client/src/widgets/type_widgets/canvas/Canvas.tsx rename apps/client/src/widgets/type_widgets/{Canvas.tsx => canvas/persistence.ts} (75%) diff --git a/apps/client/src/widgets/note_types.tsx b/apps/client/src/widgets/note_types.tsx index f5d733052..cb4372c4a 100644 --- a/apps/client/src/widgets/note_types.tsx +++ b/apps/client/src/widgets/note_types.tsx @@ -118,7 +118,7 @@ export const TYPE_MAPPINGS: Record = { printable: true }, canvas: { - view: () => import("./type_widgets/Canvas"), + view: () => import("./type_widgets/canvas/Canvas"), className: "note-detail-canvas", printable: true, isFullHeight: true diff --git a/apps/client/src/widgets/type_widgets/Canvas.css b/apps/client/src/widgets/type_widgets/canvas/Canvas.css similarity index 100% rename from apps/client/src/widgets/type_widgets/Canvas.css rename to apps/client/src/widgets/type_widgets/canvas/Canvas.css diff --git a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx new file mode 100644 index 000000000..79f7f3795 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx @@ -0,0 +1,73 @@ +import { Excalidraw } from "@excalidraw/excalidraw"; +import { TypeWidgetProps } from "../type_widget"; +import "@excalidraw/excalidraw/index.css"; +import { useNoteLabelBoolean } from "../../react/hooks"; +import { useCallback, useMemo, useRef } from "preact/hooks"; +import { type ExcalidrawImperativeAPI, type AppState } from "@excalidraw/excalidraw/types"; +import options from "../../../services/options"; +import "./Canvas.css"; +import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types"; +import { goToLinkExt } from "../../../services/link"; +import useCanvasPersistence from "./persistence"; + +// currently required by excalidraw, in order to allows self-hosting fonts locally. +// this avoids making excalidraw load the fonts from an external CDN. +window.EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`; + +export default function Canvas({ note, noteContext }: TypeWidgetProps) { + const apiRef = useRef(null); + const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const themeStyle = useMemo(() => { + const documentStyle = window.getComputedStyle(document.documentElement); + return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"]; + }, []); + const persistence = useCanvasPersistence(note, noteContext, apiRef, themeStyle, isReadOnly); + + /** Use excalidraw's native zoom instead of the global zoom. */ + const onWheel = useCallback((e: MouseEvent) => { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + } + }, []); + + const onLinkOpen = useCallback((element: NonDeletedExcalidrawElement, event: CustomEvent) => { + let link = element.link; + if (!link) { + return false; + } + + if (link.startsWith("root/")) { + link = "#" + link; + } + + const { nativeEvent } = event.detail; + event.preventDefault(); + return goToLinkExt(nativeEvent, link, null); + }, []); + + return ( +
+
+ apiRef.current = api} + theme={themeStyle} + viewModeEnabled={isReadOnly || options.is("databaseReadonly")} + zenModeEnabled={false} + isCollaborating={false} + detectScroll={false} + handleKeyboardGlobally={false} + autoFocus={false} + UIOptions={{ + canvasActions: { + saveToActiveFile: false, + export: false + } + }} + onLinkOpen={onLinkOpen} + {...persistence} + /> +
+
+ ) +} diff --git a/apps/client/src/widgets/type_widgets/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/persistence.ts similarity index 75% rename from apps/client/src/widgets/type_widgets/Canvas.tsx rename to apps/client/src/widgets/type_widgets/canvas/persistence.ts index 7644c46d1..17dad2cfb 100644 --- a/apps/client/src/widgets/type_widgets/Canvas.tsx +++ b/apps/client/src/widgets/type_widgets/canvas/persistence.ts @@ -1,92 +1,25 @@ -import { Excalidraw, exportToSvg, getSceneVersion } from "@excalidraw/excalidraw"; -import { TypeWidgetProps } from "./type_widget"; -import "@excalidraw/excalidraw/index.css"; -import { useEditorSpacedUpdate, useNoteLabelBoolean } from "../react/hooks"; -import { useCallback, useMemo, useRef } from "preact/hooks"; -import { type ExcalidrawImperativeAPI, type AppState, type BinaryFileData, LibraryItem, ExcalidrawProps } from "@excalidraw/excalidraw/types"; -import options from "../../services/options"; -import "./Canvas.css"; -import FNote from "../../entities/fnote"; import { RefObject } from "preact"; -import server from "../../services/server"; +import NoteContext from "../../../components/note_context"; +import FNote from "../../../entities/fnote"; +import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types"; +import { useRef } from "preact/hooks"; +import { useEditorSpacedUpdate } from "../../react/hooks"; import { ExcalidrawElement, NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types"; -import { goToLinkExt } from "../../services/link"; -import NoteContext from "../../components/note_context"; - -// currently required by excalidraw, in order to allows self-hosting fonts locally. -// this avoids making excalidraw load the fonts from an external CDN. -window.EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`; +import { exportToSvg, getSceneVersion } from "@excalidraw/excalidraw"; +import server from "../../../services/server"; interface AttachmentMetadata { title: string; attachmentId: string; } -interface CanvasContent { +export interface CanvasContent { elements: ExcalidrawElement[]; files: BinaryFileData[]; appState: Partial; } -export default function Canvas({ note, noteContext }: TypeWidgetProps) { - const apiRef = useRef(null); - const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); - const themeStyle = useMemo(() => { - const documentStyle = window.getComputedStyle(document.documentElement); - return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"]; - }, []); - const persistence = usePersistence(note, noteContext, apiRef, themeStyle, isReadOnly); - - /** Use excalidraw's native zoom instead of the global zoom. */ - const onWheel = useCallback((e: MouseEvent) => { - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - } - }, []); - - const onLinkOpen = useCallback((element: NonDeletedExcalidrawElement, event: CustomEvent) => { - let link = element.link; - if (!link) { - return false; - } - - if (link.startsWith("root/")) { - link = "#" + link; - } - - const { nativeEvent } = event.detail; - event.preventDefault(); - return goToLinkExt(nativeEvent, link, null); - }, []); - - return ( -
-
- apiRef.current = api} - theme={themeStyle} - viewModeEnabled={isReadOnly || options.is("databaseReadonly")} - zenModeEnabled={false} - isCollaborating={false} - detectScroll={false} - handleKeyboardGlobally={false} - autoFocus={false} - UIOptions={{ - canvasActions: { - saveToActiveFile: false, - export: false - } - }} - onLinkOpen={onLinkOpen} - {...persistence} - /> -
-
- ) -} - -function usePersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject, theme: AppState["theme"], isReadOnly: boolean): Partial { +export default function useCanvasPersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject, theme: AppState["theme"], isReadOnly: boolean): Partial { const libraryChanged = useRef(false); /**