Code quality improvements
This commit is contained in:
parent
ea9b0be856
commit
e23cc424f8
3 changed files with 176 additions and 133 deletions
172
src/editor.ts
Normal file
172
src/editor.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import {MaterialIconProvider} from "@js-draw/material-icons";
|
||||||
|
import {getFile, saveFile, uploadAsset} from "@/file";
|
||||||
|
import {DATA_PATH, JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const";
|
||||||
|
import {IDsToAssetPath} from "@/helper";
|
||||||
|
import Editor, {BaseWidget, EditorEventType} from "js-draw";
|
||||||
|
import {Dialog, Plugin, openTab, getFrontend} from "siyuan";
|
||||||
|
import {replaceSyncID} from "@/protyle";
|
||||||
|
import {removeFile} from "@/api";
|
||||||
|
|
||||||
|
export class PluginEditor {
|
||||||
|
|
||||||
|
private readonly element: HTMLElement;
|
||||||
|
private readonly editor: Editor;
|
||||||
|
|
||||||
|
private readonly fileID: string;
|
||||||
|
private syncID: string;
|
||||||
|
private readonly initialSyncID: string;
|
||||||
|
|
||||||
|
getElement(): HTMLElement { return this.element; }
|
||||||
|
getEditor(): Editor { return this.editor; }
|
||||||
|
getFileID(): string { return this.fileID; }
|
||||||
|
getSyncID(): string { return this.syncID; }
|
||||||
|
getInitialSyncID(): string { return this.initialSyncID; }
|
||||||
|
|
||||||
|
constructor(fileID: string, initialSyncID: string) {
|
||||||
|
|
||||||
|
this.element = document.createElement("div");
|
||||||
|
this.element.style.height = '100%';
|
||||||
|
this.editor = new Editor(this.element, {
|
||||||
|
iconProvider: new MaterialIconProvider(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fileID = fileID;
|
||||||
|
this.initialSyncID = initialSyncID;
|
||||||
|
this.syncID = initialSyncID;
|
||||||
|
|
||||||
|
this.genToolbar()
|
||||||
|
|
||||||
|
// restore drawing
|
||||||
|
getFile(DATA_PATH +IDsToAssetPath(fileID, initialSyncID)).then(svg => {
|
||||||
|
if(svg != null) {
|
||||||
|
this.editor.loadFromSVG(svg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false);
|
||||||
|
this.editor.getRootElement().style.height = '100%';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async genToolbar() {
|
||||||
|
|
||||||
|
const toolbar = this.editor.addToolbar();
|
||||||
|
|
||||||
|
// restore toolbar state
|
||||||
|
const toolbarState = await getFile(TOOLBAR_PATH);
|
||||||
|
if (toolbarState != null) {
|
||||||
|
toolbar.deserializeState(toolbarState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, () => {
|
||||||
|
saveFile(TOOLBAR_PATH, JSON_MIME, toolbar.serializeState());
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveCallback(saveButton: BaseWidget) {
|
||||||
|
|
||||||
|
const svgElem = this.editor.toSVG();
|
||||||
|
let newSyncID: string;
|
||||||
|
const oldSyncID = this.syncID;
|
||||||
|
|
||||||
|
try {
|
||||||
|
newSyncID = (await uploadAsset(this.fileID, SVG_MIME, svgElem.outerHTML)).syncID;
|
||||||
|
if(newSyncID != oldSyncID) {
|
||||||
|
const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID);
|
||||||
|
if(!changed) {
|
||||||
|
alert(
|
||||||
|
"Error replacing old sync ID with new one! You may need to manually replace the file path." +
|
||||||
|
"\nTry saving the drawing again. This is a bug, please open an issue as soon as you can." +
|
||||||
|
"\nIf your document doesn't show the drawing, you can recover it from the SiYuan workspace directory."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await removeFile(DATA_PATH + IDsToAssetPath(this.fileID, oldSyncID));
|
||||||
|
}
|
||||||
|
saveButton.setDisabled(true);
|
||||||
|
setTimeout(() => { // @todo improve save button feedback
|
||||||
|
saveButton.setDisabled(false);
|
||||||
|
}, 500);
|
||||||
|
} catch (error) {
|
||||||
|
alert("Error saving drawing! Enter developer mode to find the error, and a copy of the current status.");
|
||||||
|
console.error(error);
|
||||||
|
console.log("Couldn't save SVG: ", svgElem.outerHTML)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncID = newSyncID;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditorManager {
|
||||||
|
|
||||||
|
private editor: PluginEditor
|
||||||
|
|
||||||
|
constructor(editor: PluginEditor) {
|
||||||
|
this.editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static registerTab(p: Plugin) {
|
||||||
|
p.addTab({
|
||||||
|
'type': "whiteboard",
|
||||||
|
init() {
|
||||||
|
const fileID = this.data.fileID;
|
||||||
|
const initialSyncID = this.data.initialSyncID;
|
||||||
|
if (fileID == null || initialSyncID == null) {
|
||||||
|
alert("File or Sync ID and path missing - couldn't open file.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const editor = new PluginEditor(fileID, initialSyncID);
|
||||||
|
this.element.appendChild(editor.getElement());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toTab(p: Plugin) {
|
||||||
|
for(const tab of p.getOpenedTab()["whiteboard"]) {
|
||||||
|
if(tab.data.fileID == this.editor.getFileID()) {
|
||||||
|
alert("File is already open in another editor tab!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openTab({
|
||||||
|
app: p.app,
|
||||||
|
custom: {
|
||||||
|
title: 'Drawing',
|
||||||
|
icon: 'iconDraw',
|
||||||
|
id: "siyuan-jsdraw-pluginwhiteboard",
|
||||||
|
data: {
|
||||||
|
fileID: this.editor.getFileID(),
|
||||||
|
initialSyncID: this.editor.getInitialSyncID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toDialog() {
|
||||||
|
const dialog = new Dialog({
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
content: `<div id="DrawingPanel" style="width:100%; height: 100%;"></div>`,
|
||||||
|
});
|
||||||
|
dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
open(p: Plugin) {
|
||||||
|
if(getFrontend() != "mobile") {
|
||||||
|
this.toTab(p);
|
||||||
|
} else {
|
||||||
|
this.toDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
126
src/editorTab.ts
126
src/editorTab.ts
|
@ -1,126 +0,0 @@
|
||||||
import {Dialog, getFrontend, ITabModel, openTab, Plugin} from "siyuan"
|
|
||||||
import Editor, {BaseWidget, EditorEventType} from "js-draw";
|
|
||||||
import { MaterialIconProvider } from '@js-draw/material-icons';
|
|
||||||
import 'js-draw/styles';
|
|
||||||
import {getFile, saveFile, uploadAsset} from "@/file";
|
|
||||||
import {DATA_PATH, JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const";
|
|
||||||
import {replaceSyncID} from "@/protyle";
|
|
||||||
import {IDsToAssetPath} from "@/helper";
|
|
||||||
import {removeFile} from "@/api";
|
|
||||||
|
|
||||||
export function openEditorTab(p: Plugin, fileID: string, initialSyncID: string) {
|
|
||||||
if(getFrontend() == "mobile") {
|
|
||||||
const dialog = new Dialog({
|
|
||||||
width: "100vw",
|
|
||||||
height: "100vh",
|
|
||||||
content: `<div id="DrawingPanel" style="width:100%; height: 100%;"></div>`,
|
|
||||||
});
|
|
||||||
createEditor(dialog.element.querySelector("#DrawingPanel"), fileID, initialSyncID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(const tab of p.getOpenedTab()["whiteboard"]) {
|
|
||||||
if(tab.data.fileID == fileID) {
|
|
||||||
alert("File is already open in another editor tab!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openTab({
|
|
||||||
app: p.app,
|
|
||||||
custom: {
|
|
||||||
title: 'Drawing',
|
|
||||||
icon: 'iconDraw',
|
|
||||||
id: "siyuan-jsdraw-pluginwhiteboard",
|
|
||||||
data: {
|
|
||||||
fileID: fileID,
|
|
||||||
initialSyncID: initialSyncID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveCallback(editor: Editor, fileID: string, oldSyncID: string, saveButton: BaseWidget): Promise<string> {
|
|
||||||
|
|
||||||
const svgElem = editor.toSVG();
|
|
||||||
let newSyncID;
|
|
||||||
|
|
||||||
try {
|
|
||||||
newSyncID = (await uploadAsset(fileID, SVG_MIME, svgElem.outerHTML)).syncID;
|
|
||||||
if(newSyncID != oldSyncID) {
|
|
||||||
const changed = await replaceSyncID(fileID, oldSyncID, newSyncID);
|
|
||||||
if(!changed) {
|
|
||||||
alert(
|
|
||||||
"Error replacing old sync ID with new one! You may need to manually replace the file path." +
|
|
||||||
"\nTry saving the drawing again. This is a bug, please open an issue as soon as you can." +
|
|
||||||
"\nIf your document doesn't show the drawing, you can recover it from the SiYuan workspace directory."
|
|
||||||
);
|
|
||||||
return oldSyncID;
|
|
||||||
}
|
|
||||||
await removeFile(DATA_PATH + IDsToAssetPath(fileID, oldSyncID));
|
|
||||||
}
|
|
||||||
saveButton.setDisabled(true);
|
|
||||||
setTimeout(() => { // @todo improve save button feedback
|
|
||||||
saveButton.setDisabled(false);
|
|
||||||
}, 500);
|
|
||||||
} catch (error) {
|
|
||||||
alert("Error saving drawing! Enter developer mode to find the error, and a copy of the current status.");
|
|
||||||
console.error(error);
|
|
||||||
console.log("Couldn't save SVG: ", svgElem.outerHTML)
|
|
||||||
return oldSyncID;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newSyncID
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createEditor(element: HTMLElement, fileID: string, initialSyncID: string) {
|
|
||||||
|
|
||||||
const editor = new Editor(element, {
|
|
||||||
iconProvider: new MaterialIconProvider(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const toolbar = editor.addToolbar();
|
|
||||||
|
|
||||||
// restore toolbar state
|
|
||||||
getFile(TOOLBAR_PATH).then(toolbarState => {
|
|
||||||
if(toolbarState!= null) {
|
|
||||||
toolbar.deserializeState(toolbarState)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// restore drawing
|
|
||||||
getFile(DATA_PATH +IDsToAssetPath(fileID, initialSyncID)).then(svg => {
|
|
||||||
if(svg != null) {
|
|
||||||
editor.loadFromSVG(svg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let syncID = initialSyncID;
|
|
||||||
// save logic
|
|
||||||
const saveButton = toolbar.addSaveButton(() => {
|
|
||||||
saveCallback(editor, fileID, syncID, saveButton).then(
|
|
||||||
newSyncID => {
|
|
||||||
syncID = newSyncID
|
|
||||||
}
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// save toolbar config on tool change (toolbar state is not saved in SVGs!)
|
|
||||||
editor.notifier.on(EditorEventType.ToolUpdated, () => {
|
|
||||||
saveFile(TOOLBAR_PATH, JSON_MIME, toolbar.serializeState());
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.dispatch(editor.setBackgroundStyle({ autoresize: true }), false);
|
|
||||||
editor.getRootElement().style.height = '100%';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editorTabInit(tab: ITabModel) {
|
|
||||||
|
|
||||||
const fileID = tab.data.fileID;
|
|
||||||
const initialSyncID = tab.data.initialSyncID;
|
|
||||||
if (fileID == null || initialSyncID == null) {
|
|
||||||
alert("File or Sync ID and path missing - couldn't open file.")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createEditor(tab.element, fileID, initialSyncID);
|
|
||||||
|
|
||||||
}
|
|
11
src/index.ts
11
src/index.ts
|
@ -6,18 +6,15 @@ import {
|
||||||
findImgSrc,
|
findImgSrc,
|
||||||
imgSrcToIDs, generateTimeString, generateRandomString
|
imgSrcToIDs, generateTimeString, generateRandomString
|
||||||
} from "@/helper";
|
} from "@/helper";
|
||||||
import {editorTabInit, openEditorTab} from "@/editorTab";
|
|
||||||
import {migrate} from "@/migration";
|
import {migrate} from "@/migration";
|
||||||
|
import {EditorManager, PluginEditor} from "@/editor";
|
||||||
|
|
||||||
export default class DrawJSPlugin extends Plugin {
|
export default class DrawJSPlugin extends Plugin {
|
||||||
|
|
||||||
onload() {
|
onload() {
|
||||||
|
|
||||||
loadIcons(this);
|
loadIcons(this);
|
||||||
this.addTab({
|
EditorManager.registerTab(this);
|
||||||
'type': "whiteboard",
|
|
||||||
init() { editorTabInit(this) }
|
|
||||||
});
|
|
||||||
migrate()
|
migrate()
|
||||||
|
|
||||||
this.protyleSlash = [{
|
this.protyleSlash = [{
|
||||||
|
@ -28,7 +25,7 @@ export default class DrawJSPlugin extends Plugin {
|
||||||
const fileID = generateRandomString();
|
const fileID = generateRandomString();
|
||||||
const syncID = generateTimeString() + '-' + generateRandomString();
|
const syncID = generateTimeString() + '-' + generateRandomString();
|
||||||
protyle.insert(getMarkdownBlock(fileID, syncID), true, false);
|
protyle.insert(getMarkdownBlock(fileID, syncID), true, false);
|
||||||
openEditorTab(this, fileID, syncID);
|
new EditorManager(new PluginEditor(fileID, syncID)).open(this)
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
@ -39,7 +36,7 @@ export default class DrawJSPlugin extends Plugin {
|
||||||
icon: "iconDraw",
|
icon: "iconDraw",
|
||||||
label: "Edit with js-draw",
|
label: "Edit with js-draw",
|
||||||
click: () => {
|
click: () => {
|
||||||
openEditorTab(this, ids.fileID, ids.syncID);
|
new EditorManager(new PluginEditor(ids.fileID, ids.syncID)).open(this)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue