From 44b92a024ca112ef995632567e2847e575658d58 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 22 Sep 2025 10:14:24 +0300 Subject: [PATCH] chore(react/type_widget): set up self-hosted fonts --- .../src/widgets/type_widgets/Canvas.tsx | 18 ++-- .../src/widgets/type_widgets_old/canvas.ts | 89 ------------------- apps/server/src/routes/assets.ts | 4 +- 3 files changed, 14 insertions(+), 97 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/Canvas.tsx b/apps/client/src/widgets/type_widgets/Canvas.tsx index 975b78a19..349b965be 100644 --- a/apps/client/src/widgets/type_widgets/Canvas.tsx +++ b/apps/client/src/widgets/type_widgets/Canvas.tsx @@ -1,8 +1,8 @@ import { Excalidraw, exportToSvg, getSceneVersion } from "@excalidraw/excalidraw"; import { TypeWidgetProps } from "./type_widget"; import "@excalidraw/excalidraw/index.css"; -import { useEditorSpacedUpdate, useNoteBlob } from "../react/hooks"; -import { useEffect, useMemo, useRef } from "preact/hooks"; +import { useEditorSpacedUpdate } from "../react/hooks"; +import { 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"; @@ -12,6 +12,10 @@ import server from "../../services/server"; import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types"; import { CanvasContent } from "../type_widgets_old/canvas_el"; +// 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 { title: string; attachmentId: string; @@ -19,12 +23,12 @@ interface AttachmentMetadata { export default function Canvas({ note }: TypeWidgetProps) { const apiRef = useRef(null); - const viewModeEnabled = options.is("databaseReadonly"); + const isReadOnly = options.is("databaseReadonly"); const themeStyle = useMemo(() => { const documentStyle = window.getComputedStyle(document.documentElement); return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"]; }, []); - const persistence = usePersistence(note, apiRef, themeStyle); + const persistence = usePersistence(note, apiRef, themeStyle, isReadOnly); return (
@@ -33,7 +37,7 @@ export default function Canvas({ note }: TypeWidgetProps) { apiRef.current = api} theme={themeStyle} - viewModeEnabled={viewModeEnabled} + viewModeEnabled={isReadOnly} zenModeEnabled={false} isCollaborating={false} detectScroll={false} @@ -53,7 +57,7 @@ export default function Canvas({ note }: TypeWidgetProps) { ) } -function usePersistence(note: FNote, apiRef: RefObject, theme: AppState["theme"]): Partial { +function usePersistence(note: FNote, apiRef: RefObject, theme: AppState["theme"], isReadOnly: boolean): Partial { const libraryChanged = useRef(false); const currentSceneVersion = useRef(0); @@ -169,7 +173,7 @@ function usePersistence(note: FNote, apiRef: RefObject, return { onChange: () => { - if (!apiRef.current) return; + if (!apiRef.current || isReadOnly) return; const oldSceneVersion = currentSceneVersion.current; const newSceneVersion = getSceneVersion(apiRef.current.getSceneElements()); diff --git a/apps/client/src/widgets/type_widgets_old/canvas.ts b/apps/client/src/widgets/type_widgets_old/canvas.ts index bd630fa6d..442d72693 100644 --- a/apps/client/src/widgets/type_widgets_old/canvas.ts +++ b/apps/client/src/widgets/type_widgets_old/canvas.ts @@ -113,93 +113,4 @@ export default class ExcalidrawTypeWidget extends TypeWidget { return this.$widget; } - async #init() { - const renderElement = this.$render.get(0); - if (!renderElement) { - throw new Error("Unable to find element to render."); - } - - const Canvas = (await import("./canvas_el.js")).default; - this.canvasInstance = new Canvas({ - // this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu - onChange: () => this.onChangeHandler(), - onLibraryChange: () => { - this.libraryChanged = true; - - this.saveData(); - }, - }); - - await setupFonts(); - const canvasEl = renderReactWidget(this, this.canvasInstance.createCanvasElement())[0]; - renderElement.replaceChildren(canvasEl); - } - - /** - * called to populate the widget container with the note content - */ - async doRefresh(note: FNote) { - if (!this.canvasInstance) { - await this.#init(); - } - - this.currentNoteId = note.noteId; - - // get note from backend and put into canvas - const blob = await note.getBlob(); - } - - /** - * save content to backend - */ - saveData() { - // Since Excalidraw sends an enormous amount of events, wait for them to stop before actually saving. - this.spacedUpdate.resetUpdateTimer(); - this.spacedUpdate.scheduleUpdate(); - } - - onChangeHandler() { - if (options.is("databaseReadonly")) { - return; - } - - if (!this.canvasInstance.isInitialized()) return; - - // changeHandler is called upon any tiny change in excalidraw. button clicked, hover, etc. - // make sure only when a new element is added, we actually save something. - const isNewSceneVersion = this.canvasInstance.isNewSceneVersion(); - /** - * FIXME: however, we might want to make an exception, if viewport changed, since viewport - * is desired to save? (add) and appState background, and some things - */ - - // upon updateScene, onchange is called, even though "nothing really changed" that is worth saving - const isNotInitialScene = !this.canvasInstance.isInitialScene(); - const shouldSave = isNewSceneVersion && isNotInitialScene; - - if (shouldSave) { - this.canvasInstance.updateSceneVersion(); - this.saveData(); - } - } - -} - -async function setupFonts() { - if (window.EXCALIDRAW_ASSET_PATH) { - return; - } - - // currently required by excalidraw, in order to allows self-hosting fonts locally. - // this avoids making excalidraw load the fonts from an external CDN. - let path: string; - if (!glob.isDev) { - path = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`; - } else { - path = (await import("../../../../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2?url")).default; - let pathComponents = path.split("/"); - path = pathComponents.slice(0, pathComponents.length - 2).join("/"); - } - - window.EXCALIDRAW_ASSET_PATH = path; } diff --git a/apps/server/src/routes/assets.ts b/apps/server/src/routes/assets.ts index a1a2bfb63..a4096d6ad 100644 --- a/apps/server/src/routes/assets.ts +++ b/apps/server/src/routes/assets.ts @@ -21,17 +21,19 @@ async function register(app: express.Application) { if (process.env.NODE_ENV === "development") { const { createServer: createViteServer } = await import("vite"); + const clientDir = path.join(srcRoot, "../client"); const vite = await createViteServer({ server: { middlewareMode: true }, appType: "custom", cacheDir: path.join(srcRoot, "../../.cache/vite"), base: `/${assetUrlFragment}/`, - root: path.join(srcRoot, "../client") + root: clientDir }); app.use(`/${assetUrlFragment}/`, (req, res, next) => { req.url = `/${assetUrlFragment}` + req.url; vite.middlewares(req, res, next); }); + app.use(`/node_modules/@excalidraw/excalidraw/dist/prod`, persistentCacheStatic(path.join(srcRoot, "../../node_modules/@excalidraw/excalidraw/dist/prod"))); } else { const publicDir = path.join(resourceDir, "public"); if (!existsSync(publicDir)) {