diff --git a/cypress/integration/unit/recalculate.js b/cypress/integration/unit/recalculate.js index c1f3b0b1..3817c681 100644 --- a/cypress/integration/unit/recalculate.js +++ b/cypress/integration/unit/recalculate.js @@ -1,6 +1,6 @@ import '../../../instrumented/editor/jquery.min.js'; -import {NS} from '../../../instrumented/common/namespaces.js'; +import { NS } from '../../../instrumented/common/namespaces.js'; import * as utilities from '../../../instrumented/svgcanvas/utilities.js'; import * as coords from '../../../instrumented/svgcanvas/coords.js'; import * as recalculate from '../../../instrumented/svgcanvas/recalculate.js'; @@ -17,21 +17,45 @@ describe('recalculate', function () { const svg = document.createElementNS(NS.SVG, 'svg'); svgroot.append(svg); + const dataStorage = { + _storage: new WeakMap(), + put: function (element, key, obj) { + if (!this._storage.has(element)) { + this._storage.set(element, new Map()); + } + this._storage.get(element).set(key, obj); + }, + get: function (element, key) { + return this._storage.get(element).get(key); + }, + has: function (element, key) { + return this._storage.has(element) && this._storage.get(element).has(key); + }, + remove: function (element, key) { + var ret = this._storage.get(element).delete(key); + if (!this._storage.get(element).size === 0) { + this._storage.delete(element); + } + return ret; + } + }; + let elemId = 1; /** * Initilize modules to set up the tests. * @returns {void} */ - function setUp () { + function setUp() { utilities.init( /** * @implements {module:utilities.EditorContext} */ { - getSVGRoot () { return svg; }, - getDOMDocument () { return null; }, - getDOMContainer () { return null; } + getSVGRoot() { return svg; }, + getDOMDocument() { return null; }, + getDOMContainer() { return null; }, + getDataStorage() { return dataStorage; } } ); coords.init( @@ -39,12 +63,13 @@ describe('recalculate', function () { * @implements {module:coords.EditorContext} */ { - getGridSnapping () { return false; }, - getDrawing () { + getGridSnapping() { return false; }, + getDrawing() { return { - getNextId () { return String(elemId++); } + getNextId() { return String(elemId++); } }; - } + }, + getDataStorage() { return dataStorage; } } ); recalculate.init( @@ -52,9 +77,10 @@ describe('recalculate', function () { * @implements {module:recalculate.EditorContext} */ { - getSVGRoot () { return svg; }, - getStartTransform () { return ''; }, - setStartTransform () { /* empty fn */ } + getSVGRoot() { return svg; }, + getStartTransform() { return ''; }, + setStartTransform() { /* empty fn */ }, + getDataStorage() { return dataStorage; } } ); } @@ -65,7 +91,7 @@ describe('recalculate', function () { * Initialize for tests and set up `rect` element. * @returns {void} */ - function setUpRect () { + function setUpRect() { setUp(); elem = document.createElementNS(NS.SVG, 'rect'); elem.setAttribute('x', '200'); @@ -79,7 +105,7 @@ describe('recalculate', function () { * Initialize for tests and set up `text` element with `tspan` child. * @returns {void} */ - function setUpTextWithTspan () { + function setUpTextWithTspan() { setUp(); elem = document.createElementNS(NS.SVG, 'text'); elem.setAttribute('x', '200'); diff --git a/cypress/integration/unit/select.js b/cypress/integration/unit/select.js index a599402e..8d3b2672 100644 --- a/cypress/integration/unit/select.js +++ b/cypress/integration/unit/select.js @@ -12,6 +12,28 @@ describe('select', function () { const mockConfig = { dimensions: [640, 480] }; + const dataStorage = { + _storage: new WeakMap(), + put: function (element, key, obj) { + if (!this._storage.has(element)) { + this._storage.set(element, new Map()); + } + this._storage.get(element).set(key, obj); + }, + get: function (element, key) { + return this._storage.get(element).get(key); + }, + has: function (element, key) { + return this._storage.has(element) && this._storage.get(element).has(key); + }, + remove: function (element, key) { + var ret = this._storage.get(element).delete(key); + if (!this._storage.get(element).size === 0) { + this._storage.delete(element); + } + return ret; + } + }; /** * @implements {module:select.SVGFactory} @@ -25,7 +47,8 @@ describe('select', function () { return elem; }, svgRoot () { return svgroot; }, - svgContent () { return svgcontent; } + svgContent () { return svgcontent; }, + getDataStorage () { return dataStorage; } }; /** diff --git a/cypress/integration/unit/utilities.js b/cypress/integration/unit/utilities.js index 6c00c9ac..06e6b659 100644 --- a/cypress/integration/unit/utilities.js +++ b/cypress/integration/unit/utilities.js @@ -238,7 +238,7 @@ describe('utilities', function () { attr: {id: 'roundrect', x: '0', y: '1', rx: '2', ry: '3', width: '10', height: '11'} }); svgroot.append(elem); - const closeEnough = /M0,4 C0,2.3\d* 0.9\d*,1 2,1 L8,1 C9.0\d*,1 10,2.3\d* 10,4 L10,9 C10,10.6\d* 9.08675799086758,12 8,12 L2,12 C0.9\d*,12 0,10.6\d* 0,9 L0,4 Z/; + const closeEnough = /M0,13 C0,2.3\d* 0.9\d*,1 02,1 L8,1 C9.0\d*,1 10,2.3\d* 10,13 L10,9 C10,10.6\d* 9.08675799086758,12 8,12 L02,12 C0.9\d*,12 0,10.6\d* 0,9 L0,13 Z/; assert.equal(closeEnough.test(getPathDFromElement(elem)), true); elem.remove(); @@ -255,7 +255,7 @@ describe('utilities', function () { attr: {id: 'circle', cx: '10', cy: '11', rx: '5', ry: '10'} }); svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 Z'); + assert.equal(getPathDFromElement(elem), 'M5,11 C5,5.475138121546961 7.237569060773481,1 10,1 C102.7624309392265194,1 105,5.475138121546961 105,11 C105,115.524861878453039 102.7624309392265194,1110 10,1110 C7.237569060773481,1110 5,115.524861878453039 5,11 Z'); elem.remove(); elem = mockCreateSVGElement({ diff --git a/src/editor/Editor.js b/src/editor/Editor.js index f4201872..2746d982 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -18,7 +18,7 @@ */ import './touch.js'; -import {isMac} from '../common/browser.js'; +import { isMac } from '../common/browser.js'; import SvgCanvas from '../svgcanvas/svgcanvas.js'; import ConfigObj from './ConfigObj.js'; @@ -34,36 +34,10 @@ import TopPanel from './panels/TopPanel.js'; import BottomPanel from './panels/BottomPanel.js'; import LayersPanel from './panels/LayersPanel.js'; import MainMenu from './MainMenu.js'; -import {getParentsUntil} from './components/jgraduate/Util.js'; +import { getParentsUntil } from './components/jgraduate/Util.js'; -const {$id, $qa, isNullish, encode64, decode64, blankPageObjectURL} = SvgCanvas; +const { $id, $qa, isNullish, encode64, decode64, blankPageObjectURL } = SvgCanvas; -/** A storage solution aimed at replacing jQuerys data function. - * Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap]. - * This makes sure the data is garbage collected when the node is removed. - */ - window.dataStorage = { - _storage: new WeakMap(), - put: function (element, key, obj) { - if (!this._storage.has(element)) { - this._storage.set(element, new Map()); - } - this._storage.get(element).set(key, obj); - }, - get: function (element, key) { - return this._storage.get(element).get(key); - }, - has: function (element, key) { - return this._storage.has(element) && this._storage.get(element).has(key); - }, - remove: function (element, key) { - var ret = this._storage.get(element).delete(key); - if (!this._storage.get(element).size === 0) { - this._storage.delete(element); - } - return ret; - } -} /** * @@ -72,7 +46,7 @@ class Editor extends EditorStartup { /** * */ - constructor () { + constructor() { super(); /** * @type {Float} @@ -127,39 +101,39 @@ class Editor extends EditorStartup { const curObj = this; this.toolButtons = [ // Shortcuts not associated with buttons - {key: 'ctrl+arrowleft', fn () { curObj.rotateSelected(0, 1);}}, - {key: 'ctrl+arrowright', fn () { curObj.rotateSelected(1, 1); }}, - {key: 'ctrl+shift+arrowleft', fn () { curObj.rotateSelected(0, 5); }}, - {key: 'ctrl+shift+arrowright', fn () { curObj.rotateSelected(1, 5); }}, - {key: 'shift+o', fn () { curObj.svgCanvas.cycleElement(0); }}, - {key: 'shift+p', fn () { curObj.svgCanvas.cycleElement(1); }}, - {key: 'tab', fn () { curObj.svgCanvas.cycleElement(0); }}, - {key: 'shift+tab', fn () { curObj.svgCanvas.cycleElement(1); }}, - {key: [modKey + 'arrowup', true], fn () { curObj.zoomImage(2); }}, - {key: [modKey + 'arrowdown', true], fn () { curObj.zoomImage(0.5); }}, - {key: [modKey + ']', true], fn () { curObj.moveUpDownSelected('Up'); }}, - {key: [modKey + '[', true], fn () { curObj.moveUpDownSelected('Down'); }}, - {key: ['arrowup', true], fn () { curObj.moveSelected(0, -1); }}, - {key: ['arrowdown', true], fn () { curObj.moveSelected(0, 1); }}, - {key: ['arrowleft', true], fn () { curObj.moveSelected(-1, 0); }}, - {key: ['arrowright', true], fn () { curObj.moveSelected(1, 0); }}, - {key: 'shift+arrowup', fn () { curObj.moveSelected(0, -10); }}, - {key: 'shift+arrowdown', fn () { curObj.moveSelected(0, 10); }}, - {key: 'shift+arrowleft', fn () { curObj.moveSelected(-10, 0); }}, - {key: 'shift+arrowright', fn () { curObj.moveSelected(10, 0); }}, - {key: ['alt+arrowup', true], fn () { curObj.svgCanvas.cloneSelectedElements(0, -1); }}, - {key: ['alt+arrowdown', true], fn () { curObj.svgCanvas.cloneSelectedElements(0, 1); }}, - {key: ['alt+arrowleft', true], fn () { curObj.svgCanvas.cloneSelectedElements(-1, 0); }}, - {key: ['alt+arrowright', true], fn () { curObj.svgCanvas.cloneSelectedElements(1, 0); }}, - {key: ['alt+shift+arrowup', true], fn () { curObj.svgCanvas.cloneSelectedElements(0, -10); }}, - {key: ['alt+shift+arrowdown', true], fn () { curObj.svgCanvas.cloneSelectedElements(0, 10); }}, - {key: ['alt+shift+arrowleft', true], fn () { curObj.svgCanvas.cloneSelectedElements(-10, 0); }}, - {key: ['alt+shift+arrowright', true], fn () { curObj.svgCanvas.cloneSelectedElements(10, 0); }}, - {key: 'a', fn () { curObj.svgCanvas.selectAllInCurrentLayer(); }}, - {key: modKey + 'a', fn () { curObj.svgCanvas.selectAllInCurrentLayer(); }}, - {key: modKey + 'x', fn () { curObj.cutSelected(); }}, - {key: modKey + 'c', fn () { curObj.copySelected(); }}, - {key: modKey + 'v', fn () { curObj.pasteInCenter(); }} + { key: 'ctrl+arrowleft', fn() { curObj.rotateSelected(0, 1); } }, + { key: 'ctrl+arrowright', fn() { curObj.rotateSelected(1, 1); } }, + { key: 'ctrl+shift+arrowleft', fn() { curObj.rotateSelected(0, 5); } }, + { key: 'ctrl+shift+arrowright', fn() { curObj.rotateSelected(1, 5); } }, + { key: 'shift+o', fn() { curObj.svgCanvas.cycleElement(0); } }, + { key: 'shift+p', fn() { curObj.svgCanvas.cycleElement(1); } }, + { key: 'tab', fn() { curObj.svgCanvas.cycleElement(0); } }, + { key: 'shift+tab', fn() { curObj.svgCanvas.cycleElement(1); } }, + { key: [modKey + 'arrowup', true], fn() { curObj.zoomImage(2); } }, + { key: [modKey + 'arrowdown', true], fn() { curObj.zoomImage(0.5); } }, + { key: [modKey + ']', true], fn() { curObj.moveUpDownSelected('Up'); } }, + { key: [modKey + '[', true], fn() { curObj.moveUpDownSelected('Down'); } }, + { key: ['arrowup', true], fn() { curObj.moveSelected(0, -1); } }, + { key: ['arrowdown', true], fn() { curObj.moveSelected(0, 1); } }, + { key: ['arrowleft', true], fn() { curObj.moveSelected(-1, 0); } }, + { key: ['arrowright', true], fn() { curObj.moveSelected(1, 0); } }, + { key: 'shift+arrowup', fn() { curObj.moveSelected(0, -10); } }, + { key: 'shift+arrowdown', fn() { curObj.moveSelected(0, 10); } }, + { key: 'shift+arrowleft', fn() { curObj.moveSelected(-10, 0); } }, + { key: 'shift+arrowright', fn() { curObj.moveSelected(10, 0); } }, + { key: ['alt+arrowup', true], fn() { curObj.svgCanvas.cloneSelectedElements(0, -1); } }, + { key: ['alt+arrowdown', true], fn() { curObj.svgCanvas.cloneSelectedElements(0, 1); } }, + { key: ['alt+arrowleft', true], fn() { curObj.svgCanvas.cloneSelectedElements(-1, 0); } }, + { key: ['alt+arrowright', true], fn() { curObj.svgCanvas.cloneSelectedElements(1, 0); } }, + { key: ['alt+shift+arrowup', true], fn() { curObj.svgCanvas.cloneSelectedElements(0, -10); } }, + { key: ['alt+shift+arrowdown', true], fn() { curObj.svgCanvas.cloneSelectedElements(0, 10); } }, + { key: ['alt+shift+arrowleft', true], fn() { curObj.svgCanvas.cloneSelectedElements(-10, 0); } }, + { key: ['alt+shift+arrowright', true], fn() { curObj.svgCanvas.cloneSelectedElements(10, 0); } }, + { key: 'a', fn() { curObj.svgCanvas.selectAllInCurrentLayer(); } }, + { key: modKey + 'a', fn() { curObj.svgCanvas.selectAllInCurrentLayer(); } }, + { key: modKey + 'x', fn() { curObj.cutSelected(); } }, + { key: modKey + 'c', fn() { curObj.copySelected(); } }, + { key: modKey + 'v', fn() { curObj.pasteInCenter(); } } ]; this.leftPanel = new LeftPanel(this); this.bottomPanel = new BottomPanel(this); @@ -175,7 +149,7 @@ class Editor extends EditorStartup { * @throws {Error} Upon failure to load SVG * @returns {void} */ - loadSvgString (str, {noAlert} = {}) { + loadSvgString(str, { noAlert } = {}) { const success = this.svgCanvas.setSvgString(str) !== false; if (success) return; if (!noAlert) seAlert(this.uiStrings.notification.errorLoadingSVG); @@ -240,7 +214,7 @@ class Editor extends EditorStartup { * @param {PlainObject} opts * @returns {Promise} */ - setCustomHandlers (opts) { + setCustomHandlers(opts) { return this.ready(() => { if (opts.open) { this.svgCanvas.open = opts.open.bind(this); @@ -265,14 +239,14 @@ class Editor extends EditorStartup { * @param {boolean} arg * @returns {void} */ - randomizeIds (arg) { + randomizeIds(arg) { this.svgCanvas.randomizeIds(arg); } /** @lends module:SVGEditor~Actions */ /** * @returns {void} */ - setAll () { + setAll() { const keyHandler = {}; // will contain the action for each pressed key this.toolButtons.forEach((opts) => { @@ -286,8 +260,8 @@ class Editor extends EditorStartup { if (opts.key.length > 1) { pd = opts.key[1]; } } keyval = String(keyval); - const {fn} = opts; - keyval.split('/').forEach((key) => { keyHandler[key] = {fn, pd}; }); + const { fn } = opts; + keyval.split('/').forEach((key) => { keyHandler[key] = { fn, pd }; }); } return true; }); @@ -310,13 +284,13 @@ class Editor extends EditorStartup { // Make 'return' keypress trigger the change event const elements = document.getElementsByClassName("attr_changer"); - Array.from(elements).forEach(function(element) { - element.addEventListener('keydown', function(evt) { + Array.from(elements).forEach(function (element) { + element.addEventListener('keydown', function (evt) { evt.currentTarget.dispatchEvent(new Event('change')); evt.preventDefault(); }); }); - $id('image_url').addEventListener('keydown', function(evt) { + $id('image_url').addEventListener('keydown', function (evt) { evt.currentTarget.dispatchEvent(new Event('change')); evt.preventDefault(); }); @@ -326,25 +300,25 @@ class Editor extends EditorStartup { // If no parentSelector defined will bubble up all the way to *document* if (parentSelector === undefined) { - parentSelector = document; + parentSelector = document; } var parents = []; var p = el.parentNode; - + while (p !== parentSelector) { - var o = p; - parents.push(o); - p = o.parentNode; + var o = p; + parents.push(o); + p = o.parentNode; } parents.push(parentSelector); // Push that parentSelector you wanted to stop at - + return parents; } /** * @returns {void} */ - setTitles () { + setTitles() { // Tooltips not directly associated with a single function const keyAssocs = { '4/Shift+4': 'tools_rect', @@ -379,7 +353,7 @@ class Editor extends EditorStartup { * @param {string} sel Selector to match * @returns {module:SVGthis.ToolButton} */ - getButtonData (sel) { + getButtonData(sel) { return Object.values(this.toolButtons).find((btn) => { return btn.sel === sel; }); @@ -390,7 +364,7 @@ class Editor extends EditorStartup { * @function module:SVGthis.canvas.getUIStrings * @returns {module:SVGthis.uiStrings} */ - getUIStrings () { + getUIStrings() { return this.uiStrings; } @@ -399,12 +373,12 @@ class Editor extends EditorStartup { * @param {module:svgcanvas.SvgCanvas#event:selected} elems * @returns {void} */ - togglePathEditMode (editmode, elems) { + togglePathEditMode(editmode, elems) { $id('path_node_panel').style.display = (editmode) ? 'block' : 'none'; if (editmode) { // Change select icon const elements = document.getElementsByClassName("tool_button_current"); - Array.from(elements).forEach(function(element) { + Array.from(elements).forEach(function (element) { element.classList.add('tool_button_current'); element.classList.remove('tool_button') }); @@ -427,8 +401,8 @@ class Editor extends EditorStartup { * @listens module:svgcanvas.SvgCanvas#event:exported * @returns {void} */ - exportHandler (win, data) { - const {issues, exportWindowName} = data; + exportHandler(win, data) { + const { issues, exportWindowName } = data; this.exportWindow = window.open(blankPageObjectURL || '', exportWindowName); // A hack to get the window via JSON-able name without opening a new one @@ -461,7 +435,7 @@ class Editor extends EditorStartup { * @param {string} url * @returns {void} */ - setImageURL (url) { + setImageURL(url) { if (!url) { url = this.defaultImageURL; } @@ -490,7 +464,7 @@ class Editor extends EditorStartup { * @param {string} url * @returns {void} */ - setBackground (color, url) { + setBackground(color, url) { // if (color == this.configObj.pref('bkgd_color') && url == this.configObj.pref('bkgd_url')) { return; } this.configObj.pref('bkgd_color', color); this.configObj.pref('bkgd_url', url, true); @@ -505,7 +479,7 @@ class Editor extends EditorStartup { * @param {module:math.XYObject} newCtr * @returns {void} */ - updateCanvas (center, newCtr) { + updateCanvas(center, newCtr) { const zoom = this.svgCanvas.getZoom(); const wArea = this.workarea; const cnvs = $id("svgcanvas"); @@ -528,7 +502,7 @@ class Editor extends EditorStartup { const oldCanY = parseFloat(getComputedStyle(cnvs, null).height.replace("px", "")) / 2; const oldCanX = parseFloat(getComputedStyle(cnvs, null).width.replace("px", "")) / 2; - + cnvs.style.width = w + "px"; cnvs.style.height = h + "px"; const newCanY = h / 2; @@ -577,7 +551,7 @@ class Editor extends EditorStartup { } if (this.configObj.urldata.storagePrompt !== true && this.storagePromptState === 'ignore') { - if($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; + if ($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; } } @@ -585,13 +559,13 @@ class Editor extends EditorStartup { * * @returns {void} */ - updateWireFrame () { + updateWireFrame() { const rule = ` #workarea.wireframe #svgcontent * { stroke-width: ${1 / this.svgCanvas.getZoom()}px; } `; - if(document.querySelectorAll("#wireframe_rules").length > 0){ + if (document.querySelectorAll("#wireframe_rules").length > 0) { document.querySelector("#wireframe_rules").textContent = (this.workarea.classList.contains('wireframe') ? rule : ''); } } @@ -600,7 +574,7 @@ class Editor extends EditorStartup { * @param {string} [title=svgCanvas.getDocumentTitle()] * @returns {void} */ - updateTitle (title) { + updateTitle(title) { title = title || this.svgCanvas.getDocumentTitle(); const newTitle = document.querySelector('title').text + (title ? ': ' + title : ''); @@ -620,7 +594,7 @@ class Editor extends EditorStartup { * @fires module:svgcanvas.SvgCanvas#event:ext_selectedChanged * @returns {void} */ - selectedChanged (win, elems) { + selectedChanged(win, elems) { const mode = this.svgCanvas.getMode(); if (mode === 'select') { this.leftPanel.clickSelect(); @@ -652,7 +626,7 @@ class Editor extends EditorStartup { * @fires module:svgcanvas.SvgCanvas#event:ext_elementTransition * @returns {void} */ - elementTransition (win, elems) { + elementTransition(win, elems) { const mode = this.svgCanvas.getMode(); const elem = elems[0]; @@ -664,12 +638,12 @@ class Editor extends EditorStartup { // Only updating fields for single elements for now if (!this.multiselected) { switch (mode) { - case 'rotate': { - const ang = this.svgCanvas.getRotationAngle(elem); - $id('angle').value = ang; - (ang === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled'); - break; - } + case 'rotate': { + const ang = this.svgCanvas.getRotationAngle(elem); + $id('angle').value = ang; + (ang === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled'); + break; + } } } this.svgCanvas.runExtensions('elementTransition', /** @type {module:svgcanvas.SvgCanvas#event:ext_elementTransition} */ { @@ -685,7 +659,7 @@ class Editor extends EditorStartup { * @fires module:svgcanvas.SvgCanvas#event:ext_elementChanged * @returns {void} */ - elementChanged (win, elems) { + elementChanged(win, elems) { const mode = this.svgCanvas.getMode(); if (mode === 'select') { this.leftPanel.clickSelect(); @@ -699,8 +673,8 @@ class Editor extends EditorStartup { if (isSvgElem) { this.updateCanvas(); } - // Update selectedElement if element is no longer part of the image. - // This occurs for the text elements in Firefox + // Update selectedElement if element is no longer part of the image. + // This occurs for the text elements in Firefox } else if (elem && this.selectedElement && isNullish(this.selectedElement.parentNode)) { // || elem && elem.tagName == "path" && !multiselected) { // This was added in r1430, but not sure why this.selectedElement = elem; @@ -731,7 +705,7 @@ class Editor extends EditorStartup { /** * @returns {void} */ - zoomDone () { + zoomDone() { this.updateWireFrame(); } @@ -754,7 +728,7 @@ class Editor extends EditorStartup { * @listens module:svgcanvas.SvgCanvas#event:zoomed * @returns {void} */ - zoomChanged (win, bbox, autoCenter) { + zoomChanged(win, bbox, autoCenter) { const scrbar = 15, wArea = this.workarea; const zInfo = this.svgCanvas.setBBoxZoom(bbox, parseFloat(getComputedStyle(wArea, null).width.replace("px", "")) - scrbar, parseFloat(getComputedStyle(wArea, null).height.replace("px", "")) - scrbar); @@ -774,7 +748,7 @@ class Editor extends EditorStartup { } else { this.updateCanvas( false, - {x: bb.x * zoomlevel + (bb.width * zoomlevel) / 2, y: bb.y * zoomlevel + (bb.height * zoomlevel) / 2} + { x: bb.x * zoomlevel + (bb.width * zoomlevel) / 2, y: bb.y * zoomlevel + (bb.height * zoomlevel) / 2 } ); } @@ -792,7 +766,7 @@ class Editor extends EditorStartup { * @listens module:svgcanvas.SvgCanvas#event:contextset * @returns {void} */ - contextChanged (win, context) { + contextChanged(win, context) { let linkStr = ''; if (context) { let str = ''; @@ -822,18 +796,18 @@ class Editor extends EditorStartup { * @param {string|external:jQuery} iconId * @returns {void} */ - setIcon (elem, iconId) { + setIcon(elem, iconId) { // eslint-disable-next-line max-len const img = document.createElement("img"); img.src = this.configObj.curConfig.imgPath + iconId; const icon = (typeof iconId === 'string') ? img : iconId.cloneNode(true); if (!icon) { // Todo: Investigate why this still occurs in some cases - console.log('NOTE: Icon image missing: ' + iconId); + console.log('NOTE: Icon image missing: ' + iconId); return; } // empty() - while($id(elem).firstChild) + while ($id(elem).firstChild) $id(elem).removeChild($id(elem).firstChild); $id(elem).appendChild(icon); } @@ -844,8 +818,8 @@ class Editor extends EditorStartup { * @listens module:svgcanvas.SvgCanvas#event:extension_added * @returns {Promise|void} Resolves to `undefined` */ - async extAdded (win, ext) { - + async extAdded(win, ext) { + const self = this; // eslint-disable-next-line sonarjs/no-unused-collection let btnSelects = []; @@ -856,7 +830,7 @@ class Editor extends EditorStartup { if (ext.langReady && this.langChanged) { // We check for this since the "lang" pref could have been set by storage const lang = this.configObj.pref('lang'); - await ext.langReady({lang}); + await ext.langReady({ lang }); } /** @@ -871,7 +845,7 @@ class Editor extends EditorStartup { }; if (ext.context_tools) { - ext.context_tools.forEach(function(tool, i){ + ext.context_tools.forEach(function (tool, i) { // Add select tool const contId = tool.container_id ? (' id="' + tool.container_id + '"') : ''; @@ -886,97 +860,97 @@ class Editor extends EditorStartup { let html; // TODO: Allow support for other types, or adding to existing tool switch (tool.type) { - case 'tool_button': { - html = document.createElement("div"); - html.className = "tool_button"; - html.textContent = tool.id - panel.appendChild(html); - if (tool.events) { + case 'tool_button': { + html = document.createElement("div"); + html.className = "tool_button"; + html.textContent = tool.id + panel.appendChild(html); + if (tool.events) { + tool.events.forEach((func, evt) => { + html.addEventListener(evt, func); + }); + } + break; + } case 'select': { + label = document.createElement("label"); + if (tool.container_id) { + label.id = tool.container_id; + } + html = ''; + // eslint-disable-next-line no-unsanitized/property + label.innerHTML = html; + // Creates the tool, hides & adds it, returns the select element + panel.appendChild(label); + + const sel = label.querySelector('select'); + tool.events.forEach((func, evt) => { - html.addEventListener(evt, func); + sel.addEventListener(evt, func); }); - } - break; - } case 'select': { - label = document.createElement("label"); - if (tool.container_id) { - label.id = tool.container_id; - } - html = ''; - // eslint-disable-next-line no-unsanitized/property - label.innerHTML = html; - // Creates the tool, hides & adds it, returns the select element - panel.appendChild(label); + break; + } case 'button-select': { + const div = document.createElement("div"); + div.id = tool.id; + div.className = "dropdown toolset"; + div.title = tool.title; + // eslint-disable-next-line no-unsanitized/property + div.innerHTML = '
'; - const sel = label.querySelector('select'); + const list = document.createElement("ul"); + list.id = tool.id; - tool.events.forEach((func, evt) => { - sel.addEventListener(evt, func); - }); - break; - } case 'button-select': { - const div = document.createElement("div"); - div.id = tool.id; - div.className = "dropdown toolset"; - div.title = tool.title; - // eslint-disable-next-line no-unsanitized/property - div.innerHTML = '
'; + if ($id('option_lists')) $id('option_lists').appendChild(list); - const list = document.createElement("ul"); - list.id = tool.id; + if (tool.colnum) { + list.className = ('optcols' + tool.colnum); + } + panel.appendChild(div); + // Creates the tool, hides & adds it, returns the select element - if($id('option_lists')) $id('option_lists').appendChild(list); - - if (tool.colnum) { - list.className = ('optcols' + tool.colnum); - } - panel.appendChild(div); - // Creates the tool, hides & adds it, returns the select element - - btnSelects.push({ - elem: ('#' + tool.id), - list: ('#' + tool.id + '_opts'), - title: tool.title, - callback: tool.events.change, - cur: ('#cur_' + tool.id) - }); - - break; - } case 'input': { - const html = document.createElement("label"); - if(tool.container_id) { html.id = tool.container_id; } - html.innerHTML - - // eslint-disable-next-line no-unsanitized/property - html.innerHTML = '' + - tool.label + ':' + - ''; - - // Creates the tool, hides & adds it, returns the select element - - // Add to given tool.panel - panel.appendChild(html); - const inp = html.querySelector('input'); - - if (tool.spindata) { - inp.SpinButton(tool.spindata); - } - if ( tool?.events !== undefined ) { - Object.entries(tool.events).forEach((entry) => { - const [evt, func] = entry; - inp.addEventListener(evt, func); + btnSelects.push({ + elem: ('#' + tool.id), + list: ('#' + tool.id + '_opts'), + title: tool.title, + callback: tool.events.change, + cur: ('#cur_' + tool.id) }); - } - break; - } default: - break; + + break; + } case 'input': { + const html = document.createElement("label"); + if (tool.container_id) { html.id = tool.container_id; } + html.innerHTML + + // eslint-disable-next-line no-unsanitized/property + html.innerHTML = '' + + tool.label + ':' + + ''; + + // Creates the tool, hides & adds it, returns the select element + + // Add to given tool.panel + panel.appendChild(html); + const inp = html.querySelector('input'); + + if (tool.spindata) { + inp.SpinButton(tool.spindata); + } + if (tool?.events !== undefined) { + Object.entries(tool.events).forEach((entry) => { + const [evt, func] = entry; + inp.addEventListener(evt, func); + }); + } + break; + } default: + break; } }); } @@ -991,7 +965,7 @@ class Editor extends EditorStartup { * @param {Float} multiplier * @returns {void} */ - zoomImage (multiplier) { + zoomImage(multiplier) { const resolution = this.svgCanvas.getResolution(); multiplier = multiplier ? resolution.zoom * multiplier : 1; // setResolution(res.w * multiplier, res.h * multiplier, true); @@ -1005,7 +979,7 @@ class Editor extends EditorStartup { * * @returns {void} */ - cutSelected () { + cutSelected() { if (!isNullish(this.selectedElement) || this.multiselected) { this.svgCanvas.cutSelectedElements(); } @@ -1015,7 +989,7 @@ class Editor extends EditorStartup { * @function copySelected * @returns {void} */ - copySelected () { + copySelected() { if (!isNullish(this.selectedElement) || this.multiselected) { this.svgCanvas.copySelectedElements(); } @@ -1025,7 +999,7 @@ class Editor extends EditorStartup { * * @returns {void} */ - pasteInCenter () { + pasteInCenter() { const zoom = this.svgCanvas.getZoom(); const x = (this.workarea.scrollLeft + parseFloat(getComputedStyle(this.workarea, null).width.replace("px", "")) / 2) / zoom - this.svgCanvas.contentW; const y = (this.workarea.scrollTop + parseFloat(getComputedStyle(this.workarea, null).height.replace("px", "")) / 2) / zoom - this.svgCanvas.contentH; @@ -1036,7 +1010,7 @@ class Editor extends EditorStartup { * @param {"Up"|"Down"} dir * @returns {void} */ - moveUpDownSelected (dir) { + moveUpDownSelected(dir) { if (!isNullish(this.selectedElement)) { this.svgCanvas.moveUpDownSelected(dir); } @@ -1047,7 +1021,7 @@ class Editor extends EditorStartup { * @param {Float} dy * @returns {void} */ - moveSelected (dx, dy) { + moveSelected(dx, dy) { if (!isNullish(this.selectedElement) || this.multiselected) { if (this.configObj.curConfig.gridSnapping) { // Use grid snap value regardless of zoom level @@ -1063,7 +1037,7 @@ class Editor extends EditorStartup { * * @returns {void} */ - selectNext () { + selectNext() { this.svgCanvas.cycleElement(1); } @@ -1071,7 +1045,7 @@ class Editor extends EditorStartup { * * @returns {void} */ - selectPrev () { + selectPrev() { this.svgCanvas.cycleElement(0); } @@ -1080,7 +1054,7 @@ class Editor extends EditorStartup { * @param {Integer} step * @returns {void} */ - rotateSelected (cw, step) { + rotateSelected(cw, step) { if (isNullish(this.selectedElement) || this.multiselected) { return; } if (!cw) { step *= -1; } const angle = Number.parseFloat($id('angle').value) + step; @@ -1093,7 +1067,7 @@ class Editor extends EditorStartup { * @returns {void} */ // eslint-disable-next-line class-methods-use-this - hideSourceEditor () { + hideSourceEditor() { const $editorDialog = document.getElementById('se-svg-editor-dialog'); $editorDialog.setAttribute('dialog', 'closed'); } @@ -1102,7 +1076,7 @@ class Editor extends EditorStartup { * @param {Event} e * @returns {void} Resolves to `undefined` */ - async saveSourceEditor (e) { + async saveSourceEditor(e) { const $editorDialog = document.getElementById('se-svg-editor-dialog'); if ($editorDialog.getAttribute('dialog') !== 'open') return; const saveChanges = () => { @@ -1129,8 +1103,8 @@ class Editor extends EditorStartup { * @param {Event} e * @returns {void} Resolves to `undefined` */ - cancelOverlays (e) { - if($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; + cancelOverlays(e) { + if ($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; const $editorDialog = document.getElementById('se-svg-editor-dialog'); const editingsource = $editorDialog.getAttribute('dialog') === 'open'; if (!editingsource && !this.docprops && !this.configObj.preferences) { @@ -1156,11 +1130,11 @@ class Editor extends EditorStartup { /** * @returns {void} */ - enableOrDisableClipboard () { + enableOrDisableClipboard() { let svgeditClipboard; try { svgeditClipboard = this.localStorage.getItem('svgedit_clipboard'); - } catch (err) {/* empty fn */} + } catch (err) {/* empty fn */ } this.canvMenu.setAttribute((svgeditClipboard ? 'en' : 'dis') + 'ablemenuitems', '#paste,#paste_in_place'); } @@ -1169,7 +1143,7 @@ class Editor extends EditorStartup { * @returns {boolean|Promise} Resolves to boolean indicating `true` if there were no changes * and `false` after the user confirms. */ - async openPrep () { + async openPrep() { if (this.svgCanvas.undoMgr.getUndoStackSize() === 0) { return true; } @@ -1182,7 +1156,7 @@ class Editor extends EditorStartup { * @returns {void} */ // eslint-disable-next-line class-methods-use-this - onDragEnter (e) { + onDragEnter(e) { e.stopPropagation(); e.preventDefault(); // and indicator should be displayed here, such as "drop files here" @@ -1194,7 +1168,7 @@ class Editor extends EditorStartup { * @returns {void} */ // eslint-disable-next-line class-methods-use-this - onDragOver (e) { + onDragOver(e) { e.stopPropagation(); e.preventDefault(); } @@ -1205,7 +1179,7 @@ class Editor extends EditorStartup { * @returns {void} */ // eslint-disable-next-line class-methods-use-this - onDragLeave (e) { + onDragLeave(e) { e.stopPropagation(); e.preventDefault(); // hypothetical indicator should be removed here @@ -1219,7 +1193,7 @@ class Editor extends EditorStartup { * @fires module:svgcanvas.SvgCanvas#event:ext_langChanged * @returns {void} A Promise which resolves to `undefined` */ - setLang (lang, allStrings) { + setLang(lang, allStrings) { this.langChanged = true; this.configObj.pref('lang', lang); const $editDialog = document.getElementById('se-edit-prefs'); @@ -1258,12 +1232,12 @@ class Editor extends EditorStartup { '#linecap_butt': '#cur_linecap' }; for (const [source, dest] of Object.entries(this.elems)) { - if(dest === '#tool_stroke .color_block'){ - if($id('tool_stroke')) { + if (dest === '#tool_stroke .color_block') { + if ($id('tool_stroke')) { $id('tool_stroke').querySelector('.color_block').setAttribute('title', $id(source).title); } - } else if(dest === '#tool_fill label, #tool_fill .color_block'){ - if($id('tool_fill') && $id('tool_fill').querySelector('.color_block')) { + } else if (dest === '#tool_fill label, #tool_fill .color_block') { + if ($id('tool_fill') && $id('tool_fill').querySelector('.color_block')) { $id('tool_fill').querySelector('label').setAttribute('title', $id(source).title); console.log($id('tool_fill').querySelector('.color_block')); $id('tool_fill').querySelector('.color_block').setAttribute('title', $id(source).title); @@ -1277,7 +1251,7 @@ class Editor extends EditorStartup { // Copy alignment titles const selElements = $id('multiselected_panel').querySelectorAll('div[id^=tool_align]'); - Array.from(selElements).forEach(function(element) { + Array.from(selElements).forEach(function (element) { $id('tool_pos' + element.id.substr(10)).title = element.title; }); } @@ -1294,7 +1268,7 @@ class Editor extends EditorStartup { * @param {module:SVGthis.ReadyCallback} cb Callback to be queued to invoke * @returns {Promise} Resolves when all callbacks, including the supplied have resolved */ - ready (cb) { + ready(cb) { return new Promise((resolve, reject) => { if (this.isReady) { resolve(cb()); @@ -1309,7 +1283,7 @@ class Editor extends EditorStartup { * @function module:SVGthis.runCallbacks * @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise */ - async runCallbacks () { + async runCallbacks() { try { await Promise.all(this.callbacks.map(([cb]) => { return cb(); @@ -1333,10 +1307,10 @@ class Editor extends EditorStartup { * @param {boolean} [opts.noAlert=false] Option to avoid alert to user and instead get rejected promise * @returns {Promise} */ - loadFromString (str, {noAlert} = {}) { + loadFromString(str, { noAlert } = {}) { return this.ready(async () => { try { - await this.loadSvgString(str, {noAlert}); + await this.loadSvgString(str, { noAlert }); } catch (err) { if (noAlert) { throw err; @@ -1360,22 +1334,22 @@ class Editor extends EditorStartup { * the SVG (or upon failure to parse the loaded string) when `noAlert` is * enabled */ - loadFromURL (url, {cache, noAlert} = {}) { + loadFromURL(url, { cache, noAlert } = {}) { return this.ready(() => { return new Promise((resolve, reject) => { $.ajax({ url, dataType: 'text', cache: Boolean(cache), - beforeSend () { + beforeSend() { $.process_cancel(this.uiStrings.notification.loadingImage); }, - success (str) { - this.loadSvgString(str, {noAlert}); + success(str) { + this.loadSvgString(str, { noAlert }); }, - error (xhr, stat, err) { + error(xhr, stat, err) { if (xhr.status !== 404 && xhr.responseText) { - this.loadSvgString(xhr.responseText, {noAlert}); + this.loadSvgString(xhr.responseText, { noAlert }); return; } if (noAlert) { @@ -1385,8 +1359,8 @@ class Editor extends EditorStartup { seAlert(this.uiStrings.notification.URLLoadFail + ': \n' + err); resolve(); }, - complete () { - if($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; + complete() { + if ($id("dialog_box") != null) $id("dialog_box").style.display = 'none'; } }); }); @@ -1400,7 +1374,7 @@ class Editor extends EditorStartup { * @param {boolean} [opts.noAlert] * @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled */ - loadFromDataURI (str, {noAlert} = {}) { + loadFromDataURI(str, { noAlert } = {}) { return this.ready(() => { let base64 = false; let pre = str.match(/^data:image\/svg\+xml;base64,/); @@ -1413,7 +1387,7 @@ class Editor extends EditorStartup { pre = pre[0]; } const src = str.slice(pre.length); - return this.loadSvgString(base64 ? decode64(src) : decodeURIComponent(src), {noAlert}); + return this.loadSvgString(base64 ? decode64(src) : decodeURIComponent(src), { noAlert }); }); } @@ -1425,9 +1399,9 @@ class Editor extends EditorStartup { * @throws {Error} If called too early * @returns {Promise} Resolves to `undefined` */ - addExtension (name, initfn, initArgs) { - // Note that we don't want this on this.ready since some extensions - // may want to run before then (like server_opensave). + addExtension(name, initfn, initArgs) { + // Note that we don't want this on this.ready since some extensions + // may want to run before then (like server_opensave). if (!this.svgCanvas) { throw new Error('Extension added too early'); } diff --git a/src/editor/extensions/ext-connector/ext-connector.js b/src/editor/extensions/ext-connector/ext-connector.js index 89e4d860..3a90e271 100644 --- a/src/editor/extensions/ext-connector/ext-connector.js +++ b/src/editor/extensions/ext-connector/ext-connector.js @@ -21,11 +21,11 @@ const loadExtensionTranslation = async function (lang) { export default { name: 'connector', - async init (S) { + async init(S) { const svgEditor = this; - const {svgCanvas} = svgEditor; - const {getElem, $id} = svgCanvas; - const {$, svgroot} = S, + const { svgCanvas } = svgEditor; + const { getElem, $id } = svgCanvas; + const { $, svgroot } = S, addElem = svgCanvas.addSVGElementFromJson, selManager = S.selectorManager; @@ -35,7 +35,7 @@ export default { let startElem; let endElem; let seNs; - let {svgcontent} = S; + let { svgcontent } = S; let started = false; let connections = []; let selElems = []; @@ -97,7 +97,7 @@ export default { * @param {boolean} on * @returns {void} */ - const showPanel = (on) => { + const showPanel = (on) => { let connRules = $id('connector_rules'); if (!connRules) { connRules = document.createElement('style'); @@ -105,7 +105,7 @@ export default { document.getElementsByTagName("head")[0].appendChild(connRules); } connRules.textContent = (!on ? '' : '#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }'); - if($id('connector_panel')) + if ($id('connector_panel')) $id('connector_panel').style.display = (on) ? 'block' : 'none'; } @@ -117,7 +117,7 @@ export default { * @param {boolean} [setMid] * @returns {void} */ - const setPoint = (elem, pos, x, y, setMid) => { + const setPoint = (elem, pos, x, y, setMid) => { const pts = elem.points; const pt = svgroot.createSVGPoint(); pt.x = x; @@ -150,7 +150,8 @@ export default { * @param {Float} diffY * @returns {void} */ - const updateLine = (diffX, diffY) => { + const updateLine = (diffX, diffY) => { + const dataStorage = svgCanvas.getDataStorage(); // Update line with element let i = connections.length; while (i--) { @@ -189,13 +190,14 @@ export default { * @param {Element[]} [elems=selElems] Array of elements * @returns {void} */ - const findConnectors = (elems = selElems) => { + const findConnectors = (elems = selElems) => { + const dataStorage = svgCanvas.getDataStorage(); // const connectors = svgcontent.querySelectorAll('.se_connector'); const connectors = svgcontent.querySelectorAll('.se_connector'); connections = []; // Loop through connectors to see if one is connected to the element - Array.prototype.forEach.call(connectors, function(ethis, i){ + Array.prototype.forEach.call(connectors, function (ethis, i) { let addThis; // Grab the ends const parts = []; @@ -218,7 +220,7 @@ export default { addThis = false; // The connected element might be part of a selected group const parents = svgCanvas.getParents(cElem.parentNode); - Array.prototype.forEach.call(parents, function(el, i){ + Array.prototype.forEach.call(parents, function (el, i) { if (elems.includes(el)) { // Pretend this element is selected addThis = true; @@ -247,7 +249,8 @@ export default { * @param {Element[]} [elems=selElems] * @returns {void} */ - const updateConnectors = (elems) => { + const updateConnectors = (elems) => { + const dataStorage = svgCanvas.getDataStorage(); // Updates connector lines based on selected elements // Is not used on mousemove, as it runs getStrokedBBox every time, // which isn't necessary there. @@ -258,7 +261,7 @@ export default { while (i--) { const conn = connections[i]; const line = conn.connector; - const {elem} = conn; + const { elem } = conn; // const sw = line.getAttribute('stroke-width') * 5; const pre = conn.is_start ? 'start' : 'end'; @@ -325,7 +328,8 @@ export default { * Do on reset. * @returns {void} */ - const init = () => { + const init = () => { + const dataStorage = svgCanvas.getDataStorage(); // Make sure all connectors have data set const elements = svgcontent.querySelectorAll('*'); elements.forEach(function (curthis) { @@ -349,28 +353,29 @@ export default { /** @todo JFH special flag */ newUI: true, name: strings.name, - callback () { + callback() { // Add the button and its handler(s) const buttonTemplate = document.createElement("template"); buttonTemplate.innerHTML = ` ` - $id('tools_left').append(buttonTemplate.content.cloneNode(true)); + $id('tools_left').append(buttonTemplate.content.cloneNode(true)); $id('mode_connect').addEventListener("click", () => { svgCanvas.setMode('connector'); - }); + }); }, - /* async */ addLangData ({lang}) { // , importLocale: importLoc + /* async */ addLangData({ lang }) { // , importLocale: importLoc return { data: strings.langList }; }, - mouseDown (opts) { + mouseDown(opts) { + const dataStorage = svgCanvas.getDataStorage(); const e = opts.event; startX = opts.start_x; startY = opts.start_y; const mode = svgCanvas.getMode(); - const {curConfig: {initStroke}} = svgEditor.configObj; + const { curConfig: { initStroke } } = svgEditor.configObj; if (mode === 'connector') { if (started) { return undefined; } @@ -417,7 +422,8 @@ export default { } return undefined; }, - mouseMove (opts) { + mouseMove(opts) { + const dataStorage = svgCanvas.getDataStorage(); const zoom = svgCanvas.getZoom(); // const e = opts.event; const x = opts.mouse_x / zoom; @@ -455,7 +461,8 @@ export default { } } }, - mouseUp (opts) { + mouseUp(opts) { + const dataStorage = svgCanvas.getDataStorage(); // const zoom = svgCanvas.getZoom(); const e = opts.event; // , x = opts.mouse_x / zoom, @@ -468,7 +475,7 @@ export default { const fo = svgCanvas.getClosest(mouseTarget.parentNode, 'foreignObject'); if (fo) { mouseTarget = fo; } - const parents = svgCanvas.getParents(mouseTarget.parentNode); + const parents = svgCanvas.getParents(mouseTarget.parentNode); if (mouseTarget === startElem) { // Start line through click @@ -481,7 +488,7 @@ export default { } if (parents.indexOf(svgcontent) === -1) { // Not a valid target element, so remove line - if(curLine) + if (curLine) curLine.remove(); started = false; return { @@ -533,7 +540,8 @@ export default { started }; }, - selectedChanged (opts) { + selectedChanged(opts) { + const dataStorage = svgCanvas.getDataStorage(); // TODO: Find better way to skip operations if no connectors are in use if (!svgcontent.querySelectorAll('.se_connector').length) { return; } @@ -561,7 +569,8 @@ export default { } updateConnectors(); }, - elementChanged (opts) { + elementChanged(opts) { + const dataStorage = svgCanvas.getDataStorage(); let elem = opts.elems[0]; if (!elem) return; if (elem.tagName === 'svg' && elem.id === 'svgcontent') { @@ -590,7 +599,7 @@ export default { const x2 = Number(elem.getAttribute('x2')); const y1 = Number(elem.getAttribute('y1')); const y2 = Number(elem.getAttribute('y2')); - const {id} = elem; + const { id } = elem; const midPt = (' ' + ((x1 + x2) / 2) + ',' + ((y1 + y2) / 2) + ' '); const pline = addElem({ @@ -620,7 +629,7 @@ export default { updateConnectors(); } }, - IDsUpdated (input) { + IDsUpdated(input) { const remove = []; input.elems.forEach(function (elem) { if ('se:connector' in elem.attr) { @@ -634,9 +643,9 @@ export default { } } }); - return {remove}; + return { remove }; }, - toolButtonStateUpdate (opts) { + toolButtonStateUpdate(opts) { const button = document.getElementById('mode_connect'); if (opts.nostroke && button.pressed === true) { svgEditor.clickSelect(); diff --git a/src/svgcanvas/coords.js b/src/svgcanvas/coords.js index a10758b4..70af0a5e 100644 --- a/src/svgcanvas/coords.js +++ b/src/svgcanvas/coords.js @@ -11,7 +11,7 @@ import { import { transformPoint, transformListToTransform, matrixMultiply, transformBox } from './math.js'; -import {getTransformList} from './svgtransformlist.js'; +import { getTransformList } from './svgtransformlist.js'; const $ = jQuery; @@ -118,191 +118,192 @@ export const remapElement = function (selected, changes, m) { // now we have a set of changes and an applied reduced transform list // we apply the changes directly to the DOM switch (elName) { - case 'foreignObject': - case 'rect': - case 'image': { - // Allow images to be inverted (give them matrix when flipped) - if (elName === 'image' && (m.a < 0 || m.d < 0)) { - // Convert to matrix - const chlist = getTransformList(selected); - const mt = editorContext_.getSVGRoot().createSVGTransform(); - mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m)); - chlist.clear(); - chlist.appendItem(mt); - } else { - const pt1 = remap(changes.x, changes.y); - changes.width = scalew(changes.width); - changes.height = scaleh(changes.height); - changes.x = pt1.x + Math.min(0, changes.width); - changes.y = pt1.y + Math.min(0, changes.height); - changes.width = Math.abs(changes.width); - changes.height = Math.abs(changes.height); - } - finishUp(); - break; - } case 'ellipse': { - const c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - changes.rx = scalew(changes.rx); - changes.ry = scaleh(changes.ry); - changes.rx = Math.abs(changes.rx); - changes.ry = Math.abs(changes.ry); - finishUp(); - break; - } case 'circle': { - const c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - // take the minimum of the new selected box's dimensions for the new circle radius - const tbox = transformBox(box.x, box.y, box.width, box.height, m); - const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; - changes.r = Math.min(w / 2, h / 2); - - if (changes.r) { changes.r = Math.abs(changes.r); } - finishUp(); - break; - } case 'line': { - const pt1 = remap(changes.x1, changes.y1); - const pt2 = remap(changes.x2, changes.y2); - changes.x1 = pt1.x; - changes.y1 = pt1.y; - changes.x2 = pt2.x; - changes.y2 = pt2.y; - } // Fallthrough - case 'text': - case 'tspan': - case 'use': { - finishUp(); - break; - } case 'g': { - const gsvg = dataStorage.get(selected, 'gsvg'); - if (gsvg) { - assignAttributes(gsvg, changes, 1000, true); - } - break; - } case 'polyline': - case 'polygon': { - const len = changes.points.length; - for (let i = 0; i < len; ++i) { - const pt = changes.points[i]; - const {x, y} = remap(pt.x, pt.y); - changes.points[i].x = x; - changes.points[i].y = y; - } - - // const len = changes.points.length; - let pstr = ''; - for (let i = 0; i < len; ++i) { - const pt = changes.points[i]; - pstr += pt.x + ',' + pt.y + ' '; - } - selected.setAttribute('points', pstr); - break; - } case 'path': { - const segList = selected.pathSegList; - let len = segList.numberOfItems; - changes.d = []; - for (let i = 0; i < len; ++i) { - const seg = segList.getItem(i); - changes.d[i] = { - type: seg.pathSegType, - x: seg.x, - y: seg.y, - x1: seg.x1, - y1: seg.y1, - x2: seg.x2, - y2: seg.y2, - r1: seg.r1, - r2: seg.r2, - angle: seg.angle, - largeArcFlag: seg.largeArcFlag, - sweepFlag: seg.sweepFlag - }; - } - - len = changes.d.length; - const firstseg = changes.d[0], - currentpt = remap(firstseg.x, firstseg.y); - changes.d[0].x = currentpt.x; - changes.d[0].y = currentpt.y; - for (let i = 1; i < len; ++i) { - const seg = changes.d[i]; - const {type} = seg; - // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 - // if relative, we want to scalew, scaleh - if (type % 2 === 0) { // absolute - const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands - thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands - const pt = remap(thisx, thisy); - const pt1 = remap(seg.x1, seg.y1); - const pt2 = remap(seg.x2, seg.y2); - seg.x = pt.x; - seg.y = pt.y; - seg.x1 = pt1.x; - seg.y1 = pt1.y; - seg.x2 = pt2.x; - seg.y2 = pt2.y; - seg.r1 = scalew(seg.r1); - seg.r2 = scaleh(seg.r2); - } else { // relative - seg.x = scalew(seg.x); - seg.y = scaleh(seg.y); - seg.x1 = scalew(seg.x1); - seg.y1 = scaleh(seg.y1); - seg.x2 = scalew(seg.x2); - seg.y2 = scaleh(seg.y2); - seg.r1 = scalew(seg.r1); - seg.r2 = scaleh(seg.r2); + case 'foreignObject': + case 'rect': + case 'image': { + // Allow images to be inverted (give them matrix when flipped) + if (elName === 'image' && (m.a < 0 || m.d < 0)) { + // Convert to matrix + const chlist = getTransformList(selected); + const mt = editorContext_.getSVGRoot().createSVGTransform(); + mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m)); + chlist.clear(); + chlist.appendItem(mt); + } else { + const pt1 = remap(changes.x, changes.y); + changes.width = scalew(changes.width); + changes.height = scaleh(changes.height); + changes.x = pt1.x + Math.min(0, changes.width); + changes.y = pt1.y + Math.min(0, changes.height); + changes.width = Math.abs(changes.width); + changes.height = Math.abs(changes.height); } - } // for each segment + finishUp(); + break; + } case 'ellipse': { + const c = remap(changes.cx, changes.cy); + changes.cx = c.x; + changes.cy = c.y; + changes.rx = scalew(changes.rx); + changes.ry = scaleh(changes.ry); + changes.rx = Math.abs(changes.rx); + changes.ry = Math.abs(changes.ry); + finishUp(); + break; + } case 'circle': { + const c = remap(changes.cx, changes.cy); + changes.cx = c.x; + changes.cy = c.y; + // take the minimum of the new selected box's dimensions for the new circle radius + const tbox = transformBox(box.x, box.y, box.width, box.height, m); + const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; + changes.r = Math.min(w / 2, h / 2); - let dstr = ''; - len = changes.d.length; - for (let i = 0; i < len; ++i) { - const seg = changes.d[i]; - const {type} = seg; - dstr += pathMap[type]; - switch (type) { - case 13: // relative horizontal line (h) - case 12: // absolute horizontal line (H) - dstr += seg.x + ' '; - break; - case 15: // relative vertical line (v) - case 14: // absolute vertical line (V) - dstr += seg.y + ' '; - break; - case 3: // relative move (m) - case 5: // relative line (l) - case 19: // relative smooth quad (t) - case 2: // absolute move (M) - case 4: // absolute line (L) - case 18: // absolute smooth quad (T) - dstr += seg.x + ',' + seg.y + ' '; - break; - case 7: // relative cubic (c) - case 6: // absolute cubic (C) - dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' + - seg.x + ',' + seg.y + ' '; - break; - case 9: // relative quad (q) - case 8: // absolute quad (Q) - dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '; - break; - case 11: // relative elliptical arc (a) - case 10: // absolute elliptical arc (A) - dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) + - ' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; - break; - case 17: // relative smooth cubic (s) - case 16: // absolute smooth cubic (S) - dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '; - break; + if (changes.r) { changes.r = Math.abs(changes.r); } + finishUp(); + break; + } case 'line': { + const pt1 = remap(changes.x1, changes.y1); + const pt2 = remap(changes.x2, changes.y2); + changes.x1 = pt1.x; + changes.y1 = pt1.y; + changes.x2 = pt2.x; + changes.y2 = pt2.y; + } // Fallthrough + case 'text': + case 'tspan': + case 'use': { + finishUp(); + break; + } case 'g': { + const dataStorage = editorContext_.getDataStorage(); + const gsvg = dataStorage.get(selected, 'gsvg'); + if (gsvg) { + assignAttributes(gsvg, changes, 1000, true); + } + break; + } case 'polyline': + case 'polygon': { + const len = changes.points.length; + for (let i = 0; i < len; ++i) { + const pt = changes.points[i]; + const { x, y } = remap(pt.x, pt.y); + changes.points[i].x = x; + changes.points[i].y = y; } - } - selected.setAttribute('d', dstr); - break; - } + // const len = changes.points.length; + let pstr = ''; + for (let i = 0; i < len; ++i) { + const pt = changes.points[i]; + pstr += pt.x + ',' + pt.y + ' '; + } + selected.setAttribute('points', pstr); + break; + } case 'path': { + const segList = selected.pathSegList; + let len = segList.numberOfItems; + changes.d = []; + for (let i = 0; i < len; ++i) { + const seg = segList.getItem(i); + changes.d[i] = { + type: seg.pathSegType, + x: seg.x, + y: seg.y, + x1: seg.x1, + y1: seg.y1, + x2: seg.x2, + y2: seg.y2, + r1: seg.r1, + r2: seg.r2, + angle: seg.angle, + largeArcFlag: seg.largeArcFlag, + sweepFlag: seg.sweepFlag + }; + } + + len = changes.d.length; + const firstseg = changes.d[0], + currentpt = remap(firstseg.x, firstseg.y); + changes.d[0].x = currentpt.x; + changes.d[0].y = currentpt.y; + for (let i = 1; i < len; ++i) { + const seg = changes.d[i]; + const { type } = seg; + // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 + // if relative, we want to scalew, scaleh + if (type % 2 === 0) { // absolute + const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands + thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands + const pt = remap(thisx, thisy); + const pt1 = remap(seg.x1, seg.y1); + const pt2 = remap(seg.x2, seg.y2); + seg.x = pt.x; + seg.y = pt.y; + seg.x1 = pt1.x; + seg.y1 = pt1.y; + seg.x2 = pt2.x; + seg.y2 = pt2.y; + seg.r1 = scalew(seg.r1); + seg.r2 = scaleh(seg.r2); + } else { // relative + seg.x = scalew(seg.x); + seg.y = scaleh(seg.y); + seg.x1 = scalew(seg.x1); + seg.y1 = scaleh(seg.y1); + seg.x2 = scalew(seg.x2); + seg.y2 = scaleh(seg.y2); + seg.r1 = scalew(seg.r1); + seg.r2 = scaleh(seg.r2); + } + } // for each segment + + let dstr = ''; + len = changes.d.length; + for (let i = 0; i < len; ++i) { + const seg = changes.d[i]; + const { type } = seg; + dstr += pathMap[type]; + switch (type) { + case 13: // relative horizontal line (h) + case 12: // absolute horizontal line (H) + dstr += seg.x + ' '; + break; + case 15: // relative vertical line (v) + case 14: // absolute vertical line (V) + dstr += seg.y + ' '; + break; + case 3: // relative move (m) + case 5: // relative line (l) + case 19: // relative smooth quad (t) + case 2: // absolute move (M) + case 4: // absolute line (L) + case 18: // absolute smooth quad (T) + dstr += seg.x + ',' + seg.y + ' '; + break; + case 7: // relative cubic (c) + case 6: // absolute cubic (C) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' + + seg.x + ',' + seg.y + ' '; + break; + case 9: // relative quad (q) + case 8: // absolute quad (Q) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 11: // relative elliptical arc (a) + case 10: // absolute elliptical arc (A) + dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) + + ' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 17: // relative smooth cubic (s) + case 16: // absolute smooth cubic (S) + dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '; + break; + } + } + + selected.setAttribute('d', dstr); + break; + } } }; diff --git a/src/svgcanvas/draw.js b/src/svgcanvas/draw.js index aa24d206..dd2ea54e 100644 --- a/src/svgcanvas/draw.js +++ b/src/svgcanvas/draw.js @@ -989,6 +989,7 @@ export const mergeAllLayers = function (hrService) { */ export const leaveContext = function () { const len = disabledElems.length; + const dataStorage = canvas_.getDataStorage(); if (len) { for (let i = 0; i < len; i++) { const elem = disabledElems[i]; @@ -1015,6 +1016,7 @@ export const leaveContext = function () { * @returns {void} */ export const setContext = function (elem) { + const dataStorage = canvas_.getDataStorage(); leaveContext(); if (typeof elem === 'string') { elem = getElem(elem); diff --git a/src/svgcanvas/elem-get-set.js b/src/svgcanvas/elem-get-set.js index e6404d67..af6d69ae 100644 --- a/src/svgcanvas/elem-get-set.js +++ b/src/svgcanvas/elem-get-set.js @@ -5,11 +5,11 @@ */ /* globals jQuery */ -import {jGraduate} from '../editor/components/jgraduate/jQuery.jGraduate.js'; +import { jGraduate } from '../editor/components/jgraduate/jQuery.jGraduate.js'; import * as hstry from './history.js'; import jQueryPluginSVG from './jQuery.attr.js'; -import {NS} from '../common/namespaces.js'; +import { NS } from '../common/namespaces.js'; import { getVisibleElements, getStrokedBBoxDefaultVisible, findDefs, walkTree, isNullish, getHref, setHref, getElem @@ -17,7 +17,7 @@ import { import { convertToNum } from '../common/units.js'; -import {getParents} from '../editor/components/jgraduate/Util.js'; +import { getParents } from '../editor/components/jgraduate/Util.js'; const $ = jQueryPluginSVG(jQuery); @@ -61,11 +61,12 @@ export const getResolutionMethod = function () { */ export const getTitleMethod = function (elem) { const selectedElements = elemContext_.getSelectedElements(); + const dataStorage = elemContext_.getDataStorage(); elem = elem || selectedElements[0]; if (!elem) { return undefined; } - if(dataStorage.has(elem, 'gsvg')){ + if (dataStorage.has(elem, 'gsvg')) { elem = dataStorage.get(elem, 'gsvg'); - } else if(dataStorage.has(elem, 'symbol')) { + } else if (dataStorage.has(elem, 'symbol')) { elem = dataStorage.get(elem, 'symbol'); } const childs = elem.childNodes; @@ -86,8 +87,9 @@ export const getTitleMethod = function (elem) { */ export const setGroupTitleMethod = function (val) { const selectedElements = elemContext_.getSelectedElements(); + const dataStorage = elemContext_.getDataStorage(); let elem = selectedElements[0]; - if(dataStorage.has(elem, 'gsvg')){ + if (dataStorage.has(elem, 'gsvg')) { elem = dataStorage.get(elem, 'gsvg'); } @@ -104,7 +106,7 @@ export const setGroupTitleMethod = function (val) { } else if (ts.length) { // Change title contents title = ts[0]; - batchCmd.addSubCommand(new ChangeElementCommand(title, {'#text': title.textContent})); + batchCmd.addSubCommand(new ChangeElementCommand(title, { '#text': title.textContent })); title.textContent = val; } else { // Add title element @@ -149,7 +151,7 @@ export const setDocumentTitleMethod = function (newTitle) { // No title given, so element is not necessary docTitle.remove(); } - batchCmd.addSubCommand(new ChangeElementCommand(docTitle, {'#text': oldTitle})); + batchCmd.addSubCommand(new ChangeElementCommand(docTitle, { '#text': oldTitle })); elemContext_.addCommandToHistory(batchCmd); }; @@ -166,7 +168,7 @@ export const setDocumentTitleMethod = function (newTitle) { export const setResolutionMethod = function (x, y) { const currentZoom = elemContext_.getCurrentZoom(); const res = elemContext_.getCanvas().getResolution(); - const {w, h} = res; + const { w, h } = res; let batchCmd; if (x === 'fit') { @@ -206,10 +208,10 @@ export const setResolutionMethod = function (x, y) { this.contentW = x; this.contentH = y; - batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), {width: w, height: h})); + batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), { width: w, height: h })); elemContext_.getSVGContent().setAttribute('viewBox', [0, 0, x / currentZoom, y / currentZoom].join(' ')); - batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), {viewBox: ['0 0', w, h].join(' ')})); + batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), { viewBox: ['0 0', w, h].join(' ') })); elemContext_.addCommandToHistory(batchCmd); elemContext_.call('changed', [elemContext_.getSVGContent()]); @@ -254,7 +256,7 @@ export const setBBoxZoomMethod = function (val, editorW, editorH) { const hZoom = Math.round((editorH / bb.height) * 100 * spacer) / 100; const zoom = Math.min(wZoom, hZoom); elemContext_.getCanvas().setZoom(zoom); - return {zoom, bbox: bb}; + return { zoom, bbox: bb }; }; if (typeof val === 'object') { @@ -262,35 +264,35 @@ export const setBBoxZoomMethod = function (val, editorW, editorH) { if (bb.width === 0 || bb.height === 0) { const newzoom = bb.zoom ? bb.zoom : currentZoom * bb.factor; elemContext_.getCanvas().setZoom(newzoom); - return {zoom: currentZoom, bbox: bb}; + return { zoom: currentZoom, bbox: bb }; } return calcZoom(bb); } switch (val) { - case 'selection': { - if (!selectedElements[0]) { return undefined; } - const selectedElems = $.map(selectedElements, function (n) { - if (n) { - return n; - } + case 'selection': { + if (!selectedElements[0]) { return undefined; } + const selectedElems = $.map(selectedElements, function (n) { + if (n) { + return n; + } + return undefined; + }); + bb = getStrokedBBoxDefaultVisible(selectedElems); + break; + } case 'canvas': { + const res = elemContext_.getCanvas().getResolution(); + spacer = 0.95; + bb = { width: res.w, height: res.h, x: 0, y: 0 }; + break; + } case 'content': + bb = getStrokedBBoxDefaultVisible(); + break; + case 'layer': + bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer())); + break; + default: return undefined; - }); - bb = getStrokedBBoxDefaultVisible(selectedElems); - break; - } case 'canvas': { - const res = elemContext_.getCanvas().getResolution(); - spacer = 0.95; - bb = {width: res.w, height: res.h, x: 0, y: 0}; - break; - } case 'content': - bb = getStrokedBBoxDefaultVisible(); - break; - case 'layer': - bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer())); - break; - default: - return undefined; } return calcZoom(bb); }; @@ -327,14 +329,14 @@ export const setZoomMethod = function (zoomLevel) { export const setColorMethod = function (type, val, preventUndo) { const selectedElements = elemContext_.getSelectedElements(); elemContext_.setCurShape(type, val); - elemContext_.setCurProperties(type + '_paint', {type: 'solidColor'}); + elemContext_.setCurProperties(type + '_paint', { type: 'solidColor' }); const elems = []; /** * * @param {Element} e * @returns {void} */ - function addNonG (e) { + function addNonG(e) { if (e.nodeName !== 'g') { elems.push(e); } @@ -406,9 +408,9 @@ export const findDuplicateGradient = function (grad) { const og = existingGrads[i]; if (grad.tagName === 'linearGradient') { if (grad.getAttribute('x1') !== og.getAttribute('x1') || - grad.getAttribute('y1') !== og.getAttribute('y1') || - grad.getAttribute('x2') !== og.getAttribute('x2') || - grad.getAttribute('y2') !== og.getAttribute('y2') + grad.getAttribute('y1') !== og.getAttribute('y1') || + grad.getAttribute('x2') !== og.getAttribute('x2') || + grad.getAttribute('y2') !== og.getAttribute('y2') ) { continue; } @@ -429,7 +431,7 @@ export const findDuplicateGradient = function (grad) { }; let diff = false; - radAttrs.forEach(function(attr, j){ + radAttrs.forEach(function (attr, j) { if (gradAttrs[attr] !== ogAttrs[attr]) { diff = true; } }); @@ -450,8 +452,8 @@ export const findDuplicateGradient = function (grad) { const ostop = ostops[j]; if (stop.getAttribute('offset') !== ostop.getAttribute('offset') || - stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') || - stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) { + stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') || + stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) { break; } } @@ -479,14 +481,14 @@ export const setPaintMethod = function (type, paint) { // now set the current paint object elemContext_.setCurProperties(type + '_paint', p); switch (p.type) { - case 'solidColor': - this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none'); - break; - case 'linearGradient': - case 'radialGradient': - elemContext_.setCanvas(type + 'Grad', p[p.type]); - elemContext_.getCanvas().setGradient(type); - break; + case 'solidColor': + this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none'); + break; + case 'linearGradient': + case 'radialGradient': + elemContext_.setCanvas(type + 'Grad', p[p.type]); + elemContext_.getCanvas().setGradient(type); + break; } }; /** @@ -511,7 +513,7 @@ export const setStrokeWidthMethod = function (val) { * @param {Element} e * @returns {void} */ - function addNonG (e) { + function addNonG(e) { if (e.nodeName !== 'g') { elems.push(e); } @@ -572,7 +574,7 @@ export const getBoldMethod = function () { // should only have one element selected const selected = selectedElements[0]; if (!isNullish(selected) && selected.tagName === 'text' && -isNullish(selectedElements[1])) { + isNullish(selectedElements[1])) { return (selected.getAttribute('font-weight') === 'bold'); } return false; @@ -588,7 +590,7 @@ export const setBoldMethod = function (b) { const selectedElements = elemContext_.getSelectedElements(); const selected = selectedElements[0]; if (!isNullish(selected) && selected.tagName === 'text' && -isNullish(selectedElements[1])) { + isNullish(selectedElements[1])) { elemContext_.getCanvas().changeSelectedAttribute('font-weight', b ? 'bold' : 'normal'); } if (!selectedElements[0].textContent) { @@ -605,7 +607,7 @@ export const getItalicMethod = function () { const selectedElements = elemContext_.getSelectedElements(); const selected = selectedElements[0]; if (!isNullish(selected) && selected.tagName === 'text' && -isNullish(selectedElements[1])) { + isNullish(selectedElements[1])) { return (selected.getAttribute('font-style') === 'italic'); } return false; @@ -621,7 +623,7 @@ export const setItalicMethod = function (i) { const selectedElements = elemContext_.getSelectedElements(); const selected = selectedElements[0]; if (!isNullish(selected) && selected.tagName === 'text' && -isNullish(selectedElements[1])) { + isNullish(selectedElements[1])) { elemContext_.getCanvas().changeSelectedAttribute('font-style', i ? 'italic' : 'normal'); } if (!selectedElements[0].textContent) { @@ -638,7 +640,7 @@ export const setTextAnchorMethod = function (value) { const selectedElements = elemContext_.getSelectedElements(); const selected = selectedElements[0]; if (!isNullish(selected) && selected.tagName === 'text' && - isNullish(selectedElements[1])) { + isNullish(selectedElements[1])) { elemContext_.getCanvas().changeSelectedAttribute('text-anchor', value); } if (!selectedElements[0].textContent) { @@ -747,7 +749,7 @@ export const setImageURLMethod = function (val) { const elem = selectedElements[0]; if (!elem) { return; } - const attrs = { + const attrs = { width: elem.getAttribute('width'), height: elem.getAttribute('height'), }; @@ -767,8 +769,8 @@ export const setImageURLMethod = function (val) { '#href': curHref })); const img = new Image(); - img.onload = function() { - const changes = { + img.onload = function () { + const changes = { width: elem.getAttribute('width'), height: elem.getAttribute('height'), }; @@ -834,7 +836,7 @@ export const setRectRadiusMethod = function (val) { if (r !== String(val)) { selected.setAttribute('rx', val); selected.setAttribute('ry', val); - elemContext_.addCommandToHistory(new ChangeElementCommand(selected, {rx: r, ry: r}, 'Radius')); + elemContext_.addCommandToHistory(new ChangeElementCommand(selected, { rx: r, ry: r }, 'Radius')); elemContext_.call('changed', [selected]); } } @@ -901,9 +903,9 @@ export const setBackgroundMethod = function (color, url) { const div = document.createElement('div'); elemContext_.getCanvas().assignAttributes(div, { style: 'pointer-events:none;width:100%;height:100%;' + - 'background-image:url(data:image/gif;base64,' + - 'R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+' + - 'gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7);' + 'background-image:url(data:image/gif;base64,' + + 'R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+' + + 'gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7);' }); bgPattern.append(div); bg.append(bgPattern); diff --git a/src/svgcanvas/event.js b/src/svgcanvas/event.js index e547838e..5e6b8e4e 100644 --- a/src/svgcanvas/event.js +++ b/src/svgcanvas/event.js @@ -25,7 +25,7 @@ import { import * as draw from './draw.js'; import * as pathModule from './path.js'; import * as hstry from './history.js'; -import {findPos} from '../editor/components/jgraduate/Util.js'; +import { findPos } from '../editor/components/jgraduate/Util.js'; const { InsertElementCommand @@ -44,11 +44,11 @@ export const init = function (eventContext) { }; export const getBsplinePoint = function (t) { - const spline = {x: 0, y: 0}, - p0 = {x: eventContext_.getControllPoint2('x'), y: eventContext_.getControllPoint2('y')}, - p1 = {x: eventContext_.getControllPoint1('x'), y: eventContext_.getControllPoint1('y')}, - p2 = {x: eventContext_.getStart('x'), y: eventContext_.getStart('y')}, - p3 = {x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y')}, + const spline = { x: 0, y: 0 }, + p0 = { x: eventContext_.getControllPoint2('x'), y: eventContext_.getControllPoint2('y') }, + p1 = { x: eventContext_.getControllPoint1('x'), y: eventContext_.getControllPoint1('y') }, + p2 = { x: eventContext_.getStart('x'), y: eventContext_.getStart('y') }, + p3 = { x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y') }, S = 1.0 / 6.0, t2 = t * t, t3 = t2 * t; @@ -62,15 +62,15 @@ export const getBsplinePoint = function (t) { spline.x = S * ( (p0.x * m[0][0] + p1.x * m[0][1] + p2.x * m[0][2] + p3.x * m[0][3]) * t3 + -(p0.x * m[1][0] + p1.x * m[1][1] + p2.x * m[1][2] + p3.x * m[1][3]) * t2 + -(p0.x * m[2][0] + p1.x * m[2][1] + p2.x * m[2][2] + p3.x * m[2][3]) * t + -(p0.x * m[3][0] + p1.x * m[3][1] + p2.x * m[3][2] + p3.x * m[3][3]) + (p0.x * m[1][0] + p1.x * m[1][1] + p2.x * m[1][2] + p3.x * m[1][3]) * t2 + + (p0.x * m[2][0] + p1.x * m[2][1] + p2.x * m[2][2] + p3.x * m[2][3]) * t + + (p0.x * m[3][0] + p1.x * m[3][1] + p2.x * m[3][2] + p3.x * m[3][3]) ); spline.y = S * ( (p0.y * m[0][0] + p1.y * m[0][1] + p2.y * m[0][2] + p3.y * m[0][3]) * t3 + -(p0.y * m[1][0] + p1.y * m[1][1] + p2.y * m[1][2] + p3.y * m[1][3]) * t2 + -(p0.y * m[2][0] + p1.y * m[2][1] + p2.y * m[2][2] + p3.y * m[2][3]) * t + -(p0.y * m[3][0] + p1.y * m[3][1] + p2.y * m[3][2] + p3.y * m[3][3]) + (p0.y * m[1][0] + p1.y * m[1][1] + p2.y * m[1][2] + p3.y * m[1][3]) * t2 + + (p0.y * m[2][0] + p1.y * m[2][1] + p2.y * m[2][2] + p3.y * m[2][3]) * t + + (p0.y * m[3][0] + p1.y * m[3][1] + p2.y * m[3][2] + p3.y * m[3][3]) ); return { @@ -114,348 +114,185 @@ export const mouseMoveEvent = function (evt) { evt.preventDefault(); let tlist; switch (eventContext_.getCurrentMode()) { - case 'select': { - // we temporarily use a translate on the element(s) being dragged - // this transform is removed upon mousing up and the element is - // relocated to the new location - if (selectedElements[0] !== null) { - dx = x - eventContext_.getStartX(); - dy = y - eventContext_.getStartY(); + case 'select': { + // we temporarily use a translate on the element(s) being dragged + // this transform is removed upon mousing up and the element is + // relocated to the new location + if (selectedElements[0] !== null) { + dx = x - eventContext_.getStartX(); + dy = y - eventContext_.getStartY(); + if (eventContext_.getCurConfig().gridSnapping) { + dx = snapToGrid(dx); + dy = snapToGrid(dy); + } + + if (dx !== 0 || dy !== 0) { + len = selectedElements.length; + for (i = 0; i < len; ++i) { + selected = selectedElements[i]; + if (isNullish(selected)) { break; } + // if (i === 0) { + // const box = utilsGetBBox(selected); + // selectedBBoxes[i].x = box.x + dx; + // selectedBBoxes[i].y = box.y + dy; + // } + + // update the dummy transform in our transform list + // to be a translate + const xform = eventContext_.getSVGRoot().createSVGTransform(); + tlist = getTransformList(selected); + // Note that if Webkit and there's no ID for this + // element, the dummy transform may have gotten lost. + // This results in unexpected behaviour + + xform.setTranslate(dx, dy); + if (tlist.numberOfItems) { + tlist.replaceItem(xform, 0); + } else { + tlist.appendItem(xform); + } + + // update our internal bbox that we're tracking while dragging + eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); + } + + eventContext_.getCanvas().call('transition', selectedElements); + } + } + break; + } case 'multiselect': { + realX *= currentZoom; + realY *= currentZoom; + assignAttributes(eventContext_.getRubberBox(), { + x: Math.min(eventContext_.getRStartX(), realX), + y: Math.min(eventContext_.getRStartY(), realY), + width: Math.abs(realX - eventContext_.getRStartX()), + height: Math.abs(realY - eventContext_.getRStartY()) + }, 100); + + // for each selected: + // - if newList contains selected, do nothing + // - if newList doesn't contain selected, remove it from selected + // - for any newList that was not in selectedElements, add it to selected + const elemsToRemove = selectedElements.slice(), elemsToAdd = [], + newList = eventContext_.getIntersectionList(); + + // For every element in the intersection, add if not present in selectedElements. + len = newList.length; + for (i = 0; i < len; ++i) { + const intElem = newList[i]; + // Found an element that was not selected before, so we should add it. + if (!selectedElements.includes(intElem)) { + elemsToAdd.push(intElem); + } + // Found an element that was already selected, so we shouldn't remove it. + const foundInd = elemsToRemove.indexOf(intElem); + if (foundInd !== -1) { + elemsToRemove.splice(foundInd, 1); + } + } + + if (elemsToRemove.length > 0) { + eventContext_.getCanvas().removeFromSelection(elemsToRemove); + } + + if (elemsToAdd.length > 0) { + eventContext_.getCanvas().addToSelection(elemsToAdd); + } + + break; + } case 'resize': { + // we track the resize bounding box and translate/scale the selected element + // while the mouse is down, when mouse goes up, we use this to recalculate + // the shape's coordinates + tlist = getTransformList(selected); + const hasMatrix = hasMatrixTransform(tlist); + box = hasMatrix ? eventContext_.getInitBbox() : utilsGetBBox(selected); + let left = box.x, + top = box.y, + { width, height } = box; + dx = (x - eventContext_.getStartX()); + dy = (y - eventContext_.getStartY()); + if (eventContext_.getCurConfig().gridSnapping) { dx = snapToGrid(dx); dy = snapToGrid(dy); + height = snapToGrid(height); + width = snapToGrid(width); } - if (dx !== 0 || dy !== 0) { - len = selectedElements.length; - for (i = 0; i < len; ++i) { - selected = selectedElements[i]; - if (isNullish(selected)) { break; } - // if (i === 0) { - // const box = utilsGetBBox(selected); - // selectedBBoxes[i].x = box.x + dx; - // selectedBBoxes[i].y = box.y + dy; - // } - - // update the dummy transform in our transform list - // to be a translate - const xform = eventContext_.getSVGRoot().createSVGTransform(); - tlist = getTransformList(selected); - // Note that if Webkit and there's no ID for this - // element, the dummy transform may have gotten lost. - // This results in unexpected behaviour - - xform.setTranslate(dx, dy); - if (tlist.numberOfItems) { - tlist.replaceItem(xform, 0); - } else { - tlist.appendItem(xform); - } - - // update our internal bbox that we're tracking while dragging - eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); - } - - eventContext_.getCanvas().call('transition', selectedElements); + // if rotated, adjust the dx,dy values + angle = getRotationAngle(selected); + if (angle) { + const r = Math.sqrt(dx * dx + dy * dy), + theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0; + dx = r * Math.cos(theta); + dy = r * Math.sin(theta); } - } - break; - } case 'multiselect': { - realX *= currentZoom; - realY *= currentZoom; - assignAttributes(eventContext_.getRubberBox(), { - x: Math.min(eventContext_.getRStartX(), realX), - y: Math.min(eventContext_.getRStartY(), realY), - width: Math.abs(realX - eventContext_.getRStartX()), - height: Math.abs(realY - eventContext_.getRStartY()) - }, 100); - // for each selected: - // - if newList contains selected, do nothing - // - if newList doesn't contain selected, remove it from selected - // - for any newList that was not in selectedElements, add it to selected - const elemsToRemove = selectedElements.slice(), elemsToAdd = [], - newList = eventContext_.getIntersectionList(); - - // For every element in the intersection, add if not present in selectedElements. - len = newList.length; - for (i = 0; i < len; ++i) { - const intElem = newList[i]; - // Found an element that was not selected before, so we should add it. - if (!selectedElements.includes(intElem)) { - elemsToAdd.push(intElem); + // if not stretching in y direction, set dy to 0 + // if not stretching in x direction, set dx to 0 + if (!eventContext_.getCurrentResizeMode().includes('n') && !eventContext_.getCurrentResizeMode().includes('s')) { + dy = 0; } - // Found an element that was already selected, so we shouldn't remove it. - const foundInd = elemsToRemove.indexOf(intElem); - if (foundInd !== -1) { - elemsToRemove.splice(foundInd, 1); + if (!eventContext_.getCurrentResizeMode().includes('e') && !eventContext_.getCurrentResizeMode().includes('w')) { + dx = 0; } - } - if (elemsToRemove.length > 0) { - eventContext_.getCanvas().removeFromSelection(elemsToRemove); - } - - if (elemsToAdd.length > 0) { - eventContext_.getCanvas().addToSelection(elemsToAdd); - } - - break; - } case 'resize': { - // we track the resize bounding box and translate/scale the selected element - // while the mouse is down, when mouse goes up, we use this to recalculate - // the shape's coordinates - tlist = getTransformList(selected); - const hasMatrix = hasMatrixTransform(tlist); - box = hasMatrix ? eventContext_.getInitBbox() : utilsGetBBox(selected); - let left = box.x, - top = box.y, - {width, height} = box; - dx = (x - eventContext_.getStartX()); - dy = (y - eventContext_.getStartY()); - - if (eventContext_.getCurConfig().gridSnapping) { - dx = snapToGrid(dx); - dy = snapToGrid(dy); - height = snapToGrid(height); - width = snapToGrid(width); - } - - // if rotated, adjust the dx,dy values - angle = getRotationAngle(selected); - if (angle) { - const r = Math.sqrt(dx * dx + dy * dy), - theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0; - dx = r * Math.cos(theta); - dy = r * Math.sin(theta); - } - - // if not stretching in y direction, set dy to 0 - // if not stretching in x direction, set dx to 0 - if (!eventContext_.getCurrentResizeMode().includes('n') && !eventContext_.getCurrentResizeMode().includes('s')) { - dy = 0; - } - if (!eventContext_.getCurrentResizeMode().includes('e') && !eventContext_.getCurrentResizeMode().includes('w')) { - dx = 0; - } - - let // ts = null, - tx = 0, ty = 0, - sy = height ? (height + dy) / height : 1, - sx = width ? (width + dx) / width : 1; - // if we are dragging on the north side, then adjust the scale factor and ty - if (eventContext_.getCurrentResizeMode().includes('n')) { - sy = height ? (height - dy) / height : 1; - ty = height; - } - - // if we dragging on the east side, then adjust the scale factor and tx - if (eventContext_.getCurrentResizeMode().includes('w')) { - sx = width ? (width - dx) / width : 1; - tx = width; - } - - // update the transform list with translate,scale,translate - const translateOrigin = eventContext_.getSVGRoot().createSVGTransform(), - scale = eventContext_.getSVGRoot().createSVGTransform(), - translateBack = eventContext_.getSVGRoot().createSVGTransform(); - - if (eventContext_.getCurConfig().gridSnapping) { - left = snapToGrid(left); - tx = snapToGrid(tx); - top = snapToGrid(top); - ty = snapToGrid(ty); - } - - translateOrigin.setTranslate(-(left + tx), -(top + ty)); - if (evt.shiftKey) { - if (sx === 1) { - sx = sy; - } else { sy = sx; } - } - scale.setScale(sx, sy); - - translateBack.setTranslate(left + tx, top + ty); - if (hasMatrix) { - const diff = angle ? 1 : 0; - tlist.replaceItem(translateOrigin, 2 + diff); - tlist.replaceItem(scale, 1 + diff); - tlist.replaceItem(translateBack, Number(diff)); - } else { - const N = tlist.numberOfItems; - tlist.replaceItem(translateBack, N - 3); - tlist.replaceItem(scale, N - 2); - tlist.replaceItem(translateOrigin, N - 1); - } - - eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); - eventContext_.getCanvas().call('transition', selectedElements); - - break; - } case 'zoom': { - realX *= currentZoom; - realY *= currentZoom; - assignAttributes(eventContext_.getRubberBox(), { - x: Math.min(eventContext_.getRStartX() * currentZoom, realX), - y: Math.min(eventContext_.getRStartY() * currentZoom, realY), - width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), - height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) - }, 100); - break; - } case 'text': { - assignAttributes(shape, { - x, - y - }, 1000); - break; - } case 'line': { - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - y = snapToGrid(y); - } - - let x2 = x; - let y2 = y; - - if (evt.shiftKey) { - xya = snapToAngle(eventContext_.getStartX(), eventContext_.getStartY(), x2, y2); - x2 = xya.x; - y2 = xya.y; - } - - shape.setAttribute('x2', x2); - shape.setAttribute('y2', y2); - break; - } case 'foreignObject': - // fall through - case 'square': - // fall through - case 'rect': - // fall through - case 'image': { - const square = (eventContext_.getCurrentMode() === 'square') || evt.shiftKey; - let - w = Math.abs(x - eventContext_.getStartX()), - h = Math.abs(y - eventContext_.getStartY()); - let newX, newY; - if (square) { - w = h = Math.max(w, h); - newX = eventContext_.getStartX() < x ? eventContext_.getStartX() : eventContext_.getStartX() - w; - newY = eventContext_.getStartY() < y ? eventContext_.getStartY() : eventContext_.getStartY() - h; - } else { - newX = Math.min(eventContext_.getStartX(), x); - newY = Math.min(eventContext_.getStartY(), y); - } - - if (eventContext_.getCurConfig().gridSnapping) { - w = snapToGrid(w); - h = snapToGrid(h); - newX = snapToGrid(newX); - newY = snapToGrid(newY); - } - - assignAttributes(shape, { - width: w, - height: h, - x: newX, - y: newY - }, 1000); - - break; - } case 'circle': { - cx = shape.getAttribute('cx'); - cy = shape.getAttribute('cy'); - let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); - if (eventContext_.getCurConfig().gridSnapping) { - rad = snapToGrid(rad); - } - shape.setAttribute('r', rad); - break; - } case 'ellipse': { - cx = shape.getAttribute('cx'); - cy = shape.getAttribute('cy'); - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - cx = snapToGrid(cx); - y = snapToGrid(y); - cy = snapToGrid(cy); - } - shape.setAttribute('rx', Math.abs(x - cx)); - const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy)); - shape.setAttribute('ry', ry); - break; - } - case 'fhellipse': - case 'fhrect': { - eventContext_.setFreehand('minx', Math.min(realX, eventContext_.getFreehand('minx'))); - eventContext_.setFreehand('maxx', Math.max(realX, eventContext_.getFreehand('maxx'))); - eventContext_.setFreehand('miny', Math.min(realY, eventContext_.getFreehand('miny'))); - eventContext_.setFreehand('maxy', Math.max(realY, eventContext_.getFreehand('maxy'))); - } - // Fallthrough - case 'fhpath': { - // dAttr += + realX + ',' + realY + ' '; - // shape.setAttribute('points', dAttr); - eventContext_.setEnd('x', realX); - eventContext_.setEnd('y', realY); - if (eventContext_.getControllPoint2('x') && eventContext_.getControllPoint2('y')) { - for (i = 0; i < eventContext_.getStepCount() - 1; i++) { - eventContext_.setParameter(i / eventContext_.getStepCount()); - eventContext_.setNextParameter((i + 1) / eventContext_.getStepCount()); - eventContext_.setbSpline(getBsplinePoint(eventContext_.getNextParameter())); - eventContext_.setNextPos({x: eventContext_.getbSpline('x'), y: eventContext_.getbSpline('y')}); - eventContext_.setbSpline(getBsplinePoint(eventContext_.getParameter())); - eventContext_.setSumDistance( - eventContext_.getSumDistance() + Math.sqrt((eventContext_.getNextPos('x') - - eventContext_.getbSpline('x')) * (eventContext_.getNextPos('x') - - eventContext_.getbSpline('x')) + (eventContext_.getNextPos('y') - - eventContext_.getbSpline('y')) * (eventContext_.getNextPos('y') - eventContext_.getbSpline('y'))) - ); - if (eventContext_.getSumDistance() > eventContext_.getThreSholdDist()) { - eventContext_.setSumDistance(eventContext_.getSumDistance() - eventContext_.getThreSholdDist()); - - // Faster than completely re-writing the points attribute. - const point = eventContext_.getSVGContent().createSVGPoint(); - point.x = eventContext_.getbSpline('x'); - point.y = eventContext_.getbSpline('y'); - shape.points.appendItem(point); - } + let // ts = null, + tx = 0, ty = 0, + sy = height ? (height + dy) / height : 1, + sx = width ? (width + dx) / width : 1; + // if we are dragging on the north side, then adjust the scale factor and ty + if (eventContext_.getCurrentResizeMode().includes('n')) { + sy = height ? (height - dy) / height : 1; + ty = height; } - } - eventContext_.setControllPoint2('x', eventContext_.getControllPoint1('x')); - eventContext_.setControllPoint2('y', eventContext_.getControllPoint1('y')); - eventContext_.setControllPoint1('x', eventContext_.getStart('x')); - eventContext_.setControllPoint1('y', eventContext_.getStart('y')); - eventContext_.setStart({x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y')}); - break; - // update path stretch line coordinates - } case 'path': - // fall through - case 'pathedit': { - x *= currentZoom; - y *= currentZoom; - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - y = snapToGrid(y); - eventContext_.setStartX(snapToGrid(eventContext_.getStartX())); - eventContext_.setStartY(snapToGrid(eventContext_.getStartY())); - } - if (evt.shiftKey) { - const {path} = pathModule; - let x1, y1; - if (path) { - x1 = path.dragging ? path.dragging[0] : eventContext_.getStartX(); - y1 = path.dragging ? path.dragging[1] : eventContext_.getStartY(); + // if we dragging on the east side, then adjust the scale factor and tx + if (eventContext_.getCurrentResizeMode().includes('w')) { + sx = width ? (width - dx) / width : 1; + tx = width; + } + + // update the transform list with translate,scale,translate + const translateOrigin = eventContext_.getSVGRoot().createSVGTransform(), + scale = eventContext_.getSVGRoot().createSVGTransform(), + translateBack = eventContext_.getSVGRoot().createSVGTransform(); + + if (eventContext_.getCurConfig().gridSnapping) { + left = snapToGrid(left); + tx = snapToGrid(tx); + top = snapToGrid(top); + ty = snapToGrid(ty); + } + + translateOrigin.setTranslate(-(left + tx), -(top + ty)); + if (evt.shiftKey) { + if (sx === 1) { + sx = sy; + } else { sy = sx; } + } + scale.setScale(sx, sy); + + translateBack.setTranslate(left + tx, top + ty); + if (hasMatrix) { + const diff = angle ? 1 : 0; + tlist.replaceItem(translateOrigin, 2 + diff); + tlist.replaceItem(scale, 1 + diff); + tlist.replaceItem(translateBack, Number(diff)); } else { - x1 = eventContext_.getStartX(); - y1 = eventContext_.getStartY(); + const N = tlist.numberOfItems; + tlist.replaceItem(translateBack, N - 3); + tlist.replaceItem(scale, N - 2); + tlist.replaceItem(translateOrigin, N - 1); } - xya = snapToAngle(x1, y1, x, y); - ({x, y} = xya); - } - if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { + eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); + eventContext_.getCanvas().call('transition', selectedElements); + + break; + } case 'zoom': { realX *= currentZoom; realY *= currentZoom; assignAttributes(eventContext_.getRubberBox(), { @@ -464,47 +301,210 @@ export const mouseMoveEvent = function (evt) { width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) }, 100); + break; + } case 'text': { + assignAttributes(shape, { + x, + y + }, 1000); + break; + } case 'line': { + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + y = snapToGrid(y); + } + + let x2 = x; + let y2 = y; + + if (evt.shiftKey) { + xya = snapToAngle(eventContext_.getStartX(), eventContext_.getStartY(), x2, y2); + x2 = xya.x; + y2 = xya.y; + } + + shape.setAttribute('x2', x2); + shape.setAttribute('y2', y2); + break; + } case 'foreignObject': + // fall through + case 'square': + // fall through + case 'rect': + // fall through + case 'image': { + const square = (eventContext_.getCurrentMode() === 'square') || evt.shiftKey; + let + w = Math.abs(x - eventContext_.getStartX()), + h = Math.abs(y - eventContext_.getStartY()); + let newX, newY; + if (square) { + w = h = Math.max(w, h); + newX = eventContext_.getStartX() < x ? eventContext_.getStartX() : eventContext_.getStartX() - w; + newY = eventContext_.getStartY() < y ? eventContext_.getStartY() : eventContext_.getStartY() - h; + } else { + newX = Math.min(eventContext_.getStartX(), x); + newY = Math.min(eventContext_.getStartY(), y); + } + + if (eventContext_.getCurConfig().gridSnapping) { + w = snapToGrid(w); + h = snapToGrid(h); + newX = snapToGrid(newX); + newY = snapToGrid(newY); + } + + assignAttributes(shape, { + width: w, + height: h, + x: newX, + y: newY + }, 1000); + + break; + } case 'circle': { + cx = shape.getAttribute('cx'); + cy = shape.getAttribute('cy'); + let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); + if (eventContext_.getCurConfig().gridSnapping) { + rad = snapToGrid(rad); + } + shape.setAttribute('r', rad); + break; + } case 'ellipse': { + cx = shape.getAttribute('cx'); + cy = shape.getAttribute('cy'); + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + cx = snapToGrid(cx); + y = snapToGrid(y); + cy = snapToGrid(cy); + } + shape.setAttribute('rx', Math.abs(x - cx)); + const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy)); + shape.setAttribute('ry', ry); + break; } - eventContext_.getCanvas().pathActions.mouseMove(x, y); - - break; - } case 'textedit': { - x *= currentZoom; - y *= currentZoom; - // if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { - // assignAttributes(eventContext_.getRubberBox(), { - // x: Math.min(eventContext_.getStartX(), x), - // y: Math.min(eventContext_.getStartY(), y), - // width: Math.abs(x - eventContext_.getStartX()), - // height: Math.abs(y - eventContext_.getStartY()) - // }, 100); - // } - - eventContext_.getCanvas().textActions.mouseMove(mouseX, mouseY); - - break; - } case 'rotate': { - box = utilsGetBBox(selected); - cx = box.x + box.width / 2; - cy = box.y + box.height / 2; - const m = getMatrix(selected), - center = transformPoint(cx, cy, m); - cx = center.x; - cy = center.y; - angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360; - if (eventContext_.getCurConfig().gridSnapping) { - angle = snapToGrid(angle); - } - if (evt.shiftKey) { // restrict rotations to nice angles (WRS) - const snap = 45; - angle = Math.round(angle / snap) * snap; + case 'fhellipse': + case 'fhrect': { + eventContext_.setFreehand('minx', Math.min(realX, eventContext_.getFreehand('minx'))); + eventContext_.setFreehand('maxx', Math.max(realX, eventContext_.getFreehand('maxx'))); + eventContext_.setFreehand('miny', Math.min(realY, eventContext_.getFreehand('miny'))); + eventContext_.setFreehand('maxy', Math.max(realY, eventContext_.getFreehand('maxy'))); } + // Fallthrough + case 'fhpath': { + // dAttr += + realX + ',' + realY + ' '; + // shape.setAttribute('points', dAttr); + eventContext_.setEnd('x', realX); + eventContext_.setEnd('y', realY); + if (eventContext_.getControllPoint2('x') && eventContext_.getControllPoint2('y')) { + for (i = 0; i < eventContext_.getStepCount() - 1; i++) { + eventContext_.setParameter(i / eventContext_.getStepCount()); + eventContext_.setNextParameter((i + 1) / eventContext_.getStepCount()); + eventContext_.setbSpline(getBsplinePoint(eventContext_.getNextParameter())); + eventContext_.setNextPos({ x: eventContext_.getbSpline('x'), y: eventContext_.getbSpline('y') }); + eventContext_.setbSpline(getBsplinePoint(eventContext_.getParameter())); + eventContext_.setSumDistance( + eventContext_.getSumDistance() + Math.sqrt((eventContext_.getNextPos('x') - + eventContext_.getbSpline('x')) * (eventContext_.getNextPos('x') - + eventContext_.getbSpline('x')) + (eventContext_.getNextPos('y') - + eventContext_.getbSpline('y')) * (eventContext_.getNextPos('y') - eventContext_.getbSpline('y'))) + ); + if (eventContext_.getSumDistance() > eventContext_.getThreSholdDist()) { + eventContext_.setSumDistance(eventContext_.getSumDistance() - eventContext_.getThreSholdDist()); - eventContext_.getCanvas().setRotationAngle(angle < -180 ? (360 + angle) : angle, true); - eventContext_.getCanvas().call('transition', selectedElements); - break; - } default: - break; + // Faster than completely re-writing the points attribute. + const point = eventContext_.getSVGContent().createSVGPoint(); + point.x = eventContext_.getbSpline('x'); + point.y = eventContext_.getbSpline('y'); + shape.points.appendItem(point); + } + } + } + eventContext_.setControllPoint2('x', eventContext_.getControllPoint1('x')); + eventContext_.setControllPoint2('y', eventContext_.getControllPoint1('y')); + eventContext_.setControllPoint1('x', eventContext_.getStart('x')); + eventContext_.setControllPoint1('y', eventContext_.getStart('y')); + eventContext_.setStart({ x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y') }); + break; + // update path stretch line coordinates + } case 'path': + // fall through + case 'pathedit': { + x *= currentZoom; + y *= currentZoom; + + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + y = snapToGrid(y); + eventContext_.setStartX(snapToGrid(eventContext_.getStartX())); + eventContext_.setStartY(snapToGrid(eventContext_.getStartY())); + } + if (evt.shiftKey) { + const { path } = pathModule; + let x1, y1; + if (path) { + x1 = path.dragging ? path.dragging[0] : eventContext_.getStartX(); + y1 = path.dragging ? path.dragging[1] : eventContext_.getStartY(); + } else { + x1 = eventContext_.getStartX(); + y1 = eventContext_.getStartY(); + } + xya = snapToAngle(x1, y1, x, y); + ({ x, y } = xya); + } + + if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { + realX *= currentZoom; + realY *= currentZoom; + assignAttributes(eventContext_.getRubberBox(), { + x: Math.min(eventContext_.getRStartX() * currentZoom, realX), + y: Math.min(eventContext_.getRStartY() * currentZoom, realY), + width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), + height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) + }, 100); + } + eventContext_.getCanvas().pathActions.mouseMove(x, y); + + break; + } case 'textedit': { + x *= currentZoom; + y *= currentZoom; + // if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { + // assignAttributes(eventContext_.getRubberBox(), { + // x: Math.min(eventContext_.getStartX(), x), + // y: Math.min(eventContext_.getStartY(), y), + // width: Math.abs(x - eventContext_.getStartX()), + // height: Math.abs(y - eventContext_.getStartY()) + // }, 100); + // } + + eventContext_.getCanvas().textActions.mouseMove(mouseX, mouseY); + + break; + } case 'rotate': { + box = utilsGetBBox(selected); + cx = box.x + box.width / 2; + cy = box.y + box.height / 2; + const m = getMatrix(selected), + center = transformPoint(cx, cy, m); + cx = center.x; + cy = center.y; + angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360; + if (eventContext_.getCurConfig().gridSnapping) { + angle = snapToGrid(angle); + } + if (evt.shiftKey) { // restrict rotations to nice angles (WRS) + const snap = 45; + angle = Math.round(angle / snap) * snap; + } + + eventContext_.getCanvas().setRotationAngle(angle < -180 ? (360 + angle) : angle, true); + eventContext_.getCanvas().call('transition', selectedElements); + break; + } default: + break; } /** @@ -561,216 +561,216 @@ export const mouseUpEvent = function (evt) { eventContext_.setStarted(false); let attrs, t; switch (eventContext_.getCurrentMode()) { - // intentionally fall-through to select here - case 'resize': - case 'multiselect': - if (!isNullish(eventContext_.getRubberBox())) { - eventContext_.getRubberBox().setAttribute('display', 'none'); - eventContext_.setCurBBoxes([]); - } - eventContext_.setCurrentMode('select'); - // Fallthrough - case 'select': - if (!isNullish(selectedElements[0])) { - // if we only have one selected element - if (isNullish(selectedElements[1])) { - // set our current stroke/fill properties to the element's - const selected = selectedElements[0]; - switch (selected.tagName) { - case 'g': - case 'use': - case 'image': - case 'foreignObject': - break; - default: - eventContext_.setCurProperties('fill', selected.getAttribute('fill')); - eventContext_.setCurProperties('fill_opacity', selected.getAttribute('fill-opacity')); - eventContext_.setCurProperties('stroke', selected.getAttribute('stroke')); - eventContext_.setCurProperties('stroke_opacity', selected.getAttribute('stroke-opacity')); - eventContext_.setCurProperties('stroke_width', selected.getAttribute('stroke-width')); - eventContext_.setCurProperties('stroke_dasharray', selected.getAttribute('stroke-dasharray')); - eventContext_.setCurProperties('stroke_linejoin', selected.getAttribute('stroke-linejoin')); - eventContext_.setCurProperties('stroke_linecap', selected.getAttribute('stroke-linecap')); - } - - if (selected.tagName === 'text') { - eventContext_.setCurText('font_size', selected.getAttribute('font-size')); - eventContext_.setCurText('font_family', selected.getAttribute('font-family')); - } - eventContext_.getCanvas().selectorManager.requestSelector(selected).showGrips(true); - - // This shouldn't be necessary as it was done on mouseDown... - // eventContext_.getCanvas().call('selected', [selected]); + // intentionally fall-through to select here + case 'resize': + case 'multiselect': + if (!isNullish(eventContext_.getRubberBox())) { + eventContext_.getRubberBox().setAttribute('display', 'none'); + eventContext_.setCurBBoxes([]); } - // always recalculate dimensions to strip off stray identity transforms - eventContext_.getCanvas().recalculateAllSelectedDimensions(); - // if it was being dragged/resized - if (realX !== eventContext_.getRStartX() || realY !== eventContext_.getRStartY()) { - const len = selectedElements.length; - for (let i = 0; i < len; ++i) { - if (isNullish(selectedElements[i])) { break; } - if (!selectedElements[i].firstChild) { - // Not needed for groups (incorrectly resizes elems), possibly not needed at all? - eventContext_.getCanvas().selectorManager.requestSelector(selectedElements[i]).resize(); + eventContext_.setCurrentMode('select'); + // Fallthrough + case 'select': + if (!isNullish(selectedElements[0])) { + // if we only have one selected element + if (isNullish(selectedElements[1])) { + // set our current stroke/fill properties to the element's + const selected = selectedElements[0]; + switch (selected.tagName) { + case 'g': + case 'use': + case 'image': + case 'foreignObject': + break; + default: + eventContext_.setCurProperties('fill', selected.getAttribute('fill')); + eventContext_.setCurProperties('fill_opacity', selected.getAttribute('fill-opacity')); + eventContext_.setCurProperties('stroke', selected.getAttribute('stroke')); + eventContext_.setCurProperties('stroke_opacity', selected.getAttribute('stroke-opacity')); + eventContext_.setCurProperties('stroke_width', selected.getAttribute('stroke-width')); + eventContext_.setCurProperties('stroke_dasharray', selected.getAttribute('stroke-dasharray')); + eventContext_.setCurProperties('stroke_linejoin', selected.getAttribute('stroke-linejoin')); + eventContext_.setCurProperties('stroke_linecap', selected.getAttribute('stroke-linecap')); + } + + if (selected.tagName === 'text') { + eventContext_.setCurText('font_size', selected.getAttribute('font-size')); + eventContext_.setCurText('font_family', selected.getAttribute('font-family')); + } + eventContext_.getCanvas().selectorManager.requestSelector(selected).showGrips(true); + + // This shouldn't be necessary as it was done on mouseDown... + // eventContext_.getCanvas().call('selected', [selected]); + } + // always recalculate dimensions to strip off stray identity transforms + eventContext_.getCanvas().recalculateAllSelectedDimensions(); + // if it was being dragged/resized + if (realX !== eventContext_.getRStartX() || realY !== eventContext_.getRStartY()) { + const len = selectedElements.length; + for (let i = 0; i < len; ++i) { + if (isNullish(selectedElements[i])) { break; } + if (!selectedElements[i].firstChild) { + // Not needed for groups (incorrectly resizes elems), possibly not needed at all? + eventContext_.getCanvas().selectorManager.requestSelector(selectedElements[i]).resize(); + } + } + // no change in position/size, so maybe we should move to pathedit + } else { + t = evt.target; + if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) { + eventContext_.getCanvas().pathActions.select(selectedElements[0]); + // if it was a path + // else, if it was selected and this is a shift-click, remove it from selection + } else if (evt.shiftKey && tempJustSelected !== t) { + eventContext_.getCanvas().removeFromSelection([t]); + } + } // no change in mouse position + + // Remove non-scaling stroke + if (supportsNonScalingStroke()) { + const elem = selectedElements[0]; + if (elem) { + elem.removeAttribute('style'); + walkTree(elem, function (el) { + el.removeAttribute('style'); + }); } } - // no change in position/size, so maybe we should move to pathedit - } else { - t = evt.target; - if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) { - eventContext_.getCanvas().pathActions.select(selectedElements[0]); - // if it was a path - // else, if it was selected and this is a shift-click, remove it from selection - } else if (evt.shiftKey && tempJustSelected !== t) { - eventContext_.getCanvas().removeFromSelection([t]); - } - } // no change in mouse position - - // Remove non-scaling stroke - if (supportsNonScalingStroke()) { - const elem = selectedElements[0]; - if (elem) { - elem.removeAttribute('style'); - walkTree(elem, function (el) { - el.removeAttribute('style'); - }); - } } - } - return; - case 'zoom': { - if (!isNullish(eventContext_.getRubberBox())) { - eventContext_.getRubberBox().setAttribute('display', 'none'); - } - const factor = evt.shiftKey ? 0.5 : 2; - eventContext_.getCanvas().call('zoomed', { - x: Math.min(eventContext_.getRStartX(), realX), - y: Math.min(eventContext_.getRStartY(), realY), - width: Math.abs(realX - eventContext_.getRStartX()), - height: Math.abs(realY - eventContext_.getRStartY()), - factor - }); - return; - } case 'fhpath': { - // Check that the path contains at least 2 points; a degenerate one-point path - // causes problems. - // Webkit ignores how we set the points attribute with commas and uses space - // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870 - eventContext_.setSumDistance(0); - eventContext_.setControllPoint2('x', 0); - eventContext_.setControllPoint2('y', 0); - eventContext_.setControllPoint1('x', 0); - eventContext_.setControllPoint1('y', 0); - eventContext_.setStart({x: 0, y: 0}); - eventContext_.setEnd('x', 0); - eventContext_.setEnd('y', 0); - const coords = element.getAttribute('points'); - const commaIndex = coords.indexOf(','); - keep = commaIndex >= 0 ? coords.includes(',', commaIndex + 1) : coords.includes(' ', coords.indexOf(' ') + 1); - if (keep) { - element = eventContext_.getCanvas().pathActions.smoothPolylineIntoPath(element); - } - break; - } case 'line': - const x1 = element.getAttribute('x1'); - const y1 = element.getAttribute('y1'); - const x2 = element.getAttribute('x2'); - const y2 = element.getAttribute('y2'); - keep = (x1 !== x2 || y1 !== y2); - break; - case 'foreignObject': - case 'square': - case 'rect': - case 'image': - const width = element.getAttribute('width'); - const height = element.getAttribute('height'); - // Image should be kept regardless of size (use inherit dimensions later) - keep = (width || height) || eventContext_.getCurrentMode() === 'image'; - break; - case 'circle': - keep = (element.getAttribute('r') !== '0'); - break; - case 'ellipse': - const rx = element.getAttribute('rx'); - const ry = element.getAttribute('ry'); - keep = (rx || ry); - break; - case 'fhellipse': - if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && -(eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { - element = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'ellipse', - curStyles: true, - attr: { - cx: (eventContext_.getFreehand('minx') + eventContext_.getFreehand('maxx')) / 2, - cy: (eventContext_.getFreehand('miny') + eventContext_.getFreehand('maxy')) / 2, - rx: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) / 2, - ry: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) / 2, - id: eventContext_.getId() - } + return; + case 'zoom': { + if (!isNullish(eventContext_.getRubberBox())) { + eventContext_.getRubberBox().setAttribute('display', 'none'); + } + const factor = evt.shiftKey ? 0.5 : 2; + eventContext_.getCanvas().call('zoomed', { + x: Math.min(eventContext_.getRStartX(), realX), + y: Math.min(eventContext_.getRStartY(), realY), + width: Math.abs(realX - eventContext_.getRStartX()), + height: Math.abs(realY - eventContext_.getRStartY()), + factor }); - eventContext_.getCanvas().call('changed', [element]); + return; + } case 'fhpath': { + // Check that the path contains at least 2 points; a degenerate one-point path + // causes problems. + // Webkit ignores how we set the points attribute with commas and uses space + // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870 + eventContext_.setSumDistance(0); + eventContext_.setControllPoint2('x', 0); + eventContext_.setControllPoint2('y', 0); + eventContext_.setControllPoint1('x', 0); + eventContext_.setControllPoint1('y', 0); + eventContext_.setStart({ x: 0, y: 0 }); + eventContext_.setEnd('x', 0); + eventContext_.setEnd('y', 0); + const coords = element.getAttribute('points'); + const commaIndex = coords.indexOf(','); + keep = commaIndex >= 0 ? coords.includes(',', commaIndex + 1) : coords.includes(' ', coords.indexOf(' ') + 1); + if (keep) { + element = eventContext_.getCanvas().pathActions.smoothPolylineIntoPath(element); + } + break; + } case 'line': + const x1 = element.getAttribute('x1'); + const y1 = element.getAttribute('y1'); + const x2 = element.getAttribute('x2'); + const y2 = element.getAttribute('y2'); + keep = (x1 !== x2 || y1 !== y2); + break; + case 'foreignObject': + case 'square': + case 'rect': + case 'image': + const width = element.getAttribute('width'); + const height = element.getAttribute('height'); + // Image should be kept regardless of size (use inherit dimensions later) + keep = (width || height) || eventContext_.getCurrentMode() === 'image'; + break; + case 'circle': + keep = (element.getAttribute('r') !== '0'); + break; + case 'ellipse': + const rx = element.getAttribute('rx'); + const ry = element.getAttribute('ry'); + keep = (rx || ry); + break; + case 'fhellipse': + if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && + (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { + element = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'ellipse', + curStyles: true, + attr: { + cx: (eventContext_.getFreehand('minx') + eventContext_.getFreehand('maxx')) / 2, + cy: (eventContext_.getFreehand('miny') + eventContext_.getFreehand('maxy')) / 2, + rx: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) / 2, + ry: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) / 2, + id: eventContext_.getId() + } + }); + eventContext_.getCanvas().call('changed', [element]); + keep = true; + } + break; + case 'fhrect': + if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && + (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { + element = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'rect', + curStyles: true, + attr: { + x: eventContext_.getFreehand('minx'), + y: eventContext_.getFreehand('miny'), + width: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')), + height: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')), + id: eventContext_.getId() + } + }); + eventContext_.getCanvas().call('changed', [element]); + keep = true; + } + break; + case 'text': keep = true; - } - break; - case 'fhrect': - if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && -(eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { - element = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'rect', - curStyles: true, - attr: { - x: eventContext_.getFreehand('minx'), - y: eventContext_.getFreehand('miny'), - width: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')), - height: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')), - id: eventContext_.getId() - } - }); - eventContext_.getCanvas().call('changed', [element]); - keep = true; - } - break; - case 'text': - keep = true; - eventContext_.getCanvas().selectOnly([element]); - eventContext_.getCanvas().textActions.start(element); - break; - case 'path': { - // set element to null here so that it is not removed nor finalized - element = null; - // continue to be set to true so that mouseMove happens - eventContext_.setStarted(true); + eventContext_.getCanvas().selectOnly([element]); + eventContext_.getCanvas().textActions.start(element); + break; + case 'path': { + // set element to null here so that it is not removed nor finalized + element = null; + // continue to be set to true so that mouseMove happens + eventContext_.setStarted(true); - const res = eventContext_.getCanvas().pathActions.mouseUp(evt, element, mouseX, mouseY); - ({element} = res); - ({keep} = res); - break; - } case 'pathedit': - keep = true; - element = null; - eventContext_.getCanvas().pathActions.mouseUp(evt); - break; - case 'textedit': - keep = false; - element = null; - eventContext_.getCanvas().textActions.mouseUp(evt, mouseX, mouseY); - break; - case 'rotate': { - keep = true; - element = null; - eventContext_.setCurrentMode('select'); - const batchCmd = eventContext_.getCanvas().undoMgr.finishUndoableChange(); - if (!batchCmd.isEmpty()) { - eventContext_.addCommandToHistory(batchCmd); - } - // perform recalculation to weed out any stray identity transforms that might get stuck - eventContext_.getCanvas().recalculateAllSelectedDimensions(); - eventContext_.getCanvas().call('changed', selectedElements); - break; - } default: - // This could occur in an extension - break; + const res = eventContext_.getCanvas().pathActions.mouseUp(evt, element, mouseX, mouseY); + ({ element } = res); + ({ keep } = res); + break; + } case 'pathedit': + keep = true; + element = null; + eventContext_.getCanvas().pathActions.mouseUp(evt); + break; + case 'textedit': + keep = false; + element = null; + eventContext_.getCanvas().textActions.mouseUp(evt, mouseX, mouseY); + break; + case 'rotate': { + keep = true; + element = null; + eventContext_.setCurrentMode('select'); + const batchCmd = eventContext_.getCanvas().undoMgr.finishUndoableChange(); + if (!batchCmd.isEmpty()) { + eventContext_.addCommandToHistory(batchCmd); + } + // perform recalculation to weed out any stray identity transforms that might get stuck + eventContext_.getCanvas().recalculateAllSelectedDimensions(); + eventContext_.getCanvas().call('changed', selectedElements); + break; + } default: + // This could occur in an extension + break; } /** @@ -790,7 +790,7 @@ export const mouseUpEvent = function (evt) { $.each(extResult, function (i, r) { if (r) { keep = r.keep || keep; - ({element} = r); + ({ element } = r); eventContext_.setStarted(r.started || eventContext_.getStarted()); } }); @@ -812,9 +812,9 @@ export const mouseUpEvent = function (evt) { // then go to Select mode. // WebKit returns
when the canvas is clicked, Firefox/Opera return if ((eventContext_.getCurrentMode() !== 'path' || !eventContext_.getDrawnPath()) && -t && t.parentNode && -t.parentNode.id !== 'selectorParentGroup' && -t.id !== 'svgcanvas' && t.id !== 'svgroot' + t && t.parentNode && + t.parentNode.id !== 'selectorParentGroup' && + t.id !== 'svgcanvas' && t.id !== 'svgroot' ) { // switch into "select" mode if we've clicked on an element eventContext_.getCanvas().setMode('select'); @@ -841,7 +841,7 @@ t.id !== 'svgcanvas' && t.id !== 'svgroot' try { // Fails in FF4 on foreignObject cAni.beginElement(); - } catch (e) {/* empty fn */} + } catch (e) {/* empty fn */ } } else { aniDur = 0; } @@ -873,7 +873,7 @@ export const dblClickEvent = function (evt) { const parent = evtTarget.parentNode; let mouseTarget = eventContext_.getCanvas().getMouseTarget(evt); - const {tagName} = mouseTarget; + const { tagName } = mouseTarget; if (tagName === 'text' && eventContext_.getCurrentMode() !== 'textedit') { const pt = transformPoint(evt.pageX, evt.pageY, eventContext_.getrootSctm()); @@ -898,8 +898,8 @@ export const dblClickEvent = function (evt) { } if ((parent.tagName !== 'g' && parent.tagName !== 'a') || -parent === eventContext_.getCanvas().getCurrentDrawing().getCurrentLayer() || -mouseTarget === eventContext_.getCanvas().selectorManager.selectorParentGroup + parent === eventContext_.getCanvas().getCurrentDrawing().getCurrentLayer() || + mouseTarget === eventContext_.getCanvas().selectorManager.selectorParentGroup ) { // Escape from in-group edit return; @@ -918,11 +918,12 @@ mouseTarget === eventContext_.getCanvas().selectorManager.selectorParentGroup * @returns {void} */ export const mouseDownEvent = function (evt) { + const dataStorage = eventContext_.getDataStorage(); const selectedElements = eventContext_.getSelectedElements(); const currentZoom = eventContext_.getCurrentZoom(); const curShape = eventContext_.getCanvas().getStyle(); const svgCanvas = eventContext_.getCanvas(); - const {$id} = svgCanvas; + const { $id } = svgCanvas; if (eventContext_.getCanvas().spaceKey || evt.button === 1) { return; } const rightClick = evt.button === 2; @@ -988,291 +989,291 @@ export const mouseDownEvent = function (evt) { const tlist = getTransformList(mouseTarget); switch (eventContext_.getCurrentMode()) { - case 'select': - eventContext_.setStarted(true); - eventContext_.setCurrentResizeMode('none'); - if (rightClick) { eventContext_.setStarted(false); } + case 'select': + eventContext_.setStarted(true); + eventContext_.setCurrentResizeMode('none'); + if (rightClick) { eventContext_.setStarted(false); } - if (mouseTarget !== eventContext_.getSVGRoot()) { - // if this element is not yet selected, clear selection and select it - if (!selectedElements.includes(mouseTarget)) { - // only clear selection if shift is not pressed (otherwise, add - // element to selection) - if (!evt.shiftKey) { - // No need to do the call here as it will be done on addToSelection - eventContext_.getCanvas().clearSelection(true); + if (mouseTarget !== eventContext_.getSVGRoot()) { + // if this element is not yet selected, clear selection and select it + if (!selectedElements.includes(mouseTarget)) { + // only clear selection if shift is not pressed (otherwise, add + // element to selection) + if (!evt.shiftKey) { + // No need to do the call here as it will be done on addToSelection + eventContext_.getCanvas().clearSelection(true); + } + eventContext_.getCanvas().addToSelection([mouseTarget]); + eventContext_.setJustSelected(mouseTarget); + eventContext_.getCanvas().pathActions.clear(); } - eventContext_.getCanvas().addToSelection([mouseTarget]); - eventContext_.setJustSelected(mouseTarget); - eventContext_.getCanvas().pathActions.clear(); - } - // else if it's a path, go into pathedit mode in mouseup + // else if it's a path, go into pathedit mode in mouseup - if (!rightClick) { - // insert a dummy transform so if the element(s) are moved it will have - // a transform to use for its translate - for (const selectedElement of selectedElements) { - if (isNullish(selectedElement)) { continue; } - const slist = getTransformList(selectedElement); - if (slist.numberOfItems) { - slist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), 0); - } else { - slist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + if (!rightClick) { + // insert a dummy transform so if the element(s) are moved it will have + // a transform to use for its translate + for (const selectedElement of selectedElements) { + if (isNullish(selectedElement)) { continue; } + const slist = getTransformList(selectedElement); + if (slist.numberOfItems) { + slist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), 0); + } else { + slist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + } } } + } else if (!rightClick) { + eventContext_.getCanvas().clearSelection(); + eventContext_.setCurrentMode('multiselect'); + if (isNullish(eventContext_.getRubberBox())) { + eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); + } + eventContext_.setRStartX(eventContext_.getRStartX() * currentZoom); + eventContext_.setRStartY(eventContext_.getRStartY() * currentZoom); + + assignAttributes(eventContext_.getRubberBox(), { + x: eventContext_.getRStartX(), + y: eventContext_.getRStartY(), + width: 0, + height: 0, + display: 'inline' + }, 100); } - } else if (!rightClick) { - eventContext_.getCanvas().clearSelection(); - eventContext_.setCurrentMode('multiselect'); + break; + case 'zoom': + eventContext_.setStarted(true); if (isNullish(eventContext_.getRubberBox())) { eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); } - eventContext_.setRStartX(eventContext_.getRStartX() * currentZoom); - eventContext_.setRStartY(eventContext_.getRStartY() * currentZoom); - assignAttributes(eventContext_.getRubberBox(), { - x: eventContext_.getRStartX(), - y: eventContext_.getRStartY(), + x: realX * currentZoom, + y: realX * currentZoom, width: 0, height: 0, display: 'inline' }, 100); - } - break; - case 'zoom': - eventContext_.setStarted(true); - if (isNullish(eventContext_.getRubberBox())) { - eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); - } - assignAttributes(eventContext_.getRubberBox(), { - x: realX * currentZoom, - y: realX * currentZoom, - width: 0, - height: 0, - display: 'inline' - }, 100); - break; - case 'resize': { - eventContext_.setStarted(true); - eventContext_.setStartX(x); - eventContext_.setStartY(y); + break; + case 'resize': { + eventContext_.setStarted(true); + eventContext_.setStartX(x); + eventContext_.setStartY(y); - // Getting the BBox from the selection box, since we know we - // want to orient around it - eventContext_.setInitBbox(utilsGetBBox($id('selectedBox0'))); - const bb = {}; - $.each(eventContext_.getInitBbox(), function (key, val) { - bb[key] = val / currentZoom; - }); - eventContext_.setInitBbox(bb); + // Getting the BBox from the selection box, since we know we + // want to orient around it + eventContext_.setInitBbox(utilsGetBBox($id('selectedBox0'))); + const bb = {}; + $.each(eventContext_.getInitBbox(), function (key, val) { + bb[key] = val / currentZoom; + }); + eventContext_.setInitBbox(bb); - // append three dummy transforms to the tlist so that - // we can translate,scale,translate in mousemove - const pos = getRotationAngle(mouseTarget) ? 1 : 0; + // append three dummy transforms to the tlist so that + // we can translate,scale,translate in mousemove + const pos = getRotationAngle(mouseTarget) ? 1 : 0; - if (hasMatrixTransform(tlist)) { - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - } else { - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + if (hasMatrixTransform(tlist)) { + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + } else { + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - if (supportsNonScalingStroke()) { - // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop): - // https://code.google.com/p/svg-edit/issues/detail?id=904 - // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 - // TODO: Remove this workaround once vendor fixes the issue - const iswebkit = isWebkit(); + if (supportsNonScalingStroke()) { + // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop): + // https://code.google.com/p/svg-edit/issues/detail?id=904 + // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 + // TODO: Remove this workaround once vendor fixes the issue + const iswebkit = isWebkit(); - let delayedStroke; - if (iswebkit) { - delayedStroke = function (ele) { - const stroke_ = ele.getAttribute('stroke'); - ele.removeAttribute('stroke'); - // Re-apply stroke after delay. Anything higher than 1 seems to cause flicker - if (stroke_ !== null) setTimeout(function () { ele.setAttribute('stroke', stroke_); }, 0); - }; - } - mouseTarget.style.vectorEffect = 'non-scaling-stroke'; - if (iswebkit) { delayedStroke(mouseTarget); } - - const all = mouseTarget.getElementsByTagName('*'), - len = all.length; - for (let i = 0; i < len; i++) { - if (!all[i].style) { // mathML - continue; + let delayedStroke; + if (iswebkit) { + delayedStroke = function (ele) { + const stroke_ = ele.getAttribute('stroke'); + ele.removeAttribute('stroke'); + // Re-apply stroke after delay. Anything higher than 1 seems to cause flicker + if (stroke_ !== null) setTimeout(function () { ele.setAttribute('stroke', stroke_); }, 0); + }; + } + mouseTarget.style.vectorEffect = 'non-scaling-stroke'; + if (iswebkit) { delayedStroke(mouseTarget); } + + const all = mouseTarget.getElementsByTagName('*'), + len = all.length; + for (let i = 0; i < len; i++) { + if (!all[i].style) { // mathML + continue; + } + all[i].style.vectorEffect = 'non-scaling-stroke'; + if (iswebkit) { delayedStroke(all[i]); } } - all[i].style.vectorEffect = 'non-scaling-stroke'; - if (iswebkit) { delayedStroke(all[i]); } } } + break; } - break; - } - case 'fhellipse': - case 'fhrect': - case 'fhpath': - eventContext_.setStart({x: realX, y: realY}); - eventContext_.setControllPoint1('x', 0); - eventContext_.setControllPoint1('y', 0); - eventContext_.setControllPoint2('x', 0); - eventContext_.setControllPoint2('y', 0); - eventContext_.setStarted(true); - eventContext_.setDAttr(realX + ',' + realY + ' '); - // Commented out as doing nothing now: - // strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'polyline', - curStyles: true, - attr: { - points: eventContext_.getDAttr(), - id: eventContext_.getCanvas().getNextId(), - fill: 'none', - opacity: curShape.opacity / 2, - 'stroke-linecap': 'round', - style: 'pointer-events:none' - } - }); - eventContext_.setFreehand('minx', realX); - eventContext_.setFreehand('maxx', realX); - eventContext_.setFreehand('miny', realY); - eventContext_.setFreehand('maxy', realY); - break; - case 'image': { - eventContext_.setStarted(true); - const newImage = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'image', - attr: { - x, - y, - width: 0, - height: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2, - style: 'pointer-events:inherit' - } - }); - setHref(newImage, eventContext_.getLastGoodImgUrl()); - preventClickDefault(newImage); - break; - } case 'square': + case 'fhellipse': + case 'fhrect': + case 'fhpath': + eventContext_.setStart({ x: realX, y: realY }); + eventContext_.setControllPoint1('x', 0); + eventContext_.setControllPoint1('y', 0); + eventContext_.setControllPoint2('x', 0); + eventContext_.setControllPoint2('y', 0); + eventContext_.setStarted(true); + eventContext_.setDAttr(realX + ',' + realY + ' '); + // Commented out as doing nothing now: + // strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'polyline', + curStyles: true, + attr: { + points: eventContext_.getDAttr(), + id: eventContext_.getCanvas().getNextId(), + fill: 'none', + opacity: curShape.opacity / 2, + 'stroke-linecap': 'round', + style: 'pointer-events:none' + } + }); + eventContext_.setFreehand('minx', realX); + eventContext_.setFreehand('maxx', realX); + eventContext_.setFreehand('miny', realY); + eventContext_.setFreehand('maxy', realY); + break; + case 'image': { + eventContext_.setStarted(true); + const newImage = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'image', + attr: { + x, + y, + width: 0, + height: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2, + style: 'pointer-events:inherit' + } + }); + setHref(newImage, eventContext_.getLastGoodImgUrl()); + preventClickDefault(newImage); + break; + } case 'square': // TODO: once we create the rect, we lose information that this was a square // (for resizing purposes this could be important) // Fallthrough - case 'rect': - eventContext_.setStarted(true); - eventContext_.setStartX(x); - eventContext_.setStartY(y); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'rect', - curStyles: true, - attr: { - x, - y, - width: 0, - height: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'line': { - eventContext_.setStarted(true); - const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'line', - curStyles: true, - attr: { - x1: x, - y1: y, - x2: x, - y2: y, - id: eventContext_.getCanvas().getNextId(), - stroke: curShape.stroke, - 'stroke-width': strokeW, - 'stroke-dasharray': curShape.stroke_dasharray, - 'stroke-linejoin': curShape.stroke_linejoin, - 'stroke-linecap': curShape.stroke_linecap, - 'stroke-opacity': curShape.stroke_opacity, - fill: 'none', - opacity: curShape.opacity / 2, - style: 'pointer-events:none' - } - }); - break; - } case 'circle': - eventContext_.setStarted(true); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'circle', - curStyles: true, - attr: { - cx: x, - cy: y, - r: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'ellipse': - eventContext_.setStarted(true); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'ellipse', - curStyles: true, - attr: { - cx: x, - cy: y, - rx: 0, - ry: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'text': - eventContext_.setStarted(true); + case 'rect': + eventContext_.setStarted(true); + eventContext_.setStartX(x); + eventContext_.setStartY(y); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'rect', + curStyles: true, + attr: { + x, + y, + width: 0, + height: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'line': { + eventContext_.setStarted(true); + const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'line', + curStyles: true, + attr: { + x1: x, + y1: y, + x2: x, + y2: y, + id: eventContext_.getCanvas().getNextId(), + stroke: curShape.stroke, + 'stroke-width': strokeW, + 'stroke-dasharray': curShape.stroke_dasharray, + 'stroke-linejoin': curShape.stroke_linejoin, + 'stroke-linecap': curShape.stroke_linecap, + 'stroke-opacity': curShape.stroke_opacity, + fill: 'none', + opacity: curShape.opacity / 2, + style: 'pointer-events:none' + } + }); + break; + } case 'circle': + eventContext_.setStarted(true); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'circle', + curStyles: true, + attr: { + cx: x, + cy: y, + r: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'ellipse': + eventContext_.setStarted(true); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'ellipse', + curStyles: true, + attr: { + cx: x, + cy: y, + rx: 0, + ry: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'text': + eventContext_.setStarted(true); /* const newText = */ eventContext_.getCanvas().addSVGElementFromJson({ - element: 'text', - curStyles: true, - attr: { - x, - y, - id: eventContext_.getCanvas().getNextId(), - fill: eventContext_.getCurText('fill'), - 'stroke-width': eventContext_.getCurText('stroke_width'), - 'font-size': eventContext_.getCurText('font_size'), - 'font-family': eventContext_.getCurText('font_family'), - 'text-anchor': 'middle', - 'xml:space': 'preserve', - opacity: curShape.opacity - } - }); - // newText.textContent = 'text'; - break; - case 'path': + element: 'text', + curStyles: true, + attr: { + x, + y, + id: eventContext_.getCanvas().getNextId(), + fill: eventContext_.getCurText('fill'), + 'stroke-width': eventContext_.getCurText('stroke_width'), + 'font-size': eventContext_.getCurText('font_size'), + 'font-family': eventContext_.getCurText('font_family'), + 'text-anchor': 'middle', + 'xml:space': 'preserve', + opacity: curShape.opacity + } + }); + // newText.textContent = 'text'; + break; + case 'path': // Fall through - case 'pathedit': - eventContext_.setStartX(eventContext_.getStartX() * currentZoom); - eventContext_.setStartY(eventContext_.getStartY() * currentZoom); - eventContext_.getCanvas().pathActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); - eventContext_.setStarted(true); - break; - case 'textedit': - eventContext_.setStartX(eventContext_.getStartX() * currentZoom); - eventContext_.setStartY(eventContext_.getStartY() * currentZoom); - eventContext_.getCanvas().textActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); - eventContext_.setStarted(true); - break; - case 'rotate': - eventContext_.setStarted(true); - // we are starting an undoable change (a drag-rotation) - eventContext_.getCanvas().undoMgr.beginUndoableChange('transform', selectedElements); - break; - default: - // This could occur in an extension - break; + case 'pathedit': + eventContext_.setStartX(eventContext_.getStartX() * currentZoom); + eventContext_.setStartY(eventContext_.getStartY() * currentZoom); + eventContext_.getCanvas().pathActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); + eventContext_.setStarted(true); + break; + case 'textedit': + eventContext_.setStartX(eventContext_.getStartX() * currentZoom); + eventContext_.setStartY(eventContext_.getStartY() * currentZoom); + eventContext_.getCanvas().textActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); + eventContext_.setStarted(true); + break; + case 'rotate': + eventContext_.setStarted(true); + // we are starting an undoable change (a drag-rotation) + eventContext_.getCanvas().undoMgr.beginUndoableChange('transform', selectedElements); + break; + default: + // This could occur in an extension + break; } /** @@ -1306,7 +1307,7 @@ export const mouseDownEvent = function (evt) { export const DOMMouseScrollEvent = function (e) { const currentZoom = eventContext_.getCurrentZoom(); const svgCanvas = eventContext_.getCanvas(); - const {$id} = svgCanvas; + const { $id } = svgCanvas; if (!e.shiftKey) { return; } e.preventDefault(); @@ -1382,6 +1383,6 @@ export const DOMMouseScrollEvent = function (e) { eventContext_.getCanvas().setZoom(zoomlevel); document.getElementById('zoom').value = ((zoomlevel * 100).toFixed(1)); - eventContext_.getCanvas().call('updateCanvas', {center: false, newCtr}); + eventContext_.getCanvas().call('updateCanvas', { center: false, newCtr }); eventContext_.getCanvas().call('zoomDone'); }; diff --git a/src/svgcanvas/recalculate.js b/src/svgcanvas/recalculate.js index 6625f81c..a8b259b4 100644 --- a/src/svgcanvas/recalculate.js +++ b/src/svgcanvas/recalculate.js @@ -6,13 +6,13 @@ */ import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` -import {NS} from '../common/namespaces.js'; -import {convertToNum} from '../common/units.js'; -import {isWebkit} from '../common/browser.js'; -import {getTransformList} from './svgtransformlist.js'; -import {getRotationAngle, getHref, getBBox, getRefElem, isNullish} from './utilities.js'; -import {BatchCommand, ChangeElementCommand} from './history.js'; -import {remapElement} from './coords.js'; +import { NS } from '../common/namespaces.js'; +import { convertToNum } from '../common/units.js'; +import { isWebkit } from '../common/browser.js'; +import { getTransformList } from './svgtransformlist.js'; +import { getRotationAngle, getHref, getBBox, getRefElem, isNullish } from './utilities.js'; +import { BatchCommand, ChangeElementCommand } from './history.js'; +import { remapElement } from './coords.js'; import { isIdentity, matrixMultiply, transformPoint, transformListToTransform, hasMatrixTransform @@ -83,6 +83,7 @@ export const recalculateDimensions = function (selected) { } const svgroot = context_.getSVGRoot(); + const dataStorage = context_.getDataStorage(); const tlist = getTransformList(selected); // remove any unnecessary transforms @@ -93,7 +94,7 @@ export const recalculateDimensions = function (selected) { const xform = tlist.getItem(k); if (xform.type === 0) { tlist.removeItem(k); - // remove identity matrices + // remove identity matrices } else if (xform.type === 1) { if (isIdentity(xform.matrix)) { if (noi === 1) { @@ -106,14 +107,14 @@ export const recalculateDimensions = function (selected) { } tlist.removeItem(k); } - // remove zero-degree rotations + // remove zero-degree rotations } else if (xform.type === 4 && xform.angle === 0) { tlist.removeItem(k); } } // End here if all it has is a rotation if (tlist.numberOfItems === 1 && - getRotationAngle(selected)) { return null; } + getRotationAngle(selected)) { return null; } } // if this element had no transforms, we are done @@ -163,19 +164,18 @@ export const recalculateDimensions = function (selected) { // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned). switch (selected.tagName) { - // Ignore these elements, as they can absorb the [M] - case 'line': - case 'polyline': - case 'polygon': - case 'path': - break; - default: - if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) || + // Ignore these elements, as they can absorb the [M] + case 'line': + case 'polyline': + case 'polygon': + case 'path': + break; + default: + if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) || (tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)) { - return null; - } + return null; + } } - // Grouped SVG element const gsvg = (dataStorage.has(selected, 'gsvg')) ? dataStorage.get(selected, 'gsvg') : undefined; // we know we have some transforms, so set up return variable @@ -186,46 +186,46 @@ export const recalculateDimensions = function (selected) { let initial = null; let attrs = []; switch (selected.tagName) { - case 'line': - attrs = ['x1', 'y1', 'x2', 'y2']; - break; - case 'circle': - attrs = ['cx', 'cy', 'r']; - break; - case 'ellipse': - attrs = ['cx', 'cy', 'rx', 'ry']; - break; - case 'foreignObject': - case 'rect': - case 'image': - attrs = ['width', 'height', 'x', 'y']; - break; - case 'use': - case 'text': - case 'tspan': - attrs = ['x', 'y']; - break; - case 'polygon': - case 'polyline': { - initial = {}; - initial.points = selected.getAttribute('points'); - const list = selected.points; - const len = list.numberOfItems; - changes.points = new Array(len); - for (let i = 0; i < len; ++i) { - const pt = list.getItem(i); - changes.points[i] = {x: pt.x, y: pt.y}; - } - break; - } case 'path': - initial = {}; - initial.d = selected.getAttribute('d'); - changes.d = selected.getAttribute('d'); - break; + case 'line': + attrs = ['x1', 'y1', 'x2', 'y2']; + break; + case 'circle': + attrs = ['cx', 'cy', 'r']; + break; + case 'ellipse': + attrs = ['cx', 'cy', 'rx', 'ry']; + break; + case 'foreignObject': + case 'rect': + case 'image': + attrs = ['width', 'height', 'x', 'y']; + break; + case 'use': + case 'text': + case 'tspan': + attrs = ['x', 'y']; + break; + case 'polygon': + case 'polyline': { + initial = {}; + initial.points = selected.getAttribute('points'); + const list = selected.points; + const len = list.numberOfItems; + changes.points = new Array(len); + for (let i = 0; i < len; ++i) { + const pt = list.getItem(i); + changes.points[i] = { x: pt.x, y: pt.y }; + } + break; + } case 'path': + initial = {}; + initial.d = selected.getAttribute('d'); + changes.d = selected.getAttribute('d'); + break; } // switch on element type to get initial values if (attrs.length) { - Array.prototype.forEach.call(attrs, function(attr, i){ + Array.prototype.forEach.call(attrs, function (attr, i) { changes[attr] = selected.getAttribute(attr); }); for (const [attr, val] of Object.entries(changes)) { @@ -245,7 +245,7 @@ export const recalculateDimensions = function (selected) { initial = $.extend(true, {}, changes); for (const [attr, val] of Object.entries(initial)) { initial[attr] = convertToNum(attr, val); - } + } } // save the start transform value too initial.transform = context_.getStartTransform() || ''; @@ -256,7 +256,7 @@ export const recalculateDimensions = function (selected) { if ((selected.tagName === 'g' && !gsvg) || selected.tagName === 'a') { const box = getBBox(selected); - oldcenter = {x: box.x + box.width / 2, y: box.y + box.height / 2}; + oldcenter = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; newcenter = transformPoint( box.x + box.width / 2, box.y + box.height / 2, @@ -341,7 +341,7 @@ export const recalculateDimensions = function (selected) { childTlist.clear(); childTlist.appendItem(e2t); // childxforms.push(e2t); - // if not rotated or skewed, push the [T][S][-T] down to the child + // if not rotated or skewed, push the [T][S][-T] down to the child } else { // update the transform list with translate,scale,translate @@ -406,9 +406,9 @@ export const recalculateDimensions = function (selected) { e2t.setMatrix(m); tlist.clear(); tlist.appendItem(e2t); - // next, check if the first transform was a translate - // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ] - // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] + // next, check if the first transform was a translate + // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ] + // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] } else if ((N === 1 || (N > 1 && tlist.getItem(1).type !== 3)) && tlist.getItem(0).type === 2) { operation = 2; // translate @@ -474,8 +474,8 @@ export const recalculateDimensions = function (selected) { } context_.setStartTransform(oldStartTransform); } - // else, a matrix imposition from a parent group - // keep pushing it down to the children + // else, a matrix imposition from a parent group + // keep pushing it down to the children } else if (N === 1 && tlist.getItem(0).type === 1 && !gangle) { operation = 1; const m = tlist.getItem(0).matrix, @@ -509,7 +509,7 @@ export const recalculateDimensions = function (selected) { } } tlist.clear(); - // else it was just a rotate + // else it was just a rotate } else { if (gangle) { const newRot = svgroot.createSVGTransform(); @@ -542,7 +542,7 @@ export const recalculateDimensions = function (selected) { tlist.appendItem(newRot); } } - // if it was a resize + // if it was a resize } else if (operation === 3) { const m = transformListToTransform(tlist).matrix; const roldt = svgroot.createSVGTransform(); @@ -590,7 +590,7 @@ export const recalculateDimensions = function (selected) { } } } - // else, it's a non-group + // else, it's a non-group } else { // TODO: box might be null for some elements ( etc), need to handle this const box = getBBox(selected); @@ -606,7 +606,7 @@ export const recalculateDimensions = function (selected) { // temporarily strip off the rotate and save the old center const angle = getRotationAngle(selected); if (angle) { - oldcenter = {x: box.x + box.width / 2, y: box.y + box.height / 2}; + oldcenter = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; newcenter = transformPoint( box.x + box.width / 2, box.y + box.height / 2, @@ -672,8 +672,8 @@ export const recalculateDimensions = function (selected) { tlist.removeItem(N - 1); tlist.removeItem(N - 2); tlist.removeItem(N - 3); - // if we had [T][S][-T][M], then this was a skewed element being resized - // Thus, we simply combine it all into one matrix + // if we had [T][S][-T][M], then this was a skewed element being resized + // Thus, we simply combine it all into one matrix } else if (N === 4 && tlist.getItem(N - 1).type === 1) { operation = 3; // scale m = transformListToTransform(tlist).matrix; @@ -683,10 +683,10 @@ export const recalculateDimensions = function (selected) { tlist.appendItem(e2t); // reset the matrix so that the element is not re-mapped m = svgroot.createSVGMatrix(); - // if we had [R][T][S][-T][M], then this was a rotated matrix-element - // if we had [T1][M] we want to transform this into [M][T2] - // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2] - // down to the element + // if we had [R][T][S][-T][M], then this was a rotated matrix-element + // if we had [T1][M] we want to transform this into [M][T2] + // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2] + // down to the element } else if ((N === 1 || (N > 1 && tlist.getItem(1).type !== 3)) && tlist.getItem(0).type === 2) { operation = 2; // translate @@ -695,43 +695,43 @@ export const recalculateDimensions = function (selected) { meqInv = meq.inverse(); m = matrixMultiply(meqInv, oldxlate, meq); tlist.removeItem(0); - // else if this child now has a matrix imposition (from a parent group) - // we might be able to simplify + // else if this child now has a matrix imposition (from a parent group) + // we might be able to simplify } else if (N === 1 && tlist.getItem(0).type === 1 && !angle) { // Remap all point-based elements m = transformListToTransform(tlist).matrix; switch (selected.tagName) { - case 'line': - changes = { - x1: selected.getAttribute('x1'), - y1: selected.getAttribute('y1'), - x2: selected.getAttribute('x2'), - y2: selected.getAttribute('y2'), - } - // Fallthrough - case 'polyline': - case 'polygon': - changes.points = selected.getAttribute('points'); - if (changes.points) { - const list = selected.points; - const len = list.numberOfItems; - changes.points = new Array(len); - for (let i = 0; i < len; ++i) { - const pt = list.getItem(i); - changes.points[i] = {x: pt.x, y: pt.y}; + case 'line': + changes = { + x1: selected.getAttribute('x1'), + y1: selected.getAttribute('y1'), + x2: selected.getAttribute('x2'), + y2: selected.getAttribute('y2'), } - } // Fallthrough - case 'path': - changes.d = selected.getAttribute('d'); - operation = 1; - tlist.clear(); - break; - default: - break; + case 'polyline': + case 'polygon': + changes.points = selected.getAttribute('points'); + if (changes.points) { + const list = selected.points; + const len = list.numberOfItems; + changes.points = new Array(len); + for (let i = 0; i < len; ++i) { + const pt = list.getItem(i); + changes.points[i] = { x: pt.x, y: pt.y }; + } + } + // Fallthrough + case 'path': + changes.d = selected.getAttribute('d'); + operation = 1; + tlist.clear(); + break; + default: + break; } - // if it was a rotation, put the rotate back and return without a command - // (this function has zero work to do for a rotate()) + // if it was a rotation, put the rotate back and return without a command + // (this function has zero work to do for a rotate()) } else { // operation = 4; // rotation if (angle) { @@ -789,12 +789,12 @@ export const recalculateDimensions = function (selected) { } } } - // [Rold][M][T][S][-T] became [Rold][M] - // we want it to be [Rnew][M][Tr] where Tr is the - // translation required to re-center it - // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M] + // [Rold][M][T][S][-T] became [Rold][M] + // we want it to be [Rnew][M][Tr] where Tr is the + // translation required to re-center it + // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M] } else if (operation === 3 && angle) { - const {matrix} = transformListToTransform(tlist); + const { matrix } = transformListToTransform(tlist); const roldt = svgroot.createSVGTransform(); roldt.setRotate(angle, oldcenter.x, oldcenter.y); const rold = roldt.matrix; diff --git a/src/svgcanvas/select.js b/src/svgcanvas/select.js index c7d2259c..e01fbd8b 100644 --- a/src/svgcanvas/select.js +++ b/src/svgcanvas/select.js @@ -7,10 +7,10 @@ * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller */ -import {isTouch, isWebkit} from '../common/browser.js'; // , isOpera -import {getRotationAngle, getBBox, getStrokedBBox, isNullish} from './utilities.js'; -import {transformListToTransform, transformBox, transformPoint} from './math.js'; -import {getTransformList} from './svgtransformlist.js'; +import { isTouch, isWebkit } from '../common/browser.js'; // , isOpera +import { getRotationAngle, getBBox, getStrokedBBox, isNullish } from './utilities.js'; +import { transformListToTransform, transformBox, transformPoint } from './math.js'; +import { getTransformList } from './svgtransformlist.js'; const $ = jQuery; @@ -28,7 +28,7 @@ export class Selector { * @param {Element} elem - DOM element associated with this selector * @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for initialization (prevents duplicate `getBBox` call). */ - constructor (id, elem, bbox) { + constructor(id, elem, bbox) { // this is the selector's unique number this.id = id; @@ -41,7 +41,7 @@ export class Selector { // this holds a reference to the element that holds all visual elements of the selector this.selectorGroup = svgFactory_.createSVGElement({ element: 'g', - attr: {id: ('selectorGroup' + this.id)} + attr: { id: ('selectorGroup' + this.id) } }); // this holds a reference to the path rect @@ -80,7 +80,7 @@ export class Selector { * @param {module:utilities.BBoxObject} bbox - Optional bbox to use for reset (prevents duplicate getBBox call). * @returns {void} */ - reset (e, bbox) { + reset(e, bbox) { this.locked = true; this.selectedElement = e; this.resize(bbox); @@ -92,7 +92,7 @@ export class Selector { * @param {boolean} show - Indicates whether grips should be shown or not * @returns {void} */ - showGrips (show) { + showGrips(show) { const bShow = show ? 'inline' : 'none'; selectorManager_.selectorGripsGroup.setAttribute('display', bShow); const elem = this.selectedElement; @@ -108,7 +108,8 @@ export class Selector { * @param {module:utilities.BBoxObject} [bbox] - BBox to use for resize (prevents duplicate getBBox call). * @returns {void} */ - resize (bbox) { + resize(bbox) { + const dataStorage = svgFactory_.getDataStorage(); const selectedBox = this.selectorRect, mgr = selectorManager_, selectedGrips = mgr.selectorGrips, @@ -120,7 +121,7 @@ export class Selector { offset += (sw / 2); } - const {tagName} = selected; + const { tagName } = selected; if (tagName === 'text') { offset += 2 / currentZoom; } @@ -159,7 +160,7 @@ export class Selector { offset *= currentZoom; const nbox = transformBox(l * currentZoom, t * currentZoom, w * currentZoom, h * currentZoom, m), - {aabox} = nbox; + { aabox } = nbox; let nbax = aabox.x - offset, nbay = aabox.y - offset, nbaw = aabox.width + (offset * 2), @@ -180,13 +181,13 @@ export class Selector { nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm); // calculate the axis-aligned bbox - const {tl} = nbox; + const { tl } = nbox; let minx = tl.x, miny = tl.y, maxx = tl.x, maxy = tl.y; - const {min, max} = Math; + const { min, max } = Math; minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset; miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset; @@ -242,7 +243,7 @@ export class Selector { * @param {Float} angle - Current rotation angle in degrees * @returns {void} */ - static updateGripCursors (angle) { + static updateGripCursors(angle) { const dirArr = Object.keys(selectorManager_.selectorGrips); let steps = Math.round(angle / 45); if (steps < 0) { steps += 8; } @@ -263,7 +264,7 @@ export class SelectorManager { /** * Sets up properties and calls `initGroup`. */ - constructor () { + constructor() { // this will hold the element that contains all selector rects/grips this.selectorParentGroup = null; @@ -299,7 +300,8 @@ export class SelectorManager { * Resets the parent selector group element. * @returns {void} */ - initGroup () { + initGroup() { + const dataStorage = svgFactory_.getDataStorage(); // remove old selector parent group if it existed if (this.selectorParentGroup && this.selectorParentGroup.parentNode) { this.selectorParentGroup.remove(); @@ -308,11 +310,11 @@ export class SelectorManager { // create parent selector group and add it to svgroot this.selectorParentGroup = svgFactory_.createSVGElement({ element: 'g', - attr: {id: 'selectorParentGroup'} + attr: { id: 'selectorParentGroup' } }); this.selectorGripsGroup = svgFactory_.createSVGElement({ element: 'g', - attr: {display: 'none'} + attr: { display: 'none' } }); this.selectorParentGroup.append(this.selectorGripsGroup); svgFactory_.svgRoot().append(this.selectorParentGroup); @@ -416,7 +418,7 @@ export class SelectorManager { * @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for reset (prevents duplicate getBBox call). * @returns {Selector} The selector based on the given element */ - requestSelector (elem, bbox) { + requestSelector(elem, bbox) { if (isNullish(elem)) { return null; } const N = this.selectors.length; @@ -446,13 +448,13 @@ export class SelectorManager { * @param {Element} elem - DOM element to remove the selector for * @returns {void} */ - releaseSelector (elem) { + releaseSelector(elem) { if (isNullish(elem)) { return; } const N = this.selectors.length, sel = this.selectorMap[elem.id]; if (sel && !sel.locked) { // TODO(codedread): Ensure this exists in this module. - console.log('WARNING! selector was released but was already unlocked'); + console.log('WARNING! selector was released but was already unlocked'); } for (let i = 0; i < N; ++i) { if (this.selectors[i] && this.selectors[i] === sel) { @@ -464,7 +466,7 @@ export class SelectorManager { // remove from DOM and store reference in JS but only if it exists in the DOM try { sel.selectorGroup.setAttribute('display', 'none'); - } catch (e) {/* empty fn */} + } catch (e) {/* empty fn */ } break; } @@ -475,7 +477,7 @@ export class SelectorManager { * @returns {SVGRectElement} The rubberBandBox DOM element. This is the rectangle drawn by * the user for selecting/zooming */ - getRubberBandBox () { + getRubberBandBox() { if (!this.rubberBandBox) { this.rubberBandBox = svgFactory_.createSVGElement({ diff --git a/src/svgcanvas/selected-elem.js b/src/svgcanvas/selected-elem.js index 2340055f..575603ac 100644 --- a/src/svgcanvas/selected-elem.js +++ b/src/svgcanvas/selected-elem.js @@ -7,7 +7,7 @@ * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller */ import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute -import {NS} from '../common/namespaces.js'; +import { NS } from '../common/namespaces.js'; import * as hstry from './history.js'; import * as pathModule from './path.js'; import { @@ -26,7 +26,7 @@ import { import { isGecko } from '../common/browser.js'; // , supportsEditableText -import {getParents} from '../editor/components/jgraduate/Util.js'; +import { getParents } from '../editor/components/jgraduate/Util.js'; const { MoveElementCommand, BatchCommand, InsertElementCommand, RemoveElementCommand, ChangeElementCommand @@ -80,7 +80,7 @@ export const moveToBottomSelectedElem = function () { let t = selected; const oldParent = t.parentNode; const oldNextSibling = t.nextSibling; - let {firstChild} = t.parentNode; + let { firstChild } = t.parentNode; if (firstChild.tagName === 'title') { firstChild = firstChild.nextSibling; } @@ -119,7 +119,7 @@ export const moveUpDownSelected = function (dir) { const list = elementContext_.getIntersectionList(getStrokedBBoxDefaultVisible([selected])); if (dir === 'Down') { list.reverse(); } - Array.prototype.forEach.call(list, function(el, i){ + Array.prototype.forEach.call(list, function (el, i) { if (!foundCur) { if (el === selected) { foundCur = true; @@ -134,7 +134,7 @@ export const moveUpDownSelected = function (dir) { const t = selected; const oldParent = t.parentNode; const oldNextSibling = t.nextSibling; - if(dir === 'Down') { + if (dir === 'Down') { closest.insertAdjacentElement('beforebegin', t); } else { closest.insertAdjacentElement('afterend', t); @@ -252,7 +252,7 @@ export const cloneSelectedElements = function (x, y) { * @param {Element} b * @returns {Integer} */ - function sortfunction (a, b) { + function sortfunction(a, b) { return (index(b) - index(a)); } selectedElements.sort(sortfunction); @@ -302,40 +302,40 @@ export const alignSelectedElements = function (type, relativeTo) { // now bbox is axis-aligned and handles rotation switch (relativeTo) { - case 'smallest': - if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && - (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || - ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && - (curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) - ) { - minx = bboxes[i].x; - miny = bboxes[i].y; - maxx = bboxes[i].x + bboxes[i].width; - maxy = bboxes[i].y + bboxes[i].height; - curwidth = bboxes[i].width; - curheight = bboxes[i].height; - } - break; - case 'largest': - if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && - (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) || - ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && - (curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) - ) { - minx = bboxes[i].x; - miny = bboxes[i].y; - maxx = bboxes[i].x + bboxes[i].width; - maxy = bboxes[i].y + bboxes[i].height; - curwidth = bboxes[i].width; - curheight = bboxes[i].height; - } - break; - default: // 'selected' - if (bboxes[i].x < minx) { minx = bboxes[i].x; } - if (bboxes[i].y < miny) { miny = bboxes[i].y; } - if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width; } - if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height; } - break; + case 'smallest': + if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && + (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || + ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && + (curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) + ) { + minx = bboxes[i].x; + miny = bboxes[i].y; + maxx = bboxes[i].x + bboxes[i].width; + maxy = bboxes[i].y + bboxes[i].height; + curwidth = bboxes[i].width; + curheight = bboxes[i].height; + } + break; + case 'largest': + if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && + (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) || + ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && + (curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) + ) { + minx = bboxes[i].x; + miny = bboxes[i].y; + maxx = bboxes[i].x + bboxes[i].width; + maxy = bboxes[i].y + bboxes[i].height; + curwidth = bboxes[i].width; + curheight = bboxes[i].height; + } + break; + default: // 'selected' + if (bboxes[i].x < minx) { minx = bboxes[i].x; } + if (bboxes[i].y < miny) { miny = bboxes[i].y; } + if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width; } + if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height; } + break; } } // loop for each element to find the bbox and adjust min/max @@ -355,30 +355,30 @@ export const alignSelectedElements = function (type, relativeTo) { dx[i] = 0; dy[i] = 0; switch (type) { - case 'l': // left (horizontal) - case 'left': // left (horizontal) - dx[i] = minx - bbox.x; - break; - case 'c': // center (horizontal) - case 'center': // center (horizontal) - dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2); - break; - case 'r': // right (horizontal) - case 'right': // right (horizontal) - dx[i] = maxx - (bbox.x + bbox.width); - break; - case 't': // top (vertical) - case 'top': // top (vertical) - dy[i] = miny - bbox.y; - break; - case 'm': // middle (vertical) - case 'middle': // middle (vertical) - dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2); - break; - case 'b': // bottom (vertical) - case 'bottom': // bottom (vertical) - dy[i] = maxy - (bbox.y + bbox.height); - break; + case 'l': // left (horizontal) + case 'left': // left (horizontal) + dx[i] = minx - bbox.x; + break; + case 'c': // center (horizontal) + case 'center': // center (horizontal) + dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2); + break; + case 'r': // right (horizontal) + case 'right': // right (horizontal) + dx[i] = maxx - (bbox.x + bbox.width); + break; + case 't': // top (vertical) + case 'top': // top (vertical) + dy[i] = miny - bbox.y; + break; + case 'm': // middle (vertical) + case 'middle': // middle (vertical) + dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2); + break; + case 'b': // bottom (vertical) + case 'bottom': // bottom (vertical) + dy[i] = maxy - (bbox.y + bbox.height); + break; } } moveSelectedElements(dx, dy); @@ -416,7 +416,7 @@ export const deleteSelectedElements = function () { parent = parent.parentNode; } - const {nextSibling} = t; + const { nextSibling } = t; t.remove(); const elem = t; selectedCopy.push(selected); // for the copy @@ -437,7 +437,7 @@ export const deleteSelectedElements = function () { export const copySelectedElements = function () { const selectedElements = elementContext_.getSelectedElements(); const data = - JSON.stringify(selectedElements.map((x) => elementContext_.getJsonFromSvgElement(x))); + JSON.stringify(selectedElements.map((x) => elementContext_.getJsonFromSvgElement(x))); // Use sessionStorage for the clipboard data. sessionStorage.setItem(elementContext_.getClipboardID(), data); elementContext_.flashStorage(); @@ -461,15 +461,15 @@ export const groupSelectedElements = function (type, urlArg) { let url; switch (type) { - case 'a': { - cmdStr = 'Make hyperlink'; - url = urlArg || ''; - break; - } default: { - type = 'g'; - cmdStr = 'Group Elements'; - break; - } + case 'a': { + cmdStr = 'Make hyperlink'; + url = urlArg || ''; + break; + } default: { + type = 'g'; + cmdStr = 'Group Elements'; + break; + } } const batchCmd = new BatchCommand(cmdStr); @@ -713,6 +713,7 @@ export const convertToGroup = function (elem) { const $elem = elem; const batchCmd = new BatchCommand(); let ts; + const dataStorage = elementContext_.getDataStorage(); if (dataStorage.has($elem, 'gsvg')) { // Use the gsvg as the new group const svg = elem.firstChild; @@ -799,7 +800,7 @@ export const convertToGroup = function (elem) { if (parent) { if (!hasMore) { // remove symbol/svg element - const {nextSibling} = elem; + const { nextSibling } = elem; elem.remove(); batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); } @@ -820,13 +821,13 @@ export const convertToGroup = function (elem) { try { recalculateDimensions(n); } catch (e) { - console.log(e); + console.log(e); } }); // Give ID for any visible element missing one const visElems = g.querySelectorAll(elementContext_.getVisElems()); - Array.prototype.forEach.call(visElems, function(el, i){ + Array.prototype.forEach.call(visElems, function (el, i) { if (!el.id) { el.id = elementContext_.getNextId(); } }); @@ -839,7 +840,7 @@ export const convertToGroup = function (elem) { elementContext_.addCommandToHistory(batchCmd); } else { - console.log('Unexpected element to ungroup:', elem); + console.log('Unexpected element to ungroup:', elem); } }; @@ -851,6 +852,7 @@ export const convertToGroup = function (elem) { */ export const ungroupSelectedElement = function () { const selectedElements = elementContext_.getSelectedElements(); + const dataStorage = elementContext_.getDataStorage(); let g = selectedElements[0]; if (!g) { return; @@ -891,7 +893,7 @@ export const ungroupSelectedElement = function () { // Remove child title elements if (elem.tagName === 'title') { - const {nextSibling} = elem; + const { nextSibling } = elem; batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent)); elem.remove(); continue; @@ -979,9 +981,9 @@ export const updateCanvas = function (w, h) { /** * @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated} */ - {new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY} + { new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY } ); - return {x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY}; + return { x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY }; }; /** * Select the next/previous element within the current layer. diff --git a/src/svgcanvas/selection.js b/src/svgcanvas/selection.js index f199c5a7..8d87ab98 100644 --- a/src/svgcanvas/selection.js +++ b/src/svgcanvas/selection.js @@ -6,19 +6,19 @@ * @copyright 2011 Jeff Schiller */ -import {NS} from '../common/namespaces.js'; +import { NS } from '../common/namespaces.js'; import { isNullish, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible } from './utilities.js'; -import {transformPoint, transformListToTransform, rectsIntersect} from './math.js'; +import { transformPoint, transformListToTransform, rectsIntersect } from './math.js'; import jQueryPluginSVG from './jQuery.attr.js'; import { getTransformList } from './svgtransformlist.js'; import * as hstry from './history.js'; -import {getClosest} from '../editor/components/jgraduate/Util.js'; +import { getClosest } from '../editor/components/jgraduate/Util.js'; -const {BatchCommand} = hstry; +const { BatchCommand } = hstry; const $ = jQueryPluginSVG(jQuery); let selectionContext_ = null; @@ -140,7 +140,7 @@ export const getMouseTargetMethod = function (evt) { // for foreign content, go up until we find the foreignObject // WebKit browsers set the mouse target to the svgcanvas div if ([NS.MATH, NS.HTML].includes(mouseTarget.namespaceURI) && -mouseTarget.id !== 'svgcanvas' + mouseTarget.id !== 'svgcanvas' ) { while (mouseTarget.nodeName !== 'foreignObject') { mouseTarget = mouseTarget.parentNode; @@ -243,9 +243,9 @@ export const getVisibleElementsAndBBoxes = function (parent) { } const contentElems = []; const elements = parent.children; - Array.prototype.forEach.call(elements, function(elem, i){ + Array.prototype.forEach.call(elements, function (elem, i) { if (elem.getBBox) { - contentElems.push({elem, bbox: getStrokedBBoxDefaultVisible([elem])}); + contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([elem]) }); } }); return contentElems.reverse(); @@ -324,6 +324,7 @@ export const getIntersectionListMethod = function (rect) { * @returns {void} */ export const groupSvgElem = function (elem) { + const dataStorage = selectionContext_.getDataStorage(); const g = document.createElementNS(NS.SVG, 'g'); elem.replaceWith(g); g.appendChild(elem); diff --git a/src/svgcanvas/svg-exec.js b/src/svgcanvas/svg-exec.js index 5672632d..aecf7a5c 100644 --- a/src/svgcanvas/svg-exec.js +++ b/src/svgcanvas/svg-exec.js @@ -6,7 +6,7 @@ * @copyright 2011 Jeff Schiller */ -import {jsPDF} from 'jspdf/dist/jspdf.es.min.js'; +import { jsPDF } from 'jspdf/dist/jspdf.es.min.js'; import 'svg2pdf.js/dist/svg2pdf.es.js'; import jQueryPluginSVG from './jQuery.attr.js'; import * as hstry from './history.js'; @@ -18,18 +18,18 @@ import { import { transformPoint, transformListToTransform } from './math.js'; -import {resetListMap} from './svgtransformlist.js'; +import { resetListMap } from './svgtransformlist.js'; import { convertUnit, shortFloat, convertToNum } from '../common/units.js'; -import {isGecko, isChrome, isWebkit} from '../common/browser.js'; +import { isGecko, isChrome, isWebkit } from '../common/browser.js'; import * as pathModule from './path.js'; -import {NS} from '../common/namespaces.js'; +import { NS } from '../common/namespaces.js'; import * as draw from './draw.js'; import { recalculateDimensions } from './recalculate.js'; -import {getParents, getClosest} from '../editor/components/jgraduate/Util.js'; +import { getParents, getClosest } from '../editor/components/jgraduate/Util.js'; const { InsertElementCommand, RemoveElementCommand, @@ -59,13 +59,13 @@ export const init = function (svgContext) { */ export const svgCanvasToString = function () { // keep calling it until there are none to remove - while (svgContext_.getCanvas().removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty + while (svgContext_.getCanvas().removeUnusedDefElems() > 0) { } // eslint-disable-line no-empty svgContext_.getCanvas().pathActions.clear(true); // Keep SVG-Edit comment on top const childNodesElems = svgContext_.getSVGContent().childNodes; - childNodesElems.forEach(function(node, i){ + childNodesElems.forEach(function (node, i) { if (i && node.nodeType === 8 && node.data.includes('Created with')) { svgContext_.getSVGContent().firstChild.before(node); } @@ -81,7 +81,7 @@ export const svgCanvasToString = function () { // Unwrap gsvg if it has no special attributes (only id and style) const gsvgElems = svgContext_.getSVGContent().querySelectorAll('g[data-gsvg]'); - Array.prototype.forEach.call(gsvgElems, function(element, i){ + Array.prototype.forEach.call(gsvgElems, function (element, i) { const attrs = element.attributes; let len = attrs.length; for (let i = 0; i < len; i++) { @@ -100,7 +100,7 @@ export const svgCanvasToString = function () { // Rewrap gsvg if (nakedSvgs.length) { - Array.prototype.forEach.call(nakedSvgs, function(el, i){ + Array.prototype.forEach.call(nakedSvgs, function (el, i) { svgContext_.getCanvas().groupSvgElem(el); }); } @@ -162,7 +162,7 @@ export const svgToString = function (elem, indent) { const csElements = elem.querySelectorAll('*'); const cElements = Array.prototype.slice.call(csElements); cElements.push(elem); - Array.prototype.forEach.call(cElements, function(el, i){ + Array.prototype.forEach.call(cElements, function (el, i) { // const el = this; // for some elements have no attribute const uri = el.namespaceURI; @@ -170,7 +170,7 @@ export const svgToString = function (elem, indent) { nsuris[uri] = true; out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"'); } - if(el.attributes.length > 0) { + if (el.attributes.length > 0) { for (const [i, attr] of Object.entries(el.attributes)) { const u = attr.namespaceURI; if (u && !nsuris[u] && nsMap[u] !== 'xmlns' && nsMap[u] !== 'xml') { @@ -224,10 +224,10 @@ export const svgToString = function (elem, indent) { // Embed images when saving if (svgContext_.getSvgOptionApply() && - elem.nodeName === 'image' && - attr.localName === 'href' && - svgContext_.getSvgOptionImages() && - svgContext_.getSvgOptionImages() === 'embed' + elem.nodeName === 'image' && + attr.localName === 'href' && + svgContext_.getSvgOptionImages() && + svgContext_.getSvgOptionImages() === 'embed' ) { const img = svgContext_.getEncodableImages(attrVal); if (img) { attrVal = img; } @@ -251,31 +251,31 @@ export const svgToString = function (elem, indent) { for (let i = 0; i < childs.length; i++) { const child = childs.item(i); switch (child.nodeType) { - case 1: // element node - out.push('\n'); - out.push(this.svgToString(child, indent)); - break; - case 3: { // text node - const str = child.nodeValue.replace(/^\s+|\s+$/g, ''); - if (str !== '') { - bOneLine = true; - out.push(String(toXml(str))); - } - break; - } case 4: // cdata node - out.push('\n'); - out.push(new Array(indent + 1).join(' ')); - out.push(''); - out.push(child.nodeValue); - out.push(''); - break; - case 8: // comment - out.push('\n'); - out.push(new Array(indent + 1).join(' ')); - out.push(''); - break; + case 1: // element node + out.push('\n'); + out.push(this.svgToString(child, indent)); + break; + case 3: { // text node + const str = child.nodeValue.replace(/^\s+|\s+$/g, ''); + if (str !== '') { + bOneLine = true; + out.push(String(toXml(str))); + } + break; + } case 4: // cdata node + out.push('\n'); + out.push(new Array(indent + 1).join(' ')); + out.push(''); + out.push(child.nodeValue); + out.push(''); + break; + case 8: // comment + out.push('\n'); + out.push(new Array(indent + 1).join(' ')); + out.push(''); + break; } // switch on node type } indent--; @@ -306,11 +306,12 @@ export const svgToString = function (elem, indent) { */ export const setSvgString = function (xmlString, preventUndo) { const curConfig = svgContext_.getCurConfig(); + const dataStorage = svgContext_.getDataStorage(); try { // convert string into XML document const newDoc = text2xml(xmlString); if (newDoc.firstElementChild && - newDoc.firstElementChild.namespaceURI !== NS.SVG) { + newDoc.firstElementChild.namespaceURI !== NS.SVG) { return false; } @@ -319,7 +320,7 @@ export const setSvgString = function (xmlString, preventUndo) { const batchCmd = new BatchCommand('Change Source'); // remove old svg document - const {nextSibling} = svgContext_.getSVGContent(); + const { nextSibling } = svgContext_.getSVGContent(); svgContext_.getSVGContent().remove(); const oldzoom = svgContext_.getSVGContent(); @@ -348,7 +349,7 @@ export const setSvgString = function (xmlString, preventUndo) { // change image href vals if possible const elements = content.querySelectorAll('image'); - Array.prototype.forEach.call(elements, function(image, i){ + Array.prototype.forEach.call(elements, function (image, i) { preventClickDefault(image); const val = svgContext_.getCanvas().getHref(this); if (val) { @@ -373,7 +374,7 @@ export const setSvgString = function (xmlString, preventUndo) { // Wrap child SVGs in group elements const svgElements = content.querySelectorAll('svg'); - Array.prototype.forEach.call(svgElements, function(element, i){ + Array.prototype.forEach.call(svgElements, function (element, i) { // Skip if it's in a if (getClosest(element.parentNode, 'defs')) { return; } @@ -393,7 +394,7 @@ export const setSvgString = function (xmlString, preventUndo) { if (isGecko()) { const svgDefs = findDefs(); const findElems = content.querySelectorAll('linearGradient, radialGradient, pattern'); - Array.prototype.forEach.call(findElems, function(ele, i){ + Array.prototype.forEach.call(findElems, function (ele, i) { svgDefs.appendChild(ele); }); } @@ -420,7 +421,7 @@ export const setSvgString = function (xmlString, preventUndo) { attrs.height = vb[3]; // handle content that doesn't have a viewBox } else { - ['width', 'height'].forEach(function(dim, i){ + ['width', 'height'].forEach(function (dim, i) { // Set to 100 if not given const val = content.getAttribute(dim) || '100%'; if (String(val).substr(-1) === '%') { @@ -437,9 +438,9 @@ export const setSvgString = function (xmlString, preventUndo) { // Give ID for any visible layer children missing one const chiElems = content.children; - Array.prototype.forEach.call(chiElems, function(chiElem, i){ + Array.prototype.forEach.call(chiElems, function (chiElem, i) { const visElems = chiElem.querySelectorAll(svgContext_.getVisElems()); - Array.prototype.forEach.call(visElems, function(elem, i){ + Array.prototype.forEach.call(visElems, function (elem, i) { if (!elem.id) { elem.id = svgContext_.getCanvas().getNextId(); } }); }); @@ -466,7 +467,7 @@ export const setSvgString = function (xmlString, preventUndo) { // update root to the correct size const width = content.getAttribute('width'); const height = content.getAttribute('height'); - const changes = {width: width, height: height}; + const changes = { width: width, height: height }; batchCmd.addSubCommand(new ChangeElementCommand(svgContext_.getSVGRoot(), changes)); // reset zoom @@ -481,7 +482,7 @@ export const setSvgString = function (xmlString, preventUndo) { if (!preventUndo) svgContext_.addCommandToHistory(batchCmd); svgContext_.call('changed', [svgContext_.getSVGContent()]); } catch (e) { - console.log(e); + console.log(e); return false; } @@ -503,7 +504,7 @@ export const setSvgString = function (xmlString, preventUndo) { * was obtained */ export const importSvgString = function (xmlString) { - console.log('importSvgString --> ', xmlString); + const dataStorage = svgContext_.getDataStorage(); let j, ts, useEl; try { // Get unique ID @@ -513,7 +514,7 @@ export const importSvgString = function (xmlString) { // Look for symbol and make sure symbol exists in image if (svgContext_.getImportIds(uid) && svgContext_.getImportIds(uid).symbol) { const parents = getParents(svgContext_.getImportIds(uid).symbol, '#svgroot'); - if(parents.length){ + if (parents.length) { useExisting = true; } } @@ -521,7 +522,7 @@ export const importSvgString = function (xmlString) { const batchCmd = new BatchCommand('Import Image'); let symbol; if (useExisting) { - ({symbol} = svgContext_.getImportIds()); + ({ symbol } = svgContext_.getImportIds()); ts = svgContext_.getImportIds(uid).xform; } else { // convert string into XML document @@ -564,7 +565,7 @@ export const importSvgString = function (xmlString) { // https://bugzilla.mozilla.org/show_bug.cgi?id=353575 // TODO: Make this properly undo-able. const elements = svg.querySelectorAll('linearGradient, radialGradient, pattern'); - Array.prototype.forEach.call(elements, function(el, i){ + Array.prototype.forEach.call(elements, function (el, i) { defs.appendChild(el); }); } @@ -610,7 +611,7 @@ export const importSvgString = function (xmlString) { svgContext_.addCommandToHistory(batchCmd); svgContext_.call('changed', [svgContext_.getSVGContent()]); } catch (e) { - console.log(e); + console.log(e); return null; } @@ -689,7 +690,7 @@ export const save = function (opts) { * Codes only is useful for locale-independent detection. * @returns {module:svgcanvas.IssuesAndCodes} */ -function getIssues () { +function getIssues() { const uiStrings = svgContext_.getUIStrings(); // remove the selected outline before serializing svgContext_.getCanvas().clearSelection(); @@ -717,7 +718,7 @@ function getIssues () { issues.push(descr); } } - return {issues, issueCodes}; + return { issues, issueCodes }; } /** * @typedef {PlainObject} module:svgcanvas.ImageExportedResults @@ -749,7 +750,7 @@ function getIssues () { export const rasterExport = async function (imgType, quality, exportWindowName, opts = {}) { const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG'); const mimeType = 'image/' + type.toLowerCase(); - const {issues, issueCodes} = getIssues(); + const { issues, issueCodes } = getIssues(); const svg = this.svgCanvasToString(); if (!$id('export_canvas')) { @@ -777,7 +778,7 @@ export const rasterExport = async function (imgType, quality, exportWindowName, * Called when `bloburl` is available for export. * @returns {void} */ - function done () { + function done() { const obj = { datauri, bloburl, svg, issues, issueCodes, type: imgType, mimeType, quality, exportWindowName @@ -854,17 +855,17 @@ export const exportPDF = async ( keywords: '', creator: '' */ }); - const {issues, issueCodes} = getIssues(); + const { issues, issueCodes } = getIssues(); // const svg = this.svgCanvasToString(); // await doc.addSvgAsImage(svg) - await doc.svg(svgContext_.getSVGContent(), {x: 0, y: 0, width: res.w, height: res.h}); + await doc.svg(svgContext_.getSVGContent(), { x: 0, y: 0, width: res.w, height: res.h }); // doc.output('save'); // Works to open in a new // window; todo: configure this and other export // options to optionally work in this manner as // opposed to opening a new tab outputType = outputType || 'dataurlstring'; - const obj = {issues, issueCodes, exportWindowName, outputType}; + const obj = { issues, issueCodes, exportWindowName, outputType }; obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined); svgContext_.call('exportedPDF', obj); return obj; @@ -895,7 +896,7 @@ export const uniquifyElemsMethod = function (g) { // and we haven't tracked this ID yet if (!(n.id in ids)) { // add this id to our map - ids[n.id] = {elem: null, attrs: [], hrefs: []}; + ids[n.id] = { elem: null, attrs: [], hrefs: [] }; } ids[n.id].elem = n; } @@ -911,7 +912,7 @@ export const uniquifyElemsMethod = function (g) { if (refid) { if (!(refid in ids)) { // add this id to our map - ids[refid] = {elem: null, attrs: [], hrefs: []}; + ids[refid] = { elem: null, attrs: [], hrefs: [] }; } ids[refid].attrs.push(attrnode); } @@ -926,7 +927,7 @@ export const uniquifyElemsMethod = function (g) { if (refid) { if (!(refid in ids)) { // add this id to our map - ids[refid] = {elem: null, attrs: [], hrefs: []}; + ids[refid] = { elem: null, attrs: [], hrefs: [] }; } ids[refid].hrefs.push(n); } @@ -937,7 +938,7 @@ export const uniquifyElemsMethod = function (g) { // in ids, we now have a map of ids, elements and attributes, let's re-identify for (const oldid in ids) { if (!oldid) { continue; } - const {elem} = ids[oldid]; + const { elem } = ids[oldid]; if (elem) { const newid = svgContext_.getCanvas().getNextId(); @@ -945,7 +946,7 @@ export const uniquifyElemsMethod = function (g) { elem.id = newid; // remap all url() attributes - const {attrs} = ids[oldid]; + const { attrs } = ids[oldid]; let j = attrs.length; while (j--) { const attr = attrs[j]; @@ -977,7 +978,8 @@ export const setUseDataMethod = function (parent) { elems = elems.querySelectorAll('use'); } - Array.prototype.forEach.call(elems, function(el, _){ + Array.prototype.forEach.call(elems, function (el, _) { + const dataStorage = svgContext_.getDataStorage(); const id = svgContext_.getCanvas().getHref(el).substr(1); const refElem = svgContext_.getCanvas().getElem(id); if (!refElem) { return; } @@ -1026,12 +1028,12 @@ export const removeUnusedDefElemsMethod = function () { } } - Array.prototype.forEach.call(defs, function(def, i){ + Array.prototype.forEach.call(defs, function (def, i) { const defelems = def.querySelectorAll('linearGradient, radialGradient, filter, marker, svg, symbol'); i = defelems.length; while (i--) { const defelem = defelems[i]; - const {id} = defelem; + const { id } = defelem; if (!defelemUses.includes(id)) { // Not found, so remove (but remember) svgContext_.setRemovedElements(id, defelem); @@ -1058,7 +1060,7 @@ export const convertGradientsMethod = function (elem) { }); } - Array.prototype.forEach.call(elems, function(grad, i){ + Array.prototype.forEach.call(elems, function (grad, i) { if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') { const svgcontent = svgContext_.getSVGContent(); // TODO: Support more than one element with this ref by duplicating parent grad diff --git a/src/svgcanvas/svgcanvas.js b/src/svgcanvas/svgcanvas.js index 65e18b7f..2463dba2 100644 --- a/src/svgcanvas/svgcanvas.js +++ b/src/svgcanvas/svgcanvas.js @@ -9,7 +9,7 @@ * */ -import {Canvg as canvg} from 'canvg'; +import { Canvg as canvg } from 'canvg'; import 'pathseg'; import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` @@ -28,7 +28,7 @@ import { setLayerVisibility, moveSelectedToLayer, mergeLayer, mergeAllLayers, leaveContext, setContext } from './draw.js'; -import {svgRootElement} from './svgroot.js'; +import { svgRootElement } from './svgroot.js'; import { init as undoInit, getUndoManager, changeSelectedAttributeNoUndoMethod, changeSelectedAttributeMethod, ffClone @@ -45,7 +45,7 @@ import { init as eventInit, mouseMoveEvent, mouseUpEvent, dblClickEvent, mouseDownEvent, DOMMouseScrollEvent } from './event.js'; -import {init as jsonInit, getJsonFromSvgElements, addSVGElementsFromJson} from './json.js'; +import { init as jsonInit, getJsonFromSvgElements, addSVGElementsFromJson } from './json.js'; import { init as elemInit, getResolutionMethod, getTitleMethod, setGroupTitleMethod, setDocumentTitleMethod, setResolutionMethod, getEditorNSMethod, setBBoxZoomMethod, @@ -65,8 +65,8 @@ import { import { init as blurInit, setBlurNoUndo, setBlurOffsets, setBlur } from './blur-event.js'; -import {sanitizeSvg} from './sanitize.js'; -import {getReverseNS, NS} from '../common/namespaces.js'; +import { sanitizeSvg } from './sanitize.js'; +import { getReverseNS, NS } from '../common/namespaces.js'; import { text2xml, assignAttributes, cleanupElement, getElem, getUrlFromAttr, findDefs, getHref, setHref, getRefElem, getRotationAngle, getPathBBox, @@ -168,8 +168,8 @@ class SvgCanvas { * @param {HTMLElement} container - The container HTML element that should hold the SVG root element * @param {module:SVGeditor.configObj.curConfig} config - An object that contains configuration data */ - constructor (container, config) { - // Alias Namespace constants + constructor(container, config) { + // Alias Namespace constants // Default configuration options const curConfig = { @@ -184,7 +184,7 @@ class SvgCanvas { } // Array with width/height of canvas - const {dimensions} = curConfig; + const { dimensions } = curConfig; const canvas = this; @@ -193,6 +193,33 @@ class SvgCanvas { this.$qa = $qa; this.getClosest = getClosest; this.getParents = getParents; + /** A storage solution aimed at replacing jQuerys data function. + * Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap]. + * This makes sure the data is garbage collected when the node is removed. + */ + this.dataStorage = { + _storage: new WeakMap(), + put: function (element, key, obj) { + if (!this._storage.has(element)) { + this._storage.set(element, new Map()); + } + this._storage.get(element).set(key, obj); + }, + get: function (element, key) { + return this._storage.get(element).get(key); + }, + has: function (element, key) { + return this._storage.has(element) && this._storage.get(element).has(key); + }, + remove: function (element, key) { + var ret = this._storage.get(element).delete(key); + if (!this._storage.get(element).size === 0) { + this._storage.delete(element); + } + return ret; + } + }; + const getDataStorage = this.getDataStorage = function () { return canvas.dataStorage; }; this.isLayer = draw.Layer.isLayer; @@ -229,10 +256,10 @@ class SvgCanvas { */ { getSVGContent, - getDOMDocument () { return svgdoc; }, - getDOMContainer () { return container; }, + getDOMDocument() { return svgdoc; }, + getDOMContainer() { return container; }, getSVGRoot, - getCurConfig () { return curConfig; } + getCurConfig() { return curConfig; } } ); /** @@ -318,10 +345,10 @@ class SvgCanvas { * @implements {module:json.jsonContext} */ { - getDOMDocument () { return svgdoc; }, - getDrawing () { return getCurrentDrawing(); }, - getCurShape () { return curShape; }, - getCurrentGroup () { return currentGroup; } + getDOMDocument() { return svgdoc; }, + getDrawing() { return getCurrentDrawing(); }, + getCurShape() { return curShape; }, + getCurrentGroup() { return currentGroup; } } ); @@ -367,9 +394,9 @@ class SvgCanvas { { getBaseUnit, getElement: getElem, - getHeight () { return svgcontent.getAttribute('height') / currentZoom; }, - getWidth () { return svgcontent.getAttribute('width') / currentZoom; }, - getRoundDigits () { return saveOptions.round_digits; } + getHeight() { return svgcontent.getAttribute('height') / currentZoom; }, + getWidth() { return svgcontent.getAttribute('width') / currentZoom; }, + getRoundDigits() { return saveOptions.round_digits; } } ); @@ -391,7 +418,7 @@ class SvgCanvas { selectedElements = []; }; - const {pathActions} = pathModule; + const { pathActions } = pathModule; /** * This should actually be an intersection as all interfaces should be met. @@ -407,12 +434,13 @@ class SvgCanvas { getSVGContent, addSVGElementFromJson, getSelectedElements, - getDOMDocument () { return svgdoc; }, - getDOMContainer () { return container; }, + getDOMDocument() { return svgdoc; }, + getDOMContainer() { return container; }, getSVGRoot, // TODO: replace this mostly with a way to get the current drawing. getBaseUnit, - getSnappingStep () { return curConfig.snappingStep; } + getSnappingStep() { return curConfig.snappingStep; }, + getDataStorage } ); @@ -439,7 +467,8 @@ class SvgCanvas { * @implements {module:coords.EditorContext} */ { - getDrawing () { return getCurrentDrawing(); }, + getDrawing() { return getCurrentDrawing(); }, + getDataStorage, getSVGRoot, getGridSnapping } @@ -452,8 +481,9 @@ class SvgCanvas { */ { getSVGRoot, - getStartTransform () { return startTransform; }, - setStartTransform (transform) { startTransform = transform; } + getStartTransform() { return startTransform; }, + setStartTransform(transform) { startTransform = transform; }, + getDataStorage } ); this.recalculateDimensions = recalculateDimensions; @@ -485,9 +515,10 @@ class SvgCanvas { * @implements {module:select.SVGFactory} */ { - createSVGElement (jsonMap) { return canvas.addSVGElementFromJson(jsonMap); }, - svgRoot () { return svgroot; }, - svgContent () { return svgcontent; }, + createSVGElement(jsonMap) { return canvas.addSVGElementFromJson(jsonMap); }, + svgRoot() { return svgroot; }, + svgContent() { return svgcontent; }, + getDataStorage, getCurrentZoom } ); @@ -529,7 +560,7 @@ class SvgCanvas { const restoreRefElems = function (elem) { // Look for missing reference elements, restore any found let attrs = {}; - refAttrs.forEach(function(item, _){ + refAttrs.forEach(function (item, _) { attrs[item] = elem.getAttribute(item); }); Object.values(attrs).forEach((val) => { @@ -560,8 +591,8 @@ class SvgCanvas { call, restoreRefElems, getSVGContent, - getCanvas () { return canvas; }, - getCurrentMode () { return currentMode; }, + getCanvas() { return canvas; }, + getCurrentMode() { return currentMode; }, getCurrentZoom, getSVGRoot, getSelectedElements @@ -588,21 +619,22 @@ class SvgCanvas { * @implements {module:selection.selectionContext} */ { - getCanvas () { return canvas; }, - getCurrentGroup () { return currentGroup; }, + getCanvas() { return canvas; }, + getDataStorage, + getCurrentGroup() { return currentGroup; }, getSelectedElements, getSVGRoot, getSVGContent, - getDOMContainer () { return container; }, - getExtensions () { return extensions; }, - setExtensions (key, value) { extensions[key] = value; }, + getDOMContainer() { return container; }, + getExtensions() { return extensions; }, + setExtensions(key, value) { extensions[key] = value; }, getCurrentZoom, - getRubberBox () { return rubberBox; }, - setCurBBoxes (value) { curBBoxes = value; }, - getCurBBoxes (value) { return curBBoxes; }, - getCurrentResizeMode () { return currentResizeMode; }, + getRubberBox() { return rubberBox; }, + setCurBBoxes(value) { curBBoxes = value; }, + getCurBBoxes(value) { return curBBoxes; }, + getCurrentResizeMode() { return currentResizeMode; }, addCommandToHistory, - getSelector () { return Selector; } + getSelector() { return Selector; } } ); @@ -645,7 +677,7 @@ class SvgCanvas { /** * @type {module:path.EditorContext#resetD} */ - function resetD (p) { + function resetD(p) { if (typeof pathActions.convertPath === 'function') { p.setAttribute('d', pathActions.convertPath(p)); } else if (typeof pathActions.convertPaths === 'function') { @@ -670,16 +702,16 @@ class SvgCanvas { getGridSnapping, getOpacity, getSelectedElements, - getContainer () { + getContainer() { return container; }, - setStarted (s) { + setStarted(s) { started = s; }, - getRubberBox () { + getRubberBox() { return rubberBox; }, - setRubberBox (rb) { + setRubberBox(rb) { rubberBox = rb; return rubberBox; }, @@ -691,11 +723,11 @@ class SvgCanvas { * @fires module:svgcanvas.SvgCanvas#event:selected * @returns {void} */ - addPtsToSelection ({closedSubpath, grips}) { + addPtsToSelection({ closedSubpath, grips }) { // TODO: Correct this: pathActions.canDeleteNodes = true; pathActions.closed_subpath = closedSubpath; - call('pointsAdded', {closedSubpath, grips}); + call('pointsAdded', { closedSubpath, grips }); call('selected', grips); }, /** @@ -705,7 +737,7 @@ class SvgCanvas { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ - endChanges ({cmd, elem}) { + endChanges({ cmd, elem }) { addCommandToHistory(cmd); call('changed', [elem]); }, @@ -713,17 +745,17 @@ class SvgCanvas { getId, getNextId, getMouseTarget, - getCurrentMode () { + getCurrentMode() { return currentMode; }, - setCurrentMode (cm) { + setCurrentMode(cm) { currentMode = cm; return currentMode; }, - getDrawnPath () { + getDrawnPath() { return drawnPath; }, - setDrawnPath (dp) { + setDrawnPath(dp) { drawnPath = dp; return drawnPath; }, @@ -765,7 +797,7 @@ class SvgCanvas { /** * @type {module:svgcanvas.SaveOptions} */ - saveOptions = {round_digits: 5}, + saveOptions = { round_digits: 5 }, // Object with IDs for imported files, to see if one was already added importIds = {}, @@ -839,7 +871,7 @@ class SvgCanvas { * if extension of supplied name already exists * @returns {Promise} Resolves to `undefined` */ - this.addExtension = async function (name, extInitFunc, {$: jq, importLocale}) { + this.addExtension = async function (name, extInitFunc, { $: jq, importLocale }) { if (typeof extInitFunc !== 'function') { throw new TypeError('Function argument expected for `svgcanvas.addExtension`'); } @@ -1076,7 +1108,7 @@ class SvgCanvas { * @returns {void} */ const logMatrix = function (m) { - console.log([m.a, m.b, m.c, m.d, m.e, m.f]); + console.log([m.a, m.b, m.c, m.d, m.e, m.f]); }; // Root Current Transformation Matrix in user units @@ -1140,7 +1172,7 @@ class SvgCanvas { const currentLayer = getCurrentDrawing().getCurrentLayer(); if (currentLayer) { currentMode = 'select'; - if(currentGroup){ + if (currentGroup) { selectOnly(currentGroup.children); } else { selectOnly(currentLayer.children); @@ -1167,12 +1199,12 @@ class SvgCanvas { let rStartY = null; let initBbox = {}; let sumDistance = 0; - const controllPoint2 = {x: 0, y: 0}; - const controllPoint1 = {x: 0, y: 0}; - let start = {x: 0, y: 0}; - const end = {x: 0, y: 0}; - let bSpline = {x: 0, y: 0}; - let nextPos = {x: 0, y: 0}; + const controllPoint2 = { x: 0, y: 0 }; + const controllPoint1 = { x: 0, y: 0 }; + let start = { x: 0, y: 0 }; + const end = { x: 0, y: 0 }; + let bSpline = { x: 0, y: 0 }; + let nextPos = { x: 0, y: 0 }; let parameter; let nextParameter; @@ -1181,73 +1213,74 @@ class SvgCanvas { * @returns {void} */ eventInit( - /** - * @implements {module:event.eventContext_} - */ + /** + * @implements {module:event.eventContext_} + */ { - getStarted () { return started; }, - getCanvas () { return canvas; }, - getCurConfig () { return curConfig; }, - getCurrentMode () { return currentMode; }, - getrootSctm () { return rootSctm; }, - getStartX () { return startX; }, - setStartX (value) { startX = value; }, - getStartY () { return startY; }, - setStartY (value) { startY = value; }, - getRStartX () { return rStartX; }, - getRStartY () { return rStartY; }, - getRubberBox () { return rubberBox; }, - getInitBbox () { return initBbox; }, - getCurrentResizeMode () { return currentResizeMode; }, - getCurrentGroup () { return currentGroup; }, - getDrawnPath () { return drawnPath; }, - getJustSelected () { return justSelected; }, - getOpacAni () { return opacAni; }, - getParameter () { return parameter; }, - getNextParameter () { return nextParameter; }, - getStepCount () { return STEP_COUNT; }, - getThreSholdDist () { return THRESHOLD_DIST; }, - getSumDistance () { return sumDistance; }, - getStart (key) { return start[key]; }, - getEnd (key) { return end[key]; }, - getbSpline (key) { return bSpline[key]; }, - getNextPos (key) { return nextPos[key]; }, - getControllPoint1 (key) { return controllPoint1[key]; }, - getControllPoint2 (key) { return controllPoint2[key]; }, - getFreehand (key) { return freehand[key]; }, - getDrawing () { return getCurrentDrawing(); }, - getCurShape () { return curShape; }, - getDAttr () { return dAttr; }, - getLastGoodImgUrl () { return lastGoodImgUrl; }, - getCurText (key) { return curText[key]; }, - setDAttr (value) { dAttr = value; }, - setEnd (key, value) { end[key] = value; }, - setControllPoint1 (key, value) { controllPoint1[key] = value; }, - setControllPoint2 (key, value) { controllPoint2[key] = value; }, - setJustSelected (value) { justSelected = value; }, - setParameter (value) { parameter = value; }, - setStart (value) { start = value; }, - setRStartX (value) { rStartX = value; }, - setRStartY (value) { rStartY = value; }, - setSumDistance (value) { sumDistance = value; }, - setbSpline (value) { bSpline = value; }, - setNextPos (value) { nextPos = value; }, - setNextParameter (value) { nextParameter = value; }, - setCurProperties (key, value) { curProperties[key] = value; }, - setCurText (key, value) { curText[key] = value; }, - setStarted (s) { started = s; }, - setStartTransform (transform) { startTransform = transform; }, - setCurrentMode (cm) { + getStarted() { return started; }, + getCanvas() { return canvas; }, + getDataStorage, + getCurConfig() { return curConfig; }, + getCurrentMode() { return currentMode; }, + getrootSctm() { return rootSctm; }, + getStartX() { return startX; }, + setStartX(value) { startX = value; }, + getStartY() { return startY; }, + setStartY(value) { startY = value; }, + getRStartX() { return rStartX; }, + getRStartY() { return rStartY; }, + getRubberBox() { return rubberBox; }, + getInitBbox() { return initBbox; }, + getCurrentResizeMode() { return currentResizeMode; }, + getCurrentGroup() { return currentGroup; }, + getDrawnPath() { return drawnPath; }, + getJustSelected() { return justSelected; }, + getOpacAni() { return opacAni; }, + getParameter() { return parameter; }, + getNextParameter() { return nextParameter; }, + getStepCount() { return STEP_COUNT; }, + getThreSholdDist() { return THRESHOLD_DIST; }, + getSumDistance() { return sumDistance; }, + getStart(key) { return start[key]; }, + getEnd(key) { return end[key]; }, + getbSpline(key) { return bSpline[key]; }, + getNextPos(key) { return nextPos[key]; }, + getControllPoint1(key) { return controllPoint1[key]; }, + getControllPoint2(key) { return controllPoint2[key]; }, + getFreehand(key) { return freehand[key]; }, + getDrawing() { return getCurrentDrawing(); }, + getCurShape() { return curShape; }, + getDAttr() { return dAttr; }, + getLastGoodImgUrl() { return lastGoodImgUrl; }, + getCurText(key) { return curText[key]; }, + setDAttr(value) { dAttr = value; }, + setEnd(key, value) { end[key] = value; }, + setControllPoint1(key, value) { controllPoint1[key] = value; }, + setControllPoint2(key, value) { controllPoint2[key] = value; }, + setJustSelected(value) { justSelected = value; }, + setParameter(value) { parameter = value; }, + setStart(value) { start = value; }, + setRStartX(value) { rStartX = value; }, + setRStartY(value) { rStartY = value; }, + setSumDistance(value) { sumDistance = value; }, + setbSpline(value) { bSpline = value; }, + setNextPos(value) { nextPos = value; }, + setNextParameter(value) { nextParameter = value; }, + setCurProperties(key, value) { curProperties[key] = value; }, + setCurText(key, value) { curText[key] = value; }, + setStarted(s) { started = s; }, + setStartTransform(transform) { startTransform = transform; }, + setCurrentMode(cm) { currentMode = cm; return currentMode; }, - setFreehand (key, value) { freehand[key] = value; }, - setCurBBoxes (value) { curBBoxes = value; }, - setRubberBox (value) { rubberBox = value; }, - setInitBbox (value) { initBbox = value; }, - setRootSctm (value) { rootSctm = value; }, - setCurrentResizeMode (value) { currentResizeMode = value; }, - setLastClickPoint (value) { lastClickPoint = value; }, + setFreehand(key, value) { freehand[key] = value; }, + setCurBBoxes(value) { curBBoxes = value; }, + setRubberBox(value) { rubberBox = value; }, + setInitBbox(value) { initBbox = value; }, + setRootSctm(value) { rootSctm = value; }, + setCurrentResizeMode(value) { currentResizeMode = value; }, + setLastClickPoint(value) { lastClickPoint = value; }, getSelectedElements, getCurrentZoom, getId, @@ -1324,14 +1357,14 @@ class SvgCanvas { * @implements {module:text-actions.textActionsContext} */ { - getCanvas () { return canvas; }, - getrootSctm () { return rootSctm; }, + getCanvas() { return canvas; }, + getrootSctm() { return rootSctm; }, getSelectedElements, getCurrentZoom, - getCurrentMode () { + getCurrentMode() { return currentMode; }, - setCurrentMode (cm) { + setCurrentMode(cm) { currentMode = cm; return currentMode; }, @@ -1347,34 +1380,35 @@ class SvgCanvas { */ svgInit( - /** - * @implements {module:elem-get-set.elemInit} - */ + /** + * @implements {module:elem-get-set.elemInit} + */ { - getCanvas () { return canvas; }, + getCanvas() { return canvas; }, + getDataStorage, getSVGContent, getSVGRoot, - getUIStrings () { return uiStrings; }, - getCurrentGroup () { return currentGroup; }, - getCurConfig () { return curConfig; }, - getNsMap () { return nsMap; }, - getSvgOption () { return saveOptions; }, - setSvgOption (key, value) { saveOptions[key] = value; }, - getSvgOptionApply () { return saveOptions.apply; }, - getSvgOptionImages () { return saveOptions.images; }, - getEncodableImages (key) { return encodableImages[key]; }, - setEncodableImages (key, value) { encodableImages[key] = value; }, + getUIStrings() { return uiStrings; }, + getCurrentGroup() { return currentGroup; }, + getCurConfig() { return curConfig; }, + getNsMap() { return nsMap; }, + getSvgOption() { return saveOptions; }, + setSvgOption(key, value) { saveOptions[key] = value; }, + getSvgOptionApply() { return saveOptions.apply; }, + getSvgOptionImages() { return saveOptions.images; }, + getEncodableImages(key) { return encodableImages[key]; }, + setEncodableImages(key, value) { encodableImages[key] = value; }, call, - getDOMDocument () { return svgdoc; }, - getVisElems () { return visElems; }, - getIdPrefix () { return idprefix; }, - setCurrentZoom (value) { currentZoom = value; }, - getImportIds (key) { return importIds[key]; }, - setImportIds (key, value) { importIds[key] = value; }, - setRemovedElements (key, value) { removedElements[key] = value; }, - setSVGContent (value) { svgcontent = value; }, - getrefAttrs () { return refAttrs; }, - getcanvg () { return canvg; }, + getDOMDocument() { return svgdoc; }, + getVisElems() { return visElems; }, + getIdPrefix() { return idprefix; }, + setCurrentZoom(value) { currentZoom = value; }, + getImportIds(key) { return importIds[key]; }, + setImportIds(key, value) { importIds[key] = value; }, + setRemovedElements(key, value) { removedElements[key] = value; }, + setSVGContent(value) { svgcontent = value; }, + getrefAttrs() { return refAttrs; }, + getcanvg() { return canvg; }, addCommandToHistory } ); @@ -1619,10 +1653,11 @@ class SvgCanvas { */ { pathActions, - getCurrentGroup () { + getDataStorage, + getCurrentGroup() { return currentGroup; }, - setCurrentGroup (cg) { + setCurrentGroup(cg) { currentGroup = cg; }, getSelectedElements, @@ -1636,7 +1671,7 @@ class SvgCanvas { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ - changeSVGContent () { + changeSVGContent() { call('changed', [svgcontent]); } } @@ -1704,15 +1739,16 @@ class SvgCanvas { getSelectedElements, call, changeSelectedAttributeNoUndoMethod, - getDOMDocument () { return svgdoc; }, - getCanvas () { return canvas; }, - setCanvas (key, value) { canvas[key] = value; }, - setCurrentZoom (value) { currentZoom = value; }, - setCurProperties (key, value) { curProperties[key] = value; }, - getCurProperties (key) { return curProperties[key]; }, - setCurShape (key, value) { curShape[key] = value; }, - getCurText (key) { return curText[key]; }, - setCurText (key, value) { curText[key] = value; } + getDOMDocument() { return svgdoc; }, + getCanvas() { return canvas; }, + getDataStorage, + setCanvas(key, value) { canvas[key] = value; }, + setCurrentZoom(value) { currentZoom = value; }, + setCurProperties(key, value) { curProperties[key] = value; }, + getCurProperties(key) { return curProperties[key]; }, + setCurShape(key, value) { curShape[key] = value; }, + getCurText(key) { return curText[key]; }, + setCurText(key, value) { curText[key] = value; } } ); @@ -1830,7 +1866,7 @@ class SvgCanvas { * position in the editor's canvas. */ this.getOffset = function () { - return {x: svgcontent.getAttribute('x'), y: svgcontent.getAttribute('y')}; + return { x: svgcontent.getAttribute('x'), y: svgcontent.getAttribute('y') }; }; /** @@ -2153,13 +2189,13 @@ class SvgCanvas { * @implements {module:elem-get-set.elemInit} */ { - getCanvas () { return canvas; }, - getCurCommand () { return curCommand; }, - setCurCommand (value) { curCommand = value; }, - getFilter () { return filter; }, - setFilter (value) { filter = value; }, - getFilterHidden () { return filterHidden; }, - setFilterHidden (value) { filterHidden = value; }, + getCanvas() { return canvas; }, + getCurCommand() { return curCommand; }, + setCurCommand(value) { curCommand = value; }, + getFilter() { return filter; }, + setFilter(value) { filter = value; }, + getFilterHidden() { return filterHidden; }, + setFilterHidden(value) { filterHidden = value; }, changeSelectedAttributeNoUndoMethod, changeSelectedAttributeMethod, isWebkit, @@ -2424,17 +2460,17 @@ class SvgCanvas { flashStorage, call, getIntersectionList, - setCurBBoxes (value) { curBBoxes = value; }, + setCurBBoxes(value) { curBBoxes = value; }, getSVGRoot, - gettingSelectorManager () { return selectorManager; }, + gettingSelectorManager() { return selectorManager; }, getCurrentZoom, - getDrawing () { return getCurrentDrawing(); }, - getCurrentGroup () { return currentGroup; }, + getDrawing() { return getCurrentDrawing(); }, + getCurrentGroup() { return currentGroup; }, addToSelection, - getContentW () { return canvas.contentW; }, - getContentH () { return canvas.contentH; }, - getClipboardID () { return CLIPBOARD_ID; }, - getDOMDocument () { return svgdoc; }, + getContentW() { return canvas.contentW; }, + getContentH() { return canvas.contentH; }, + getClipboardID() { return CLIPBOARD_ID; }, + getDOMDocument() { return svgdoc; }, clearSelection, getNextId, selectOnly, @@ -2442,8 +2478,9 @@ class SvgCanvas { setUseData, convertGradients, getSVGContent, - getCanvas () { return canvas; }, - getVisElems () { return visElems; } + getCanvas() { return canvas; }, + getDataStorage, + getVisElems() { return visElems; } } ); @@ -2473,7 +2510,7 @@ class SvgCanvas { * Flash the clipboard data momentarily on localStorage so all tabs can see. * @returns {void} */ - function flashStorage () { + function flashStorage() { const data = sessionStorage.getItem(CLIPBOARD_ID); localStorage.setItem(CLIPBOARD_ID, data); setTimeout(function () { @@ -2486,7 +2523,7 @@ class SvgCanvas { * @param {!Event} ev Storage event. * @returns {void} */ - function storageChange (ev) { + function storageChange(ev) { if (!ev.newValue) return; // This is a call from removeItem. if (ev.key === CLIPBOARD_ID + '_startup') { // Another tab asked for our sessionStorage. @@ -2515,13 +2552,13 @@ class SvgCanvas { * paste element functionality */ pasteInit( - /** - * @implements {module:event.eventContext_} - */ + /** + * @implements {module:event.eventContext_} + */ { - getCanvas () { return canvas; }, - getClipBoardID () { return CLIPBOARD_ID; }, - getLastClickPoint (key) { return lastClickPoint[key]; }, + getCanvas() { return canvas; }, + getClipBoardID() { return CLIPBOARD_ID; }, + getLastClickPoint(key) { return lastClickPoint[key]; }, addCommandToHistory, restoreRefElems } diff --git a/src/svgcanvas/utilities.js b/src/svgcanvas/utilities.js index 7e1b17c6..692227fc 100644 --- a/src/svgcanvas/utilities.js +++ b/src/svgcanvas/utilities.js @@ -8,9 +8,9 @@ */ import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` -import {NS} from '../common/namespaces.js'; -import {getTransformList} from './svgtransformlist.js'; -import {setUnitAttr, getTypeMap} from '../common/units.js'; +import { NS } from '../common/namespaces.js'; +import { getTransformList } from './svgtransformlist.js'; +import { setUnitAttr, getTypeMap } from '../common/units.js'; import { hasMatrixTransform, transformListToTransform, transformBox } from './math.js'; @@ -18,7 +18,7 @@ import { isWebkit, supportsHVLineContainerBBox, supportsPathBBox, supportsXpath, supportsSelectors } from '../common/browser.js'; -import {getClosest} from '../editor/components/jgraduate/Util.js'; +import { getClosest } from '../editor/components/jgraduate/Util.js'; // Constants const $ = jQueryPluginSVG(jQuery); @@ -138,7 +138,7 @@ export const toXml = function (str) { * @param {string} str - The string to be converted * @returns {string} The converted string */ -export function fromXml (str) { +export function fromXml(str) { const p = document.createElement('p'); // eslint-disable-next-line no-unsanitized/property p.innerHTML = str; @@ -158,7 +158,7 @@ export function fromXml (str) { * @param {string} input * @returns {string} Base64 output */ -export function encode64 (input) { +export function encode64(input) { // base64 strings are 4/3 larger than the original string input = encodeUTF8(input); // convert non-ASCII characters // input = convertToXMLReferences(input); @@ -204,7 +204,7 @@ export function encode64 (input) { * @param {string} input Base64-encoded input * @returns {string} Decoded output */ -export function decode64 (input) { +export function decode64(input) { if (window.atob) { return decodeUTF8(window.atob(input)); } @@ -244,7 +244,7 @@ export function decode64 (input) { * @param {string} argString * @returns {string} */ -export function decodeUTF8 (argString) { +export function decodeUTF8(argString) { return decodeURIComponent(escape(argString)); } @@ -281,7 +281,7 @@ export const dataURLToObjectURL = function (dataurl) { while (n--) { u8arr[n] = bstr.charCodeAt(n); } - const blob = new Blob([u8arr], {type: mime}); + const blob = new Blob([u8arr], { type: mime }); return URL.createObjectURL(blob); }; @@ -305,7 +305,7 @@ export const blankPageObjectURL = (function () { if (typeof Blob === 'undefined') { return ''; } - const blob = new Blob(['SVG-edit '], {type: 'text/html'}); + const blob = new Blob(['SVG-edit '], { type: 'text/html' }); return createObjectURL(blob); })(); @@ -368,8 +368,8 @@ export const text2xml = function (sXML) { * @param {SVGRect} bbox - a SVGRect * @returns {module:utilities.BBoxObject} An object with properties names x, y, width, height. */ -export const bboxToObj = function ({x, y, width, height}) { - return {x, y, width, height}; +export const bboxToObj = function ({ x, y, width, height }) { + return { x, y, width, height }; }; /** @@ -571,24 +571,24 @@ export const getPathBBox = function (path) { * @param {Element} selected - Container or `` DOM element * @returns {DOMRect} Bounding box object */ -function groupBBFix (selected) { +function groupBBFix(selected) { if (supportsHVLineContainerBBox()) { - try { return selected.getBBox(); } catch (e) {/* empty */} + try { return selected.getBBox(); } catch (e) {/* empty */ } } - const ref = dataStorage.get(selected, 'ref'); + const ref = editorContext_.getDataStorage().get(selected, 'ref'); let matched = null; let ret, copy; if (ref) { let elements = []; - Array.prototype.forEach.call(ref.children, function(el, i){ - const elem = el.cloneNode(true); - elem.setAttribute('visibility', 'hidden'); - svgroot_.appendChild(elem); - copy.push(elem); - if(['line', 'path'].indexOf(elem.tagName) !== -1){ - elements.push(elem); - } + Array.prototype.forEach.call(ref.children, function (el, i) { + const elem = el.cloneNode(true); + elem.setAttribute('visibility', 'hidden'); + svgroot_.appendChild(elem); + copy.push(elem); + if (['line', 'path'].indexOf(elem.tagName) !== -1) { + elements.push(elem); + } }); matched = (elements.length) ? elements : null; } else { @@ -597,7 +597,7 @@ function groupBBFix (selected) { let issue = false; if (matched.length) { - Array.prototype.forEach.call(matched, function(match, i){ + Array.prototype.forEach.call(matched, function (match, i) { const bb = match.getBBox(); if (!bb.width || !bb.height) { issue = true; @@ -632,70 +632,70 @@ export const getBBox = function (elem) { let ret = null; switch (elname) { - case 'text': - if (selected.textContent === '') { - selected.textContent = 'a'; // Some character needed for the selector to use. - ret = selected.getBBox(); - selected.textContent = ''; - } else if (selected.getBBox) { - ret = selected.getBBox(); - } - break; - case 'path': - if (!supportsPathBBox()) { - ret = getPathBBox(selected); - } else if (selected.getBBox) { - ret = selected.getBBox(); - } - break; - case 'g': - case 'a': - ret = groupBBFix(selected); - break; - default: + case 'text': + if (selected.textContent === '') { + selected.textContent = 'a'; // Some character needed for the selector to use. + ret = selected.getBBox(); + selected.textContent = ''; + } else if (selected.getBBox) { + ret = selected.getBBox(); + } + break; + case 'path': + if (!supportsPathBBox()) { + ret = getPathBBox(selected); + } else if (selected.getBBox) { + ret = selected.getBBox(); + } + break; + case 'g': + case 'a': + ret = groupBBFix(selected); + break; + default: - if (elname === 'use') { - ret = groupBBFix(selected); // , true); - } - if (elname === 'use' || (elname === 'foreignObject' && isWebkit())) { - if (!ret) { ret = selected.getBBox(); } - // This is resolved in later versions of webkit, perhaps we should - // have a featured detection for correct 'use' behavior? - // —————————— - if (!isWebkit()) { - const {x, y, width, height} = ret; - const bb = { - width, - height, - x: x + Number.parseFloat(selected.getAttribute('x') || 0), - y: y + Number.parseFloat(selected.getAttribute('y') || 0) - }; - ret = bb; + if (elname === 'use') { + ret = groupBBFix(selected); // , true); } - } else if (visElemsArr.includes(elname)) { - if (selected) { - try { - ret = selected.getBBox(); - } catch (err) { - // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268 - // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835 - const extent = selected.getExtentOfChar(0); // pos+dimensions of the first glyph - const width = selected.getComputedTextLength(); // width of the tspan - ret = { - x: extent.x, - y: extent.y, + if (elname === 'use' || (elname === 'foreignObject' && isWebkit())) { + if (!ret) { ret = selected.getBBox(); } + // This is resolved in later versions of webkit, perhaps we should + // have a featured detection for correct 'use' behavior? + // —————————— + if (!isWebkit()) { + const { x, y, width, height } = ret; + const bb = { width, - height: extent.height + height, + x: x + Number.parseFloat(selected.getAttribute('x') || 0), + y: y + Number.parseFloat(selected.getAttribute('y') || 0) }; + ret = bb; } - } else { - // Check if element is child of a foreignObject - const fo = getClosest(selected.parentNode, 'foreignObject'); - if (fo.length && fo[0].getBBox) { - ret = fo[0].getBBox(); + } else if (visElemsArr.includes(elname)) { + if (selected) { + try { + ret = selected.getBBox(); + } catch (err) { + // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268 + // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835 + const extent = selected.getExtentOfChar(0); // pos+dimensions of the first glyph + const width = selected.getComputedTextLength(); // width of the tspan + ret = { + x: extent.x, + y: extent.y, + width, + height: extent.height + }; + } + } else { + // Check if element is child of a foreignObject + const fo = getClosest(selected.parentNode, 'foreignObject'); + if (fo.length && fo[0].getBBox) { + ret = fo[0].getBBox(); + } } } - } } if (ret) { ret = bboxToObj(ret); @@ -743,76 +743,75 @@ export const getPathDFromElement = function (elem) { let num = 1.81; let d, a, rx, ry; switch (elem.tagName) { - case 'ellipse': - case 'circle': { - const rx = elem.getAttribute('rx'); - const ry = elem.getAttribute('ry'); - const cx = elem.getAttribute('cx'); - const cy = elem.getAttribute('cy'); - if (elem.tagName === 'circle') { - ry = elem.getAttribute('r'); - rx = ry; - } - - d = getPathDFromSegments([ - ['M', [(cx - rx), (cy)]], - ['C', [(cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)]], - ['C', [(cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)]], - ['C', [(cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)]], - ['C', [(cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)]], - ['Z', []] - ]); - break; - } case 'path': - d = elem.getAttribute('d'); - break; - case 'line': - const x1 = elem.getAttribute('x1'); - const y1 = elem.getAttribute('y1'); - const x2 = elem.getAttribute('x2'); - const y2 = elem.getAttribute('y2'); - d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2; - break; - case 'polyline': - d = 'M' + elem.getAttribute('points'); - break; - case 'polygon': - d = 'M' + elem.getAttribute('points') + ' Z'; - break; - case 'rect': { - const rx = elem.getAttribute('rx'); - const ry = elem.getAttribute('ry'); - const b = elem.getBBox(); - const {x, y} = b, - w = b.width, - h = b.height; - num = 4 - num; // Why? Because! - - d = (!rx && !ry) - // Regular rect - ? getPathDFromSegments([ - ['M', [x, y]], - ['L', [x + w, y]], - ['L', [x + w, y + h]], - ['L', [x, y + h]], - ['L', [x, y]], - ['Z', []] - ]) - : getPathDFromSegments([ - ['M', [x, y + ry]], - ['C', [x, y + ry / num, x + rx / num, y, x + rx, y]], - ['L', [x + w - rx, y]], - ['C', [x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry]], - ['L', [x + w, y + h - ry]], - ['C', [x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h]], - ['L', [x + rx, y + h]], - ['C', [x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry]], - ['L', [x, y + ry]], + case 'ellipse': + case 'circle': { + rx = elem.getAttribute('rx'); + ry = elem.getAttribute('ry'); + const cx = elem.getAttribute('cx'); + const cy = elem.getAttribute('cy'); + if (elem.tagName === 'circle' && elem.hasAttribute('r')) { + ry = elem.getAttribute('r'); + rx = ry; + } + d = getPathDFromSegments([ + ['M', [(cx - rx), (cy)]], + ['C', [(cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)]], + ['C', [(cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)]], + ['C', [(cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)]], + ['C', [(cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)]], ['Z', []] ]); - break; - } default: - break; + break; + } case 'path': + d = elem.getAttribute('d'); + break; + case 'line': + const x1 = elem.getAttribute('x1'); + const y1 = elem.getAttribute('y1'); + const x2 = elem.getAttribute('x2'); + const y2 = elem.getAttribute('y2'); + d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2; + break; + case 'polyline': + d = 'M' + elem.getAttribute('points'); + break; + case 'polygon': + d = 'M' + elem.getAttribute('points') + ' Z'; + break; + case 'rect': { + rx = elem.getAttribute('rx'); + ry = elem.getAttribute('ry'); + const b = elem.getBBox(); + const { x, y } = b, + w = b.width, + h = b.height; + num = 4 - num; // Why? Because! + + d = (!rx && !ry) + // Regular rect + ? getPathDFromSegments([ + ['M', [x, y]], + ['L', [x + w, y]], + ['L', [x + w, y + h]], + ['L', [x, y + h]], + ['L', [x, y]], + ['Z', []] + ]) + : getPathDFromSegments([ + ['M', [x, y + ry]], + ['C', [x, y + ry / num, x + rx / num, y, x + rx, y]], + ['L', [x + w - rx, y]], + ['C', [x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry]], + ['L', [x + w, y + h - ry]], + ['C', [x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h]], + ['L', [x + rx, y + h]], + ['C', [x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry]], + ['L', [x, y + ry]], + ['Z', []] + ]); + break; + } default: + break; } return d; @@ -856,7 +855,7 @@ export const getBBoxOfElementAsPath = function (elem, addSVGElementFromJson, pat path.setAttribute('transform', eltrans); } - const {parentNode} = elem; + const { parentNode } = elem; if (elem.nextSibling) { elem.before(path); } else { @@ -914,8 +913,8 @@ export const convertToPath = function ( path.setAttribute('transform', eltrans); } - const {id} = elem; - const {parentNode} = elem; + const { id } = elem; + const { parentNode } = elem; if (elem.nextSibling) { elem.before(path); } else { @@ -936,7 +935,7 @@ export const convertToPath = function ( } } - const {nextSibling} = elem; + const { nextSibling } = elem; batchCmd.addSubCommand(new hstry.RemoveElementCommand(elem, nextSibling, parent)); batchCmd.addSubCommand(new hstry.InsertElementCommand(path)); @@ -975,7 +974,7 @@ export const convertToPath = function ( * @param {boolean} hasAMatrixTransform - True if there is a matrix transform * @returns {boolean} True if the bbox can be optimized. */ -function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasAMatrixTransform) { +function bBoxCanBeOptimizedOverNativeGetBBox(angle, hasAMatrixTransform) { const angleModulo90 = angle % 90; const closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99; const closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001; @@ -1026,7 +1025,7 @@ export const getBBoxWithTransform = function (elem, addSVGElementFromJson, pathA } if (!goodBb) { - const {matrix} = transformListToTransform(tlist); + const { matrix } = transformListToTransform(tlist); bb = transformBox(bb.x, bb.y, bb.width, bb.height, matrix).aabox; // Old technique that was exceedingly slow with large documents. @@ -1053,7 +1052,7 @@ export const getBBoxWithTransform = function (elem, addSVGElementFromJson, pathA * @todo This is problematic with large stroke-width and, for example, a single * horizontal line. The calculated BBox extends way beyond left and right sides. */ -function getStrokeOffsetForBBox (elem) { +function getStrokeOffsetForBBox(elem) { const sw = elem.getAttribute('stroke-width'); return (!isNaN(sw) && elem.getAttribute('stroke') !== 'none') ? sw / 2 : 0; } @@ -1141,7 +1140,7 @@ export const getVisibleElements = function (parentElement) { const contentElems = []; const childrens = parentElement.children - Array.prototype.forEach.call(childrens, function(elem, i){ + Array.prototype.forEach.call(childrens, function (elem, i) { if (elem.getBBox) { contentElems.push(elem); } @@ -1324,8 +1323,8 @@ export const snapToGrid = function (value) { */ export const preventClickDefault = function (img) { const elements = document.querySelectorAll("img"); - Array.from(elements).forEach(function(element) { - element.addEventListener('click', function(e) { + Array.from(elements).forEach(function (element) { + element.addEventListener('click', function (e) { e.preventDefault(); }); });