Skip to content

Commit

Permalink
missing path2d support for freedawings, remove node-side rendering, a…
Browse files Browse the repository at this point in the history
…llow async getContent()

 * ## Excalidraw and SVG
 * 2022-04-16 - @thfrei
 *
 * Known issues:
 *  - excalidraw-to-svg (node.js) does not render any hand drawn (freedraw) paths. There is an issue with
 *    Path2D object not present in node-canvas library used by jsdom. (See Trilium PR for samples and other issues
 *    in respective library. Link will be added later). Related links:
 *     - Automattic/node-canvas#2013
 *     - https://github.com/google/canvas-5-polyfill
 *     - Automattic/node-canvas#1116
 *     - https://www.npmjs.com/package/path2d-polyfill
 *  - excalidraw-to-svg (node.js) takes quite some time to load an image (1-2s)
 *  - excalidraw-utils (browser) does render freedraw, however NOT freedraw with background
 *
 * Due to this issues, we opt to use **only excalidraw in the frontend**. Upon saving, we will also get the SVG
 * output from the live excalidraw instance. We will save this **SVG side by side the native excalidraw format
 * in the trilium note**.
 *
 * Pro: we will combat bit-rot. Showing the SVG will be very fast, since it is already rendered.
 * Con: The note will get bigger (maybe +30%?), we will generate more bandwith.
 *      (However, using trilium desktop instance, does not care too much about bandwidth. Size increase is probably
 *       acceptable, as a trade off.)
  • Loading branch information
