chore(react/type_widgets): start porting canvas

This commit is contained in:
Elian Doran
2025-09-21 23:32:11 +03:00
parent e1ac319a7b
commit 592a8b2232
5 changed files with 78 additions and 78 deletions

View File

@@ -33,7 +33,8 @@ const TYPE_MAPPINGS: Record<ExtendedNoteType, () => Promise<{ default: TypeWidge
"attachmentList": async () => (await import("./type_widgets/Attachment")).AttachmentList, "attachmentList": async () => (await import("./type_widgets/Attachment")).AttachmentList,
"attachmentDetail": async () => (await import("./type_widgets/Attachment")).AttachmentDetail, "attachmentDetail": async () => (await import("./type_widgets/Attachment")).AttachmentDetail,
"readOnlyText": () => import("./type_widgets/text/ReadOnlyText"), "readOnlyText": () => import("./type_widgets/text/ReadOnlyText"),
"render": () => import("./type_widgets/Render") "render": () => import("./type_widgets/Render"),
"canvas": () => import("./type_widgets/Canvas")
// TODO: finalize the record. // TODO: finalize the record.
}; };

View File

@@ -0,0 +1,34 @@
.excalidraw .App-menu_top .buttonList {
display: flex;
}
/* Conflict between excalidraw and bootstrap classes keeps the menu hidden */
/* https://github.com/zadam/trilium/issues/3780 */
/* https://github.com/excalidraw/excalidraw/issues/6567 */
.excalidraw .dropdown-menu {
display: block;
}
.excalidraw-wrapper {
height: 100%;
}
:root[dir="ltr"]
.excalidraw
.layer-ui__wrapper
.zen-mode-transition.App-menu_bottom--transition-left {
transform: none;
}
/* collaboration not possible so hide the button */
.CollabButton {
display: none !important;
}
.library-button {
display: none !important; /* library won't work without extra support which isn't currently implemented */
}
.note-detail-canvas > .canvas-render {
height: 100%;
}

View File

@@ -0,0 +1,41 @@
import { Excalidraw } from "@excalidraw/excalidraw";
import { TypeWidgetProps } from "./type_widget";
import "@excalidraw/excalidraw/index.css";
import { useNoteBlob } from "../react/hooks";
import { useEffect, useRef } from "preact/hooks";
import type { ExcalidrawImperativeAPI, Theme } from "@excalidraw/excalidraw/types";
import "./Canvas.css";
export default function Canvas({ note }: TypeWidgetProps) {
const apiRef = useRef<ExcalidrawImperativeAPI>(null);
const blob = useNoteBlob(note);
useEffect(() => {
const documentStyle = window.getComputedStyle(document.documentElement);
const themeStyle = documentStyle.getPropertyValue("--theme-style")?.trim() as Theme;
const api = apiRef.current;
const content = blob?.content;
if (!api) return;
if (!content?.trim()) {
api.updateScene({
elements: [],
appState: {
theme: themeStyle
}
});
}
}, [ blob ]);
return (
<div className="canvas-widget note-detail-canvas note-detail-printable note-detail full-height">
<div className="canvas-render">
<div className="excalidraw-wrapper">
<Excalidraw
excalidrawAPI={api => apiRef.current = api}
/>
</div>
</div>
</div>
)
}

View File

