/* globals svgEditor */
/**
* @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?
*/
import SvgCanvas from '@svgedit/svgcanvas'
import { jPickerDefaults, jPickerMethod } from './jQuery.jPicker.js'
import { findPos } from '../../../common/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 () { /* empty fn */ },
dir () { /* 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 ($) {
/**
* @namespace {PlainObject} jGraduate
* @memberof external:jQuery
*/
export const jGraduate = /** @lends external:jQuery.jGraduate */ {
/**
* @class external:jQuery.jGraduate.Paint
* @see module:jGraduate~Paint
*/
Paint: SvgCanvas.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<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?.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}
*/
export function jGraduateMethod (elem, options, okCallback, cancelCallback, i18next) {
const $this = elem
const $settings = Object.assign({}, jGraduateDefaults, options || {})
const id = $this.getAttribute('id')
const idref = '#' + $this.getAttribute('id') + ' '
if (!idref) {
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')
$this.innerHTML = `<ul class="jGraduate_tabs">
<li class="jGraduate_tab_color jGraduate_tab_current" id="jGraduate_tab_color" data-type="col">${i18next.t('config.jgraduate_solid_color')}</li>
<li class="jGraduate_tab_lingrad" id="jGraduate_tab_lingrad" data-type="lg">${i18next.t('config.jgraduate_linear_gradient')}</li>
<li class="jGraduate_tab_radgrad" id="jGraduate_tab_radgrad" data-type="rg">${i18next.t('config.jgraduate_radial_gradient')}</li>
</ul>
<div class="jGraduate_colPick" id="jGraduate_colPick"></div>
<div class="jGraduate_gradPick" id="jGraduate_gradPick"></div>
<div class="jGraduate_LightBox" id="jGraduate_LightBox"></div>
<div id="${id}_jGraduate_stopPicker" class="jGraduate_stopPicker"></div>`
const colPicker = $this.querySelector('#jGraduate_colPick')
const gradPicker = $this.querySelector('#jGraduate_gradPick')
const 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">${i18next.t('config.jgraduate_begin_point')}</label>
<div class="jGraduate_Form_Section">
<label>x:</label>
<input type="text" id="${id}_jGraduate_x1" size="3" title="${i18next.t('config.jgraduate_enter_starting_x')}"/>
<label>y:</label>
<input type="text" id="${id}_jGraduate_y1" size="3" title="${i18next.t('config.jgraduate_enter_starting_y')}"/>
</div>
</div>
<div class="jGraduate_StopSection">
<label class="jGraduate_Form_Heading">${i18next.t('config.jgraduate_end_point')}</label>
<div class="jGraduate_Form_Section">
<label>x:</label>
<input type="text" id="${id}_jGraduate_x2" size="3" title="${i18next.t('config.jgraduate_enter_ending_x')}"/>
<label>y:</label>
<input type="text" id="${id}_jGraduate_y2" size="3" title="${i18next.t('config.jgraduate_enter_ending_y')}"/>
</div>
</div>
</div>
<div class="jGraduate_Form jGraduate_Points jGraduate_rg_field">
<div class="jGraduate_StopSection">
<label class="jGraduate_Form_Heading">${i18next.t('config.jgraduate_center_point')}</label>
<div class="jGraduate_Form_Section">
<label>x:</label>
<input type="text" id="${id}_jGraduate_cx" size="3" title="${i18next.t('config.jgraduate_enter_value_x')}"/>
<label>y:</label>
<input type="text" id="${id}_jGraduate_cy" size="3" title="${i18next.t('config.jgraduate_enter_value_y')}"/>
</div>
</div>
<div class="jGraduate_StopSection">
<label class="jGraduate_Form_Heading">${i18next.t('config.jgraduate_focal_point')}</label>
<div class="jGraduate_Form_Section">
<label>${i18next.t('config.jgraduate_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="${i18next.t('config.jgraduate_enter_focal_x')}"/>
<label>y:</label>
<input type="text" id="${id}_jGraduate_fy" size="3" title="${i18next.t('config.jgraduate_enter_focal_y')}"/>
</div>
</div>
</div>
<div class="jGraduate_StopSection jGraduate_SpreadMethod">
<label class="jGraduate_Form_Heading">${i18next.t('config.jgraduate_spread_method')}</label>
<div class="jGraduate_Form_Section">
<select class="jGraduate_spreadMethod" id="jGraduate_spreadMethod">
<option value=pad selected>${i18next.t('properties.jgraduate_pad')}</option>
<option value=reflect>${i18next.t('properties.jgraduate_reflect')}</option>
<option value=repeat>${i18next.t('properties.jgraduate_repeat')}</option>
</select>
</div>
</div>
<div class="jGraduate_Form">
<div class="jGraduate_Slider jGraduate_RadiusField jGraduate_rg_field">
<label class="prelabel">${i18next.t('config.jgraduate_radius')}</label>
<div id="${id}_jGraduate_Radius" class="jGraduate_SliderBar jGraduate_Radius" title="${i18next.t('config.jgraduate_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">${i18next.t('config.jgraduate_ellip')}</label>
<div id="${id}_jGraduate_Ellip" class="jGraduate_SliderBar jGraduate_Ellip" title="${i18next.t('config.jgraduate_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">${i18next.t('config.jgraduate_angle')}</label>
<div id="${id}_jGraduate_Angle" class="jGraduate_SliderBar jGraduate_Angle" title="${i18next.t('config.jgraduate_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"/>${i18next.t('config.jgraduate_deg')}</label>
</div>
<div class="jGraduate_Slider jGraduate_OpacField">
<label class="prelabel">${i18next.t('config.jgraduate_opac')}</label>
<div id="${id}_jGraduate_Opac" class="jGraduate_SliderBar jGraduate_Opac" title="${i18next.t('config.jgraduate_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="${i18next.t('common.ok')}"/>
<input type="button" id="${id}_jGraduate_Cancel" class="jGraduate_Cancel" value="${i18next.t('common.cancel')}"/>
</div>`
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
const MARGINX = 0
const MARGINY = 0
// STOP_RADIUS = 15 / 2,
const SIZEX = MAX - 2 * MARGINX
const 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)
const y1 = Number.parseFloat(grad.getAttribute('y1') || 0.0)
const x2 = Number.parseFloat(grad.getAttribute('x2') || 1.0)
const y2 = Number.parseFloat(grad.getAttribute('y2') || 0.0)
const cx = Number.parseFloat(grad.getAttribute('cx') || 0.5)
const cy = Number.parseFloat(grad.getAttribute('cy') || 0.5)
const fx = Number.parseFloat(grad.getAttribute('fx') || cx)
const 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 [, 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) {
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'
},
i18next
)
})
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; let curStop; let 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; let scaleY = 1; let 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')
} else {
const x = -cX * (scaleX - 1)
const y = -cY * (scaleY - 1)
curGradient.setAttribute(
'gradientTransform',
rot + 'translate(' + x + ',' + y + ') scale(' +
scaleX + ',' + scaleY + ')'
)
}
}
/**
* @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)
svgEditor.$click(stopMakerSVG, 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
svgEditor.$click($this.querySelector('#' + id + '_jGraduate_Ok'), function () {
$this.paint.type = curType
$this.paint[curType] = curGradient.cloneNode(true)
$this.paint.solidColor = null
okClicked()
})
svgEditor.$click($this.querySelector('#' + id + '_jGraduate_Cancel'), 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; let 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; let 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 [, [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 () {
$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() },
i18next
)
// 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) {
svgEditor.$click(tab, 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)
}