diff options
Diffstat (limited to 'internal/admin/static/colorpicker.js')
| -rw-r--r-- | internal/admin/static/colorpicker.js | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/internal/admin/static/colorpicker.js b/internal/admin/static/colorpicker.js new file mode 100644 index 0000000..9431e83 --- /dev/null +++ b/internal/admin/static/colorpicker.js @@ -0,0 +1,197 @@ +window.xtermToRGB = function(idx) { + idx = parseInt(idx); + if (idx < 0 || idx > 255) return [0,0,0]; + if (idx < 16) { + var a = [[0,0,0],[170,0,0],[0,170,0],[170,170,0],[0,0,170],[170,0,170],[0,170,170],[170,170,170], + [85,85,85],[255,85,85],[85,255,85],[255,255,85],[85,85,255],[255,85,255],[85,255,255],[255,255,255]]; + return a[idx]; + } + if (idx < 232) { + var n = idx - 16; + var cube = [0,95,135,175,215,255]; + return [cube[Math.floor(n/36)], cube[Math.floor((n%36)/6)], cube[n%6]]; + } + var g = 8 + (idx-232)*10; + return [g,g,g]; +}; + +window.xtermIdxToHex = function(idx) { + idx = parseInt(idx) || 0; + return idx.toString(16).toUpperCase().padStart(2,'0'); +}; + +window.hexToXtermIdx = function(hex) { + return parseInt(hex, 16); +}; + +window.xtermToCss = function(idx) { + var rgb = xtermToRGB(idx); + return '#' + ((rgb[0]<<16)|(rgb[1]<<8)|rgb[2]).toString(16).padStart(6,'0').toUpperCase(); +}; + +window.resolveColor = function(v) { + if (!v) return '#3a3a6a'; + v = v.toUpperCase().replace(/[^0-9A-F]/g,''); + if (v.length === 2) return xtermToCss(parseInt(v,16)); + return '#' + v.substring(0,6).padEnd(6,'0'); +}; + +window.hexLuminance = function(hex) { + hex = hex.replace('#','').toUpperCase(); + var r = parseInt(hex.substring(0,2),16)/255; + var g = parseInt(hex.substring(2,4),16)/255; + var b = parseInt(hex.substring(4,6),16)/255; + return 0.299*r + 0.587*g + 0.114*b; +}; + +window.cssToXtermIdx = function(css) { + css = css.replace('#','').toUpperCase(); + if (css.length < 6) return 0; + var tr = parseInt(css.substring(0,2),16); + var tg = parseInt(css.substring(2,4),16); + var tb = parseInt(css.substring(4,6),16); + var best = 0, bestDist = Infinity; + for (var i = 0; i < 256; i++) { + var srgb = xtermToRGB(i); + var dr = tr-srgb[0], dg = tg-srgb[1], db = tb-srgb[2]; + var d = dr*dr + dg*dg + db*db; + if (d < bestDist) { bestDist = d; best = i; } + } + return best; +}; + +(function() { + var pickerEl = null, boundInput = null, boundSwatch = null; + + function ensurePicker() { + if (pickerEl) return; + pickerEl = document.createElement('div'); + pickerEl.id = '__cp_popover'; + pickerEl.style.cssText = 'display:none;position:fixed;z-index:200;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;box-shadow:0 4px 20px rgba(0,0,0,.5);min-width:280px'; + pickerEl.innerHTML = buildPickerHTML(); + document.body.appendChild(pickerEl); + + pickerEl.addEventListener('click', function(e) { + var sw = e.target.closest('.cp-swatch'); + if (sw) { + var idx = parseInt(sw.getAttribute('data-idx')); + var hex = xtermIdxToHex(idx); + if (boundInput) boundInput.value = hex; + if (boundSwatch) boundSwatch.style.background = xtermToCss(idx); + if (boundInput) boundInput.dispatchEvent(new Event('input', {bubbles:true})); + hidePicker(); + } + }); + } + + function buildPickerHTML() { + var h = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">'; + h += '<span id="__cp_preview" class="color-swatch" style="width:24px;height:24px;flex-shrink:0"></span>'; + h += '<input id="__cp_hex" style="flex:1;padding:3px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace;text-transform:uppercase" maxlength="2" placeholder="00">'; + h += '<button id="__cp_close" style="background:none;border:none;color:#aaa;cursor:pointer;font-size:14px;padding:0 4px">×</button>'; + h += '</div>'; + h += '<div class="cp-grid">'; + for (var i = 0; i < 256; i++) { + var css = xtermToCss(i); + h += '<div class="cp-swatch" data-idx="'+i+'" style="background:'+css+'" title="'+xtermIdxToHex(i)+' ('+i+')"></div>'; + } + h += '</div>'; + return h; + } + + window.showColorPicker = function(inputEl, swatchEl) { + ensurePicker(); + boundInput = inputEl; + boundSwatch = swatchEl; + + var raw = (inputEl.value || '').toUpperCase().replace(/[^0-9A-F]/g,''); + var idx; + if (raw.length === 2) { + idx = parseInt(raw, 16); + } else if (raw.length >= 6) { + idx = cssToXtermIdx(raw); + } else { + idx = parseInt(raw, 16) || 0; + } + var hex = xtermIdxToHex(idx); + inputEl.value = hex; + if (swatchEl) swatchEl.style.background = xtermToCss(idx); + + $('#__cp_hex').value = hex; + $('#__cp_preview').style.background = xtermToCss(idx); + updatePickerSelection(idx); + + var rect = inputEl.getBoundingClientRect(); + pickerEl.style.display = 'block'; + var pw = pickerEl.offsetWidth; + var left = rect.left; + if (left + pw > window.innerWidth - 10) left = Math.max(10, window.innerWidth - pw - 10); + pickerEl.style.left = left + 'px'; + pickerEl.style.top = (rect.bottom + 4) + 'px'; + + $('#__cp_hex').oninput = function() { + var v = this.value.toUpperCase().replace(/[^0-9A-F]/g,'').substring(0,2); + this.value = v; + if (v.length === 2) { + var i = parseInt(v, 16); + inputEl.value = v; + if (swatchEl) swatchEl.style.background = xtermToCss(i); + updatePickerSelection(i); + } + }; + + $('#__cp_close').onclick = hidePicker; + }; + + function updatePickerSelection(idx) { + var swatches = document.querySelectorAll('.cp-swatch'); + for (var i = 0; i < swatches.length; i++) swatches[i].classList.remove('selected'); + var s = document.querySelector('.cp-swatch[data-idx="'+idx+'"]'); + if (s) s.classList.add('selected'); + } + + window.hideColorPicker = hidePicker; + function hidePicker() { + if (pickerEl) pickerEl.style.display = 'none'; + boundInput = null; boundSwatch = null; + } + + document.addEventListener('click', function(e) { + if (!pickerEl || pickerEl.style.display === 'none') return; + if (!pickerEl.contains(e.target) && e.target !== boundInput && e.target !== (boundSwatch || {}).parentElement) { + hidePicker(); + } + }); + + window.bindColorField = function(fieldEl) { + var input = fieldEl.querySelector('input[type=text]') || fieldEl.querySelector('input:not([type])'); + var swatch = fieldEl.querySelector('.color-swatch'); + if (!input) return; + + fieldEl.addEventListener('click', function(e) { + if (e.target === input || e.target === swatch) { + showColorPicker(input, swatch); + } + }); + + var raw = (input.value || '').toUpperCase().replace(/[^0-9A-F]/g,''); + if (raw.length === 2) { + if (swatch) swatch.style.background = xtermToCss(parseInt(raw, 16)); + } else if (raw.length >= 6) { + var idx = cssToXtermIdx(raw); + input.value = xtermIdxToHex(idx); + if (swatch) swatch.style.background = xtermToCss(idx); + } + + input.addEventListener('input', function() { + var v = this.value.toUpperCase().replace(/[^0-9A-F]/g,'').substring(0,2); + this.value = v; + if (v.length === 2 && swatch) { + swatch.style.background = xtermToCss(parseInt(v, 16)); + } + }); + input.setAttribute('maxlength', '2'); + input.setAttribute('placeholder', '00'); + input.style.textTransform = 'uppercase'; + }; +})(); |
