aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static/colorpicker.js
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-29 22:31:11 -0400
committerhistoria <[not public]>2026-06-29 22:31:11 -0400
commit069d3c1c81d71042706372df153254487a765dfc (patch)
treed83a4a3954d2b8304f49d83e365f4c2b075b91eb /internal/admin/static/colorpicker.js
parent6c5271fb085b4571a10f106391974c249cd84317 (diff)
downloadthehouseoficarus-069d3c1c81d71042706372df153254487a765dfc.tar.gz
feat: wip web admin for mapping and crud
Diffstat (limited to 'internal/admin/static/colorpicker.js')
-rw-r--r--internal/admin/static/colorpicker.js197
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">&times;</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';
+ };
+})();