#603 browser-fs-access library used to create new extension

master
agriyadev5 2021-08-23 15:59:09 +05:30
parent 8b8db12be6
commit 1f7725b7ae
8 changed files with 211 additions and 24 deletions

11
package-lock.json generated
View File

@ -9,6 +9,7 @@
"license": "(MIT AND Apache-2.0 AND ISC AND LGPL-3.0-or-later AND X11)",
"dependencies": {
"@babel/polyfill": "7.12.1",
"browser-fs-access": "^0.20.4",
"canvg": "3.0.7",
"core-js": "3.16.2",
"elix": "15.0.0",
@ -5299,6 +5300,11 @@
"node": ">= 10.16.0"
}
},
"node_modules/browser-fs-access": {
"version": "0.20.4",
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.20.4.tgz",
"integrity": "sha512-rSbY1AIoDe+fvYZ1LiRDdKBnytfsd1nN/GKS/DRZAhaJkz3cfbp14IHw5lk4FFWBelD6Sw6EtdnAI990ZuBZjg=="
},
"node_modules/browser-pack": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
@ -26396,6 +26402,11 @@
"duplexer": "0.1.1"
}
},
"browser-fs-access": {
"version": "0.20.4",
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.20.4.tgz",
"integrity": "sha512-rSbY1AIoDe+fvYZ1LiRDdKBnytfsd1nN/GKS/DRZAhaJkz3cfbp14IHw5lk4FFWBelD6Sw6EtdnAI990ZuBZjg=="
},
"browser-pack": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",

View File

@ -77,6 +77,7 @@
],
"dependencies": {
"@babel/polyfill": "7.12.1",
"browser-fs-access": "^0.20.4",
"canvg": "3.0.7",
"core-js": "3.16.2",
"elix": "15.0.0",

View File

@ -307,9 +307,6 @@ class MainMenu {
// eslint-disable-next-line no-unsanitized/property
template.innerHTML = `
<se-menu id="main_button" label="SVG-Edit" src="${imgPath}/logo.svg" alt="logo">
<se-menu-item id="tool_clear" label="${i18next.t('tools.new_doc')}" shortcut="N" src="${imgPath}/new.svg"></se-menu-item>
<se-menu-item id="tool_open" label="${i18next.t('tools.open_doc')}" src="${imgPath}/open.svg"></se-menu-item>
<se-menu-item id="tool_save" label="${i18next.t('tools.save_doc')}" shortcut="S" src="${imgPath}/saveImg.svg"></se-menu-item>
<se-menu-item id="tool_import" label="${i18next.t('tools.import_doc')}" src="${imgPath}/importImg.svg"></se-menu-item>
<se-menu-item id="tool_export" label="${i18next.t('tools.export_img')}" src="${imgPath}/export.svg"></se-menu-item>
<se-menu-item id="tool_docprops" label="${i18next.t('tools.docprops')}" shortcut="D" src="${imgPath}/docprop.svg"></se-menu-item>
@ -324,29 +321,10 @@ class MainMenu {
* Associate all button actions as well as non-button keyboard shortcuts.
*/
$id("tool_clear").addEventListener("click", this.clickClear.bind(this));
$id("tool_open").addEventListener("click", (e) => {
e.preventDefault();
this.clickOpen();
window.dispatchEvent(new CustomEvent("openImage"));
});
$id("tool_import").addEventListener("click", () => {
this.clickImport();
window.dispatchEvent(new CustomEvent("importImages"));
});
$id("tool_save").addEventListener(
"click",
function() {
const $editorDialog = $id("se-svg-editor-dialog");
const editingsource = $editorDialog.getAttribute("dialog") === "open";
if (editingsource) {
this.saveSourceEditor();
} else {
this.clickSave();
}
}.bind(this)
);
// this.clickExport.bind(this)
$id("tool_export").addEventListener("click", function() {
document
.getElementById("se-export-dialog")

View File

@ -0,0 +1,169 @@
/* globals seConfirm */
/**
* @file ext-opensave.js
*
* @license MIT
*
* @copyright 2020 OptimistikSAS
*
*/
/**
* @type {module:svgcanvas.EventHandler}
* @param {external:Window} wind
* @param {module:svgcanvas.SvgCanvas#event:saved} svg The SVG source
* @listens module:svgcanvas.SvgCanvas#event:saved
* @returns {void}
*/
import { fileOpen, fileSave } from 'browser-fs-access';
const name = "opensave";
const loadExtensionTranslation = async function (svgEditor) {
let translationModule;
const lang = svgEditor.configObj.pref('lang');
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${lang}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/en.js`);
}
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
};
export default {
name,
async init(_S) {
const svgEditor = this;
const { imgPath } = svgEditor.configObj.curConfig;
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
await loadExtensionTranslation(svgEditor);
/**
* @fires module:svgcanvas.SvgCanvas#event:ext_onNewDocument
* @returns {void}
*/
const clickClear = async function () {
const [ x, y ] = svgEditor.configObj.curConfig.dimensions;
const ok = await seConfirm(svgEditor.i18next.t('notification.QwantToClear'));
if (ok === "Cancel") {
return;
}
svgEditor.leftPanel.clickSelect();
svgEditor.svgCanvas.clear();
svgEditor.svgCanvas.setResolution(x, y);
svgEditor.updateCanvas(true);
svgEditor.zoomImage();
svgEditor.layersPanel.populateLayers();
svgEditor.topPanel.updateContextPanel();
svgEditor.svgCanvas.runExtensions("onNewDocument");
};
/**
* By default, this.editor.svgCanvas.open() is a no-op. It is up to an extension
* mechanism (opera widget, etc.) to call `setCustomHandlers()` which
* will make it do something.
* @returns {void}
*/
const clickOpen = async function () {
// ask user before clearing an unsaved SVG
const response = await svgEditor.openPrep();
if (response === 'Cancel') { return; }
svgCanvas.clear();
try {
const blob = await fileOpen({
mimeTypes: [ 'image/*' ]
});
const svgContent = await blob.text();
await svgEditor.loadSvgString(svgContent);
svgEditor.updateCanvas();
} catch (err) {
if (err.name !== 'AbortError') {
return console.error(err);
}
}
};
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: contentType });
return blob;
};
/**
*
* @returns {void}
*/
const clickSave = async function () {
const $editorDialog = $id("se-svg-editor-dialog");
const editingsource = $editorDialog.getAttribute("dialog") === "open";
if (editingsource) {
svgEditor.saveSourceEditor();
} else {
// In the future, more options can be provided here
const saveOpts = {
images: svgEditor.configObj.pref("img_save"),
round_digits: 6
};
// remove the selected outline before serializing
svgCanvas.clearSelection();
// Update save options if provided
if (saveOpts) {
const saveOptions = svgCanvas.mergeDeep(svgCanvas.getSvgOption(), saveOpts);
for (const [ key, value ] of Object.entries(saveOptions)) {
svgCanvas.setSvgOption(key, value);
}
}
svgCanvas.setSvgOption('apply', true);
// no need for doctype, see https://jwatt.org/svg/authoring/#doctype-declaration
const svg = '<?xml version="1.0"?>\n' + svgCanvas.svgCanvasToString();
const b64Data = svgCanvas.encode64(svg);
const blob = b64toBlob(b64Data, 'image/svg+xml');
try {
await fileSave(blob, {
fileName: 'icon.svg',
extensions: [ '.svg' ]
});
} catch (err) {
if (err.name !== 'AbortError') {
return console.error(err);
}
}
}
};
return {
name: svgEditor.i18next.t(`${name}:name`),
// The callback should be used to load the DOM with the appropriate UI items
callback() {
// eslint-disable-next-line no-unsanitized/property
const buttonTemplate = `
<se-menu-item id="tool_clear" label="${svgEditor.i18next.t('tools.new_doc')}" shortcut="N" src="${imgPath}/new.svg"></se-menu-item>`;
svgCanvas.insertChildAtIndex($id('main_button'), buttonTemplate, 0);
const openButtonTemplate = `<se-menu-item id="tool_open" label="${svgEditor.i18next.t('tools.open_doc')}" src="${imgPath}/open.svg"></se-menu-item>`;
svgCanvas.insertChildAtIndex($id('main_button'), openButtonTemplate, 1);
const saveButtonTemplate = `<se-menu-item id="tool_save" label="${svgEditor.i18next.t('tools.save_doc')}" shortcut="S" src="${imgPath}/saveImg.svg"></se-menu-item>`;
svgCanvas.insertChildAtIndex($id('main_button'), saveButtonTemplate, 2);
// handler
$id("tool_clear").addEventListener("click", clickClear.bind(this));
$id("tool_open").addEventListener("click", clickOpen.bind(this));
$id("tool_save").addEventListener("click", clickSave.bind(this));
}
};
}
};

