Merge pull request #622 from SVG-Edit/issues/603

master
JFH 2021-08-25 11:39:58 +02:00 committed by GitHub
commit e83fa3f97e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 475 additions and 303 deletions

View File

@ -0,0 +1,78 @@
/* globals seAlert */
/**
* @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}
*/
export default {
name: 'opensave',
init ({ encode64 }) {
const svgEditor = this;
svgEditor.setCustomHandlers({
save (win, svg) {
this.showSaveWarning = false;
// by default, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs)
// can just provide their own custom save handler and might not want the XML prolog
svg = '<?xml version="1.0"?>\n' + svg;
// Since saving SVGs by opening a new window was removed in Chrome use artificial link-click
// https://stackoverflow.com/questions/45603201/window-is-not-allowed-to-navigate-top-frame-navigations-to-data-urls
const a = document.createElement('a');
a.href = 'data:image/svg+xml;base64,' + encode64(svg);
a.download = 'icon.svg';
a.style.display = 'none';
document.body.append(a); // Need to append for Firefox
a.click();
// Alert will only appear the first time saved OR the
// first time the bug is encountered
const done = this.configObj.pref('save_notice_done');
if (done !== 'all') {
const note = svgEditor.i18next.t('notification.saveFromBrowser', { type: 'SVG' });
this.configObj.pref('save_notice_done', 'all');
if (done !== 'part') {
seAlert(note);
}
}
},
async open () {
const ok = await this.openPrep();
if (ok === 'Cancel') { return; }
this.svgCanvas.clear();
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', (e) => {
// getting a hold of the file reference
const file = e.target.files[0];
// setting up the reader
const reader = new FileReader();
reader.readAsText(file, 'UTF-8');
// here we tell the reader what to do when it's done reading...
reader.addEventListener('load', async (readerEvent) => {
const content = readerEvent.target.result;
await this.loadSvgString(content);
this.updateCanvas();
});
});
input.click();
}
});
}
};

View File

