UNPKG

svgedit

Version:

Powerful SVG-Editor for your browser

1,073 lines (937 loc) 47.8 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: svgcanvas/selected-elem.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: svgcanvas/selected-elem.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/** * Tools for SVG selected element operation. * @module selected-elem * @license MIT * * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller */ import { NS } from './namespaces.js'; import * as hstry from './history.js'; import * as pathModule from './path.js'; import { isNullish, getStrokedBBoxDefaultVisible, setHref, getElem, getHref, getVisibleElements, findDefs, getRotationAngle, getRefElem, getBBox as utilsGetBBox, walkTreePost, assignAttributes, getFeGaussianBlur } from './utilities.js'; import { transformPoint, matrixMultiply, transformListToTransform } from './math.js'; import { recalculateDimensions } from './recalculate.js'; import { isGecko } from '../common/browser.js'; // , supportsEditableText import { getParents } from '../editor/components/jgraduate/Util.js'; const { MoveElementCommand, BatchCommand, InsertElementCommand, RemoveElementCommand, ChangeElementCommand } = hstry; let elementContext_ = null; /** * @function module:selected-elem.init * @param {module:selected-elem.elementContext} elementContext * @returns {void} */ export const init = function (elementContext) { elementContext_ = elementContext; }; /** * Repositions the selected element to the bottom in the DOM to appear on top of * other elements. * @function module:selected-elem.SvgCanvas#moveToTopSelectedElem * @fires module:selected-elem.SvgCanvas#event:changed * @returns {void} */ export const moveToTopSelectedElem = function () { const [ selected ] = elementContext_.getSelectedElements(); if (!isNullish(selected)) { const t = selected; const oldParent = t.parentNode; const oldNextSibling = t.nextSibling; t.parentNode.append(t); // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { elementContext_.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'top')); elementContext_.call('changed', [ t ]); } } }; /** * Repositions the selected element to the top in the DOM to appear under * other elements. * @function module:selected-elem.SvgCanvas#moveToBottomSelectedElement * @fires module:selected-elem.SvgCanvas#event:changed * @returns {void} */ export const moveToBottomSelectedElem = function () { const [ selected ] = elementContext_.getSelectedElements(); if (!isNullish(selected)) { let t = selected; const oldParent = t.parentNode; const oldNextSibling = t.nextSibling; let { firstChild } = t.parentNode; if (firstChild.tagName === 'title') { firstChild = firstChild.nextSibling; } // This can probably be removed, as the defs should not ever apppear // inside a layer group if (firstChild.tagName === 'defs') { firstChild = firstChild.nextSibling; } t = t.parentNode.insertBefore(t, firstChild); // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { elementContext_.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'bottom')); elementContext_.call('changed', [ t ]); } } }; /** * Moves the select element up or down the stack, based on the visibly * intersecting elements. * @function module:selected-elem.SvgCanvas#moveUpDownSelected * @param {"Up"|"Down"} dir - String that's either 'Up' or 'Down' * @fires module:selected-elem.SvgCanvas#event:changed * @returns {void} */ export const moveUpDownSelected = function (dir) { const selectedElements = elementContext_.getSelectedElements(); const selected = selectedElements[0]; if (!selected) { return; } elementContext_.setCurBBoxes([]); // curBBoxes = []; let closest; let foundCur; // jQuery sorts this list const list = elementContext_.getIntersectionList(getStrokedBBoxDefaultVisible([ selected ])); if (dir === 'Down') { list.reverse(); } Array.prototype.forEach.call(list, function (el) { if (!foundCur) { if (el === selected) { foundCur = true; } return true; } closest = el; return false; }); if (!closest) { return; } const t = selected; const oldParent = t.parentNode; const oldNextSibling = t.nextSibling; if (dir === 'Down') { closest.insertAdjacentElement('beforebegin', t); } else { closest.insertAdjacentElement('afterend', t); } // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { elementContext_.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'Move ' + dir)); elementContext_.call('changed', [ t ]); } }; /** * Moves selected elements on the X/Y axis. * @function module:selected-elem.SvgCanvas#moveSelectedElements * @param {Float} dx - Float with the distance to move on the x-axis * @param {Float} dy - Float with the distance to move on the y-axis * @param {boolean} undoable - Boolean indicating whether or not the action should be undoable * @fires module:selected-elem.SvgCanvas#event:changed * @returns {BatchCommand|void} Batch command for the move */ export const moveSelectedElements = function (dx, dy, undoable = true) { const selectedElements = elementContext_.getSelectedElements(); const currentZoom = elementContext_.getCurrentZoom(); // if undoable is not sent, default to true // if single values, scale them to the zoom if (!Array.isArray(dx)) { dx /= currentZoom; dy /= currentZoom; } const batchCmd = new BatchCommand('position'); let i = selectedElements.length; while (i--) { const selected = selectedElements[i]; if (!isNullish(selected)) { const xform = elementContext_.getSVGRoot().createSVGTransform(); const tlist = selected.transform?.baseVal; // dx and dy could be arrays if (Array.isArray(dx)) { xform.setTranslate(dx[i], dy[i]); } else { xform.setTranslate(dx, dy); } if (tlist.numberOfItems) { tlist.insertItemBefore(xform, 0); } else { tlist.appendItem(xform); } const cmd = recalculateDimensions(selected); if (cmd) { batchCmd.addSubCommand(cmd); } elementContext_.gettingSelectorManager().requestSelector(selected).resize(); } } if (!batchCmd.isEmpty()) { if (undoable) { elementContext_.addCommandToHistory(batchCmd); } elementContext_.call('changed', selectedElements); return batchCmd; } return undefined; }; /** * Create deep DOM copies (clones) of all selected elements and move them slightly * from their originals. * @function module:selected-elem.SvgCanvas#cloneSelectedElements * @param {Float} x Float with the distance to move on the x-axis * @param {Float} y Float with the distance to move on the y-axis * @returns {void} */ export const cloneSelectedElements = function (x, y) { const selectedElements = elementContext_.getSelectedElements(); const currentGroup = elementContext_.getCurrentGroup(); let i; let elem; const batchCmd = new BatchCommand('Clone Elements'); // find all the elements selected (stop at first null) const len = selectedElements.length; function index(el) { if (!el) return -1; let i = 0; do { i++; } while (el == el.previousElementSibling); return i; } /** * Sorts an array numerically and ascending. * @param {Element} a * @param {Element} b * @returns {Integer} */ function sortfunction(a, b) { return (index(b) - index(a)); } selectedElements.sort(sortfunction); for (i = 0; i &lt; len; ++i) { elem = selectedElements[i]; if (isNullish(elem)) { break; } } // use slice to quickly get the subset of elements we need const copiedElements = selectedElements.slice(0, i); elementContext_.clearSelection(true); // note that we loop in the reverse way because of the way elements are added // to the selectedElements array (top-first) const drawing = elementContext_.getDrawing(); i = copiedElements.length; while (i--) { // clone each element and replace it within copiedElements elem = copiedElements[i] = drawing.copyElem(copiedElements[i]); (currentGroup || drawing.getCurrentLayer()).append(elem); batchCmd.addSubCommand(new InsertElementCommand(elem)); } if (!batchCmd.isEmpty()) { elementContext_.addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding moveSelectedElements(x, y, false); elementContext_.addCommandToHistory(batchCmd); } }; /** * Aligns selected elements. * @function module:selected-elem.SvgCanvas#alignSelectedElements * @param {string} type - String with single character indicating the alignment type * @param {"selected"|"largest"|"smallest"|"page"} relativeTo * @returns {void} */ export const alignSelectedElements = function (type, relativeTo) { const selectedElements = elementContext_.getSelectedElements(); const bboxes = []; // angles = []; const len = selectedElements.length; if (!len) { return; } let minx = Number.MAX_VALUE; let maxx = Number.MIN_VALUE; let miny = Number.MAX_VALUE; let maxy = Number.MIN_VALUE; let curwidth = Number.MIN_VALUE; let curheight = Number.MIN_VALUE; for (let i = 0; i &lt; len; ++i) { if (isNullish(selectedElements[i])) { break; } const elem = selectedElements[i]; bboxes[i] = getStrokedBBoxDefaultVisible([ elem ]); // 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') &amp;&amp; (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') &amp;&amp; (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') &amp;&amp; (curwidth === Number.MIN_VALUE || curwidth &lt; bboxes[i].width)) || ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') &amp;&amp; (curheight === Number.MIN_VALUE || curheight &lt; 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 &lt; minx) { minx = bboxes[i].x; } if (bboxes[i].y &lt; 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 if (relativeTo === 'page') { minx = 0; miny = 0; maxx = elementContext_.getContentW(); maxy = elementContext_.getContentH(); } const dx = new Array(len); const dy = new Array(len); for (let i = 0; i &lt; len; ++i) { if (isNullish(selectedElements[i])) { break; } // const elem = selectedElements[i]; const bbox = bboxes[i]; 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; } } moveSelectedElements(dx, dy); }; /** * Removes all selected elements from the DOM and adds the change to the * history stack. * @function module:selected-elem.SvgCanvas#deleteSelectedElements * @fires module:selected-elem.SvgCanvas#event:changed * @returns {void} */ export const deleteSelectedElements = function () { const selectedElements = elementContext_.getSelectedElements(); const batchCmd = new BatchCommand('Delete Elements'); const len = selectedElements.length; const selectedCopy = []; // selectedElements is being deleted for (let i = 0; i &lt; len; ++i) { const selected = selectedElements[i]; if (isNullish(selected)) { break; } let parent = selected.parentNode; let t = selected; // this will unselect the element and remove the selectedOutline elementContext_.gettingSelectorManager().releaseSelector(t); // Remove the path if present. pathModule.removePath_(t.id); // Get the parent if it's a single-child anchor if (parent.tagName === 'a' &amp;&amp; parent.childNodes.length === 1) { t = parent; parent = parent.parentNode; } const { nextSibling } = t; t.remove(); const elem = t; selectedCopy.push(selected); // for the copy batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); } elementContext_.getCanvas().setEmptySelectedElements(); if (!batchCmd.isEmpty()) { elementContext_.addCommandToHistory(batchCmd); } elementContext_.call('changed', selectedCopy); elementContext_.clearSelection(); }; /** * Remembers the current selected elements on the clipboard. * @function module:selected-elem.SvgCanvas#copySelectedElements * @returns {void} */ export const copySelectedElements = function () { const selectedElements = elementContext_.getSelectedElements(); const data = JSON.stringify(selectedElements.map((x) => elementContext_.getJsonFromSvgElement(x))); // Use sessionStorage for the clipboard data. sessionStorage.setItem(elementContext_.getClipboardID(), data); elementContext_.flashStorage(); // Context menu might not exist (it is provided by editor.js). const canvMenu = document.getElementById('se-cmenu_canvas'); canvMenu.setAttribute('enablemenuitems', '#paste,#paste_in_place'); }; /** * Wraps all the selected elements in a group (`g`) element. * @function module:selected-elem.SvgCanvas#groupSelectedElements * @param {"a"|"g"} [type="g"] - type of element to group into, defaults to `&lt;g>` * @param {string} [urlArg] * @returns {void} */ export const groupSelectedElements = function (type, urlArg) { const selectedElements = elementContext_.getSelectedElements(); if (!type) { type = 'g'; } let cmdStr = ''; let url; switch (type) { case 'a': { cmdStr = 'Make hyperlink'; url = urlArg || ''; break; } default: { type = 'g'; cmdStr = 'Group Elements'; break; } } const batchCmd = new BatchCommand(cmdStr); // create and insert the group element const g = elementContext_.addSVGElementFromJson({ element: type, attr: { id: elementContext_.getNextId() } }); if (type === 'a') { setHref(g, url); } batchCmd.addSubCommand(new InsertElementCommand(g)); // now move all children into the group let i = selectedElements.length; while (i--) { let elem = selectedElements[i]; if (isNullish(elem)) { continue; } if (elem.parentNode.tagName === 'a' &amp;&amp; elem.parentNode.childNodes.length === 1) { elem = elem.parentNode; } const oldNextSibling = elem.nextSibling; const oldParent = elem.parentNode; g.append(elem); batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); } if (!batchCmd.isEmpty()) { elementContext_.addCommandToHistory(batchCmd); } // update selection elementContext_.selectOnly([ g ], true); }; /** * Pushes all appropriate parent group properties down to its children, then * removes them from the group. * @function module:selected-elem.SvgCanvas#pushGroupProperty * @param {SVGAElement|SVGGElement} g * @param {boolean} undoable * @returns {BatchCommand|void} */ export const pushGroupProperty = function (g, undoable) { const children = g.childNodes; const len = children.length; const xform = g.getAttribute('transform'); const glist = g.transform.baseVal; const m = transformListToTransform(glist).matrix; const batchCmd = new BatchCommand('Push group properties'); // TODO: get all fill/stroke properties from the group that we are about to destroy // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset", // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", // "stroke-width" // and then for each child, if they do not have the attribute (or the value is 'inherit') // then set the child's attribute const gangle = getRotationAngle(g); const gattrs = { filter: g.getAttribute('filter'), opacity: g.getAttribute('opacity') }; let gfilter; let gblur; let changes; const drawing = elementContext_.getDrawing(); for (let i = 0; i &lt; len; i++) { const elem = children[i]; if (elem.nodeType !== 1) { continue; } if (gattrs.opacity !== null &amp;&amp; gattrs.opacity !== 1) { // const c_opac = elem.getAttribute('opacity') || 1; const newOpac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) / 100; elementContext_.changeSelectedAttribute('opacity', newOpac, [ elem ]); } if (gattrs.filter) { let cblur = elementContext_.getCanvas().getBlur(elem); const origCblur = cblur; if (!gblur) { gblur = elementContext_.getCanvas().getBlur(g); } if (cblur) { // Is this formula correct? cblur = Number(gblur) + Number(cblur); } else if (cblur === 0) { cblur = gblur; } // If child has no current filter, get group's filter or clone it. if (!origCblur) { // Set group's filter to use first child's ID if (!gfilter) { gfilter = getRefElem(gattrs.filter); } else { // Clone the group's filter gfilter = drawing.copyElem(gfilter); findDefs().append(gfilter); // const filterElem = getRefElem(gfilter); const blurElem = getFeGaussianBlur(gfilter); // Change this in future for different filters const suffix = (blurElem?.tagName === 'feGaussianBlur') ? 'blur' : 'filter'; gfilter.id = elem.id + '_' + suffix; elementContext_.changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [ elem ]); } } else { gfilter = getRefElem(elem.getAttribute('filter')); } // const filterElem = getRefElem(gfilter); const blurElem = getFeGaussianBlur(gfilter); // Update blur value if (cblur) { elementContext_.changeSelectedAttribute('stdDeviation', cblur, [ blurElem ]); elementContext_.getCanvas().setBlurOffsets(gfilter, cblur); } } let chtlist = elem.transform?.baseVal; // Don't process gradient transforms if (elem.tagName.includes('Gradient')) { chtlist = null; } // Hopefully not a problem to add this. Necessary for elements like &lt;desc/> if (!chtlist) { continue; } // Apparently &lt;defs> can get get a transformlist, but we don't want it to have one! if (elem.tagName === 'defs') { continue; } if (glist.numberOfItems) { // TODO: if the group's transform is just a rotate, we can always transfer the // rotate() down to the children (collapsing consecutive rotates and factoring // out any translates) if (gangle &amp;&amp; glist.numberOfItems === 1) { // [Rg] [Rc] [Mc] // we want [Tr] [Rc2] [Mc] where: // - [Rc2] is at the child's current center but has the // sum of the group and child's rotation angles // - [Tr] is the equivalent translation that this child // undergoes if the group wasn't there // [Tr] = [Rg] [Rc] [Rc2_inv] // get group's rotation matrix (Rg) const rgm = glist.getItem(0).matrix; // get child's rotation matrix (Rc) let rcm = elementContext_.getSVGRoot().createSVGMatrix(); const cangle = getRotationAngle(elem); if (cangle) { rcm = chtlist.getItem(0).matrix; } // get child's old center of rotation const cbox = utilsGetBBox(elem); const ceqm = transformListToTransform(chtlist).matrix; const coldc = transformPoint(cbox.x + cbox.width / 2, cbox.y + cbox.height / 2, ceqm); // sum group and child's angles const sangle = gangle + cangle; // get child's rotation at the old center (Rc2_inv) const r2 = elementContext_.getSVGRoot().createSVGTransform(); r2.setRotate(sangle, coldc.x, coldc.y); // calculate equivalent translate const trm = matrixMultiply(rgm, rcm, r2.matrix.inverse()); // set up tlist if (cangle) { chtlist.removeItem(0); } if (sangle) { if (chtlist.numberOfItems) { chtlist.insertItemBefore(r2, 0); } else { chtlist.appendItem(r2); } } if (trm.e || trm.f) { const tr = elementContext_.getSVGRoot().createSVGTransform(); tr.setTranslate(trm.e, trm.f); if (chtlist.numberOfItems) { chtlist.insertItemBefore(tr, 0); } else { chtlist.appendItem(tr); } } } else { // more complicated than just a rotate // transfer the group's transform down to each child and then // call recalculateDimensions() const oldxform = elem.getAttribute('transform'); changes = {}; changes.transform = oldxform || ''; const newxform = elementContext_.getSVGRoot().createSVGTransform(); // [ gm ] [ chm ] = [ chm ] [ gm' ] // [ gm' ] = [ chmInv ] [ gm ] [ chm ] const chm = transformListToTransform(chtlist).matrix; const chmInv = chm.inverse(); const gm = matrixMultiply(chmInv, m, chm); newxform.setMatrix(gm); chtlist.appendItem(newxform); } const cmd = recalculateDimensions(elem); if (cmd) { batchCmd.addSubCommand(cmd); } } } // remove transform and make it undo-able if (xform) { changes = {}; changes.transform = xform; g.setAttribute('transform', ''); g.removeAttribute('transform'); batchCmd.addSubCommand(new ChangeElementCommand(g, changes)); } if (undoable &amp;&amp; !batchCmd.isEmpty()) { return batchCmd; } return undefined; }; /** * Converts selected/given `&lt;use>` or child SVG element to a group. * @function module:selected-elem.SvgCanvas#convertToGroup * @param {Element} elem * @fires module:selected-elem.SvgCanvas#event:selected * @returns {void} */ export const convertToGroup = function (elem) { const selectedElements = elementContext_.getSelectedElements(); if (!elem) { elem = selectedElements[0]; } 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; const pt = { x: Number(svg.getAttribute('x')), y: Number(svg.getAttribute('y')) }; // $(elem.firstChild.firstChild).unwrap(); const firstChild = elem.firstChild.firstChild; if (firstChild) { // eslint-disable-next-line no-unsanitized/property firstChild.outerHTML = firstChild.innerHTML; } dataStorage.remove(elem, 'gsvg'); const tlist = elem.transform.baseVal; const xform = elementContext_.getSVGRoot().createSVGTransform(); xform.setTranslate(pt.x, pt.y); tlist.appendItem(xform); recalculateDimensions(elem); elementContext_.call('selected', [ elem ]); } else if (dataStorage.has($elem, 'symbol')) { elem = dataStorage.get($elem, 'symbol'); ts = $elem.getAttribute('transform'); const pos = { x: Number($elem.getAttribute('x')), y: Number($elem.getAttribute('y')) }; const vb = elem.getAttribute('viewBox'); if (vb) { const nums = vb.split(' '); pos.x -= Number(nums[0]); pos.y -= Number(nums[1]); } // Not ideal, but works ts += ' translate(' + (pos.x || 0) + ',' + (pos.y || 0) + ')'; const prev = $elem.previousElementSibling; // Remove &lt;use> element batchCmd.addSubCommand(new RemoveElementCommand($elem, $elem.nextElementSibling, $elem.parentNode)); $elem.remove(); // See if other elements reference this symbol const svgcontent = elementContext_.getSVGContent(); // const hasMore = svgcontent.querySelectorAll('use:data(symbol)').length; // @todo review this logic const hasMore = svgcontent.querySelectorAll('use').length; const g = elementContext_.getDOMDocument().createElementNS(NS.SVG, 'g'); const childs = elem.childNodes; let i; for (i = 0; i &lt; childs.length; i++) { g.append(childs[i].cloneNode(true)); } // Duplicate the gradients for Gecko, since they weren't included in the &lt;symbol> if (isGecko()) { const svgElement = findDefs(); const gradients = svgElement.querySelectorAll('linearGradient,radialGradient,pattern'); for (let i = 0, im = gradients.length; im > i; i++) { g.appendChild(gradients[i].cloneNode(true)); } } if (ts) { g.setAttribute('transform', ts); } const parent = elem.parentNode; elementContext_.uniquifyElems(g); // Put the dupe gradients back into &lt;defs> (after uniquifying them) if (isGecko()) { const svgElement = findDefs(); const elements = g.querySelectorAll('linearGradient,radialGradient,pattern'); for (let i = 0, im = elements.length; im > i; i++) { svgElement.appendChild(elements[i]); } } // now give the g itself a new id g.id = elementContext_.getNextId(); prev.after(g); if (parent) { if (!hasMore) { // remove symbol/svg element const { nextSibling } = elem; elem.remove(); batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); } batchCmd.addSubCommand(new InsertElementCommand(g)); } elementContext_.setUseData(g); if (isGecko()) { elementContext_.convertGradients(findDefs()); } else { elementContext_.convertGradients(g); } // recalculate dimensions on the top-level children so that unnecessary transforms // are removed walkTreePost(g, function (n) { try { recalculateDimensions(n); } catch (e) { console.error(e); } }); // Give ID for any visible element missing one const visElems = g.querySelectorAll(elementContext_.getVisElems()); Array.prototype.forEach.call(visElems, function (el) { if (!el.id) { el.id = elementContext_.getNextId(); } }); elementContext_.selectOnly([ g ]); const cm = pushGroupProperty(g, true); if (cm) { batchCmd.addSubCommand(cm); } elementContext_.addCommandToHistory(batchCmd); } else { console.warn('Unexpected element to ungroup:', elem); } }; /** * Unwraps all the elements in a selected group (`g`) element. This requires * significant recalculations to apply group's transforms, etc. to its children. * @function module:selected-elem.SvgCanvas#ungroupSelectedElement * @returns {void} */ export const ungroupSelectedElement = function () { const selectedElements = elementContext_.getSelectedElements(); const dataStorage = elementContext_.getDataStorage(); let g = selectedElements[0]; if (!g) { return; } if (dataStorage.has(g, 'gsvg') || dataStorage.has(g, 'symbol')) { // Is svg, so actually convert to group convertToGroup(g); return; } if (g.tagName === 'use') { // Somehow doesn't have data set, so retrieve const symbol = getElem(getHref(g).substr(1)); dataStorage.put(g, 'symbol', symbol); dataStorage.put(g, 'ref', symbol); convertToGroup(g); return; } const parentsA = getParents(g.parentNode, 'a'); if (parentsA?.length) { g = parentsA[0]; } // Look for parent "a" if (g.tagName === 'g' || g.tagName === 'a') { const batchCmd = new BatchCommand('Ungroup Elements'); const cmd = pushGroupProperty(g, true); if (cmd) { batchCmd.addSubCommand(cmd); } const parent = g.parentNode; const anchor = g.nextSibling; const children = new Array(g.childNodes.length); let i = 0; while (g.firstChild) { const elem = g.firstChild; const oldNextSibling = elem.nextSibling; const oldParent = elem.parentNode; // Remove child title elements if (elem.tagName === 'title') { const { nextSibling } = elem; batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent)); elem.remove(); continue; } if (anchor) { anchor.before(elem); } else { g.after(elem); } children[i++] = elem; batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)); } // remove the group from the selection elementContext_.clearSelection(); // delete the group element (but make undo-able) const gNextSibling = g.nextSibling; g.remove(); batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent)); if (!batchCmd.isEmpty()) { elementContext_.addCommandToHistory(batchCmd); } // update selection elementContext_.addToSelection(children); } }; /** * Updates the editor canvas width/height/position after a zoom has occurred. * @function module:svgcanvas.SvgCanvas#updateCanvas * @param {Float} w - Float with the new width * @param {Float} h - Float with the new height * @fires module:svgcanvas.SvgCanvas#event:ext_canvasUpdated * @returns {module:svgcanvas.CanvasInfo} */ export const updateCanvas = function (w, h) { elementContext_.getSVGRoot().setAttribute('width', w); elementContext_.getSVGRoot().setAttribute('height', h); const currentZoom = elementContext_.getCurrentZoom(); const bg = document.getElementById('canvasBackground'); const oldX = Number(elementContext_.getSVGContent().getAttribute('x')); const oldY = Number(elementContext_.getSVGContent().getAttribute('y')); const x = ((w - this.contentW * currentZoom) / 2); const y = ((h - this.contentH * currentZoom) / 2); assignAttributes(elementContext_.getSVGContent(), { width: this.contentW * currentZoom, height: this.contentH * currentZoom, x, y, viewBox: '0 0 ' + this.contentW + ' ' + this.contentH }); assignAttributes(bg, { width: elementContext_.getSVGContent().getAttribute('width'), height: elementContext_.getSVGContent().getAttribute('height'), x, y }); const bgImg = getElem('background_image'); if (bgImg) { assignAttributes(bgImg, { width: '100%', height: '100%' }); } elementContext_.getCanvas().selectorManager.selectorParentGroup.setAttribute('transform', 'translate(' + x + ',' + y + ')'); /** * Invoked upon updates to the canvas. * @event module:svgcanvas.SvgCanvas#event:ext_canvasUpdated * @type {PlainObject} * @property {Integer} new_x * @property {Integer} new_y * @property {string} old_x (Of Integer) * @property {string} old_y (Of Integer) * @property {Integer} d_x * @property {Integer} d_y */ elementContext_.getCanvas().runExtensions( 'canvasUpdated', /** * @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 } ); 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. * @function module:svgcanvas.SvgCanvas#cycleElement * @param {boolean} next - true = next and false = previous element * @fires module:svgcanvas.SvgCanvas#event:selected * @returns {void} */ export const cycleElement = function (next) { const selectedElements = elementContext_.getSelectedElements(); const currentGroup = elementContext_.getCurrentGroup(); let num; const curElem = selectedElements[0]; let elem = false; const allElems = getVisibleElements(currentGroup || elementContext_.getCanvas().getCurrentDrawing().getCurrentLayer()); if (!allElems.length) { return; } if (isNullish(curElem)) { num = next ? allElems.length - 1 : 0; elem = allElems[num]; } else { let i = allElems.length; while (i--) { if (allElems[i] === curElem) { num = next ? i - 1 : i + 1; if (num >= allElems.length) { num = 0; } else if (num &lt; 0) { num = allElems.length - 1; } elem = allElems[num]; break; } } } elementContext_.getCanvas().selectOnly([ elem ], true); elementContext_.call('selected', selectedElements); }; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-blur.html">blur</a></li><li><a href="module-browser.html">browser</a></li><li><a href="module-clear.html">clear</a></li><li><a href="module-contextmenu.html">contextmenu</a></li><li><a href="module-coords.html">coords</a></li><li><a href="module-draw.html">draw</a></li><li><a href="module-elem-get-set%2520get%2520and%2520set%2520methods..html">elem-get-set get and set methods.</a></li><li><a href="module-event.html">event</a></li><li><a href="module-history.html">history</a></li><li><a href="module-jGraduate.html">jGraduate</a></li><li><a href="module-jPicker.html">jPicker</a></li><li><a href="module-jQueryAttr.html">jQueryAttr</a></li><li><a href="module-layer.html">layer</a></li><li><a href="module-locale.html">locale</a></li><li><a href="module-math.html">math</a></li><li><a href="module-namespaces.html">namespaces</a></li><li><a href="module-path.html">path</a></li><li><a href="module-recalculate.html">recalculate</a></li><li><a href="module-sanitize.html">sanitize</a></li><li><a href="module-select.html">select</a></li><li><a href="module-selected-elem.html">selected-elem</a></li><li><a href="module-selection.html">selection</a></li><li><a href="module-svg.html">svg</a></li><li><a href="module-svgcanvas.html">svgcanvas</a></li><li><a href="module-SVGEditor.html">SVGEditor</a></li><li><a href="module-text-actions%2520Tools%2520for%2520Text%2520edit%2520functions.html">text-actions Tools for Text edit functions</a></li><li><a href="module-undo.html">undo</a></li><li><a href="module-units.html">units</a></li><li><a href="module-utilities.html">utilities</a></li></ul><h3>Externals</h3><ul><li><a href="external-JamilihArray.html">JamilihArray</a></li><li><a href="external-jQuery.html">jQuery</a></li><li><a href="external-Math.html">Math</a></li><li><a href="external-MouseEvent.html">MouseEvent</a></li><li><a href="external-Window.html">Window</a></li></ul><h3>Namespaces</h3><ul><li><a href="external-jQuery.fn.html">fn</a></li><li><a href="external-jQuery.fn.$.fn.jPicker.defaults.html">defaults</a></li><li><a href="external-jQuery.fn.exports.jPickerMethod.html">exports.jPickerMethod</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.html">jGraduateDefaults</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.images.html">images</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.window.html">window</a></li><li><a href="external-jQuery.jGraduate.html">jGraduate</a></li><li><a href="external-jQuery.jPicker.html">jPicker</a></li><li><a href="external-jQuery.jPicker.ColorMethods.html">ColorMethods</a></li><li><a href="module-path.html#.pathActions">pathActions</a></li><li><a href="module-svgcanvas.SvgCanvas_pathActions.html">pathActions</a></li><li><a href="module-svgcanvas.SvgCanvas_textActions.html">textActions</a></li></ul><h3>Classes</h3><ul><li><a href="BottomPanel.html">BottomPanel</a></li><li><a href="configObj.html">configObj</a></li><li><a href="Dropdown.html">Dropdown</a></li><li><a href="EditorStartup.html">EditorStartup</a></li><li><a href="ElixMenuButton.html">ElixMenuButton</a></li><li><a href="ElixNumberSpinBox.html">ElixNumberSpinBox</a></li><li><a href="ExplorerButton.html">ExplorerButton</a></li><li><a href="external-jQuery.jGraduate.Paint.html">Paint</a></li><li><a href="external-jQuery.jPicker.Color.html">Color</a></li><li><a href="FlyingButton.html">FlyingButton</a></li><li><a href="LayersPanel.html">LayersPanel</a></li><li><a href="LeftPanel.html">LeftPanel</a></li><li><a href="MainMenu.html">MainMenu</a></li><li><a href="module.exports.html">exports</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="module-draw.Drawing.html">Drawing</a></li><li><a href="module-draw.Layer.html">Layer</a></li><li><a href="module-history.BatchCommand.html">BatchCommand</a></li><li><a href="module-history.ChangeElementCommand.html">ChangeElementCommand</a></li><li><a href="module-history.Command.html">Command</a></li><li><a href="module-history.HistoryRecordingService.html">HistoryRecordingService</a></li><li><a href="module-history.InsertElementCommand.html">InsertElementCommand</a></li><li><a href="module-history.MoveElementCommand.html">MoveElementCommand</a></li><li><a href="module-history.RemoveElementCommand.html">RemoveElementCommand</a></li><li><a href="module-history.UndoManager.html">UndoManager</a></li><li><a href="module-jPicker.module.exports.html">module.exports</a></li><li><a href="module-layer.Layer.html">Layer</a></li><li><a href="module-path.Path.html">Path</a></li><li><a href="module-path.Segment.html">Segment</a></li><li><a href="module-select.Selector.html">Selector</a></li><li><a href="module-select.SelectorManager.html">SelectorManager</a></li><li><a href="module-svgcanvas.SvgCanvas.html">SvgCanvas</a></li><li><a href="module-SVGEditor-Editor.html">Editor</a></li><li><a href="NumberSpinBox.html">NumberSpinBox</a></li><li><a href="PaintBox.html">PaintBox</a></li><li><a href="PlainNumberSpinBox.html">PlainNumberSpinBox</a></li><li><a href="Rulers.html">Rulers</a></li><li><a href="SeCMenuDialog.html">SeCMenuDialog</a></li><li><a href="SeCMenuLayerDialog.html">SeCMenuLayerDialog</a></li><li><a href="SeColorPicker.html">SeColorPicker</a></li><li><a href="SeEditPrefsDialog.html">SeEditPrefsDialog</a></li><li><a href="SeExportDialog.html">SeExportDialog</a></li><li><a href="SeImgPropDialog.html">SeImgPropDialog</a></li><li><a href="SEInput.html">SEInput</a></li><li><a href="SeList.html">SeList</a></li><li><a href="SeMenu.html">SeMenu</a></li><li><a href="SeMenuItem.html">SeMenuItem</a></li><li><a href="SEPalette.html">SEPalette</a></li><li><a href="SePlainAlertDialog.html">SePlainAlertDialog</a></li><li><a href="SePlainBorderButton.html">SePlainBorderButton</a></li><li><a href="SePromptDialog.html">SePromptDialog</a></li><li><a href="SESpinInput.html">SESpinInput</a></li><li><a href="SeStorageDialog.html">SeStorageDialog</a></li><li><a href="SeSvgSourceEditorDialog.html">SeSvgSourceEditorDialog</a></li><li><a href="SeText.html">SeText</a></li><li><a href="ToolButton.html">ToolButton</a></li><li><a href="TopPanel.html">TopPanel</a></li></ul><h3>Interfaces</h3><ul><li><a href="module-coords.EditorContext.html">EditorContext</a></li><li><a href="module-draw.DrawCanvasInit.html">DrawCanvasInit</a></li><li><a href="module-history.HistoryCommand.html">HistoryCommand</a></li><li><a href="module-history.HistoryEventHandler.html">HistoryEventHandler</a></li><li><a href="module-locale.LocaleEditorInit.html">LocaleEditorInit</a></li><li><a href="module-path.EditorContext.html">EditorContext</a></li><li><a href="module-recalculate.EditorContext.html">EditorContext</a></li><li><a href="module-select.SVGFactory.html">SVGFactory</a></li><li><a href="module-svgcanvas.PrivateMethods.html">PrivateMethods</a></li><li><a href="module-SVGEditor.Config.html">Config</a></li><li><a href="module-SVGEditor.Prefs.html">Prefs</a></li><li><a href="module-SVGthis.CustomHandler.html">CustomHandler</a></li><li><a href="module-units.ElementContainer.html">ElementContainer</a></li><li><a href="module-utilities.EditorContext.html">EditorContext</a></li></ul><h3>Events</h3><ul><li><a href="module-history-Command.html#event:event:history">history</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:changed">changed</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:cleared">cleared</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:contextset">contextset</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:exported">exported</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:exportedPDF">exportedPDF</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_addLangData">ext_addLangData</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_callback">ext_callback</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_canvasUpdated">ext_canvasUpdated</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_elementChanged">ext_elementChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_elementTransition">ext_elementTransition</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_IDsUpdated">ext_IDsUpdated</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_langChanged">ext_langChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_langReady">ext_langReady</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseDown">ext_mouseDown</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseMove">ext_mouseMove</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseUp">ext_mouseUp</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_onNewDocument">ext_onNewDocument</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_selectedChanged">ext_selectedChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_toolButtonStateUpdate">ext_toolButtonStateUpdate</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_workareaResized">ext_workareaResized</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_zoomChanged">ext_zoomChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:extension_added">extension_added</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:extensions_added">extensions_added</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:GenericCanvasEvent">GenericCanvasEvent</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:message">message</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:pointsAdded">pointsAdded</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:saved">saved</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:selected">selected</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:setnonce">setnonce</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:transition">transition</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:unsetnonce">unsetnonce</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:updateCanvas">updateCanvas</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:zoomDone">zoomDone</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:zoomed">zoomed</a></li><li><a href="module-SVGEditor.html#event:event:svgEditorReadyEvent">svgEditorReadyEvent</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-CanvasAPI.html">CanvasAPI</a></li><li><a href="tutorial-Editor.html">Editor</a></li><li><a href="tutorial-EditorAPI.html">EditorAPI</a></li><li><a href="tutorial-Events.html">Events</a></li><li><a href="tutorial-FrequentlyAskedQuestions.html">Frequently Asked Questions (FAQ)</a></li></ul><h3>Global</h3><ul><li><a href="global.html#attributeChangedCallback">attributeChangedCallback</a></li><li><a href="global.html#connectedCallback">connectedCallback</a></li><li><a href="global.html#constructor">constructor</a></li><li><a href="global.html#expireCookie">expireCookie</a></li><li><a href="global.html#findPos">findPos</a></li><li><a href="global.html#formatValueFormatthenumericvalueasastring.Thisisusedafterincrementing/decrementingthevaluetoreformatthevalueasastring.">formatValue Format the numeric value as a string. This is used after incrementing/decrementing the value to reformat the value as a string.</a></li><li><a href="global.html#get">get</a></li><li><a href="global.html#getClosest">getClosest</a></li><li><a href="global.html#getParents">getParents</a></li><li><a href="global.html#init">init</a></li><li><a href="global.html#inputsize">inputsize</a></li><li><a href="global.html#isNullish">isNullish</a></li><li><a href="global.html#loadloadConfig">load load Config</a></li><li><a href="global.html#loadFromURLLoadconfig/datafromURLifgiven">loadFromURL Load config/data from URL if given</a></li><li><a href="global.html#name">name</a></li><li><a href="global.html#observedAttributes">observedAttributes</a></li><li><a href="global.html#parseValue">parseValue</a></li><li><a href="global.html#pref">pref</a></li><li><a href="global.html#processResults">processResults</a></li><li><a href="global.html#readySignal">readySignal</a></li><li><a href="global.html#regexEscape">regexEscape</a></li><li><a href="global.html#removeStoragePrefCookie">removeStoragePrefCookie</a></li><li><a href="global.html#replaceStoragePrompt">replaceStoragePrompt</a></li><li><a href="global.html#set">set</a></li><li><a href="global.html#setupCurConfig">setupCurConfig</a></li><li><a href="global.html#setupCurPrefs">setupCurPrefs</a></li><li><a href="global.html#src">src</a></li><li><a href="global.html#stateEffects">stateEffects</a></li><li><a href="global.html#stepDown">stepDown</a></li><li><a href="global.html#stepUp">stepUp</a></li><li><a href="global.html#touchHandler">touchHandler</a></li><li><a href="global.html#updateLib">updateLib</a></li><li><a href="global.html#value">value</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.7</a> on Mon Nov 08 2021 09:47:00 GMT+0100 (Central European Standard Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>