From 8d4779b8fe3828e1e97e79a2e2fb06917a35c762 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Wed, 23 Apr 2025 09:52:45 +0200 Subject: [PATCH] Improve error handling and code structure --- public/i18n/en_US.json | 3 + src/config.ts | 31 ++++++---- src/editor.ts | 129 +++++++++++++++++++++++++---------------- src/errors.ts | 12 ++++ src/index.ts | 8 +-- 5 files changed, 116 insertions(+), 67 deletions(-) create mode 100644 src/errors.ts diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json index 88c96a3..a282081 100644 --- a/public/i18n/en_US.json +++ b/public/i18n/en_US.json @@ -2,6 +2,9 @@ "insertDrawing": "Insert Drawing", "editDrawing": "Edit with js-draw", "errNoFileID": "File ID missing - couldn't open file.", + "errSyncIDNotFound": "Couldn't find SyncID in document for drawing, make sure you're trying to edit a drawing that is included in at least a note.", + "errCreateUnknown": "Unknown error while creating editor, please try again.", + "errInvalidBackgroundColor": "Invalid background color! Please enter an HEX color, like #000000 (black) or #FFFFFF (white). The old background color will be used.", "drawing": "Drawing", "settings": { "name": "js-draw Plugin Settings", diff --git a/src/config.ts b/src/config.ts index 4491329..59842eb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,6 @@ import {PluginFile} from "@/file"; import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const"; -import {Plugin} from "siyuan"; +import {Plugin, showMessage} from "siyuan"; import {SettingUtils} from "@/libs/setting-utils"; import {validateColor} from "@/helper"; @@ -61,10 +61,6 @@ export class PluginConfig { } setConfig(config: Options) { - if(!validateColor(config.background)) { - alert("Invalid background color! Please enter an HEX color, like #000000 (black) or #FFFFFF (white)"); - config.background = this.options.background; - } this.options = config; } @@ -83,19 +79,30 @@ export class PluginConfigViewer { this.populateSettingMenu(); } + async configSaveCallback(data) { + + if(!validateColor(data.background)) { + showMessage(this.plugin.i18n.errInvalidBackgroundColor, 0, 'error'); + data.background = this.config.options.background; + this.settingUtils.set('background', data.background); + } + this.config.setConfig({ + grid: data.grid, + background: data.background, + dialogOnDesktop: data.dialogOnDesktop, + analytics: data.analytics, + }); + await this.config.save(); + + } + populateSettingMenu() { this.settingUtils = new SettingUtils({ plugin: this.plugin, name: this.plugin.i18n.settings.name, callback: async (data) => { - this.config.setConfig({ - grid: data.grid, - background: data.background, - dialogOnDesktop: data.dialogOnDesktop, - analytics: data.analytics, - }); - await this.config.save(); + await this.configSaveCallback(data); } }); diff --git a/src/editor.ts b/src/editor.ts index d8bb16e..ca4914b 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -2,11 +2,12 @@ import {MaterialIconProvider} from "@js-draw/material-icons"; import {PluginAsset, PluginFile} from "@/file"; import {JSON_MIME, STORAGE_PATH, SVG_MIME, TOOLBAR_FILENAME} from "@/const"; import Editor, {BackgroundComponentBackgroundType, BaseWidget, Color4, EditorEventType} from "js-draw"; -import {Dialog, getFrontend, openTab, Plugin} from "siyuan"; +import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan"; import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; import DrawJSPlugin from "@/index"; import {DefaultEditorOptions} from "@/config"; import 'js-draw/styles'; +import {SyncIDNotFoundError, UnchangedProtyleError} from "@/errors"; export class PluginEditor { @@ -23,8 +24,9 @@ export class PluginEditor { getEditor(): Editor { return this.editor; } getFileID(): string { return this.fileID; } getSyncID(): string { return this.syncID; } + setSyncID(syncID: string) { this.syncID = syncID; } - constructor(fileID: string, defaultEditorOptions: DefaultEditorOptions) { + private constructor(fileID: string) { this.fileID = fileID; @@ -34,55 +36,56 @@ export class PluginEditor { iconProvider: new MaterialIconProvider(), }); - this.genToolbar().then(() => { - this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false); - this.editor.getRootElement().style.height = '100%'; - }); - - findSyncIDInProtyle(this.fileID).then(async (syncID) => { - - if(syncID == null) { - alert( - "Couldn't find SyncID in protyle for this file.\n" + - "Make sure the drawing you're trying to edit exists in a note.\n" + - "Close this editor tab now, and try to open the editor again." - ); - return; - } - - this.syncID = syncID; - // restore drawing - this.drawingFile = new PluginAsset(this.fileID, syncID, SVG_MIME); - await this.drawingFile.loadFromSiYuanFS(); - - if(this.drawingFile.getContent() != null) { - await this.editor.loadFromSVG(this.drawingFile.getContent()); - }else{ - // it's a new drawing - this.editor.dispatch(this.editor.setBackgroundStyle({ - color: Color4.fromHex(defaultEditorOptions.background), - type: defaultEditorOptions.grid ? BackgroundComponentBackgroundType.Grid : BackgroundComponentBackgroundType.SolidColor, - autoresize: true - })); - } - - }).catch((error) => { - alert("Error loading drawing: " + error); - }); + this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false); + this.editor.getRootElement().style.height = '100%'; } - private async genToolbar() { + static async create(fileID: string, defaultEditorOptions: DefaultEditorOptions): Promise { + + const instance = new PluginEditor(fileID); + + await instance.genToolbar(); + let syncID = await findSyncIDInProtyle(fileID); + + if(syncID == null) { + throw new SyncIDNotFoundError(fileID); + } + instance.setSyncID(syncID); + await instance.restoreOrInitFile(defaultEditorOptions); + + return instance; + + } + + async restoreOrInitFile(defaultEditorOptions: DefaultEditorOptions) { + + this.drawingFile = new PluginAsset(this.fileID, this.syncID, SVG_MIME); + await this.drawingFile.loadFromSiYuanFS(); + + if(this.drawingFile.getContent() != null) { + await this.editor.loadFromSVG(this.drawingFile.getContent()); + }else{ + // it's a new drawing + this.editor.dispatch(this.editor.setBackgroundStyle({ + color: Color4.fromHex(defaultEditorOptions.background), + type: defaultEditorOptions.grid ? BackgroundComponentBackgroundType.Grid : BackgroundComponentBackgroundType.SolidColor, + autoresize: true + })); + } + + } + + async genToolbar() { const toolbar = this.editor.addToolbar(); // restore toolbarFile state this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME); - this.toolbarFile.loadFromSiYuanFS().then(() => { - if(this.toolbarFile.getContent() != null) { - toolbar.deserializeState(this.toolbarFile.getContent()); - } - }); + await this.toolbarFile.loadFromSiYuanFS(); + if(this.toolbarFile.getContent() != null) { + toolbar.deserializeState(this.toolbarFile.getContent()); + } // save button const saveButton = toolbar.addSaveButton(async () => { @@ -109,7 +112,7 @@ export class PluginEditor { newSyncID = this.drawingFile.getSyncID(); if(newSyncID != oldSyncID) { // supposed to replace protyle const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); // try to change protyle - if(!changed) throw new Error("Couldn't replace old images in protyle"); + if(!changed) throw new UnchangedProtyleError(); await this.drawingFile.removeOld(oldSyncID); } saveButton.setDisabled(true); @@ -117,7 +120,10 @@ export class PluginEditor { saveButton.setDisabled(false); }, 500); } catch (error) { - alert("Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there."); + showMessage("Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there.", 0, 'error'); + if(error instanceof UnchangedProtyleError) { + showMessage("Make sure the image you're trying to edit still exists in your documents.", 0, 'error'); + } await navigator.clipboard.writeText(svgElem.outerHTML); console.error(error); console.log("Couldn't save SVG: ", svgElem.outerHTML) @@ -133,26 +139,47 @@ export class PluginEditor { export class EditorManager { private editor: PluginEditor + setEditor(editor: PluginEditor) { this.editor = editor;} - constructor(fileID: string, defaultEditorOptions: DefaultEditorOptions) { - this.editor = new PluginEditor(fileID, defaultEditorOptions); + static async create(fileID: string, p: DrawJSPlugin) { + let instance = new EditorManager(); + try { + let editor = await PluginEditor.create(fileID, p.config.getDefaultEditorOptions()); + instance.setEditor(editor); + }catch (error) { + EditorManager.handleCreationError(error, p); + } + return instance; } static registerTab(p: DrawJSPlugin) { p.addTab({ 'type': "whiteboard", - init() { + async init() { const fileID = this.data.fileID; if (fileID == null) { alert(p.i18n.errNoFileID); return; } - const editor = new PluginEditor(fileID, p.config.getDefaultEditorOptions()); - this.element.appendChild(editor.getElement()); + try { + const editor = await PluginEditor.create(fileID, p.config.getDefaultEditorOptions()); + this.element.appendChild(editor.getElement()); + }catch (error){ + EditorManager.handleCreationError(error, p); + } } }); } + static handleCreationError(error: any, p: DrawJSPlugin) { + console.error(error); + let errorTxt = p.i18n.errCreateUnknown; + if(error instanceof SyncIDNotFoundError) { + errorTxt = p.i18n.errSyncIDNotFound; + } + showMessage(errorTxt, 0, 'error'); + } + toTab(p: Plugin) { openTab({ app: p.app, @@ -176,7 +203,7 @@ export class EditorManager { dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement()); } - async open(p: DrawJSPlugin) { + open(p: DrawJSPlugin) { if(getFrontend() != "mobile" && !p.config.options.dialogOnDesktop) { this.toTab(p); } else { diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..914bd9c --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,12 @@ + +export class SyncIDNotFoundError extends Error { + readonly fileID: string; + + constructor(fileID: string) { + super(`SyncID not found for file ${fileID}`); + this.fileID = fileID; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class UnchangedProtyleError extends Error {} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1ef36aa..9d02d6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,12 +29,12 @@ export default class DrawJSPlugin extends Plugin { id: "insert-drawing", filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"], html: getMenuHTML("iconDraw", this.i18n.insertDrawing), - callback: (protyle: Protyle) => { + callback: async (protyle: Protyle) => { void this.analytics.sendEvent('create'); const fileID = generateRandomString(); const syncID = generateTimeString() + '-' + generateRandomString(); protyle.insert(getMarkdownBlock(fileID, syncID), true, false); - new EditorManager(fileID, this.config.getDefaultEditorOptions()).open(this); + (await EditorManager.create(fileID, this)).open(this); } }]; @@ -44,9 +44,9 @@ export default class DrawJSPlugin extends Plugin { e.detail.menu.addItem({ icon: "iconDraw", label: this.i18n.editDrawing, - click: () => { + click: async () => { void this.analytics.sendEvent('edit'); - new EditorManager(ids.fileID, this.config.getDefaultEditorOptions()).open(this); + (await EditorManager.create(ids.fileID, this)).open(this); } }) })