mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-08 02:52:49 +00:00
204 lines
4.9 KiB
TypeScript
204 lines
4.9 KiB
TypeScript
import type { Action } from "svelte/action";
|
|
import { changes, ChangeType, settings } from "$lib/undo-redo";
|
|
import { activeProfile } from "./serial/connection";
|
|
import { combineLatest, map } from "rxjs";
|
|
import { fromReadable } from "./util/from-readable";
|
|
import { get } from "svelte/store";
|
|
|
|
/**
|
|
* https://gist.github.com/mjackson/5311256
|
|
*/
|
|
function rgbToHsv(r: number, g: number, b: number): [number, number, number] {
|
|
r /= 255;
|
|
g /= 255;
|
|
b /= 255;
|
|
|
|
const max = Math.max(r, g, b);
|
|
const min = Math.min(r, g, b);
|
|
let h = 0;
|
|
const v = max;
|
|
|
|
const d = max - min;
|
|
const s = max == 0 ? 0 : d / max;
|
|
|
|
if (max == min) {
|
|
h = 0; // achromatic
|
|
} 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 [Math.floor(h * 0xffff), Math.floor(s * 0xff), Math.floor(v * 0xff)];
|
|
}
|
|
|
|
/**
|
|
* https://gist.github.com/mjackson/5311256
|
|
*/
|
|
function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
|
|
h /= 0xffff;
|
|
s /= 0xff;
|
|
v /= 0xff;
|
|
|
|
let r = 0;
|
|
let g = 0;
|
|
let b = 0;
|
|
|
|
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 [Math.floor(r * 0xff), Math.floor(g * 0xff), Math.floor(b * 0xff)];
|
|
}
|
|
|
|
export const setting: Action<
|
|
HTMLInputElement | HTMLSelectElement,
|
|
{ id: number; inverse?: number; scale?: number }
|
|
> = function (
|
|
node: HTMLInputElement | HTMLSelectElement,
|
|
{ id, inverse, scale },
|
|
) {
|
|
node.setAttribute("disabled", "");
|
|
const type = node.getAttribute("type") as
|
|
| "number"
|
|
| "checkbox"
|
|
| "range"
|
|
| "color";
|
|
const isColor = type === "color";
|
|
const isNumeric =
|
|
type === "number" || type === "range" || node instanceof HTMLSelectElement;
|
|
const min = node.hasAttribute("min")
|
|
? Number(node.getAttribute("min"))
|
|
: undefined;
|
|
const max = node.hasAttribute("max")
|
|
? Number(node.getAttribute("max"))
|
|
: undefined;
|
|
|
|
const subscription = combineLatest([
|
|
fromReadable(settings),
|
|
fromReadable(activeProfile),
|
|
])
|
|
.pipe(map(([settings, profile]) => settings[profile]!))
|
|
.subscribe(async (settings) => {
|
|
if (id in settings) {
|
|
const { value, isApplied } = settings[id]!;
|
|
if (isNumeric) {
|
|
node.value = (
|
|
inverse !== undefined
|
|
? inverse / value
|
|
: scale !== undefined
|
|
? scale * value
|
|
: value
|
|
).toString();
|
|
} else if (isColor) {
|
|
const rgb = hsvToRgb(
|
|
settings[id]!.value,
|
|
settings[id + 1]!.value,
|
|
settings[id + 2]!.value,
|
|
);
|
|
node.value = `#${rgb.map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
} else {
|
|
node.checked = value !== 0;
|
|
}
|
|
if (isApplied) {
|
|
node.classList.remove("pending-changes");
|
|
} else {
|
|
node.classList.add("pending-changes");
|
|
}
|
|
node.removeAttribute("disabled");
|
|
} else {
|
|
node.setAttribute("disabled", "");
|
|
}
|
|
});
|
|
|
|
async function listener() {
|
|
let value: number;
|
|
if (isNumeric) {
|
|
value = Number(node.value);
|
|
if (Number.isNaN(value)) return;
|
|
if (min !== undefined) value = Math.max(min, value);
|
|
if (max !== undefined) value = Math.min(max, value);
|
|
value = Math.floor(
|
|
inverse !== undefined
|
|
? inverse / value
|
|
: scale !== undefined
|
|
? value / scale
|
|
: value,
|
|
);
|
|
} else if (isColor) {
|
|
const r = parseInt(node.value.slice(1, 3), 16);
|
|
const g = parseInt(node.value.slice(3, 5), 16);
|
|
const b = parseInt(node.value.slice(5, 7), 16);
|
|
const hsv = rgbToHsv(r, g, b);
|
|
changes.update((changes) => {
|
|
changes.push(
|
|
hsv.map((value, i) => ({
|
|
type: ChangeType.Setting,
|
|
id: id + i,
|
|
setting: value,
|
|
profile: get(activeProfile),
|
|
})),
|
|
);
|
|
return changes;
|
|
});
|
|
return;
|
|
} else {
|
|
value = node.checked ? 1 : 0;
|
|
}
|
|
|
|
changes.update((changes) => {
|
|
changes.push([
|
|
{
|
|
type: ChangeType.Setting,
|
|
id: id,
|
|
setting: value,
|
|
profile: get(activeProfile),
|
|
},
|
|
]);
|
|
return changes;
|
|
});
|
|
}
|
|
|
|
node.addEventListener("change", listener);
|
|
|
|
return {
|
|
destroy() {
|
|
node.removeEventListener("change", listener);
|
|
subscription.unsubscribe();
|
|
},
|
|
};
|
|
};
|