@ -101,7 +101,109 @@ exports[`use all parts of svg-edit > check tool_shape #0`] = `
</svg>
`;
exports[`use all parts of svg-edit > check mode_connect #0`] = `
exports[`use all parts of svg-edit > check tool_rect_square #0`] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
width="640"
height="480"
id="svgcontent"
overflow="visible"
x="640"
y="480"
viewBox="0 0 640 480"
>
<g class="layer" style="pointer-events:all">
<title style="pointer-events:inherit">Layer 1</title>
<line
fill="none"
stroke="#000000"
stroke-width="5"
style="pointer-events:inherit"
x1="200"
y1="200"
x2="220"
y2="220"
id="svg_1"
fill-opacity="1"
stroke-opacity="1"
transform="rotate(43.2643 210 210)"
></line>
<path
fill="#FF0000"
stroke="#000000"
stroke-width="5"
style="pointer-events:inherit"
d="m309.60653833763354,262.7975086895094 c37.05917105721012,-106.31729401658639 182.2582183141481,0 0,136.6936637356111 c-182.2582183141481,-136.6936637356111 -37.05917105721012,-243.0109577521975 0,-136.6936637356111 z"
id="svg_2"
fill-opacity="1"
stroke-opacity="1"
></path>
<rect
fill="#FF0000"
stroke="#000000"
stroke-width="5"
opacity="0.5"
style="pointer-events:inherit"
x="296"
y="136"
width="150"
height="120"
id="svg_3"
>
<animate
attributeName="opacity"
begin="indefinite"
dur="0.2"
fill="freeze"
to="1"
></animate>
</rect>
<rect
fill="#FF0000"
stroke="#000000"
stroke-width="5"
opacity="0.5"
style="pointer-events:inherit"
x="446"
y="76"
width="180"
height="180"
id="svg_4"
>
<animate
attributeName="opacity"
begin="indefinite"
dur="0.2"
fill="freeze"
to="1"
></animate>
</rect>
<rect
fill="#FF0000"
stroke="#000000"
stroke-width="5"
opacity="0.5"
style="pointer-events:inherit"
x="216"
y="156"
width="100"
height="100"
id="svg_5"
>
<animate
attributeName="opacity"
begin="indefinite"
dur="0.2"
fill="freeze"
to="1"
></animate>
</rect>
</g>
</svg>
`;
exports[`use all parts of svg-edit > check tool_image #0`] = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
@ -156,13 +258,37 @@ exports[`use all parts of svg-edit > check mode_connect #0`] = `
fill="#FF0000"
stroke="#000000"
stroke-width="5"
opacity="0.5"
style="pointer-events:inherit"
x="446"
y="76"
width="180"
height="180"
id="svg_4"
fill-opacity="1"
stroke-opacity="1"
></rect>
<rect
fill="#FF0000"
stroke="#000000"
stroke-width="5"
style="pointer-events:inherit"
x="216"
y="156"
width="100"
height="100"
id="svg_5"
fill-opacity="1"
stroke-opacity="1"
></rect>
<image
x="296"
y="176"
width="20"
height="20"
id="svg_6"
opacity="0.5"
style="pointer-events:inherit"
xlink:href="./images/logo.svg"
>
<animate
attributeName="opacity"
@ -171,7 +297,7 @@ exports[`use all parts of svg-edit > check mode_connect #0`] = `
fill="freeze"
to="1"
></animate>
</rect>
</image>
</g>
</svg>
`;

View File

@ -49,7 +49,7 @@ describe('use all parts of svg-edit', function () {
.trigger('mouseup', { force: true });
cy.get('#svgcontent').toMatchSnapshot();
});
it('check mode_connect', function () {
it('check tool_rect_square', function () {
cy.get('#tool_rect').click({ force: true });
cy.get('#svgcontent')
.trigger('mousedown', 100, -60, { force: true })
@ -60,24 +60,32 @@ describe('use all parts of svg-edit', function () {
.trigger('mousedown', 250, -60, { force: true })
.trigger('mousemove', 430, 120, { force: true })
.trigger('mouseup', { force: true });
cy.get('#tool_select').click({ force: true });
cy.get('#mode_connect').click({ force: true });
cy.get('#tool_fhrect')
.click({ force: true });
cy.get('#svgcontent')
.trigger('mousedown', -10, -10, { force: true })
.trigger('mousemove', -180, -180, { force: true })
.trigger('mouseup', { force: true });
.trigger('mousedown', 20, 80, { force: true })
.trigger('mousemove', 120, 80, { force: true })
.trigger('mousemove', 120, 180, { force: true })
.trigger('mousemove', 20, 180, { force: true })
.trigger('mousemove', 20, 80, { force: true })
.trigger('mouseup', 20, 80, { force: true });
cy.get('#svgcontent').toMatchSnapshot();
});
/* it('check tool_image', function () {
it('check tool_image', function () {
cy.get('#tool_image').click({ force: true });
cy.get('#svgcontent')
.trigger('mousemove', 100, 100, { force: true })
.trigger('mousedown', 100, 100, { force: true })
.trigger('mousemove', 120, 120, { force: true })
.trigger('mouseup', { force: true });
cy.on('window:confirm', () => true);
// eslint-disable-next-line promise/catch-or-return
cy.window()
// eslint-disable-next-line promise/always-return
.then(($win) => {
cy.stub($win, 'prompt').returns('./images/logo.svg');
cy.contains('OK');
});
cy.get('#svgcontent').toMatchSnapshot();
}); */
});
});

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

@ -180,38 +180,6 @@ class Editor extends EditorStartup {
* @returns {void}
*/
/**
* Allows one to override default SVGEdit `open`, `save`, and
* `export` editor behaviors.
* @function module:SVGthis.setCustomHandlers
* @param {module:SVGthis.CustomHandler} opts Extension mechanisms may call `setCustomHandlers` with three functions: `opts.open`, `opts.save`, and `opts.exportImage`
* @returns {Promise<void>}
*/
/**
* @param {PlainObject} opts
* @returns {Promise<PlainObject>}
*/
setCustomHandlers(opts) {
return this.ready(() => {
if (opts.open) {
this.svgCanvas.open = opts.open.bind(this);
}
if (opts.save) {
this.showSaveWarning = false;
this.svgCanvas.bind('saved', opts.save.bind(this));
}
if (opts.exportImage) {
this.customExportImage = opts.exportImage.bind(this);
this.svgCanvas.bind('exported', this.customExportImage); // canvg and our RGBColor will be available to the method
}
if (opts.exportPDF) {
this.customExportPDF = opts.exportPDF.bind(this);
this.svgCanvas.bind('exportedPDF', this.customExportPDF); // jsPDF and our RGBColor will be available to the method
}
});
}
/**
* @function module:SVGthis.randomizeIds
* @param {boolean} arg

View File

@ -129,20 +129,6 @@ class MainMenu {
this.editor.updateCanvas();
this.hidePreferences();
}
/**
*
* @returns {void}
*/
clickSave() {
// In the future, more options can be provided here
const saveOpts = {
images: this.editor.configObj.pref("img_save"),
round_digits: 6
};
this.editor.svgCanvas.save(saveOpts);
}
/**
*
* @param e
@ -210,16 +196,6 @@ class MainMenu {
}
}
/**
* 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}
*/
clickOpen() {
this.editor.svgCanvas.open();
}
/**
*
* @returns {void}
@ -307,9 +283,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 +297,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

@ -1,4 +1,4 @@
/* globals seAlert */
/* globals seConfirm */
/**
* @file ext-opensave.js
*
@ -15,64 +15,167 @@
* @listens module:svgcanvas.SvgCanvas#event:saved
* @returns {void}
*/
import { fileOpen, fileSave } from 'browser-fs-access';
const name = "opensave";
let handle = null;
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, 'translation', translationModule.default, true, true);
};
export default {
name: 'opensave',
init ({ encode64 }) {
name,
async init(_S) {
const svgEditor = this;
const { imgPath } = svgEditor.configObj.curConfig;
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
await loadExtensionTranslation(svgEditor);
svgEditor.setCustomHandlers({
save (win, svg) {
this.showSaveWarning = false;
/**
* @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, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs)
// can just provide their own custom save handler and might not want the XML prolog
svg = '<?xml version="1.0"?>\n' + svg;
/**
* 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);
}
}
};
// Since saving SVGs by opening a new window was removed in Chrome use artificial link-click
// https://stackoverflow.com/questions/45603201/window-is-not-allowed-to-navigate-top-frame-navigations-to-data-urls
const a = document.createElement('a');
a.href = 'data:image/svg+xml;base64,' + encode64(svg);
a.download = 'icon.svg';
a.style.display = 'none';
document.body.append(a); // Need to append for Firefox
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;
};
a.click();
// Alert will only appear the first time saved OR the
// first time the bug is encountered
const done = this.configObj.pref('save_notice_done');
if (done !== 'all') {
const note = svgEditor.i18next.t('notification.saveFromBrowser', { type: 'SVG' });
this.configObj.pref('save_notice_done', 'all');
if (done !== 'part') {
seAlert(note);
/**
*
* @returns {void}
*/
const clickSave = async function (type, _) {
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 {
if(type === "save" && handle !== null) {
const throwIfExistingHandleNotGood = false;
handle = await fileSave(blob, {
fileName: 'icon.svg',
extensions: [ '.svg' ]
}, handle, throwIfExistingHandleNotGood);
} else {
handle = await fileSave(blob, {
fileName: 'icon.svg',
extensions: [ '.svg' ]
});
}
} catch (err) {
if (err.name !== 'AbortError') {
return console.error(err);
}
}
},
async open () {
const ok = await this.openPrep();
if (ok === 'Cancel') { return; }
this.svgCanvas.clear();
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', (e) => {
// getting a hold of the file reference
const file = e.target.files[0];
// setting up the reader
const reader = new FileReader();
reader.readAsText(file, 'UTF-8');
// here we tell the reader what to do when it's done reading...
reader.addEventListener('load', async (readerEvent) => {
const content = readerEvent.target.result;
await this.loadSvgString(content);
this.updateCanvas();
});
});
input.click();
}
});
};
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('opensave.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('opensave.open_image_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('opensave.save_doc')}" shortcut="S" src="${imgPath}/saveImg.svg"></se-menu-item>`;
svgCanvas.insertChildAtIndex($id('main_button'), saveButtonTemplate, 2);
const saveAsButtonTemplate = `<se-menu-item id="tool_save_as" label="${svgEditor.i18next.t('opensave.save_as_doc')}" src="${imgPath}/saveImg.svg"></se-menu-item>`;
svgCanvas.insertChildAtIndex($id('main_button'), saveAsButtonTemplate, 3);
// 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, "save"));
$id("tool_save_as").addEventListener("click", clickSave.bind(this, "saveas"));
}
};
}
};

