#603 browser-fs-access library used to create new extension
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"license": "(MIT AND Apache-2.0 AND ISC AND LGPL-3.0-or-later AND X11)",
|
"license": "(MIT AND Apache-2.0 AND ISC AND LGPL-3.0-or-later AND X11)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "7.12.1",
|
"@babel/polyfill": "7.12.1",
|
||||||
|
"browser-fs-access": "^0.20.4",
|
||||||
"canvg": "3.0.7",
|
"canvg": "3.0.7",
|
||||||
"core-js": "3.16.2",
|
"core-js": "3.16.2",
|
||||||
"elix": "15.0.0",
|
"elix": "15.0.0",
|
||||||
@@ -5299,6 +5300,11 @@
|
|||||||
"node": ">= 10.16.0"
|
"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": {
|
"node_modules/browser-pack": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
|
||||||
@@ -26396,6 +26402,11 @@
|
|||||||
"duplexer": "0.1.1"
|
"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": {
|
"browser-pack": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "7.12.1",
|
"@babel/polyfill": "7.12.1",
|
||||||
|
"browser-fs-access": "^0.20.4",
|
||||||
"canvg": "3.0.7",
|
"canvg": "3.0.7",
|
||||||
"core-js": "3.16.2",
|
"core-js": "3.16.2",
|
||||||
"elix": "15.0.0",
|
"elix": "15.0.0",
|
||||||
|
|||||||
@@ -307,9 +307,6 @@ class MainMenu {
|
|||||||
// eslint-disable-next-line no-unsanitized/property
|
// eslint-disable-next-line no-unsanitized/property
|
||||||
template.innerHTML = `
|
template.innerHTML = `
|
||||||
<se-menu id="main_button" label="SVG-Edit" src="${imgPath}/logo.svg" alt="logo">
|
<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_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_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>
|
<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.
|
* 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", () => {
|
$id("tool_import").addEventListener("click", () => {
|
||||||
this.clickImport();
|
this.clickImport();
|
||||||
window.dispatchEvent(new CustomEvent("importImages"));
|
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() {
|
$id("tool_export").addEventListener("click", function() {
|
||||||
document
|
document
|
||||||
.getElementById("se-export-dialog")
|
.getElementById("se-export-dialog")
|
||||||
|
|||||||
169
src/editor/extensions/ext-opensave/ext-opensave.js
Normal file
169
src/editor/extensions/ext-opensave/ext-opensave.js
Normal 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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
8
src/editor/extensions/ext-opensave/locale/en.js
Normal file
8
src/editor/extensions/ext-opensave/locale/en.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
name: 'opensave',
|
||||||
|
tools: {
|
||||||
|
new_doc: 'New Image',
|
||||||
|
open_doc: 'Open SVG',
|
||||||
|
save_doc: 'Save Image'
|
||||||
|
}
|
||||||
|
};
|
||||||
8
src/editor/extensions/ext-opensave/locale/fr.js
Normal file
8
src/editor/extensions/ext-opensave/locale/fr.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
name: 'ouvreenregistrer',
|
||||||
|
tools: {
|
||||||
|
new_doc: 'Nouvelle image',
|
||||||
|
open_doc: 'Ouvrir le SVG',
|
||||||
|
save_doc: 'Enregistrer l\'image'
|
||||||
|
}
|
||||||
|
};
|
||||||
8
src/editor/extensions/ext-opensave/locale/zh-CN.js
Executable file
8
src/editor/extensions/ext-opensave/locale/zh-CN.js
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
name: '打开保存',
|
||||||
|
tools: {
|
||||||
|
new_doc: '新图片',
|
||||||
|
open_doc: '打开 SVG',
|
||||||
|
save_doc: '保存图像'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -188,6 +188,8 @@ class SvgCanvas {
|
|||||||
this.$id = $id;
|
this.$id = $id;
|
||||||
this.$qq = $qq;
|
this.$qq = $qq;
|
||||||
this.$qa = $qa;
|
this.$qa = $qa;
|
||||||
|
this.encode64 = encode64;
|
||||||
|
this.decode64 = decode64;
|
||||||
this.stringToHTML = stringToHTML;
|
this.stringToHTML = stringToHTML;
|
||||||
this.insertChildAtIndex = insertChildAtIndex;
|
this.insertChildAtIndex = insertChildAtIndex;
|
||||||
this.getClosest = getClosest;
|
this.getClosest = getClosest;
|
||||||
@@ -1365,6 +1367,8 @@ class SvgCanvas {
|
|||||||
/**
|
/**
|
||||||
* Group: Serialization.
|
* Group: Serialization.
|
||||||
*/
|
*/
|
||||||
|
this.getSvgOption = () => { return saveOptions; };
|
||||||
|
this.setSvgOption = (key, value) => { saveOptions[key] = value; };
|
||||||
|
|
||||||
svgInit(
|
svgInit(
|
||||||
/**
|
/**
|
||||||
@@ -1379,8 +1383,8 @@ class SvgCanvas {
|
|||||||
getCurrentGroup() { return currentGroup; },
|
getCurrentGroup() { return currentGroup; },
|
||||||
getCurConfig() { return curConfig; },
|
getCurConfig() { return curConfig; },
|
||||||
getNsMap() { return nsMap; },
|
getNsMap() { return nsMap; },
|
||||||
getSvgOption() { return saveOptions; },
|
getSvgOption: this.getSvgOption,
|
||||||
setSvgOption(key, value) { saveOptions[key] = value; },
|
setSvgOption: this.setSvgOption,
|
||||||
getSvgOptionApply() { return saveOptions.apply; },
|
getSvgOptionApply() { return saveOptions.apply; },
|
||||||
getSvgOptionImages() { return saveOptions.images; },
|
getSvgOptionImages() { return saveOptions.images; },
|
||||||
getEncodableImages(key) { return encodableImages[key]; },
|
getEncodableImages(key) { return encodableImages[key]; },
|
||||||
|
|||||||
Reference in New Issue
Block a user