View File

@ -0,0 +1,8 @@
export default {
name: 'opensave',
tools: {
new_doc: 'New Image',
open_doc: 'Open SVG',
save_doc: 'Save Image'
}
};

View File

@ -0,0 +1,8 @@
export default {
name: 'ouvreenregistrer',
tools: {
new_doc: 'Nouvelle image',
open_doc: 'Ouvrir le SVG',
save_doc: 'Enregistrer l\'image'
}
};

View File

@ -0,0 +1,8 @@
export default {
name: '打开保存',
tools: {
new_doc: '新图片',
open_doc: '打开 SVG',
save_doc: '保存图像'
}
};

View File

@ -188,6 +188,8 @@ class SvgCanvas {
this.$id = $id;
this.$qq = $qq;
this.$qa = $qa;
this.encode64 = encode64;
this.decode64 = decode64;
this.stringToHTML = stringToHTML;
this.insertChildAtIndex = insertChildAtIndex;
this.getClosest = getClosest;
@ -1365,6 +1367,8 @@ class SvgCanvas {
/**
* Group: Serialization.
*/
this.getSvgOption = () => { return saveOptions; };
this.setSvgOption = (key, value) => { saveOptions[key] = value; };
svgInit(
/**
@ -1379,8 +1383,8 @@ class SvgCanvas {
getCurrentGroup() { return currentGroup; },
getCurConfig() { return curConfig; },
getNsMap() { return nsMap; },
getSvgOption() { return saveOptions; },
setSvgOption(key, value) { saveOptions[key] = value; },
getSvgOption: this.getSvgOption,
setSvgOption: this.setSvgOption,
getSvgOptionApply() { return saveOptions.apply; },
getSvgOptionImages() { return saveOptions.images; },
getEncodableImages(key) { return encodableImages[key]; },