View File

@ -0,0 +1,8 @@
export default {
opensave: {
new_doc: 'New Image',
open_image_doc: 'Open SVG',
save_doc: 'Save SVG',
save_as_doc: 'Save as SVG'
}
};

View File

@ -0,0 +1,8 @@
export default {
opensave: {
new_doc: 'Nouvelle image',
open_image_doc: 'Ouvrir le SVG',
save_doc: 'Enregistrer l\'image',
save_as_doc: 'Enregistrer en tant qu\'image'
}
};

View File

@ -0,0 +1,8 @@
export default {
opensave: {
new_doc: '新图片',
open_image_doc: '打开 SVG',
save_doc: '保存图像',
save_as_doc: '另存为图像'
}
};

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'grootste voorwerp',
selected_objects: 'verkose voorwerpe',
smallest_object: 'kleinste voorwerp',
new_doc: 'Nuwe Beeld',
open_doc: 'Open Beeld',
export_img: 'Export',
save_doc: 'Slaan Beeld',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Rig Middel',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'أكبر كائن',
selected_objects: 'انتخب الأجسام',
smallest_object: 'أصغر كائن',
new_doc: 'صورة جديدة',
open_doc: 'فتح الصورة',
export_img: 'Export',
save_doc: 'حفظ صورة',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'محاذاة القاع',
align_center: 'مركز محاذاة',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'largest object',
selected_objects: 'selected objects',
smallest_object: 'smallest object',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'найбуйнейшы аб&#39;ект',
selected_objects: 'выбранымі аб&#39;ектамі',
smallest_object: 'маленькі аб&#39;ект',
new_doc: 'Новае выява',
open_doc: 'Адкрыць выява',
export_img: 'Export',
save_doc: 'Захаваць малюнак',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Лінаваць па ніжнім краю',
align_center: 'Лінаваць па цэнтру',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'най-големият обект',
selected_objects: 'избраните обекти',
smallest_object: 'най-малката обект',
new_doc: 'Ню Имидж',
open_doc: 'Отворете изображението',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Привеждане Отдолу',
align_center: 'Подравняване в средата',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Obrir imatge',
export_img: 'Export',
save_doc: 'Guardar imatge',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Alinear baix',
align_center: 'Alinear al centre',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Delwedd Agored',
export_img: 'Export',
save_doc: 'Cadw Delwedd',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Alinio Gwaelod',
align_center: 'Alinio Center',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'største objekt',
selected_objects: 'valgte objekter',
smallest_object: 'mindste objekt',
new_doc: 'Nyt billede',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Gem billede',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Juster Bottom',
align_center: 'Centrer',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Άνοιγμα εικόνας',
export_img: 'Export',
save_doc: 'Αποθήκευση εικόνας',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Στοίχισηκάτω',
align_center: 'Στοίχισηστοκέντρο',

