From 0468b3adf53df49fa39b5d2fb2f4992792269043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 30 Sep 2017 21:17:38 +0200 Subject: [PATCH] term_conf refactor, add color pickers --- .babelrc | 6 +- .eslintignore | 3 +- js/index.js | 2 + js/lib/color_utils.js | 121 ++++++++ js/lib/colortriangle.js | 604 ++++++++++++++++++++++++++++++++++++++++ js/term/themes.js | 13 +- js/term_conf.js | 105 +++++++ pages/cfg_term.php | 84 +----- 8 files changed, 846 insertions(+), 92 deletions(-) create mode 100644 js/lib/color_utils.js create mode 100644 js/lib/colortriangle.js create mode 100644 js/term_conf.js diff --git a/.babelrc b/.babelrc index d8d6229..e087903 100644 --- a/.babelrc +++ b/.babelrc @@ -6,13 +6,9 @@ "last 2 versions", "> 4%", "ie 11", - "safari 8", - "android 4.4" + "safari 8" ] } - }], - ["minify", { - "mergeVars": false }] ] } diff --git a/.eslintignore b/.eslintignore index 76fd6af..94e5049 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,8 @@ out/**/* # libraries -js/lib/* +js/lib/chibi.js +js/lib/polyfills.js # php generated file js/lang.js diff --git a/js/index.js b/js/index.js index eff520a..dfb22b0 100644 --- a/js/index.js +++ b/js/index.js @@ -14,3 +14,5 @@ window.$ = $ window.qs = qs window.themes = require('./term/themes') + +window.TermConf = require('./term_conf') diff --git a/js/lib/color_utils.js b/js/lib/color_utils.js new file mode 100644 index 0000000..74cff36 --- /dev/null +++ b/js/lib/color_utils.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010 Tim Baumann + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// NOTE: +// Extracted from ColorTriangle and +// Converted to ES6 by MightyPork (2017) + +/******************* + * Color conversion * + *******************/ + +const M = Math +const PI = M.PI + +exports.hue_to_rgb = function (v1, v2, h) { + if (h < 0) h += 1 + if (h > 1) h -= 1 + + if ((6 * h) < 1) return v1 + (v2 - v1) * 6 * h + if ((2 * h) < 1) return v2 + if ((3 * h) < 2) return v1 + (v2 - v1) * ((2 / 3) - h) * 6 + return v1 +} + +exports.hsl_to_rgb = function (h, s, l) { + h /= 2 * PI + let r, g, b + + if (s === 0) { + r = g = b = l + } else { + let var_1, var_2 + + if (l < 0.5) var_2 = l * (1 + s) + else var_2 = (l + s) - (s * l) + + var_1 = 2 * l - var_2 + + r = exports.hue_to_rgb(var_1, var_2, h + (1 / 3)) + g = exports.hue_to_rgb(var_1, var_2, h) + b = exports.hue_to_rgb(var_1, var_2, h - (1 / 3)) + } + return [r, g, b] +} + +exports.rgb_to_hsl = function (r, g, b) { + const min = M.min(r, g, b) + const max = M.max(r, g, b) + const d = max - min // delta + + let h, s, l + + l = (max + min) / 2 + + if (d === 0) { + // gray + h = s = 0 // HSL results from 0 to 1 + } else { + // chroma + if (l < 0.5) s = d / (max + min) + else s = d / (2 - max - min) + + const d_r = (((max - r) / 6) + (d / 2)) / d + const d_g = (((max - g) / 6) + (d / 2)) / d + const d_b = (((max - b) / 6) + (d / 2)) / d // deltas + + if (r === max) h = d_b - d_g + else if (g === max) h = (1 / 3) + d_r - d_b + else if (b === max) h = (2 / 3) + d_g - d_r + + if (h < 0) h += 1 + else if (h > 1) h -= 1 + } + h *= 2 * PI + return [h, s, l] +} + +exports.hex_to_rgb = function (hex) { + const groups = hex.match(/^#([A-Fa-f0-9]+)$/) + if (groups && groups[1].length % 3 === 0) { + hex = groups[1] + const bytes = hex.length / 3 + const max = Math.pow(16, bytes) - 1 + const r = parseInt(hex.slice(0 * bytes, 1 * bytes), 16) / max + const g = parseInt(hex.slice(1 * bytes, 2 * bytes), 16) / max + const b = parseInt(hex.slice(2 * bytes, 3 * bytes), 16) / max + return [r, g, b] + } + return [0, 0, 0] +} + +function pad (n) { + if (n.length === 1) n = '0' + n + return n +} + +exports.rgb_to_hex = function (r, g, b) { + r = Math.round(r * 255).toString(16) + g = Math.round(g * 255).toString(16) + b = Math.round(b * 255).toString(16) + return `#${pad(r)}${pad(g)}${pad(b)}` +} diff --git a/js/lib/colortriangle.js b/js/lib/colortriangle.js new file mode 100644 index 0000000..2c4e60d --- /dev/null +++ b/js/lib/colortriangle.js @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2010 Tim Baumann + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// NOTE: Converted to ES6 by MightyPork (2017) + +const { + rgb_to_hex, + hex_to_rgb, + hsl_to_rgb, + rgb_to_hsl +} = require('./color_utils') + +const win = window +const doc = document +const M = Math +const PI = M.PI + +function times (i, fn) { + for (let j = 0; j < i; j++) { + fn(j) + } +} + +function each (obj, fn) { + if (obj.length) { + times(obj.length, function (i) { + fn(obj[i], i) + }) + } else { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + fn(obj[key], key) + } + } + } +} + +function getOffsets (el) { + let left = 0 + let top = 0 + + while (el !== null) { + console.log(el) + console.log(el.offsetLeft, el.offsetTop) + left += el.offsetLeft + top += el.offsetTop + el = el.offsetParent + } + + return [left, top] +} + +module.exports = class ColorTriangle { + /**************** + * ColorTriangle * + ****************/ + + // Constructor function: + constructor (color, options) { + this.options = { + size: 150, + padding: 8, + triangleSize: 0.8, + wheelPointerColor1: '#444', + wheelPointerColor2: '#eee', + trianglePointerSize: 16, + // wheelPointerSize: 16, + trianglePointerColor1: '#eee', + trianglePointerColor2: '#444', + background: 'transparent' + } + + this._setOptions(options) + this._calculateProperties() + + this._createContainer() + this._createTriangle() + this._createWheel() + this._createWheelPointer() + this._createTrianglePointer() + this._attachEvents() + + color = color || '#f00' + if (typeof color == 'string') { + this.setHEX(color) + } + } + + _calculateProperties () { + let opts = this.options + + this.padding = opts.padding + this.innerSize = opts.size - opts.padding * 2 + this.triangleSize = opts.triangleSize * this.innerSize + this.wheelThickness = (this.innerSize - this.triangleSize) / 2 + this.wheelPointerSize = opts.wheelPointerSize || this.wheelThickness + + this.wheelRadius = (this.innerSize) / 2 + this.triangleRadius = (this.triangleSize) / 2 + this.triangleSideLength = M.sqrt(3) * this.triangleRadius + } + + _calculatePositions () { + const r = this.triangleRadius + const hue = this.hue + const third = (2 / 3) * PI + const s = this.saturation + const l = this.lightness + + // Colored point + const hx = this.hx = M.cos(hue) * r + const hy = this.hy = -M.sin(hue) * r + // Black point + const sx = this.sx = M.cos(hue - third) * r + const sy = this.sy = -M.sin(hue - third) * r + // White point + const vx = this.vx = M.cos(hue + third) * r + const vy = this.vy = -M.sin(hue + third) * r + // Current point + const mx = (sx + vx) / 2 + const my = (sy + vy) / 2 + const a = (1 - 2 * M.abs(l - 0.5)) * s + this.x = sx + (vx - sx) * l + (hx - mx) * a + this.y = sy + (vy - sy) * l + (hy - my) * a + } + + _createContainer () { + let c = this.container = doc.createElement('div') + c.className = 'color-triangle' + + c.style.display = 'block' + c.style.padding = `${this.padding}px` + c.style.position = 'relative' + c.style.boxShadow = '0 1px 10px black' + c.style.borderRadius = '5px' + c.style.width = c.style.height = `${this.innerSize + 2 * this.padding}px` + c.style.background = this.options.background + } + + _createWheel () { + let c = this.wheel = doc.createElement('canvas') + c.width = c.height = this.innerSize + c.style.position = 'absolute' + c.style.margin = c.style.padding = '0' + c.style.left = c.style.top = `${this.padding}px` + + this._drawWheel(c.getContext('2d')) + this.container.appendChild(c) + } + + _drawWheel (ctx) { + let s, i + + ctx.save() + ctx.translate(this.wheelRadius, this.wheelRadius) + s = this.wheelRadius - this.triangleRadius + // Draw a circle for every color + for (i = 0; i < 360; i++) { + ctx.rotate(PI / -180) // rotate one degree + ctx.beginPath() + ctx.fillStyle = 'hsl(' + i + ', 100%, 50%)' + ctx.arc(this.wheelRadius - (s / 2), 0, s / 2, 0, PI * 2, true) + ctx.fill() + } + ctx.restore() + } + + _createTriangle () { + let c = this.triangle = doc.createElement('canvas') + + c.width = c.height = this.innerSize + c.style.position = 'absolute' + c.style.margin = c.style.padding = '0' + c.style.left = c.style.top = this.padding + 'px' + + this.triangleCtx = c.getContext('2d') + + this.container.appendChild(c) + } + + _drawTriangle () { + const hx = this.hx + const hy = this.hy + const sx = this.sx + const sy = this.sy + const vx = this.vx + const vy = this.vy + const size = this.innerSize + + let ctx = this.triangleCtx + + // clear + ctx.clearRect(0, 0, size, size) + + ctx.save() + ctx.translate(this.wheelRadius, this.wheelRadius) + + // make a triangle + ctx.beginPath() + ctx.moveTo(hx, hy) + ctx.lineTo(sx, sy) + ctx.lineTo(vx, vy) + ctx.closePath() + ctx.clip() + + ctx.fillStyle = '#000' + ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) + // => black triangle + + // create gradient from hsl(hue, 1, 1) to transparent + let grad0 = ctx.createLinearGradient(hx, hy, (sx + vx) / 2, (sy + vy) / 2) + const hsla = 'hsla(' + M.round(this.hue * (180 / PI)) + ', 100%, 50%, ' + grad0.addColorStop(0, hsla + '1)') + grad0.addColorStop(1, hsla + '0)') + ctx.fillStyle = grad0 + ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) + // => gradient: one side of the triangle is black, the opponent angle is $color + + // create color gradient from white to transparent + let grad1 = ctx.createLinearGradient(vx, vy, (hx + sx) / 2, (hy + sy) / 2) + grad1.addColorStop(0, '#fff') + grad1.addColorStop(1, 'rgba(255, 255, 255, 0)') + ctx.globalCompositeOperation = 'lighter' + ctx.fillStyle = grad1 + ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) + // => white angle + + ctx.restore() + } + + // The two pointers + _createWheelPointer () { + let c = this.wheelPointer = doc.createElement('canvas') + const size = this.wheelPointerSize + c.width = c.height = size + c.style.position = 'absolute' + c.style.margin = c.style.padding = '0' + this._drawPointer(c.getContext('2d'), size / 2, this.options.wheelPointerColor1, this.options.wheelPointerColor2) + this.container.appendChild(c) + } + + _moveWheelPointer () { + const r = this.wheelPointerSize / 2 + const s = this.wheelPointer.style + s.top = this.padding + this.wheelRadius - M.sin(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' + s.left = this.padding + this.wheelRadius + M.cos(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' + } + + _createTrianglePointer () { // create pointer in the triangle + let c = this.trianglePointer = doc.createElement('canvas') + const size = this.options.trianglePointerSize + + c.width = c.height = size + c.style.position = 'absolute' + c.style.margin = c.style.padding = '0' + this._drawPointer(c.getContext('2d'), size / 2, this.options.trianglePointerColor1, this.options.trianglePointerColor2) + this.container.appendChild(c) + } + + _moveTrianglePointer (x, y) { + const s = this.trianglePointer.style + const r = this.options.trianglePointerSize / 2 + s.top = (this.y + this.wheelRadius + this.padding - r) + 'px' + s.left = (this.x + this.wheelRadius + this.padding - r) + 'px' + } + + _drawPointer (ctx, r, color1, color2) { + ctx.fillStyle = color2 + ctx.beginPath() + ctx.arc(r, r, r, 0, PI * 2, true) + ctx.fill() // => black circle + ctx.fillStyle = color1 + ctx.beginPath() + ctx.arc(r, r, r - 2, 0, PI * 2, true) + ctx.fill() // => white circle with 1px black border + ctx.fillStyle = color2 + ctx.beginPath() + ctx.arc(r, r, r / 4 + 2, 0, PI * 2, true) + ctx.fill() // => black circle with big white border and a small black border + ctx.globalCompositeOperation = 'destination-out' + ctx.beginPath() + ctx.arc(r, r, r / 4, 0, PI * 2, true) + ctx.fill() // => transparent center + } + + // The Element and the DOM + inject (parent) { + parent.appendChild(this.container) + + // calculate canvas position on page + const offsets = getOffsets(this.triangle) + this.offset = { + x: offsets[0], + y: offsets[1] + } + } + + dispose () { + let parent = this.container.parentNode + if (parent) { + parent.removeChild(this.container) + } + } + + getElement () { + return this.container + } + + // Color accessors + getCSS () { + const h = Math.round(this.hue * (180 / PI)) + const s = Math.round(this.saturation * 100) + const l = Math.round(this.lightness * 100) + + return `hsl(${h}, ${s}%, ${l}%)` + } + + getHEX () { + return rgb_to_hex(...this.getRGB()) + } + + setHEX (hex) { + this.setRGB(...hex_to_rgb(hex)) + } + + getRGB () { + return hsl_to_rgb(...this.getHSL()) + } + + setRGB (r, g, b) { + this.setHSL(...rgb_to_hsl(r, g, b)) + } + + getHSL () { + return [this.hue, this.saturation, this.lightness] + } + + setHSL (h, s, l) { + this.hue = h + this.saturation = s + this.lightness = l + + this._initColor() + } + + _initColor () { + this._calculatePositions() + this._moveWheelPointer() + this._drawTriangle() + this._moveTrianglePointer() + } + + // Mouse event handling + _attachEvents () { + this.down = null + + let mousedown = (evt) => { + evt.stopPropagation() + evt.preventDefault() + + doc.body.addEventListener('mousemove', mousemove, false) + doc.body.addEventListener('mouseup', mouseup, false) + this._map(evt.pageX, evt.pageY) + } + + let mousemove = (evt) => { + this._move(evt.pageX, evt.pageY) + } + + let mouseup = (evt) => { + if (this.down) { + this.down = null + this._fireEvent('dragend') + } + doc.body.removeEventListener('mousemove', mousemove, false) + doc.body.removeEventListener('mouseup', mouseup, false) + } + + this.container.addEventListener('mousedown', mousedown, false) + this.container.addEventListener('mousemove', mousemove, false) + } + + _map (x, y) { + const ox = x + const oy = y + + x -= this.offset.x + this.wheelRadius + y -= this.offset.y + this.wheelRadius + + const r = M.sqrt(x * x + y * y) // Pythagoras + if (r > this.triangleRadius && r < this.wheelRadius) { + // Wheel + this.down = 'wheel' + this._fireEvent('dragstart') + this._move(ox, oy) + } else if (r < this.triangleRadius) { + // Inner circle + this.down = 'triangle' + this._fireEvent('dragstart') + this._move(ox, oy) + } + } + + _move (x, y) { + if (!this.down) { + return + } + + x -= this.offset.x + this.wheelRadius + y -= this.offset.y + this.wheelRadius + + let rad = M.atan2(-y, x) + if (rad < 0) { + rad += 2 * PI + } + + if (this.down === 'wheel') { + this.hue = rad + this._initColor() + this._fireEvent('drag') + } else if (this.down === 'triangle') { + // get radius and max radius + let rad0 = (rad + 2 * PI - this.hue) % (2 * PI) + let rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) + let a = 0.5 * this.triangleRadius + let b = M.tan(rad1) * a + let r = M.sqrt(x * x + y * y) // Pythagoras + let maxR = M.sqrt(a * a + b * b) // Pythagoras + + if (r > maxR) { + const dx = M.tan(rad1) * r + let rad2 = M.atan(dx / maxR) + if (rad2 > PI / 3) { + rad2 = PI / 3 + } else if (rad2 < -PI / 3) { + rad2 = -PI / 3 + } + rad += rad2 - rad1 + + rad0 = (rad + 2 * PI - this.hue) % (2 * PI) + rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) + b = M.tan(rad1) * a + r = maxR = M.sqrt(a * a + b * b) // Pythagoras + } + + x = M.round(M.cos(rad) * r) + y = M.round(-M.sin(rad) * r) + + const l = this.lightness = ((M.sin(rad0) * r) / this.triangleSideLength) + 0.5 + + const widthShare = 1 - (M.abs(l - 0.5) * 2) + let s = this.saturation = (((M.cos(rad0) * r) + (this.triangleRadius / 2)) / (1.5 * this.triangleRadius)) / widthShare + s = M.max(0, s) // cannot be lower than 0 + s = M.min(1, s) // cannot be greater than 1 + + this.lightness = l + this.saturation = s + + this.x = x + this.y = y + this._moveTrianglePointer() + + this._fireEvent('drag') + } + } + + /*************** + * Init helpers * + ***************/ + + static initInput (input, options) { + options = options || {} + + let ct + let openColorTriangle = function () { + let hex = input.value + if (options.parseColor) hex = options.parseColor(hex) + if (!ct) { + options.size = options.size || input.offsetWidth + options.background = win.getComputedStyle(input, null).backgroundColor + options.margin = options.margin || 10 + options.event = options.event || 'dragend' + + ct = new ColorTriangle(hex, options) + ct.addEventListener(options.event, () => { + const hex = ct.getHEX() + input.value = options.uppercase ? hex.toUpperCase() : hex + fireChangeEvent() + }) + } else { + ct.setHEX(hex) + } + + let top = input.offsetTop + if (win.innerHeight - input.getBoundingClientRect().top > input.offsetHeight + options.margin + options.size) { + top += input.offsetHeight + options.margin // below + } else { + top -= options.margin + options.size // above + } + + let el = ct.getElement() + el.style.position = 'absolute' + el.style.left = input.offsetLeft + 'px' + el.style.top = top + 'px' + el.style.zIndex = '1338' // above everything + + ct.inject(input.parentNode) + } + + let closeColorTriangle = () => { + if (ct) { + ct.dispose() + } + } + + let fireChangeEvent = () => { + let evt = doc.createEvent('HTMLEvents') + evt.initEvent('input', true, false) // bubbles = true, cancable = false + input.dispatchEvent(evt) // fire event + } + + input.addEventListener('focus', openColorTriangle, false) + input.addEventListener('blur', closeColorTriangle, false) + input.addEventListener('keyup', () => { + const val = input.value + if (val.match(/^#((?:[0-9A-Fa-f]{3})|(?:[0-9A-Fa-f]{6}))$/)) { + openColorTriangle() + fireChangeEvent() + } else { + closeColorTriangle() + } + }, false) + } + + /******************* + * Helper functions * + *******************/ + + _setOptions (opts) { + opts = opts || {} + let dflt = this.options + let options = this.options = {} + + each(dflt, function (val, key) { + options[key] = (opts.hasOwnProperty(key)) + ? opts[key] + : val + }) + } + + addEventListener (type, fn) { + if (!this.events) { + this.events = {} + } + if (!this.events[type]) { + this.events[type] = [] + } + this.events[type].push(fn) + } + + removeEventListener (type, fn) { + if (this.events) { + let fns = this.events[type] + const l = fns.length + for (let i = 0; i < l; i += 1) { + if (fns[i] === fn) { + fns.splice(i, 1) + break + } + } + } + } + + _fireEvent (type) { + if (this.events) { + let evts = this.events[type] + if (evts) { + const args = Array.prototype.slice.call(arguments, 1) + each(evts, function (evt) { + evt(...args) + }) + } + } + } +} diff --git a/js/term/themes.js b/js/term/themes.js index 699c017..aa93bc1 100644 --- a/js/term/themes.js +++ b/js/term/themes.js @@ -1,4 +1,5 @@ const $ = require('../lib/chibi') +const { rgb_to_hex } = require('../lib/color_utils') const themes = exports.themes = [ [ // 0 - Tango - terminator @@ -73,7 +74,7 @@ exports.buildColorTable = function () { // 256color lookup table // should not be used to look up 0-15 (will return transparent) - colorTable256 = new Array(16).fill('rgba(0, 0, 0, 0)') + colorTable256 = new Array(16).fill('#000000') // fill color table // colors 16-231 are a 6x6x6 color cube @@ -83,14 +84,14 @@ exports.buildColorTable = function () { let redValue = red * 40 + (red ? 55 : 0) let greenValue = green * 40 + (green ? 55 : 0) let blueValue = blue * 40 + (blue ? 55 : 0) - colorTable256.push(`rgb(${redValue}, ${greenValue}, ${blueValue})`) + colorTable256.push(rgb_to_hex(redValue, greenValue, blueValue)) } } } // colors 232-255 are a grayscale ramp, sans black and white for (let gray = 0; gray < 24; gray++) { let value = gray * 10 + 8 - colorTable256.push(`rgb(${value}, ${value}, ${value})`) + colorTable256.push(rgb_to_hex(value, value, value)) } return colorTable256 @@ -102,17 +103,17 @@ exports.SELECTION_BG = '#b2d7fe' exports.themePreview = function (themeN) { $('[data-fg]').forEach((elem) => { let shade = elem.dataset.fg - if (/^\d+$/.test(shade)) shade = exports.toCss(shade, themeN) + if (/^\d+$/.test(shade)) shade = exports.toHex(shade, themeN) elem.style.color = shade }) $('[data-bg]').forEach((elem) => { let shade = elem.dataset.bg - if (/^\d+$/.test(shade)) shade = exports.toCss(shade, themeN) + if (/^\d+$/.test(shade)) shade = exports.toHex(shade, themeN) elem.style.backgroundColor = shade }) } -exports.toCss = function (shade, themeN) { +exports.toHex = function (shade, themeN) { if (/^\d+$/.test(shade)) { shade = +shade if (shade < 16) shade = themes[themeN][shade] diff --git a/js/term_conf.js b/js/term_conf.js new file mode 100644 index 0000000..87d9d70 --- /dev/null +++ b/js/term_conf.js @@ -0,0 +1,105 @@ +const ColorTriangle = require('./lib/colortriangle') +const $ = require('./lib/chibi') +const themes = require('./term/themes') +const { qs } = require('./utils') + +function selectedTheme () { + return +$('#theme').val() +} + +exports.init = function () { + $('#theme').on('change', showColor) + + $('#default_fg').on('input', showColor) + $('#default_bg').on('input', showColor) + + let opts = { + padding: 10, + event: 'drag', + uppercase: true, + trianglePointerSize: 20, + // wheelPointerSize: 12, + size: 200, + parseColor: (color) => { + return themes.toHex(color, selectedTheme()) + } + } + + ColorTriangle.initInput(qs('#default_fg'), opts) + ColorTriangle.initInput(qs('#default_bg'), opts) + + $('.colorprev.bg span').on('click', function () { + const bg = this.dataset.bg + if (typeof bg != 'undefined') $('#default_bg').val(bg) + showColor() + }) + + $('.colorprev.fg span').on('click', function () { + const fg = this.dataset.fg + if (typeof fg != 'undefined') $('#default_fg').val(fg) + showColor() + }) + + let $presets = $('#fgbg_presets') + for (let i = 0; i < themes.fgbgThemes.length; i++) { + const thm = themes.fgbgThemes[i] + const fg = thm[0] + const bg = thm[1] + const lbl = thm[2] + const tit = thm[3] + $presets.htmlAppend( + ' ' + lbl + ' ') + + if ((i + 1) % 5 === 0) $presets.htmlAppend('
') + } + + $('.preset').on('click', function () { + $('#default_fg').val(this.dataset.xfg) + $('#default_bg').val(this.dataset.xbg) + showColor() + }) + + showColor() +} + +function showColor () { + let ex = qs('.color-example') + let fg = $('#default_fg').val() + let bg = $('#default_bg').val() + + if (/^\d+$/.test(fg)) { + fg = +fg + } else if (!/^#[\da-f]{6}$/i.test(fg)) { + fg = 'black' + } + + if (/^\d+$/.test(bg)) { + bg = +bg + } else if (!/^#[\da-f]{6}$/i.test(bg)) { + bg = 'black' + } + + const themeN = selectedTheme() + ex.dataset.fg = fg + ex.dataset.bg = bg + + themes.themePreview(themeN) + + $('.colorprev.fg span').css('background', themes.toHex(bg, themeN)) +} + +exports.nextTheme = () => { + let sel = qs('#theme') + let i = sel.selectedIndex + sel.options[++i % sel.options.length].selected = true + showColor() +} + +exports.prevTheme = () => { + let sel = qs('#theme') + let i = sel.selectedIndex + sel.options[(sel.options.length + (--i)) % sel.options.length].selected = true + showColor() +} diff --git a/pages/cfg_term.php b/pages/cfg_term.php index 51fe377..9db48f2 100644 --- a/pages/cfg_term.php +++ b/pages/cfg_term.php @@ -26,8 +26,8 @@ - - + +
@@ -300,30 +300,6 @@