Skip to content

Commit 9771b44

Browse files
committed
missing path2d support for freedawings, remove node-side rendering, allow 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.)
1 parent c295fdb commit 9771b44

File tree

10 files changed

+84
-420
lines changed

10 files changed

+84
-420
lines changed

package-lock.json

Lines changed: 1 addition & 335 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
},
2626
"dependencies": {
2727
"@excalidraw/excalidraw": "0.11.0",
28-
"@excalidraw/utils": "0.1.2",
2928
"archiver": "5.3.1",
3029
"async-mutex": "0.3.2",
3130
"axios": "0.26.1",
@@ -42,7 +41,6 @@
4241
"electron-find": "1.0.7",
4342
"electron-window-state": "5.0.3",
4443
"@electron/remote": "2.0.8",
45-
"excalidraw-to-svg": "3.0.0",
4644
"express": "4.17.2",
4745
"express-partial-content": "1.0.2",
4846
"express-rate-limit": "6.3.0",

src/public/app/dialogs/note_revisions.js

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -176,33 +176,19 @@ async function setContentPane() {
176176
* can the revisions called without being on the note type before?
177177
* if so: load excalidraw
178178
*/
179-
// FIXME: Does it make sense to use EXCALIDRAW_UTILS that are 1.5 MB
180-
// whereas excalidraw (650kB) +react(12kB)+reactdom(118kB)
181179
/**
182180
* FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
183181
* REMOVE external dependency!!!! This is defined in the svg in defs.style
184-
*/
185-
await libraryLoader.requireLibrary(libraryLoader.EXCALIDRAW_UTILS);
186-
const {exportToSvg} = window.ExcalidrawUtils
187-
182+
*/
183+
/**
184+
* FIXME: If svg is not present, probably use live excalidraw?
185+
*/
188186
const content = fullNoteRevision.content;
189187

190188
try {
191189
const data = JSON.parse(content)
192-
const excData = {
193-
type: "excalidraw",
194-
version: 2,
195-
source: "trilium",
196-
elements: data.elements,
197-
appState: data.appState,
198-
files: data.files,
199-
}
200-
const svg = await exportToSvg(excData);
201-
$content
202-
.html(
203-
$('<div>')
204-
.html(svg)
205-
);
190+
const svg = data.svg || "no svg present."
191+
$content.html($('<div>').html(svg));
206192
} catch(err) {
207193
console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err);
208194
$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));

src/public/app/services/library_loader.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,6 @@ const EXCALIDRAW = {
6767
// ]
6868
};
6969

