- Breaking change: loadSvgString now returns a Promise rather than accepting a callback

- Breaking change: Treat callbacks to `editor.ready` as Promises, only resolving after all resolve
- Breaking change: Make `editor.runCallbacks` return a `Promise` which resolves upon all callbacks resolving
- Breaking change: Require `npx` (used with `babel-node`) to allow Node files
  for HTML building and JSDoc type checking to be expressed as ESM.
- Breaking change: `addExtension` now throws upon a repeated attempt to add an already-added extension
- Breaking change (storage preference cookies): Namespace the cookie as "svgeditstore" instead of just "store"
- Breaking change (API): Remove `svgCanvas.rasterExport` fourth (callback) argument, collapsing fifth (options) to fourth
- Breaking change (API): Remove `svgCanvas.exportPDF` third (callback) argument
- Breaking change (API): `editor/contextmenu.js` `add` now throws instead of giving a console error only upon detecting a bad menuitem or preexisting context menu
- Breaking change (API): Remove `svgCanvas.embedImage` second (callback) argument
- Breaking change (API): Make `getHelpXML` a class instead of instance method of `RGBColor`
- Breaking change (internal API): Refactor `dbox` (and `alert`/`confirm`/`process`/`prompt`/`select`) to avoid a callback argument in favor of return a Promise
- Fix: Avoid running in extension `langReady` multiple times or serially
- Enhancement (API): Add svgCanvas.runExtension to run just one extension and add `nameFilter` callback to `runExtensions`
- Enhancement (API): Supply `$` (our wrapped jQuery) to extensions so can use its plugins, e.g., dbox with its `alert`
- Enhancement: Use alert dialog in place of `alert` in webappfind
- Enhancement: `editor.ready` now returns a Promise resolving when all callbacks have resolved
- Enhancement: Allow `noAlert` option as part of second argument to `loadSvgString` (and `loadFromURL` and `loadFromDataURI`) to avoid UI alert (and trigger promise rejection)
- Enhancement: Make `dbox` as a separate module for alert, prompt, etc. dialogs
- Refactoring: Internal `PaintBox` as class; other misc. tweaks; no bitwise in canvg
- Linting (ESLint): Further linting changes (for editor); rename `.eslintrc` -> `.eslintrc.json` per recommendation
- Optimization: Recompress images (imageoptim-cli updated)
- npm: Update devDeps
- npm: Bump to 4.0.0
This commit is contained in:
Brett Zamir
2018-11-08 14:48:01 +08:00
parent 7c470e9909
commit 2e5c7557a9
159 changed files with 25502 additions and 15658 deletions

View File

@@ -1,4 +1,4 @@
/* eslint-disable new-cap */
/* eslint-disable new-cap, class-methods-use-this */
// Todo: Compare with latest canvg (add any improvements of ours) and add full JSDocs (denoting links to standard APIs and which are custom): https://github.com/canvg/canvg
/**
* canvg.js - Javascript SVG parser and renderer on Canvas
@@ -20,32 +20,11 @@ const isNullish = (val) => {
return val === null || val === undefined;
};
let canvasRGBA_ = canvasRGBA;
/**
* @callback module:canvg.StackBlurCanvasRGBA
* @param {string} id
* @param {Float} x
* @param {Float} y
* @param {Float} width
* @param {Float} height
* @param {Float} blurRadius
*/
/**
* @callback module:canvg.ForceRedraw
* @returns {boolean}
*/
/**
* @function module:canvg.setStackBlurCanvasRGBA
* @param {module:canvg.StackBlurCanvasRGBA} cb Will be passed the canvas ID, x, y, width, height, blurRadius
* @returns {undefined}
*/
export const setStackBlurCanvasRGBA = (cb) => {
canvasRGBA_ = cb;
};
/**
* @typedef {PlainObject} module:canvg.CanvgOptions
* @property {boolean} opts.ignoreMouse true => ignore mouse events
@@ -175,12 +154,13 @@ function build (opts) {
};
// ajax
// Todo: Replace with `fetch` and polyfill
svg.ajax = function (url, asynch) {
const AJAX = window.XMLHttpRequest
? new XMLHttpRequest()
: new window.ActiveXObject('Microsoft.XMLHTTP');
if (asynch) {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
const req = AJAX.open('GET', url, true);
req.addEventListener('load', () => {
resolve(AJAX.responseText);
@@ -393,12 +373,12 @@ function build (opts) {
Parse (s) {
const f = {};
const d = svg.trim(svg.compressSpaces(s || '')).split(' ');
const ds = svg.trim(svg.compressSpaces(s || '')).split(' ');
const set = {
fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false
};
let ff = '';
d.forEach((d) => {
ds.forEach((d) => {
if (!set.fontStyle && this.Styles.includes(d)) {
if (d !== 'inherit') {
f.fontStyle = d;
@@ -419,8 +399,8 @@ function build (opts) {
f.fontSize = d.split('/')[0];
}
set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true;
} else {
if (d !== 'inherit') { ff += d; }
} else if (d !== 'inherit') {
ff += d;
}
});
if (ff !== '') { f.fontFamily = ff; }
@@ -431,7 +411,7 @@ function build (opts) {
// points and paths
svg.ToNumberArray = function (s) {
const a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
return a.map((a) => parseFloat(a));
return a.map((_a) => parseFloat(_a));
};
svg.Point = class {
constructor (x, y) {
@@ -598,9 +578,9 @@ function build (opts) {
ctx.translate(-this.cx, -this.cy);
};
this.applyToPoint = function (p) {
const a = this.angle.toRadians();
const _a = this.angle.toRadians();
p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
p.applyTransform([Math.cos(_a), Math.sin(_a), -Math.sin(_a), Math.cos(_a), 0, 0]);
p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
};
}
@@ -727,7 +707,8 @@ function build (opts) {
svg.Element.ElementBase = class {
constructor (node) {
this.captureTextNodes = arguments[1]; // Argument from inheriting class
// Argument from inheriting class
this.captureTextNodes = arguments[1]; // eslint-disable-line prefer-rest-params
this.attributes = {};
this.styles = {};
this.children = [];
@@ -757,9 +738,9 @@ function build (opts) {
// add tag styles
let styles = svg.Styles[node.nodeName];
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
Object.entries(styles).forEach(([name, styleValue]) => {
this.styles[name] = styleValue;
});
}
// add class styles
@@ -768,33 +749,33 @@ function build (opts) {
classes.forEach((clss) => {
styles = svg.Styles['.' + clss];
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
Object.entries(styles).forEach(([name, styleValue]) => {
this.styles[name] = styleValue;
});
}
styles = svg.Styles[node.nodeName + '.' + clss];
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
Object.entries(styles).forEach(([name, styleValue]) => {
this.styles[name] = styleValue;
});
}
});
}
// add id styles
if (this.attribute('id').hasValue()) {
const styles = svg.Styles['#' + this.attribute('id').value];
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
const _styles = svg.Styles['#' + this.attribute('id').value];
if (!isNullish(_styles)) {
Object.entries(_styles).forEach(([name, styleValue]) => {
this.styles[name] = styleValue;
});
}
}
// add inline styles
if (this.attribute('style').hasValue()) {
const styles = this.attribute('style').value.split(';');
styles.forEach((style) => {
const _styles = this.attribute('style').value.split(';');
_styles.forEach((style) => {
if (svg.trim(style) !== '') {
let {name, value} = style.split(':');
name = svg.trim(name);
@@ -1232,9 +1213,9 @@ function build (opts) {
ctx.moveTo(x, y);
}
for (let i = 1; i < this.points.length; i++) {
const {x, y} = this.points[i];
bb.addPoint(x, y);
if (!isNullish(ctx)) ctx.lineTo(x, y);
const {x: _x, y: _y} = this.points[i];
bb.addPoint(_x, _y);
if (!isNullish(ctx)) ctx.lineTo(_x, _y);
}
return bb;
}
@@ -1410,20 +1391,20 @@ function build (opts) {
pp.nextCommand();
switch (pp.command) {
case 'M':
case 'm':
case 'm': {
const p = pp.getAsCurrentPoint();
pp.addMarker(p);
bb.addPoint(p.x, p.y);
if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);
pp.start = pp.current;
while (!pp.isCommandOrEnd()) {
const p = pp.getAsCurrentPoint();
pp.addMarker(p, pp.start);
bb.addPoint(p.x, p.y);
if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);
const _p = pp.getAsCurrentPoint();
pp.addMarker(_p, pp.start);
bb.addPoint(_p.x, _p.y);
if (!isNullish(ctx)) ctx.lineTo(_p.x, _p.y);
}
break;
case 'L':
} case 'L':
case 'l':
while (!pp.isCommandOrEnd()) {
const c = pp.current;
@@ -1570,14 +1551,14 @@ function build (opts) {
bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
if (!isNullish(ctx)) {
const r = rx > ry ? rx : ry;
const _r = rx > ry ? rx : ry;
const sx = rx > ry ? 1 : rx / ry;
const sy = rx > ry ? ry / rx : 1;
ctx.translate(centp.x, centp.y);
ctx.rotate(xAxisRotation);
ctx.scale(sx, sy);
ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
ctx.arc(0, 0, _r, a1, a1 + ad, 1 - sweepFlag);
ctx.scale(1 / sx, 1 / sy);
ctx.rotate(-xAxisRotation);
ctx.translate(-centp.x, -centp.y);
@@ -2115,8 +2096,8 @@ function build (opts) {
child.render(ctx);
for (let i = 0; i < child.children.length; i++) {
this.renderChild(ctx, child, i);
for (let j = 0; j < child.children.length; j++) {
this.renderChild(ctx, child, j);
}
}
};
@@ -2309,9 +2290,12 @@ function build (opts) {
};
this.img.src = href;
} else {
svg.ajax(href, true).then((img) => {
svg.ajax(href, true).then((img) => { // eslint-disable-line promise/prefer-await-to-then, promise/always-return
this.img = img;
this.loaded = true;
}).catch((err) => { // eslint-disable-line promise/prefer-await-to-callbacks
this.erred = true;
console.error('Ajax error for canvg', err); // eslint-disable-line no-console
});
}
}
@@ -2448,6 +2432,7 @@ function build (opts) {
getBoundingBox () {
const {_el: element} = this;
if (!isNullish(element)) return element.getBoundingBox();
return undefined;
}
renderChildren (ctx) {
@@ -2605,10 +2590,29 @@ function build (opts) {
}
};
/**
* @param {Uint8ClampedArray} img
* @param {Integer} x
* @param {Integer} y
* @param {Float} width
* @param {Float} height
* @param {Integer} rgba
* @returns {undefined}
*/
function imGet (img, x, y, width, height, rgba) {
return img[y * width * 4 + x * 4 + rgba];
}
/**
* @param {Uint8ClampedArray} img
* @param {Integer} x
* @param {Integer} y
* @param {Float} width
* @param {Float} height
* @param {Integer} rgba
* @param {Float} val
* @returns {undefined}
*/
function imSet (img, x, y, width, height, rgba, val) {
img[y * width * 4 + x * 4 + rgba] = val;
}
@@ -2619,7 +2623,7 @@ function build (opts) {
let matrix = svg.ToNumberArray(this.attribute('values').value);
switch (this.attribute('type').valueOrDefault('matrix')) { // https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement
case 'saturate':
case 'saturate': {
const s = matrix[0];
matrix = [
0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0,
@@ -2629,7 +2633,7 @@ function build (opts) {
0, 0, 0, 0, 1
];
break;
case 'hueRotate':
} case 'hueRotate': {
const a = matrix[0] * Math.PI / 180.0;
const c = function (m1, m2, m3) {
return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;
@@ -2642,7 +2646,7 @@ function build (opts) {
0, 0, 0, 0, 1
];
break;
case 'luminanceToAlpha':
} case 'luminanceToAlpha':
matrix = [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
@@ -2663,16 +2667,16 @@ function build (opts) {
const {_m: m} = this;
// assuming x==0 && y==0 for now
const srcData = ctx.getImageData(0, 0, width, height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const r = imGet(srcData.data, x, y, width, height, 0);
const g = imGet(srcData.data, x, y, width, height, 1);
const b = imGet(srcData.data, x, y, width, height, 2);
const a = imGet(srcData.data, x, y, width, height, 3);
imSet(srcData.data, x, y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));
imSet(srcData.data, x, y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));
imSet(srcData.data, x, y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));
imSet(srcData.data, x, y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));
for (let _y = 0; _y < height; _y++) {
for (let _x = 0; _x < width; _x++) {
const r = imGet(srcData.data, _x, _y, width, height, 0);
const g = imGet(srcData.data, _x, _y, width, height, 1);
const b = imGet(srcData.data, _x, _y, width, height, 2);
const a = imGet(srcData.data, _x, _y, width, height, 3);
imSet(srcData.data, _x, _y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));
imSet(srcData.data, _x, _y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));
imSet(srcData.data, _x, _y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));
imSet(srcData.data, _x, _y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));
}
}
ctx.clearRect(0, 0, width, height);
@@ -2689,17 +2693,12 @@ function build (opts) {
}
apply (ctx, x, y, width, height) {
if (typeof canvasRGBA_ !== 'function') {
svg.log('ERROR: The function `setStackBlurCanvasRGBA` must be present for blur to work');
return;
}
// Todo: This might not be a problem anymore with out `instanceof` fix
// StackBlur requires canvas be on document
ctx.canvas.id = svg.UniqueId();
ctx.canvas.style.display = 'none';
document.body.append(ctx.canvas);
canvasRGBA_(ctx.canvas, x, y, width, height, this.blurRadius);
canvasRGBA(ctx.canvas, x, y, width, height, this.blurRadius);
ctx.canvas.remove();
}
};
@@ -2773,14 +2772,14 @@ function build (opts) {
ctx.canvas.onclick = function (e) {
const args = !isNullish(e)
? [e.clientX, e.clientY]
: [event.clientX, event.clientY];
: [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals
const {x, y} = mapXY(new svg.Point(...args));
svg.Mouse.onclick(x, y);
};
ctx.canvas.onmousemove = function (e) {
const args = !isNullish(e)
? [e.clientX, e.clientY]
: [event.clientX, event.clientY];
: [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals
const {x, y} = mapXY(new svg.Point(...args));
svg.Mouse.onmousemove(x, y);
};
@@ -2878,13 +2877,14 @@ function build (opts) {
// need update from mouse events?
if (svg.opts.ignoreMouse !== true) {
needUpdate = needUpdate | svg.Mouse.hasEvents();
needUpdate = needUpdate || svg.Mouse.hasEvents();
}
// need update from animations?
if (svg.opts.ignoreAnimation !== true) {
svg.Animations.forEach((animation) => {
needUpdate = needUpdate | animation.update(1000 / svg.FRAMERATE);
const needAnimationUpdate = animation.update(1000 / svg.FRAMERATE);
needUpdate = needUpdate || needAnimationUpdate;
});
}
@@ -2901,7 +2901,8 @@ function build (opts) {
svg.Mouse.runEvents(); // run and clear our events
}
}, 1000 / svg.FRAMERATE);
return new Promise((resolve, reject) => {
// Todo: Replace with an image loading Promise utility?
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
if (svg.ImagesLoaded()) {
waitingForImages = false;
draw(resolve);

View File

@@ -156,34 +156,22 @@ const colorDefs = [
{
re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
process (bits) {
return [
parseInt(bits[1]),
parseInt(bits[2]),
parseInt(bits[3])
];
process (_, ...bits) {
return bits.map((b) => parseInt(b));
}
},
{
re: /^(\w{2})(\w{2})(\w{2})$/,
example: ['#00ff00', '336699'],
process (bits) {
return [
parseInt(bits[1], 16),
parseInt(bits[2], 16),
parseInt(bits[3], 16)
];
process (_, ...bits) {
return bits.map((b) => parseInt(b, 16));
}
},
{
re: /^(\w{1})(\w{1})(\w{1})$/,
example: ['#fb0', 'f0f'],
process (bits) {
return [
parseInt(bits[1] + bits[1], 16),
parseInt(bits[2] + bits[2], 16),
parseInt(bits[3] + bits[3], 16)
];
process (_, ...bits) {
return bits.map((b) => parseInt(b + b, 16));
}
}
];
@@ -214,16 +202,15 @@ export default class RGBColor {
// end of simple type-in colors
// search through the definitions to find a match
for (let i = 0; i < colorDefs.length; i++) {
const {re} = colorDefs[i];
const processor = colorDefs[i].process;
colorDefs.forEach(({re, process: processor}) => {
const bits = re.exec(colorString);
if (bits) {
const [r, g, b] = processor(bits);
Object.assign(this, {r, g, b});
this.ok = true;
}
}
}, this);
// validate/cleanup values
this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
@@ -251,41 +238,42 @@ export default class RGBColor {
if (b.length === 1) { b = '0' + b; }
return '#' + r + g + b;
}
}
/**
* Offers a bulleted list of help.
* @returns {HTMLUListElement}
*/
getHelpXML () {
const examples = [];
/**
* Offers a bulleted list of help.
* @returns {HTMLUListElement}
*/
RGBColor.getHelpXML = function () {
const examples = [
// add regexps
for (let i = 0; i < colorDefs.length; i++) {
const {example} = colorDefs[i];
for (let j = 0; j < example.length; j++) {
examples[examples.length] = example[j];
}
}
...colorDefs.flatMap(({example}) => {
return example;
}),
// add type-in colors
examples.push(...Object.keys(simpleColors));
...Object.keys(simpleColors)
];
const xml = document.createElement('ul');
xml.setAttribute('id', 'rgbcolor-examples');
for (let i = 0; i < examples.length; i++) {
try {
const listItem = document.createElement('li');
const listColor = new RGBColor(examples[i]);
const exampleDiv = document.createElement('div');
exampleDiv.style.cssText = `
const xml = document.createElement('ul');
xml.setAttribute('id', 'rgbcolor-examples');
xml.append(...examples.map((example) => {
try {
const listItem = document.createElement('li');
const listColor = new RGBColor(example);
const exampleDiv = document.createElement('div');
exampleDiv.style.cssText = `
margin: 3px;
border: 1px solid black;
background: ${listColor.toHex()};
color: ${listColor.toHex()};`;
exampleDiv.append('test');
const listItemValue = ` ${examples[i]} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;
listItem.append(exampleDiv, listItemValue);
xml.append(listItem);
} catch (e) {}
exampleDiv.append('test');
const listItemValue = ` ${example} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;
listItem.append(exampleDiv, listItemValue);
return listItem;
} catch (e) {
return '';
}
return xml;
}
}
}));
return xml;
};

View File

@@ -36,20 +36,19 @@ const menuItemIsValid = function (menuItem) {
/**
* @function module:contextmenu.add
* @param {module:contextmenu.MenuItem} menuItem
* @throws {Error|TypeError}
* @returns {undefined}
*/
export const add = function (menuItem) {
// menuItem: {id, label, shortcut, action}
if (!menuItemIsValid(menuItem)) {
console.error('Menu items must be defined and have at least properties: id, label, action, where action must be a function');
return;
throw new TypeError('Menu items must be defined and have at least properties: id, label, action, where action must be a function');
}
if (menuItem.id in contextMenuExtensions) {
console.error('Cannot add extension "' + menuItem.id + '", an extension by that name already exists"');
return;
throw new Error('Cannot add extension "' + menuItem.id + '", an extension by that name already exists"');
}
// Register menuItem action, see below for deferred menu dom injection
console.log('Registed contextmenu item: {id:' + menuItem.id + ', label:' + menuItem.label + '}');
console.log('Registered contextmenu item: {id:' + menuItem.id + ', label:' + menuItem.label + '}'); // eslint-disable-line no-console
contextMenuExtensions[menuItem.id] = menuItem;
// TODO: Need to consider how to handle custom enable/disable behavior
};

View File

@@ -20,7 +20,7 @@
import {isMac} from '../browser.js';
/**
* @callback module:jQueryContextMenu.jQueryContextMenuCallback
* @callback module:jQueryContextMenu.jQueryContextMenuListener
* @param {string} href The `href` value after the first character (for bypassing an initial `#`)
* @param {external:jQuery} srcElement The wrapped jQuery srcElement
* @param {{x: Float, y: Float, docX: Float, docY: Float}} coords
@@ -53,10 +53,10 @@ function jQueryContextMenu ($) {
/**
* @memberof external:jQuery.fn
* @param {module:jQueryContextMenu.jQueryContextMenuConfig} o
* @param {module:jQueryContextMenu.jQueryContextMenuCallback} callback
* @param {module:jQueryContextMenu.jQueryContextMenuListener} listener
* @returns {external:jQuery}
*/
contextMenu (o, callback) {
contextMenu (o, listener) {
// Defaults
if (o.menu === undefined) return false;
if (o.inSpeed === undefined) o.inSpeed = 150;
@@ -78,86 +78,88 @@ function jQueryContextMenu ($) {
$(this).mouseup(function (e) {
const srcElement = $(this);
srcElement.unbind('mouseup');
if (evt.button === 2 || o.allowLeft ||
(evt.ctrlKey && isMac())) {
e.stopPropagation();
// Hide context menus that may be showing
if (!(evt.button === 2 || o.allowLeft ||
(evt.ctrlKey && isMac()))) {
return undefined;
}
e.stopPropagation();
// Hide context menus that may be showing
$('.contextMenu').hide();
// Get this context menu
if (el.hasClass('disabled')) return false;
// Detect mouse position
let x = e.pageX, y = e.pageY;
const xOff = win.width() - menu.width(),
yOff = win.height() - menu.height();
if (x > xOff - 15) x = xOff - 15;
if (y > yOff - 30) y = yOff - 30; // 30 is needed to prevent scrollbars in FF
// Show the menu
doc.unbind('click');
menu.css({top: y, left: x}).fadeIn(o.inSpeed);
// Hover events
menu.find('A').mouseover(function () {
menu.find('LI.hover').removeClass('hover');
$(this).parent().addClass('hover');
}).mouseout(function () {
menu.find('LI.hover').removeClass('hover');
});
// Keyboard
doc.keypress(function (ev) {
switch (ev.keyCode) {
case 38: // up
if (!menu.find('LI.hover').length) {
menu.find('LI:last').addClass('hover');
} else {
menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
if (!menu.find('LI.hover').length) menu.find('LI:last').addClass('hover');
}
break;
case 40: // down
if (!menu.find('LI.hover').length) {
menu.find('LI:first').addClass('hover');
} else {
menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
if (!menu.find('LI.hover').length) menu.find('LI:first').addClass('hover');
}
break;
case 13: // enter
menu.find('LI.hover A').trigger('click');
break;
case 27: // esc
doc.trigger('click');
break;
}
});
// When items are selected
menu.find('A').unbind('mouseup');
menu.find('LI:not(.disabled) A').mouseup(function () {
doc.unbind('click').unbind('keypress');
$('.contextMenu').hide();
// Get this context menu
if (listener) {
listener($(this).attr('href').substr(1), $(srcElement), {
x: x - offset.left, y: y - offset.top, docX: x, docY: y
});
}
return false;
});
if (el.hasClass('disabled')) return false;
// Detect mouse position
let x = e.pageX, y = e.pageY;
const xOff = win.width() - menu.width(),
yOff = win.height() - menu.height();
if (x > xOff - 15) x = xOff - 15;
if (y > yOff - 30) y = yOff - 30; // 30 is needed to prevent scrollbars in FF
// Show the menu
doc.unbind('click');
menu.css({top: y, left: x}).fadeIn(o.inSpeed);
// Hover events
menu.find('A').mouseover(function () {
menu.find('LI.hover').removeClass('hover');
$(this).parent().addClass('hover');
}).mouseout(function () {
menu.find('LI.hover').removeClass('hover');
});
// Keyboard
doc.keypress(function (ev) {
switch (ev.keyCode) {
case 38: // up
if (!menu.find('LI.hover').length) {
menu.find('LI:last').addClass('hover');
} else {
menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
if (!menu.find('LI.hover').length) menu.find('LI:last').addClass('hover');
}
break;
case 40: // down
if (!menu.find('LI.hover').length) {
menu.find('LI:first').addClass('hover');
} else {
menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
if (!menu.find('LI.hover').length) menu.find('LI:first').addClass('hover');
}
break;
case 13: // enter
menu.find('LI.hover A').trigger('click');
break;
case 27: // esc
doc.trigger('click');
break;
}
});
// When items are selected
menu.find('A').unbind('mouseup');
menu.find('LI:not(.disabled) A').mouseup(function () {
// Hide bindings
setTimeout(function () { // Delay for Mozilla
doc.click(function () {
doc.unbind('click').unbind('keypress');
$('.contextMenu').hide();
// Callback
if (callback) {
callback($(this).attr('href').substr(1), $(srcElement), {
x: x - offset.left, y: y - offset.top, docX: x, docY: y
});
}
menu.fadeOut(o.outSpeed);
return false;
});
// Hide bindings
setTimeout(function () { // Delay for Mozilla
doc.click(function () {
doc.unbind('click').unbind('keypress');
menu.fadeOut(o.outSpeed);
return false;
});
}, 0);
}
}, 0);
return undefined;
});
});

165
editor/dbox.js Normal file
View File

@@ -0,0 +1,165 @@
/**
* @module jQueryPluginDBox
*/
/**
* @param {external:jQuery} $
* @param {PlainObject} [strings]
* @param {PlainObject} [strings.ok]
* @param {PlainObject} [strings.cancel]
* @returns {external:jQuery}
*/
export default function jQueryPluginDBox ($, strings = {ok: 'Ok', cancel: 'Cancel'}) {
// This sets up alternative dialog boxes. They mostly work the same way as
// their UI counterparts, expect instead of returning the result, a callback
// needs to be included that returns the result as its first parameter.
// In the future we may want to add additional types of dialog boxes, since
// they should be easy to handle this way.
$('#dialog_container').draggable({
cancel: '#dialog_content, #dialog_buttons *',
containment: 'window'
}).css('position', 'absolute');
const box = $('#dialog_box'),
btnHolder = $('#dialog_buttons'),
dialogContent = $('#dialog_content');
/**
* Resolves to `false` (if cancelled), for prompts and selects
* without checkboxes, it resolves to the value of the form control. For other
* types without checkboxes, it resolves to `true`. For checkboxes, it resolves
* to an object with the `response` key containing the same value as the previous
* mentioned (string or `true`) and a `checked` (boolean) property.
* @typedef {Promise} module:jQueryPluginDBox.PromiseResult
*/
/**
* @typedef {PlainObject} module:jQueryPluginDBox.SelectOption
* @property {string} text
* @property {string} value
*/
/**
* @typedef {PlainObject} module:jQueryPluginDBox.CheckboxInfo
* @property {string} label Label for the checkbox
* @property {string} value Value of the checkbox
* @property {string} tooltip Tooltip on the checkbox label
* @property {boolean} checked Whether the checkbox is checked by default
*/
/**
* Triggered upon a change of value for the select pull-down.
* @callback module:jQueryPluginDBox.SelectChangeListener
* @returns {undefined}
*/
/**
* @param {"alert"|"prompt"|"select"|"process"} type
* @param {string} msg
* @param {string} [defaultVal]
* @param {module:jQueryPluginDBox.SelectOption[]} [opts]
* @param {module:jQueryPluginDBox.SelectChangeListener} [changeListener]
* @param {module:jQueryPluginDBox.CheckboxInfo} [checkbox]
* @returns {jQueryPluginDBox.PromiseResult}
*/
function dbox (type, msg, defaultVal, opts, changeListener, checkbox) {
dialogContent.html('<p>' + msg.replace(/\n/g, '</p><p>') + '</p>')
.toggleClass('prompt', (type === 'prompt'));
btnHolder.empty();
const ok = $('<input type="button" value="' + strings.ok + '">').appendTo(btnHolder);
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
if (type !== 'alert') {
$('<input type="button" value="' + strings.cancel + '">')
.appendTo(btnHolder)
.click(function () {
box.hide();
resolve(false);
});
}
let ctrl, chkbx;
if (type === 'prompt') {
ctrl = $('<input type="text">').prependTo(btnHolder);
ctrl.val(defaultVal || '');
ctrl.bind('keydown', 'return', function () { ok.click(); });
} else if (type === 'select') {
const div = $('<div style="text-align:center;">');
ctrl = $('<select>').appendTo(div);
if (checkbox) {
const label = $('<label>').text(checkbox.label);
chkbx = $('<input type="checkbox">').appendTo(label);
chkbx.val(checkbox.value);
if (checkbox.tooltip) {
label.attr('title', checkbox.tooltip);
}
chkbx.prop('checked', Boolean(checkbox.checked));
div.append($('<div>').append(label));
}
$.each(opts || [], function (opt, val) {
if (typeof val === 'object') {
ctrl.append($('<option>').val(val.value).html(val.text));
} else {
ctrl.append($('<option>').html(val));
}
});
dialogContent.append(div);
if (defaultVal) {
ctrl.val(defaultVal);
}
if (changeListener) {
ctrl.bind('change', 'return', changeListener);
}
ctrl.bind('keydown', 'return', function () { ok.click(); });
} else if (type === 'process') {
ok.hide();
}
box.show();
ok.click(function () {
box.hide();
const response = (type === 'prompt' || type === 'select') ? ctrl.val() : true;
if (chkbx) {
resolve({response, checked: chkbx.prop('checked')});
return;
}
resolve(response);
}).focus();
if (type === 'prompt' || type === 'select') {
ctrl.focus();
}
});
}
/**
* @param {string} msg Message to alert
* @returns {jQueryPluginDBox.PromiseResult}
*/
$.alert = function (msg) {
return dbox('alert', msg);
};
/**
* @param {string} msg Message for which to ask confirmation
* @returns {jQueryPluginDBox.PromiseResult}
*/
$.confirm = function (msg) {
return dbox('confirm', msg);
};
/**
* @param {string} msg Message to indicate upon cancelable indicator
* @returns {jQueryPluginDBox.PromiseResult}
*/
$.process_cancel = function (msg) {
return dbox('process', msg);
};
/**
* @param {string} msg Message to accompany the prompt
* @param {string} [defaultText=''] The default text to show for the prompt
* @returns {jQueryPluginDBox.PromiseResult}
*/
$.prompt = function (msg, defaultText = '') {
return dbox('prompt', msg, defaultText);
};
$.select = function (msg, opts, changeListener, txt, checkbox) {
return dbox('select', msg, txt, opts, changeListener, checkbox);
};
return $;
}

View File

@@ -10,23 +10,42 @@ const $ = jQuery;
let svgCanvas = null;
/**
* @param {string} data
* @param {string} error
* @returns {undefined}
*/
function handleSvgData (data, error) {
if (error) {
alert('error ' + error);
// Todo: This should be replaced with a general purpose dialog alert library call
alert('error ' + error); // eslint-disable-line no-alert
} else {
alert('Congratulations. Your SVG string is back in the host page, do with it what you will\n\n' + data);
// Todo: This should be replaced with a general purpose dialog alert library call
alert('Congratulations. Your SVG string is back in the host page, do with it what you will\n\n' + data); // eslint-disable-line no-alert
}
}
/**
* Set the canvas with an example SVG string.
* @returns {undefined}
*/
function loadSvg () {
const svgexample = '<svg width="640" height="480" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><g><title>Layer 1</title><rect stroke-width="5" stroke="#000000" fill="#FF0000" id="svg_1" height="35" width="51" y="35" x="32"/><ellipse ry="15" rx="24" stroke-width="5" stroke="#000000" fill="#0000ff" id="svg_2" cy="60" cx="66"/></g></svg>';
svgCanvas.setSvgString(svgexample);
}
/**
*
* @returns {undefined}
*/
function saveSvg () {
svgCanvas.getSvgString()(handleSvgData);
}
/**
* Perform a PNG export.
* @returns {undefined}
*/
function exportPNG () {
svgCanvas.getUIStrings()(function (uiStrings) {
const str = uiStrings.notification.loadingImage;
@@ -41,6 +60,10 @@ function exportPNG () {
});
}
/**
* Perform a PDF export.
* @returns {undefined}
*/
function exportPDF () {
svgCanvas.getUIStrings()(function (uiStrings) {
const str = uiStrings.notification.loadingImage;
@@ -83,7 +106,7 @@ iframe[0].addEventListener('load', function () {
try {
doc = frame.contentDocument || frame.contentWindow.document;
} catch (err) {
console.log('Blocked from accessing document', err);
console.log('Blocked from accessing document', err); // eslint-disable-line no-console
}
if (doc) {
// Todo: Provide a way to get this to occur by `postMessage`

View File

@@ -334,10 +334,10 @@ class EmbeddedSVGEdit {
/**
* @param {string} name
* @param {ArgumentsArray} args Signature dependent on function
* @param {GenericCallback} callback
* @param {GenericCallback} callback (This may be better than a promise in case adding an event.)
* @returns {Integer}
*/
send (name, args, callback) {
send (name, args, callback) { // eslint-disable-line promise/prefer-await-to-callbacks
const that = this;
cbid++;

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-arrows.js
*
@@ -13,22 +12,30 @@ export default {
const strings = await S.importLocale();
const svgEditor = this;
const svgCanvas = svgEditor.canvas;
const $ = jQuery;
const // {svgcontent} = S,
addElem = svgCanvas.addSVGElementFromJson,
{nonce} = S,
{nonce, $} = S,
prefix = 'se_arrow_';
let selElems, arrowprefix, randomizeIds = S.randomize_ids;
function setArrowNonce (window, n) {
/**
* @param {Window} win
* @param {!(string|Integer)} n
* @returns {undefined}
*/
function setArrowNonce (win, n) {
randomizeIds = true;
arrowprefix = prefix + n + '_';
pathdata.fw.id = arrowprefix + 'fw';
pathdata.bk.id = arrowprefix + 'bk';
}
function unsetArrowNonce (window) {
/**
* @param {Window} win
* @returns {undefined}
*/
function unsetArrowNonce (win) {
randomizeIds = false;
arrowprefix = prefix;
pathdata.fw.id = arrowprefix + 'fw';
@@ -49,6 +56,12 @@ export default {
bk: {d: 'm10,0l-10,5l10,5l-5,-5l5,-5z', refx: 2, id: arrowprefix + 'bk'}
};
/**
* Gets linked element.
* @param {Element} elem
* @param {string} attr
* @returns {Element}
*/
function getLinked (elem, attr) {
const str = elem.getAttribute(attr);
if (!str) { return null; }
@@ -59,6 +72,10 @@ export default {
return svgCanvas.getElem(m[1]);
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
$('#arrow_panel').toggle(on);
if (on) {
@@ -88,6 +105,10 @@ export default {
}
}
/**
*
* @returns {undefined}
*/
function resetMarker () {
const el = selElems[0];
el.removeAttribute('marker-start');
@@ -95,6 +116,12 @@ export default {
el.removeAttribute('marker-end');
}
/**
* @param {"bk"|"fw"} dir
* @param {"both"|"mid"|"end"|"start"} type
* @param {string} id
* @returns {Element}
*/
function addMarker (dir, type, id) {
// TODO: Make marker (or use?) per arrow type, since refX can be different
id = id || arrowprefix + dir;
@@ -136,6 +163,10 @@ export default {
return marker;
}
/**
*
* @returns {undefined}
*/
function setArrow () {
resetMarker();
@@ -163,6 +194,10 @@ export default {
svgCanvas.call('changed', selElems);
}
/**
* @param {Element} elem
* @returns {undefined}
*/
function colorChanged (elem) {
const color = elem.getAttribute('stroke');
const mtypes = ['start', 'mid', 'end'];
@@ -183,7 +218,7 @@ export default {
const attrs = $(this).children().attr(['fill', 'd']);
if (attrs.fill === color && attrs.d === curD) {
// Found another marker with this color and this path
newMarker = this;
newMarker = this; // eslint-disable-line consistent-this
}
});
@@ -202,14 +237,16 @@ export default {
// Check if last marker can be removed
let remove = true;
$(S.svgcontent).find('line, polyline, path, polygon').each(function () {
const elem = this;
const element = this; // eslint-disable-line consistent-this
$.each(mtypes, function (j, mtype) {
if ($(elem).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
if ($(element).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
remove = false;
return remove;
}
return undefined;
});
if (!remove) { return false; }
return undefined;
});
// Not found, so can safely remove
@@ -242,9 +279,9 @@ export default {
$('#arrow_list option')[0].id = 'connector_no_arrow';
},
async addLangData ({lang, importLocale}) {
const strings = await importLocale();
const {langList} = await importLocale();
return {
data: strings.langList
data: langList
};
},
selectedChanged (opts) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-closepath.js
*
@@ -13,9 +12,8 @@ import '../svgpathseg.js';
// The button toggles whether the path is open or closed
export default {
name: 'closepath',
async init ({importLocale}) {
async init ({importLocale, $}) {
const strings = await importLocale();
const $ = jQuery;
const svgEditor = this;
let selElems;
const updateButton = function (path) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-connector.js
*
@@ -11,11 +10,10 @@
export default {
name: 'connector',
async init (S) {
const $ = jQuery;
const svgEditor = this;
const svgCanvas = svgEditor.canvas;
const {getElem} = svgCanvas;
const {svgroot, importLocale} = S,
const {$, svgroot, importLocale} = S,
addElem = svgCanvas.addSVGElementFromJson,
selManager = S.selectorManager,
connSel = '.se_connector',
@@ -34,6 +32,14 @@ export default {
connections = [],
selElems = [];
/**
*
* @param {Float} x
* @param {Float} y
* @param {module:utilities.BBoxObject} bb
* @param {Float} offset
* @returns {module:math.XYObject}
*/
function getBBintersect (x, y, bb, offset) {
if (offset) {
offset -= 0;
@@ -66,6 +72,11 @@ export default {
};
}
/**
* @param {"start"|"end"} side
* @param {Element} line
* @returns {Float}
*/
function getOffset (side, line) {
const giveOffset = line.getAttribute('marker-' + side);
// const giveOffset = $(line).data(side+'_off');
@@ -75,6 +86,10 @@ export default {
return giveOffset ? size : 0;
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let connRules = $('#connector_rules');
if (!connRules.length) {
@@ -84,6 +99,14 @@ export default {
$('#connector_panel').toggle(on);
}
/**
* @param {Element} elem
* @param {Integer|"end"} pos
* @param {Float} x
* @param {Float} y
* @param {boolean} [setMid]
* @returns {undefined}
*/
function setPoint (elem, pos, x, y, setMid) {
const pts = elem.points;
const pt = svgroot.createSVGPoint();
@@ -112,6 +135,11 @@ export default {
}
}
/**
* @param {Float} diffX
* @param {Float} diffY
* @returns {undefined}
*/
function updateLine (diffX, diffY) {
// Update line with element
let i = connections.length;
@@ -158,6 +186,10 @@ export default {
// Loop through connectors to see if one is connected to the element
connectors.each(function () {
let addThis;
/**
*
* @returns {undefined}
*/
function add () {
if (elems.includes(this)) {
// Pretend this element is selected
@@ -205,6 +237,10 @@ export default {
});
}
/**
* @param {Element[]} [elems=selElems]
* @returns {undefined}
*/
function updateConnectors (elems) {
// Updates connector lines based on selected elements
// Is not used on mousemove, as it runs getStrokedBBox every time,
@@ -278,7 +314,10 @@ export default {
seNs = svgCanvas.getEditorNS();
}());
// Do on reset
/**
* Do on reset.
* @returns {undefined}
*/
function init () {
// Make sure all connectors have data set
$(svgcontent).find('*').each(function () {
@@ -331,7 +370,7 @@ export default {
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
}),
/* async */ addLangData ({lang, importLocale}) {
/* async */ addLangData ({lang}) { // , importLocale: importLoc
return {
data: strings.langList
};
@@ -344,7 +383,7 @@ export default {
const {curConfig: {initStroke}} = svgEditor;
if (mode === 'connector') {
if (started) { return; }
if (started) { return undefined; }
const mouseTarget = e.target;
@@ -386,6 +425,7 @@ export default {
if (mode === 'select') {
findConnectors();
}
return undefined;
},
mouseMove (opts) {
const zoom = svgCanvas.getZoom();
@@ -433,7 +473,7 @@ export default {
let mouseTarget = e.target;
if (svgCanvas.getMode() !== 'connector') {
return;
return undefined;
}
const fo = $(mouseTarget).closest('foreignObject');
if (fo.length) { mouseTarget = fo[0]; }
@@ -469,6 +509,7 @@ export default {
const dupe = $(svgcontent).find(connSel).filter(function () {
const conn = this.getAttributeNS(seNs, 'connector');
if (conn === connStr || conn === altStr) { return true; }
return false;
});
if (dupe.length) {
$(curLine).remove();
@@ -596,7 +637,7 @@ export default {
// Check validity - the field would be something like 'svg_21 svg_22', but
// if one end is missing, it would be 'svg_21' and therefore fail this test
if (!/. ./.test(elem.attr['se:connector'])) {
if (!(/. ./).test(elem.attr['se:connector'])) {
remove.push(elem.attr.id);
}
}

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-eyedropper.js
*
@@ -13,8 +12,7 @@ export default {
async init (S) {
const strings = await S.importLocale();
const svgEditor = this;
const $ = jQuery;
const {ChangeElementCommand} = S, // , svgcontent,
const {$, ChangeElementCommand} = S, // , svgcontent,
// svgdoc = S.svgroot.parentNode.ownerDocument,
svgCanvas = svgEditor.canvas,
addToHistory = function (cmd) { svgCanvas.undoMgr.addCommandToHistory(cmd); },
@@ -27,6 +25,11 @@ export default {
strokeLinejoin: 'miter'
};
/**
*
* @param {module:svgcanvas.SvgCanvas#event:ext-selectedChanged|module:svgcanvas.SvgCanvas#event:ext-elementChanged} opts
* @returns {undefined}
*/
function getStyle (opts) {
// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
const mode = svgCanvas.getMode();

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-foreignobject.js
*
@@ -12,8 +11,7 @@ export default {
name: 'foreignobject',
async init (S) {
const svgEditor = this;
const {text2xml, NS, importLocale} = S;
const $ = jQuery;
const {$, text2xml, NS, importLocale} = S;
const svgCanvas = svgEditor.canvas;
const
// {svgcontent} = S,
@@ -27,6 +25,10 @@ export default {
$('#svg_source_textarea').css('height', height);
};
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -36,6 +38,10 @@ export default {
$('#foreignObject_panel').toggle(on);
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function toggleSourceButtons (on) {
$('#tool_source_save, #tool_source_cancel').toggle(!on);
$('#foreign_save, #foreign_cancel').toggle(on);
@@ -62,13 +68,18 @@ export default {
svgCanvas.call('changed', [elt]);
svgCanvas.clearSelection();
} catch (e) {
console.log(e);
// Todo: Surface error to user
console.log(e); // eslint-disable-line no-console
return false;
}
return true;
}
/**
*
* @returns {undefined}
*/
function showForeignEditor () {
const elt = selElems[0];
if (!elt || editingforeign) { return; }
@@ -83,6 +94,11 @@ export default {
$('#svg_source_textarea').focus();
}
/**
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
@@ -167,14 +183,13 @@ export default {
// Create source save/cancel buttons
/* const save = */ $('#tool_source_save').clone()
.hide().attr('id', 'foreign_save').unbind()
.appendTo('#tool_source_back').click(function () {
.appendTo('#tool_source_back').click(async function () {
if (!editingforeign) { return; }
if (!setForeignString($('#svg_source_textarea').val())) {
$.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) { return false; }
endChanges();
});
const ok = await $.confirm('Errors found. Revert to original?');
if (!ok) { return; }
endChanges();
} else {
endChanges();
}
@@ -190,50 +205,51 @@ export default {
},
mouseDown (opts) {
// const e = opts.event;
if (svgCanvas.getMode() === 'foreign') {
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'foreignObject',
attr: {
x: opts.start_x,
y: opts.start_y,
id: svgCanvas.getNextId(),
'font-size': 16, // cur_text.font_size,
width: '48',
height: '20',
style: 'pointer-events:inherit'
}
});
const m = svgdoc.createElementNS(NS.MATH, 'math');
m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
m.setAttribute('display', 'inline');
const mi = svgdoc.createElementNS(NS.MATH, 'mi');
mi.setAttribute('mathvariant', 'normal');
mi.textContent = '\u03A6';
const mo = svgdoc.createElementNS(NS.MATH, 'mo');
mo.textContent = '\u222A';
const mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
mi2.textContent = '\u2133';
m.append(mi, mo, mi2);
newFO.append(m);
return {
started: true
};
if (svgCanvas.getMode() !== 'foreign') {
return undefined;
}
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'foreignObject',
attr: {
x: opts.start_x,
y: opts.start_y,
id: svgCanvas.getNextId(),
'font-size': 16, // cur_text.font_size,
width: '48',
height: '20',
style: 'pointer-events:inherit'
}
});
const m = svgdoc.createElementNS(NS.MATH, 'math');
m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
m.setAttribute('display', 'inline');
const mi = svgdoc.createElementNS(NS.MATH, 'mi');
mi.setAttribute('mathvariant', 'normal');
mi.textContent = '\u03A6';
const mo = svgdoc.createElementNS(NS.MATH, 'mo');
mo.textContent = '\u222A';
const mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
mi2.textContent = '\u2133';
m.append(mi, mo, mi2);
newFO.append(m);
return {
started: true
};
},
mouseUp (opts) {
// const e = opts.event;
if (svgCanvas.getMode() === 'foreign' && started) {
const attrs = $(newFO).attr(['width', 'height']);
const keep = (attrs.width !== '0' || attrs.height !== '0');
svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
if (svgCanvas.getMode() !== 'foreign' || !started) {
return undefined;
}
const attrs = $(newFO).attr(['width', 'height']);
const keep = (attrs.width !== '0' || attrs.height !== '0');
svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-grid.js
*
@@ -10,10 +9,9 @@
export default {
name: 'grid',
async init ({NS, getTypeMap, importLocale}) {
async init ({$, NS, getTypeMap, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const svgdoc = document.getElementById('svgcanvas').ownerDocument,
{assignAttributes} = svgCanvas,
@@ -72,6 +70,11 @@ export default {
});
$('#canvasGrid').append(gridBox);
/**
*
* @param {Float} zoom
* @returns {undefined}
*/
function updateGrid (zoom) {
// TODO: Try this with <line> elements, then compare performance difference
const unit = units[svgEditor.curConfig.baseUnit]; // 1 = 1px
@@ -124,6 +127,10 @@ export default {
svgCanvas.setHref(gridimg, datauri);
}
/**
*
* @returns {undefined}
*/
function gridUpdate () {
if (showGrid) {
updateGrid(svgCanvas.getZoom());

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-helloworld.js
*
@@ -15,11 +14,10 @@
*/
export default {
name: 'helloworld',
async init ({importLocale}) {
async init ({$, importLocale}) {
// See `/editor/extensions/ext-locale/helloworld/`
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
return {
name: strings.name,
@@ -61,6 +59,7 @@ export default {
// a value of true in order for mouseUp to be triggered
return {started: true};
}
return undefined;
},
// This is triggered from anywhere, but "started" must have been set

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-imagelib.js
*
@@ -9,7 +8,7 @@
*/
export default {
name: 'imagelib',
async init ({decode64, importLocale, dropXMLInternalSubset}) {
async init ({$, decode64, importLocale, dropXMLInternalSubset}) {
const imagelibStrings = await importLocale();
const modularVersion = !('svgEditor' in window) ||
@@ -18,16 +17,17 @@ export default {
const svgEditor = this;
const $ = jQuery;
const {uiStrings, canvas: svgCanvas, curConfig: {extIconsPath}} = svgEditor;
imagelibStrings.imgLibs = imagelibStrings.imgLibs.map(({name, url, description}) => {
// Todo: Adopt some standard formatting library like `fluent.js` instead
url = url
.replace(/\{path\}/g, extIconsPath)
.replace(/\{modularVersion\}/g, modularVersion
? (imagelibStrings.moduleEnding || '-es')
: ''
.replace(
/\{modularVersion\}/g,
modularVersion
? (imagelibStrings.moduleEnding || '-es')
: ''
);
return {name, url, description};
});
@@ -39,10 +39,18 @@ export default {
}
});
/**
*
* @returns {undefined}
*/
function closeBrowser () {
$('#imgbrowse_holder').hide();
}
/**
* @param {string} url
* @returns {undefined}
*/
function importImage (url) {
const newImage = svgCanvas.addSVGElementFromJson({
element: 'image',
@@ -68,7 +76,7 @@ export default {
let preview, submit;
// Receive `postMessage` data
window.addEventListener('message', function ({origin, data: response}) {
window.addEventListener('message', async function ({origin, data: response}) { // eslint-disable-line no-shadow
if (!response || !['string', 'object'].includes(typeof response)) {
// Do nothing
return;
@@ -84,7 +92,8 @@ export default {
return;
}
if (!allowedImageLibOrigins.includes('*') && !allowedImageLibOrigins.includes(origin)) {
console.log(`Origin ${origin} not whitelisted for posting to ${window.origin}`);
// Todo: Surface this error to user?
console.log(`Origin ${origin} not whitelisted for posting to ${window.origin}`); // eslint-disable-line no-console
return;
}
const hasName = 'name' in response;
@@ -140,12 +149,11 @@ export default {
const message = uiStrings.notification.retrieving.replace('%s', name);
if (mode !== 'm') {
$.process_cancel(message, function () {
transferStopped = true;
// Should a message be sent back to the frame?
await $.process_cancel(message);
transferStopped = true;
// Should a message be sent back to the frame?
$('#dialog_box').hide();
});
$('#dialog_box').hide();
} else {
entry = $('<div>').text(message).data('id', curMeta.id);
preview.append(entry);
@@ -183,7 +191,7 @@ export default {
} else {
pending[id].entry.remove();
}
// $.alert('Unexpected data was returned: ' + response, function() {
// await $.alert('Unexpected data was returned: ' + response, function() {
// if (mode !== 'm') {
// closeBrowser();
// } else {
@@ -203,7 +211,7 @@ export default {
}
closeBrowser();
break;
case 'm':
case 'm': {
// Import multiple
multiArr.push([(svgStr ? 'svg' : 'img'), response]);
curMeta = pending[id];
@@ -265,20 +273,24 @@ export default {
}
}
break;
case 'o':
} case 'o': {
// Open
if (!svgStr) { break; }
svgEditor.openPrep(function (ok) {
if (!ok) { return; }
svgCanvas.clear();
svgCanvas.setSvgString(response);
// updateCanvas();
});
closeBrowser();
const ok = await svgEditor.openPrep();
if (!ok) { return; }
svgCanvas.clear();
svgCanvas.setSvgString(response);
// updateCanvas();
break;
}
}
}, true);
/**
* @param {boolean} show
* @returns {undefined}
*/
function toggleMulti (show) {
$('#lib_framewrap, #imglib_opts').css({right: (show ? 200 : 10)});
if (!preview) {
@@ -319,6 +331,10 @@ export default {
submit.toggle(show);
}
/**
*
* @returns {undefined}
*/
function showBrowser () {
let browser = $('#imgbrowse');
if (!browser.length) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-markers.js
*
@@ -34,7 +33,7 @@ export default {
async init (S) {
const strings = await S.importLocale();
const svgEditor = this;
const $ = jQuery;
const {$} = S;
const svgCanvas = svgEditor.canvas;
const // {svgcontent} = S,
addElem = svgCanvas.addSVGElementFromJson;
@@ -94,6 +93,12 @@ export default {
return svgCanvas.getElem(m[1]);
}
/**
*
* @param {"start"|"mid"|"end"} pos
* @param {string} id
* @returns {undefined}
*/
function setIcon (pos, id) {
if (id.substr(0, 1) !== '\\') { id = '\\textmarker'; }
const ci = '#' + idPrefix + pos + '_' + id.substr(1);
@@ -102,8 +107,12 @@ export default {
}
let selElems;
// toggles context tool panel off/on
// sets the controls with the selected element's settings
/**
* Toggles context tool panel off/on. Sets the controls with the
* selected element's settings.
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
$('#marker_panel').toggle(on);
@@ -135,15 +144,20 @@ export default {
}
}
/**
* @param {string} id
* @param {""|"\\nomarker"|"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} val
* @returns {undefined}
*/
function addMarker (id, val) {
const txtBoxBg = '#ffffff';
const txtBoxBorder = 'none';
const txtBoxStrokeWidth = 0;
let marker = svgCanvas.getElem(id);
if (marker) { return; }
if (marker) { return undefined; }
if (val === '' || val === '\\nomarker') { return; }
if (val === '' || val === '\\nomarker') { return undefined; }
const el = selElems[0];
const color = el.getAttribute('stroke');
@@ -161,7 +175,7 @@ export default {
seType = val.substr(1);
} else { seType = 'textmarker'; }
if (!markerTypes[seType]) { return; } // an unknown type!
if (!markerTypes[seType]) { return undefined; } // an unknown type!
// create a generic marker
marker = addElem({
@@ -233,6 +247,10 @@ export default {
return marker;
}
/**
* @param {Element} elem
* @returns {SVGPolylineElement}
*/
function convertline (elem) {
// this routine came from the connectors extension
// it is needed because midpoint markers don't work with line elements
@@ -275,6 +293,10 @@ export default {
return pline;
}
/**
*
* @returns {undefined}
*/
function setMarker () {
const poslist = {start_marker: 'start', mid_marker: 'mid', end_marker: 'end'};
const pos = poslist[this.id];
@@ -301,8 +323,12 @@ export default {
setIcon(pos, val);
}
// called when the main system modifies an object
// this routine changes the associated markers to be the same color
/**
* Called when the main system modifies an object. This routine changes
* the associated markers to be the same color.
* @param {Element} elem
* @returns {undefined}
*/
function colorChanged (elem) {
const color = elem.getAttribute('stroke');
@@ -319,8 +345,12 @@ export default {
});
}
// called when the main system creates or modifies an object
// primary purpose is create new markers for cloned objects
/**
* Called when the main system creates or modifies an object.
* Its primary purpose is to create new markers for cloned objects.
* @param {Element} el
* @returns {undefined}
*/
function updateReferences (el) {
$.each(mtypes, function (i, pos) {
const id = markerPrefix + pos + '_' + el.id;
@@ -343,6 +373,11 @@ export default {
}
// simulate a change event a text box that stores the current element's marker type
/**
* @param {"start"|"mid"|"end"} pos
* @param {string} val
* @returns {undefined}
*/
function triggerTextEntry (pos, val) {
$('#' + pos + '_marker').val(val);
$('#' + pos + '_marker').change();
@@ -351,12 +386,17 @@ export default {
// else {txtbox.show();}
}
function showTextPrompt (pos) {
/**
* @param {"start"|"mid"|"end"} pos
* @returns {Promise} Resolves to `undefined`
*/
async function showTextPrompt (pos) {
let def = $('#' + pos + '_marker').val();
if (def.substr(0, 1) === '\\') { def = ''; }
$.prompt('Enter text for ' + pos + ' marker', def, function (txt) {
if (txt) { triggerTextEntry(pos, txt); }
});
const txt = await $.prompt('Enter text for ' + pos + ' marker', def);
if (txt) {
triggerTextEntry(pos, txt);
}
}
/*
@@ -372,18 +412,23 @@ export default {
case 'dimension':
triggerTextEntry('start','\\leftarrow');
triggerTextEntry('end','\\rightarrow');
showTextPrompt('mid');
await showTextPrompt('mid');
break;
case 'label':
triggerTextEntry('mid','\\nomarker');
triggerTextEntry('end','\\rightarrow');
showTextPrompt('start');
await showTextPrompt('start');
break;
}
}
*/
// callback function for a toolbar button click
function setArrowFromButton (obj) {
/**
* @param {Event} ev
* @returns {Promise} Resolves to `undefined`
*/
async function setArrowFromButton (ev) {
const parts = this.id.split('_');
const pos = parts[1];
let val = parts[2];
@@ -392,20 +437,27 @@ export default {
if (val !== 'textmarker') {
triggerTextEntry(pos, '\\' + val);
} else {
showTextPrompt(pos);
await showTextPrompt(pos);
}
}
/**
* @param {"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} id
* @returns {undefined}
*/
function getTitle (id) {
const {langList} = strings;
const item = langList.find((item) => {
return item.id === id;
const item = langList.find((itm) => {
return itm.id === id;
});
return item ? item.title : id;
}
// build the toolbar button array from the marker definitions
function buildButtonList (lang) {
/**
* Build the toolbar button array from the marker definitions.
* @returns {module:SVGEditor.Button[]}
*/
function buildButtonList () {
const buttons = [];
// const i = 0;
/*
@@ -500,7 +552,7 @@ export default {
callback () {
$('#marker_panel').addClass('toolset').hide();
},
async addLangData ({importLocale, lang}) {
/* async */ addLangData ({importLocale, lang}) {
return {data: strings.langList};
},
selectedChanged (opts) {

View File

@@ -1,4 +1,4 @@
/* globals jQuery, MathJax */
/* globals MathJax */
/**
* ext-mathjax.js
*
@@ -12,10 +12,9 @@ import {importScript} from '../external/dynamic-import-polyfill/importModule.js'
export default {
name: 'mathjax',
async init ({importLocale}) {
async init ({$, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
// Configuration of the MathJax extention.
@@ -54,7 +53,8 @@ export default {
locationY,
mathjaxLoaded = false;
// TODO: Implement language support. Move these uiStrings to the locale files and the code to the langReady callback.
// TODO: Implement language support. Move these uiStrings to the locale files and
// the code to the langReady callback. Also i18nize alert and HTML below
$.extend(uiStrings, {
mathjax: {
embed_svg: 'Save as mathematics',
@@ -65,6 +65,10 @@ export default {
}
});
/**
*
* @returns {undefined}
*/
function saveMath () {
const code = $('#mathjax_code_textarea').val();
// displaystyle to force MathJax NOT to use the inline style. Because it is
@@ -122,11 +126,15 @@ export default {
type: 'mode',
icon: svgEditor.curConfig.extIconsPath + 'mathjax.png',
events: {
click () {
async click () {
// Set the mode.
svgCanvas.setMode('mathjax');
// Only load Mathjax when needed, we don't want to strain Svg-Edit any more.
// From this point on it is very probable that it will be needed, so load it.
if (mathjaxLoaded === false) {
$('<div id="mathjax">' +
$(
'<div id="mathjax">' +
'<!-- Here is where MathJax creates the math -->' +
'<div id="mathjax_creator" class="tex2jax_process" style="display:none">' +
'$${}$$' +
@@ -191,21 +199,20 @@ export default {
*/
// We use `extIconsPath` here for now as it does not vary with
// the modular type as does `extPath`
importScript(svgEditor.curConfig.extIconsPath + mathjaxSrcSecure).then(() => {
try {
await importScript(svgEditor.curConfig.extIconsPath + mathjaxSrcSecure);
// When MathJax is loaded get the div where the math will be rendered.
MathJax.Hub.queue.Push(function () {
math = MathJax.Hub.getAllJax('#mathjax_creator')[0];
console.log(math);
console.log(math); // eslint-disable-line no-console
mathjaxLoaded = true;
console.log('MathJax Loaded');
console.log('MathJax Loaded'); // eslint-disable-line no-console
});
}).catch(() => {
console.log('Failed loadeing MathJax.');
} catch (e) {
console.log('Failed loading MathJax.'); // eslint-disable-line no-console
$.alert('Failed loading MathJax. You will not be able to change the mathematics.');
});
}
}
// Set the mode.
svgCanvas.setMode('mathjax');
}
}
}];
@@ -221,6 +228,7 @@ export default {
if (svgCanvas.getMode() === 'mathjax') {
return {started: true};
}
return undefined;
},
mouseUp (opts) {
if (svgCanvas.getMode() === 'mathjax') {
@@ -233,6 +241,7 @@ export default {
$('#mathjax').show();
return {started: false}; // Otherwise the last selected object dissapears.
}
return undefined;
},
callback () {
$('<style>').text(

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-overview_window.js
*
@@ -9,8 +8,7 @@
*/
export default {
name: 'overview_window',
init ({isChrome, isIE}) {
const $ = jQuery;
init ({$, isChrome, isIE}) {
const overviewWindowGlobals = {};
// Disabled in Chrome 48-, see https://github.com/SVG-Edit/svgedit/issues/26 and
// https://code.google.com/p/chromium/issues/detail?id=565120.
@@ -18,7 +16,7 @@ export default {
const verIndex = navigator.userAgent.indexOf('Chrome/') + 7;
const chromeVersion = parseInt(navigator.userAgent.substring(verIndex));
if (chromeVersion < 49) {
return;
return undefined;
}
}

View File

@@ -36,6 +36,7 @@ export default {
svgEditor.setPanning(true);
return {started: true};
}
return undefined;
},
mouseUp () {
if (svgCanvas.getMode() === 'ext-panning') {
@@ -45,6 +46,7 @@ export default {
element: null
};
}
return undefined;
}
};
}

View File

@@ -1,13 +1,15 @@
/* globals jQuery */
// TODO: Might add support for "exportImage" custom
// handler as in "ext-server_opensave.js" (and in savefile.php)
export default {
name: 'php_savefile',
init () {
init ({$}) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
/**
* Get file name out of SVGEdit document title.
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
return title.trim();

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-polygon.js
*
@@ -10,9 +9,8 @@ export default {
name: 'polygon',
async init (S) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const {importLocale} = S, // {svgcontent}
const {$, importLocale} = S, // {svgcontent}
// addElem = svgCanvas.addSVGElementFromJson,
editingitex = false;
const strings = await importLocale();
@@ -37,6 +35,11 @@ export default {
const height = $('#svg_source_container').height() - 80;
$('#svg_source_textarea').css('height', height);
}; */
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -53,15 +56,28 @@ export default {
}
*/
/**
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
}
/**
* @param {Float} n
* @returns {Float}
*/
function cot (n) {
return 1 / Math.tan(n);
}
/**
* @param {Float} n
* @returns {Float}
*/
function sec (n) {
return 1 / Math.cos(n);
}
@@ -148,6 +164,7 @@ export default {
$('#polygon_panel').hide();
const endChanges = function () {
// Todo: Missing?
};
// TODO: Needs to be done after orig icon loads
@@ -160,12 +177,11 @@ export default {
// Todo: Uncomment the setItexString() function above and handle ajaxEndpoint?
/*
if (!setItexString($('#svg_source_textarea').val())) {
$.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) {
return false;
}
endChanges();
});
const ok = await $.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) {
return false;
}
endChanges();
} else { */
endChanges();
// }
@@ -178,6 +194,9 @@ export default {
}, 3000);
},
mouseDown (opts) {
if (svgCanvas.getMode() !== 'polygon') {
return undefined;
}
// const e = opts.event;
const rgb = svgCanvas.getColor('fill');
// const ccRgbEl = rgb.substring(1, rgb.length);
@@ -185,79 +204,76 @@ export default {
// ccSRgbEl = sRgb.substring(1, rgb.length);
const sWidth = svgCanvas.getStrokeWidth();
if (svgCanvas.getMode() === 'polygon') {
started = true;
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'polygon',
attr: {
cx: opts.start_x,
cy: opts.start_y,
id: svgCanvas.getNextId(),
shape: 'regularPoly',
sides: document.getElementById('polySides').value,
orient: 'x',
edge: 0,
fill: rgb,
strokecolor: sRgb,
strokeWidth: sWidth
}
});
newFO = svgCanvas.addSVGElementFromJson({
element: 'polygon',
attr: {
cx: opts.start_x,
cy: opts.start_y,
id: svgCanvas.getNextId(),
shape: 'regularPoly',
sides: document.getElementById('polySides').value,
orient: 'x',
edge: 0,
fill: rgb,
strokecolor: sRgb,
strokeWidth: sWidth
}
});
return {
started: true
};
}
return {
started: true
};
},
mouseMove (opts) {
if (!started) {
return;
if (!started || svgCanvas.getMode() !== 'polygon') {
return undefined;
}
if (svgCanvas.getMode() === 'polygon') {
// const e = opts.event;
const c = $(newFO).attr(['cx', 'cy', 'sides', 'orient', 'fill', 'strokecolor', 'strokeWidth']);
let x = opts.mouse_x;
let y = opts.mouse_y;
const {cx, cy, fill, strokecolor, strokeWidth, sides} = c, // {orient} = c,
edg = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5;
newFO.setAttribute('edge', edg);
// const e = opts.event;
const c = $(newFO).attr(['cx', 'cy', 'sides', 'orient', 'fill', 'strokecolor', 'strokeWidth']);
let x = opts.mouse_x;
let y = opts.mouse_y;
const {cx, cy, fill, strokecolor, strokeWidth, sides} = c, // {orient} = c,
edg = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5;
newFO.setAttribute('edge', edg);
const inradius = (edg / 2) * cot(Math.PI / sides);
const circumradius = inradius * sec(Math.PI / sides);
let points = '';
for (let s = 0; sides >= s; s++) {
const angle = 2.0 * Math.PI * s / sides;
x = (circumradius * Math.cos(angle)) + cx;
y = (circumradius * Math.sin(angle)) + cy;
const inradius = (edg / 2) * cot(Math.PI / sides);
const circumradius = inradius * sec(Math.PI / sides);
let points = '';
for (let s = 0; sides >= s; s++) {
const angle = 2.0 * Math.PI * s / sides;
x = (circumradius * Math.cos(angle)) + cx;
y = (circumradius * Math.sin(angle)) + cy;
points += x + ',' + y + ' ';
}
// const poly = newFO.createElementNS(NS.SVG, 'polygon');
newFO.setAttribute('points', points);
newFO.setAttribute('fill', fill);
newFO.setAttribute('stroke', strokecolor);
newFO.setAttribute('stroke-width', strokeWidth);
// newFO.setAttribute('transform', 'rotate(-90)');
// const shape = newFO.getAttribute('shape');
// newFO.append(poly);
// DrawPoly(cx, cy, sides, edg, orient);
return {
started: true
};
points += x + ',' + y + ' ';
}
// const poly = newFO.createElementNS(NS.SVG, 'polygon');
newFO.setAttribute('points', points);
newFO.setAttribute('fill', fill);
newFO.setAttribute('stroke', strokecolor);
newFO.setAttribute('stroke-width', strokeWidth);
// newFO.setAttribute('transform', 'rotate(-90)');
// const shape = newFO.getAttribute('shape');
// newFO.append(poly);
// DrawPoly(cx, cy, sides, edg, orient);
return {
started: true
};
},
mouseUp (opts) {
if (svgCanvas.getMode() === 'polygon') {
const attrs = $(newFO).attr('edge');
const keep = (attrs.edge !== '0');
// svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
if (svgCanvas.getMode() !== 'polygon') {
return undefined;
}
const attrs = $(newFO).attr('edge');
const keep = (attrs.edge !== '0');
// svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-server_moinsave.js
*
@@ -12,10 +11,9 @@ import {canvg} from '../canvg/canvg.js';
export default {
name: 'server_moinsave',
async init ({encode64, importLocale}) {
async init ({$, encode64, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const saveSvgAction = '/+modify';
@@ -48,7 +46,7 @@ export default {
.append('<input type="hidden" name="contenttype" value="application/x-svgdraw">')
.appendTo('body')
.submit().remove();
alert(strings.saved);
$.alert(strings.saved);
top.window.location = '/' + name;
}
});

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-server_opensave.js
*
@@ -11,19 +10,35 @@ import {canvg} from '../canvg/canvg.js';
export default {
name: 'server_opensave',
async init ({decode64, encode64, importLocale}) {
async init ({$, decode64, encode64, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
/**
*
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
// We convert (to underscore) only those disallowed Win7 file name characters
return title.trim().replace(/[/\\:*?"<>|]/g, '_');
}
/**
* Escapes XML predefined entities for quoted attributes.
* @param {string} str
* @returns {string}
*/
function xhtmlEscape (str) {
return str.replace(/&(?!amp;)/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;'); // < is actually disallowed above anyways
}
/**
*
* @param {string} [filename='image']
* @param {string} suffix To add to file name
* @param {string} uri
* @returns {boolean}
*/
function clientDownloadSupport (filename, suffix, uri) {
const support = $('<a>')[0].download === '';
let a;
@@ -35,6 +50,7 @@ export default {
a[0].click();
return true;
}
return false;
}
const
saveSvgAction = svgEditor.curConfig.extPath + 'filesave.php',
@@ -101,7 +117,7 @@ export default {
}
if (note.length) {
alert(note);
await $.alert(note);
}
const filename = getFileNameFromTitle();
@@ -178,36 +194,45 @@ export default {
// It appears necessary to rebuild this input every time a file is
// selected so the same file can be picked and the change event can fire.
/**
*
* @param {external:jQuery} form
* @returns {undefined}
*/
function rebuildInput (form) {
form.empty();
const inp = $('<input type="file" name="svg_file">').appendTo(form);
function submit () {
// This submits the form, which returns the file data using svgEditor.processFile()
/**
* Submit the form, empty its contents for reuse and show
* uploading message.
* @returns {undefined}
*/
async function submit () {
// This submits the form, which returns the file data using `svgEditor.processFile()`
form.submit();
rebuildInput(form);
$.process_cancel(strings.uploading, function () {
cancelled = true;
$('#dialog_box').hide();
});
await $.process_cancel(strings.uploading);
cancelled = true;
$('#dialog_box').hide();
}
if (form[0] === openSvgForm[0]) {
inp.change(function () {
inp.change(async function () {
// This takes care of the "are you sure" dialog box
svgEditor.openPrep(function (ok) {
if (!ok) {
rebuildInput(form);
return;
}
submit();
});
const ok = await svgEditor.openPrep();
if (!ok) {
rebuildInput(form);
return;
}
await submit();
});
} else {
inp.change(function () {
inp.change(async function () {
// This submits the form, which returns the file data using svgEditor.processFile()
submit();
await submit();
});
}
}

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-shapes.js
*
@@ -9,10 +8,9 @@
*/
export default {
name: 'shapes',
async init ({importLocale}) {
async init ({$, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const canv = svgEditor.canvas;
const svgroot = canv.getRootElem();
let lastBBox = {};
@@ -63,10 +61,26 @@ export default {
let currentD, curShapeId, curShape, startX, startY;
let curLib = library.basic;
/**
*
* @returns {undefined}
*/
function loadIcons () {
$('#shape_buttons').empty().append(curLib.buttons);
}
/**
* @typedef {PlainObject} module:Extension.Shapes.Shapes
* @property {PlainObject.<string, string>} data
* @property {Integer} [size]
* @property {boolean} [fill]
*/
/**
* @param {string|"basic"} cat Category ID
* @param {module:Extension.Shapes.Shapes} shapes
* @returns {undefined}
*/
function makeButtons (cat, shapes) {
const size = curLib.size || 300;
const fill = curLib.fill || false;
@@ -78,7 +92,8 @@ export default {
'<svg viewBox="' + vb + '">' +
'<path fill="' + (fill ? '#333' : 'none') +
'" stroke="#000" stroke-width="' + stroke + '" /></svg></svg>',
'text/xml');
'text/xml'
);
const width = 24;
const height = 24;
@@ -88,9 +103,7 @@ export default {
const {data} = shapes;
curLib.buttons = [];
for (const id in data) {
const pathD = data[id];
curLib.buttons = Object.entries(data).map(([id, pathD]) => {
const icon = svgElem.clone();
icon.find('path').attr('d', pathD);
@@ -99,10 +112,14 @@ export default {
title: id
});
// Store for later use
curLib.buttons.push(iconBtn[0]);
}
return iconBtn[0];
});
}
/**
* @param {string|"basic"} catId
* @returns {undefined}
*/
function loadLibrary (catId) {
const lib = library[catId];
@@ -141,33 +158,34 @@ export default {
return Object.assign(buttons[i], button);
}),
callback () {
$('<style>').text(
'#shape_buttons {' +
'overflow: auto;' +
'width: 180px;' +
'max-height: 300px;' +
'display: table-cell;' +
'vertical-align: middle;' +
'}' +
'#shape_cats {' +
'min-width: 110px;' +
'display: table-cell;' +
'vertical-align: middle;' +
'height: 300px;' +
'}' +
'#shape_cats > div {' +
'line-height: 1em;' +
'padding: .5em;' +
'border:1px solid #B0B0B0;' +
'background: #E8E8E8;' +
'margin-bottom: -1px;' +
'}' +
'#shape_cats div:hover {' +
'background: #FFFFCC;' +
'}' +
'#shape_cats div.current {' +
'font-weight: bold;' +
'}').appendTo('head');
$('<style>').text(`
#shape_buttons {
overflow: auto;
width: 180px;
max-height: 300px;
display: table-cell;
vertical-align: middle;
}
#shape_cats {
min-width: 110px;
display: table-cell;
vertical-align: middle;
height: 300px;
}
#shape_cats > div {
line-height: 1em;
padding: .5em;
border:1px solid #B0B0B0;
background: #E8E8E8;
margin-bottom: -1px;
}
#shape_cats div:hover {
background: #FFFFCC;
}
#shape_cats div.current {
font-weight: bold;
}
`).appendTo('head');
const btnDiv = $('<div id="shape_buttons">');
$('#tools_shapelib > *').wrapAll(btnDiv);
@@ -229,14 +247,14 @@ export default {
});
// Now add shape categories from locale
const cats = {};
for (const o in categories) {
cats['#shape_cats [data-cat="' + o + '"]'] = categories[o];
}
Object.entries(categories).forEach(([o, categoryName]) => {
cats['#shape_cats [data-cat="' + o + '"]'] = categoryName;
});
this.setStrings('content', cats);
},
mouseDown (opts) {
const mode = canv.getMode();
if (mode !== modeId) { return; }
if (mode !== modeId) { return undefined; }
startX = opts.start_x;
const x = startX;
@@ -259,7 +277,7 @@ export default {
});
// Make sure shape uses absolute values
if (/[a-z]/.test(currentD)) {
if ((/[a-z]/).test(currentD)) {
currentD = curLib.data[curShapeId] = canv.pathActions.convertPath(curShape);
curShape.setAttribute('d', currentD);
canv.pathActions.fixEnd(curShape);
@@ -343,7 +361,7 @@ export default {
},
mouseUp (opts) {
const mode = canv.getMode();
if (mode !== modeId) { return; }
if (mode !== modeId) { return undefined; }
const keepObject = (opts.event.clientX !== startClientPos.x && opts.event.clientY !== startClientPos.y);

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-star.js
*
@@ -10,10 +9,9 @@ export default {
name: 'star',
async init (S) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const {importLocale} = S; // {svgcontent},
const {$, importLocale} = S; // {svgcontent},
let
selElems,
// editingitex = false,
@@ -25,6 +23,12 @@ export default {
// undoCommand = 'Not image',
// modeChangeG, ccZoom, wEl, hEl, wOffset, hOffset, ccRgbEl, brushW, brushH;
const strings = await importLocale();
/**
*
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -40,6 +44,12 @@ export default {
}
*/
/**
*
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
@@ -140,10 +150,11 @@ export default {
started: true
};
}
return undefined;
},
mouseMove (opts) {
if (!started) {
return;
return undefined;
}
if (svgCanvas.getMode() === 'star') {
const c = $(newFO).attr(['cx', 'cy', 'point', 'orient', 'fill', 'strokecolor', 'strokeWidth', 'radialshift']);
@@ -195,6 +206,7 @@ export default {
started: true
};
}
return undefined;
},
mouseUp () {
if (svgCanvas.getMode() === 'star') {
@@ -205,6 +217,7 @@ export default {
element: newFO
};
}
return undefined;
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-storage.js
*
@@ -23,9 +22,8 @@
*/
export default {
name: 'storage',
init () {
init ({$}) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
// We could empty any already-set data for users when they decline storage,
@@ -49,6 +47,11 @@ export default {
} = svgEditor.curConfig;
const {storage, updateCanvas} = svgEditor;
/**
* Replace `storagePrompt` parameter within URL.
* @param {string} val
* @returns {undefined}
*/
function replaceStoragePrompt (val) {
val = val ? 'storagePrompt=' + val : '';
const loc = top.location; // Allow this to work with the embedded editor as well
@@ -60,6 +63,13 @@ export default {
loc.href += (loc.href.includes('?') ? '&' : '?') + val;
}
}
/**
* Sets SVG content as a string with "svgedit-" and the current
* canvas name as namespace.
* @param {string} val
* @returns {undefined}
*/
function setSVGContentStorage (val) {
if (storage) {
const name = 'svgedit-' + svgEditor.curConfig.canvasName;
@@ -71,25 +81,36 @@ export default {
}
}
/**
* Set the cookie to expire.
* @param {string} cookie
* @returns {undefined}
*/
function expireCookie (cookie) {
document.cookie = encodeURIComponent(cookie) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
/**
* Expire the storage cookie.
* @returns {undefined}
*/
function removeStoragePrefCookie () {
expireCookie('store');
expireCookie('svgeditstore');
}
/**
* Empties storage for each of the current preferences.
* @returns {undefined}
*/
function emptyStorage () {
setSVGContentStorage('');
for (let name in svgEditor.curPrefs) {
if (svgEditor.curPrefs.hasOwnProperty(name)) {
name = 'svg-edit-' + name;
if (storage) {
storage.removeItem(name);
}
expireCookie(name);
Object.keys(svgEditor.curPrefs).forEach((name) => {
name = 'svg-edit-' + name;
if (storage) {
storage.removeItem(name);
}
}
expireCookie(name);
});
}
// emptyStorage();
@@ -106,10 +127,10 @@ export default {
function setupBeforeUnloadListener () {
window.addEventListener('beforeunload', function (e) {
// Don't save anything unless the user opted in to storage
if (!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)) {
if (!document.cookie.match(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/)) {
return;
}
if (document.cookie.match(/(?:^|;\s*)store=prefsAndContent/)) {
if (document.cookie.match(/(?:^|;\s*)svgeditstore=prefsAndContent/)) {
setSVGContentStorage(svgCanvas.getSvgString());
}
@@ -118,24 +139,21 @@ export default {
const {curPrefs} = svgEditor;
for (let key in curPrefs) {
if (curPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain
let val = curPrefs[key];
const store = (val !== undefined);
key = 'svg-edit-' + key;
if (!store) {
continue;
}
if (storage) {
storage.setItem(key, val);
} else if (window.widget) {
window.widget.setPreferenceForKey(val, key);
} else {
val = encodeURIComponent(val);
document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
}
Object.entries(curPrefs).forEach(([key, val]) => {
const store = (val !== undefined);
key = 'svg-edit-' + key;
if (!store) {
return;
}
}
if (storage) {
storage.setItem(key, val);
} else if (window.widget) {
window.widget.setPreferenceForKey(val, key);
} else {
val = encodeURIComponent(val);
document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
}
});
});
}
@@ -175,7 +193,7 @@ export default {
// continual prompts about it)...
storagePrompt !== false &&
// ...and this user hasn't previously indicated a desire for storage
!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)
!document.cookie.match(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/)
)
// ...then show the storage prompt.
)) {
@@ -205,60 +223,10 @@ export default {
// Open select-with-checkbox dialog
// From svg-editor.js
$.select(
svgEditor.storagePromptState = 'waiting';
const {response: pref, checked} = await $.select(
message,
options,
function (pref, checked) {
if (pref && pref !== 'noPrefsOrContent') {
// Regardless of whether the user opted
// to remember the choice (and move to a URL which won't
// ask them again), we have to assume the user
// doesn't even want to remember their not wanting
// storage, so we don't set the cookie or continue on with
// setting storage on beforeunload
document.cookie = 'store=' + encodeURIComponent(pref) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT'; // 'prefsAndContent' | 'prefsOnly'
// If the URL was configured to always insist on a prompt, if
// the user does indicate a wish to store their info, we
// don't want ask them again upon page refresh so move
// them instead to a URL which does not always prompt
if (storagePrompt === true && checked) {
replaceStoragePrompt();
return;
}
} else { // The user does not wish storage (or cancelled, which we treat equivalently)
removeStoragePrefCookie();
if (pref && // If the user explicitly expresses wish for no storage
emptyStorageOnDecline
) {
emptyStorage();
}
if (pref && checked) {
// Open a URL which won't set storage and won't prompt user about storage
replaceStoragePrompt('false');
return;
}
}
// Reset width/height of dialog (e.g., for use by Export)
$('#dialog_container')[0].style.width = oldContainerWidth;
$('#dialog_container')[0].style.marginLeft = oldContainerMarginLeft;
$('#dialog_content')[0].style.height = oldContentHeight;
$('#dialog_container')[0].style.height = oldContainerHeight;
// It should be enough to (conditionally) add to storage on
// beforeunload, but if we wished to update immediately,
// we might wish to try setting:
// svgEditor.setConfig({noStorageOnLoad: true});
// and then call:
// svgEditor.loadContentAndPrefs();
// We don't check for noStorageOnLoad here because
// the prompt gives the user the option to store data
setupBeforeUnloadListener();
svgEditor.storagePromptState = 'closed';
updateCanvas(true);
},
null,
null,
{
@@ -267,7 +235,55 @@ export default {
tooltip: rememberTooltip
}
);
svgEditor.storagePromptState = 'waiting';
if (pref && pref !== 'noPrefsOrContent') {
// Regardless of whether the user opted
// to remember the choice (and move to a URL which won't
// ask them again), we have to assume the user
// doesn't even want to remember their not wanting
// storage, so we don't set the cookie or continue on with
// setting storage on beforeunload
document.cookie = 'svgeditstore=' + encodeURIComponent(pref) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT'; // 'prefsAndContent' | 'prefsOnly'
// If the URL was configured to always insist on a prompt, if
// the user does indicate a wish to store their info, we
// don't want ask them again upon page refresh so move
// them instead to a URL which does not always prompt
if (storagePrompt === true && checked) {
replaceStoragePrompt();
return;
}
} else { // The user does not wish storage (or cancelled, which we treat equivalently)
removeStoragePrefCookie();
if (pref && // If the user explicitly expresses wish for no storage
emptyStorageOnDecline
) {
emptyStorage();
}
if (pref && checked) {
// Open a URL which won't set storage and won't prompt user about storage
replaceStoragePrompt('false');
return;
}
}
// Reset width/height of dialog (e.g., for use by Export)
$('#dialog_container')[0].style.width = oldContainerWidth;
$('#dialog_container')[0].style.marginLeft = oldContainerMarginLeft;
$('#dialog_content')[0].style.height = oldContentHeight;
$('#dialog_container')[0].style.height = oldContainerHeight;
// It should be enough to (conditionally) add to storage on
// beforeunload, but if we wished to update immediately,
// we might wish to try setting:
// svgEditor.setConfig({noStorageOnLoad: true});
// and then call:
// svgEditor.loadContentAndPrefs();
// We don't check for noStorageOnLoad here because
// the prompt gives the user the option to store data
setupBeforeUnloadListener();
svgEditor.storagePromptState = 'closed';
updateCanvas(true);
} else if (!noStorageOnLoad || forceStorage) {
setupBeforeUnloadListener();
}

View File

@@ -7,7 +7,7 @@
export default {
name: 'webappfind',
async init ({importLocale}) {
async init ({importLocale, $}) {
const strings = await importLocale();
const svgEditor = this;
const saveMessage = 'save',
@@ -48,7 +48,7 @@ export default {
} */
break;
case 'save-end':
alert(`save complete for pathID ${pathID}!`);
$.alert(`save complete for pathID ${pathID}!`);
break;
default:
throw new Error('Unexpected WebAppFind event type');

View File

@@ -23,7 +23,7 @@ export default {
// to configure
const {allowedOrigins} = svgEditor.curConfig;
if (!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin)) {
console.log(`Origin ${e.origin} not whitelisted for posting to ${window.origin}`);
console.log(`Origin ${e.origin} not whitelisted for posting to ${window.origin}`); // eslint-disable-line no-console
return;
}
const cbid = data.id;
@@ -42,7 +42,7 @@ export default {
e.source.postMessage(JSON.stringify(message), '*');
});
} catch (err) {
console.log('Error with xdomain message listener: ' + err);
console.log('Error with xdomain message listener: ' + err); // eslint-disable-line no-console
}
}
};

View File

@@ -32,7 +32,8 @@ $('a').click(function () {
data = canvas.toDataURL();
} catch (err) {
// This fails in Firefox with `file:///` URLs :(
alert('Data URL conversion failed: ' + err);
// Todo: This could use a generic alert library instead
alert('Data URL conversion failed: ' + err); // eslint-disable-line no-alert
data = '';
}
post({href, data});

View File

@@ -6,6 +6,8 @@ manipulation($, jml);
const baseAPIURL = 'https://openclipart.org/search/json/';
const jsVoid = 'javascript: void(0);'; // eslint-disable-line no-script-url
/**
* Shows results after query submission.
* @param {string} url
@@ -18,7 +20,7 @@ async function processResults (url) {
*/
function queryLink (query) {
return ['a', {
href: 'javascript: void(0);',
href: jsVoid,
dataset: {value: query},
$on: {click (e) {
e.preventDefault();
@@ -34,7 +36,8 @@ async function processResults (url) {
// console.log('json', json);
if (!json || json.msg !== 'success') {
alert('There was a problem downloading the results');
// Todo: This could use a generic alert library instead
alert('There was a problem downloading the results'); // eslint-disable-line no-alert
return;
}
const {payload, info: {
@@ -82,8 +85,8 @@ async function processResults (url) {
['button', {style: 'margin-right: 8px; border: 2px solid black;', dataset: {id, value: svgURL}, $on: {
async click (e) {
e.preventDefault();
const {value: svgURL, id} = this.dataset;
// console.log('this', id, svgURL);
const {value: svgurl} = this.dataset;
// console.log('this', id, svgurl);
const post = (message) => {
// Todo: Make origin customizable as set by opening window
// Todo: If dropping IE9, avoid stringifying
@@ -95,13 +98,13 @@ async function processResults (url) {
// Send metadata (also indicates file is about to be sent)
post({
name: title,
id: svgURL
id: svgurl
});
const result = await fetch(svgURL);
const result = await fetch(svgurl);
const svg = await result.text();
// console.log('url and svg', svgURL, svg);
// console.log('url and svg', svgurl, svg);
post({
href: svgURL,
href: svgurl,
data: svg
});
}
@@ -117,7 +120,7 @@ async function processResults (url) {
['span', [
'(ID: ',
['a', {
href: 'javascript: void(0);',
href: jsVoid,
dataset: {value: id},
$on: {
click (e) {
@@ -172,7 +175,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -188,7 +191,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -204,7 +207,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -220,7 +223,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();

View File

@@ -1,5 +1,15 @@
// From https://github.com/inexorabletash/polyfill/blob/master/dom.js
/**
* @module DOMPolyfill
*/
/**
*
* @param {Node} o
* @param {module:DOMPolyfill~ParentNode|module:DOMPolyfill~ChildNode} ps
* @returns {undefined}
*/
function mixin (o, ps) {
if (!o) return;
Object.keys(ps).forEach((p) => {
@@ -17,6 +27,11 @@ function mixin (o, ps) {
});
}
/**
*
* @param {Node[]} nodes
* @returns {Node}
*/
function convertNodesIntoANode (nodes) {
nodes = nodes.map((node) => {
return !(node instanceof Node) ? document.createTextNode(node) : node;

View File

@@ -52,7 +52,7 @@ export function importSetGlobalDefault (url, config) {
* @returns {Promise} The promise resolves to either an `ArbitraryModule` or
* any other value depends on the export of the targeted module.
*/
export async function importSetGlobal (url, {global, returnDefault}) {
export async function importSetGlobal (url, {global: glob, returnDefault}) {
// Todo: Replace calls to this function with `import()` when supported
const modularVersion = !('svgEditor' in window) ||
!window.svgEditor ||
@@ -61,13 +61,13 @@ export async function importSetGlobal (url, {global, returnDefault}) {
return importModule(url, undefined, {returnDefault});
}
await importScript(url);
return window[global];
return window[glob];
}
/**
*
* @author Brett Zamir (other items are from `dynamic-import-polyfill`)
* @param {string|string[]} url
* @param {Object} [atts={}]
* @param {PlainObject} [atts={}]
* @returns {Promise} Resolves to `undefined` or rejects with an `Error` upon a
* script loading error
*/
@@ -104,7 +104,7 @@ export function importScript (url, atts = {}) {
/**
*
* @param {string|string[]} url
* @param {Object} [atts={}]
* @param {PlainObject} [atts={}]
* @param {PlainObject} opts
* @param {boolean} [opts.returnDefault=false} = {}]
* @returns {Promise} Resolves to value of loading module or rejects with

View File

@@ -114,7 +114,7 @@ export class MoveElementCommand extends Command {
this.newNextSibling = elem.nextSibling;
this.newParent = elem.parentNode;
}
type () {
type () { // eslint-disable-line class-methods-use-this
return 'svgedit.history.MoveElementCommand';
}
@@ -180,7 +180,7 @@ export class InsertElementCommand extends Command {
this.nextSibling = this.elem.nextSibling;
}
type () {
type () { // eslint-disable-line class-methods-use-this
return 'svgedit.history.InsertElementCommand';
}
@@ -249,7 +249,7 @@ export class RemoveElementCommand extends Command {
// special hack for webkit: remove this element's entry in the svgTransformLists map
removeElementFromListMap(elem);
}
type () {
type () { // eslint-disable-line class-methods-use-this
return 'svgedit.history.RemoveElementCommand';
}
@@ -338,7 +338,7 @@ export class ChangeElementCommand extends Command {
}
}
}
type () {
type () { // eslint-disable-line class-methods-use-this
return 'svgedit.history.ChangeElementCommand';
}
@@ -354,26 +354,24 @@ export class ChangeElementCommand extends Command {
}
let bChangedTransform = false;
for (const attr in this.newValues) {
if (this.newValues[attr]) {
Object.entries(this.newValues).forEach(([attr, value]) => {
if (value) {
if (attr === '#text') {
this.elem.textContent = this.newValues[attr];
this.elem.textContent = value;
} else if (attr === '#href') {
setHref(this.elem, this.newValues[attr]);
setHref(this.elem, value);
} else {
this.elem.setAttribute(attr, this.newValues[attr]);
this.elem.setAttribute(attr, value);
}
} else if (attr === '#text') {
this.elem.textContent = '';
} else {
if (attr === '#text') {
this.elem.textContent = '';
} else {
this.elem.setAttribute(attr, '');
this.elem.removeAttribute(attr);
}
this.elem.setAttribute(attr, '');
this.elem.removeAttribute(attr);
}
if (attr === 'transform') { bChangedTransform = true; }
}
});
// relocate rotational transform, if necessary
if (!bChangedTransform) {
@@ -408,24 +406,22 @@ export class ChangeElementCommand extends Command {
}
let bChangedTransform = false;
for (const attr in this.oldValues) {
if (this.oldValues[attr]) {
Object.entries(this.oldValues).forEach(([attr, value]) => {
if (value) {
if (attr === '#text') {
this.elem.textContent = this.oldValues[attr];
this.elem.textContent = value;
} else if (attr === '#href') {
setHref(this.elem, this.oldValues[attr]);
setHref(this.elem, value);
} else {
this.elem.setAttribute(attr, this.oldValues[attr]);
this.elem.setAttribute(attr, value);
}
} else if (attr === '#text') {
this.elem.textContent = '';
} else {
if (attr === '#text') {
this.elem.textContent = '';
} else {
this.elem.removeAttribute(attr);
}
this.elem.removeAttribute(attr);
}
if (attr === 'transform') { bChangedTransform = true; }
}
});
// relocate rotational transform, if necessary
if (!bChangedTransform) {
const angle = getRotationAngle(this.elem);
@@ -477,7 +473,7 @@ export class BatchCommand extends Command {
this.stack = [];
}
type () {
type () { // eslint-disable-line class-methods-use-this
return 'svgedit.history.BatchCommand';
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 B

After

Width:  |  Height:  |  Size: 71 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 B

After

Width:  |  Height:  |  Size: 81 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 B

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -36,8 +36,8 @@ const ns = {
if (!window.console) {
window.console = {
log (str) {},
dir (str) {}
log (str) { /* */ },
dir (str) { /* */ }
};
}
@@ -49,7 +49,7 @@ if (!window.console) {
* @param {external:jQuery} $ The jQuery instance to wrap
* @returns {external:jQuery}
*/
export default function ($) {
export default function jQueryPluginJGraduate ($) {
if (!$.loadingStylesheets) {
$.loadingStylesheets = [];
}
@@ -199,21 +199,37 @@ export default function ($) {
const isGecko = navigator.userAgent.includes('Gecko/');
/**
* @typedef {PlainObject.<string, string>} module:jGraduate.Attrs
*/
/**
* @param {SVGElement} elem
* @param {module:jGraduate.Attrs} attrs
* @returns {undefined}
*/
function setAttrs (elem, attrs) {
if (isGecko) {
for (const aname in attrs) elem.setAttribute(aname, attrs[aname]);
Object.entries(attrs).forEach(([aname, val]) => {
elem.setAttribute(aname, val);
});
} else {
for (const aname in attrs) {
const val = attrs[aname], prop = elem[aname];
Object.entries(attrs).forEach(([aname, val]) => {
const prop = elem[aname];
if (prop && prop.constructor === 'SVGLength') {
prop.baseVal.value = val;
} else {
elem.setAttribute(aname, val);
}
}
});
}
}
/**
* @param {string} name
* @param {module:jGraduate.Attrs} attrs
* @param {Element} newparent
* @returns {SVGElement}
*/
function mkElem (name, attrs, newparent) {
const elem = document.createElementNS(ns.svg, name);
setAttrs(elem, attrs);
@@ -263,7 +279,7 @@ export default function ($) {
idref = '#' + $this.attr('id') + ' ';
if (!idref) {
alert('Container element must have an id attribute to maintain unique id strings for sub-elements.');
/* await */ $.alert('Container element must have an id attribute to maintain unique id strings for sub-elements.');
return;
}
@@ -485,7 +501,7 @@ export default function ($) {
mkStop(1, '#' + color, 1);
break;
case 'inverse':
case 'inverse': {
// Invert current color for second stop
let inverted = '';
for (let i = 0; i < 6; i += 2) {
@@ -496,8 +512,7 @@ export default function ($) {
}
mkStop(1, '#' + inverted, 1);
break;
case 'white':
} case 'white':
mkStop(1, '#ffffff', 1);
break;
@@ -615,10 +630,23 @@ export default function ($) {
}).change();
});
function mkStop (n, color, opac, sel, stopElem) {
const stop = stopElem || mkElem('stop', {'stop-color': color, 'stop-opacity': opac, offset: n}, curGradient);
/**
*
* @param {Float} n
* @param {Float|string} colr
* @param {Float} opac
* @param {boolean} [sel]
* @param {SVGStopElement} [stopElem]
* @returns {SVGStopElement}
*/
function mkStop (n, colr, opac, sel, stopElem) {
const stop = stopElem || mkElem('stop', {
'stop-color': colr,
'stop-opacity': opac,
offset: n
}, curGradient);
if (stopElem) {
color = stopElem.getAttribute('stop-color');
colr = stopElem.getAttribute('stop-color');
opac = stopElem.getAttribute('stop-opacity');
n = stopElem.getAttribute('offset');
} else {
@@ -636,7 +664,7 @@ export default function ($) {
const path = mkElem('path', {
d: pickerD,
fill: color,
fill: colr,
'fill-opacity': opac,
transform: 'translate(' + (10 + n * MAX) + ', 26)',
stroke: '#000',
@@ -652,19 +680,19 @@ export default function ($) {
return false;
}).data('stop', stop).data('bg', pathbg).dblclick(function () {
$('div.jGraduate_LightBox').show();
const colorhandle = this;
let stopOpacity = +stop.getAttribute('stop-opacity') || 1;
const colorhandle = this; // eslint-disable-line consistent-this
let stopOpacity = Number(stop.getAttribute('stop-opacity')) || 1;
let stopColor = stop.getAttribute('stop-color') || 1;
let thisAlpha = (parseFloat(stopOpacity) * 255).toString(16);
while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; }
color = stopColor.substr(1) + thisAlpha;
colr = stopColor.substr(1) + thisAlpha;
$('#' + id + '_jGraduate_stopPicker').css({left: 100, bottom: 15}).jPicker({
window: {title: 'Pick the start color and opacity for the gradient'},
images: {clientPath: $settings.images.clientPath},
color: {active: color, alphaSupport: true}
}, function (color, arg2) {
stopColor = color.val('hex') ? ('#' + color.val('hex')) : 'none';
stopOpacity = color.val('a') !== null ? color.val('a') / 256 : 1;
color: {active: colr, alphaSupport: true}
}, function (clr, arg2) {
stopColor = clr.val('hex') ? ('#' + clr.val('hex')) : 'none';
stopOpacity = clr.val('a') !== null ? clr.val('a') / 256 : 1;
colorhandle.setAttribute('fill', stopColor);
colorhandle.setAttribute('fill-opacity', stopOpacity);
stop.setAttribute('stop-color', stopColor);
@@ -679,8 +707,8 @@ export default function ($) {
$(curGradient).find('stop').each(function () {
const curS = $(this);
if (+this.getAttribute('offset') > n) {
if (!color) {
if (Number(this.getAttribute('offset')) > n) {
if (!colr) {
const newcolor = this.getAttribute('stop-color');
const newopac = this.getAttribute('stop-opacity');
stop.setAttribute('stop-color', newcolor);
@@ -691,11 +719,16 @@ export default function ($) {
curS.before(stop);
return false;
}
return true;
});
if (sel) selectStop(path);
return stop;
}
/**
*
* @returns {undefined}
*/
function remStop () {
delStop.setAttribute('display', 'none');
const path = $(curStop);
@@ -716,6 +749,10 @@ export default function ($) {
display: 'none'
}, undefined); // stopMakerSVG);
/**
* @param {Element} item
* @returns {undefined}
*/
function selectStop (item) {
if (curStop) curStop.setAttribute('stroke', '#000');
item.setAttribute('stroke', 'blue');
@@ -728,6 +765,10 @@ export default function ($) {
let stopOffset;
/**
*
* @returns {undefined}
*/
function remDrags () {
$win.unbind('mousemove', dragColor);
if (delStop.getAttribute('display') !== 'none') {
@@ -740,6 +781,10 @@ export default function ($) {
let cX = cx;
let cY = cy;
/**
*
* @returns {undefined}
*/
function xform () {
const rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : '';
if (scaleX === 1 && scaleY === 1) {
@@ -753,6 +798,10 @@ export default function ($) {
}
}
/**
* @param {Event} evt
* @returns {undefined}
*/
function dragColor (evt) {
let x = evt.pageX - stopOffset.left;
const y = evt.pageY - stopOffset.top;
@@ -857,26 +906,26 @@ export default function ($) {
const fracy = y / SIZEY;
const type = draggingCoord.data('coord');
const grad = curGradient;
const grd = curGradient;
switch (type) {
case 'start':
attrInput.x1.val(fracx);
attrInput.y1.val(fracy);
grad.setAttribute('x1', fracx);
grad.setAttribute('y1', fracy);
grd.setAttribute('x1', fracx);
grd.setAttribute('y1', fracy);
break;
case 'end':
attrInput.x2.val(fracx);
attrInput.y2.val(fracy);
grad.setAttribute('x2', fracx);
grad.setAttribute('y2', fracy);
grd.setAttribute('x2', fracx);
grd.setAttribute('y2', fracy);
break;
case 'center':
attrInput.cx.val(fracx);
attrInput.cy.val(fracy);
grad.setAttribute('cx', fracx);
grad.setAttribute('cy', fracy);
grd.setAttribute('cx', fracx);
grd.setAttribute('cy', fracy);
cX = fracx;
cY = fracy;
xform();
@@ -884,8 +933,8 @@ export default function ($) {
case 'focus':
attrInput.fx.val(fracx);
attrInput.fy.val(fracy);
grad.setAttribute('fx', fracx);
grad.setAttribute('fy', fracy);
grd.setAttribute('fx', fracx);
grd.setAttribute('fy', fracy);
xform();
}
@@ -963,19 +1012,19 @@ export default function ($) {
focusCoord.toggle(showFocus);
attrInput.fx.val('');
attrInput.fy.val('');
const grad = curGradient;
const grd = curGradient;
if (!showFocus) {
lastfx = grad.getAttribute('fx');
lastfy = grad.getAttribute('fy');
grad.removeAttribute('fx');
grad.removeAttribute('fy');
lastfx = grd.getAttribute('fx');
lastfy = grd.getAttribute('fy');
grd.removeAttribute('fx');
grd.removeAttribute('fy');
} else {
const fx = lastfx || 0.5;
const fy = lastfy || 0.5;
grad.setAttribute('fx', fx);
grad.setAttribute('fy', fy);
attrInput.fx.val(fx);
attrInput.fy.val(fy);
const fX = lastfx || 0.5;
const fY = lastfy || 0.5;
grd.setAttribute('fx', fX);
grd.setAttribute('fy', fY);
attrInput.fx.val(fX);
attrInput.fy.val(fY);
}
});
@@ -993,9 +1042,9 @@ export default function ($) {
let slider;
const setSlider = function (e) {
const {offset} = slider;
const {offset: {left}} = slider;
const div = slider.parent;
let x = (e.pageX - offset.left - parseInt(div.css('border-left-width')));
let x = (e.pageX - left - parseInt(div.css('border-left-width')));
if (x > SLIDERW) x = SLIDERW;
if (x <= 0) x = 0;
const posx = x - 5;
@@ -1030,7 +1079,7 @@ export default function ($) {
}
break;
case 'angle':
x = x - 0.5;
x -= 0.5;
angle = x *= 180;
xform();
x /= 100;
@@ -1117,7 +1166,7 @@ export default function ($) {
$(data.input).val(data.val).change(function () {
const isRad = curType === 'radialGradient';
let val = +this.value;
let val = Number(this.value);
let xpos = 0;
switch (type) {
case 'radius':
@@ -1195,10 +1244,10 @@ export default function ($) {
images: {clientPath: $settings.images.clientPath},
color: {active: color, alphaSupport: true}
},
function (color) {
function (clr) {
$this.paint.type = 'solidColor';
$this.paint.alpha = color.val('ahex') ? Math.round((color.val('a') / 255) * 100) : 100;
$this.paint.solidColor = color.val('hex') ? color.val('hex') : 'none';
$this.paint.alpha = clr.val('ahex') ? Math.round((clr.val('a') / 255) * 100) : 100;
$this.paint.solidColor = clr.val('hex') ? clr.val('hex') : 'none';
$this.paint.radialGradient = null;
okClicked();
},

File diff suppressed because it is too large Load Diff

View File

@@ -68,8 +68,8 @@ const getLinesOptionsOfPoly = function (node) {
}
}
if (nums.length < 4) {
console.log('invalid points attribute:', node);
return;
console.log('invalid points attribute:', node); // eslint-disable-line no-console
return undefined;
}
const [x, y] = nums, lines = [];
for (let i = 2; i < nums.length; i += 2) {
@@ -171,7 +171,7 @@ const svgElementToPdf = function (element, pdf, options) {
removeAttributes(node, pdfSvgAttr.circle);
break;
case 'polygon':
case 'polyline':
case 'polyline': {
const linesOptions = getLinesOptionsOfPoly(node);
if (linesOptions) {
pdf.lines(
@@ -186,7 +186,7 @@ const svgElementToPdf = function (element, pdf, options) {
removeAttributes(node, pdfSvgAttr.polygon);
break;
// TODO: path
case 'text':
} case 'text': {
if (node.hasAttribute('font-family')) {
switch ((node.getAttribute('font-family') || '').toLowerCase()) {
case 'serif': pdf.setFont('times'); break;
@@ -219,19 +219,24 @@ const svgElementToPdf = function (element, pdf, options) {
? parseInt(node.getAttribute('font-size'))
: 16;
const getWidth = (node) => {
/**
*
* @param {Element} elem
* @returns {Float}
*/
const getWidth = (elem) => {
let box;
try {
box = node.getBBox(); // Firefox on MacOS will raise error here
box = elem.getBBox(); // Firefox on MacOS will raise error here
} catch (err) {
// copy and append to body so that getBBox is available
const nodeCopy = node.cloneNode(true);
const svg = node.ownerSVGElement.cloneNode(false);
const nodeCopy = elem.cloneNode(true);
const svg = elem.ownerSVGElement.cloneNode(false);
svg.appendChild(nodeCopy);
document.body.appendChild(svg);
try {
box = nodeCopy.getBBox();
} catch (err) {
} catch (error) {
box = {width: 0};
}
document.body.removeChild(svg);
@@ -259,9 +264,9 @@ const svgElementToPdf = function (element, pdf, options) {
removeAttributes(node, pdfSvgAttr.text);
break;
// TODO: image
default:
} default:
if (remove) {
console.log("can't translate to pdf:", node);
console.log("can't translate to pdf:", node); // eslint-disable-line no-console
node.remove();
}
}

View File

@@ -39,7 +39,9 @@ export const setStrings = function (type, obj, ids) {
// Root element to look for element from
const parent = $('#svg_editor').parent();
Object.entries(obj).forEach(([sel, val]) => {
if (!val) { console.log(sel); }
if (!val) {
console.log(sel); // eslint-disable-line no-console
}
if (ids) { sel = '#' + sel; }
const $elem = parent.find(sel);
@@ -53,6 +55,7 @@ export const setStrings = function (type, obj, ids) {
node.textContent = val;
return true;
}
return false;
});
break;
@@ -61,7 +64,7 @@ export const setStrings = function (type, obj, ids) {
break;
}
} else {
console.log('Missing: ' + sel);
console.log('Missing element for localization: ' + sel); // eslint-disable-line no-console
}
});
};
@@ -114,7 +117,7 @@ export const readLang = async function (langData) {
});
// Old locale file, do nothing for now.
if (!langData.tools) { return; }
if (!langData.tools) { return undefined; }
const {
tools,
@@ -331,7 +334,7 @@ export const putLocale = async function (givenParam, goodLangs, conf) {
}
}
console.log('Lang: ' + langParam);
console.log('Lang: ' + langParam); // eslint-disable-line no-console
// Set to English if language is not in list of good langs
if (!goodLangs.includes(langParam) && langParam !== 'test') {

View File

@@ -84,18 +84,20 @@ export default function jQueryPluginSpinButton ($) {
/**
* @callback module:jQuerySpinButton.StepCallback
* @param {external:jQuery} thisArg Value of `this`
* @param {Float} i Value to adjust
* @returns {Float}
*/
/**
* @callback module:jQuerySpinButton.ValueCallback
* @param {external:jQuery} thisArg Value of `this`
* @param {Float} value Value that was changed
* @param {external:jQuery.fn.SpinButton} thisArg Spin Button; check its `value` to see how it was changed.
* @returns {undefined}
*/
/**
* @typedef {PlainObject} module:jQuerySpinButton.SpinButtonConfig
* @property {Float} min Set lower limit
* @property {Float} max Set upper limit.
* @property {Float} step Set increment size.
* @property {module:jQuerySpinButton.StepCallback} stepfunc Custom function to run when changing a value; called with `this` of object and the value to adjust
* @property {module:jQuerySpinButton.StepCallback} stepfunc Custom function to run when changing a value; called with `this` of object and the value to adjust and returns a float.
* @property {module:jQuerySpinButton.ValueCallback} callback Called after value adjusted (with `this` of object)
* @property {Float} smallStep Set shift-click increment size.
* @property {PlainObject} stateObj Object to allow passing in live-updating scale
@@ -115,6 +117,13 @@ export default function jQueryPluginSpinButton ($) {
*/
$.fn.SpinButton = function (cfg) {
cfg = cfg || {};
/**
*
* @param {Element} el
* @param {"offsetLeft"|"offsetTop"} prop
* @returns {Integer}
*/
function coord (el, prop) {
const b = document.body;
@@ -169,7 +178,9 @@ export default function jQueryPluginSpinButton ($) {
if (this.spinCfg.min !== null) { v = Math.max(v, this.spinCfg.min); }
if (this.spinCfg.max !== null) { v = Math.min(v, this.spinCfg.max); }
this.value = v;
if (typeof this.spinCfg.callback === 'function') { this.spinCfg.callback(this); }
if (typeof this.spinCfg.callback === 'function') {
this.spinCfg.callback(this);
}
};
$(this)
@@ -186,7 +197,8 @@ export default function jQueryPluginSpinButton ($) {
const direction =
(x > coord(el, 'offsetLeft') +
el.offsetWidth * scale - this.spinCfg._btn_width)
? ((y < coord(el, 'offsetTop') + height * scale) ? 1 : -1) : 0;
? ((y < coord(el, 'offsetTop') + height * scale) ? 1 : -1)
: 0;
if (direction !== this.spinCfg._direction) {
// Style up/down buttons:

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,14 @@
import './svgpathseg.js';
import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
import * as draw from './draw.js';
import jQueryPluginDBox from './dbox.js';
import * as draw from './draw.js'; // eslint-disable-line import/no-duplicates
import { // eslint-disable-line no-duplicate-imports
identifyLayers, createLayer, cloneLayer, deleteCurrentLayer,
setCurrentLayer, renameCurrentLayer, setCurrentLayerPosition,
setLayerVisibility, moveSelectedToLayer, mergeLayer, mergeAllLayers,
leaveContext, setContext
} from './draw.js'; // eslint-disable-line import/no-duplicates
import * as pathModule from './path.js';
import {sanitizeSvg} from './sanitize.js';
import {getReverseNS, NS} from './namespaces.js';
@@ -65,7 +71,7 @@ import {
init as selectInit
} from './select.js';
const $ = jQueryPluginSVG(jQuery);
let $ = jQueryPluginSVG(jQuery);
const {
MoveElementCommand, InsertElementCommand, RemoveElementCommand,
ChangeElementCommand, BatchCommand, UndoManager, HistoryEventTypes
@@ -976,6 +982,9 @@ let
// Canvas point for the most recent right click
lastClickPoint = null;
this.runExtension = function (name, action, vars) {
return this.runExtensions(action, vars, false, (n) => n === name);
};
/**
* @typedef {module:svgcanvas.ExtensionMouseDownStatus|module:svgcanvas.ExtensionMouseUpStatus|module:svgcanvas.ExtensionIDsUpdatedStatus|module:locale.ExtensionLocaleData[]|undefined} module:svgcanvas.ExtensionStatus
* @tutorial ExtensionDocs
@@ -983,6 +992,12 @@ let
/**
* @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}
*/
/**
* @todo Consider: Should this return an array by default, so extension results aren't overwritten?
@@ -991,11 +1006,15 @@ let
* @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>|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus.
*/
const runExtensions = this.runExtensions = function (action, vars, returnArray) {
const runExtensions = this.runExtensions = function (action, vars, returnArray, nameFilter) {
let result = returnArray ? [] : false;
$.each(extensions, function (name, ext) {
if (nameFilter && !nameFilter(name)) {
return;
}
if (ext && action in ext) {
if (typeof vars === 'function') {
vars = vars(name); // ext, action
@@ -1126,44 +1145,45 @@ const runExtensions = this.runExtensions = function (action, vars, returnArray)
* @param {module:svgcanvas.ExtensionInitCallback} [extInitFunc] - Function supplied by the extension with its data
* @param {module:SVGEditor~ImportLocale} importLocale
* @fires module:svgcanvas.SvgCanvas#event:extension_added
* @throws {TypeError} If `extInitFunc` is not a function
* @throws {TypeError|Error} `TypeError` if `extInitFunc` is not a function, `Error`
* if extension of supplied name already exists
* @returns {Promise} Resolves to `undefined`
*/
this.addExtension = async function (name, extInitFunc, importLocale) {
if (typeof extInitFunc !== 'function') {
throw new TypeError('Function argument expected for `svgcanvas.addExtension`');
}
if (!(name in extensions)) {
// Provide private vars/funcs here. Is there a better way to do this?
/**
* @typedef {module:svgcanvas.PrivateMethods} module:svgcanvas.ExtensionArgumentObject
* @property {SVGSVGElement} svgroot See {@link module:svgcanvas~svgroot}
* @property {SVGSVGElement} svgcontent See {@link module:svgcanvas~svgcontent}
* @property {!(string|Integer)} nonce See {@link module:draw.Drawing#getNonce}
* @property {module:select.SelectorManager} selectorManager
* @property {module:SVGEditor~ImportLocale} importLocale
*/
/**
* @type {module:svgcanvas.ExtensionArgumentObject}
* @see {@link module:svgcanvas.PrivateMethods} source for the other methods/properties
*/
const argObj = $.extend(canvas.getPrivateMethods(), {
importLocale,
svgroot,
svgcontent,
nonce: getCurrentDrawing().getNonce(),
selectorManager
});
const extObj = await extInitFunc(argObj);
if (extObj) {
extObj.name = name;
}
extensions[name] = extObj;
return call('extension_added', extObj);
if (name in extensions) {
throw new Error('Cannot add extension "' + name + '", an extension by that name already exists.');
}
console.log('Cannot add extension "' + name + '", an extension by that name already exists.');
return undefined;
// Provide private vars/funcs here. Is there a better way to do this?
/**
* @typedef {module:svgcanvas.PrivateMethods} module:svgcanvas.ExtensionArgumentObject
* @property {SVGSVGElement} svgroot See {@link module:svgcanvas~svgroot}
* @property {SVGSVGElement} svgcontent See {@link module:svgcanvas~svgcontent}
* @property {!(string|Integer)} nonce See {@link module:draw.Drawing#getNonce}
* @property {module:select.SelectorManager} selectorManager
* @property {module:SVGEditor~ImportLocale} importLocale
*/
/**
* @type {module:svgcanvas.ExtensionArgumentObject}
* @see {@link module:svgcanvas.PrivateMethods} source for the other methods/properties
*/
const argObj = $.extend(canvas.getPrivateMethods(), {
$,
importLocale,
svgroot,
svgcontent,
nonce: getCurrentDrawing().getNonce(),
selectorManager
});
const extObj = await extInitFunc(argObj);
if (extObj) {
extObj.name = name;
}
extensions[name] = extObj;
return call('extension_added', extObj);
};
/**
@@ -1188,9 +1208,9 @@ const getIntersectionList = this.getIntersectionList = function (rect) {
rubberBBox = rubberBox.getBBox();
const bb = svgcontent.createSVGRect();
for (const o in rubberBBox) {
['x', 'y', 'width', 'height', 'top', 'right', 'bottom', 'left'].forEach((o) => {
bb[o] = rubberBBox[o] / currentZoom;
}
});
rubberBBox = bb;
} else {
rubberBBox = svgcontent.createSVGRect();
@@ -1550,7 +1570,7 @@ const recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions =
* @returns {undefined}
*/
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]); // eslint-disable-line no-console
};
// Root Current Transformation Matrix in user units
@@ -1757,7 +1777,7 @@ const mouseDown = function (evt) {
}
startTransform = mouseTarget.getAttribute('transform');
let i, strokeW;
const tlist = getTransformList(mouseTarget);
switch (currentMode) {
case 'select':
@@ -1783,7 +1803,7 @@ const mouseDown = function (evt) {
if (!rightClick) {
// insert a dummy transform so if the element(s) are moved it will have
// a transform to use for its translate
for (i = 0; i < selectedElements.length; ++i) {
for (let i = 0; i < selectedElements.length; ++i) {
if (isNullish(selectedElements[i])) { continue; }
const slist = getTransformList(selectedElements[i]);
if (slist.numberOfItems) {
@@ -1876,7 +1896,7 @@ const mouseDown = function (evt) {
const all = mouseTarget.getElementsByTagName('*'),
len = all.length;
for (i = 0; i < len; i++) {
for (let i = 0; i < len; i++) {
if (!all[i].style) { // mathML
continue;
}
@@ -1950,9 +1970,9 @@ const mouseDown = function (evt) {
}
});
break;
case 'line':
case 'line': {
started = true;
strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width;
const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width;
addSVGElementFromJson({
element: 'line',
curStyles: true,
@@ -1974,7 +1994,7 @@ const mouseDown = function (evt) {
}
});
break;
case 'circle':
} case 'circle':
started = true;
addSVGElementFromJson({
element: 'circle',
@@ -2421,8 +2441,8 @@ const mouseMove = function (evt) {
start = {x: end.x, y: end.y};
break;
// update path stretch line coordinates
} case 'path': { // eslint-disable-line no-empty
} // fall through
} case 'path':
// fall through
case 'pathedit': {
x *= currentZoom;
y *= currentZoom;
@@ -2623,14 +2643,14 @@ const mouseUp = function (evt) {
const elem = selectedElements[0];
if (elem) {
elem.removeAttribute('style');
walkTree(elem, function (elem) {
elem.removeAttribute('style');
walkTree(elem, function (el) {
el.removeAttribute('style');
});
}
}
}
return;
case 'zoom':
case 'zoom': {
if (!isNullish(rubberBox)) {
rubberBox.setAttribute('display', 'none');
}
@@ -2643,7 +2663,7 @@ const mouseUp = function (evt) {
factor
});
return;
case 'fhpath':
} 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
@@ -2664,7 +2684,7 @@ const mouseUp = function (evt) {
element = pathActions.smoothPolylineIntoPath(element);
}
break;
case 'line':
} case 'line':
attrs = $(element).attr(['x1', 'x2', 'y1', 'y2']);
keep = (attrs.x1 !== attrs.x2 || attrs.y1 !== attrs.y2);
break;
@@ -2724,7 +2744,7 @@ const mouseUp = function (evt) {
selectOnly([element]);
textActions.start(element);
break;
case 'path':
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
@@ -2734,7 +2754,7 @@ const mouseUp = function (evt) {
({element} = res);
({keep} = res);
break;
case 'pathedit':
} case 'pathedit':
keep = true;
element = null;
pathActions.mouseUp(evt);
@@ -2744,7 +2764,7 @@ const mouseUp = function (evt) {
element = null;
textActions.mouseUp(evt, mouseX, mouseY);
break;
case 'rotate':
case 'rotate': {
keep = true;
element = null;
currentMode = 'select';
@@ -2756,7 +2776,7 @@ const mouseUp = function (evt) {
recalculateAllSelectedDimensions();
call('changed', selectedElements);
break;
default:
} default:
// This could occur in an extension
break;
}
@@ -3016,6 +3036,11 @@ let matrix;
let lastX, lastY;
let allowDbl;
/**
*
* @param {Integer} index
* @returns {undefined}
*/
function setCursor (index) {
const empty = (textinput.value === '');
$(textinput).focus();
@@ -3066,6 +3091,13 @@ function setCursor (index) {
if (selblock) { selblock.setAttribute('d', ''); }
}
/**
*
* @param {Integer} start
* @param {Integer} end
* @param {boolean} skipInput
* @returns {undefined}
*/
function setSelection (start, end, skipInput) {
if (start === end) {
setCursor(end);
@@ -3109,6 +3141,12 @@ function setSelection (start, end, skipInput) {
});
}
/**
*
* @param {Float} mouseX
* @param {Float} mouseY
* @returns {Integer}
*/
function getIndexFromPoint (mouseX, mouseY) {
// Position cursor here
const pt = svgroot.createSVGPoint();
@@ -3136,10 +3174,23 @@ function getIndexFromPoint (mouseX, mouseY) {
return charpos;
}
/**
*
* @param {Float} mouseX
* @param {Float} mouseY
* @returns {undefined}
*/
function setCursorFromPoint (mouseX, mouseY) {
setCursor(getIndexFromPoint(mouseX, mouseY));
}
/**
*
* @param {Float} x
* @param {Float} y
* @param {boolean} apply
* @returns {undefined}
*/
function setEndSelectionFromPoint (x, y, apply) {
const i1 = textinput.selectionStart;
const i2 = getIndexFromPoint(x, y);
@@ -3149,6 +3200,12 @@ function setEndSelectionFromPoint (x, y, apply) {
setSelection(start, end, !apply);
}
/**
*
* @param {Float} xIn
* @param {Float} yIn
* @returns {module:math.XYObject}
*/
function screenToPt (xIn, yIn) {
const out = {
x: xIn,
@@ -3167,6 +3224,12 @@ function screenToPt (xIn, yIn) {
return out;
}
/**
*
* @param {Float} xIn
* @param {Float} yIn
* @returns {module:math.XYObject}
*/
function ptToScreen (xIn, yIn) {
const out = {
x: xIn,
@@ -3194,11 +3257,21 @@ function hideCursor () {
}
*/
/**
*
* @param {Event} evt
* @returns {undefined}
*/
function selectAll (evt) {
setSelection(0, curtext.textContent.length);
$(this).unbind(evt);
}
/**
*
* @param {Event} evt
* @returns {undefined}
*/
function selectWord (evt) {
if (!allowDbl || !curtext) { return; }
@@ -3579,13 +3652,12 @@ this.svgToString = function (elem, indent) {
if (elem) {
cleanupElement(elem);
const attrs = Array.from(elem.attributes);
let i;
const childs = elem.childNodes;
attrs.sort((a, b) => {
return a.name > b.name ? -1 : 1;
});
for (i = 0; i < indent; i++) { out.push(' '); }
for (let i = 0; i < indent; i++) { out.push(' '); }
out.push('<'); out.push(elem.nodeName);
if (elem.id === 'svgcontent') {
// Process root element separately
@@ -3624,15 +3696,15 @@ this.svgToString = function (elem, indent) {
}
$.each(this.attributes, function (i, attr) {
const uri = attr.namespaceURI;
if (uri && !nsuris[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') {
nsuris[uri] = true;
out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"');
const u = attr.namespaceURI;
if (u && !nsuris[u] && nsMap[u] !== 'xmlns' && nsMap[u] !== 'xml') {
nsuris[u] = true;
out.push(' xmlns:' + nsMap[u] + '="' + u + '"');
}
});
});
i = attrs.length;
let i = attrs.length;
const attrNames = ['width', 'height', 'xmlns', 'x', 'y', 'viewBox', 'id', 'overflow'];
while (i--) {
const attr = attrs[i];
@@ -3652,10 +3724,10 @@ this.svgToString = function (elem, indent) {
}
} else {
// Skip empty defs
if (elem.nodeName === 'defs' && !elem.firstChild) { return; }
if (elem.nodeName === 'defs' && !elem.firstChild) { return ''; }
const mozAttrs = ['-moz-math-font-style', '_moz-math-font-style'];
for (i = attrs.length - 1; i >= 0; i--) {
for (let i = attrs.length - 1; i >= 0; i--) {
const attr = attrs[i];
let attrVal = toXml(attr.value);
// remove bogus attributes added by Gecko
@@ -3697,7 +3769,7 @@ this.svgToString = function (elem, indent) {
indent++;
let bOneLine = false;
for (i = 0; i < childs.length; i++) {
for (let i = 0; i < childs.length; i++) {
const child = childs.item(i);
switch (child.nodeType) {
case 1: // element node
@@ -3730,7 +3802,7 @@ this.svgToString = function (elem, indent) {
indent--;
if (!bOneLine) {
out.push('\n');
for (i = 0; i < indent; i++) { out.push(' '); }
for (let i = 0; i < indent; i++) { out.push(' '); }
}
out.push('</'); out.push(elem.nodeName); out.push('>');
} else {
@@ -3750,11 +3822,11 @@ this.svgToString = function (elem, indent) {
* Converts a given image file to a data URL when possible, then runs a given callback.
* @function module:svgcanvas.SvgCanvas#embedImage
* @param {string} src - The path/URL of the image
* @param {module:svgcanvas.ImageEmbeddedCallback} [callback] - Function to run when image data is found
* @returns {Promise} Resolves to Data URL (string|false)
*/
this.embedImage = function (src, callback) {
return new Promise(function (resolve, reject) {
this.embedImage = function (src) {
// Todo: Remove this Promise in favor of making an async/await `Image.load` utility
return new Promise(function (resolve, reject) { // eslint-disable-line promise/avoid-new
// load in the image and once it's loaded, get the dimensions
$(new Image()).load(function (response, status, xhr) {
if (status === 'error') {
@@ -3776,7 +3848,6 @@ this.embedImage = function (src, callback) {
encodableImages[src] = false;
}
lastGoodImgUrl = src;
if (callback) { callback(encodableImages[src]); }
resolve(encodableImages[src]);
}).attr('src', src);
});
@@ -3798,6 +3869,7 @@ this.setGoodImage = function (val) {
* @returns {undefined}
*/
this.open = function () {
/* */
};
/**
@@ -3878,12 +3950,6 @@ let canvg;
* @property {string} exportWindowName A convenience for passing along a `window.name` to target a window on which the export could be added
*/
/**
* Function to run when image data is found
* @callback module:svgcanvas.ImageExportedCallback
* @param {module:svgcanvas.ImageExportedResults} obj
* @returns {undefined}
*/
/**
* Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image,
* then calls "exported" with an object including the string, image
@@ -3892,38 +3958,42 @@ let canvg;
* @param {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} [imgType="PNG"]
* @param {Float} [quality] Between 0 and 1
* @param {string} [exportWindowName]
* @param {module:svgcanvas.ImageExportedCallback} [cb]
* @param {PlainObject} [opts]
* @param {boolean} [opts.avoidEvent]
* @fires module:svgcanvas.SvgCanvas#event:exported
* @todo Confirm/fix ICO type
* @returns {Promise} Resolves to {@link module:svgcanvas.ImageExportedResults}
*/
this.rasterExport = function (imgType, quality, exportWindowName, cb, opts = {}) {
this.rasterExport = async function (imgType, quality, exportWindowName, opts = {}) {
const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG');
const mimeType = 'image/' + type.toLowerCase();
const {issues, issueCodes} = getIssues();
const svg = this.svgCanvasToString();
return new Promise(async (resolve, reject) => {
if (!canvg) {
({canvg} = await importSetGlobal(curConfig.canvgPath + 'canvg.js', {
global: 'canvg'
}));
}
if (!$('#export_canvas').length) {
$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
}
const c = $('#export_canvas')[0];
c.width = canvas.contentW;
c.height = canvas.contentH;
if (!canvg) {
({canvg} = await importSetGlobal(curConfig.canvgPath + 'canvg.js', {
global: 'canvg'
}));
}
if (!$('#export_canvas').length) {
$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
}
const c = $('#export_canvas')[0];
c.width = canvas.contentW;
c.height = canvas.contentH;
await canvg(c, svg);
await canvg(c, svg);
// Todo: Make async/await utility in place of `toBlob`, so we can remove this constructor
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
const dataURLType = type.toLowerCase();
const datauri = quality
? c.toDataURL('image/' + dataURLType, quality)
: c.toDataURL('image/' + dataURLType);
let bloburl;
/**
* Called when `bloburl` is available for export.
* @returns {undefined}
*/
function done () {
const obj = {
datauri, bloburl, svg, issues, issueCodes, type: imgType,
@@ -3932,9 +4002,6 @@ this.rasterExport = function (imgType, quality, exportWindowName, cb, opts = {})
if (!opts.avoidEvent) {
call('exported', obj);
}
if (cb) {
cb(obj);
}
resolve(obj);
}
if (c.toBlob) {
@@ -3974,86 +4041,72 @@ this.rasterExport = function (imgType, quality, exportWindowName, cb, opts = {})
* @property {string} exportWindowName
*/
/**
* Function to run when PDF data is found
* @callback module:svgcanvas.PDFExportedCallback
* @param {module:svgcanvas.PDFExportedResults} obj
* @returns {undefined}
*/
/**
* Generates a PDF based on the current image, then calls "exportedPDF" with
* an object including the string, the data URL, and any issues found.
* @function module:svgcanvas.SvgCanvas#exportPDF
* @param {string} [exportWindowName] Will also be used for the download file name here
* @param {external:jsPDF.OutputType} [outputType="dataurlstring"]
* @param {module:svgcanvas.PDFExportedCallback} [cb]
* @fires module:svgcanvas.SvgCanvas#event:exportedPDF
* @returns {Promise} Resolves to {@link module:svgcanvas.PDFExportedResults}
*/
this.exportPDF = function (
this.exportPDF = async function (
exportWindowName,
outputType = isChrome() ? 'save' : undefined,
cb
outputType = isChrome() ? 'save' : undefined
) {
const that = this;
return new Promise(async (resolve, reject) => {
if (!window.jsPDF) {
// Todo: Switch to `import()` when widely supported and available (also allow customization of path)
await importScript([
// We do not currently have these paths configurable as they are
// currently global-only, so not Rolled-up
'jspdf/underscore-min.js',
'jspdf/jspdf.min.js'
]);
if (!window.jsPDF) {
// Todo: Switch to `import()` when widely supported and available (also allow customization of path)
await importScript([
// We do not currently have these paths configurable as they are
// currently global-only, so not Rolled-up
'jspdf/underscore-min.js',
'jspdf/jspdf.min.js'
]);
const modularVersion = !('svgEditor' in window) ||
!window.svgEditor ||
window.svgEditor.modules !== false;
// Todo: Switch to `import()` when widely supported and available (also allow customization of path)
await importScript(curConfig.jspdfPath + 'jspdf.plugin.svgToPdf.js', {
type: modularVersion
? 'module'
: 'text/javascript'
});
// await importModule('jspdf/jspdf.plugin.svgToPdf.js');
}
const res = getResolution();
const orientation = res.w > res.h ? 'landscape' : 'portrait';
const unit = 'pt'; // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes
// Todo: Give options to use predefined jsPDF formats like "a4", etc. from pull-down (with option to keep customizable)
const doc = jsPDF({
orientation,
unit,
format: [res.w, res.h]
// , compressPdf: true
const modularVersion = !('svgEditor' in window) ||
!window.svgEditor ||
window.svgEditor.modules !== false;
// Todo: Switch to `import()` when widely supported and available (also allow customization of path)
await importScript(curConfig.jspdfPath + 'jspdf.plugin.svgToPdf.js', {
type: modularVersion
? 'module'
: 'text/javascript'
});
const docTitle = getDocumentTitle();
doc.setProperties({
title: docTitle /* ,
subject: '',
author: '',
keywords: '',
creator: '' */
});
const {issues, issueCodes} = getIssues();
const svg = that.svgCanvasToString();
doc.addSVG(svg, 0, 0);
// await importModule('jspdf/jspdf.plugin.svgToPdf.js');
}
// 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 = {svg, issues, issueCodes, exportWindowName, outputType};
obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined);
if (cb) {
cb(obj);
}
resolve(obj);
call('exportedPDF', obj);
const res = getResolution();
const orientation = res.w > res.h ? 'landscape' : 'portrait';
const unit = 'pt'; // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes
// Todo: Give options to use predefined jsPDF formats like "a4", etc. from pull-down (with option to keep customizable)
const doc = jsPDF({
orientation,
unit,
format: [res.w, res.h]
// , compressPdf: true
});
const docTitle = getDocumentTitle();
doc.setProperties({
title: docTitle /* ,
subject: '',
author: '',
keywords: '',
creator: '' */
});
const {issues, issueCodes} = getIssues();
const svg = this.svgCanvasToString();
doc.addSVG(svg, 0, 0);
// 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 = {svg, issues, issueCodes, exportWindowName, outputType};
obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined);
call('exportedPDF', obj);
return obj;
};
/**
@@ -4218,14 +4271,14 @@ const convertGradients = this.convertGradients = function (elem) {
}
elems.each(function () {
const grad = this;
const grad = this; // eslint-disable-line consistent-this
if ($(grad).attr('gradientUnits') === 'userSpaceOnUse') {
// TODO: Support more than one element with this ref by duplicating parent grad
const elems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
if (!elems.length) { return; }
const fillStrokeElems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
if (!fillStrokeElems.length) { return; }
// get object's bounding box
const bb = utilsGetBBox(elems[0]);
const bb = utilsGetBBox(fillStrokeElems[0]);
// This will occur if the element is inside a <defs> or a <symbol>,
// in which we shouldn't need to convert anyway.
@@ -4391,7 +4444,7 @@ const convertToGroup = this.convertToGroup = function (elem) {
try {
recalculateDimensions(n);
} catch (e) {
console.log(e);
console.log(e); // eslint-disable-line no-console
}
});
@@ -4409,7 +4462,7 @@ const convertToGroup = this.convertToGroup = function (elem) {
addCommandToHistory(batchCmd);
} else {
console.log('Unexpected element to ungroup:', elem);
console.log('Unexpected element to ungroup:', elem); // eslint-disable-line no-console
}
};
@@ -4467,7 +4520,7 @@ this.setSvgString = function (xmlString, preventUndo) {
// change image href vals if possible
content.find('image').each(function () {
const image = this;
const image = this; // eslint-disable-line consistent-this
preventClickDefault(image);
const val = getHref(this);
if (val) {
@@ -4583,7 +4636,7 @@ this.setSvgString = function (xmlString, preventUndo) {
if (!preventUndo) addCommandToHistory(batchCmd);
call('changed', [svgcontent]);
} catch (e) {
console.log(e);
console.log(e); // eslint-disable-line no-console
return false;
}
@@ -4714,7 +4767,7 @@ this.importSvgString = function (xmlString) {
addCommandToHistory(batchCmd);
call('changed', [svgcontent]);
} catch (e) {
console.log(e);
console.log(e); // eslint-disable-line no-console
return null;
}
@@ -4724,13 +4777,14 @@ this.importSvgString = function (xmlString) {
// Could deprecate, but besides external uses, their usage makes clear that
// canvas is a dependency for all of these
[
'identifyLayers', 'createLayer', 'cloneLayer', 'deleteCurrentLayer',
'setCurrentLayer', 'renameCurrentLayer', 'setCurrentLayerPosition',
'setLayerVisibility', 'moveSelectedToLayer', 'mergeLayer', 'mergeAllLayers',
'leaveContext', 'setContext'
].forEach((prop) => {
canvas[prop] = draw[prop];
const dr = {
identifyLayers, createLayer, cloneLayer, deleteCurrentLayer,
setCurrentLayer, renameCurrentLayer, setCurrentLayerPosition,
setLayerVisibility, moveSelectedToLayer, mergeLayer, mergeAllLayers,
leaveContext, setContext
};
Object.entries(dr).forEach(([prop, propVal]) => {
canvas[prop] = propVal;
});
draw.init(
/**
@@ -4860,6 +4914,7 @@ this.getVersion = function () {
*/
this.setUiStrings = function (strs) {
Object.assign(uiStrings, strs.notification);
$ = jQueryPluginDBox($, strs.common);
pathModule.setUiStrings(strs);
};
@@ -5080,7 +5135,7 @@ this.getOffset = function () {
this.setBBoxZoom = function (val, editorW, editorH) {
let spacer = 0.85;
let bb;
const calcZoom = function (bb) {
const calcZoom = function (bb) { // eslint-disable-line no-shadow
if (!bb) { return false; }
const wZoom = Math.round((editorW / bb.width) * 100 * spacer) / 100;
const hZoom = Math.round((editorH / bb.height) * 100 * spacer) / 100;
@@ -5268,6 +5323,11 @@ this.setColor = function (type, val, preventUndo) {
curShape[type] = val;
curProperties[type + '_paint'] = {type: 'solidColor'};
const elems = [];
/**
*
* @param {Element} e
* @returns {undefined}
*/
function addNonG (e) {
if (e.nodeName !== 'g') {
elems.push(e);
@@ -5348,7 +5408,7 @@ const findDuplicateGradient = function (grad) {
const ogAttrs = $(og).attr(radAttrs);
let diff = false;
$.each(radAttrs, function (i, attr) {
$.each(radAttrs, function (j, attr) {
if (gradAttrs[attr] !== ogAttrs[attr]) { diff = true; }
});
@@ -5451,6 +5511,11 @@ this.setStrokeWidth = function (val) {
curProperties.stroke_width = val;
const elems = [];
/**
*
* @param {Element} e
* @returns {undefined}
*/
function addNonG (e) {
if (e.nodeName !== 'g') {
elems.push(e);
@@ -5632,7 +5697,7 @@ canvas.setBlurNoUndo = function (val) {
changeSelectedAttributeNoUndo('filter', 'url(#' + elem.id + '_blur)');
}
if (isWebkit()) {
console.log('e', elem);
// console.log('e', elem); // eslint-disable-line no-console
elem.removeAttribute('filter');
elem.setAttribute('filter', 'url(#' + elem.id + '_blur)');
}
@@ -5641,6 +5706,10 @@ canvas.setBlurNoUndo = function (val) {
}
};
/**
*
* @returns {undefined}
*/
function finishChange () {
const bCmd = canvas.undoMgr.finishUndoableChange();
curCommand.addSubCommand(bCmd);
@@ -5653,14 +5722,14 @@ function finishChange () {
* Sets the `x`, `y`, `width`, `height` values of the filter element in order to
* make the blur not be clipped. Removes them if not neeeded.
* @function module:svgcanvas.SvgCanvas#setBlurOffsets
* @param {Element} filter - The filter DOM element to update
* @param {Element} filterElem - The filter DOM element to update
* @param {Float} stdDev - The standard deviation value on which to base the offset size
* @returns {undefined}
*/
canvas.setBlurOffsets = function (filter, stdDev) {
canvas.setBlurOffsets = function (filterElem, stdDev) {
if (stdDev > 3) {
// TODO: Create algorithm here where size is based on expected blur
assignAttributes(filter, {
assignAttributes(filterElem, {
x: '-50%',
y: '-50%',
width: '200%',
@@ -5668,10 +5737,10 @@ canvas.setBlurOffsets = function (filter, stdDev) {
}, 100);
// Removing these attributes hides text in Chrome (see Issue 579)
} else if (!isWebkit()) {
filter.removeAttribute('x');
filter.removeAttribute('y');
filter.removeAttribute('width');
filter.removeAttribute('height');
filterElem.removeAttribute('x');
filterElem.removeAttribute('y');
filterElem.removeAttribute('width');
filterElem.removeAttribute('height');
}
};
@@ -6301,6 +6370,11 @@ this.pasteElements = function (type, x, y) {
const changedIDs = {};
// Recursively replace IDs and record the changes
/**
*
* @param {module:svgcanvas.SVGAsJSON} elem
* @returns {undefined}
*/
function checkIDs (elem) {
if (elem.attr && elem.attr.id) {
changedIDs[elem.attr.id] = getNextId();
@@ -6442,7 +6516,7 @@ this.groupSelectedElements = function (type, urlArg) {
* @function module:svgcanvas.SvgCanvas#pushGroupProperties
* @param {SVGAElement|SVGGElement} g
* @param {boolean} undoable
* @returns {undefined}
* @returns {BatchCommand|undefined}
*/
const pushGroupProperties = this.pushGroupProperties = function (g, undoable) {
const children = g.childNodes;
@@ -6621,6 +6695,7 @@ const pushGroupProperties = this.pushGroupProperties = function (g, undoable) {
if (undoable && !batchCmd.isEmpty()) {
return batchCmd;
}
return undefined;
};
/**
@@ -6798,7 +6873,7 @@ this.moveUpDownSelected = function (dir) {
* @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:svgcanvas.SvgCanvas#event:changed
* @returns {BatchCommand} Batch command for the move
* @returns {BatchCommand|undefined} Batch command for the move
*/
this.moveSelectedElements = function (dx, dy, undoable) {
// if undoable is not sent, default to true
@@ -6859,6 +6934,7 @@ this.moveSelectedElements = function (dx, dy, undoable) {
call('changed', selectedElements);
return batchCmd;
}
return undefined;
};
/**
@@ -6874,8 +6950,14 @@ this.cloneSelectedElements = function (x, y) {
const batchCmd = new BatchCommand('Clone Elements');
// find all the elements selected (stop at first null)
const len = selectedElements.length;
/**
* Sorts an array numerically and ascending.
* @param {Element} a
* @param {Element} b
* @returns {Integer}
*/
function sortfunction (a, b) {
return ($(b).index() - $(a).index()); // causes an array to be sorted numerically and ascending
return ($(b).index() - $(a).index());
}
selectedElements.sort(sortfunction);
for (i = 0; i < len; ++i) {

View File

@@ -920,8 +920,8 @@ export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathA
}
const {nextSibling} = elem;
batchCmd.addSubCommand(new history.RemoveElementCommand(elem, nextSibling, parent));
batchCmd.addSubCommand(new history.InsertElementCommand(path));
batchCmd.addSubCommand(new hstry.RemoveElementCommand(elem, nextSibling, parent));
batchCmd.addSubCommand(new hstry.InsertElementCommand(path));
clearSelection();
elem.remove();

File diff suppressed because one or more lines are too long