AvocadoAmber/AvocadoEdition_Light/adm/js/amberstone.cp.js

633 lines
18 KiB
JavaScript

class ColorPicker {
constructor(wrapper) {
this.pointerDrag = false;
this.hueDrag = false;
this.opacityDrag = false;
this.currentSaturation = 0;
this.currentValue = 0;
this.currentHue = 0;
this.currentAlpha = 1;
this.currentX;
this.currentY;
this.initialX;
this.initialY;
this.xOffset = 0;
this.yOffset = 0;
// this.element = wrapper;
this.wrapper = createElement("div", {
classList: ["amber-color-picker"]
});
this.preview = createElement("div", {
classList: ["neo-preview"]
});
this.input = createElement("input", {
});
this.dialog = createElement("div", {
classList: ["amber-palette"],
css: {
display: "none"
}
});
this.inner = createElement("div", {
classList: ["amber-inner"]
});
this.palette_area = createElement("div", {
classList: ["amber-palette-area"]
});
this.palette_pointer = createElement("div", {
});
this.hue_area = createElement("div", {
classList: ["amber-hue-area"]
});
this.hue_pointer = createElement("div", {
});
this.alpha_area = createElement("div", {
classList: ["amber-alpha-area"]
});
this.alpha_pointer = createElement("div", {
});
this.desc = createElement("div", {
innerHTML: "rgb(0,0,0), rgba(0,0,0,0) 등 css 에서 지원하는 색상 형식 입력 가능",
css: {
"color":"#A0A0A0",
"font-size":"9px",
"line-height":"16px",
"padding": "4px"
}
});
this.recentcolor = createElement("div", {
classList: ["amber-recent-color"]
});
this.palette_area.appendChild(this.palette_pointer);
this.hue_area.appendChild(this.hue_pointer);
this.alpha_area.appendChild(this.alpha_pointer);
this.inner.appendChild(this.palette_area);
this.inner.appendChild(this.hue_area);
this.inner.appendChild(this.alpha_area);
this.inner.appendChild(this.recentcolor);
this.dialog.appendChild(this.inner);
this.dialog.appendChild(this.desc);
this.wrapper.appendChild(this.input);
this.wrapper.appendChild(this.preview);
this.wrapper.appendChild(this.dialog);
for (let j = 0; j < wrapper.attributes.length; j++) {
const attr = wrapper.attributes[j];
if (attr.name == "class") continue;
this.input.setAttribute(attr.name, attr.value);
}
wrapper.parentNode.replaceChild(this.wrapper, wrapper);
this.input.addEventListener("change", (evt) => {
this.procEvent(evt, true);
});
this.input.addEventListener("keyup", (evt) => {
this.procEvent(evt);
});
this.input.addEventListener("focus", (evt) => {
this.openDialog();
});
this.palette_area.addEventListener("mousedown", (evt) => {
this.pointerDrag = true;
this.movePointer(evt);
});
this.hue_area.addEventListener("mousedown", (evt) => {
this.hueDrag = true;
this.moveHue(evt);
});
this.alpha_area.addEventListener("mousedown", (evt) => {
this.opacityDrag = true;
this.moveAlpha(evt);
});
window.addEventListener("mouseup", (evt) => {
this.pointerDrag = false;
this.hueDrag = false;
this.opacityDrag = false;
});
window.addEventListener("mousemove", (evt) => {
this.movePointer(evt);
this.moveHue(evt);
this.moveAlpha(evt);
});
this.init();
}
init() {
this.checkFocusLeave(() => {
this.dialog.style.setProperty('display', 'none');
this.addPaletteColor(this.input.value);
});
this.regex = /[ㅁㅠㅊㅇㄷㄹ]/g;
this.replacementMap = {
'ㅁ': 'a',
'ㅠ': 'b',
'ㅊ': 'c',
'ㅇ': 'd',
'ㄷ': 'e',
'ㄹ': 'f'
};
this.setColor(1);
this.getSavedPalette();
}
getSavedPalette() {
const d = localStorage.getItem('amber-palette');
if (d) {
try {
this.saved_palette = JSON.parse(d);
this.prepareSavedPalette();
} catch(ex) {
console.error(ex);
}
} else {
this.saved_palette = [];
localStorage.setItem('amber-palette', JSON.stringify([]));
}
}
addPaletteColor(color) {
const index = this.saved_palette.indexOf(color.toLowerCase());
if (index > -1) {
this.saved_palette.splice(index, 1);
}
this.saved_palette.unshift(color.toLowerCase());
if (this.saved_palette.length > 45) {
this.saved_palette.pop();
}
localStorage.setItem('amber-palette', JSON.stringify(this.saved_palette));
}
prepareSavedPalette() {
this.recentcolor.innerHTML = "";
for(const e of this.saved_palette) {
const col = createElement("div", {
css: {
"--background": e
},
attributes: {
title: e
}
});
col.addEventListener("click", (evt) => {
this.input.value = e;
this.setColor(1);
});
this.recentcolor.appendChild(col);
}
}
openDialog() {
this.dialog.style.setProperty('display', 'flex');
this.getSavedPalette();
}
movePointer(evt) {
if (this.pointerDrag) {
evt.preventDefault();
let rect = this.palette_area.getBoundingClientRect();
let cstyle = getComputedStyle(this.palette_area);
let newX = evt.clientX - rect.left;
let newY = evt.clientY - rect.top;
newX = Math.max(0, Math.min(parseInt(cstyle.width), newX));
newY = Math.max(0, Math.min(parseInt(cstyle.height), newY));
this.currentSaturation = newX / parseInt(cstyle.width);
this.currentValue = 1 - (newY / parseInt(cstyle.height));
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
this.setColor();
this.currentX = newX;
this.currentY = newY;
this.setTranslate(this.currentX, this.currentY, this.palette_pointer);
}
}
moveHue(evt) {
if (this.hueDrag) {
evt.preventDefault();
let rect = this.hue_area.getBoundingClientRect();
let cstyle = getComputedStyle(this.hue_area);
let newY = evt.clientY - rect.top;
const my = parseInt(cstyle.height);
newY = Math.max(0, Math.min(my, newY));
this.currentHue = (1 - (newY / my)) * 360;
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
this.setColor();
this.setTranslateY(newY, this.hue_pointer);
}
}
moveAlpha(evt) {
if (this.opacityDrag) {
evt.preventDefault();
let rect = this.alpha_area.getBoundingClientRect();
let cstyle = getComputedStyle(this.alpha_area);
let newY = evt.clientY - rect.top;
const my = parseInt(cstyle.height);
newY = Math.max(0, Math.min(my, newY));
this.currentAlpha = 1 - Math.min(1, Math.max(0, newY / my));
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
this.setColor();
this.setTranslateY(newY, this.alpha_pointer);
}
}
setTranslate(xPos, yPos, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}
setTranslateY(yPos, el) {
el.style.transform = `translateY(${yPos}px)`;
}
checkFocusLeave(callback) {
const self = this;
function isFocusInside(elem) {
return self.wrapper.contains(elem == undefined ? document.activeElement : elem);
}
let wasFocusInside = isFocusInside();
document.addEventListener('focusin', checkFocus);
document.addEventListener('mousedown', checkFocus);
document.addEventListener('touchstart', checkFocus);
function checkFocus(event) {
setTimeout(() => {
const isFocusInsideNow = isFocusInside(event.target);
if (wasFocusInside && !isFocusInsideNow) {
callback();
}
wasFocusInside = isFocusInsideNow;
}, 0);
}
return function cleanup() {
document.removeEventListener('focusin', checkFocus);
document.removeEventListener('mousedown', checkFocus);
document.removeEventListener('touchstart', checkFocus);
};
}
setColor(c) {
const val = this.procInput(this.input.value);
if (val) {
if (c) {
const tar = this.rgbToHsv(val.r, val.g, val.b);
this.currentHue = tar.h;
this.currentSaturation = tar.s;
this.currentValue = tar.v;
this.currentAlpha = val.a;
}
const t1 = this.hsvToRgba(this.currentHue, 0, 1, 1);
const t2 = this.hsvToRgba(this.currentHue, 1, 1, 1);
let cstyle = getComputedStyle(this.palette_area);
if (c) {
const hsv = this.rgbToHsv(val.r, val.g, val.b);
const mx = parseInt(cstyle.width);
const my = parseInt(cstyle.height);
const x = Math.min(mx, Math.max(0, (mx * hsv.s)));
const y = Math.min(my, Math.max(0, my - (my * hsv.v)));
this.setTranslate(x, y, this.palette_pointer);
this.setTranslateY(my - ((hsv.h / 360) * my), this.hue_pointer);
this.setTranslateY(my - (this.currentAlpha * my), this.alpha_pointer);
}
this.palette_area.style.background = `linear-gradient(to right, rgb(${t1.r},${t1.g},${t1.b}), rgb(${t2.r},${t2.g},${t2.b}))`;
this.preview.style.setProperty('--background', `rgba(${val.r},${val.g},${val.b},${val.a.toFixed(2)})`);
this.alpha_area.style.setProperty('--background', `linear-gradient(to bottom, rgba(${val.r},${val.g},${val.b},1), rgba(${val.r},${val.g},${val.b},0))`);
}
}
procEvent(evt, validate = false) {
const cursorPosition = this.input.selectionStart;
const val = this.procInput(this.input.value);
if (val) {
this.setColor(1);
}
if (validate) {
if (val) {
this.wrapper.style.setProperty('background', '#FFFFFF');
this.wrapper.style.setProperty('border-color', '#F2F2F0');
if (val.type == "hex") {
if (this.input.value[0] != '#') {
this.input.value = `#${this.input.value}`;
}
}
this.addPaletteColor(this.input.value);
} else {
this.wrapper.style.setProperty('background', '#FFDDDD');
this.wrapper.style.setProperty('border-color', '#EEA0A0');
}
}
}
procInput(input) {
input = input.replace(/\s/g, '');
// RGB and RGBA
let match = input.match(/^rgba?\((\d+),(\d+),(\d+)(?:,([\d.]+))?\)$/);
if (match) {
return {
r: parseInt(match[1]),
g: parseInt(match[2]),
b: parseInt(match[3]),
a: match[4] ? parseFloat(match[4]) : 1,
type: "rgb/rgba"
};
}
// HSV and HSVA
match = input.match(/^hsva?\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
if (match) {
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let v = parseInt(match[3]) / 100;
let a = match[4] ? parseFloat(match[4]) : 1;
return this.hsvToRgb(h, s, v, a);
}
// HSL and HSLA
match = input.match(/^hsla?\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
if (match) {
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
let a = match[4] ? parseFloat(match[4]) : 1;
return this.hslToRgb(h, s, l, a);
}
// HWB
match = input.match(/^hwb\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
if (match) {
let h = parseInt(match[1]) / 360;
let w = parseInt(match[2]) / 100;
let b = parseInt(match[3]) / 100;
let a = match[4] ? parseFloat(match[4]) : 1;
return this.hwbToRgb(h, w, b, a);
}
// LAB
match = input.match(/^lab\((\d+)%,([-\d.]+),([-\d.]+)(?:,([\d.]+))?\)$/);
if (match) {
let l = parseInt(match[1]);
let a = parseFloat(match[2]);
let b = parseFloat(match[3]);
let alpha = match[4] ? parseFloat(match[4]) : 1;
return this.labToRgb(l, a, b, alpha);
}
// LCH
match = input.match(/^lch\((\d+)%,([-\d.]+),([-\d.]+)(?:,([\d.]+))?\)$/);
if (match) {
let l = parseInt(match[1]);
let c = parseFloat(match[2]);
let h = parseFloat(match[3]);
let alpha = match[4] ? parseFloat(match[4]) : 1;
return this.lchToRgb(l, c, h, alpha);
}
// If no match found, return null
return this.procHexRgba(input);
}
procHexRgba(input) {
input = input.replace(/^#/, '');
if (input.length != 4 && input.length != 8 && input.length != 3 && input.length != 6) {
return null;
}
if (input.length == 3) {
let val = `${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`;
let rgb = this.hexToRgb(val);
return { r: rgb.r, g: rgb.g, b: rgb.b, a: 1, type: "hex" };
} else if (input.length == 4) {
let val = `${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`;
let rgb = this.hexToRgb(`${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`);
let alpha = parseInt(`${input[3]}${input[3]}`, 16);
return { r: rgb.r, g: rgb.g, b: rgb.b, a: alpha / 255, type: "hex" };
} else if (input.length == 6) {
let rgb = this.hexToRgb(input.substring(0, 6));
return { r: rgb.r, g: rgb.g, b: rgb.b, a: 1, type: "hex" };
} else if (input.length == 8) {
let rgb = this.hexToRgb(input.substring(0, 6));
let alpha = parseInt(input.substring(6, 8), 16);
return { r: rgb.r, g: rgb.g, b: rgb.b, a: alpha / 255, type: "hex" };
}
}
hsvToRgba(h, s, v, a = 1) {
h /= 360;
let r, g, b;
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
a: a,
type: "hsv/hsva"
};
}
hslToRgb(h, s, l, a = 1) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
a: a,
type: "hsl/hsla"
};
}
rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {h, s, l};
}
hwbToRgb(h, w, b, a = 1) {
// First convert to HSL
const l = (1 - w - b) / 2;
const s = l === 0 || l === 1 ? 0 : (1 - w / (1 - b)) / 2;
// Then convert HSL to RGB
return hslToRgb(h, s, l, a);
}
labToRgb(l, a, b, alpha = 1) {
// LAB to XYZ
let y = (l + 16) / 116;
let x = a / 500 + y;
let z = y - b / 200;
[x, y, z] = [x, y, z].map(value => {
if (Math.pow(value, 3) > 0.008856) {
return Math.pow(value, 3);
} else {
return (value - 16 / 116) / 7.787;
}
});
// XYZ to RGB
let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
let b1 = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
[r, g, b1] = [r, g, b1].map(value => {
if (value > 0.0031308) {
return 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
} else {
return 12.92 * value;
}
});
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b1 * 255),
a: alpha,
type: "lab/laba"
};
}
lchToRgb(l, c, h, alpha = 1) {
// Convert LCH to LAB
const a = c * Math.cos(h * Math.PI / 180);
const b = c * Math.sin(h * Math.PI / 180);
// Then convert LAB to RGB
return labToRgb(l, a, b, alpha);
}
rgbToHsv(r, g, b) {
r /= 255, g /= 255, b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, v = max;
const d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s, v: v };
}
rgbToHex(r, g, b) {
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
hexToRgb(hex) {
hex = hex.replace(/^#/, '');
let bigint = parseInt(hex, 16);
let r = (bigint >> 16) & 255;
let g = (bigint >> 8) & 255;
let b = bigint & 255;
return { r, g, b };
}
isValidColor(color) {
const s = new Option().style;
s.color = color;
return s.color !== '';
}
parseColor(color) {
const s = new Option().style;
s.color = color;
return {
r: parseInt(s.color.match(/\d+/g)[0]),
g: parseInt(s.color.match(/\d+/g)[1]),
b: parseInt(s.color.match(/\d+/g)[2]),
a: s.color.match(/\d+/g)[3] ? parseFloat(s.color.match(/\d+/g)[3]) : 1
};
}
}
document.addEventListener("DOMContentLoaded", (evt) => {
document.querySelectorAll('.neo_color').forEach((e) => {
new ColorPicker(e);
});
});