View File

@ -108,11 +108,8 @@ export default {
largest_object: 'largest object',
selected_objects: 'selected objects',
smallest_object: 'smallest object',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Pildi avamine',
export_img: 'Export',
save_doc: 'Salvesta pilt',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Viia Bottom',
align_center: 'Keskele joondamine',

View File

@ -111,7 +111,7 @@ export default {
open_doc: '‫باز کردن تصویر ',
export_img: 'Export',
save_doc: '‫ذخیره تصویر ',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: '‫تراز پایین‬',
align_center: '‫وسط چین‬',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'Suurin kohde',
selected_objects: 'valittujen objektien',
smallest_object: 'pienin kohde',
new_doc: 'Uusi kuva',
open_doc: 'Avaa kuva',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Keskitä',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Ôfbielding iepenje',
export_img: 'Export',
save_doc: 'Ôfbielding bewarje',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Ûnder útlijne',
align_center: 'Midden útlijne',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Íomhá Oscailte',
export_img: 'Export',
save_doc: 'Sábháil Íomhá',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Cineál Bun',
align_center: 'Ailínigh sa Lár',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Abrir Imaxe',
export_img: 'Export',
save_doc: 'Gardar Imaxe',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align bottom',
align_center: 'Centrar',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'פתח תמונה',
export_img: 'Export',
save_doc: 'שמור תמונה',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'יישור תחתון',
align_center: 'ישור לאמצע',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'छवि खोलें',
export_img: 'Export',
save_doc: 'सहेजें छवि',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'तलमेंपंक्तिबद्धकरें',
align_center: 'मध्य में समंजित करें',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Otvori sliku',
export_img: 'Export',
save_doc: 'Spremanje slike',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Poravnaj dolje',
align_center: 'Centriraj',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Kép megnyitása',
export_img: 'Export',
save_doc: 'Kép mentése más',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Alulra igazítás',
align_center: 'Középre igazítás',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'largest object',
selected_objects: 'elected objects',
smallest_object: 'smallest object',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'objek terbesar',
selected_objects: 'objek terpilih',
smallest_object: 'objek terkecil',
new_doc: 'Gambar Baru',
open_doc: 'Membuka Image',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Rata Bottom',
align_center: 'Rata Tengah',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'stærsti hlutinn',
selected_objects: 'kjörinn hlutir',
smallest_object: 'lítill hluti',
new_doc: 'New Image',
open_doc: 'Opna mynd',
export_img: 'Export',
save_doc: 'Spara Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Jafna Bottom',
align_center: 'Jafna Center',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'イメージを開く',
export_img: 'Export',
save_doc: '画像を保存',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: '下揃え',
align_center: '中央揃え',

View File

@ -111,7 +111,7 @@ export default {
open_doc: '오픈 이미지',
export_img: 'Export',
save_doc: '이미지 저장',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: '히프 정렬',
align_center: '정렬 센터',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'didžiausias objektas',
selected_objects: 'išrinktas objektai',
smallest_object: 'mažiausias objektą',
new_doc: 'New Image',
open_doc: 'Atidaryti atvaizdą',
export_img: 'Export',
save_doc: 'Išsaugoti nuotrauką',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Lygiuoti apačioje',
align_center: 'Lygiuoti',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'lielākais objekts',
selected_objects: 'ievēlēts objekti',
smallest_object: 'mazākais objekts',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Līdzināt Bottom',
align_center: 'Līdzināt uz centru',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Отвори слика',
export_img: 'Export',
save_doc: 'Зачувај слика',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Центрирано',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'objek terbesar',
selected_objects: 'objek terpilih',
smallest_object: 'objek terkecil',
new_doc: 'Imej Baru',
open_doc: 'Membuka Image',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Rata Bottom',
align_center: 'Rata Tengah',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'akbar oġġett',
selected_objects: 'oġġetti elett',
smallest_object: 'iżgħar oġġett',
new_doc: 'Image New',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Image Save',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Tallinja Bottom',
align_center: 'Tallinja Center',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'største objekt',
selected_objects: 'velges objekter',
smallest_object: 'minste objekt',
new_doc: 'New Image',
open_doc: 'Åpne Image',
export_img: 'Export',
save_doc: 'Lagre bilde',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Midtstill',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Abrir Imagem',
export_img: 'Export',
save_doc: 'Salvar Imagem',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Alinhar ao centro',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Otvoriť obrázok',
export_img: 'Export',
save_doc: 'Uložiť obrázok',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Zarovnať element na stránku',
align_bottom: 'Zarovnať dole',
align_center: 'Zarovnať na stred',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'madh objekt',
selected_objects: 'objektet e zgjedhur',
smallest_object: 'objektit më të vogël',
new_doc: 'New Image',
open_doc: 'Image Hapur',
export_img: 'Export',
save_doc: 'Image Ruaj',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Отвори слике',
export_img: 'Export',
save_doc: 'Сачувај слика',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Поравнај доле',
align_center: 'Поравнај по центру',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'största objekt',
selected_objects: 'valda objekt',
smallest_object: 'minsta objektet',
new_doc: 'New Image',
open_doc: 'Öppna bild',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Centrera',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'ukubwa object',
selected_objects: 'waliochaguliwa vitu',
smallest_object: 'minsta object',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'largest object',
selected_objects: 'selected objects',
smallest_object: 'smallest object',
new_doc: 'New Image',
open_doc: 'Open SVG',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Align Center',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'ภาพเปิด',
export_img: 'Export',
save_doc: 'บันทึกรูปภาพ',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'ด้านล่างชิด',
align_center: 'จัดแนวกึ่งกลาง',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Buksan ang Image',
export_img: 'Export',
save_doc: 'I-save ang Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Pantayin sa Ibaba',
align_center: 'Pantayin sa Gitna',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Aç Resim',
export_img: 'Export',
save_doc: 'Görüntüyü Kaydet',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Ortala',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Відкрити зображення',
export_img: 'Export',
save_doc: 'Зберегти малюнок',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Вирівняти по нижньому краю',
align_center: 'Вирівняти по центру',

View File

@ -107,11 +107,8 @@ export default {
largest_object: 'lớn nhất đối tượng',
selected_objects: 'bầu các đối tượng',
smallest_object: 'nhỏ đối tượng',
new_doc: 'Hình mới',
open_doc: 'Mở Image',
export_img: 'Export',
save_doc: 'Save Image',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'Align Bottom',
align_center: 'Căn giữa',

View File

@ -111,7 +111,7 @@ export default {
open_doc: 'Open בילד',
export_img: 'Export',
save_doc: 'היט בילד',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: 'יינרייען באָטטאָם',
align_center: 'יינרייען צענטער',

View File

@ -111,7 +111,7 @@ export default {
open_doc: '打开图像',
export_img: 'Export',
save_doc: '保存图像',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: '底部对齐',
align_center: '居中对齐',

View File

@ -111,7 +111,7 @@ export default {
open_doc: '打開圖像',
export_img: 'Export',
save_doc: '保存圖像',
import_doc: 'Import Image',
import_doc: 'Import SVG',
align_to_page: 'Align Element to Page',
align_bottom: '底部對齊',
align_center: '居中對齊',

View File

@ -191,6 +191,7 @@ class TopPanel {
this.hideTool("container_panel");
this.hideTool("use_panel");
this.hideTool("a_panel");
this.hideTool("xy_panel");
if (elem) {
const elname = elem.nodeName;

View File

@ -55,7 +55,7 @@ const svgWhiteList_ = {
stop: [ 'class', 'id', 'offset', 'requiredFeatures', 'stop-opacity', 'style', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform' ],
svg: [ 'class', 'clip-path', 'clip-rule', 'filter', 'id', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'style', 'systemLanguage', 'version', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xmlns:oi', 'oi:animations', 'y', 'stroke-linejoin', 'fill-rule', 'aria-label', 'stroke-width', 'fill-rule' ],
switch: [ 'class', 'id', 'requiredFeatures', 'systemLanguage' ],
symbol: [ 'class', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'viewBox' ],
symbol: [ 'class', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'viewBox', 'width', 'height' ],
text: [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'transform', 'x', 'xml:space', 'y' ],
textPath: [ 'class', 'id', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'style', 'systemLanguage', 'transform', 'xlink:href' ],
title: [],

View File

@ -743,15 +743,17 @@ export const convertToGroup = function (elem) {
// Not ideal, but works
ts += ' translate(' + (pos.x || 0) + ',' + (pos.y || 0) + ')';
const prev = $elem.prev();
const prev = $elem.previousElementSibling;
// Remove <use> element
batchCmd.addSubCommand(new RemoveElementCommand($elem[0], $elem[0].nextSibling, $elem[0].parentNode));
batchCmd.addSubCommand(new RemoveElementCommand($elem, $elem.nextElementSibling, $elem.parentNode));
$elem.remove();
// See if other elements reference this symbol
const svgcontent = elementContext_.getSVGContent();
const hasMore = svgcontent.querySelectorAll('use:data(symbol)').length;
// const hasMore = svgcontent.querySelectorAll('use:data(symbol)').length;
// @todo review this logic
const hasMore = svgcontent.querySelectorAll('use').length;
const g = elementContext_.getDOMDocument().createElementNS(NS.SVG, 'g');
const childs = elem.childNodes;

View File

@ -678,31 +678,6 @@ export const embedImage = function (src) {
});
};
/**
* Serializes the current drawing into SVG XML text and passes it to the 'saved' handler.
* This function also includes the XML prolog. Clients of the `SvgCanvas` bind their save
* function to the 'saved' event.
* @function module:svgcanvas.SvgCanvas#save
* @param {module:svgcanvas.SaveOptions} opts
* @fires module:svgcanvas.SvgCanvas#event:saved
* @returns {void}
*/
export const save = function (opts) {
// remove the selected outline before serializing
svgCanvas.clearSelection();
// Update save options if provided
if (opts) {
const saveOptions = svgCanvas.mergeDeep(svgContext_.getSvgOption(), opts);
for (const [ key, value ] of Object.entries(saveOptions)) {
svgContext_.setSvgOption(key, value);
}
}
svgContext_.setSvgOption('apply', true);
// no need for doctype, see https://jwatt.org/svg/authoring/#doctype-declaration
const str = svgCanvas.svgCanvasToString();
svgContext_.call('saved', str);
};
/**
* @typedef {PlainObject} module:svgcanvas.IssuesAndCodes
* @property {string[]} issueCodes The locale-independent code names

View File

@ -80,7 +80,7 @@ import {
convertToNum, getTypeMap, init as unitsInit
} from '../common/units.js';
import {
svgCanvasToString, svgToString, setSvgString, save, exportPDF, setUseDataMethod,
svgCanvasToString, svgToString, setSvgString, exportPDF, setUseDataMethod,
init as svgInit, importSvgString, embedImage, rasterExport,
uniquifyElemsMethod, removeUnusedDefElemsMethod, convertGradientsMethod
} from './svg-exec.js';
@ -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]; },
@ -1448,26 +1452,6 @@ class SvgCanvas {
lastGoodImgUrl = val;
};
/**
* Does nothing by default, handled by optional widget/extension.
* @function module:svgcanvas.SvgCanvas#open
* @returns {void}
*/
this.open = function () {
/* empty fn */
};
/**
* Serializes the current drawing into SVG XML text and passes it to the 'saved' handler.
* This function also includes the XML prolog. Clients of the `SvgCanvas` bind their save
* function to the 'saved' event.
* @function module:svgcanvas.SvgCanvas#save
* @param {module:svgcanvas.SaveOptions} opts
* @fires module:svgcanvas.SvgCanvas#event:saved
* @returns {void}
*/
this.save = save;
/**
* @typedef {PlainObject} module:svgcanvas.IssuesAndCodes
* @property {string[]} issueCodes The locale-independent code names