refactor(react/type_widgets): separate persistence from canvas
Some checks failed
Checks / main (push) Has been cancelled

This commit is contained in:
Elian Doran 2025-11-06 15:44:09 +02:00
parent 0b740bb007
commit 319e28387f
No known key found for this signature in database
4 changed files with 83 additions and 77 deletions

View File

@ -118,7 +118,7 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
printable: true printable: true
}, },
canvas: { canvas: {
view: () => import("./type_widgets/Canvas"), view: () => import("./type_widgets/canvas/Canvas"),
className: "note-detail-canvas", className: "note-detail-canvas",
printable: true, printable: true,
isFullHeight: true isFullHeight: true

View File

@ -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<ExcalidrawImperativeAPI>(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 (
<div className="canvas-render" onWheel={onWheel}>
<div className="excalidraw-wrapper">
<Excalidraw
excalidrawAPI={api => 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}
/>
</div>
</div>
)
}

View File

@ -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 { 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 { ExcalidrawElement, NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import { goToLinkExt } from "../../services/link"; import { exportToSvg, getSceneVersion } from "@excalidraw/excalidraw";
import NoteContext from "../../components/note_context"; import server from "../../../services/server";
// 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`;
interface AttachmentMetadata { interface AttachmentMetadata {
title: string; title: string;
attachmentId: string; attachmentId: string;
} }
interface CanvasContent { export interface CanvasContent {
elements: ExcalidrawElement[]; elements: ExcalidrawElement[];
files: BinaryFileData[]; files: BinaryFileData[];
appState: Partial<AppState>; appState: Partial<AppState>;
} }
export default function Canvas({ note, noteContext }: TypeWidgetProps) { export default function useCanvasPersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
const apiRef = useRef<ExcalidrawImperativeAPI>(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 (
<div className="canvas-render" onWheel={onWheel}>
<div className="excalidraw-wrapper">
<Excalidraw
excalidrawAPI={api => 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}
/>
</div>
</div>
)
}
function usePersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
const libraryChanged = useRef(false); const libraryChanged = useRef(false);
/** /**