- 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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
@@ -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 $;
|
||||
}
|
||||
@@ -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`
|
||||
|
||||
@@ -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++;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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, '&').replace(/"/g, '"').replace(/</g, '<'); // < 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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();
|
||||
|
||||
15
editor/external/dom-polyfill/dom-polyfill.js
vendored
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 195 B |
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 474 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 157 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 71 B |
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 268 B |
|
Before Width: | Height: | Size: 85 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 150 B After Width: | Height: | Size: 143 B |
|
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 147 B |
@@ -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();
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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:
|
||||
|
||||
1362
editor/svg-editor.js
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||