241 lines
9.6 KiB
JavaScript
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));
|
|
}
|