add noctalia and fuzzel

This commit is contained in:
2026-05-23 21:20:58 +02:00
parent 364801f1a3
commit 9a3eceb3ab
599 changed files with 204318 additions and 0 deletions
@@ -0,0 +1,161 @@
// AdvancedMath.js - Lightweight math library for Noctalia Calculator
// Provides advanced mathematical functions beyond basic arithmetic
// Helper function to convert degrees to radians
function toRadians(degrees) {
return degrees * (Math.PI / 180);
}
// Helper function to convert radians to degrees
function toDegrees(radians) {
return radians * (180 / Math.PI);
}
// Constants
var constants = {
PI: Math.PI,
E: Math.E,
LN2: Math.LN2,
LN10: Math.LN10,
LOG2E: Math.LOG2E,
LOG10E: Math.LOG10E,
SQRT1_2: Math.SQRT1_2,
SQRT2: Math.SQRT2
};
// Safe evaluation function that handles advanced math
function evaluate(expression) {
try {
// Fixes decimal arithmetic
var cleanExpr = expression.replace(/\s+/g, '').toLowerCase();
// Allows numbers (including decimals), basic operators, and explicitly permitted math terms only
var safeRegex = /^(\d*\.?\d+|[+\-*/()^%,]|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|log|ln|exp|pow|sqrt|cbrt|abs|floor|ceil|round|trunc|min|max|random|pi|e|sind|cosd|tand)+$/;
if (!safeRegex.test(cleanExpr)) {
throw new Error("Invalid characters or unauthorized functions in expression");
}
// Replace mathematical constants (Original Structure)
var processed = cleanExpr
.replace(/\bpi\b/gi, Math.PI)
.replace(/\be\b/gi, Math.E);
// Replace function calls with Math object equivalents
processed = processed
// Trigonometric functions
.replace(/\bsin\s*\(/g, 'Math.sin(')
.replace(/\bcos\s*\(/g, 'Math.cos(')
.replace(/\btan\s*\(/g, 'Math.tan(')
.replace(/\basin\s*\(/g, 'Math.asin(')
.replace(/\bacos\s*\(/g, 'Math.acos(')
.replace(/\batan\s*\(/g, 'Math.atan(')
.replace(/\batan2\s*\(/g, 'Math.atan2(')
// Hyperbolic functions
.replace(/\bsinh\s*\(/g, 'Math.sinh(')
.replace(/\bcosh\s*\(/g, 'Math.cosh(')
.replace(/\btanh\s*\(/g, 'Math.tanh(')
.replace(/\basinh\s*\(/g, 'Math.asinh(')
.replace(/\bacosh\s*\(/g, 'Math.acosh(')
.replace(/\batanh\s*\(/g, 'Math.atanh(')
// Logarithmic and exponential functions
.replace(/\blog\s*\(/g, 'Math.log10(')
.replace(/\bln\s*\(/g, 'Math.log(')
.replace(/\bexp\s*\(/g, 'Math.exp(')
.replace(/\bpow\s*\(/g, 'Math.pow(')
// Root functions
.replace(/\bsqrt\s*\(/g, 'Math.sqrt(')
.replace(/\bcbrt\s*\(/g, 'Math.cbrt(')
// Rounding and absolute
.replace(/\babs\s*\(/g, 'Math.abs(')
.replace(/\bfloor\s*\(/g, 'Math.floor(')
.replace(/\bceil\s*\(/g, 'Math.ceil(')
.replace(/\bround\s*\(/g, 'Math.round(')
.replace(/\btrunc\s*\(/g, 'Math.trunc(')
// Min/Max
.replace(/\bmin\s*\(/g, 'Math.min(')
.replace(/\bmax\s*\(/g, 'Math.max(')
// Random
.replace(/\brandom\s*\(\s*\)/g, 'Math.random()');
// Handle degree versions of trig functions
processed = processed
.replace(/\bsind\s*\(/g, '(function(x) { return Math.sin(' + (Math.PI / 180) + ' * x); })(')
.replace(/\bcosd\s*\(/g, '(function(x) { return Math.cos(' + (Math.PI / 180) + ' * x); })(')
.replace(/\btand\s*\(/g, '(function(x) { return Math.tan(' + (Math.PI / 180) + ' * x); })(');
// Handle ^ for exponentiation: convert 2^3 to Math.pow(2,3)
processed = processed.replace(/([\d.]+|\))\^([\d.]+|\([^)]*\))/g, 'Math.pow($1,$2)');
// Replacing eval() with a scoped function constructor
// This is safe because the strict whitelist guarantees only math reaches this point
var result = new Function('return ' + processed)();
if (!isFinite(result) || isNaN(result)) {
throw new Error("Invalid result");
}
return result;
} catch (error) {
throw new Error("Evaluation failed: " + error.message);
}
}
// Format result for display
function formatResult(result) {
if (Number.isInteger(result)) {
return result.toString();
}
// Handle very large or very small numbers
if (Math.abs(result) >= 1e15 || (Math.abs(result) < 1e-6 && result !== 0)) {
return result.toExponential(6);
}
// Normal decimal formatting
return parseFloat(result.toFixed(10)).toString();
}
// Get list of available functions for help
function getAvailableFunctions() {
return [
// Basic arithmetic: +, -, *, /, %, ^, ()
// Trigonometric functions
"sin(x), cos(x), tan(x) - trigonometric functions (radians)",
"sind(x), cosd(x), tand(x) - trigonometric functions (degrees)",
"asin(x), acos(x), atan(x) - inverse trigonometric",
"atan2(y, x) - two-argument arctangent",
// Hyperbolic functions
"sinh(x), cosh(x), tanh(x) - hyperbolic functions",
"asinh(x), acosh(x), atanh(x) - inverse hyperbolic",
// Logarithmic and exponential
"log(x) - base 10 logarithm",
"ln(x) - natural logarithm",
"exp(x) - e^x",
"pow(x, y) - x^y",
// Root functions
"sqrt(x) - square root",
"cbrt(x) - cube root",
// Rounding and absolute
"abs(x) - absolute value",
"floor(x), ceil(x), round(x), trunc(x)",
// Min/Max/Random
"min(a, b, ...), max(a, b, ...)",
"random() - random number 0-1",
// Constants
"pi, e - mathematical constants"
];
}
@@ -0,0 +1,127 @@
.pragma library
// Address helpers
var macFromDevice = (dev) => {
if (!dev) return "";
if (dev.address && dev.address.length > 0) return dev.address;
if (dev.nativePath && dev.nativePath.indexOf("/dev_") !== -1)
return dev.nativePath.split("dev_")[1].split("_").join(":");
return "";
};
var deviceKey = (dev) => {
if (!dev) return "";
if (dev.address && dev.address.length > 0) return dev.address.toUpperCase();
if (dev.nativePath && dev.nativePath.length > 0) return dev.nativePath;
if (dev.devicePath && dev.devicePath.length > 0) return dev.devicePath;
return (dev.name || dev.deviceName || "") + "|" + (dev.icon || "");
};
var dedupeDevices = (list) => {
if (!list || list.length === 0) return [];
var seen = ({});
var out = [];
for (var i = 0; i < list.length; ++i) {
var d = list[i];
if (!d) continue;
var k = deviceKey(d);
if (k && !seen[k]) { seen[k] = true; out.push(d); }
}
return out;
};
// RSSI parsing
var parseRssiOutput = (text) => {
try {
text = text || "";
var mParen = text.match(/\(\s*(-?\d+)\s*(?:d?b?m?)?\s*\)/i);
if (mParen && mParen.length > 1) return Number(mParen[1]);
var mDec = text.match(/RSSI:\s*(-?\d+)/i);
if (mDec && mDec.length > 1) return Number(mDec[1]);
var mHex = text.match(/RSSI:\s*0x([0-9a-fA-F]+)/i);
if (mHex && mHex.length > 1) {
var v = parseInt(mHex[1], 16);
if (v >= 0x80000000) v = v - 0x100000000; // 32-bit two's complement
else if (v >= 0x8000) v = v - 0x10000; // 16-bit
else if (v >= 0x80) v = v - 0x100; // 8-bit
return v;
}
} catch (e) {}
return null;
};
var dbmToPercent = (dbm) => {
if (dbm === null || dbm === undefined || isNaN(dbm)) return null;
// Clamp simple linear map roughly from -100..0 dBm to 0..100%
var pct = Math.round((Number(dbm) + 100) * 2);
if (isNaN(pct)) return null;
return Math.max(0, Math.min(100, pct));
};
// Signal helpers
var signalPercent = (device, cache, _version) => {
if (!device) return null;
try {
var addr = macFromDevice(device);
if (addr && cache && cache[addr] !== undefined) {
var cached = Number(cache[addr]) | 0;
return Math.max(0, Math.min(100, cached));
}
} catch (e) {}
var s = device && device.signalStrength;
if (s === undefined || s <= 0) return null;
var p = Number(s) | 0;
return Math.max(0, Math.min(100, p));
};
var signalIcon = (p) => {
if (p === null) return "antenna-bars-off";
if (p >= 80) return "antenna-bars-5";
if (p >= 60) return "antenna-bars-4";
if (p >= 40) return "antenna-bars-3";
if (p >= 20) return "antenna-bars-2";
return "antenna-bars-1";
};
// Icon mapping
var deviceIcon = (name, icon) => {
var s1 = String(name || "").toLowerCase();
var s2 = String(icon || "").toLowerCase();
// Prefer icon-based hints for display devices first to avoid "audio" catching TVs
var displayHints = ["display", "tv", "monitor", "projector", "screen", "chromecast", "cast"];
for (var dh = 0; dh < displayHints.length; dh++) {
if (s2.indexOf(displayHints[dh]) !== -1) return "bt-device-tv";
}
var tests = [
[["controller", "gamepad"], "bt-device-gamepad"],
[["microphone"], "bt-device-microphone"],
[["pod", "bud", "minor"], "bt-device-earbuds"],
[["headset", "arctis", "major"], "bt-device-headset"],
[["headphone"], "bt-device-headphones"],
[["mouse"], "bt-device-mouse"],
[["keyboard"], "bt-device-keyboard"],
[["watch"], "bt-device-watch"],
[["display", "tv", "monitor", "projector", "screen", "chromecast", "cast"], "bt-device-tv"],
[["speaker", "audio", "sound"], "bt-device-speaker"],
[["phone", "iphone", "android", "samsung"], "bt-device-phone"]
];
for (var i = 0; i < tests.length; i++) {
var keys = tests[i][0];
var out = tests[i][1];
for (var j = 0; j < keys.length; j++) {
var k = keys[j];
if (s1.indexOf(k) !== -1 || s2.indexOf(k) !== -1) return out;
}
}
return "bt-device-generic";
};
// Battery percent helper
var batteryPercent = (device) => {
if (!device || !device.batteryAvailable || device.battery === undefined) return null;
var val = Math.round(Number(device.battery) * 100);
if (isNaN(val)) return null;
return Math.max(0, Math.min(100, val));
};
@@ -0,0 +1,187 @@
var colors = [
// --- REDS ---
{ name: "MistyRose", color: "mistyrose" },
{ name: "LightPink", color: "lightpink" },
{ name: "Pink", color: "pink" },
{ name: "PaleVioletRed", color: "palevioletred" },
{ name: "Pink 500", color: "#E91E63" }, // Material
{ name: "HotPink", color: "hotpink" },
{ name: "DeepPink", color: "deeppink" },
{ name: "MediumVioletRed", color: "mediumvioletred" },
{ name: "LightSalmon", color: "lightsalmon" },
{ name: "Salmon", color: "salmon" },
{ name: "DarkSalmon", color: "darksalmon" },
{ name: "LightCoral", color: "lightcoral" },
{ name: "IndianRed", color: "indianred" },
{ name: "Alizarin", color: "#E74C3C" }, // Flat UI
{ name: "Red 500", color: "#F44336" }, // Material
{ name: "Crimson", color: "crimson" },
{ name: "Red", color: "red" },
{ name: "FireBrick", color: "firebrick" },
{ name: "DarkRed", color: "darkred" },
{ name: "Maroon", color: "maroon" },
{ name: "Brown", color: "brown" },
// --- ORANGES & BROWNS ---
{ name: "Coral", color: "coral" },
{ name: "Tomato", color: "tomato" },
{ name: "OrangeRed", color: "orangered" },
{ name: "Deep Orange 500", color: "#FF5722" }, // Material
{ name: "DarkOrange", color: "darkorange" },
{ name: "Carrot", color: "#E67E22" }, // Flat UI
{ name: "Orange 500", color: "#FF9800" }, // Material
{ name: "Orange", color: "orange" },
{ name: "SandyBrown", color: "sandybrown" },
{ name: "Peru", color: "peru" },
{ name: "Chocolate", color: "chocolate" },
{ name: "SaddleBrown", color: "saddlebrown" },
{ name: "Sienna", color: "sienna" },
{ name: "Brown 500", color: "#795548" }, // Material
// --- YELLOWS, BEIGES & GOLDS ---
{ name: "LightYellow", color: "lightyellow" },
{ name: "LemonChiffon", color: "lemonchiffon" },
{ name: "LightGoldenrodYellow", color: "lightgoldenrodyellow" },
{ name: "PapayaWhip", color: "papayawhip" },
{ name: "Moccasin", color: "moccasin" },
{ name: "PeachPuff", color: "peachpuff" },
{ name: "NavajoWhite", color: "navajowhite" },
{ name: "Wheat", color: "wheat" },
{ name: "BurlyWood", color: "burlywood" },
{ name: "Tan", color: "tan" },
{ name: "Bisque", color: "bisque" },
{ name: "BlanchedAlmond", color: "blanchedalmond" },
{ name: "Cornsilk", color: "cornsilk" },
{ name: "PaleGoldenrod", color: "palegoldenrod" },
{ name: "Khaki", color: "khaki" },
{ name: "DarkKhaki", color: "darkkhaki" },
{ name: "Goldenrod", color: "goldenrod" },
{ name: "DarkGoldenrod", color: "darkgoldenrod" },
{ name: "Sun Flower", color: "#F1C40F" }, // Flat UI
{ name: "Yellow 500", color: "#FFEB3B" }, // Material
{ name: "Yellow", color: "yellow" },
{ name: "Gold", color: "gold" },
{ name: "Amber 500", color: "#FFC107" }, // Material
// --- GREENS ---
{ name: "GreenYellow", color: "greenyellow" },
{ name: "Chartreuse", color: "chartreuse" },
{ name: "LawnGreen", color: "lawngreen" },
{ name: "Lime 500", color: "#CDDC39" }, // Material
{ name: "Lime", color: "lime" },
{ name: "LimeGreen", color: "limegreen" },
{ name: "PaleGreen", color: "palegreen" },
{ name: "LightGreen", color: "lightgreen" },
{ name: "Light Green 500", color: "#8BC34A" }, // Material
{ name: "MediumSpringGreen", color: "mediumspringgreen" },
{ name: "SpringGreen", color: "springgreen" },
{ name: "Emerald", color: "#2ECC71" }, // Flat UI
{ name: "Green 500", color: "#4CAF50" }, // Material
{ name: "MediumSeaGreen", color: "mediumseagreen" },
{ name: "SeaGreen", color: "seagreen" },
{ name: "ForestGreen", color: "forestgreen" },
{ name: "Green", color: "green" },
{ name: "DarkGreen", color: "darkgreen" },
{ name: "YellowGreen", color: "yellowgreen" },
{ name: "OliveDrab", color: "olivedrab" },
{ name: "Olive", color: "olive" },
{ name: "DarkOliveGreen", color: "darkolivegreen" },
// --- TEALS & CYANS ---
{ name: "MediumAquamarine", color: "mediumaquamarine" },
{ name: "DarkSeaGreen", color: "darkseagreen" },
{ name: "LightSeaGreen", color: "lightseagreen" },
{ name: "DarkCyan", color: "darkcyan" },
{ name: "Teal", color: "teal" },
{ name: "Turquoise", color: "#1ABC9C" }, // Flat UI
{ name: "LightCyan", color: "lightcyan" },
{ name: "PaleTurquoise", color: "paleturquoise" },
{ name: "Aquamarine", color: "aquamarine" },
{ name: "Turquoise", color: "turquoise" },
{ name: "MediumTurquoise", color: "mediumturquoise" },
{ name: "DarkTurquoise", color: "darkturquoise" },
{ name: "Aqua", color: "aqua" },
{ name: "Cyan", color: "cyan" },
{ name: "Cyan 500", color: "#00BCD4" }, // Material
{ name: "CadetBlue", color: "cadetblue" },
{ name: "Teal 500", color: "#009688" }, // Material
{ name: "DarkSlateGray", color: "darkslategray" },
// --- BLUES ---
{ name: "PowderBlue", color: "powderblue" },
{ name: "LightBlue", color: "lightblue" },
{ name: "SkyBlue", color: "skyblue" },
{ name: "LightSkyBlue", color: "lightskyblue" },
{ name: "Light Blue 500", color: "#03A9F4" }, // Material
{ name: "DeepSkyBlue", color: "deepskyblue" },
{ name: "DodgerBlue", color: "dodgerblue" },
{ name: "CornflowerBlue", color: "cornflowerblue" },
{ name: "Peter River", color: "#3498DB" }, // Flat UI
{ name: "Blue 500", color: "#2196F3" }, // Material
{ name: "SteelBlue", color: "steelblue" },
{ name: "LightSteelBlue", color: "lightsteelblue" },
{ name: "RoyalBlue", color: "royalblue" },
{ name: "Blue", color: "blue" },
{ name: "MediumBlue", color: "mediumblue" },
{ name: "Belize Hole", color: "#2980B9" }, // Flat UI
{ name: "DarkBlue", color: "darkblue" },
{ name: "Navy", color: "navy" },
{ name: "MidnightBlue", color: "midnightblue" },
{ name: "Midnight Blue", color: "#2C3E50" }, // Flat UI (Same name, different color)
{ name: "Indigo 500", color: "#3F51B5" }, // Material
{ name: "DarkSlateBlue", color: "darkslateblue" },
{ name: "MediumSlateBlue", color: "mediumslateblue" },
{ name: "SlateBlue", color: "slateblue" },
// --- PURPLES & MAGENTAS ---
{ name: "Lavender", color: "lavender" },
{ name: "Thistle", color: "thistle" },
{ name: "Plum", color: "plum" },
{ name: "Violet", color: "violet" },
{ name: "Orchid", color: "orchid" },
{ name: "Fuchsia", color: "fuchsia" },
{ name: "Magenta", color: "magenta" },
{ name: "MediumOrchid", color: "mediumorchid" },
{ name: "MediumPurple", color: "mediumpurple" },
{ name: "Amethyst", color: "#9B59B6" }, // Flat UI
{ name: "Purple 500", color: "#9C27B0" }, // Material
{ name: "BlueViolet", color: "blueviolet" },
{ name: "DarkViolet", color: "darkviolet" },
{ name: "DarkOrchid", color: "darkorchid" },
{ name: "DarkMagenta", color: "darkmagenta" },
{ name: "Purple", color: "purple" },
{ name: "Deep Purple 500", color: "#673AB7" }, // Material
{ name: "Indigo", color: "indigo" },
// --- NEUTRALS ---
{ name: "White", color: "white" },
{ name: "Snow", color: "snow" },
{ name: "HoneyDew", color: "honeydew" },
{ name: "MintCream", color: "mintcream" },
{ name: "Azure", color: "azure" },
{ name: "AliceBlue", color: "aliceblue" },
{ name: "GhostWhite", color: "ghostwhite" },
{ name: "WhiteSmoke", color: "whitesmoke" },
{ name: "Seashell", color: "seashell" },
{ name: "Beige", color: "beige" },
{ name: "OldLace", color: "oldlace" },
{ name: "FloralWhite", color: "floralwhite" },
{ name: "Ivory", color: "ivory" },
{ name: "AntiqueWhite", color: "antiquewhite" },
{ name: "Linen", color: "linen" },
{ name: "LavenderBlush", color: "lavenderblush" },
{ name: "Gainsboro", color: "gainsboro" },
{ name: "LightGray", color: "lightgray" },
{ name: "Silver", color: "silver" },
{ name: "DarkGray", color: "darkgray" },
{ name: "Gray", color: "gray" },
{ name: "Grey 500", color: "#9E9E9E" }, // Material
{ name: "Concrete", color: "#95A5A6" }, // Flat UI
{ name: "DimGray", color: "dimgray" },
{ name: "LightSlateGray", color: "lightslategray" },
{ name: "SlateGray", color: "slategray" },
{ name: "Asbestos", color: "#7F8C8D" }, // Flat UI
{ name: "Blue Grey 500", color: "#607D8B" }, // Material
{ name: "Wet Asphalt", color: "#34495E" }, // Flat UI
{ name: "Black", color: "black" }
]
@@ -0,0 +1,270 @@
// Convert hex color to HSL
function hexToHSL(hex) {
const rgb = hexToRgb(hex);
if (!rgb) return null;
return rgbToHsl(rgb.r, rgb.g, rgb.b);
}
// Convert HSL to hex color
function hslToHex(h, s, l) {
const rgb = hslToRgb(h, s, l);
return rgbToHex(rgb.r, rgb.g, rgb.b);
}
// Convert hex color to RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
// Convert RGB to hex color
function rgbToHex(r, g, b) {
return "#" + [r, g, b].map(x => {
const hex = Math.round(Math.max(0, Math.min(255, x))).toString(16);
return hex.length === 1 ? "0" + hex : hex;
}).join("");
}
// Convert RGB to HSL
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const 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)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return { h: h * 360, s: s * 100, l: l * 100 };
}
// Convert HSL to RGB
function hslToRgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l;
} 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) };
}
// Convert RGB to HSV
function rgbToHsv(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, v = max;
var 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 * 100, v: v * 100 };
}
// Convert HSV to RGB
function hsvToRgb(h, s, v) {
h /= 360;
s /= 100;
v /= 100;
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var 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) };
}
// Calculate relative luminance (WCAG standard)
function getLuminance(hex) {
const rgb = hexToRgb(hex);
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(val => {
val /= 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// Calculate contrast ratio between two colors
function getContrastRatio(hex1, hex2) {
const lum1 = getLuminance(hex1);
const lum2 = getLuminance(hex2);
const brightest = Math.max(lum1, lum2);
const darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
// Check if a color is considered "light"
function isLightColor(hex) {
return getLuminance(hex) > 0.5;
}
// Adjust color lightness
function adjustLightness(hex, amount) {
const hsl = hexToHSL(hex);
hsl.l = Math.max(0, Math.min(100, hsl.l + amount));
return hslToHex(hsl.h, hsl.s, hsl.l);
}
// Adjust color saturation
function adjustSaturation(hex, amount) {
const hsl = hexToHSL(hex);
hsl.s = Math.max(0, Math.min(100, hsl.s + amount));
return hslToHex(hsl.h, hsl.s, hsl.l);
}
// Adjust both lightness and saturation
function adjustLightnessAndSaturation(hex, lightnessAmount, saturationAmount) {
const hsl = hexToHSL(hex);
hsl.l = Math.max(0, Math.min(100, hsl.l + lightnessAmount));
hsl.s = Math.max(0, Math.min(100, hsl.s + saturationAmount));
return hslToHex(hsl.h, hsl.s, hsl.l);
}
// Generate "on" color with proper contrast (for text/icons)
function generateOnColor(baseColor, isDarkMode) {
const isBaseLight = isLightColor(baseColor);
// If base is light, we need dark text; if base is dark, we need light text
if (isBaseLight) {
// Try darker variants
let testColor = "#000000";
if (getContrastRatio(baseColor, testColor) >= 4.5) {
return testColor;
}
// Fallback to dark gray
return "#1c1b1f";
} else {
// Try lighter variants
let testColor = "#ffffff";
if (getContrastRatio(baseColor, testColor) >= 4.5) {
return testColor;
}
// Fallback to light gray
return "#e6e1e5";
}
}
// Generate container color (lighter in light mode, darker in dark mode)
function generateContainerColor(baseColor, isDarkMode) {
const rgb = hexToRgb(baseColor);
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
if (isDarkMode) {
// In dark mode, containers are darker and more saturated
hsl.l = Math.max(10, Math.min(30, hsl.l - 20));
hsl.s = Math.min(100, hsl.s + 10);
} else {
// In light mode, containers are lighter and less saturated
hsl.l = Math.min(90, Math.max(75, hsl.l + 30));
hsl.s = Math.max(0, hsl.s - 10);
}
const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
}
// Generate surface variant colors
function generateSurfaceVariant(backgroundColor, step, isDarkMode) {
const rgb = hexToRgb(backgroundColor);
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
if (isDarkMode) {
// In dark mode, variants get progressively lighter
hsl.l = Math.min(100, hsl.l + (step * 3));
} else {
// In light mode, variants get progressively darker
hsl.l = Math.max(0, hsl.l - (step * 2));
}
const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
}
@@ -0,0 +1,25 @@
// Helper function to stringify even if there are circular refs.
function stringify(obj, replacer, spaces, cycleReplacer) {
return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
}
function serializer(replacer, cycleReplacer) {
var stack = [], keys = []
if (cycleReplacer == null) cycleReplacer = function(key, value) {
if (stack[0] === value) return "[Circular ~]"
return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
}
return function(key, value) {
if (stack.length > 0) {
var thisPos = stack.indexOf(this)
~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
}
else stack.push(value)
return replacer == null ? value : replacer.call(this, key, value)
}
}
@@ -0,0 +1,114 @@
// -----------------------------------------------------
// Helper function to convert Qt objects to plain JavaScript objects
// Only used when generating settings-default.json
function qtObjectToPlainObject(obj) {
if (obj === null || obj === undefined) {
return obj;
}
// Handle primitive types
if (typeof obj !== "object") {
return obj;
}
// Handle native JavaScript arrays
if (Array.isArray(obj)) {
return obj.map((item) => qtObjectToPlainObject(item));
}
// Detect QML arrays FIRST (before color detection)
// QML arrays have a numeric length property and indexed properties
if (typeof obj.length === "number" && obj.length >= 0) {
// Check if it has indexed properties - be more flexible about detection
var hasIndexedProps = true;
var hasNumericKeys = false;
// Check if we have at least some numeric properties
for (var i = 0; i < obj.length; i++) {
if (obj.hasOwnProperty(i) || obj[i] !== undefined) {
hasNumericKeys = true;
break;
}
}
// If we have length > 0 and some numeric keys, treat as array
if (obj.length > 0 && hasNumericKeys) {
var arr = [];
for (var i = 0; i < obj.length; i++) {
// Use direct property access, handle undefined gracefully
var item = obj[i];
if (item !== undefined) {
arr.push(qtObjectToPlainObject(item));
}
}
return arr; // Return here to avoid processing as object
}
// Handle empty arrays (length = 0)
if (obj.length === 0) {
return [];
}
}
// Detect and convert QML color objects to hex strings
if (
typeof obj.r === "number" &&
typeof obj.g === "number" &&
typeof obj.b === "number" &&
typeof obj.a === "number" &&
typeof obj.valid === "boolean"
) {
// This looks like a QML color object
try {
// Try to get the string representation (should be hex like "#000000")
if (typeof obj.toString === "function") {
return obj.toString();
} else {
// Fallback: convert RGBA to hex manually
var r = Math.round(obj.r * 255);
var g = Math.round(obj.g * 255);
var b = Math.round(obj.b * 255);
var hex =
"#" +
r.toString(16).padStart(2, "0") +
g.toString(16).padStart(2, "0") +
b.toString(16).padStart(2, "0");
return hex;
}
} catch (e) {
// If conversion fails, fall through to regular object handling
}
}
// Handle regular objects
var plainObj = {};
// Get all property names, but filter out Qt-specific ones
var propertyNames = Object.getOwnPropertyNames(obj);
for (var i = 0; i < propertyNames.length; i++) {
var propName = propertyNames[i];
// Skip Qt-specific properties, functions, and array-like properties
if (
propName === "objectName" ||
propName === "objectNameChanged" ||
propName === "length" || // Skip length property
/^\d+$/.test(propName) || // Skip numeric keys (0, 1, 2, etc.)
propName.endsWith("Changed") ||
typeof obj[propName] === "function"
) {
continue;
}
try {
var value = obj[propName];
plainObj[propName] = qtObjectToPlainObject(value);
} catch (e) {
// Skip properties that can't be accessed
continue;
}
}
return plainObj;
}
@@ -0,0 +1,192 @@
function sha256(message) {
// SHA-256 constants
const K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
];
// Initial hash values
let h0 = 0x6a09e667;
let h1 = 0xbb67ae85;
let h2 = 0x3c6ef372;
let h3 = 0xa54ff53a;
let h4 = 0x510e527f;
let h5 = 0x9b05688c;
let h6 = 0x1f83d9ab;
let h7 = 0x5be0cd19;
// Convert string to UTF-8 bytes manually
const msgBytes = stringToUtf8Bytes(message);
const msgLength = msgBytes.length;
const bitLength = msgLength * 8;
// Calculate padding
// Message + 1 bit (0x80) + padding zeros + 8 bytes for length = multiple of 64 bytes
const totalBitsNeeded = bitLength + 1 + 64; // message bits + padding bit + 64-bit length
const totalBytesNeeded = Math.ceil(totalBitsNeeded / 8);
const paddedLength = Math.ceil(totalBytesNeeded / 64) * 64; // Round up to multiple of 64
const paddedMsg = new Array(paddedLength).fill(0);
// Copy original message
for (let i = 0; i < msgLength; i++) {
paddedMsg[i] = msgBytes[i];
}
// Add padding bit (0x80 = 10000000 in binary)
paddedMsg[msgLength] = 0x80;
// Add length as 64-bit big-endian integer at the end
// JavaScript numbers are not precise enough for 64-bit integers, so we handle high/low separately
const highBits = Math.floor(bitLength / 0x100000000);
const lowBits = bitLength % 0x100000000;
// Write 64-bit length in big-endian format
paddedMsg[paddedLength - 8] = (highBits >>> 24) & 0xFF;
paddedMsg[paddedLength - 7] = (highBits >>> 16) & 0xFF;
paddedMsg[paddedLength - 6] = (highBits >>> 8) & 0xFF;
paddedMsg[paddedLength - 5] = highBits & 0xFF;
paddedMsg[paddedLength - 4] = (lowBits >>> 24) & 0xFF;
paddedMsg[paddedLength - 3] = (lowBits >>> 16) & 0xFF;
paddedMsg[paddedLength - 2] = (lowBits >>> 8) & 0xFF;
paddedMsg[paddedLength - 1] = lowBits & 0xFF;
// Process message in 512-bit (64-byte) chunks
for (let chunk = 0; chunk < paddedLength; chunk += 64) {
const w = new Array(64);
// Break chunk into sixteen 32-bit big-endian words
for (let i = 0; i < 16; i++) {
const offset = chunk + i * 4;
w[i] = (paddedMsg[offset] << 24) |
(paddedMsg[offset + 1] << 16) |
(paddedMsg[offset + 2] << 8) |
paddedMsg[offset + 3];
// Ensure unsigned 32-bit
w[i] = w[i] >>> 0;
}
// Extend the sixteen 32-bit words into sixty-four 32-bit words
for (let i = 16; i < 64; i++) {
const s0 = rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >>> 3);
const s1 = rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >>> 10);
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
}
// Initialize working variables for this chunk
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
// Main loop
for (let i = 0; i < 64; i++) {
const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
const ch = (e & f) ^ (~e & g);
const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0;
const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
const maj = (a & b) ^ (a & c) ^ (b & c);
const temp2 = (S0 + maj) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
// Add this chunk's hash to result so far
h0 = (h0 + a) >>> 0;
h1 = (h1 + b) >>> 0;
h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0;
h4 = (h4 + e) >>> 0;
h5 = (h5 + f) >>> 0;
h6 = (h6 + g) >>> 0;
h7 = (h7 + h) >>> 0;
}
// Produce the final hash value as a hex string
return [h0, h1, h2, h3, h4, h5, h6, h7]
.map(h => h.toString(16).padStart(8, '0'))
.join('');
}
function stringToUtf8Bytes(str) {
const bytes = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code < 0x80) {
// 1-byte character (ASCII)
bytes.push(code);
} else if (code < 0x800) {
// 2-byte character
bytes.push(0xC0 | (code >> 6));
bytes.push(0x80 | (code & 0x3F));
} else if (code < 0xD800 || code > 0xDFFF) {
// 3-byte character (not surrogate)
bytes.push(0xE0 | (code >> 12));
bytes.push(0x80 | ((code >> 6) & 0x3F));
bytes.push(0x80 | (code & 0x3F));
} else {
// 4-byte character (surrogate pair)
i++; // Move to next character
const code2 = str.charCodeAt(i);
const codePoint = 0x10000 + (((code & 0x3FF) << 10) | (code2 & 0x3FF));
bytes.push(0xF0 | (codePoint >> 18));
bytes.push(0x80 | ((codePoint >> 12) & 0x3F));
bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
bytes.push(0x80 | (codePoint & 0x3F));
}
}
return bytes;
}
function rightRotate(value, amount) {
return ((value >>> amount) | (value << (32 - amount))) >>> 0;
}
// Test function to verify implementation
// function testSHA256() {
// const tests = [
// {
// input: "",
// expected:
// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
// },
// {
// input: "Hello World",
// expected:
// "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
// },
// {
// input: "abc",
// expected:
// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
// },
// {
// input: "The quick brown fox jumps over the lazy dog",
// expected:
// "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
// },
// ];
// console.log("Running SHA-256 tests:");
// tests.forEach((test, i) => {
// const result = Crypto.sha256(test.input);
// const passed = result === test.expected;
// console.log(`Test ${i + 1}: ${passed ? "PASS" : "FAIL"}`);
// if (!passed) {
// console.log(` Input: "${test.input}"`);
// console.log(` Expected: ${test.expected}`);
// console.log(` Got: ${result}`);
// }
// });
// }