Improve error handling and code structure
This commit is contained in:
		
							parent
							
								
									f35342a791
								
							
						
					
					
						commit
						8d4779b8fe
					
				
					 5 changed files with 116 additions and 67 deletions
				
			
		|  | @ -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", | ||||
|  |  | |||
|  | @ -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); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										129
									
								
								src/editor.ts
									
										
									
									
									
								
							
							
						
						
									
										129
									
								
								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<PluginEditor> { | ||||
| 
 | ||||
|         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 { | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/errors.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/errors.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 {} | ||||
|  | @ -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); | ||||
|                 } | ||||
|             }) | ||||
|         }) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue