#603 browser-fs-access library used to create new extension
parent
8b8db12be6
commit
1f7725b7ae
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
name: 'opensave',
|
||||
tools: {
|
||||
new_doc: 'New Image',
|
||||
open_doc: 'Open SVG',
|
||||
save_doc: 'Save Image'
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
name: 'ouvreenregistrer',
|
||||
tools: {
|
||||
new_doc: 'Nouvelle image',
|
||||
open_doc: 'Ouvrir le SVG',
|
||||
save_doc: 'Enregistrer l\'image'
|
||||
}
|
||||
};
|
|
@ -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.$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]; },
|
||||
|
|
Loading…
Reference in New Issue