From f35342a791d92f80d34cbc4bcddee6fa85a8f694 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Sun, 20 Apr 2025 22:16:48 +0200 Subject: [PATCH 1/9] Start workin on i18n --- public/i18n/en_US.json | 24 +++++++++++++++++++++++- src/config.ts | 23 +++++++++-------------- src/editor.ts | 4 ++-- src/index.ts | 2 +- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json index b6d2382..88c96a3 100644 --- a/public/i18n/en_US.json +++ b/public/i18n/en_US.json @@ -1,3 +1,25 @@ { - "insertDrawing": "Insert Drawing" + "insertDrawing": "Insert Drawing", + "editDrawing": "Edit with js-draw", + "errNoFileID": "File ID missing - couldn't open file.", + "drawing": "Drawing", + "settings": { + "name": "js-draw Plugin Settings", + "grid": { + "title": "Enable grid by default", + "description": "Enable to automatically turn on the grid on new drawings." + }, + "background": { + "title": "Default background Color", + "description": "Default background color for new drawings, in hexadecimal." + }, + "dialogOnDesktop": { + "title": "Open editor as dialog on desktop", + "description": "Dialog mode provides a larger drawing area, but it's not as handy to use as tabs (default).
The editor will always open as a dialog on mobile." + }, + "analytics": { + "title": "Analytics", + "description": "Enable to send anonymous usage data to the developer. Privacy Policy" + } + } } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 52fd764..4491329 100644 --- a/src/config.ts +++ b/src/config.ts @@ -87,6 +87,7 @@ export class PluginConfigViewer { this.settingUtils = new SettingUtils({ plugin: this.plugin, + name: this.plugin.i18n.settings.name, callback: async (data) => { this.config.setConfig({ grid: data.grid, @@ -100,38 +101,32 @@ export class PluginConfigViewer { this.settingUtils.addItem({ key: "grid", - title: "Enable grid by default", - description: "Enable to automatically turn on the grid on new drawings.", + title: this.plugin.i18n.settings.grid.title, + description: this.plugin.i18n.settings.grid.description, value: this.config.options.grid, type: 'checkbox' }); this.settingUtils.addItem({ key: "background", - title: "Default background Color", - description: "Default background color of the drawing area for new drawings in hexadecimal.", + title: this.plugin.i18n.settings.background.title, + description: this.plugin.i18n.settings.background.description, value: this.config.options.background, type: 'textarea', }); this.settingUtils.addItem({ key: "dialogOnDesktop", - title: "Open editor as dialog on desktop", - description: ` - Dialog mode provides a larger drawing area, but it's not as handy to use as tabs (default).
- The editor will always open as a dialog on mobile. - `, + title: this.plugin.i18n.settings.dialogOnDesktop.title, + description: this.plugin.i18n.settings.dialogOnDesktop.description, value: this.config.options.dialogOnDesktop, type: 'checkbox' }); this.settingUtils.addItem({ key: "analytics", - title: "Analytics", - description: ` - Enable to send anonymous usage data to the developer. - Privacy - `, + title: this.plugin.i18n.settings.analytics.title, + description: this.plugin.i18n.settings.analytics.description, value: this.config.options.analytics, type: 'checkbox' }); diff --git a/src/editor.ts b/src/editor.ts index 8680a40..d8bb16e 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -144,7 +144,7 @@ export class EditorManager { init() { const fileID = this.data.fileID; if (fileID == null) { - alert("File ID missing - couldn't open file.") + alert(p.i18n.errNoFileID); return; } const editor = new PluginEditor(fileID, p.config.getDefaultEditorOptions()); @@ -157,7 +157,7 @@ export class EditorManager { openTab({ app: p.app, custom: { - title: 'Drawing', + title: p.i18n.drawing, icon: 'iconDraw', id: "siyuan-jsdraw-pluginwhiteboard", data: { diff --git a/src/index.ts b/src/index.ts index 599bf3a..1ef36aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,7 @@ export default class DrawJSPlugin extends Plugin { if (ids === null) return; e.detail.menu.addItem({ icon: "iconDraw", - label: "Edit with js-draw", + label: this.i18n.editDrawing, click: () => { void this.analytics.sendEvent('edit'); new EditorManager(ids.fileID, this.config.getDefaultEditorOptions()).open(this); From 8d4779b8fe3828e1e97e79a2e2fb06917a35c762 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Wed, 23 Apr 2025 09:52:45 +0200 Subject: [PATCH 2/9] 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); } }) }) From 1ad26d1e2329fdfe6120adea1d87a1a64f105eda Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Thu, 1 May 2025 23:01:55 +0200 Subject: [PATCH 3/9] Add funding link --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 3f18451..b2832fc 100644 --- a/plugin.json +++ b/plugin.json @@ -31,7 +31,7 @@ }, "funding": { "custom": [ - "" + "https://s.massive.box/jsdraw-plugin-donate" ] }, "keywords": [ From fa3eba219e55c04d47bda16c1cbac8da59c19db4 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Mon, 5 May 2025 19:17:59 +0200 Subject: [PATCH 4/9] Suggest popular background colors, add transparency support Making the UI more user-friendly by suggesting some commonly used colors in the Settings menu --- public/i18n/en_US.json | 16 ++++++++++++++-- src/config.ts | 40 +++++++++++++++++++++++++++++++++------- src/editor.ts | 10 +++++----- src/helper.ts | 7 ------- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json index a282081..aa868b1 100644 --- a/public/i18n/en_US.json +++ b/public/i18n/en_US.json @@ -8,13 +8,25 @@ "drawing": "Drawing", "settings": { "name": "js-draw Plugin Settings", + "suggestedColors":{ + "white": "White", + "black": "Black", + "transparent": "Transparent", + "custom": "Custom", + "darkBlue": "Dark Blue", + "darkGray": "Dark Gray" + }, "grid": { "title": "Enable grid by default", "description": "Enable to automatically turn on the grid on new drawings." }, + "backgroundDropdown":{ + "title": "Background color", + "description": "Default background color for new drawings." + }, "background": { - "title": "Default background Color", - "description": "Default background color for new drawings, in hexadecimal." + "title": "Custom background", + "description": "Hexadecimal code of the custom background color for new drawings.
This setting is only applied if \"Background Color\" is set to \"Custom\"!" }, "dialogOnDesktop": { "title": "Open editor as dialog on desktop", diff --git a/src/config.ts b/src/config.ts index 59842eb..dd1c3ea 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,6 @@ import {PluginFile} from "@/file"; import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const"; import {Plugin, showMessage} from "siyuan"; import {SettingUtils} from "@/libs/setting-utils"; -import {validateColor} from "@/helper"; type Options = { grid: boolean @@ -48,7 +47,7 @@ export class PluginConfig { private loadDefaultConfig() { this.options = { grid: true, - background: "#000000", + background: "#00000000", dialogOnDesktop: false, analytics: true, }; @@ -61,10 +60,16 @@ export class PluginConfig { } setConfig(config: Options) { - this.options = config; } + static validateColor(hex: string) { + hex = hex.replace('#', ''); + return typeof hex === 'string' + && (hex.length === 6 || hex.length === 8) + && !isNaN(Number('0x' + hex)) + } + } export class PluginConfigViewer { @@ -72,23 +77,34 @@ export class PluginConfigViewer { config: PluginConfig; settingUtils: SettingUtils; plugin: Plugin; + private readonly backgroundDropdownOptions; constructor(config: PluginConfig, plugin: Plugin) { this.config = config; this.plugin = plugin; + this.backgroundDropdownOptions = { + '#00000000': plugin.i18n.settings.suggestedColors.transparent, + 'CUSTOM': plugin.i18n.settings.suggestedColors.custom, + '#ffffff': plugin.i18n.settings.suggestedColors.white, + '#1e2227': plugin.i18n.settings.suggestedColors.darkBlue, + '#1e1e1e': plugin.i18n.settings.suggestedColors.darkGray, + '#000000': plugin.i18n.settings.suggestedColors.black, + } this.populateSettingMenu(); } async configSaveCallback(data) { - if(!validateColor(data.background)) { + let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown; + if(!PluginConfig.validateColor(color)) { 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, + background: color, dialogOnDesktop: data.dialogOnDesktop, analytics: data.analytics, }); @@ -100,7 +116,7 @@ export class PluginConfigViewer { this.settingUtils = new SettingUtils({ plugin: this.plugin, - name: this.plugin.i18n.settings.name, + name: 'optionsUI', callback: async (data) => { await this.configSaveCallback(data); } @@ -114,12 +130,22 @@ export class PluginConfigViewer { type: 'checkbox' }); + this.settingUtils.addItem({ + key: 'backgroundDropdown', + title: this.plugin.i18n.settings.backgroundDropdown.title, + description: this.plugin.i18n.settings.backgroundDropdown.description, + type: 'select', + value: this.config.options.background in this.backgroundDropdownOptions ? + this.config.options.background : 'CUSTOM', + options: this.backgroundDropdownOptions, + }); + this.settingUtils.addItem({ key: "background", title: this.plugin.i18n.settings.background.title, description: this.plugin.i18n.settings.background.description, value: this.config.options.background, - type: 'textarea', + type: 'textinput', }); this.settingUtils.addItem({ diff --git a/src/editor.ts b/src/editor.ts index ca4914b..804a1e1 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -80,6 +80,11 @@ export class PluginEditor { const toolbar = this.editor.addToolbar(); + // save button + const saveButton = toolbar.addSaveButton(async () => { + await this.saveCallback(saveButton); + }); + // restore toolbarFile state this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME); await this.toolbarFile.loadFromSiYuanFS(); @@ -87,11 +92,6 @@ export class PluginEditor { toolbar.deserializeState(this.toolbarFile.getContent()); } - // save button - const saveButton = toolbar.addSaveButton(async () => { - await this.saveCallback(saveButton); - }); - // save toolbar config on tool change (toolbar state is not saved in SVGs!) this.editor.notifier.on(EditorEventType.ToolUpdated, () => { this.toolbarFile.setContent(toolbar.serializeState()); diff --git a/src/helper.ts b/src/helper.ts index 5a08636..22c3e8c 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -105,11 +105,4 @@ export function imgSrcToIDs(imgSrc: string | null): { fileID: string; syncID: st return assetPathToIDs(imgSrc); -} - -export function validateColor(hex: string) { - hex = hex.replace('#', ''); - return typeof hex === 'string' - && hex.length === 6 - && !isNaN(Number('0x' + hex)) } \ No newline at end of file From 764f9fe5a450744ef8c34ed07e79c75e86967518 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Tue, 6 May 2025 18:19:18 +0200 Subject: [PATCH 5/9] Add option to remember editor position and zoom --- public/i18n/en_US.json | 4 ++++ src/config.ts | 15 ++++++++++++++- src/editor.ts | 35 ++++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/public/i18n/en_US.json b/public/i18n/en_US.json index aa868b1..fbeb088 100644 --- a/public/i18n/en_US.json +++ b/public/i18n/en_US.json @@ -35,6 +35,10 @@ "analytics": { "title": "Analytics", "description": "Enable to send anonymous usage data to the developer. Privacy Policy" + }, + "restorePosition": { + "title": "Remember editor position", + "description": "When enabled, the editor will remember the zoom factor and position, and it will restore them the next time you open the drawing." } } } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index dd1c3ea..11f5c2b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ import {Plugin, showMessage} from "siyuan"; import {SettingUtils} from "@/libs/setting-utils"; type Options = { + restorePosition: boolean; grid: boolean background: string dialogOnDesktop: boolean @@ -11,6 +12,7 @@ type Options = { }; export type DefaultEditorOptions = { + restorePosition: boolean; grid: boolean background: string } @@ -30,8 +32,9 @@ export class PluginConfig { getDefaultEditorOptions(): DefaultEditorOptions { return { + restorePosition: this.options.restorePosition, grid: this.options.grid, - background: this.options.background, + background: this.options.background }; } @@ -50,6 +53,7 @@ export class PluginConfig { background: "#00000000", dialogOnDesktop: false, analytics: true, + restorePosition: true, }; this.firstRun = true; } @@ -107,6 +111,7 @@ export class PluginConfigViewer { background: color, dialogOnDesktop: data.dialogOnDesktop, analytics: data.analytics, + restorePosition: data.restorePosition, }); await this.config.save(); @@ -148,6 +153,14 @@ export class PluginConfigViewer { type: 'textinput', }); + this.settingUtils.addItem({ + key: "restorePosition", + title: this.plugin.i18n.settings.restorePosition.title, + description: this.plugin.i18n.settings.restorePosition.description, + value: this.config.options.restorePosition, + type: 'checkbox' + }); + this.settingUtils.addItem({ key: "dialogOnDesktop", title: this.plugin.i18n.settings.dialogOnDesktop.title, diff --git a/src/editor.ts b/src/editor.ts index 804a1e1..cba875d 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,7 +1,15 @@ 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 Editor, { + BackgroundComponentBackgroundType, + BaseWidget, + Color4, + EditorEventType, + Mat33, + Vec2, + Viewport +} from "js-draw"; import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan"; import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; import DrawJSPlugin from "@/index"; @@ -62,9 +70,26 @@ export class PluginEditor { this.drawingFile = new PluginAsset(this.fileID, this.syncID, SVG_MIME); await this.drawingFile.loadFromSiYuanFS(); + const drawingContent = this.drawingFile.getContent(); + + if(drawingContent != null) { + + await this.editor.loadFromSVG(drawingContent); + + // restore position and zoom + const svgElem = new DOMParser().parseFromString(drawingContent, SVG_MIME).documentElement; + const editorViewStr = svgElem.getAttribute('editorView'); + if(editorViewStr != null && defaultEditorOptions.restorePosition) { + try { + const [viewBoxOriginX, viewBoxOriginY, zoom] = editorViewStr.split(' ').map(x => parseFloat(x)); + this.editor.dispatch(Viewport.transformBy(Mat33.scaling2D(zoom))); + this.editor.dispatch(Viewport.transformBy(Mat33.translation(Vec2.of( + - viewBoxOriginX, + - viewBoxOriginY + )))); + }catch (e){} + } - if(this.drawingFile.getContent() != null) { - await this.editor.loadFromSVG(this.drawingFile.getContent()); }else{ // it's a new drawing this.editor.dispatch(this.editor.setBackgroundStyle({ @@ -106,6 +131,10 @@ export class PluginEditor { let newSyncID: string; const oldSyncID = this.syncID; + const rect = this.editor.viewport.visibleRect; + const zoom = this.editor.viewport.getScaleFactor(); + svgElem.setAttribute('editorView', `${rect.x} ${rect.y} ${zoom}`) + try { this.drawingFile.setContent(svgElem.outerHTML); await this.drawingFile.save(); From 77e8218d1f20f8fa684b32b614917075ca044ce1 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Tue, 6 May 2025 23:12:51 +0200 Subject: [PATCH 6/9] Config improvements and compatibility with old versions --- package.json | 3 ++- src/config.ts | 63 ++++++++++++++++++++++----------------------------- src/editor.ts | 10 ++++---- src/helper.ts | 8 +++++++ 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index f355588..fc0d354 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@js-draw/material-icons": "^1.29.0", - "js-draw": "^1.29.0" + "js-draw": "^1.29.0", + "ts-serializable": "^4.2.0" } } diff --git a/src/config.ts b/src/config.ts index 11f5c2b..7c4bfca 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,16 +2,14 @@ import {PluginFile} from "@/file"; import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const"; import {Plugin, showMessage} from "siyuan"; import {SettingUtils} from "@/libs/setting-utils"; +import {getFirstDefined} from "@/helper"; -type Options = { - restorePosition: boolean; - grid: boolean - background: string +export interface Options { dialogOnDesktop: boolean analytics: boolean -}; - -export type DefaultEditorOptions = { + editorOptions: EditorOptions +} +export interface EditorOptions { restorePosition: boolean; grid: boolean background: string @@ -30,32 +28,23 @@ export class PluginConfig { this.file = new PluginFile(STORAGE_PATH, CONFIG_FILENAME, JSON_MIME); } - getDefaultEditorOptions(): DefaultEditorOptions { - return { - restorePosition: this.options.restorePosition, - grid: this.options.grid, - background: this.options.background - }; - } - async load() { this.firstRun = false; await this.file.loadFromSiYuanFS(); - this.options = JSON.parse(this.file.getContent()); - if(this.options == null) { - this.loadDefaultConfig(); + const jsonObj = JSON.parse(this.file.getContent()); + if(jsonObj == null) { + this.firstRun = true; } - } - - private loadDefaultConfig() { + // if more than one fallback, the intermediate ones are from a legacy config file version this.options = { - grid: true, - background: "#00000000", - dialogOnDesktop: false, - analytics: true, - restorePosition: true, + dialogOnDesktop: getFirstDefined(jsonObj?.dialogOnDesktop, false), + analytics: getFirstDefined(jsonObj?.analytics, true), + editorOptions: { + restorePosition: getFirstDefined(jsonObj?.editorOptions?.restorePosition, jsonObj?.restorePosition, true), + grid: getFirstDefined(jsonObj?.editorOptions?.grid, jsonObj?.grid, true), + background: getFirstDefined(jsonObj?.editorOptions?.background, jsonObj?.background, "#00000000") + }, }; - this.firstRun = true; } async save() { @@ -102,16 +91,18 @@ export class PluginConfigViewer { let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown; if(!PluginConfig.validateColor(color)) { showMessage(this.plugin.i18n.errInvalidBackgroundColor, 0, 'error'); - data.background = this.config.options.background; + data.background = this.config.options.editorOptions.background; this.settingUtils.set('background', data.background); } this.config.setConfig({ - grid: data.grid, - background: color, dialogOnDesktop: data.dialogOnDesktop, analytics: data.analytics, - restorePosition: data.restorePosition, + editorOptions: { + grid: data.grid, + background: color, + restorePosition: data.restorePosition, + } }); await this.config.save(); @@ -131,7 +122,7 @@ export class PluginConfigViewer { key: "grid", title: this.plugin.i18n.settings.grid.title, description: this.plugin.i18n.settings.grid.description, - value: this.config.options.grid, + value: this.config.options.editorOptions.grid, type: 'checkbox' }); @@ -140,8 +131,8 @@ export class PluginConfigViewer { title: this.plugin.i18n.settings.backgroundDropdown.title, description: this.plugin.i18n.settings.backgroundDropdown.description, type: 'select', - value: this.config.options.background in this.backgroundDropdownOptions ? - this.config.options.background : 'CUSTOM', + value: this.config.options.editorOptions.background in this.backgroundDropdownOptions ? + this.config.options.editorOptions.background : 'CUSTOM', options: this.backgroundDropdownOptions, }); @@ -149,7 +140,7 @@ export class PluginConfigViewer { key: "background", title: this.plugin.i18n.settings.background.title, description: this.plugin.i18n.settings.background.description, - value: this.config.options.background, + value: this.config.options.editorOptions.background, type: 'textinput', }); @@ -157,7 +148,7 @@ export class PluginConfigViewer { key: "restorePosition", title: this.plugin.i18n.settings.restorePosition.title, description: this.plugin.i18n.settings.restorePosition.description, - value: this.config.options.restorePosition, + value: this.config.options.editorOptions.restorePosition, type: 'checkbox' }); diff --git a/src/editor.ts b/src/editor.ts index cba875d..feb7094 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -13,7 +13,7 @@ import Editor, { import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan"; import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; import DrawJSPlugin from "@/index"; -import {DefaultEditorOptions} from "@/config"; +import {EditorOptions} from "@/config"; import 'js-draw/styles'; import {SyncIDNotFoundError, UnchangedProtyleError} from "@/errors"; @@ -49,7 +49,7 @@ export class PluginEditor { } - static async create(fileID: string, defaultEditorOptions: DefaultEditorOptions): Promise { + static async create(fileID: string, defaultEditorOptions: EditorOptions): Promise { const instance = new PluginEditor(fileID); @@ -66,7 +66,7 @@ export class PluginEditor { } - async restoreOrInitFile(defaultEditorOptions: DefaultEditorOptions) { + async restoreOrInitFile(defaultEditorOptions: EditorOptions) { this.drawingFile = new PluginAsset(this.fileID, this.syncID, SVG_MIME); await this.drawingFile.loadFromSiYuanFS(); @@ -173,7 +173,7 @@ export class EditorManager { static async create(fileID: string, p: DrawJSPlugin) { let instance = new EditorManager(); try { - let editor = await PluginEditor.create(fileID, p.config.getDefaultEditorOptions()); + let editor = await PluginEditor.create(fileID, p.config.options.editorOptions); instance.setEditor(editor); }catch (error) { EditorManager.handleCreationError(error, p); @@ -191,7 +191,7 @@ export class EditorManager { return; } try { - const editor = await PluginEditor.create(fileID, p.config.getDefaultEditorOptions()); + const editor = await PluginEditor.create(fileID, p.config.options.editorOptions); this.element.appendChild(editor.getElement()); }catch (error){ EditorManager.handleCreationError(error, p); diff --git a/src/helper.ts b/src/helper.ts index 22c3e8c..7041ba5 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -105,4 +105,12 @@ export function imgSrcToIDs(imgSrc: string | null): { fileID: string; syncID: st return assetPathToIDs(imgSrc); +} + +export function getFirstDefined(...a) { + for(let i = 0; i < a.length; i++) { + if(a[i] !== undefined) { + return a[i]; + } + } } \ No newline at end of file From 5322944ad9c2bc93c54a58e854292dd17fd7bdf9 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Wed, 7 May 2025 21:16:50 +0200 Subject: [PATCH 7/9] Add custom cursor on editor canvas --- public/webapp/cursor.png | Bin 0 -> 719 bytes src/editor.ts | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 public/webapp/cursor.png diff --git a/public/webapp/cursor.png b/public/webapp/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..1306cf30de52d1f03364dd87a0c10082ae02e3d9 GIT binary patch literal 719 zcmV;=0xEX>4Tx04R}tkv&L4Q5c4wdo7GmByxyoxI;DNQW1@1fdmC&1!@i5A2q>ylY6hU zI0mgkLqAJ@LrZh54Xr^C^aIfzG!?ak+&C&Ik$uZ~c+dBKobNp#9~E@d>6il3a_vMc zsw7is#s5ktetLkAsyoH$czm9=^>vRV&bx@1)xPh~3K|(l2T1{@^rG#8v<6IUx<$cz zKv-W_4Uo=(5j&YmgY+nLt6p8YX@~6s8Z=A|4;81!dkam!KM~{f2=k08@8B6xBy% z-cSopbi=%i>M~M6@FbWyFmst;k+=TO8vQl;FonDBlV`!M`>5vVdoVH37%;(-TOdow z#gL0IVK+tcjB`^&HRs|EdPOn~lPRq;8Y8WX3IHN^NQChm>cap4002ovPDHLkV1m33 BHh};D literal 0 HcmV?d00001 diff --git a/src/editor.ts b/src/editor.ts index feb7094..1331e88 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -44,6 +44,14 @@ export class PluginEditor { iconProvider: new MaterialIconProvider(), }); + const styleElement = document.createElement('style'); + styleElement.innerHTML = ` + canvas.wetInkCanvas { + cursor: url('/plugins/siyuan-jsdraw-plugin/webapp/cursor.png') 6 6, auto; + } + `; + this.element.appendChild(styleElement); + this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false); this.editor.getRootElement().style.height = '100%'; From a079298433fefb6a795e5ecee959a293eec7736a Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Thu, 8 May 2025 22:47:09 +0200 Subject: [PATCH 8/9] Add CI --- .../workflows/build.yml | 37 ++++++++++++++----- scripts/validate_tag.cjs | 24 ++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) rename .github/workflows/release.yml => .forgejo/workflows/build.yml (60%) create mode 100644 scripts/validate_tag.cjs diff --git a/.github/workflows/release.yml b/.forgejo/workflows/build.yml similarity index 60% rename from .github/workflows/release.yml rename to .forgejo/workflows/build.yml index 49834e5..4bfcc57 100644 --- a/.github/workflows/release.yml +++ b/.forgejo/workflows/build.yml @@ -1,7 +1,9 @@ -name: Create Release on Tag Push +name: Build on Push and create Release on Tag on: push: + branches: + - main tags: - "v*" @@ -20,7 +22,7 @@ jobs: node-version: 20 registry-url: "https://registry.npmjs.org" - # Install pnpm + # Install pnpm - name: Install pnpm uses: pnpm/action-setup@v4 id: pnpm-install @@ -28,6 +30,12 @@ jobs: version: 8 run_install: false + # Validate Tag Matches JSON Versions + - name: Validate Tag Matches JSON Versions + if: github.ref_type == 'tag' + run: | + node scripts/validate_tag.cjs ${{ github.ref }} + # Get pnpm store directory - name: Get pnpm store directory id: pnpm-cache @@ -52,11 +60,22 @@ jobs: - name: Build for production run: pnpm build - - name: Release - uses: ncipollo/release-action@v1 + # Move file + - name: Move file + run: mkdir built; mv package.zip built/package.zip + + # Upload artifacts + - name: Upload artifacts + uses: actions/upload-artifact@v3 with: - allowUpdates: true - artifactErrorsFailBuild: true - artifacts: "package.zip" - token: ${{ secrets.GITHUB_TOKEN }} - prerelease: false + path: built/package.zip + overwrite: true + + # Create Forgejo Release + - name: Create Forgejo Release + if: github.ref_type == 'tag' + uses: actions/forgejo-release@v1 + with: + direction: upload + release-dir: built + token: ${{ secrets.FORGE_TOKEN }} diff --git a/scripts/validate_tag.cjs b/scripts/validate_tag.cjs new file mode 100644 index 0000000..c842ffc --- /dev/null +++ b/scripts/validate_tag.cjs @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); + +const [tagName] = process.argv.slice(2); // Get tag from CLI arguments +if (!tagName) { + console.error('Error: No tag name provided.'); + process.exit(1); +} + +const TAG_VERSION = tagName.replace('refs/tags/v', ''); + +try { + const packageJson = JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8')); + const pluginJson = JSON.parse(fs.readFileSync(path.resolve('plugin.json'), 'utf8')); + + if (TAG_VERSION !== packageJson.version || TAG_VERSION !== pluginJson.version) { + console.error(`Error: Tag version (${TAG_VERSION}) does not match package.json (${packageJson.version}) or plugin.json (${pluginJson.version})`); + process.exit(1); + } + console.log('Tag version matches both JSON files.'); +} catch (err) { + console.error('Failed to read or parse JSON files:', err.message); + process.exit(1); +} From 17d4e5938bba5a7b22d292a7f3d733d604f5d991 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Fri, 9 May 2025 22:57:42 +0200 Subject: [PATCH 9/9] Version bump --- package.json | 2 +- plugin.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fc0d354..76504eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "siyuan-jsdraw-plugin", - "version": "0.3.0", + "version": "0.4.0", "type": "module", "description": "Include a whiteboard for freehand drawing anywhere in your documents.", "repository": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin", diff --git a/plugin.json b/plugin.json index b2832fc..8acdb80 100644 --- a/plugin.json +++ b/plugin.json @@ -2,7 +2,7 @@ "name": "siyuan-jsdraw-plugin", "author": "massivebox", "url": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin", - "version": "0.3.0", + "version": "0.4.0", "minAppVersion": "3.0.12", "backends": [ "windows",