mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
chore(canvas): bring back load/save
This commit is contained in:
parent
dd58685455
commit
ddbd268a66
@ -108,7 +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 excalidrawLib!: typeof import("@excalidraw/excalidraw");
|
|
||||||
private excalidrawApi!: ExcalidrawImperativeAPI;
|
private excalidrawApi!: ExcalidrawImperativeAPI;
|
||||||
private excalidrawWrapperRef!: React.RefObject<HTMLElement | null>;
|
private excalidrawWrapperRef!: React.RefObject<HTMLElement | null>;
|
||||||
|
|
||||||
@ -180,7 +179,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
const Canvas = (await import("./canvas_el.js")).default;
|
const Canvas = (await import("./canvas_el.js")).default;
|
||||||
this.canvasInstance = new Canvas({
|
this.canvasInstance = new Canvas({
|
||||||
|
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
|
||||||
|
theme: this.themeStyle,
|
||||||
|
onChange: () => this.onChangeHandler(),
|
||||||
|
viewModeEnabled: options.is("databaseReadonly"),
|
||||||
});
|
});
|
||||||
this.canvasInstance.renderCanvas(renderElement);
|
this.canvasInstance.renderCanvas(renderElement);
|
||||||
}
|
}
|
||||||
@ -238,36 +240,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { elements, files } = content;
|
this.canvasInstance.loadData(content, this.themeStyle);
|
||||||
const appState: Partial<AppState> = content.appState ?? {};
|
|
||||||
|
|
||||||
appState.theme = this.themeStyle;
|
|
||||||
|
|
||||||
if (this.excalidrawWrapperRef.current) {
|
|
||||||
const boundingClientRect = this.excalidrawWrapperRef.current.getBoundingClientRect();
|
|
||||||
appState.width = boundingClientRect.width;
|
|
||||||
appState.height = boundingClientRect.height;
|
|
||||||
appState.offsetLeft = boundingClientRect.left;
|
|
||||||
appState.offsetTop = boundingClientRect.top;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sceneData: SceneData = {
|
|
||||||
elements,
|
|
||||||
appState
|
|
||||||
};
|
|
||||||
|
|
||||||
// files are expected in an array when loading. they are stored as a key-index object
|
|
||||||
// see example for loading here:
|
|
||||||
// https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68
|
|
||||||
const fileArray: BinaryFileData[] = [];
|
|
||||||
for (const fileId in files) {
|
|
||||||
const file = files[fileId];
|
|
||||||
// TODO: dataURL is replaceable with a trilium image url
|
|
||||||
// maybe we can save normal images (pasted) with base64 data url, and trilium images
|
|
||||||
// with their respective url! nice
|
|
||||||
// file.dataURL = "http://localhost:8080/api/images/ltjOiU8nwoZx/start.png";
|
|
||||||
fileArray.push(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
(await note.getAttachmentsByRole("canvasLibraryItem")).map(async (attachment) => {
|
(await note.getAttachmentsByRole("canvasLibraryItem")).map(async (attachment) => {
|
||||||
@ -301,11 +274,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
this.attachmentMetadata = metadata;
|
this.attachmentMetadata = metadata;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the scene
|
|
||||||
// TODO: Fix type of sceneData
|
|
||||||
this.canvasInstance.excalidrawApi.updateScene(sceneData as any);
|
|
||||||
this.canvasInstance.excalidrawApi.addFiles(fileArray);
|
|
||||||
this.canvasInstance.excalidrawApi.history.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set initial scene version
|
// set initial scene version
|
||||||
@ -319,45 +288,8 @@ 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.canvasInstance.excalidrawApi.getSceneElements();
|
const { content, svg } = await this.canvasInstance.getData();
|
||||||
const appState = this.canvasInstance.excalidrawApi.getAppState();
|
const attachments = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svg, position: 0 }];
|
||||||
|
|
||||||
/**
|
|
||||||
* 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?
|
|
||||||
*/
|
|
||||||
const files = this.canvasInstance.excalidrawApi.getFiles();
|
|
||||||
|
|
||||||
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
|
||||||
const svg = await this.excalidrawLib.exportToSvg({
|
|
||||||
elements,
|
|
||||||
appState,
|
|
||||||
exportPadding: 5, // 5 px padding
|
|
||||||
files
|
|
||||||
});
|
|
||||||
const svgString = svg.outerHTML;
|
|
||||||
|
|
||||||
const activeFiles: Record<string, BinaryFileData> = {};
|
|
||||||
// TODO: Used any where upstream typings appear to be broken.
|
|
||||||
elements.forEach((element: any) => {
|
|
||||||
if ("fileId" in element && element.fileId) {
|
|
||||||
activeFiles[element.fileId] = files[element.fileId];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = {
|
|
||||||
type: "excalidraw",
|
|
||||||
version: 2,
|
|
||||||
elements,
|
|
||||||
files: activeFiles,
|
|
||||||
appState: {
|
|
||||||
scrollX: appState.scrollX,
|
|
||||||
scrollY: appState.scrollY,
|
|
||||||
zoom: appState.zoom
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const attachments = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svgString, position: 0 }];
|
|
||||||
|
|
||||||
if (this.libraryChanged) {
|
if (this.libraryChanged) {
|
||||||
// this.libraryChanged is unset in dataSaved()
|
// this.libraryChanged is unset in dataSaved()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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, render } from "preact/compat";
|
import { createElement, createRef, Fragment, RefObject, render } from "preact/compat";
|
||||||
import { ExcalidrawImperativeAPI, ExcalidrawProps } from "@excalidraw/excalidraw/types";
|
import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, SceneData } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
/** -1 indicates that it is fresh. excalidraw scene version is always >0 */
|
/** -1 indicates that it is fresh. excalidraw scene version is always >0 */
|
||||||
const SCENE_VERSION_INITIAL = -1;
|
const SCENE_VERSION_INITIAL = -1;
|
||||||
@ -12,6 +12,7 @@ export default class Canvas {
|
|||||||
|
|
||||||
private currentSceneVersion: number;
|
private currentSceneVersion: number;
|
||||||
private opts: ExcalidrawProps;
|
private opts: ExcalidrawProps;
|
||||||
|
private excalidrawWrapperRef: RefObject<HTMLElement>;
|
||||||
excalidrawApi!: ExcalidrawImperativeAPI;
|
excalidrawApi!: ExcalidrawImperativeAPI;
|
||||||
|
|
||||||
constructor(opts: ExcalidrawProps) {
|
constructor(opts: ExcalidrawProps) {
|
||||||
@ -20,7 +21,7 @@ export default class Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCanvas(targetEl: HTMLElement) {
|
renderCanvas(targetEl: HTMLElement) {
|
||||||
render(createCanvasElement({
|
render(this.createCanvasElement({
|
||||||
...this.opts,
|
...this.opts,
|
||||||
excalidrawAPI: (api: ExcalidrawImperativeAPI) => {
|
excalidrawAPI: (api: ExcalidrawImperativeAPI) => {
|
||||||
this.excalidrawApi = api;
|
this.excalidrawApi = api;
|
||||||
@ -28,6 +29,20 @@ export default class Canvas {
|
|||||||
}), targetEl);
|
}), targetEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createCanvasElement(opts: ExcalidrawProps) {
|
||||||
|
const excalidrawWrapperRef = createRef<HTMLElement>();
|
||||||
|
|
||||||
|
return createElement(Fragment, null,
|
||||||
|
createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "excalidraw-wrapper",
|
||||||
|
ref: excalidrawWrapperRef
|
||||||
|
},
|
||||||
|
createElement(Excalidraw, opts)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* needed to ensure, that multipleOnChangeHandler calls do not trigger a save.
|
* needed to ensure, that multipleOnChangeHandler calls do not trigger a save.
|
||||||
* we compare the scene version as suggested in:
|
* we compare the scene version as suggested in:
|
||||||
@ -61,18 +76,89 @@ export default class Canvas {
|
|||||||
return this.currentSceneVersion === SCENE_VERSION_INITIAL;
|
return this.currentSceneVersion === SCENE_VERSION_INITIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadData(content: any, theme: any) {
|
||||||
|
const { elements, files } = content;
|
||||||
|
const appState: Partial<AppState> = content.appState ?? {};
|
||||||
|
|
||||||
|
appState.theme = theme;
|
||||||
|
|
||||||
|
if (this.excalidrawWrapperRef?.current) {
|
||||||
|
const boundingClientRect = this.excalidrawWrapperRef.current.getBoundingClientRect();
|
||||||
|
appState.width = boundingClientRect.width;
|
||||||
|
appState.height = boundingClientRect.height;
|
||||||
|
appState.offsetLeft = boundingClientRect.left;
|
||||||
|
appState.offsetTop = boundingClientRect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneData: SceneData = {
|
||||||
|
elements,
|
||||||
|
appState
|
||||||
|
};
|
||||||
|
|
||||||
|
// files are expected in an array when loading. they are stored as a key-index object
|
||||||
|
// see example for loading here:
|
||||||
|
// https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68
|
||||||
|
const fileArray: BinaryFileData[] = [];
|
||||||
|
for (const fileId in files) {
|
||||||
|
const file = files[fileId];
|
||||||
|
// TODO: dataURL is replaceable with a trilium image url
|
||||||
|
// maybe we can save normal images (pasted) with base64 data url, and trilium images
|
||||||
|
// with their respective url! nice
|
||||||
|
// file.dataURL = "http://localhost:8080/api/images/ltjOiU8nwoZx/start.png";
|
||||||
|
fileArray.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the scene
|
||||||
|
// TODO: Fix type of sceneData
|
||||||
|
this.excalidrawApi.updateScene(sceneData as any);
|
||||||
|
this.excalidrawApi.addFiles(fileArray);
|
||||||
|
this.excalidrawApi.history.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const elements = this.excalidrawApi.getSceneElements();
|
||||||
|
const appState = this.excalidrawApi.getAppState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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?
|
||||||
|
*/
|
||||||
|
const files = this.excalidrawApi.getFiles();
|
||||||
|
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
||||||
|
const svg = await exportToSvg({
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
exportPadding: 5, // 5 px padding
|
||||||
|
files
|
||||||
|
});
|
||||||
|
const svgString = svg.outerHTML;
|
||||||
|
|
||||||
|
const activeFiles: Record<string, BinaryFileData> = {};
|
||||||
|
// TODO: Used any where upstream typings appear to be broken.
|
||||||
|
elements.forEach((element: any) => {
|
||||||
|
if ("fileId" in element && element.fileId) {
|
||||||
|
activeFiles[element.fileId] = files[element.fileId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
type: "excalidraw",
|
||||||
|
version: 2,
|
||||||
|
elements,
|
||||||
|
files: activeFiles,
|
||||||
|
appState: {
|
||||||
|
scrollX: appState.scrollX,
|
||||||
|
scrollY: appState.scrollY,
|
||||||
|
zoom: appState.zoom
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
svg: svgString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCanvasElement(opts: ExcalidrawProps) {
|
|
||||||
const excalidrawWrapperRef = createRef<HTMLElement>();
|
|
||||||
|
|
||||||
return createElement(Fragment, null,
|
|
||||||
createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "excalidraw-wrapper",
|
|
||||||
ref: excalidrawWrapperRef
|
|
||||||
},
|
|
||||||
createElement(Excalidraw, opts)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user