thfrei committed Apr 17, 2022
1 parent 77098fe commit 5dd228e
Show file tree
Hide file tree
Showing 10 changed files with 8,032 additions and 13,700 deletions.
21,564 changes: 7,949 additions & 13,615 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"dependencies": {
"@electron/remote": "2.0.8",
"@excalidraw/excalidraw": "0.11.0",
"@excalidraw/utils": "0.1.2",
"archiver": "5.3.0",
"async-mutex": "0.3.2",
"axios": "0.26.1",
Expand All @@ -42,7 +41,6 @@
"electron-dl": "3.3.1",
"electron-find": "1.0.7",
"electron-window-state": "5.0.3",
"excalidraw-to-svg": "3.0.0",
"express": "4.17.2",
"express-partial-content": "1.0.2",
"express-rate-limit": "6.3.0",
Expand Down
26 changes: 6 additions & 20 deletions src/public/app/dialogs/note_revisions.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,33 +176,19 @@ async function setContentPane() {
* can the revisions called without being on the note type before?
* if so: load excalidraw
*/
// FIXME: Does it make sense to use EXCALIDRAW_UTILS that are 1.5 MB
// whereas excalidraw (650kB) +react(12kB)+reactdom(118kB)
/**
* FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
* REMOVE external dependency!!!! This is defined in the svg in defs.style
*/
await libraryLoader.requireLibrary(libraryLoader.EXCALIDRAW_UTILS);
const {exportToSvg} = window.ExcalidrawUtils

*/
/**
* FIXME: If svg is not present, probably use live excalidraw?
*/
const content = fullNoteRevision.content;

try {
const data = JSON.parse(content)
const excData = {
type: "excalidraw",
version: 2,
source: "trilium",
elements: data.elements,
appState: data.appState,
files: data.files,
}
const svg = await exportToSvg(excData);
$content
.html(
$('<div>')
.html(svg)
);
const svg = data.svg || "no svg present."
$content.html($('<div>').html(svg));
} catch(err) {
console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err);
$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
Expand Down
12 changes: 1 addition & 11 deletions src/public/app/services/library_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,6 @@ const EXCALIDRAW = {
// ]
};

const EXCALIDRAW_UTILS = {
/**
* FIXME: excalidraw-utils does not render pen-background. maybe own built required?
*/
js: [
"node_modules/@excalidraw/utils/dist/excalidraw-utils.min.js", //v0.1.2
]
};

async function requireLibrary(library) {
if (library.css) {
library.css.map(cssUrl => requireCss(cssUrl));
Expand Down Expand Up @@ -127,6 +118,5 @@ export default {
WHEEL_ZOOM,
FORCE_GRAPH,
MERMAID,
EXCALIDRAW,
EXCALIDRAW_UTILS
EXCALIDRAW
}
10 changes: 1 addition & 9 deletions src/public/app/services/note_content_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,7 @@ async function getRenderedContent(note, options = {}) {

try {
const data = JSON.parse(content)
const excData = {
type: "excalidraw",
version: 2,
source: "trilium",
elements: data.elements,
appState: data.appState,
files: data.files,
}
const svg = await exportToSvg(excData);
const svg = data.svg || "no svg present."
$renderedContent.append($('<div>').html(svg));
} catch(err) {
console.error("error parsing content as JSON", content, err);
Expand Down
2 changes: 1 addition & 1 deletion src/public/app/widgets/note_detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
const {noteId} = note;

const dto = note.dto;
dto.content = this.getTypeWidget().getContent();
dto.content = await this.getTypeWidget().getContent();

// for read only notes
if (dto.content === undefined) {
Expand Down
69 changes: 61 additions & 8 deletions src/public/app/widgets/type_widgets/canvas_note.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import froca from "../../services/froca.js";
import debounce from "./canvas-note-utils/lodash.debounce.js";
import uniqueId from "./canvas-note-utils/lodash.uniqueId.js";

// NoteContextAwareWidget does not handle loading/refreshing of note context
import NoteContextAwareWidget from "../note_context_aware_widget.js";
// NoteContextAwareWidget does not handle loading/refreshing of note context
// import NoteContextAwareWidget from "../note_context_aware_widget.js";

const TPL = `
<div class="canvas-note-widget note-detail-canvas-note note-detail-printable note-detail">
Expand Down Expand Up @@ -53,6 +53,33 @@ VM18070:2 error trying to resing image file on insertion ChunkLoadError: Loading
*/
/**
* Discussion?: add complete @excalidraw/excalidraw, utils, react, react-dom as library? maybe also node_modules?
* Result: as of know, most dependencies are manually. however no special preference is given.
* Result2: since excalidraw to svg rendering in node is not really working, we can easily do a manual dependency
* management of excalidraw and react.
*/
/**
* ## Excalidraw and SVG
* 2022-04-16 - @thfrei
*
* Known issues:
* - excalidraw-to-svg (node.js) does not render any hand drawn (freedraw) paths. There is an issue with
* Path2D object not present in node-canvas library used by jsdom. (See Trilium PR for samples and other issues
* in respective library. Link will be added later). Related links:
* - https://github.com/Automattic/node-canvas/pull/2013
* - https://github.com/google/canvas-5-polyfill
* - https://github.com/Automattic/node-canvas/issues/1116
* - https://www.npmjs.com/package/path2d-polyfill
* - excalidraw-to-svg (node.js) takes quite some time to load an image (1-2s)
* - excalidraw-utils (browser) does render freedraw, however NOT freedraw with background
*
* Due to this issues, we opt to use **only excalidraw in the frontend**. Upon saving, we will also get the SVG
* output from the live excalidraw instance. We will save this **SVG side by side the native excalidraw format
* in the trilium note**.
*
* Pro: we will combat bit-rot. Showing the SVG will be very fast, since it is already rendered.
* Con: The note will get bigger (maybe +30%?), we will generate more bandwith.
* (However, using trilium desktop instance, does not care too much about bandwidth. Size increase is probably
* acceptable, as a trade off.)
*/
export default class ExcalidrawTypeWidget extends TypeWidget {
constructor() {
Expand Down Expand Up @@ -236,17 +263,43 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* gets data from widget container that will be sent via spacedUpdate.scheduleUpdate();
* this is automatically called after this.saveData();
*/
getContent() {
const time = new Date();

async getContent() {
const elements = this.excalidrawRef.current.getSceneElements();
const appState = this.excalidrawRef.current.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 new excalidraw version?
*/
const files = this.excalidrawRef.current.getFiles();

/**
* parallel svg export to combat bitrot and enable rendering image for note inclusion,
* preview and share.
*/
const svg = await window.Excalidraw.exportToSvg({
elements,
appState,
exportPadding: 5, // 5 px padding
metadata: 'trilium-export',
files
});

/**
* Trials for png
*/
// const png = await window.Excalidraw.exportToBlob(await window.Excalidraw.exportToCanvas({
// elements,
// appState,
// files
// }))
// function blobToBase64(blob) {
// return new Promise((resolve, _) => {
// const reader = new FileReader();
// reader.onloadend = () => resolve(reader.result);
// reader.readAsDataURL(blob);
// });
// }

const activeFiles = {};
elements.forEach((element) => {
Expand All @@ -260,9 +313,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
elements,
appState,
files: activeFiles,
time,
svg: svg.outerHTML,
// png: await blobToBase64(png),
};
// this.log('getContent()', content, activeFiles);

return JSON.stringify(content);
}
Expand Down
3 changes: 3 additions & 0 deletions src/public/app/widgets/type_widgets/type_widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default class TypeWidget extends NoteContextAwareWidget {
return this.$widget.is(":visible");
}

/**
* FIXME: add async here to indicate promise?
*/
getContent() {}

focus() {}
Expand Down
23 changes: 6 additions & 17 deletions src/routes/api/image.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";

const excalidrawToSvg = require("excalidraw-to-svg");
const imageService = require('../../services/image');
const becca = require('../../becca/becca');
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
Expand Down Expand Up @@ -28,22 +27,12 @@ function returnImage(req, res) {
// render the svg in node.js using excalidraw and jsdom
const content = image.getContent();
try {
const data = JSON.parse(content)
const excalidrawData = {
type: "excalidraw",
version: 2,
source: "trilium",
elements: data.elements,
appState: data.appState,
files: data.files,
}
excalidrawToSvg(excalidrawData)
.then(svg => {
const svgHtml = svg.outerHTML;
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svgHtml);
});
const data = JSON.parse(content);

const svg = data.svg || '<svg />'
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svg);
} catch(err) {
res.status(500).send("there was an error parsing excalidraw to svg");
}
Expand Down
21 changes: 4 additions & 17 deletions src/share/routes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const excalidrawToSvg = require("excalidraw-to-svg");

const shaca = require("./shaca/shaca");
const shacaLoader = require("./shaca/shaca_loader");
const shareRoot = require("./share_root");
Expand Down Expand Up @@ -124,21 +122,10 @@ function register(router) {
const content = image.getContent();
try {
const data = JSON.parse(content)
const excalidrawData = {
type: "excalidraw",
version: 2,
source: "trilium",
elements: data.elements,
appState: data.appState,
files: data.files,
}
excalidrawToSvg(excalidrawData)
.then(svg => {
const svgHtml = svg.outerHTML;
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svgHtml);
});
const svg = data.svg || '<svg />'
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svg);
} catch(err) {
res.status(500).send("there was an error parsing excalidraw to svg");
}
Expand Down

0 comments on commit 5dd228e

Please sign in to comment.