diff --git a/src/common/browser.js b/src/common/browser.js index 2cc67ba4..012ff07c 100644 --- a/src/common/browser.js +++ b/src/common/browser.js @@ -54,12 +54,6 @@ const supportsHVLineContainerBBox_ = (function () { return (bbox.width === 15); }()); -const supportsNonScalingStroke_ = (function () { - const rect = document.createElementNS(NSSVG, 'rect'); - rect.setAttribute('style', 'vector-effect:non-scaling-stroke'); - return rect.style.vectorEffect === 'non-scaling-stroke'; -}()); - // Public API /** @@ -100,10 +94,3 @@ export const supportsHVLineContainerBBox = () => supportsHVLineContainerBBox_; * @returns {boolean} */ export const supportsGoodTextCharPos = () => supportsGoodTextCharPos_; - -/** -* @function module:browser.supportsNonScalingStroke -* @returns {boolean} -*/ -export const supportsNonScalingStroke = () => supportsNonScalingStroke_; - diff --git a/src/svgcanvas/event.js b/src/svgcanvas/event.js index 7b343f43..d8d024ba 100644 --- a/src/svgcanvas/event.js +++ b/src/svgcanvas/event.js @@ -6,7 +6,7 @@ */ import { assignAttributes, cleanupElement, getElem, getRotationAngle, snapToGrid, walkTree, - getBBox as utilsGetBBox, isNullish, preventClickDefault, setHref + isNullish, preventClickDefault, setHref } from './utilities.js'; import { convertAttrs @@ -14,7 +14,6 @@ import { import { transformPoint, hasMatrixTransform, getMatrix, snapToAngle } from './math.js'; -import { supportsNonScalingStroke } from '../common/browser.js'; import * as draw from './draw.js'; import * as pathModule from './path.js'; import * as hstry from './history.js'; @@ -79,7 +78,7 @@ export const getBsplinePoint = function (t) { * @returns {void} */ export const mouseMoveEvent = function (evt) { - const selectedElements = eventContext_.getSelectedElements; + const selectedElements = eventContext_.getSelectedElements(); const currentZoom = eventContext_.getCurrentZoom(); const svgRoot = eventContext_.getSVGRoot(); const svgCanvas = eventContext_.getCanvas(); @@ -96,7 +95,7 @@ export const mouseMoveEvent = function (evt) { let len; let angle; let box; - let selected = selectedElements()[0]; + let selected = selectedElements[0]; const pt = transformPoint(evt.clientX, evt.clientY, eventContext_.getrootSctm()); const mouseX = pt.x * currentZoom; const mouseY = pt.y * currentZoom; @@ -119,7 +118,7 @@ export const mouseMoveEvent = function (evt) { // 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) { + if (selectedElements[0] !== null) { dx = x - eventContext_.getStartX(); dy = y - eventContext_.getStartY(); if (eventContext_.getCurConfig().gridSnapping) { @@ -128,16 +127,10 @@ export const mouseMoveEvent = function (evt) { } if (dx !== 0 || dy !== 0) { - len = selectedElements().length; + len = selectedElements.length; for (i = 0; i < len; ++i) { - selected = selectedElements()[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 = svgRoot.createSVGTransform(); @@ -157,7 +150,7 @@ export const mouseMoveEvent = function (evt) { svgCanvas.selectorManager.requestSelector(selected).resize(); } - svgCanvas.call('transition', selectedElements()); + svgCanvas.call('transition', selectedElements); } } break; @@ -175,7 +168,7 @@ export const mouseMoveEvent = function (evt) { // - 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(); const elemsToAdd = []; + const elemsToRemove = selectedElements.slice(); const elemsToAdd = []; const newList = eventContext_.getIntersectionList(); // For every element in the intersection, add if not present in selectedElements. @@ -183,7 +176,7 @@ export const mouseMoveEvent = function (evt) { 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)) { + if (!selectedElements.includes(intElem)) { elemsToAdd.push(intElem); } // Found an element that was already selected, so we shouldn't remove it. @@ -208,7 +201,7 @@ export const mouseMoveEvent = function (evt) { // the shape's coordinates tlist = selected.transform.baseVal; const hasMatrix = hasMatrixTransform(tlist); - box = hasMatrix ? eventContext_.getInitBbox() : utilsGetBBox(selected); + box = hasMatrix ? eventContext_.getInitBbox() : selected.getBBox(); let left = box.x; let top = box.y; let { width, height } = box; @@ -290,7 +283,7 @@ export const mouseMoveEvent = function (evt) { } svgCanvas.selectorManager.requestSelector(selected).resize(); - svgCanvas.call('transition', selectedElements()); + svgCanvas.call('transition', selectedElements); break; } case 'zoom': { @@ -485,7 +478,7 @@ export const mouseMoveEvent = function (evt) { break; } case 'rotate': { - box = utilsGetBBox(selected); + box = selected.getBBox(); cx = box.x + box.width / 2; cy = box.y + box.height / 2; const m = getMatrix(selected); @@ -502,7 +495,7 @@ export const mouseMoveEvent = function (evt) { } svgCanvas.setRotationAngle(angle < -180 ? (360 + angle) : angle, true); - svgCanvas.call('transition', selectedElements()); + svgCanvas.call('transition', selectedElements); break; } default: break; @@ -579,16 +572,16 @@ export const mouseUpEvent = function (evt) { // intentionally fall-through to select here case 'resize': case 'multiselect': - if (!isNullish(eventContext_.getRubberBox())) { + if (eventContext_.getRubberBox()) { eventContext_.getRubberBox().setAttribute('display', 'none'); eventContext_.setCurBBoxes([]); } eventContext_.setCurrentMode('select'); // Fallthrough case 'select': - if (!isNullish(selectedElements[0])) { + if (selectedElements[0]) { // if we only have one selected element - if (isNullish(selectedElements[1])) { + if (!selectedElements[1]) { // set our current stroke/fill properties to the element's const selected = selectedElements[0]; switch (selected.tagName) { @@ -613,9 +606,6 @@ export const mouseUpEvent = function (evt) { eventContext_.setCurText('font_family', selected.getAttribute('font-family')); } svgCanvas.selectorManager.requestSelector(selected).showGrips(true); - - // This shouldn't be necessary as it was done on mouseDown... - // svgCanvas.call('selected', [selected]); } // always recalculate dimensions to strip off stray identity transforms svgCanvas.recalculateAllSelectedDimensions(); @@ -624,10 +614,6 @@ export const mouseUpEvent = function (evt) { 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? - svgCanvas.selectorManager.requestSelector(selectedElements[i]).resize(); - } } // no change in position/size, so maybe we should move to pathedit } else { @@ -642,15 +628,14 @@ export const mouseUpEvent = function (evt) { } // 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'); - }); - } + const elem = selectedElements[0]; + if (elem) { + elem.removeAttribute('style'); + walkTree(elem, function (el) { + el.removeAttribute('style'); + }); } + } return; case 'zoom': { @@ -823,7 +808,7 @@ export const mouseUpEvent = function (evt) { // if this element is in a group, go up until we reach the top-level group // just below the layer groups // TODO: once we implement links, we also would have to check for elements - while (t && t.parentNode && t.parentNode.parentNode && t.parentNode.parentNode.tagName === 'g') { + while (t?.parentNode?.parentNode?.tagName === 'g') { t = t.parentNode; } // if we are not in the middle of creating a path, and we've clicked on some shape, @@ -1089,7 +1074,7 @@ export const mouseDownEvent = function (evt) { // Getting the BBox from the selection box, since we know we // want to orient around it - eventContext_.setInitBbox(utilsGetBBox($id('selectedBox0'))); + eventContext_.setInitBbox($id('selectedBox0').getBBox()); const bb = {}; for (const [ key, val ] of Object.entries(eventContext_.getInitBbox())) { bb[key] = val / currentZoom; diff --git a/src/svgcanvas/math.js b/src/svgcanvas/math.js index e6b8436e..63736732 100644 --- a/src/svgcanvas/math.js +++ b/src/svgcanvas/math.js @@ -176,7 +176,7 @@ export const transformListToTransform = function (tlist, min, max) { * @param {Element} elem - The DOM element to check * @returns {SVGMatrix} The matrix object associated with the element's transformlist */ -export const getMatrix = function (elem) { +export const getMatrix = (elem) => { const tlist = elem.transform.baseVal; return transformListToTransform(tlist).matrix; }; @@ -191,7 +191,7 @@ export const getMatrix = function (elem) { * @param {Integer} y2 - Second coordinate's y value * @returns {module:math.AngleCoord45} */ -export const snapToAngle = function (x1, y1, x2, y2) { +export const snapToAngle = (x1, y1, x2, y2) => { const snap = Math.PI / 4; // 45 degrees const dx = x2 - x1; const dy = y2 - y1; @@ -213,7 +213,7 @@ export const snapToAngle = function (x1, y1, x2, y2) { * @param {SVGRect} r2 - The second BBox-like object * @returns {boolean} True if rectangles intersect */ -export const rectsIntersect = function (r1, r2) { +export const rectsIntersect = (r1, r2) => { return r2.x < (r1.x + r1.width) && (r2.x + r2.width) > r1.x && r2.y < (r1.y + r1.height) && diff --git a/src/svgcanvas/select.js b/src/svgcanvas/select.js index 3803b3c4..f4596fbf 100644 --- a/src/svgcanvas/select.js +++ b/src/svgcanvas/select.js @@ -110,9 +110,9 @@ export class Selector { const mgr = selectorManager_; const selectedGrips = mgr.selectorGrips; const selected = this.selectedElement; - const sw = selected.getAttribute('stroke-width'); const currentZoom = svgFactory_.getCurrentZoom(); let offset = 1 / currentZoom; + const sw = selected.getAttribute('stroke-width'); if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) { offset += (sw / 2); } @@ -200,10 +200,8 @@ export class Selector { ' L' + (nbax + nbaw) + ',' + nbay + ' ' + (nbax + nbaw) + ',' + (nbay + nbah) + ' ' + nbax + ',' + (nbay + nbah) + 'z'; - selectedBox.setAttribute('d', dstr); const xform = angle ? 'rotate(' + [ angle, cx, cy ].join(',') + ')' : ''; - this.selectorGroup.setAttribute('transform', xform); // TODO(codedread): Is this needed? // if (selected === selectedElements[0]) { @@ -217,6 +215,8 @@ export class Selector { e: [ nbax + nbaw, nbay + (nbah) / 2 ], s: [ nbax + (nbaw) / 2, nbay + nbah ] }; + selectedBox.setAttribute('d', dstr); + this.selectorGroup.setAttribute('transform', xform); Object.entries(this.gripCoords).forEach(([ dir, coords ]) => { selectedGrips[dir].setAttribute('cx', coords[0]); selectedGrips[dir].setAttribute('cy', coords[1]); @@ -415,7 +415,7 @@ export class SelectorManager { * @returns {Selector} The selector based on the given element */ requestSelector(elem, bbox) { - if (isNullish(elem)) { return null; } + if (!elem) { return null; } const N = this.selectors.length; // If we've already acquired one for this element, return it. diff --git a/src/svgcanvas/selected-elem.js b/src/svgcanvas/selected-elem.js index e26e2807..93955fc3 100644 --- a/src/svgcanvas/selected-elem.js +++ b/src/svgcanvas/selected-elem.js @@ -108,7 +108,6 @@ export const moveUpDownSelected = function (dir) { if (!selected) { return; } elementContext_.setCurBBoxes([]); - // curBBoxes = []; let closest; let foundCur; // jQuery sorts this list const list = elementContext_.getIntersectionList(getStrokedBBoxDefaultVisible([ selected ])); @@ -163,10 +162,8 @@ export const moveSelectedElements = function (dx, dy, undoable = true) { } const batchCmd = new BatchCommand('position'); - let i = selectedElements.length; - while (i--) { - const selected = selectedElements[i]; - if (!isNullish(selected)) { + selectedElements.forEach((selected, i) => { + if (selected) { const xform = elementContext_.getSVGRoot().createSVGTransform(); const tlist = selected.transform?.baseVal; @@ -190,7 +187,7 @@ export const moveSelectedElements = function (dx, dy, undoable = true) { elementContext_.gettingSelectorManager().requestSelector(selected).resize(); } - } + }); if (!batchCmd.isEmpty()) { if (undoable) { elementContext_.addCommandToHistory(batchCmd); @@ -893,12 +890,7 @@ export const ungroupSelectedElement = function () { continue; } - if (anchor) { - anchor.before(elem); - } else { - g.after(elem); - } - children[i++] = elem; + children[i++] = parent.insertBefore(elem, anchor); batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); } diff --git a/src/svgcanvas/selection.js b/src/svgcanvas/selection.js index 9910fa85..cb265482 100644 --- a/src/svgcanvas/selection.js +++ b/src/svgcanvas/selection.js @@ -5,142 +5,159 @@ * @copyright 2011 Jeff Schiller */ -import { NS } from './namespaces.js'; +import { NS } from "./namespaces.js"; import { - isNullish, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible -} from './utilities.js'; -import { transformPoint, transformListToTransform, rectsIntersect } from './math.js'; -import * as hstry from './history.js'; -import { getClosest } from '../editor/components/jgraduate/Util.js'; + getBBox as utilsGetBBox, + getStrokedBBoxDefaultVisible +} from "./utilities.js"; +import { + transformPoint, + transformListToTransform, + rectsIntersect +} from "./math.js"; +import * as hstry from "./history.js"; +import { getClosest } from "../editor/components/jgraduate/Util.js"; const { BatchCommand } = hstry; let selectionContext_ = null; let svgCanvas = null; -let selectedElements; /** -* @function module:selection.init -* @param {module:selection.selectionContext} selectionContext -* @returns {void} -*/ + * @function module:selection.init + * @param {module:selection.selectionContext} selectionContext + * @returns {void} + */ export const init = function (selectionContext) { selectionContext_ = selectionContext; svgCanvas = selectionContext_.getCanvas(); - selectedElements = selectionContext_.getSelectedElements; }; /** -* Clears the selection. The 'selected' handler is then optionally called. -* This should really be an intersection applying to all types rather than a union. -* @name module:selection.SvgCanvas#clearSelection -* @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} -* @fires module:selection.SvgCanvas#event:selected -*/ + * Clears the selection. The 'selected' handler is then optionally called. + * This should really be an intersection applying to all types rather than a union. + * @name module:selection.SvgCanvas#clearSelection + * @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} + * @fires module:selection.SvgCanvas#event:selected + */ export const clearSelectionMethod = function (noCall) { - selectedElements().forEach((elem) => { - if (isNullish(elem)) { + const selectedElements = selectionContext_.getSelectedElements(); + selectedElements.forEach((elem) => { + if (!elem) { return; } + svgCanvas.selectorManager.releaseSelector(elem); }); - svgCanvas.setEmptySelectedElements(); + svgCanvas.setEmptySelectedElements; - if (!noCall) { svgCanvas.call('selected', selectedElements()); } + if (!noCall) { + svgCanvas.call("selected", selectedElements); + } }; /** -* Adds a list of elements to the selection. The 'selected' handler is then called. -* @name module:selection.SvgCanvas#addToSelection -* @type {module:path.EditorContext#addToSelection} -* @fires module:selection.SvgCanvas#event:selected -*/ + * Adds a list of elements to the selection. The 'selected' handler is then called. + * @name module:selection.SvgCanvas#addToSelection + * @type {module:path.EditorContext#addToSelection} + * @fires module:selection.SvgCanvas#event:selected + */ export const addToSelectionMethod = function (elemsToAdd, showGrips) { - if (!elemsToAdd.length) { return; } + const selectedElements = selectionContext_.getSelectedElements(); + if (!elemsToAdd.length) { + return; + } // find the first null in our selectedElements array - let j = 0; - while (j < selectedElements().length) { - if (isNullish(selectedElements()[j])) { + let firstNull = 0; + while (firstNull < selectedElements.length) { + if (selectedElements[firstNull] === null) { break; } - ++j; + ++firstNull; } // now add each element consecutively let i = elemsToAdd.length; while (i--) { let elem = elemsToAdd[i]; - if (!elem) { continue; } - const bbox = utilsGetBBox(elem); - if (!bbox) { continue; } + if (!elem || !elem.getBBox) { + continue; + } - if (elem.tagName === 'a' && elem.childNodes.length === 1) { + if (elem.tagName === "a" && elem.childNodes.length === 1) { // Make "a" element's child be the selected element elem = elem.firstChild; } // if it's not already there, add it - if (!selectedElements().includes(elem)) { - selectedElements()[j] = elem; + if (!selectedElements.includes(elem)) { + selectedElements[firstNull] = elem; // only the first selectedBBoxes element is ever used in the codebase these days // if (j === 0) selectedBBoxes[0] = utilsGetBBox(elem); - j++; - const sel = svgCanvas.selectorManager.requestSelector(elem, bbox); + firstNull++; + const sel = svgCanvas.selectorManager.requestSelector(elem); - if (selectedElements().length > 1) { + if (selectedElements.length > 1) { sel.showGrips(false); } } } - if (!selectedElements().length) { + if (!selectedElements.length) { return; } - svgCanvas.call('selected', selectedElements()); + svgCanvas.call("selected", selectedElements); - if (selectedElements().length === 1) { - svgCanvas.selectorManager.requestSelector(selectedElements()[0]).showGrips(showGrips); + if (selectedElements.length === 1) { + svgCanvas.selectorManager + .requestSelector(selectedElements[0]) + .showGrips(showGrips); } // make sure the elements are in the correct order // See: https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition - selectedElements().sort(function (a, b) { + selectedElements.sort(function (a, b) { if (a && b && a.compareDocumentPosition) { return 3 - (b.compareDocumentPosition(a) & 6); // eslint-disable-line no-bitwise } - if (isNullish(a)) { + if (!a) { return 1; } return 0; }); // Make sure first elements are not null - while (isNullish(selectedElements())[0]) { - selectedElements().shift(0); + while (!selectedElements[0]) { + selectedElements.shift(0); } }; /** -* @name module:svgcanvas.SvgCanvas#getMouseTarget -* @type {module:path.EditorContext#getMouseTarget} -*/ + * @name module:svgcanvas.SvgCanvas#getMouseTarget + * @type {module:path.EditorContext#getMouseTarget} + */ export const getMouseTargetMethod = function (evt) { - if (isNullish(evt)) { + if (!evt) { return null; } let mouseTarget = evt.target; // if it was a , Opera and WebKit return the SVGElementInstance - if (mouseTarget.correspondingUseElement) { mouseTarget = mouseTarget.correspondingUseElement; } + if (mouseTarget.correspondingUseElement) { + mouseTarget = mouseTarget.correspondingUseElement; + } // 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' + if ( + [ NS.MATH, NS.HTML ].includes(mouseTarget.namespaceURI) && + mouseTarget.id !== "svgcanvas" ) { - while (mouseTarget.nodeName !== 'foreignObject') { + while (mouseTarget.nodeName !== "foreignObject") { mouseTarget = mouseTarget.parentNode; - if (!mouseTarget) { return selectionContext_.getSVGRoot(); } + if (!mouseTarget) { + return selectionContext_.getSVGRoot(); + } } } @@ -155,52 +172,61 @@ export const getMouseTargetMethod = function (evt) { } // If it's a selection grip, return the grip parent - if (getClosest(mouseTarget.parentNode, '#selectorParentGroup')) { + if (getClosest(mouseTarget.parentNode, "#selectorParentGroup")) { // While we could instead have just returned mouseTarget, // this makes it easier to indentify as being a selector grip return svgCanvas.selectorManager.selectorParentGroup; } - while (!mouseTarget?.parentNode?.isSameNode(selectionContext_.getCurrentGroup() || currentLayer)) { + while ( + !mouseTarget?.parentNode?.isSameNode( + selectionContext_.getCurrentGroup() || currentLayer + ) + ) { mouseTarget = mouseTarget.parentNode; } return mouseTarget; }; /** -* @typedef {module:svgcanvas.ExtensionMouseDownStatus|module:svgcanvas.ExtensionMouseUpStatus|module:svgcanvas.ExtensionIDsUpdatedStatus|module:locale.ExtensionLocaleData[]|void} module:svgcanvas.ExtensionStatus -* @tutorial ExtensionDocs -*/ + * @typedef {module:svgcanvas.ExtensionMouseDownStatus|module:svgcanvas.ExtensionMouseUpStatus|module:svgcanvas.ExtensionIDsUpdatedStatus|module:locale.ExtensionLocaleData[]|void} module:svgcanvas.ExtensionStatus + * @tutorial ExtensionDocs + */ /** -* @callback module:svgcanvas.ExtensionVarBuilder -* @param {string} name The name of the extension -* @returns {module:svgcanvas.SvgCanvas#event:ext_addLangData} -*/ + * @callback module:svgcanvas.ExtensionVarBuilder + * @param {string} name The name of the extension + * @returns {module:svgcanvas.SvgCanvas#event:ext_addLangData} + */ /** -* @callback module:svgcanvas.ExtensionNameFilter -* @param {string} name -* @returns {boolean} -*/ + * @callback module:svgcanvas.ExtensionNameFilter + * @param {string} name + * @returns {boolean} + */ /* eslint-disable max-len */ /** -* @todo Consider: Should this return an array by default, so extension results aren't overwritten? -* @todo Would be easier to document if passing in object with key of action and vars as value; could then define an interface which tied both together -* @function module:svgcanvas.SvgCanvas#runExtensions -* @param {"mouseDown"|"mouseMove"|"mouseUp"|"zoomChanged"|"IDsUpdated"|"canvasUpdated"|"toolButtonStateUpdate"|"selectedChanged"|"elementTransition"|"elementChanged"|"langReady"|"langChanged"|"addLangData"|"onNewDocument"|"workareaResized"} action -* @param {module:svgcanvas.SvgCanvas#event:ext_mouseDown|module:svgcanvas.SvgCanvas#event:ext_mouseMove|module:svgcanvas.SvgCanvas#event:ext_mouseUp|module:svgcanvas.SvgCanvas#event:ext_zoomChanged|module:svgcanvas.SvgCanvas#event:ext_IDsUpdated|module:svgcanvas.SvgCanvas#event:ext_canvasUpdated|module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate|module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementTransition|module:svgcanvas.SvgCanvas#event:ext_elementChanged|module:svgcanvas.SvgCanvas#event:ext_langReady|module:svgcanvas.SvgCanvas#event:ext_langChanged|module:svgcanvas.SvgCanvas#event:ext_addLangData|module:svgcanvas.SvgCanvas#event:ext_onNewDocument|module:svgcanvas.SvgCanvas#event:ext_workareaResized|module:svgcanvas.ExtensionVarBuilder} [vars] -* @param {boolean} [returnArray] -* @param {module:svgcanvas.ExtensionNameFilter} nameFilter -* @returns {GenericArray|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus. -*/ + * @todo Consider: Should this return an array by default, so extension results aren't overwritten? + * @todo Would be easier to document if passing in object with key of action and vars as value; could then define an interface which tied both together + * @function module:svgcanvas.SvgCanvas#runExtensions + * @param {"mouseDown"|"mouseMove"|"mouseUp"|"zoomChanged"|"IDsUpdated"|"canvasUpdated"|"toolButtonStateUpdate"|"selectedChanged"|"elementTransition"|"elementChanged"|"langReady"|"langChanged"|"addLangData"|"onNewDocument"|"workareaResized"} action + * @param {module:svgcanvas.SvgCanvas#event:ext_mouseDown|module:svgcanvas.SvgCanvas#event:ext_mouseMove|module:svgcanvas.SvgCanvas#event:ext_mouseUp|module:svgcanvas.SvgCanvas#event:ext_zoomChanged|module:svgcanvas.SvgCanvas#event:ext_IDsUpdated|module:svgcanvas.SvgCanvas#event:ext_canvasUpdated|module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate|module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementTransition|module:svgcanvas.SvgCanvas#event:ext_elementChanged|module:svgcanvas.SvgCanvas#event:ext_langReady|module:svgcanvas.SvgCanvas#event:ext_langChanged|module:svgcanvas.SvgCanvas#event:ext_addLangData|module:svgcanvas.SvgCanvas#event:ext_onNewDocument|module:svgcanvas.SvgCanvas#event:ext_workareaResized|module:svgcanvas.ExtensionVarBuilder} [vars] + * @param {boolean} [returnArray] + * @param {module:svgcanvas.ExtensionNameFilter} nameFilter + * @returns {GenericArray|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus. + */ /* eslint-enable max-len */ -export const runExtensionsMethod = function (action, vars, returnArray, nameFilter) { +export const runExtensionsMethod = function ( + action, + vars, + returnArray, + nameFilter +) { let result = returnArray ? [] : false; for (const [ name, ext ] of Object.entries(selectionContext_.getExtensions())) { if (nameFilter && !nameFilter(name)) { return; } if (ext && action in ext) { - if (typeof vars === 'function') { + if (typeof vars === "function") { vars = vars(name); // ext, action } if (returnArray) { @@ -214,13 +240,13 @@ export const runExtensionsMethod = function (action, vars, returnArray, nameFilt }; /** -* Get all elements that have a BBox (excludes ``, ``, etc). -* Note that 0-opacity, off-screen etc elements are still considered "visible" -* for this function. -* @function module:svgcanvas.SvgCanvas#getVisibleElementsAndBBoxes -* @param {Element} parent - The parent DOM element to search within -* @returns {ElementAndBBox[]} An array with objects that include: -*/ + * Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc). + * Note that 0-opacity, off-screen etc elements are still considered "visible" + * for this function. + * @function module:svgcanvas.SvgCanvas#getVisibleElementsAndBBoxes + * @param {Element} parent - The parent DOM element to search within + * @returns {ElementAndBBox[]} An array with objects that include: + */ export const getVisibleElementsAndBBoxes = function (parent) { if (!parent) { const svgcontent = selectionContext_.getSVGContent(); @@ -228,7 +254,7 @@ export const getVisibleElementsAndBBoxes = function (parent) { } const contentElems = []; const elements = parent.children; - Array.prototype.forEach.call(elements, function (elem) { + Array.from(elements).forEach((elem) => { if (elem.getBBox) { contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([ elem ]) }); } @@ -237,31 +263,37 @@ export const getVisibleElementsAndBBoxes = function (parent) { }; /** -* This method sends back an array or a NodeList full of elements that -* intersect the multi-select rubber-band-box on the currentLayer only. -* -* We brute-force `getIntersectionList` for browsers that do not support it (Firefox). -* -* Reference: -* Firefox does not implement `getIntersectionList()`, see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=501421}. -* @function module:svgcanvas.SvgCanvas#getIntersectionList -* @param {SVGRect} rect -* @returns {Element[]|NodeList} Bbox elements -*/ + * This method sends back an array or a NodeList full of elements that + * intersect the multi-select rubber-band-box on the currentLayer only. + * + * We brute-force `getIntersectionList` for browsers that do not support it (Firefox). + * + * Reference: + * Firefox does not implement `getIntersectionList()`, see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=501421}. + * @function module:svgcanvas.SvgCanvas#getIntersectionList + * @param {SVGRect} rect + * @returns {Element[]|NodeList} Bbox elements + */ export const getIntersectionListMethod = function (rect) { const currentZoom = selectionContext_.getCurrentZoom(); - if (isNullish(selectionContext_.getRubberBox())) { return null; } + if (!selectionContext_.getRubberBox()) { + return null; + } - const parent = selectionContext_.getCurrentGroup() || svgCanvas.getCurrentDrawing().getCurrentLayer(); + const parent = + selectionContext_.getCurrentGroup() || + svgCanvas.getCurrentDrawing().getCurrentLayer(); let rubberBBox; if (!rect) { rubberBBox = selectionContext_.getRubberBox().getBBox(); const bb = selectionContext_.getSVGContent().createSVGRect(); - [ 'x', 'y', 'width', 'height', 'top', 'right', 'bottom', 'left' ].forEach((o) => { - bb[o] = rubberBBox[o] / currentZoom; - }); + [ "x", "y", "width", "height", "top", "right", "bottom", "left" ].forEach( + (o) => { + bb[o] = rubberBBox[o] / currentZoom; + } + ); rubberBBox = bb; } else { rubberBBox = selectionContext_.getSVGContent().createSVGRect(); @@ -271,22 +303,20 @@ export const getIntersectionListMethod = function (rect) { rubberBBox.height = rect.height; } - let resultList = null; - - if (isNullish(resultList) || typeof resultList.item !== 'function') { - resultList = []; - - if (!selectionContext_.getCurBBoxes().length) { - // Cache all bboxes - selectionContext_.setCurBBoxes(getVisibleElementsAndBBoxes(parent)); + const resultList = []; + if (selectionContext_.getCurBBoxes().length === 0) { + // Cache all bboxes + console.log("setbb"); + selectionContext_.setCurBBoxes(getVisibleElementsAndBBoxes(parent)); + } + let i = selectionContext_.getCurBBoxes().length; + while (i--) { + const curBBoxes = selectionContext_.getCurBBoxes(); + if (!rubberBBox.width) { + continue; } - let i = selectionContext_.getCurBBoxes().length; - while (i--) { - const curBBoxes = selectionContext_.getCurBBoxes(); - if (!rubberBBox.width) { continue; } - if (rectsIntersect(rubberBBox, curBBoxes[i].bbox)) { - resultList.push(curBBoxes[i].elem); - } + if (rectsIntersect(rubberBBox, curBBoxes[i].bbox)) { + resultList.push(curBBoxes[i].elem); } } @@ -297,61 +327,62 @@ export const getIntersectionListMethod = function (rect) { }; /** -* @typedef {PlainObject} ElementAndBBox -* @property {Element} elem - The element -* @property {module:utilities.BBoxObject} bbox - The element's BBox as retrieved from `getStrokedBBoxDefaultVisible` -*/ + * @typedef {PlainObject} ElementAndBBox + * @property {Element} elem - The element + * @property {module:utilities.BBoxObject} bbox - The element's BBox as retrieved from `getStrokedBBoxDefaultVisible` + */ /** -* Wrap an SVG element into a group element, mark the group as 'gsvg'. -* @function module:svgcanvas.SvgCanvas#groupSvgElem -* @param {Element} elem - SVG element to wrap -* @returns {void} -*/ + * Wrap an SVG element into a group element, mark the group as 'gsvg'. + * @function module:svgcanvas.SvgCanvas#groupSvgElem + * @param {Element} elem - SVG element to wrap + * @returns {void} + */ export const groupSvgElem = function (elem) { const dataStorage = selectionContext_.getDataStorage(); - const g = document.createElementNS(NS.SVG, 'g'); + const g = document.createElementNS(NS.SVG, "g"); elem.replaceWith(g); g.appendChild(elem); - dataStorage.put(g, 'gsvg', elem); + dataStorage.put(g, "gsvg", elem); g.id = svgCanvas.getNextId(); }; /** -* Runs the SVG Document through the sanitizer and then updates its paths. -* @function module:svgcanvas.SvgCanvas#prepareSvg -* @param {XMLDocument} newDoc - The SVG DOM document -* @returns {void} -*/ + * Runs the SVG Document through the sanitizer and then updates its paths. + * @function module:svgcanvas.SvgCanvas#prepareSvg + * @param {XMLDocument} newDoc - The SVG DOM document + * @returns {void} + */ export const prepareSvg = function (newDoc) { - svgCanvas.sanitizeSvg(newDoc.documentElement); // convert paths into absolute commands - const paths = [ ...newDoc.getElementsByTagNameNS(NS.SVG, 'path') ]; + const paths = [ ...newDoc.getElementsByTagNameNS(NS.SVG, "path") ]; paths.forEach((path) => { const convertedPath = svgCanvas.pathActions.convertPath(path); - path.setAttribute('d', convertedPath); + path.setAttribute("d", convertedPath); svgCanvas.pathActions.fixEnd(path); }); }; /** -* Removes any old rotations if present, prepends a new rotation at the -* transformed center. -* @function module:svgcanvas.SvgCanvas#setRotationAngle -* @param {string|Float} val - The new rotation angle in degrees -* @param {boolean} preventUndo - Indicates whether the action should be undoable or not -* @fires module:svgcanvas.SvgCanvas#event:changed -* @returns {void} -*/ + * Removes any old rotations if present, prepends a new rotation at the + * transformed center. + * @function module:svgcanvas.SvgCanvas#setRotationAngle + * @param {string|Float} val - The new rotation angle in degrees + * @param {boolean} preventUndo - Indicates whether the action should be undoable or not + * @fires module:svgcanvas.SvgCanvas#event:changed + * @returns {void} + */ export const setRotationAngle = function (val, preventUndo) { + const selectedElements = selectionContext_.getSelectedElements(); // ensure val is the proper type val = Number.parseFloat(val); - const elem = selectedElements()[0]; - const oldTransform = elem.getAttribute('transform'); + const elem = selectedElements[0]; + const oldTransform = elem.getAttribute("transform"); const bbox = utilsGetBBox(elem); - const cx = bbox.x + bbox.width / 2; const cy = bbox.y + bbox.height / 2; + const cx = bbox.x + bbox.width / 2; + const cy = bbox.y + bbox.height / 2; const tlist = elem.transform.baseVal; // only remove the real rotational transform if present (i.e. at index=0) @@ -363,7 +394,11 @@ export const setRotationAngle = function (val, preventUndo) { } // find Rnc and insert it if (val !== 0) { - const center = transformPoint(cx, cy, transformListToTransform(tlist).matrix); + const center = transformPoint( + cx, + cy, + transformListToTransform(tlist).matrix + ); const Rnc = selectionContext_.getSVGRoot().createSVGTransform(); Rnc.setRotate(val, center.x, center.y); if (tlist.numberOfItems) { @@ -372,52 +407,58 @@ export const setRotationAngle = function (val, preventUndo) { tlist.appendItem(Rnc); } } else if (tlist.numberOfItems === 0) { - elem.removeAttribute('transform'); + elem.removeAttribute("transform"); } if (!preventUndo) { // we need to undo it, then redo it so it can be undo-able! :) // TODO: figure out how to make changes to transform list undo-able cross-browser? - const newTransform = elem.getAttribute('transform'); + const newTransform = elem.getAttribute("transform"); if (oldTransform) { - elem.setAttribute('transform', oldTransform); + elem.setAttribute("transform", oldTransform); } else { - elem.removeAttribute('transform'); + elem.removeAttribute("transform"); } - svgCanvas.changeSelectedAttribute('transform', newTransform, selectedElements()); - svgCanvas.call('changed', selectedElements()); + svgCanvas.changeSelectedAttribute( + "transform", + newTransform, + selectedElements + ); + svgCanvas.call("changed", selectedElements); } // const pointGripContainer = getElem('pathpointgrip_container'); // if (elem.nodeName === 'path' && pointGripContainer) { // pathActions.setPointContainerTransform(elem.getAttribute('transform')); // } - const selector = svgCanvas.selectorManager.requestSelector(selectedElements()[0]); + const selector = svgCanvas.selectorManager.requestSelector( + selectedElements[0] + ); selector.resize(); selectionContext_.getSelector().updateGripCursors(val); }; /** -* Runs `recalculateDimensions` on the selected elements, -* adding the changes to a single batch command. -* @function module:svgcanvas.SvgCanvas#recalculateAllSelectedDimensions -* @fires module:svgcanvas.SvgCanvas#event:changed -* @returns {void} -*/ + * Runs `recalculateDimensions` on the selected elements, + * adding the changes to a single batch command. + * @function module:svgcanvas.SvgCanvas#recalculateAllSelectedDimensions + * @fires module:svgcanvas.SvgCanvas#event:changed + * @returns {void} + */ export const recalculateAllSelectedDimensions = function () { - const text = (selectionContext_.getCurrentResizeMode() === 'none' ? 'position' : 'size'); + const text = + selectionContext_.getCurrentResizeMode() === "none" ? "position" : "size"; const batchCmd = new BatchCommand(text); + const selectedElements = selectionContext_.getSelectedElements(); - let i = selectedElements().length; - while (i--) { - const elem = selectedElements()[i]; + selectedElements.forEach((elem) => { const cmd = svgCanvas.recalculateDimensions(elem); if (cmd) { batchCmd.addSubCommand(cmd); } - } + }); if (!batchCmd.isEmpty()) { selectionContext_.addCommandToHistory(batchCmd); - svgCanvas.call('changed', selectedElements()); + svgCanvas.call("changed", selectedElements); } }; diff --git a/src/svgcanvas/svgcanvas.js b/src/svgcanvas/svgcanvas.js index b920289a..93b32972 100644 --- a/src/svgcanvas/svgcanvas.js +++ b/src/svgcanvas/svgcanvas.js @@ -625,7 +625,7 @@ class SvgCanvas { getCurrentZoom, getRubberBox() { return rubberBox; }, setCurBBoxes(value) { curBBoxes = value; }, - getCurBBoxes(_value) { return curBBoxes; }, + getCurBBoxes() { return curBBoxes; }, getCurrentResizeMode() { return currentResizeMode; }, addCommandToHistory, getSelector() { return Selector; } diff --git a/src/svgcanvas/utilities.js b/src/svgcanvas/utilities.js index 4934d99c..b2f2ad67 100644 --- a/src/svgcanvas/utilities.js +++ b/src/svgcanvas/utilities.js @@ -11,9 +11,6 @@ import { setUnitAttr, getTypeMap } from '../common/units.js'; import { hasMatrixTransform, transformListToTransform, transformBox } from './math.js'; -import { - isWebkit, supportsHVLineContainerBBox -} from '../common/browser.js'; import { getClosest, mergeDeep } from '../editor/components/jgraduate/Util.js'; // Much faster than running getBBox() every time @@ -467,61 +464,6 @@ export const getPathBBox = function (path) { }; }; -/** -* Get the given/selected element's bounding box object, checking for -* horizontal/vertical lines (see issue 717) -* Note that performance is currently terrible, so some way to improve would -* be great. -* @param {Element} selected - Container or `<use>` DOM element -* @returns {DOMRect} Bounding box object -*/ -function groupBBFix(selected) { - if (supportsHVLineContainerBBox()) { - try { return selected.getBBox(); } catch (e) {/* empty */ } - } - const ref = editorContext_.getDataStorage().get(selected, 'ref'); - let matched = null; - let ret; let copy; - - if (ref) { - const elements = []; - Array.prototype.forEach.call(ref.children, function (el) { - 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 { - matched = selected.querySelectorAll('line, path'); - } - - let issue = false; - if (matched.length) { - Array.prototype.forEach.call(matched, function (match) { - const bb = match.getBBox(); - if (!bb.width || !bb.height) { - issue = true; - } - }); - if (issue) { - const elems = ref ? copy : selected.children; - ret = getStrokedBBox(elems); - } else { - ret = selected.getBBox(); - } - } else { - ret = selected.getBBox(); - } - if (ref) { - copy.remove(); - } - return ret; -} - /** * Get the given/selected element's bounding box object, convert it to be more * usable when necessary. @@ -546,22 +488,16 @@ export const getBBox = function (elem) { } break; case 'path': + case 'g': + case 'a': 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(); } - + ret = selected.getBBox(); // , true); } else if (visElemsArr.includes(elname)) { if (selected) { try { @@ -1030,8 +966,8 @@ export const getVisibleElements = function (parentElement) { } const contentElems = []; - const childrens = parentElement.children; - Array.prototype.forEach.call(childrens, function (elem) { + const children = parentElement.children; + Array.from(children, function (elem) { if (elem.getBBox) { contentElems.push(elem); }