/* eslint-disable no-loop-func */ /* eslint-disable unicorn/prefer-node-remove */ /* eslint-disable prefer-destructuring */ /* eslint-disable no-unsanitized/property */ /** * @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? */ /* globals $ */ import Paint from './paint.js'; import {jPickerDefaults, jPickerMethod} from './jQuery.jPicker.js'; import {findPos} from './Util.js'; /** * @todo JFH: This jQuery plugin was adapted to work within a Web Component. * We have to rewrite it as a pure webcomponent. */ /** * The jQuery namespace. * @external jQuery */ /** * The jQuery plugin namespace. * @namespace {PlainObject} fn * @memberof external:jQuery * @see {@link http://learn.jquery.com/plugins/|jQuery Plugins} */ const ns = { svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink' }; if (!window.console) { window.console = { log (str) { /* empty fn */ }, dir (str) { /* empty fn */ } }; } /** * 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 ($) { /* eslint-disable jsdoc/require-property */ /** * @namespace {PlainObject} jGraduate * @memberof external:jQuery */ export const 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 */ export const 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} 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; } function deepExtend(out) { out = out || {}; for (let i = 1, len = arguments.length; i < len; ++i) { let obj = arguments[i]; if (!obj) { continue; } for (const key in obj) { if (!obj.hasOwnProperty(key)) { continue; } // based on https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ if (Object.prototype.toString.call(obj[key]) === '[object Object]') { out[key] = deepExtend(out[key], obj[key]); continue; } out[key] = obj[key]; } } return out; } /** * @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} */ export function jGraduateMethod (elem, options, okCallback, cancelCallback) { const $this = elem, $settings = Object.assign({}, jGraduateDefaults, options || {}), id = $this.getAttribute('id'), idref = '#' + $this.getAttribute('id') + ' '; // JFH !!!!! const $shadowRoot = elem.parentNode; // const $wc = (selector) => $($shadowRoot.querySelectorAll(selector)); if (!idref) { // eslint-disable-next-line no-alert 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.style.display = 'none'; }; const cancelClicked = function () { typeof $this.cancelCallback === 'function' && $this.cancelCallback(); $this.style.display = 'none'; }; Object.assign($this, { // 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.classList.add('jGraduate_Picker'); /* eslint-disable max-len */ $this.innerHTML = '' + '
' + '
' + '
' + '
'; /* JFH !!!! */ const colPicker = $this.querySelector('#jGraduate_colPick'); const gradPicker = $this.querySelector('#jGraduate_gradPick'); const html = '
' + '

' + $settings.window.pickerTitle + '

' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '' + '
' + '
' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '
' + '' + '' + '
'; const div = document.createElement('div'); div.innerHTML = html; while (div.children.length > 0) { gradPicker.appendChild(div.children[0]); } /* eslint-enable max-len */ // -------------- // 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; const JQSliderBars = $this.querySelectorAll('.jGraduate_SliderBar'); for (const JQSliderBar of JQSliderBars) { JQSliderBar.style.width = SLIDERW + 'px'; } // JFH !!!!!! const container = $this.querySelector('#' + id + '_jGraduate_GradContainer'); 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); } 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); } mkElem('linearGradient', { id: id + '_lg_jgraduate_grad' }, svg); } let stopGroup; // eslint-disable-line prefer-const if (isSolid) { // JFH !!!!!!!! grad = curGradient = $this.querySelector('#' + id + '_lg_jgraduate_grad'); 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 = document.createElement('div'); beginCoord.setAttribute('class', 'grad_coord jGraduate_lg_field'); beginCoord.setAttribute('title', 'Begin Stop'); beginCoord.textContent = 1; beginCoord.style.top = y1 * MAX; beginCoord.style.left = x1 * MAX; beginCoord.dataset.coord = 'start'; container.appendChild(beginCoord); const endCoord = document.createElement('div'); endCoord.setAttribute('class', 'grad_coord jGraduate_lg_field'); endCoord.setAttribute('title', 'End stop'); endCoord.textContent = 2; endCoord.style.top = y2 * MAX; endCoord.style.left = x2 * MAX; endCoord.dataset.coord = 'end'; container.appendChild(endCoord); const centerCoord = document.createElement('div'); centerCoord.setAttribute('class', 'grad_coord jGraduate_rg_field'); centerCoord.setAttribute('title', 'Center stop'); centerCoord.textContent = 'C'; centerCoord.style.top = cy * MAX; centerCoord.style.left = cx * MAX; centerCoord.dataset.coord = 'center'; container.appendChild(centerCoord); const focusCoord = document.createElement('div'); focusCoord.setAttribute('class', 'grad_coord jGraduate_rg_field'); focusCoord.setAttribute('title', 'Focus point'); focusCoord.textContent = 'F'; focusCoord.style.top = fy * MAX; focusCoord.style.left = fx * MAX; focusCoord.style.display = 'none'; focusCoord.dataset.coord = 'focus'; focusCoord.setAttribute('id', id + '_jGraduate_focusCoord'); container.appendChild(focusCoord); let showFocus; const onAttrChangeHandler = (e, attr, isRadial) => { // TODO: Support values < 0 and > 1 (zoomable preview?) if (isNaN(Number.parseFloat(e.target.value)) || e.target.value < 0) { e.target.value = 0.0; } else if (e.target.value > 1) { e.target.value = 1.0; } if (!(attr[0] === 'f' && !showFocus) && ((isRadial && curType === 'radialGradient') || (!isRadial && curType === 'linearGradient'))) { curGradient.setAttribute(attr, e.target.value); } const $elem = isRadial ? attr[0] === 'c' ? centerCoord : focusCoord : attr[1] === '1' ? beginCoord : endCoord; if (attr.includes('x') === 'left') { $elem.style.left = e.target.value * MAX; } else if (attr.includes('x') === 'top') { $elem.style.top = e.target.value * MAX; } }; for (const [i, attr] of ['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'].entries()) { 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] = $this.querySelector('#' + id + '_jGraduate_' + attr); attrInput[attr].value = attrval; attrInput[attr].addEventListener('change', (evt) => onAttrChangeHandler(evt, attr, isRadial)); attrInput[attr].dispatchEvent(new Event('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', { id: 'jq_stop_' + Math.floor((Math.random() * 10000) + 1), '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.appendChild(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', { id: 'jq_pathbg_' + Math.floor((Math.random() * 10000) + 1), 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.addEventListener('mousedown', function (e) { selectStop(this); drag = curStop; $win.addEventListener('mousemove', dragColor); $win.addEventListener('mouseup', remDrags); stopOffset = findPos(stopMakerDiv); e.preventDefault(); return false; }); path.dataset.stop = stop.getAttribute('id'); path.dataset.bg = pathbg.getAttribute('id'); path.addEventListener('dblclick', function () { $this.querySelector('#jGraduate_LightBox').style.display = 'block'; const colorhandle = 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; const jqPickerElem = $this.querySelector('#' + id + '_jGraduate_stopPicker'); jqPickerElem.style.left = '100px'; jqPickerElem.style.bottom = '15px'; jPickerMethod(jqPickerElem, { 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); $this.querySelector('#jGraduate_LightBox').style.display = 'none'; $this.querySelector('#' + id + '_jGraduate_stopPicker').style.display = 'none'; }, null, function () { $this.querySelector('#jGraduate_LightBox').style.display = 'none'; $this.querySelector('#' + id + '_jGraduate_stopPicker').style.display = 'none'; }); }); const jqStopEls = curGradient.querySelectorAll('stop'); for (const jqStopEl of jqStopEls) { const curS = jqStopEl; if (Number(jqStopEl.getAttribute('offset')) > n) { if (!colr) { const newcolor = jqStopEl.getAttribute('stop-color'); const newopac = jqStopEl.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.insertAdjacentElement('beforebegin', stop); // curS.before(stop); // return false; } // return true; } if (sel) selectStop(path); return stop; } /** * * @returns {void} */ function remStop () { delStop.setAttribute('display', 'none'); const path = curStop; delete path.dataset.stop; delete path.dataset.bg; curStop.parentNode.removeChild(curStop); } const stopMakerDiv = $this.querySelector('#' + 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; } let stopOffset; /** * * @returns {void} */ function remDrags () { $win.removeEventListener('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'); // $wc('#ang').addClass('dis'); } else { const x = -cX * (scaleX - 1); const y = -cY * (scaleY - 1); curGradient.setAttribute( 'gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scaleX + ',' + scaleY + ')' ); // $wc('#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); const jqpgpath = $this.querySelector('#'+drag.dataset.bg); jqpgpath.setAttribute('transform', xfStr); const stop = $this.querySelector('#'+drag.dataset.stop); const sX = (x - 10) / MAX; stop.setAttribute('offset', sX); let last = 0; const jqStopElems = curGradient.querySelectorAll('stop'); [].forEach.call(jqStopElems, function (jqStopElem) { const cur = jqStopElem.getAttribute('offset'); const t = jqStopElem; if (cur < last) { t.previousElementSibling.insertAdjacentElement('beforebegin', t); stops = curGradient.querySelectorAll('stop'); } last = cur; }); } const stopMakerSVG = mkElem('svg', { width: '100%', height: 45 }, stopMakerDiv); 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.addEventListener('click', function (evt) { stopOffset = findPos(stopMakerDiv); 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.addEventListener('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.querySelector('#jGraduate_spreadMethod'); spreadMethodOpt.addEventListener('change', function () { curGradient.setAttribute('spreadMethod', this.value); }); // 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.style.left = x + 'px'; draggingCoord.style.top = y + 'px'; // calculate stop offset const fracx = x / SIZEX; const fracy = y / SIZEY; const type = draggingCoord.dataset.coord; const grd = curGradient; switch (type) { case 'start': attrInput.x1.value = fracx; attrInput.y1.value = fracy; grd.setAttribute('x1', fracx); grd.setAttribute('y1', fracy); break; case 'end': attrInput.x2.value = fracx; attrInput.y2.value = fracy; grd.setAttribute('x2', fracx); grd.setAttribute('y2', fracy); break; case 'center': attrInput.cx.value = fracx; attrInput.cy.value = fracy; grd.setAttribute('cx', fracx); grd.setAttribute('cy', fracy); cX = fracx; cY = fracy; xform(); break; case 'focus': attrInput.fx.value = fracx; attrInput.fy.value = fracy; grd.setAttribute('fx', fracx); grd.setAttribute('fy', fracy); xform(); } evt.preventDefault(); }; const onCoordUp = function () { draggingCoord = null; $win.removeEventListener('mousemove', onCoordDrag); $win.removeEventListener('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.setAttribute('value', curGradient.getAttribute('spreadMethod') || 'pad'); let offset; // No match, so show focus point showFocus = false; previewRect.setAttribute('fill-opacity', gradalpha / 100); const JQGradCoords = $this.querySelectorAll('#' + id + ' div.grad_coord'); const onMouseDownGradCoords = (e) => { e.preventDefault(); draggingCoord = e.target; offset = findPos(draggingCoord.parentNode); $win.addEventListener('mousemove', onCoordDrag); $win.addEventListener('mouseup', onCoordUp); }; for (const JQGradCoord of JQGradCoords) { JQGradCoord.addEventListener('mousedown', onMouseDownGradCoords); } // bind GUI elements $this.querySelector('#' + id + '_jGraduate_Ok').addEventListener('click', function () { $this.paint.type = curType; $this.paint[curType] = curGradient.cloneNode(true); $this.paint.solidColor = null; okClicked(); }); $this.querySelector('#' + id + '_jGraduate_Cancel').addEventListener('click', cancelClicked); if (curType === 'radialGradient') { if (showFocus) { focusCoord.style.display = 'block'; } else { focusCoord.style.display = 'none'; attrInput.fx.value = ''; attrInput.fy.value = ''; } } $this.querySelector('#' + id + '_jGraduate_match_ctr').checked = !showFocus; let lastfx, lastfy; const onMatchCtrHandler = (e) => { showFocus = !e.target.checked; if (showFocus) { focusCoord.style.display = 'block'; } else { focusCoord.style.display = 'none'; } attrInput.fx.value = ''; attrInput.fy.value = ''; 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.value = fX; attrInput.fy.value = fY; } }; $this.querySelector('#' + id + '_jGraduate_match_ctr').addEventListener('change', onMatchCtrHandler); 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 divi = slider.parent; let x = (e.pageX - left - Number.parseInt(getComputedStyle(divi, null).getPropertyValue('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.style.marginLeft = posx + 'px'; x = Math.round(x * 100); slider.input.value = 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 } }; for (const [index, [type, data]] of Object.entries(Object.entries(sliders))) { const handle = $this.querySelector(data.handle); const sInput = $this.querySelector(data.input); handle.addEventListener('mousedown', function (evt) { const parent = handle.parentNode; slider = { type, elem: handle, input: sInput, parent, offset: findPos(parent) }; $win.addEventListener('mousemove', dragSlider); $win.addEventListener('mouseup', stopSlider); evt.preventDefault(); }); sInput.value = data.val; sInput.addEventListener('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.style.marginLeft = (xpos - 5) + 'px'; }); sInput.dispatchEvent(new Event('change')); } const dragSlider = function (evt) { setSlider(evt); evt.preventDefault(); }; const stopSlider = function (evt) { $win.removeEventListener('mousemove', dragSlider); $win.removeEventListener('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 Object.assign(jPickerDefaults.window, { alphaSupport: true, effects: {type: 'show', speed: 0} }); jPickerMethod( colPicker, { 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(); } ); // JFH !!!! const tabs = $this.querySelectorAll('.jGraduate_tabs li'); const onTabsClickHandler = (e) => { for (const tab of tabs) { tab.classList.remove('jGraduate_tab_current'); } e.target.classList.add('jGraduate_tab_current'); const innerDivs = $this.querySelectorAll(idref + ' > div'); [].forEach.call(innerDivs, function (innerDiv) { innerDiv.style.display = 'none'; }); const type = e.target.dataset.type; gradPicker.style.display = 'block'; if (type === 'rg' || type === 'lg') { const tFileds = $this.querySelectorAll('.jGraduate_' + type + '_field'); [].forEach.call(tFileds, function (tFiled) { tFiled.style.display = 'block'; }); const t1Fileds = $this.querySelectorAll('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field'); [].forEach.call(t1Fileds, function (tFiled) { tFiled.style.display = 'none'; }); $this.querySelectorAll('#' + id + '_jgraduate_rect')[0] .setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)'); curType = type === 'lg' ? 'linearGradient' : 'radialGradient'; const jOpacInput = $this.querySelector('#' + id + '_jGraduate_OpacInput'); jOpacInput.value = $this.paint.alpha; jOpacInput.dispatchEvent(new Event('change')); const newGrad = $this.querySelectorAll('#' + id + '_' + type + '_jgraduate_grad')[0]; if (curGradient !== newGrad) { const curStops = curGradient.querySelectorAll('stop'); while (newGrad.firstChild) { newGrad.removeChild(newGrad.firstChild); } [].forEach.call(curStops, function (curS) { newGrad.appendChild(curS); }); curGradient = newGrad; const sm = spreadMethodOpt.getAttribute('value'); curGradient.setAttribute('spreadMethod', sm); } showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy); const jQfocusCoord = $this.querySelectorAll('#' + id + '_jGraduate_focusCoord'); if (jQfocusCoord[0].style.display === 'none') { jQfocusCoord[0].style.display = 'block'; } else { jQfocusCoord[0].style.display = 'none'; } if (showFocus) { $this.querySelectorAll('#' + id + '_jGraduate_match_ctr')[0].checked = false; } } else { gradPicker.style.display = 'none'; colPicker.style.display = 'block'; } }; for (const tab of tabs) { tab.addEventListener('click', onTabsClickHandler); } const innerDivs = $this.querySelectorAll(idref + ' > div'); [].forEach.call(innerDivs, function (innerDiv) { innerDiv.style.display = 'none'; }); for (const tab of tabs) { tab.classList.remove('jGraduate_tab_current'); } let tab; switch ($this.paint.type) { case 'linearGradient': tab = $this.querySelector(idref + ' .jGraduate_tab_lingrad'); break; case 'radialGradient': tab = $this.querySelector(idref + ' .jGraduate_tab_radgrad'); break; default: tab = $this.querySelector(idref + ' .jGraduate_tab_color'); break; } $this.style.display = 'block'; // jPicker will try to show after a 0ms timeout, so need to fire this after that setTimeout(() => { tab.classList.add('jGraduate_tab_current'); tab.dispatchEvent(new Event('click')); }, 10); }