Files

95 lines
3.1 KiB
JavaScript

'use strict';
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com> */
'require baseclass';
/**
* Row-tint paint helpers for luci-app-fwlive.
* LuCI modules must return baseclass.extend(...) — plain objects fail Class.isSubclass.
*
* Modes (fwlive-row-tint localStorage / data-row-tint):
* classic — green/red (default)
* accessible — teal/orange (colorblind-safe)
* off — no row background tint
*/
var PAINT_DELTA_MIN = 8;
var CLASSIC_PASS_HEX = '#46a546';
var CLASSIC_DENY_HEX = '#ca3c3c';
var ACCESSIBLE_PASS_HEX = '#0d9488';
var ACCESSIBLE_DENY_HEX = '#c2410c';
/* Back-compat aliases — classic is the default palette. */
var PASS_HEX = CLASSIC_PASS_HEX;
var DENY_HEX = CLASSIC_DENY_HEX;
function normalizeRowTint(mode) {
if (mode === 'off' || mode === 'accessible' || mode === 'classic') return mode;
return 'classic';
}
function hexPairForMode(mode) {
if (normalizeRowTint(mode) === 'accessible')
return { pass: ACCESSIBLE_PASS_HEX, deny: ACCESSIBLE_DENY_HEX };
return { pass: CLASSIC_PASS_HEX, deny: CLASSIC_DENY_HEX };
}
function parseCssRgbChannels(value) {
if (!value) return null;
const s = String(value).trim().toLowerCase();
if (s === 'transparent' || s === 'rgba(0, 0, 0, 0)' || s === 'rgba(0,0,0,0)') return null;
const rgb = s.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
if (rgb) return [parseFloat(rgb[1]), parseFloat(rgb[2]), parseFloat(rgb[3])];
/* color-mix() often serializes as color(srgb r g b[/a]) with 0..1 channels. */
const modern = s.match(/color\(\s*srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)/i);
if (modern)
return [
parseFloat(modern[1]) * 255,
parseFloat(modern[2]) * 255,
parseFloat(modern[3]) * 255
];
return null;
}
function cssColorPaintDelta(a, b) {
const ca = parseCssRgbChannels(a);
const cb = parseCssRgbChannels(b);
/* Transparent vs opaque color is a real paint change (common off-state). */
if (!ca && !cb) return 0;
if (!ca && cb) return Math.abs(cb[0]) + Math.abs(cb[1]) + Math.abs(cb[2]);
if (ca && !cb) return Math.abs(ca[0]) + Math.abs(ca[1]) + Math.abs(ca[2]);
return Math.abs(ca[0] - cb[0]) + Math.abs(ca[1] - cb[1]) + Math.abs(ca[2] - cb[2]);
}
function tintShouldEngageFallback(opts) {
const o = opts || {};
const minDelta = typeof o.minDelta === 'number' ? o.minDelta : PAINT_DELTA_MIN;
/* Visible paint is the success criterion; token/CSS.supports are only used when
paint cannot be measured (no delta sample yet). */
if (typeof o.paintDelta === 'number') return o.paintDelta < minDelta;
if (o.tokenResolved === false) return true;
return false;
}
return baseclass.extend({
PAINT_DELTA_MIN: PAINT_DELTA_MIN,
PASS_HEX: PASS_HEX,
DENY_HEX: DENY_HEX,
CLASSIC_PASS_HEX: CLASSIC_PASS_HEX,
CLASSIC_DENY_HEX: CLASSIC_DENY_HEX,
ACCESSIBLE_PASS_HEX: ACCESSIBLE_PASS_HEX,
ACCESSIBLE_DENY_HEX: ACCESSIBLE_DENY_HEX,
normalizeRowTint: normalizeRowTint,
hexPairForMode: hexPairForMode,
parseCssRgbChannels: parseCssRgbChannels,
cssColorPaintDelta: cssColorPaintDelta,
tintShouldEngageFallback: tintShouldEngageFallback
});