svgedit/editor/jgraduate/jQuery.jGraduate.js

1329 lines
44 KiB
JavaScript

/**
* @file jGraduate 0.4
*
* jQuery Plugin for a gradient picker
*
* @module jGraduate
* @copyright 2010 Jeff Schiller {@link http://blog.codedread.com/}, 2010 Alexis Deveria {@link http://a.deveria.com/}
*
* @license Apache-2.0
* @example
* // The Paint object is described below.
* $.jGraduate.Paint(); // constructs a 'none' color
* @example $.jGraduate.Paint({copy: o}); // creates a copy of the paint o
* @example $.jGraduate.Paint({hex: '#rrggbb'}); // creates a solid color paint with hex = "#rrggbb"
* @example $.jGraduate.Paint({linearGradient: o, a: 50}); // creates a linear gradient paint with opacity=0.5
* @example $.jGraduate.Paint({radialGradient: o, a: 7}); // creates a radial gradient paint with opacity=0.07
* @example $.jGraduate.Paint({hex: '#rrggbb', linearGradient: o}); // throws an exception?
*/
/* eslint-disable jsdoc/require-property */
/**
* The jQuery namespace.
* @external jQuery
*/
/**
* The jQuery plugin namespace.
* @namespace {PlainObject} fn
* @memberof external:jQuery
* @see {@link http://learn.jquery.com/plugins/|jQuery Plugins}
*/
/* eslint-enable jsdoc/require-property */
const ns = {
svg: 'http://www.w3.org/2000/svg',
xlink: 'http://www.w3.org/1999/xlink'
};
if (!window.console) {
window.console = {
log (str) { /* */ },
dir (str) { /* */ }
};
}
/**
* Adds {@link external:jQuery.jGraduate.Paint},
* {@link external:jQuery.fn.jGraduateDefaults},
* {@link external:jQuery.fn.jGraduate}.
* @function module:jGraduate.jGraduate
* @param {external:jQuery} $ The jQuery instance to wrap
* @returns {external:jQuery}
*/
export default function jQueryPluginJGraduate ($) {
if (!$.loadingStylesheets) {
$.loadingStylesheets = [];
}
const stylesheet = 'jgraduate/css/jGraduate.css';
if (!$.loadingStylesheets.includes(stylesheet)) {
$.loadingStylesheets.push(stylesheet);
}
/**
* @typedef {PlainObject} module:jGraduate.jGraduatePaintOptions
* @property {Float} [alpha]
* @property {module:jGraduate~Paint} [copy] Copy paint object
* @property {SVGLinearGradientElement} [linearGradient]
* @property {SVGRadialGradientElement} [radialGradient]
* @property {string} [solidColor]
*/
/**
* @memberof module:jGraduate~
*/
class Paint {
/**
* @param {module:jGraduate.jGraduatePaintOptions} [opt]
*/
constructor (opt) {
const options = opt || {};
this.alpha = isNaN(options.alpha) ? 100 : options.alpha;
// copy paint object
if (options.copy) {
/**
* @name module:jGraduate~Paint#type
* @type {"none"|"solidColor"|"linearGradient"|"radialGradient"}
*/
this.type = options.copy.type;
/**
* Represents opacity (0-100).
* @name module:jGraduate~Paint#alpha
* @type {Float}
*/
this.alpha = options.copy.alpha;
/**
* Represents #RRGGBB hex of color.
* @name module:jGraduate~Paint#solidColor
* @type {string}
*/
this.solidColor = null;
/**
* @name module:jGraduate~Paint#linearGradient
* @type {SVGLinearGradientElement}
*/
this.linearGradient = null;
/**
* @name module:jGraduate~Paint#radialGradient
* @type {SVGRadialGradientElement}
*/
this.radialGradient = null;
switch (this.type) {
case 'none':
break;
case 'solidColor':
this.solidColor = options.copy.solidColor;
break;
case 'linearGradient':
this.linearGradient = options.copy.linearGradient.cloneNode(true);
break;
case 'radialGradient':
this.radialGradient = options.copy.radialGradient.cloneNode(true);
break;
}
// create linear gradient paint
} else if (options.linearGradient) {
this.type = 'linearGradient';
this.solidColor = null;
this.radialGradient = null;
this.linearGradient = options.linearGradient.cloneNode(true);
// create linear gradient paint
} else if (options.radialGradient) {
this.type = 'radialGradient';
this.solidColor = null;
this.linearGradient = null;
this.radialGradient = options.radialGradient.cloneNode(true);
// create solid color paint
} else if (options.solidColor) {
this.type = 'solidColor';
this.solidColor = options.solidColor;
// create empty paint
} else {
this.type = 'none';
this.solidColor = null;
this.linearGradient = null;
this.radialGradient = null;
}
}
}
/* eslint-disable jsdoc/require-property */
/**
* @namespace {PlainObject} jGraduate
* @memberof external:jQuery
*/
$.jGraduate = /** @lends external:jQuery.jGraduate */ {
/* eslint-enable jsdoc/require-property */
/**
* @class external:jQuery.jGraduate.Paint
* @see module:jGraduate~Paint
*/
Paint
};
// JSDoc doesn't show this as belonging to our `module:jGraduate.Options` type,
// so we use `@see`
/**
* @namespace {module:jGraduate.Options} jGraduateDefaults
* @memberof external:jQuery.fn
*/
$.fn.jGraduateDefaults = /** @lends external:jQuery.fn.jGraduateDefaults */ {
/**
* Creates an object with a 'none' color.
* @type {external:jQuery.jGraduate.Paint}
* @see module:jGraduate.Options
*/
paint: new $.jGraduate.Paint(),
/**
* @namespace
*/
window: {
/**
* @type {string}
* @see module:jGraduate.Options
*/
pickerTitle: 'Drag markers to pick a paint'
},
/**
* @namespace
*/
images: {
/**
* @type {string}
* @see module:jGraduate.Options
*/
clientPath: 'images/'
},
/**
* @type {string}
* @see module:jGraduate.Options
*/
newstop: 'inverse' // same, inverse, black, white
};
const isGecko = navigator.userAgent.includes('Gecko/');
/**
* @typedef {PlainObject<string, string>} module:jGraduate.Attrs
*/
/**
* @param {SVGElement} elem
* @param {module:jGraduate.Attrs} attrs
* @returns {void}
*/
function setAttrs (elem, attrs) {
if (isGecko) {
Object.entries(attrs).forEach(([aname, val]) => {
elem.setAttribute(aname, val);
});
} else {
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);
if (newparent) {
newparent.append(elem);
}
return elem;
}
/**
* @typedef {PlainObject} module:jGraduate.ColorOpac Object may have one or both values
* @property {string} [color] #Hex color
* @property {Float} [opac] 0-1
*/
/**
* @typedef {PlainObject} module:jGraduate.Options
* @property {module:jGraduate~Paint} [paint] A Paint object object describing the paint to display initially; defaults to a new instance without options (defaults to opaque white)
* @property {external:Window} [window]
* @property {string} [window.pickerTitle='Drag markers to pick a paint']
* @property {PlainObject} [images]
* @property {string} [images.clientPath='images/']
* @property {"same"|"inverse"|"black"|"white"|module:jGraduate.ColorOpac} [newstop="inverse"]
*/
/**
* @callback external:jQuery.fn.jGraduate.OkCallback
* @param {external:jQuery.jGraduate.Paint} paint
* @returns {void}
*/
/**
* @callback external:jQuery.fn.jGraduate.CancelCallback
* @returns {void}
*/
/**
* @function external:jQuery.fn.jGraduate
* @param {module:jGraduate.Options} [options]
* @param {external:jQuery.fn.jGraduate.OkCallback} [okCallback] Called with a Paint object when Ok is pressed
* @param {external:jQuery.fn.jGraduate.CancelCallback} [cancelCallback] Called with no arguments when Cancel is pressed
* @returns {external:jQuery}
*/
$.fn.jGraduate = function (options, okCallback, cancelCallback) {
return this.each(function () {
const $this = $(this),
$settings = $.extend(true, {}, $.fn.jGraduateDefaults, options || {}),
id = $this.attr('id'),
idref = '#' + $this.attr('id') + ' ';
if (!idref) {
/* await */ $.alert('Container element must have an id attribute to maintain unique id strings for sub-elements.');
return;
}
const okClicked = function () {
switch ($this.paint.type) {
case 'radialGradient':
$this.paint.linearGradient = null;
break;
case 'linearGradient':
$this.paint.radialGradient = null;
break;
case 'solidColor':
$this.paint.radialGradient = $this.paint.linearGradient = null;
break;
}
typeof $this.okCallback === 'function' && $this.okCallback($this.paint);
$this.hide();
};
const cancelClicked = function () {
typeof $this.cancelCallback === 'function' && $this.cancelCallback();
$this.hide();
};
$.extend(
true,
$this,
// public properties, methods, and callbacks
{
// make a copy of the incoming paint
paint: new $.jGraduate.Paint({copy: $settings.paint}),
okCallback: typeof okCallback === 'function' ? okCallback : null,
cancelCallback: typeof cancelCallback === 'function' ? cancelCallback : null
}
);
let // pos = $this.position(),
color = null;
const $win = $(window);
if ($this.paint.type === 'none') {
$this.paint = new $.jGraduate.Paint({solidColor: 'ffffff'});
}
$this.addClass('jGraduate_Picker');
$this.html(
'<ul class="jGraduate_tabs">' +
'<li class="jGraduate_tab_color jGraduate_tab_current" data-type="col">Solid Color</li>' +
'<li class="jGraduate_tab_lingrad" data-type="lg">Linear Gradient</li>' +
'<li class="jGraduate_tab_radgrad" data-type="rg">Radial Gradient</li>' +
'</ul>' +
'<div class="jGraduate_colPick"></div>' +
'<div class="jGraduate_gradPick"></div>' +
'<div class="jGraduate_LightBox"></div>' +
'<div id="' + id + '_jGraduate_stopPicker" class="jGraduate_stopPicker"></div>'
);
const colPicker = $(idref + '> .jGraduate_colPick');
const gradPicker = $(idref + '> .jGraduate_gradPick');
gradPicker.html(
'<div id="' + id + '_jGraduate_Swatch" class="jGraduate_Swatch">' +
'<h2 class="jGraduate_Title">' + $settings.window.pickerTitle + '</h2>' +
'<div id="' + id + '_jGraduate_GradContainer" class="jGraduate_GradContainer"></div>' +
'<div id="' + id + '_jGraduate_StopSlider" class="jGraduate_StopSlider"></div>' +
'</div>' +
'<div class="jGraduate_Form jGraduate_Points jGraduate_lg_field">' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Begin Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_x1" size="3" title="Enter starting x value between 0.0 and 1.0"/>' +
'<label>y:</label>' +
'<input type="text" id="' + id + '_jGraduate_y1" size="3" title="Enter starting y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">End Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_x2" size="3" title="Enter ending x value between 0.0 and 1.0"/>' +
'<label>y:</label>' +
'<input type="text" id="' + id + '_jGraduate_y2" size="3" title="Enter ending y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="jGraduate_Form jGraduate_Points jGraduate_rg_field">' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Center Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_cx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
'<label>y:</label>' +
'<input type="text" id="' + id + '_jGraduate_cy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection">' +
'<label class="jGraduate_Form_Heading">Focal Point</label>' +
'<div class="jGraduate_Form_Section">' +
'<label>Match center: <input type="checkbox" checked="checked" id="' + id + '_jGraduate_match_ctr"/></label><br/>' +
'<label>x:</label>' +
'<input type="text" id="' + id + '_jGraduate_fx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
'<label>y:</label>' +
'<input type="text" id="' + id + '_jGraduate_fy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="jGraduate_StopSection jGraduate_SpreadMethod">' +
'<label class="jGraduate_Form_Heading">Spread method</label>' +
'<div class="jGraduate_Form_Section">' +
'<select class="jGraduate_spreadMethod">' +
'<option value=pad selected>Pad</option>' +
'<option value=reflect>Reflect</option>' +
'<option value=repeat>Repeat</option>' +
'</select>' +
'</div>' +
'</div>' +
'<div class="jGraduate_Form">' +
'<div class="jGraduate_Slider jGraduate_RadiusField jGraduate_rg_field">' +
'<label class="prelabel">Radius:</label>' +
'<div id="' + id + '_jGraduate_Radius" class="jGraduate_SliderBar jGraduate_Radius" title="Click to set radius">' +
'<img id="' + id + '_jGraduate_RadiusArrows" class="jGraduate_RadiusArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_RadiusInput" size="3" value="100"/>%</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_EllipField jGraduate_rg_field">' +
'<label class="prelabel">Ellip:</label>' +
'<div id="' + id + '_jGraduate_Ellip" class="jGraduate_SliderBar jGraduate_Ellip" title="Click to set Ellip">' +
'<img id="' + id + '_jGraduate_EllipArrows" class="jGraduate_EllipArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_EllipInput" size="3" value="0"/>%</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_AngleField jGraduate_rg_field">' +
'<label class="prelabel">Angle:</label>' +
'<div id="' + id + '_jGraduate_Angle" class="jGraduate_SliderBar jGraduate_Angle" title="Click to set Angle">' +
'<img id="' + id + '_jGraduate_AngleArrows" class="jGraduate_AngleArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_AngleInput" size="3" value="0"/>deg</label>' +
'</div>' +
'<div class="jGraduate_Slider jGraduate_OpacField">' +
'<label class="prelabel">Opac:</label>' +
'<div id="' + id + '_jGraduate_Opac" class="jGraduate_SliderBar jGraduate_Opac" title="Click to set Opac">' +
'<img id="' + id + '_jGraduate_OpacArrows" class="jGraduate_OpacArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
'</div>' +
'<label><input type="text" id="' + id + '_jGraduate_OpacInput" size="3" value="100"/>%</label>' +
'</div>' +
'</div>' +
'<div class="jGraduate_OkCancel">' +
'<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' +
'<input type="button" id="' + id + '_jGraduate_Cancel" class="jGraduate_Cancel" value="Cancel"/>' +
'</div>'
);
// --------------
// Set up all the SVG elements (the gradient, stops and rectangle)
const MAX = 256,
MARGINX = 0,
MARGINY = 0,
// STOP_RADIUS = 15 / 2,
SIZEX = MAX - 2 * MARGINX,
SIZEY = MAX - 2 * MARGINY;
const attrInput = {};
const SLIDERW = 145;
$('.jGraduate_SliderBar').width(SLIDERW);
const container = $('#' + id + '_jGraduate_GradContainer')[0];
const svg = mkElem('svg', {
id: id + '_jgraduate_svg',
width: MAX,
height: MAX,
xmlns: ns.svg
}, container);
// This wasn't working as designed
// let curType;
// curType = curType || $this.paint.type;
// if we are sent a gradient, import it
let curType = $this.paint.type;
let grad = $this.paint[curType];
let curGradient = grad;
const gradalpha = $this.paint.alpha;
const isSolid = curType === 'solidColor';
// Make any missing gradients
switch (curType) {
case 'solidColor':
// fall through
case 'linearGradient':
if (!isSolid) {
curGradient.id = id + '_lg_jgraduate_grad';
grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true));
}
mkElem('radialGradient', {
id: id + '_rg_jgraduate_grad'
}, svg);
if (curType === 'linearGradient') { break; }
// fall through
case 'radialGradient':
if (!isSolid) {
curGradient.id = id + '_rg_jgraduate_grad';
grad = curGradient = svg.appendChild(curGradient); // .cloneNode(true));
}
mkElem('linearGradient', {
id: id + '_lg_jgraduate_grad'
}, svg);
}
let stopGroup; // eslint-disable-line prefer-const
if (isSolid) {
grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0];
color = $this.paint[curType];
mkStop(0, '#' + color, 1);
const type = typeof $settings.newstop;
if (type === 'string') {
switch ($settings.newstop) {
case 'same':
mkStop(1, '#' + color, 1);
break;
case 'inverse': {
// Invert current color for second stop
let inverted = '';
for (let i = 0; i < 6; i += 2) {
// const ch = color.substr(i, 2);
let inv = (255 - Number.parseInt(color.substr(i, 2), 16)).toString(16);
if (inv.length < 2) inv = 0 + inv;
inverted += inv;
}
mkStop(1, '#' + inverted, 1);
break;
} case 'white':
mkStop(1, '#ffffff', 1);
break;
case 'black':
mkStop(1, '#000000', 1);
break;
}
} else if (type === 'object') {
const opac = ('opac' in $settings.newstop) ? $settings.newstop.opac : 1;
mkStop(1, ($settings.newstop.color || '#' + color), opac);
}
}
const x1 = Number.parseFloat(grad.getAttribute('x1') || 0.0),
y1 = Number.parseFloat(grad.getAttribute('y1') || 0.0),
x2 = Number.parseFloat(grad.getAttribute('x2') || 1.0),
y2 = Number.parseFloat(grad.getAttribute('y2') || 0.0);
const cx = Number.parseFloat(grad.getAttribute('cx') || 0.5),
cy = Number.parseFloat(grad.getAttribute('cy') || 0.5),
fx = Number.parseFloat(grad.getAttribute('fx') || cx),
fy = Number.parseFloat(grad.getAttribute('fy') || cy);
const previewRect = mkElem('rect', {
id: id + '_jgraduate_rect',
x: MARGINX,
y: MARGINY,
width: SIZEX,
height: SIZEY,
fill: 'url(#' + id + '_jgraduate_grad)',
'fill-opacity': gradalpha / 100
}, svg);
// stop visuals created here
const beginCoord = $('<div/>').attr({
class: 'grad_coord jGraduate_lg_field',
title: 'Begin Stop'
}).text(1).css({
top: y1 * MAX,
left: x1 * MAX
}).data('coord', 'start').appendTo(container);
const endCoord = beginCoord.clone().text(2).css({
top: y2 * MAX,
left: x2 * MAX
}).attr('title', 'End stop').data('coord', 'end').appendTo(container);
const centerCoord = $('<div/>').attr({
class: 'grad_coord jGraduate_rg_field',
title: 'Center stop'
}).text('C').css({
top: cy * MAX,
left: cx * MAX
}).data('coord', 'center').appendTo(container);
const focusCoord = centerCoord.clone().text('F').css({
top: fy * MAX,
left: fx * MAX,
display: 'none'
}).attr('title', 'Focus point').data('coord', 'focus').appendTo(container);
focusCoord[0].id = id + '_jGraduate_focusCoord';
// const coords = $(idref + ' .grad_coord');
// $(container).hover(function () {
// coords.animate({
// opacity: 1
// }, 500);
// }, function () {
// coords.animate({
// opacity: .2
// }, 500);
// });
let showFocus;
$.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function (i, attr) {
const isRadial = isNaN(attr[1]);
let attrval = curGradient.getAttribute(attr);
if (!attrval) {
// Set defaults
if (isRadial) {
// For radial points
attrval = '0.5';
} else {
// Only x2 is 1
attrval = attr === 'x2' ? '1.0' : '0.0';
}
}
attrInput[attr] = $('#' + id + '_jGraduate_' + attr)
.val(attrval)
.change(function () {
// TODO: Support values < 0 and > 1 (zoomable preview?)
if (isNaN(Number.parseFloat(this.value)) || this.value < 0) {
this.value = 0.0;
} else if (this.value > 1) {
this.value = 1.0;
}
if (!(attr[0] === 'f' && !showFocus)) {
if ((isRadial && curType === 'radialGradient') || (!isRadial && curType === 'linearGradient')) {
curGradient.setAttribute(attr, this.value);
}
}
const $elem = isRadial
? attr[0] === 'c' ? centerCoord : focusCoord
: attr[1] === '1' ? beginCoord : endCoord;
const cssName = attr.includes('x') ? 'left' : 'top';
$elem.css(cssName, this.value * MAX);
}).change();
});
/**
*
* @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) {
colr = stopElem.getAttribute('stop-color');
opac = stopElem.getAttribute('stop-opacity');
n = stopElem.getAttribute('offset');
} else {
curGradient.append(stop);
}
if (opac === null) opac = 1;
const pickerD = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z';
const pathbg = mkElem('path', {
d: pickerD,
fill: 'url(#jGraduate_trans)',
transform: 'translate(' + (10 + n * MAX) + ', 26)'
}, stopGroup);
const path = mkElem('path', {
d: pickerD,
fill: colr,
'fill-opacity': opac,
transform: 'translate(' + (10 + n * MAX) + ', 26)',
stroke: '#000',
'stroke-width': 1.5
}, stopGroup);
$(path).mousedown(function (e) {
selectStop(this);
drag = curStop;
$win.mousemove(dragColor).mouseup(remDrags);
stopOffset = stopMakerDiv.offset();
e.preventDefault();
return false;
}).data('stop', stop).data('bg', pathbg).dblclick(function () {
$('div.jGraduate_LightBox').show();
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 = (Number.parseFloat(stopOpacity) * 255).toString(16);
while (thisAlpha.length < 2) { thisAlpha = '0' + 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: 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);
stop.setAttribute('stop-opacity', stopOpacity);
$('div.jGraduate_LightBox').hide();
$('#' + id + '_jGraduate_stopPicker').hide();
}, null, function () {
$('div.jGraduate_LightBox').hide();
$('#' + id + '_jGraduate_stopPicker').hide();
});
});
$(curGradient).find('stop').each(function () {
const curS = $(this);
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);
path.setAttribute('fill', newcolor);
stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac);
path.setAttribute('fill-opacity', newopac === null ? 1 : newopac);
}
curS.before(stop);
return false;
}
return true;
});
if (sel) selectStop(path);
return stop;
}
/**
*
* @returns {void}
*/
function remStop () {
delStop.setAttribute('display', 'none');
const path = $(curStop);
const stop = path.data('stop');
const bg = path.data('bg');
$([curStop, stop, bg]).remove();
}
const stopMakerDiv = $('#' + id + '_jGraduate_StopSlider');
let stops, curStop, drag;
const delStop = mkElem('path', {
d: 'm9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5',
fill: 'none',
stroke: '#D00',
'stroke-width': 5,
display: 'none'
}, undefined); // stopMakerSVG);
/**
* @param {Element} item
* @returns {void}
*/
function selectStop (item) {
if (curStop) curStop.setAttribute('stroke', '#000');
item.setAttribute('stroke', 'blue');
curStop = item;
// stops = $('stop');
// opac_select.val(curStop.attr('fill-opacity') || 1);
// root.append(delStop);
}
let stopOffset;
/**
*
* @returns {void}
*/
function remDrags () {
$win.unbind('mousemove', dragColor);
if (delStop.getAttribute('display') !== 'none') {
remStop();
}
drag = null;
}
let scaleX = 1, scaleY = 1, angle = 0;
let cX = cx;
let cY = cy;
/**
*
* @returns {void}
*/
function xform () {
const rot = angle ? 'rotate(' + angle + ',' + cX + ',' + cY + ') ' : '';
if (scaleX === 1 && scaleY === 1) {
curGradient.removeAttribute('gradientTransform');
// $('#ang').addClass('dis');
} else {
const x = -cX * (scaleX - 1);
const y = -cY * (scaleY - 1);
curGradient.setAttribute('gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scaleX + ',' + scaleY + ')');
// $('#ang').removeClass('dis');
}
}
/**
* @param {Event} evt
* @returns {void}
*/
function dragColor (evt) {
let x = evt.pageX - stopOffset.left;
const y = evt.pageY - stopOffset.top;
x = x < 10
? 10
: x > MAX + 10
? MAX + 10
: x;
const xfStr = 'translate(' + x + ', 26)';
if (y < -60 || y > 130) {
delStop.setAttribute('display', 'block');
delStop.setAttribute('transform', xfStr);
} else {
delStop.setAttribute('display', 'none');
}
drag.setAttribute('transform', xfStr);
$.data(drag, 'bg').setAttribute('transform', xfStr);
const stop = $.data(drag, 'stop');
const sX = (x - 10) / MAX;
stop.setAttribute('offset', sX);
let last = 0;
$(curGradient).find('stop').each(function (i) {
const cur = this.getAttribute('offset');
const t = $(this);
if (cur < last) {
t.prev().before(t);
stops = $(curGradient).find('stop');
}
last = cur;
});
}
const stopMakerSVG = mkElem('svg', {
width: '100%',
height: 45
}, stopMakerDiv[0]);
const transPattern = mkElem('pattern', {
width: 16,
height: 16,
patternUnits: 'userSpaceOnUse',
id: 'jGraduate_trans'
}, stopMakerSVG);
const transImg = mkElem('image', {
width: 16,
height: 16
}, transPattern);
const bgImage = $settings.images.clientPath + 'map-opacity.png';
transImg.setAttributeNS(ns.xlink, 'xlink:href', bgImage);
$(stopMakerSVG).click(function (evt) {
stopOffset = stopMakerDiv.offset();
const {target} = evt;
if (target.tagName === 'path') return;
let x = evt.pageX - stopOffset.left - 8;
x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10 : x;
mkStop(x / MAX, 0, 0, true);
evt.stopPropagation();
});
$(stopMakerSVG).mouseover(function () {
stopMakerSVG.append(delStop);
});
stopGroup = mkElem('g', {}, stopMakerSVG);
mkElem('line', {
x1: 10,
y1: 15,
x2: MAX + 10,
y2: 15,
'stroke-width': 2,
stroke: '#000'
}, stopMakerSVG);
const spreadMethodOpt = gradPicker.find('.jGraduate_spreadMethod').change(function () {
curGradient.setAttribute('spreadMethod', $(this).val());
});
// handle dragging the stop around the swatch
let draggingCoord = null;
const onCoordDrag = function (evt) {
let x = evt.pageX - offset.left;
let y = evt.pageY - offset.top;
// clamp stop to the swatch
x = x < 0 ? 0 : x > MAX ? MAX : x;
y = y < 0 ? 0 : y > MAX ? MAX : y;
draggingCoord.css('left', x).css('top', y);
// calculate stop offset
const fracx = x / SIZEX;
const fracy = y / SIZEY;
const type = draggingCoord.data('coord');
const grd = curGradient;
switch (type) {
case 'start':
attrInput.x1.val(fracx);
attrInput.y1.val(fracy);
grd.setAttribute('x1', fracx);
grd.setAttribute('y1', fracy);
break;
case 'end':
attrInput.x2.val(fracx);
attrInput.y2.val(fracy);
grd.setAttribute('x2', fracx);
grd.setAttribute('y2', fracy);
break;
case 'center':
attrInput.cx.val(fracx);
attrInput.cy.val(fracy);
grd.setAttribute('cx', fracx);
grd.setAttribute('cy', fracy);
cX = fracx;
cY = fracy;
xform();
break;
case 'focus':
attrInput.fx.val(fracx);
attrInput.fy.val(fracy);
grd.setAttribute('fx', fracx);
grd.setAttribute('fy', fracy);
xform();
}
evt.preventDefault();
};
const onCoordUp = function () {
draggingCoord = null;
$win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp);
};
// Linear gradient
// (function () {
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
let numstops = stops.length;
// if there are not at least two stops, then
if (numstops < 2) {
while (numstops < 2) {
curGradient.append(document.createElementNS(ns.svg, 'stop'));
++numstops;
}
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
}
for (let i = 0; i < numstops; i++) {
mkStop(0, 0, 0, 0, stops[i]);
}
spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad');
let offset;
// No match, so show focus point
showFocus = false;
previewRect.setAttribute('fill-opacity', gradalpha / 100);
$('#' + id + ' div.grad_coord').mousedown(function (evt) {
evt.preventDefault();
draggingCoord = $(this);
// const sPos = draggingCoord.offset();
offset = draggingCoord.parent().offset();
$win.mousemove(onCoordDrag).mouseup(onCoordUp);
});
// bind GUI elements
$('#' + id + '_jGraduate_Ok').bind('click', function () {
$this.paint.type = curType;
$this.paint[curType] = curGradient.cloneNode(true);
$this.paint.solidColor = null;
okClicked();
});
$('#' + id + '_jGraduate_Cancel').bind('click', function (paint) {
cancelClicked();
});
if (curType === 'radialGradient') {
if (showFocus) {
focusCoord.show();
} else {
focusCoord.hide();
attrInput.fx.val('');
attrInput.fy.val('');
}
}
$('#' + id + '_jGraduate_match_ctr')[0].checked = !showFocus;
let lastfx, lastfy;
$('#' + id + '_jGraduate_match_ctr').change(function () {
showFocus = !this.checked;
focusCoord.toggle(showFocus);
attrInput.fx.val('');
attrInput.fy.val('');
const grd = curGradient;
if (!showFocus) {
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;
grd.setAttribute('fx', fX);
grd.setAttribute('fy', fY);
attrInput.fx.val(fX);
attrInput.fy.val(fY);
}
});
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
numstops = stops.length;
// if there are not at least two stops, then
if (numstops < 2) {
while (numstops < 2) {
curGradient.append(document.createElementNS(ns.svg, 'stop'));
++numstops;
}
stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
}
let slider;
const setSlider = function (e) {
const {offset: {left}} = slider;
const div = slider.parent;
let x = (e.pageX - left - Number.parseInt(div.css('border-left-width')));
if (x > SLIDERW) x = SLIDERW;
if (x <= 0) x = 0;
const posx = x - 5;
x /= SLIDERW;
switch (slider.type) {
case 'radius':
x = (x * 2) ** 2.5;
if (x > 0.98 && x < 1.02) x = 1;
if (x <= 0.01) x = 0.01;
curGradient.setAttribute('r', x);
break;
case 'opacity':
$this.paint.alpha = Number.parseInt(x * 100);
previewRect.setAttribute('fill-opacity', x);
break;
case 'ellip':
scaleX = 1;
scaleY = 1;
if (x < 0.5) {
x /= 0.5; // 0.001
scaleX = x <= 0 ? 0.01 : x;
} else if (x > 0.5) {
x /= 0.5; // 2
x = 2 - x;
scaleY = x <= 0 ? 0.01 : x;
}
xform();
x -= 1;
if (scaleY === x + 1) {
x = Math.abs(x);
}
break;
case 'angle':
x -= 0.5;
angle = x *= 180;
xform();
x /= 100;
break;
}
slider.elem.css({'margin-left': posx});
x = Math.round(x * 100);
slider.input.val(x);
};
let ellipVal = 0, angleVal = 0;
if (curType === 'radialGradient') {
const tlist = curGradient.gradientTransform.baseVal;
if (tlist.numberOfItems === 2) {
const t = tlist.getItem(0);
const s = tlist.getItem(1);
if (t.type === 2 && s.type === 3) {
const m = s.matrix;
if (m.a !== 1) {
ellipVal = Math.round(-(1 - m.a) * 100);
} else if (m.d !== 1) {
ellipVal = Math.round((1 - m.d) * 100);
}
}
} else if (tlist.numberOfItems === 3) {
// Assume [R][T][S]
const r = tlist.getItem(0);
const t = tlist.getItem(1);
const s = tlist.getItem(2);
if (r.type === 4 &&
t.type === 2 &&
s.type === 3
) {
angleVal = Math.round(r.angle);
const m = s.matrix;
if (m.a !== 1) {
ellipVal = Math.round(-(1 - m.a) * 100);
} else if (m.d !== 1) {
ellipVal = Math.round((1 - m.d) * 100);
}
}
}
}
const sliders = {
radius: {
handle: '#' + id + '_jGraduate_RadiusArrows',
input: '#' + id + '_jGraduate_RadiusInput',
val: (curGradient.getAttribute('r') || 0.5) * 100
},
opacity: {
handle: '#' + id + '_jGraduate_OpacArrows',
input: '#' + id + '_jGraduate_OpacInput',
val: $this.paint.alpha || 100
},
ellip: {
handle: '#' + id + '_jGraduate_EllipArrows',
input: '#' + id + '_jGraduate_EllipInput',
val: ellipVal
},
angle: {
handle: '#' + id + '_jGraduate_AngleArrows',
input: '#' + id + '_jGraduate_AngleInput',
val: angleVal
}
};
$.each(sliders, function (type, data) {
const handle = $(data.handle);
handle.mousedown(function (evt) {
const parent = handle.parent();
slider = {
type,
elem: handle,
input: $(data.input),
parent,
offset: parent.offset()
};
$win.mousemove(dragSlider).mouseup(stopSlider);
evt.preventDefault();
});
$(data.input).val(data.val).change(function () {
const isRad = curType === 'radialGradient';
let val = Number(this.value);
let xpos = 0;
switch (type) {
case 'radius':
if (isRad) curGradient.setAttribute('r', val / 100);
xpos = (((val / 100) ** (1 / 2.5)) / 2) * SLIDERW;
break;
case 'opacity':
$this.paint.alpha = val;
previewRect.setAttribute('fill-opacity', val / 100);
xpos = val * (SLIDERW / 100);
break;
case 'ellip':
scaleX = scaleY = 1;
if (val === 0) {
xpos = SLIDERW * 0.5;
break;
}
if (val > 99.5) val = 99.5;
if (val > 0) {
scaleY = 1 - (val / 100);
} else {
scaleX = -(val / 100) - 1;
}
xpos = SLIDERW * ((val + 100) / 2) / 100;
if (isRad) xform();
break;
case 'angle':
angle = val;
xpos = angle / 180;
xpos += 0.5;
xpos *= SLIDERW;
if (isRad) xform();
}
if (xpos > SLIDERW) {
xpos = SLIDERW;
} else if (xpos < 0) {
xpos = 0;
}
handle.css({'margin-left': xpos - 5});
}).change();
});
const dragSlider = function (evt) {
setSlider(evt);
evt.preventDefault();
};
const stopSlider = function (evt) {
$win.unbind('mousemove', dragSlider).unbind('mouseup', stopSlider);
slider = null;
};
// --------------
let thisAlpha = ($this.paint.alpha * 255 / 100).toString(16);
while (thisAlpha.length < 2) { thisAlpha = '0' + thisAlpha; }
thisAlpha = thisAlpha.split('.')[0];
color = $this.paint.solidColor === 'none' ? '' : $this.paint.solidColor + thisAlpha;
if (!isSolid) {
color = stops[0].getAttribute('stop-color');
}
// This should be done somewhere else, probably
$.extend($.fn.jPicker.defaults.window, {
alphaSupport: true, effects: {type: 'show', speed: 0}
});
colPicker.jPicker(
{
window: {title: $settings.window.pickerTitle},
images: {clientPath: $settings.images.clientPath},
color: {active: color, alphaSupport: true}
},
function (clr) {
$this.paint.type = 'solidColor';
$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();
},
null,
function () { cancelClicked(); }
);
const tabs = $(idref + ' .jGraduate_tabs li');
tabs.click(function () {
tabs.removeClass('jGraduate_tab_current');
$(this).addClass('jGraduate_tab_current');
$(idref + ' > div').hide();
const type = $(this).attr('data-type');
/* const container = */ $(idref + ' .jGraduate_gradPick').show();
if (type === 'rg' || type === 'lg') {
// Show/hide appropriate fields
$('.jGraduate_' + type + '_field').show();
$('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field').hide();
$('#' + id + '_jgraduate_rect')[0].setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)');
// Copy stops
curType = type === 'lg' ? 'linearGradient' : 'radialGradient';
$('#' + id + '_jGraduate_OpacInput').val($this.paint.alpha).change();
const newGrad = $('#' + id + '_' + type + '_jgraduate_grad')[0];
if (curGradient !== newGrad) {
const curStops = $(curGradient).find('stop');
$(newGrad).empty().append(curStops);
curGradient = newGrad;
const sm = spreadMethodOpt.val();
curGradient.setAttribute('spreadMethod', sm);
}
showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy);
$('#' + id + '_jGraduate_focusCoord').toggle(showFocus);
if (showFocus) {
$('#' + id + '_jGraduate_match_ctr')[0].checked = false;
}
} else {
$(idref + ' .jGraduate_gradPick').hide();
$(idref + ' .jGraduate_colPick').show();
}
});
$(idref + ' > div').hide();
tabs.removeClass('jGraduate_tab_current');
let tab;
switch ($this.paint.type) {
case 'linearGradient':
tab = $(idref + ' .jGraduate_tab_lingrad');
break;
case 'radialGradient':
tab = $(idref + ' .jGraduate_tab_radgrad');
break;
default:
tab = $(idref + ' .jGraduate_tab_color');
break;
}
$this.show();
// jPicker will try to show after a 0ms timeout, so need to fire this after that
setTimeout(() => {
tab.addClass('jGraduate_tab_current').click();
}, 10);
});
};
return $;
}