mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
minor canvas note cleanup
This commit is contained in:
parent
a1d1b4580a
commit
2085dc5ed4
@ -85,7 +85,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"electron": "16.2.6",
|
"electron": "16.2.7",
|
||||||
"electron-builder": "23.0.3",
|
"electron-builder": "23.0.3",
|
||||||
"electron-packager": "15.5.1",
|
"electron-packager": "15.5.1",
|
||||||
"electron-rebuild": "3.2.7",
|
"electron-rebuild": "3.2.7",
|
||||||
|
@ -33,42 +33,42 @@ const TPL = `
|
|||||||
/**
|
/**
|
||||||
* # Canvas note with excalidraw
|
* # Canvas note with excalidraw
|
||||||
* @author thfrei 2022-05-11
|
* @author thfrei 2022-05-11
|
||||||
*
|
*
|
||||||
* Background:
|
* Background:
|
||||||
* excalidraw gives great support for hand drawn notes. It also allows to include images and support
|
* excalidraw gives great support for hand drawn notes. It also allows to include images and support
|
||||||
* for sketching. Excalidraw has a vibrant and active community.
|
* for sketching. Excalidraw has a vibrant and active community.
|
||||||
*
|
*
|
||||||
* Functionality:
|
* Functionality:
|
||||||
* We store the excalidraw assets (elements, appState, files) in the note. In addition to that, we
|
* We store the excalidraw assets (elements, appState, files) in the note. In addition to that, we
|
||||||
* export the SVG from the canvas on every update. The SVG is also saved in the note. It is used
|
* export the SVG from the canvas on every update. The SVG is also saved in the note. It is used
|
||||||
* for displaying any canvas note inside of a text note as an image.
|
* for displaying any canvas note inside of a text note as an image.
|
||||||
*
|
*
|
||||||
* Paths not taken.
|
* Paths not taken.
|
||||||
* - excalidraw-to-svg (node.js) could be used to avoid storing the svg in the backend.
|
* - excalidraw-to-svg (node.js) could be used to avoid storing the svg in the backend.
|
||||||
* We could render the SVG on the fly. However, as of now, it does not render any hand drawn
|
* We could render the SVG on the fly. However, as of now, it does not render any hand drawn
|
||||||
* (freedraw) paths. There is an issue with Path2D object not present in node-canvas library
|
* (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.
|
* used by jsdom. (See Trilium PR for samples and other issues in respective library.
|
||||||
* Link will be added later). Related links:
|
* Link will be added later). Related links:
|
||||||
* - https://github.com/Automattic/node-canvas/pull/2013
|
* - https://github.com/Automattic/node-canvas/pull/2013
|
||||||
* - https://github.com/google/canvas-5-polyfill
|
* - https://github.com/google/canvas-5-polyfill
|
||||||
* - https://github.com/Automattic/node-canvas/issues/1116
|
* - https://github.com/Automattic/node-canvas/issues/1116
|
||||||
* - https://www.npmjs.com/package/path2d-polyfill
|
* - https://www.npmjs.com/package/path2d-polyfill
|
||||||
* - excalidraw-to-svg (node.js) takes quite some time to load an image (1-2s)
|
* - 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. It is not
|
* - excalidraw-utils (browser) does render freedraw, however NOT freedraw with background. It is not
|
||||||
* used, since it is a big dependency, and has the same functionality as react + excalidraw.
|
* used, since it is a big dependency, and has the same functionality as react + excalidraw.
|
||||||
* - infinite-drawing-canvas with fabric.js. This library lacked a lot of feature, excalidraw already
|
* - infinite-drawing-canvas with fabric.js. This library lacked a lot of feature, excalidraw already
|
||||||
* has.
|
* has.
|
||||||
*
|
*
|
||||||
* Known issues:
|
* Known issues:
|
||||||
* - v0.11.0 of excalidraw does not render freedraw backgrounds in the svg
|
* - v0.11.0 of excalidraw does not render freedraw backgrounds in the svg
|
||||||
* - the 3 excalidraw fonts should be included in the share and everywhere, so that it is shown
|
* - the 3 excalidraw fonts should be included in the share and everywhere, so that it is shown
|
||||||
* when requiring svg.
|
* when requiring svg.
|
||||||
*
|
*
|
||||||
* Discussion of storing svg in the note:
|
* Discussion of storing svg in the note:
|
||||||
* - Pro: we will combat bit-rot. Showing the SVG will be very fast and easy, since it is already there.
|
* - Pro: we will combat bit-rot. Showing the SVG will be very fast and easy, since it is already there.
|
||||||
* - Con: The note will get bigger (~40-50%?), we will generate more bandwith. However, using trilium
|
* - Con: The note will get bigger (~40-50%?), we will generate more bandwith. However, using trilium
|
||||||
* desktop instance mitigates that issue.
|
* desktop instance mitigates that issue.
|
||||||
*
|
*
|
||||||
* Roadmap:
|
* Roadmap:
|
||||||
* - Support image-notes as reference in excalidraw
|
* - Support image-notes as reference in excalidraw
|
||||||
* - Support canvas note as reference (svg) in other canvas notes.
|
* - Support canvas note as reference (svg) in other canvas notes.
|
||||||
@ -95,52 +95,42 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
// will be overwritten
|
// will be overwritten
|
||||||
this.excalidrawRef;
|
this.excalidrawRef;
|
||||||
this.$render;
|
this.$render;
|
||||||
this.renderElement;
|
|
||||||
this.$widget;
|
this.$widget;
|
||||||
this.reactHandlers; // used to control react state
|
this.reactHandlers; // used to control react state
|
||||||
|
|
||||||
this.createExcalidrawReactApp = this.createExcalidrawReactApp.bind(this);
|
this.createExcalidrawReactApp = this.createExcalidrawReactApp.bind(this);
|
||||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||||
this.isNewSceneVersion = this.isNewSceneVersion.bind(this);
|
this.isNewSceneVersion = this.isNewSceneVersion.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* (trilium)
|
|
||||||
* @returns {string} "canvas"
|
|
||||||
*/
|
|
||||||
static getType() {
|
static getType() {
|
||||||
return "canvas";
|
return "canvas";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* (trilium)
|
|
||||||
* renders note
|
|
||||||
*/
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
|
|
||||||
this.$widget.toggleClass("full-height", true); // only add
|
this.$widget.toggleClass("full-height", true); // only add
|
||||||
this.$render = this.$widget.find('.canvas-render');
|
this.$render = this.$widget.find('.canvas-render');
|
||||||
this.renderElement = this.$render.get(0);
|
|
||||||
|
|
||||||
libraryLoader
|
libraryLoader
|
||||||
.requireLibrary(libraryLoader.EXCALIDRAW)
|
.requireLibrary(libraryLoader.EXCALIDRAW)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const React = window.React;
|
const React = window.React;
|
||||||
const ReactDOM = window.ReactDOM;
|
const ReactDOM = window.ReactDOM;
|
||||||
|
const renderElement = this.$render.get(0);
|
||||||
ReactDOM.unmountComponentAtNode(this.renderElement);
|
|
||||||
ReactDOM.render(React.createElement(this.createExcalidrawReactApp), this.renderElement);
|
ReactDOM.unmountComponentAtNode(renderElement);
|
||||||
})
|
ReactDOM.render(React.createElement(this.createExcalidrawReactApp), renderElement);
|
||||||
|
});
|
||||||
|
|
||||||
return this.$widget;
|
return this.$widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (trilium)
|
|
||||||
* called to populate the widget container with the note content
|
* called to populate the widget container with the note content
|
||||||
*
|
*
|
||||||
* @param {note} note
|
* @param {note} note
|
||||||
*/
|
*/
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
// see if note changed, since we do not get a new class for a new note
|
// see if note changed, since we do not get a new class for a new note
|
||||||
@ -150,7 +140,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
||||||
}
|
}
|
||||||
this.currentNoteId = note.noteId;
|
this.currentNoteId = note.noteId;
|
||||||
|
|
||||||
// get note from backend and put into canvas
|
// get note from backend and put into canvas
|
||||||
const noteComplement = await froca.getNoteComplement(note.noteId);
|
const noteComplement = await froca.getNoteComplement(note.noteId);
|
||||||
|
|
||||||
@ -166,25 +156,18 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
* note into this fresh note. Probably due to that this note-instance does not get
|
* note into this fresh note. Probably due to that this note-instance does not get
|
||||||
* newly instantiated?
|
* newly instantiated?
|
||||||
*/
|
*/
|
||||||
if (this.excalidrawRef.current && noteComplement.content === "") {
|
if (this.excalidrawRef.current && noteComplement.content?.trim() === "") {
|
||||||
const sceneData = {
|
const sceneData = {
|
||||||
elements: [],
|
elements: [],
|
||||||
appState: {},
|
appState: {},
|
||||||
collaborators: []
|
collaborators: []
|
||||||
};
|
};
|
||||||
|
|
||||||
this.excalidrawRef.current.updateScene(sceneData);
|
this.excalidrawRef.current.updateScene(sceneData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* load saved content into excalidraw canvas
|
|
||||||
*/
|
|
||||||
else if (this.excalidrawRef.current && noteComplement.content) {
|
else if (this.excalidrawRef.current && noteComplement.content) {
|
||||||
let content ={
|
// load saved content into excalidraw canvas
|
||||||
elements: [],
|
let content;
|
||||||
appState: [],
|
|
||||||
files: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = JSON.parse(noteComplement.content || "");
|
content = JSON.parse(noteComplement.content || "");
|
||||||
@ -192,6 +175,12 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
console.error("Error parsing content. Probably note.type changed",
|
console.error("Error parsing content. Probably note.type changed",
|
||||||
"Starting with empty canvas"
|
"Starting with empty canvas"
|
||||||
, note, noteComplement, err);
|
, note, noteComplement, err);
|
||||||
|
|
||||||
|
content = {
|
||||||
|
elements: [],
|
||||||
|
appState: [],
|
||||||
|
files: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const {elements, appState, files} = content;
|
const {elements, appState, files} = content;
|
||||||
@ -207,7 +196,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
appState.offsetTop = boundingClientRect.top;
|
appState.offsetTop = boundingClientRect.top;
|
||||||
|
|
||||||
const sceneData = {
|
const sceneData = {
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
collaborators: []
|
collaborators: []
|
||||||
};
|
};
|
||||||
@ -225,12 +214,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
fileArray.push(file);
|
fileArray.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sceneVersion = window.Excalidraw.getSceneVersion(elements);
|
|
||||||
|
|
||||||
this.excalidrawRef.current.updateScene(sceneData);
|
this.excalidrawRef.current.updateScene(sceneData);
|
||||||
this.excalidrawRef.current.addFiles(fileArray);
|
this.excalidrawRef.current.addFiles(fileArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set initial scene version
|
// set initial scene version
|
||||||
if (this.currentSceneVersion === this.SCENE_VERSION_INITIAL) {
|
if (this.currentSceneVersion === this.SCENE_VERSION_INITIAL) {
|
||||||
this.currentSceneVersion = this.getSceneVersion();
|
this.currentSceneVersion = this.getSceneVersion();
|
||||||
@ -238,20 +225,19 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (trilium)
|
|
||||||
* gets data from widget container that will be sent via spacedUpdate.scheduleUpdate();
|
* gets data from widget container that will be sent via spacedUpdate.scheduleUpdate();
|
||||||
* this is automatically called after this.saveData();
|
* this is automatically called after this.saveData();
|
||||||
*/
|
*/
|
||||||
async getContent() {
|
async getContent() {
|
||||||
const elements = this.excalidrawRef.current.getSceneElements();
|
const elements = this.excalidrawRef.current.getSceneElements();
|
||||||
const appState = this.excalidrawRef.current.getAppState();
|
const appState = this.excalidrawRef.current.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 new excalidraw version?
|
* files that are referenced by an element. Maybe this will change with new excalidraw version?
|
||||||
*/
|
*/
|
||||||
const files = this.excalidrawRef.current.getFiles();
|
const files = this.excalidrawRef.current.getFiles();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parallel svg export to combat bitrot and enable rendering image for note inclusion,
|
* parallel svg export to combat bitrot and enable rendering image for note inclusion,
|
||||||
* preview and share.
|
* preview and share.
|
||||||
@ -285,13 +271,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
svg: svgSafeString, // not needed for excalidraw, used for note_short, content, and image api
|
svg: svgSafeString, // not needed for excalidraw, used for note_short, content, and image api
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentString = JSON.stringify(content);
|
return JSON.stringify(content);
|
||||||
|
|
||||||
return contentString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (trilium)
|
|
||||||
* save content to backend
|
* save content to backend
|
||||||
* spacedUpdate is kind of a debouncer.
|
* spacedUpdate is kind of a debouncer.
|
||||||
*/
|
*/
|
||||||
@ -300,8 +283,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChangeHandler() {
|
onChangeHandler() {
|
||||||
const appState = this.excalidrawRef.current.getAppState() || {};
|
|
||||||
|
|
||||||
// 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.isNewSceneVersion();
|
||||||
@ -336,11 +317,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
height: undefined
|
height: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const [viewModeEnabled, setViewModeEnabled] = React.useState(false);
|
|
||||||
const [zenModeEnabled, setZenModeEnabled] = React.useState(false);
|
|
||||||
const [gridModeEnabled, setGridModeEnabled] = React.useState(false);
|
|
||||||
const [synchronized, setSynchronized] = React.useState(true);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const dimensions = {
|
const dimensions = {
|
||||||
width: excalidrawWrapperRef.current.getBoundingClientRect().width,
|
width: excalidrawWrapperRef.current.getBoundingClientRect().width,
|
||||||
@ -355,9 +331,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
};
|
};
|
||||||
setDimensions(dimensions);
|
setDimensions(dimensions);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("resize", onResize);
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
return () => window.removeEventListener("resize", onResize);
|
return () => window.removeEventListener("resize", onResize);
|
||||||
}, [excalidrawWrapperRef]);
|
}, [excalidrawWrapperRef]);
|
||||||
|
|
||||||
@ -366,9 +342,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
const { nativeEvent } = event.detail;
|
const { nativeEvent } = event.detail;
|
||||||
const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
|
const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
|
||||||
const isNewWindow = nativeEvent.shiftKey;
|
const isNewWindow = nativeEvent.shiftKey;
|
||||||
const isInternalLink = link.startsWith("/")
|
const isInternalLink = link.startsWith("/")
|
||||||
|| link.includes(window.location.origin);
|
|| link.includes(window.location.origin);
|
||||||
|
|
||||||
if (isInternalLink && !isNewTab && !isNewWindow) {
|
if (isInternalLink && !isNewTab && !isNewWindow) {
|
||||||
// signal that we're handling the redirect ourselves
|
// signal that we're handling the redirect ourselves
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -401,9 +377,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
onCollabButtonClick: () => {
|
onCollabButtonClick: () => {
|
||||||
window.alert("You clicked on collab button. No collaboration is implemented.");
|
window.alert("You clicked on collab button. No collaboration is implemented.");
|
||||||
},
|
},
|
||||||
viewModeEnabled: viewModeEnabled,
|
viewModeEnabled: false,
|
||||||
zenModeEnabled: zenModeEnabled,
|
zenModeEnabled: false,
|
||||||
gridModeEnabled: gridModeEnabled,
|
gridModeEnabled: false,
|
||||||
isCollaborating: false,
|
isCollaborating: false,
|
||||||
detectScroll: false,
|
detectScroll: false,
|
||||||
handleKeyboardGlobally: false,
|
handleKeyboardGlobally: false,
|
||||||
@ -412,18 +388,18 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* needed to ensure, that multipleOnChangeHandler calls do not trigger a safe.
|
* needed to ensure, that multipleOnChangeHandler calls do not trigger a safe.
|
||||||
* we compare the scene version as suggested in:
|
* we compare the scene version as suggested in:
|
||||||
* https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329
|
* https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329
|
||||||
*
|
*
|
||||||
* info: sceneVersions are not incrementing. it seems to be a pseudo-random number
|
* info: sceneVersions are not incrementing. it seems to be a pseudo-random number
|
||||||
*/
|
*/
|
||||||
isNewSceneVersion() {
|
isNewSceneVersion() {
|
||||||
const sceneVersion = this.getSceneVersion();
|
const sceneVersion = this.getSceneVersion();
|
||||||
|
|
||||||
return this.currentSceneVersion === this.SCENE_VERSION_INITIAL // initial scene version update
|
return this.currentSceneVersion === this.SCENE_VERSION_INITIAL // initial scene version update
|
||||||
|| this.currentSceneVersion !== sceneVersion // ensure scene changed
|
|| this.currentSceneVersion !== sceneVersion // ensure scene changed
|
||||||
;
|
;
|
||||||
@ -432,8 +408,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
getSceneVersion() {
|
getSceneVersion() {
|
||||||
if (this.excalidrawRef) {
|
if (this.excalidrawRef) {
|
||||||
const elements = this.excalidrawRef.current.getSceneElements();
|
const elements = this.excalidrawRef.current.getSceneElements();
|
||||||
const sceneVersion = window.Excalidraw.getSceneVersion(elements);
|
return window.Excalidraw.getSceneVersion(elements);
|
||||||
return sceneVersion;
|
|
||||||
} else {
|
} else {
|
||||||
return this.SCENE_VERSION_ERROR;
|
return this.SCENE_VERSION_ERROR;
|
||||||
}
|
}
|
||||||
@ -442,11 +417,11 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
updateSceneVersion() {
|
updateSceneVersion() {
|
||||||
this.currentSceneVersion = this.getSceneVersion();
|
this.currentSceneVersion = this.getSceneVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* logs to console.log with some predefined title
|
* logs to console.log with some predefined title
|
||||||
*
|
*
|
||||||
* @param {...any} args
|
* @param {...any} args
|
||||||
*/
|
*/
|
||||||
log(...args) {
|
log(...args) {
|
||||||
let title = '';
|
let title = '';
|
||||||
@ -461,12 +436,12 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* replaces exlicraw.com with own assets
|
* replaces exlicraw.com with own assets
|
||||||
*
|
*
|
||||||
* workaround until https://github.com/excalidraw/excalidraw/pull/5065 is merged and published
|
* workaround until https://github.com/excalidraw/excalidraw/pull/5065 is merged and published
|
||||||
* needed for v0.11.0
|
* needed for v0.11.0
|
||||||
*
|
*
|
||||||
* @param {string} string
|
* @param {string} string
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
replaceExternalAssets = (string) => {
|
replaceExternalAssets = (string) => {
|
||||||
let result = string;
|
let result = string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user