knxdisplay/web-interface/src/utils.js
2026-02-12 10:31:46 +01:00

241 lines
9.6 KiB
JavaScript

import { TYPE_KEYS, WIDGET_DEFAULTS } from './constants';
export function typeKeyFor(type) {
return TYPE_KEYS[type] || 'label';
}
export function invertLightness(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const maxC = Math.max(r, g, b);
const minC = Math.min(r, g, b);
const delta = maxC - minC;
let L = (maxC + minC) * 0.5;
let S = 0, H = 0;
if (delta > 0.0001) {
S = L < 0.5 ? delta / (maxC + minC) : delta / (2 - maxC - minC);
if (maxC === r) H = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
else if (maxC === g) H = ((b - r) / delta + 2) / 6;
else H = ((r - g) / delta + 4) / 6;
}
L = 1 - L;
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;
};
let ro, go, bo;
if (S < 0.0001) {
ro = go = bo = L;
} else {
const q = L < 0.5 ? L * (1 + S) : L + S - L * S;
const p = 2 * L - q;
ro = hue2rgb(p, q, H + 1/3);
go = hue2rgb(p, q, H);
bo = hue2rgb(p, q, H - 1/3);
}
const toHex = v => Math.round(Math.max(0, Math.min(1, v)) * 255).toString(16).padStart(2, '0');
return `#${toHex(ro)}${toHex(go)}${toHex(bo)}`;
}
export function clamp(value, min, max) {
if (Number.isNaN(value)) return min;
return Math.max(min, Math.min(max, value));
}
export function minSizeFor(widget) {
const key = typeKeyFor(widget.type);
if (key === 'button') return { w: 60, h: 30 };
if (key === 'led') return { w: 20, h: 20 };
if (key === 'icon') return { w: 24, h: 24 };
if (key === 'tabview') return { w: 100, h: 100 };
if (key === 'powerflow') return { w: 240, h: 180 };
if (key === 'powernode') return { w: 70, h: 70 };
if (key === 'powerlink') return { w: 1, h: 1 };
if (key === 'chart') return { w: 160, h: 120 };
if (key === 'roomcard') return { w: 120, h: 120 };
if (key === 'rectangle') return { w: 40, h: 30 };
if (key === 'arc') return { w: 80, h: 80 };
if (key === 'buttonmatrix') return { w: 120, h: 60 };
return { w: 40, h: 20 };
}
export function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},${alpha})`;
}
export function normalizeWidget(w, nextWidgetIdRef) {
const key = typeKeyFor(w.type);
const defaults = WIDGET_DEFAULTS[key];
Object.keys(defaults).forEach((prop) => {
if (prop === 'shadow' || prop === 'conditions') return;
if (w[prop] === undefined || w[prop] === null) {
w[prop] = defaults[prop];
}
});
// Conditions: ensure each widget has its own array
if (defaults.conditions !== undefined) {
if (!Array.isArray(w.conditions)) {
w.conditions = [];
}
}
// SubButtons: ensure each widget has its own array
if (defaults.subButtons !== undefined) {
if (!Array.isArray(w.subButtons)) {
w.subButtons = [];
}
// Ensure subButtonSize has a default
if (w.subButtonSize === undefined || w.subButtonSize === null) {
w.subButtonSize = defaults.subButtonSize || 40;
}
// Ensure subButtonDistance has a default
if (w.subButtonDistance === undefined || w.subButtonDistance === null) {
w.subButtonDistance = defaults.subButtonDistance || 80;
}
// Ensure subButtonOpacity has a default
if (w.subButtonOpacity === undefined || w.subButtonOpacity === null) {
w.subButtonOpacity = defaults.subButtonOpacity || 255;
}
// Ensure cardStyle has a default
if (w.cardStyle === undefined || w.cardStyle === null) {
w.cardStyle = defaults.cardStyle || 0;
}
}
// TextLines: ensure each widget has its own array and each line has fontSize
if (defaults.textLines !== undefined) {
if (!Array.isArray(w.textLines)) {
w.textLines = [];
}
// Ensure each textLine has a fontSize
w.textLines.forEach(tl => {
if (tl.fontSize === undefined || tl.fontSize === null) {
tl.fontSize = 1; // Default 18px
}
});
}
if (!w.shadow) {
w.shadow = { ...defaults.shadow };
} else {
Object.keys(defaults.shadow).forEach((prop) => {
if (w.shadow[prop] === undefined || w.shadow[prop] === null) {
w.shadow[prop] = defaults.shadow[prop];
}
});
}
// Border style defaults (used by rectangle widget, harmless for others)
if (w.borderWidth === undefined || w.borderWidth === null) {
w.borderWidth = defaults.borderWidth ?? 0;
}
if (!w.borderColor) {
w.borderColor = defaults.borderColor || '#ffffff';
}
if (w.borderOpacity === undefined || w.borderOpacity === null) {
w.borderOpacity = defaults.borderOpacity ?? 0;
}
if (defaults.chart) {
const maxSeries = 3;
if (!w.chart) {
w.chart = {
period: defaults.chart.period ?? 0,
series: (defaults.chart.series || []).map(s => ({ ...s }))
};
} else {
if (w.chart.period === undefined || w.chart.period === null) {
w.chart.period = defaults.chart.period ?? 0;
}
if (w.chart.showXLine === undefined) w.chart.showXLine = true;
if (w.chart.showYLine === undefined) w.chart.showYLine = true;
if (w.chart.showXLabels === undefined) w.chart.showXLabels = true;
if (w.chart.showYLabels === undefined) w.chart.showYLabels = true;
if (w.chart.showBg === undefined) w.chart.showBg = true;
if (w.chart.showGrid === undefined) w.chart.showGrid = true;
if (w.chart.lineWidth === undefined) w.chart.lineWidth = 2;
if (w.chart.pointCount === undefined) w.chart.pointCount = 120;
if (w.chart.showPoints === undefined) w.chart.showPoints = false;
if (w.chart.pointSize === undefined) w.chart.pointSize = 4;
if (!Array.isArray(w.chart.series)) {
w.chart.series = [];
}
if (w.chart.series.length === 0 && defaults.chart.series) {
w.chart.series = defaults.chart.series.map(s => ({ ...s }));
}
for (let i = 0; i < w.chart.series.length && i < maxSeries; i++) {
const fallback = (defaults.chart.series && defaults.chart.series[i]) || defaults.chart.series?.[0] || { knxAddr: 0, textSrc: 1, color: '#EF6351' };
const entry = w.chart.series[i];
if (entry.knxAddr === undefined || entry.knxAddr === null) entry.knxAddr = fallback.knxAddr;
if (entry.textSrc === undefined || entry.textSrc === null) entry.textSrc = fallback.textSrc;
if (!entry.color) entry.color = fallback.color;
}
if (w.chart.series.length > maxSeries) {
w.chart.series = w.chart.series.slice(0, maxSeries);
}
}
}
if (w.visible === undefined || w.visible === null) w.visible = true;
if (w.x === undefined || w.x === null) w.x = 100;
if (w.y === undefined || w.y === null) w.y = 100;
if (w.id === undefined || w.id === null) {
if (nextWidgetIdRef) w.id = nextWidgetIdRef.value++;
}
// Action defaults
if (w.action === undefined) w.action = 0; // BUTTON_ACTIONS.KNX
if (w.targetScreen === undefined) w.targetScreen = 0;
// Icon defaults
if (w.iconCodepoint === undefined) w.iconCodepoint = defaults.iconCodepoint || 0;
if (w.iconPosition === undefined) w.iconPosition = defaults.iconPosition || 0;
if (w.iconSize === undefined) w.iconSize = defaults.iconSize || 1;
if (w.iconGap === undefined) w.iconGap = defaults.iconGap || 8;
if (w.iconPositionX === undefined) w.iconPositionX = defaults.iconPositionX || 0;
if (w.iconPositionY === undefined) w.iconPositionY = defaults.iconPositionY || 0;
// Hierarchy
if (w.parentId === undefined) w.parentId = -1;
if (key === 'button' && (w.isContainer === undefined || w.isContainer === null)) {
w.isContainer = defaults.isContainer ?? false;
}
}
export function normalizeScreen(screen, nextScreenIdRef, nextWidgetIdRef) {
if (screen.id === undefined || screen.id === null) {
if (nextScreenIdRef) screen.id = nextScreenIdRef.value++;
}
if (!screen.name) screen.name = `Screen ${screen.id}`;
if (screen.mode === undefined || screen.mode === null) screen.mode = 0;
if (!screen.bgColor) screen.bgColor = '#1A1A2E';
if (!screen.nightBgColor) screen.nightBgColor = '#0E1217';
if (screen.bgImage === undefined) screen.bgImage = '';
if (screen.bgImageMode === undefined) screen.bgImageMode = 1;
if (!Array.isArray(screen.widgets)) screen.widgets = [];
// Modal defaults
if (!screen.modal) {
screen.modal = { x: 0, y: 0, w: 0, h: 0, radius: 12, dim: true };
} else {
if (screen.modal.x === undefined) screen.modal.x = 0;
if (screen.modal.y === undefined) screen.modal.y = 0;
if (screen.modal.w === undefined) screen.modal.w = 0;
if (screen.modal.h === undefined) screen.modal.h = 0;
if (screen.modal.radius === undefined) screen.modal.radius = 12;
if (screen.modal.dim === undefined) screen.modal.dim = true;
}
screen.widgets.forEach(w => normalizeWidget(w, nextWidgetIdRef));
}