@@ -3,55 +3,12 @@ import server from "../../services/server.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import options from "../../services/options.js"; import options from "../../services/options.js";
import type { LibraryItem } from "@excalidraw/excalidraw/types"; import type { LibraryItem } from "@excalidraw/excalidraw/types";
import type { Theme } from "@excalidraw/excalidraw/element/types";
import type Canvas from "./canvas_el.js"; import type Canvas from "./canvas_el.js";
import { CanvasContent } from "./canvas_el.js"; import { CanvasContent } from "./canvas_el.js";
import { renderReactWidget } from "../react/react_utils.jsx"; import { renderReactWidget } from "../react/react_utils.jsx";
import SpacedUpdate from "../../services/spaced_update.js"; import SpacedUpdate from "../../services/spaced_update.js";
import protected_session_holder from "../../services/protected_session_holder.js"; import protected_session_holder from "../../services/protected_session_holder.js";
const TPL = /*html*/`
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
<style>
.excalidraw .App-menu_top .buttonList {
display: flex;
}
/* Conflict between excalidraw and bootstrap classes keeps the menu hidden */
/* https://github.com/zadam/trilium/issues/3780 */
/* https://github.com/excalidraw/excalidraw/issues/6567 */
.excalidraw .dropdown-menu {
display: block;
}
.excalidraw-wrapper {
height: 100%;
}
:root[dir="ltr"]
.excalidraw
.layer-ui__wrapper
.zen-mode-transition.App-menu_bottom--transition-left {
transform: none;
}
/* collaboration not possible so hide the button */
.CollabButton {
display: none !important;
}
.library-button {
display: none !important; /* library won't work without extra support which isn't currently implemented */
}
</style>
<!-- height here necessary. otherwise excalidraw not shown -->
<div class="canvas-render" style="height: 100%"></div>
</div>
`;
interface AttachmentMetadata { interface AttachmentMetadata {
title: string; title: string;
attachmentId: string; attachmentId: string;
@@ -151,10 +108,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
}); });
} }
static getType() {
return "canvas";
}
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$widget.bind("mousewheel DOMMouseScroll", (event) => { this.$widget.bind("mousewheel DOMMouseScroll", (event) => {
@@ -165,10 +118,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
} }
}); });
this.$widget.toggleClass("full-height", true);
this.$render = this.$widget.find(".canvas-render"); this.$render = this.$widget.find(".canvas-render");
const documentStyle = window.getComputedStyle(document.documentElement);
this.themeStyle = documentStyle.getPropertyValue("--theme-style")?.trim() as Theme;
this.#init(); this.#init();
@@ -229,9 +179,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
// get note from backend and put into canvas // get note from backend and put into canvas
const blob = await note.getBlob(); const blob = await note.getBlob();
// before we load content into excalidraw, make sure excalidraw has loaded
await this.canvasInstance.waitForApiToBecomeAvailable();
/** /**
* new and empty note - make sure that canvas is empty. * new and empty note - make sure that canvas is empty.
* If we do not set it manually, we occasionally get some "bleeding" from another * If we do not set it manually, we occasionally get some "bleeding" from another
@@ -239,7 +186,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* newly instantiated? * newly instantiated?
*/ */
if (!blob?.content?.trim()) { if (!blob?.content?.trim()) {
this.canvasInstance.resetScene(this.themeStyle);
} else if (blob.content) { } else if (blob.content) {
let content: CanvasContent; let content: CanvasContent;

View File

@@ -1,4 +1,3 @@
import "@excalidraw/excalidraw/index.css";
import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw"; import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw";
import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types"; import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types";
import { ExcalidrawElement, NonDeletedExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types"; import { ExcalidrawElement, NonDeletedExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types";
@@ -19,7 +18,6 @@ export default class Canvas {
private currentSceneVersion: number; private currentSceneVersion: number;
private opts: ExcalidrawProps; private opts: ExcalidrawProps;
private excalidrawApi!: ExcalidrawImperativeAPI; private excalidrawApi!: ExcalidrawImperativeAPI;
private initializedPromise: JQuery.Deferred<void>;
constructor(opts: ExcalidrawProps) { constructor(opts: ExcalidrawProps) {
this.opts = opts; this.opts = opts;
@@ -27,19 +25,9 @@ export default class Canvas {
this.initializedPromise = $.Deferred(); this.initializedPromise = $.Deferred();
} }
async waitForApiToBecomeAvailable() {
while (!this.excalidrawApi) {
await this.initializedPromise;
}
}
createCanvasElement() { createCanvasElement() {
return <CanvasElement return <CanvasElement
{...this.opts} {...this.opts}
excalidrawAPI={api => {
this.excalidrawApi = api;
this.initializedPromise.resolve();
}}
/> />
} }
@@ -80,15 +68,6 @@ export default class Canvas {
return !!this.excalidrawApi; return !!this.excalidrawApi;
} }
resetScene(theme: Theme) {
this.excalidrawApi.updateScene({
elements: [],
appState: {
theme
}
});
}
loadData(content: CanvasContent, theme: Theme) { loadData(content: CanvasContent, theme: Theme) {
const { elements, files } = content; const { elements, files } = content;
const appState: Partial<AppState> = content.appState ?? {}; const appState: Partial<AppState> = content.appState ?? {};
@@ -178,7 +157,6 @@ export default class Canvas {
function CanvasElement(opts: ExcalidrawProps) { function CanvasElement(opts: ExcalidrawProps) {
return ( return (
<div className="excalidraw-wrapper">
<Excalidraw <Excalidraw
{...opts} {...opts}
onLinkOpen={useCallback((element: NonDeletedExcalidrawElement, event: CustomEvent) => { onLinkOpen={useCallback((element: NonDeletedExcalidrawElement, event: CustomEvent) => {
@@ -196,6 +174,5 @@ function CanvasElement(opts: ExcalidrawProps) {
return linkService.goToLinkExt(nativeEvent, link, null); return linkService.goToLinkExt(nativeEvent, link, null);
}, [])} }, [])}
/> />
</div>
); );
} }