mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
chore(canvas): bring back scene API
This commit is contained in:
parent
5ad3d7d077
commit
dd58685455
@ -4,6 +4,7 @@ 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 { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
import { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
|
||||||
|
import type Canvas from "./canvas_el.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
|
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
|
||||||
@ -101,11 +102,8 @@ interface AttachmentMetadata {
|
|||||||
*/
|
*/
|
||||||
export default class ExcalidrawTypeWidget extends TypeWidget {
|
export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||||
|
|
||||||
private readonly SCENE_VERSION_INITIAL: number;
|
|
||||||
private readonly SCENE_VERSION_ERROR: number;
|
|
||||||
|
|
||||||
private currentNoteId: string;
|
private currentNoteId: string;
|
||||||
private currentSceneVersion: number;
|
|
||||||
private libraryChanged: boolean;
|
private libraryChanged: boolean;
|
||||||
private librarycache: LibraryItem[];
|
private librarycache: LibraryItem[];
|
||||||
private attachmentMetadata: AttachmentMetadata[];
|
private attachmentMetadata: AttachmentMetadata[];
|
||||||
@ -116,21 +114,17 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
private $render!: JQuery<HTMLElement>;
|
private $render!: JQuery<HTMLElement>;
|
||||||
private reactHandlers!: JQuery<HTMLElement>;
|
private reactHandlers!: JQuery<HTMLElement>;
|
||||||
|
private canvasInstance!: Canvas;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// constants
|
|
||||||
this.SCENE_VERSION_INITIAL = -1; // -1 indicates that it is fresh. excalidraw scene version is always >0
|
|
||||||
this.SCENE_VERSION_ERROR = -2; // -2 indicates error
|
|
||||||
|
|
||||||
// currently required by excalidraw, in order to allows self-hosting fonts locally.
|
// currently required by excalidraw, in order to allows self-hosting fonts locally.
|
||||||
// this avoids making excalidraw load the fonts from an external CDN.
|
// this avoids making excalidraw load the fonts from an external CDN.
|
||||||
(window as any).EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`;
|
(window as any).EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`;
|
||||||
|
|
||||||
// temporary vars
|
// temporary vars
|
||||||
this.currentNoteId = "";
|
this.currentNoteId = "";
|
||||||
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
|
||||||
|
|
||||||
// will be overwritten
|
// will be overwritten
|
||||||
this.$render;
|
this.$render;
|
||||||
@ -184,12 +178,11 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
(window.process.env as any).PREACT = false;
|
(window.process.env as any).PREACT = false;
|
||||||
|
|
||||||
const renderCanvas = (await import("./canvas_el.js")).default;
|
const Canvas = (await import("./canvas_el.js")).default;
|
||||||
renderCanvas(renderElement, {
|
this.canvasInstance = new Canvas({
|
||||||
excalidrawAPI: (api: ExcalidrawImperativeAPI) => {
|
|
||||||
this.excalidrawApi = api;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
this.canvasInstance.renderCanvas(renderElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,7 +193,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
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.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
this.canvasInstance?.resetSceneVersion();
|
||||||
}
|
}
|
||||||
this.currentNoteId = note.noteId;
|
this.currentNoteId = note.noteId;
|
||||||
|
|
||||||
@ -208,7 +201,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.excalidrawApi) {
|
while (!this.canvasInstance?.excalidrawApi) {
|
||||||
console.log("excalidrawApi not yet loaded, sleep 200ms...");
|
console.log("excalidrawApi not yet loaded, sleep 200ms...");
|
||||||
await utils.sleep(200);
|
await utils.sleep(200);
|
||||||
}
|
}
|
||||||
@ -228,7 +221,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Props mismatch.
|
// TODO: Props mismatch.
|
||||||
this.excalidrawApi.updateScene(sceneData as any);
|
this.canvasInstance.excalidrawApi.updateScene(sceneData as any);
|
||||||
} else if (blob.content) {
|
} else if (blob.content) {
|
||||||
let content: CanvasContent;
|
let content: CanvasContent;
|
||||||
|
|
||||||
@ -301,7 +294,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.excalidrawApi.updateLibrary({ libraryItems, merge: false });
|
this.canvasInstance.excalidrawApi.updateLibrary({ libraryItems, merge: false });
|
||||||
|
|
||||||
// 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;
|
||||||
@ -310,14 +303,14 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
// Update the scene
|
// Update the scene
|
||||||
// TODO: Fix type of sceneData
|
// TODO: Fix type of sceneData
|
||||||
this.excalidrawApi.updateScene(sceneData as any);
|
this.canvasInstance.excalidrawApi.updateScene(sceneData as any);
|
||||||
this.excalidrawApi.addFiles(fileArray);
|
this.canvasInstance.excalidrawApi.addFiles(fileArray);
|
||||||
this.excalidrawApi.history.clear();
|
this.canvasInstance.excalidrawApi.history.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set initial scene version
|
// set initial scene version
|
||||||
if (this.currentSceneVersion === this.SCENE_VERSION_INITIAL) {
|
if (this.canvasInstance.isInitialScene()) {
|
||||||
this.currentSceneVersion = this.getSceneVersion();
|
this.canvasInstance.updateSceneVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,14 +319,14 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
* this is automatically called after this.saveData();
|
* this is automatically called after this.saveData();
|
||||||
*/
|
*/
|
||||||
async getData() {
|
async getData() {
|
||||||
const elements = this.excalidrawApi.getSceneElements();
|
const elements = this.canvasInstance.excalidrawApi.getSceneElements();
|
||||||
const appState = this.excalidrawApi.getAppState();
|
const appState = this.canvasInstance.excalidrawApi.getAppState();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A file is not deleted, even though removed from canvas. Therefore, we only keep
|
* A file is not deleted, even though removed from canvas. Therefore, we only keep
|
||||||
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
|
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
|
||||||
*/
|
*/
|
||||||
const files = this.excalidrawApi.getFiles();
|
const files = this.canvasInstance.excalidrawApi.getFiles();
|
||||||
|
|
||||||
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
||||||
const svg = await this.excalidrawLib.exportToSvg({
|
const svg = await this.excalidrawLib.exportToSvg({
|
||||||
@ -370,7 +363,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.excalidrawApi.updateLibrary({
|
const libraryItems = await this.canvasInstance.excalidrawApi.updateLibrary({
|
||||||
libraryItems() {
|
libraryItems() {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
@ -444,53 +437,20 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
// changeHandler is called upon any tiny change in excalidraw. button clicked, hover, etc.
|
// 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.
|
// make sure only when a new element is added, we actually save something.
|
||||||
const isNewSceneVersion = this.isNewSceneVersion();
|
const isNewSceneVersion = this.canvasInstance.isNewSceneVersion();
|
||||||
/**
|
/**
|
||||||
* FIXME: however, we might want to make an exception, if viewport changed, since viewport
|
* 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
|
* 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
|
// upon updateScene, onchange is called, even though "nothing really changed" that is worth saving
|
||||||
const isNotInitialScene = this.currentSceneVersion !== this.SCENE_VERSION_INITIAL;
|
const isNotInitialScene = !this.canvasInstance.isInitialScene();
|
||||||
|
|
||||||
const shouldSave = isNewSceneVersion && isNotInitialScene;
|
const shouldSave = isNewSceneVersion && isNotInitialScene;
|
||||||
|
|
||||||
if (shouldSave) {
|
if (shouldSave) {
|
||||||
this.updateSceneVersion();
|
this.canvasInstance.updateSceneVersion();
|
||||||
this.saveData();
|
this.saveData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* needed to ensure, that multipleOnChangeHandler calls do not trigger a save.
|
|
||||||
* we compare the scene version as suggested in:
|
|
||||||
* https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329
|
|
||||||
*
|
|
||||||
* info: sceneVersions are not incrementing. it seems to be a pseudo-random number
|
|
||||||
*/
|
|
||||||
isNewSceneVersion() {
|
|
||||||
if (options.is("databaseReadonly")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sceneVersion = this.getSceneVersion();
|
|
||||||
|
|
||||||
return (
|
|
||||||
this.currentSceneVersion === this.SCENE_VERSION_INITIAL || // initial scene version update
|
|
||||||
this.currentSceneVersion !== sceneVersion
|
|
||||||
); // ensure scene changed
|
|
||||||
}
|
|
||||||
|
|
||||||
getSceneVersion() {
|
|
||||||
if (this.excalidrawApi) {
|
|
||||||
const elements = this.excalidrawApi.getSceneElements();
|
|
||||||
return this.excalidrawLib.getSceneVersion(elements);
|
|
||||||
} else {
|
|
||||||
return this.SCENE_VERSION_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSceneVersion() {
|
|
||||||
this.currentSceneVersion = this.getSceneVersion();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,66 @@
|
|||||||
import "@excalidraw/excalidraw/index.css";
|
import "@excalidraw/excalidraw/index.css";
|
||||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw";
|
||||||
import { createElement, createRef, Fragment, render } from "preact/compat";
|
import { createElement, createRef, Fragment, render } from "preact/compat";
|
||||||
import { ExcalidrawProps } from "@excalidraw/excalidraw/types";
|
import { ExcalidrawImperativeAPI, ExcalidrawProps } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
|
/** -1 indicates that it is fresh. excalidraw scene version is always >0 */
|
||||||
|
const SCENE_VERSION_INITIAL = -1;
|
||||||
|
/** -2 indicates error */
|
||||||
|
const SCENE_VERSION_ERROR = -2;
|
||||||
|
|
||||||
|
export default class Canvas {
|
||||||
|
|
||||||
|
private currentSceneVersion: number;
|
||||||
|
private opts: ExcalidrawProps;
|
||||||
|
excalidrawApi!: ExcalidrawImperativeAPI;
|
||||||
|
|
||||||
|
constructor(opts: ExcalidrawProps) {
|
||||||
|
this.opts = opts;
|
||||||
|
this.currentSceneVersion = SCENE_VERSION_INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCanvas(targetEl: HTMLElement) {
|
||||||
|
render(createCanvasElement({
|
||||||
|
...this.opts,
|
||||||
|
excalidrawAPI: (api: ExcalidrawImperativeAPI) => {
|
||||||
|
this.excalidrawApi = api;
|
||||||
|
},
|
||||||
|
}), targetEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* needed to ensure, that multipleOnChangeHandler calls do not trigger a save.
|
||||||
|
* we compare the scene version as suggested in:
|
||||||
|
* https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329
|
||||||
|
*
|
||||||
|
* info: sceneVersions are not incrementing. it seems to be a pseudo-random number
|
||||||
|
*/
|
||||||
|
isNewSceneVersion() {
|
||||||
|
const sceneVersion = this.getSceneVersion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.currentSceneVersion === SCENE_VERSION_INITIAL || // initial scene version update
|
||||||
|
this.currentSceneVersion !== sceneVersion
|
||||||
|
); // ensure scene changed
|
||||||
|
}
|
||||||
|
|
||||||
|
getSceneVersion() {
|
||||||
|
const elements = this.excalidrawApi.getSceneElements();
|
||||||
|
return getSceneVersion(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSceneVersion() {
|
||||||
|
this.currentSceneVersion = this.getSceneVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSceneVersion() {
|
||||||
|
this.currentSceneVersion = SCENE_VERSION_INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialScene() {
|
||||||
|
return this.currentSceneVersion === SCENE_VERSION_INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
export default function renderCanvas(targetEl: HTMLElement, opts: ExcalidrawProps) {
|
|
||||||
render(createCanvasElement(opts), targetEl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCanvasElement(opts: ExcalidrawProps) {
|
function createCanvasElement(opts: ExcalidrawProps) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user