70-
const EXCALIDRAW_UTILS = {
71-
/**
72-
* FIXME: excalidraw-utils does not render pen-background. maybe own built required?
73-
*/
74-
js: [
75-
"node_modules/@excalidraw/utils/dist/excalidraw-utils.min.js", //v0.1.2
76-
]
77-
};
78-
7970
async function requireLibrary(library) {
8071
if (library.css) {
8172
library.css.map(cssUrl => requireCss(cssUrl));
@@ -127,6 +118,5 @@ export default {
127118
WHEEL_ZOOM,
128119
FORCE_GRAPH,
129120
MERMAID,
130-
EXCALIDRAW,
131-
EXCALIDRAW_UTILS
121+
EXCALIDRAW
132122
}

src/public/app/services/note_content_renderer.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,7 @@ async function getRenderedContent(note, options = {}) {
150150

151151
try {
152152
const data = JSON.parse(content)
153-
const excData = {
154-
type: "excalidraw",
155-
version: 2,
156-
source: "trilium",
157-
elements: data.elements,
158-
appState: data.appState,
159-
files: data.files,
160-
}
161-
const svg = await exportToSvg(excData);
153+
const svg = data.svg || "no svg present."
162154
$renderedContent.append($('<div>').html(svg));
163155
} catch(err) {
164156
console.error("error parsing content as JSON", content, err);

src/public/app/widgets/note_detail.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
6868
const {noteId} = note;
6969

7070
const dto = note.dto;
71-
dto.content = this.getTypeWidget().getContent();
71+
dto.content = await this.getTypeWidget().getContent();
7272

7373
// for read only notes
7474
if (dto.content === undefined) {

src/public/app/widgets/type_widgets/canvas_note.js

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import froca from "../../services/froca.js";
66
import debounce from "./canvas-note-utils/lodash.debounce.js";
77
import uniqueId from "./canvas-note-utils/lodash.uniqueId.js";
88

9-
// NoteContextAwareWidget does not handle loading/refreshing of note context
10-
import NoteContextAwareWidget from "../note_context_aware_widget.js";
9+
// NoteContextAwareWidget does not handle loading/refreshing of note context
10+
// import NoteContextAwareWidget from "../note_context_aware_widget.js";
1111

1212
const TPL = `
1313
<div class="canvas-note-widget note-detail-canvas-note note-detail-printable note-detail">
@@ -53,6 +53,33 @@ VM18070:2 error trying to resing image file on insertion ChunkLoadError: Loading
5353
*/
5454
/**
5555
* Discussion?: add complete @excalidraw/excalidraw, utils, react, react-dom as library? maybe also node_modules?
56+
* Result: as of know, most dependencies are manually. however no special preference is given.
57+
* Result2: since excalidraw to svg rendering in node is not really working, we can easily do a manual dependency
58+
* management of excalidraw and react.
59+
*/
60+
/**
61+
* ## Excalidraw and SVG
62+
* 2022-04-16 - @thfrei
63+
*
64+
* Known issues:
65+
* - excalidraw-to-svg (node.js) does not render any hand drawn (freedraw) paths. There is an issue with
66+
* Path2D object not present in node-canvas library used by jsdom. (See Trilium PR for samples and other issues
67+
* in respective library. Link will be added later). Related links:
68+
* - https://github.com/Automattic/node-canvas/pull/2013
69+
* - https://github.com/google/canvas-5-polyfill
70+
* - https://github.com/Automattic/node-canvas/issues/1116
71+
* - https://www.npmjs.com/package/path2d-polyfill
72+
* - excalidraw-to-svg (node.js) takes quite some time to load an image (1-2s)
73+
* - excalidraw-utils (browser) does render freedraw, however NOT freedraw with background
74+
*
75+
* Due to this issues, we opt to use **only excalidraw in the frontend**. Upon saving, we will also get the SVG
76+
* output from the live excalidraw instance. We will save this **SVG side by side the native excalidraw format
77+
* in the trilium note**.
78+
*
79+
* Pro: we will combat bit-rot. Showing the SVG will be very fast, since it is already rendered.
80+
* Con: The note will get bigger (maybe +30%?), we will generate more bandwith.
81+
* (However, using trilium desktop instance, does not care too much about bandwidth. Size increase is probably
82+
* acceptable, as a trade off.)
5683
*/
5784
export default class ExcalidrawTypeWidget extends TypeWidget {
5885
constructor() {
@@ -236,17 +263,43 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
236263
* gets data from widget container that will be sent via spacedUpdate.scheduleUpdate();
237264
* this is automatically called after this.saveData();
238265
*/
239-
getContent() {
240-
const time = new Date();
241-
266+
async getContent() {
242267
const elements = this.excalidrawRef.current.getSceneElements();
243268
const appState = this.excalidrawRef.current.getAppState();
244-
269+
245270
/**
246271
* A file is not deleted, even though removed from canvas. therefore we only keep
247272
* files that are referenced by an element. Maybe this will change with new excalidraw version?
248273
*/
249274
const files = this.excalidrawRef.current.getFiles();
275+
276+
/**
277+
* parallel svg export to combat bitrot and enable rendering image for note inclusion,
278+
* preview and share.
279+
*/
280+
const svg = await window.Excalidraw.exportToSvg({
281+
elements,
282+
appState,
283+
exportPadding: 5, // 5 px padding
284+
metadata: 'trilium-export',
285+
files
286+
});
287+
288+
/**
289+
* Trials for png
290+
*/
291+
// const png = await window.Excalidraw.exportToBlob(await window.Excalidraw.exportToCanvas({
292+
// elements,
293+
// appState,
294+
// files
295+
// }))
296+
// function blobToBase64(blob) {
297+
// return new Promise((resolve, _) => {
298+
// const reader = new FileReader();
299+
// reader.onloadend = () => resolve(reader.result);
300+
// reader.readAsDataURL(blob);
301+
// });
302+
// }
250303

251304
const activeFiles = {};
252305
elements.forEach((element) => {
@@ -260,9 +313,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
260313
elements,
261314
appState,
262315
files: activeFiles,
263-
time,
316+
svg: svg.outerHTML,
317+
// png: await blobToBase64(png),
264318
};
265-
// this.log('getContent()', content, activeFiles);
266319

267320
return JSON.stringify(content);
268321
}

src/public/app/widgets/type_widgets/type_widget.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export default class TypeWidget extends NoteContextAwareWidget {
3737
return this.$widget.is(":visible");
3838
}
3939

40+
/**
41+
* FIXME: add async here to indicate promise?
42+
*/
4043
getContent() {}
4144

4245
focus() {}

src/routes/api/image.js

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use strict";
22

3-
const excalidrawToSvg = require("excalidraw-to-svg");
43
const imageService = require('../../services/image');
54
const becca = require('../../becca/becca');
65
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
@@ -28,22 +27,12 @@ function returnImage(req, res) {
2827
// render the svg in node.js using excalidraw and jsdom
2928
const content = image.getContent();
3029
try {
31-
const data = JSON.parse(content)
32-
const excalidrawData = {
33-
type: "excalidraw",
34-
version: 2,
35-
source: "trilium",
36-
elements: data.elements,
37-
appState: data.appState,
38-
files: data.files,
39-
}
40-
excalidrawToSvg(excalidrawData)
41-
.then(svg => {
42-
const svgHtml = svg.outerHTML;
43-
res.set('Content-Type', "image/svg+xml");
44-
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
45-
res.send(svgHtml);
46-
});
30+
const data = JSON.parse(content);
31+
32+
const svg = data.svg || '<svg />'
33+
res.set('Content-Type', "image/svg+xml");
34+
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
35+
res.send(svg);
4736
} catch(err) {
4837
res.status(500).send("there was an error parsing excalidraw to svg");
4938
}

src/share/routes.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
const excalidrawToSvg = require("excalidraw-to-svg");
2-
31
const shaca = require("./shaca/shaca");
42
const shacaLoader = require("./shaca/shaca_loader");
53
const shareRoot = require("./share_root");
@@ -124,21 +122,10 @@ function register(router) {
124122
const content = image.getContent();
125123
try {
126124
const data = JSON.parse(content)
127-
const excalidrawData = {
128-
type: "excalidraw",
129-
version: 2,
130-
source: "trilium",
131-
elements: data.elements,
132-
appState: data.appState,
133-
files: data.files,
134-
}
135-
excalidrawToSvg(excalidrawData)
136-
.then(svg => {
137-
const svgHtml = svg.outerHTML;
138-
res.set('Content-Type', "image/svg+xml");
139-
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
140-
res.send(svgHtml);
141-
});
125+
const svg = data.svg || '<svg />'
126+
res.set('Content-Type', "image/svg+xml");
127+
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
128+
res.send(svg);
142129
} catch(err) {
143130
res.status(500).send("there was an error parsing excalidraw to svg");
144131
}

0 commit comments

Comments
 (0)