refactor(canvas): don't expose API directly

This commit is contained in:
Elian Doran 2025-06-11 13:32:17 +03:00
parent dab9b02990
commit 0da05a7dbe
No known key found for this signature in database
2 changed files with 46 additions and 32 deletions

View File

@ -1,9 +1,8 @@
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import utils from "../../services/utils.js";
import server from "../../services/server.js"; 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 { AppState, BinaryFileData, ExcalidrawImperativeAPI, LibraryItem, SceneData } from "@excalidraw/excalidraw/types"; import type { AppState, BinaryFileData, LibraryItem } from "@excalidraw/excalidraw/types";
import type { ExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types"; import type { ExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types";
import type Canvas from "./canvas_el.js"; import type Canvas from "./canvas_el.js";
@ -109,8 +108,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
private librarycache: LibraryItem[]; private librarycache: LibraryItem[];
private attachmentMetadata: AttachmentMetadata[]; private attachmentMetadata: AttachmentMetadata[];
private themeStyle!: Theme; private themeStyle!: Theme;
private excalidrawApi!: ExcalidrawImperativeAPI;
private excalidrawWrapperRef!: React.RefObject<HTMLElement | null>;
private $render!: JQuery<HTMLElement>; private $render!: JQuery<HTMLElement>;
private reactHandlers!: JQuery<HTMLElement>; private reactHandlers!: JQuery<HTMLElement>;
@ -209,11 +206,15 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* called to populate the widget container with the note content * called to populate the widget container with the note content
*/ */
async doRefresh(note: FNote) { async doRefresh(note: FNote) {
if (!this.canvasInstance) {
await this.#init();
}
// see if the note changed, since we do not get a new class for a new note // see if the note changed, since we do not get a new class for a new note
const noteChanged = this.currentNoteId !== note.noteId; const noteChanged = this.currentNoteId !== note.noteId;
if (noteChanged) { if (noteChanged) {
// reset the scene to omit unnecessary onchange handler // reset the scene to omit unnecessary onchange handler
this.canvasInstance?.resetSceneVersion(); this.canvasInstance.resetSceneVersion();
} }
this.currentNoteId = note.noteId; this.currentNoteId = note.noteId;
@ -221,10 +222,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
const blob = await note.getBlob(); const blob = await note.getBlob();
// before we load content into excalidraw, make sure excalidraw has loaded // before we load content into excalidraw, make sure excalidraw has loaded
while (!this.canvasInstance?.excalidrawApi) { await this.canvasInstance.waitForApiToBecomeAvailable();
console.log("excalidrawApi not yet loaded, sleep 200ms...");
await utils.sleep(200);
}
/** /**
* new and empty note - make sure that canvas is empty. * new and empty note - make sure that canvas is empty.
@ -233,15 +231,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* newly instantiated? * newly instantiated?
*/ */
if (!blob?.content?.trim()) { if (!blob?.content?.trim()) {
const sceneData: SceneData = { this.canvasInstance.resetScene(this.themeStyle);
elements: [],
appState: {
theme: this.themeStyle
}
};
// TODO: Props mismatch.
this.canvasInstance.excalidrawApi.updateScene(sceneData as any);
} else if (blob.content) { } else if (blob.content) {
let content: CanvasContent; let content: CanvasContent;
@ -285,7 +275,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
const metadata = results.map((result) => result.metadata); const metadata = results.map((result) => result.metadata);
// Update the library and save to independent variables // Update the library and save to independent variables
this.canvasInstance.excalidrawApi.updateLibrary({ libraryItems, merge: false }); this.canvasInstance.updateLibrary(libraryItems);
// save state of library to compare it to the new state later. // save state of library to compare it to the new state later.
this.librarycache = libraryItems; this.librarycache = libraryItems;
@ -313,12 +303,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
// this.libraryChanged is unset in dataSaved() // this.libraryChanged is unset in dataSaved()
// there's no separate method to get library items, so have to abuse this one // there's no separate method to get library items, so have to abuse this one
const libraryItems = await this.canvasInstance.excalidrawApi.updateLibrary({ const libraryItems = await this.canvasInstance.getLibraryItems();
libraryItems() {
return [];
},
merge: true
});
// excalidraw saves the library as a own state. the items are saved to libraryItems. then we compare the library right now with a libraryitemcache. The cache is filled when we first load the Library into the note. // excalidraw saves the library as a own state. the items are saved to libraryItems. then we compare the library right now with a libraryitemcache. The cache is filled when we first load the Library into the note.
//We need the cache to delete old attachments later in the server. //We need the cache to delete old attachments later in the server.

View File

@ -1,19 +1,19 @@
import "@excalidraw/excalidraw/index.css"; import "@excalidraw/excalidraw/index.css";
import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw"; import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw";
import { createElement, createRef, Fragment, RefObject, render, useEffect, useState } from "preact/compat"; import { createElement, render } from "preact/compat";
import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, SceneData } from "@excalidraw/excalidraw/types"; import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem, SceneData } from "@excalidraw/excalidraw/types";
import type { ComponentType, VNode } from "preact"; import type { ComponentType } from "preact";
import utils from "../../services/utils";
import { Theme } from "@excalidraw/excalidraw/element/types";
/** -1 indicates that it is fresh. excalidraw scene version is always >0 */ /** Indicates that it is fresh. excalidraw scene version is always >0 */
const SCENE_VERSION_INITIAL = -1; const SCENE_VERSION_INITIAL = -1;
/** -2 indicates error */
const SCENE_VERSION_ERROR = -2;
export default class Canvas { export default class Canvas {
private currentSceneVersion: number; private currentSceneVersion: number;
private opts: ExcalidrawProps; private opts: ExcalidrawProps;
excalidrawApi!: ExcalidrawImperativeAPI; private excalidrawApi!: ExcalidrawImperativeAPI;
constructor(opts: ExcalidrawProps) { constructor(opts: ExcalidrawProps) {
this.opts = opts; this.opts = opts;
@ -29,6 +29,13 @@ export default class Canvas {
}), targetEl); }), targetEl);
} }
async waitForApiToBecomeAvailable() {
while (!this.excalidrawApi) {
console.log("excalidrawApi not yet loaded, sleep 200ms...");
await utils.sleep(200);
}
}
private createCanvasElement(opts: ExcalidrawProps) { private createCanvasElement(opts: ExcalidrawProps) {
return createElement("div", { className: "excalidraw-wrapper", }, return createElement("div", { className: "excalidraw-wrapper", },
createElement(Excalidraw as ComponentType<ExcalidrawProps>, opts) createElement(Excalidraw as ComponentType<ExcalidrawProps>, opts)
@ -68,6 +75,15 @@ export default class Canvas {
return this.currentSceneVersion === SCENE_VERSION_INITIAL; return this.currentSceneVersion === SCENE_VERSION_INITIAL;
} }
resetScene(theme: Theme) {
this.excalidrawApi.updateScene({
elements: [],
appState: {
theme
}
});
}
loadData(content: any, theme: any) { loadData(content: any, theme: any) {
const { elements, files } = content; const { elements, files } = content;
const appState: Partial<AppState> = content.appState ?? {}; const appState: Partial<AppState> = content.appState ?? {};
@ -143,4 +159,17 @@ export default class Canvas {
} }
} }
async getLibraryItems() {
return this.excalidrawApi.updateLibrary({
libraryItems() {
return [];
},
merge: true
});
}
async updateLibrary(libraryItems: LibraryItem[]) {
this.excalidrawApi.updateLibrary({ libraryItems, merge: false });
}
} }