Release v1.61.0-beta.3 candidate

This commit is contained in:
Matysh
2026-08-10 09:44:31 +03:00
parent 7d152e04f9
commit cd55a8e897
39 changed files with 1019 additions and 483 deletions
+83 -12
View File
@@ -23,7 +23,8 @@ import {
averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo,
stateIcon, lightColorOf, parseRoomRef, diffNewDevices, glowColorOf, doorSector, hasRoomBehind, controlsAction, isControllable,
spaceDisplayOf, resolveEffectiveRoomFill, fillColorsOf, DEFAULT_FILL_COLORS,
type FillColors, type ResolvedRoomFill, runServiceFor, RUN_TARGET_DOMAINS,
customFillOf, roomCustomFillOf, DEFAULT_CUSTOM_FILL,
type FillColors, type FillColorEntry, type ResolvedRoomFill, runServiceFor, RUN_TARGET_DOMAINS,
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf,
DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, type SpaceDisplay,
referencedContentUrls,
@@ -145,7 +146,7 @@ import {
import { renderOpeningTunnelFills } from './render/opening-tunnels';
import { safeStoredColor } from './color';
const CARD_VERSION = '1.61.0-beta.2';
const CARD_VERSION = '1.61.0-beta.3';
/** Keeps every previously valid scale at the maximum 20 cm grid scale lossless. */
const DECOR_TEXT_CM_MAX = 2000;
const CELL_CM_MIN = 0.1;
@@ -902,7 +903,9 @@ class HouseplanCard extends LitElement {
private _nameSel = '';
private _roomDialog = false;
private _roomEditId: string | null = null; // gear on a room card (edit mode)
private _roomFill: '' | 'none' | 'lqi' | 'light' | 'temp' = ''; // '' = inherit
private _roomFill: '' | 'none' | 'lqi' | 'light' | 'temp' | 'custom' = ''; // '' = inherit
/** null = inherit the space custom color; value = explicit room override. */
private _roomCustomFill: FillColorEntry | null = null;
private _roomTempSrc = ''; // '' = average
private _roomHumSrc = '';
private _roomSrcOpen: 'temp' | 'hum' | null = null;
@@ -1072,7 +1075,9 @@ class HouseplanCard extends LitElement {
bgMode: 'static' | 'daynight' | null; // plan background mode; null = inherit (docs/SUN.md)
northDeg: number | null; // per-space compass override; null = inherit
sunRays: boolean | null; // per-space wedges override; null = inherit
fillMode: 'none' | 'lqi' | 'light' | 'temp';
fillMode: 'none' | 'lqi' | 'light' | 'temp' | 'custom';
/** null keeps the optional persisted field absent and uses product default. */
customFill: FillColorEntry | null;
glowEnabled: boolean;
tempMin: number;
tempMax: number;
@@ -1274,6 +1279,7 @@ class HouseplanCard extends LitElement {
_roomDialog: { state: true },
_roomEditId: { state: true },
_roomFill: { state: true },
_roomCustomFill: { state: true },
_roomTempSrc: { state: true },
_roomHumSrc: { state: true },
_roomSrcOpen: { state: true },
@@ -1998,7 +2004,8 @@ class HouseplanCard extends LitElement {
if (this._decorTextDialog) return at('decorText', this._decorTextDialog);
if (this._roomDialog) {
return at('room', {
editId: this._roomEditId, fill: this._roomFill, tempSrc: this._roomTempSrc,
editId: this._roomEditId, fill: this._roomFill, customFill: this._roomCustomFill,
tempSrc: this._roomTempSrc,
humSrc: this._roomHumSrc, srcOpen: this._roomSrcOpen, srcFilter: this._roomSrcFilter,
nameScale: this._roomNameScale, labelScale: this._roomLabelScale,
areaSel: this._areaSel, nameSel: this._nameSel,
@@ -2063,7 +2070,8 @@ class HouseplanCard extends LitElement {
}
case 'room': {
const r = d.data;
this._roomEditId = r.editId; this._roomFill = r.fill; this._roomTempSrc = r.tempSrc;
this._roomEditId = r.editId; this._roomFill = r.fill;
this._roomCustomFill = r.customFill || null; this._roomTempSrc = r.tempSrc;
this._roomHumSrc = r.humSrc; this._roomSrcOpen = r.srcOpen; this._roomSrcFilter = r.srcFilter;
this._roomNameScale = r.nameScale; this._roomLabelScale = r.labelScale;
this._areaSel = r.areaSel; this._nameSel = r.nameSel;
@@ -8502,6 +8510,9 @@ class HouseplanCard extends LitElement {
const mode = this._roomDialog && room.id === this._roomEditId
? (this._roomFill || disp.fill)
: roomFillModeOf(disp.fill, room);
const customFill = this._roomDialog && room.id === this._roomEditId
? (this._roomCustomFill || disp.customFill)
: roomCustomFillOf(disp.customFill, room);
const resolved = resolveEffectiveRoomFill(
mode,
mode === 'lqi' && room.area ? this._roomLqi(room.area) : null,
@@ -8512,6 +8523,7 @@ class HouseplanCard extends LitElement {
disp.tempMin,
disp.tempMax,
this._fillColors,
customFill,
);
byRoom.set(room, resolved);
if (room.id) byId.set(room.id, resolved);
@@ -8533,6 +8545,7 @@ class HouseplanCard extends LitElement {
color: dialog.roomColor,
opacity: dialog.roomOpacity,
fill: dialog.fillMode,
customFill: dialog.customFill ? customFillOf(dialog.customFill) : DEFAULT_CUSTOM_FILL,
glow: dialog.glowEnabled,
tempMin: dialog.tempMin,
tempMax: dialog.tempMax,
@@ -8616,13 +8629,17 @@ class HouseplanCard extends LitElement {
});
}
/** Frame-local Glow-base projection, deliberately independent from data fill. */
/** Frame-local Glow-base projection. Data/static fills keep their exact
* colors; only rooms whose effective fill is `none` receive darkness. */
private _resolvedGlowBase(space: SpaceModel, disp: SpaceDisplay): RoomFillFrame {
const byRoom = new Map<RoomCfg, ResolvedRoomFill | null>();
const byId = new Map<string, ResolvedRoomFill | null>();
const base = this._fillColors.glow_base;
for (const room of space.rooms) {
const resolved: ResolvedRoomFill | null = roomGlowOf(disp.glow, room)
const mode = this._roomDialog && room.id === this._roomEditId
? (this._roomFill || disp.fill)
: roomFillModeOf(disp.fill, room);
const resolved: ResolvedRoomFill | null = roomGlowOf(disp.glow, room) && mode === 'none'
? { color: base.c, opacity: base.a, mode: 'glow' }
: null;
byRoom.set(room, resolved);
@@ -10120,7 +10137,9 @@ class HouseplanCard extends LitElement {
source: sp.plan_url ? 'file' : 'draw',
showBorders: disp.showBorders, showNames: disp.showNames,
hideDecor: disp.hideDecor, hideOpenings: disp.hideOpenings,
roomColor: disp.color, roomOpacity: disp.opacity, fillMode: disp.fill as 'none' | 'lqi' | 'light' | 'temp',
roomColor: disp.color, roomOpacity: disp.opacity, fillMode: disp.fill,
customFill: sp.settings?.custom_fill && typeof sp.settings.custom_fill === 'object'
? customFillOf(sp.settings.custom_fill) : null,
glowEnabled: disp.glow,
bgColor: disp.bgColor,
bgMode: sp.settings?.bg_mode === 'static' || sp.settings?.bg_mode === 'daynight' ? sp.settings.bg_mode : null,
@@ -10141,6 +10160,7 @@ class HouseplanCard extends LitElement {
showBorders: false, showNames: false,
hideDecor: false, hideOpenings: false,
roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none',
customFill: null,
glowEnabled: true,
bgColor: null,
bgMode: null, northDeg: null, sunRays: null,
@@ -10386,6 +10406,7 @@ class HouseplanCard extends LitElement {
north_deg: d.northDeg ?? undefined,
sun_rays: d.sunRays ?? undefined,
fill_mode: d.fillMode,
custom_fill: d.customFill || undefined,
// Always materialize the independent value. In particular, editing a
// legacy fill_mode:'glow' space preserves its appearance atomically
// while replacing the old token with the selected data fill.
@@ -10511,6 +10532,7 @@ class HouseplanCard extends LitElement {
showBorders: false, showNames: false,
hideDecor: false, hideOpenings: false,
roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none',
customFill: null,
glowEnabled: true,
bgColor: null,
bgMode: null, northDeg: null, sunRays: null,
@@ -11333,8 +11355,6 @@ class HouseplanCard extends LitElement {
<label class="dispsection">${this._t('gs.glow_group')}</label>
${this._renderColorRow('glow_base', 'gs.glow_base')}
${this._renderColorRow('glow_light', 'gs.glow_light')}
<label class="dispsection">${this._t('gs.wall_group')}</label>
${this._renderColorRow('wall_fill', 'gs.wall_fill')}
<div class="colorrow gsrow">
<span class="gsl">${this._t('gs.glow_radius')}</span>
<input type="number" class="tempin" min="0.5" step="0.5"
@@ -11346,6 +11366,8 @@ class HouseplanCard extends LitElement {
}} />
<span class="opl">${this._imperial ? this._t('gs.unit_ft') : this._t('gs.unit_m')}</span>
</div>
<label class="dispsection">${this._t('gs.wall_group')}</label>
${this._renderColorRow('wall_fill', 'gs.wall_fill')}
<label class="dispsection">${this._t('gs.bg_group')}</label>
<div class="colorrow gsrow">
<span class="gsl">${this._t('gs.bg_mode')}</span>
@@ -12947,6 +12969,7 @@ class HouseplanCard extends LitElement {
private _resetRoomDialogFields(): void {
this._roomEditId = null;
this._roomFill = '';
this._roomCustomFill = null;
this._roomTempSrc = '';
this._roomHumSrc = '';
this._roomSrcOpen = null;
@@ -12964,6 +12987,10 @@ class HouseplanCard extends LitElement {
this._roomFill = r.settings?.fill_mode === 'glow'
? ''
: ((r.settings?.fill_mode as any) || '');
const rawCustom = r.settings?.custom_fill;
this._roomCustomFill = rawCustom && typeof rawCustom === 'object'
? customFillOf(rawCustom, spaceDisplayOf(this._curSpaceCfg).customFill)
: null;
this._roomTempSrc = r.settings?.temp_source || '';
this._roomHumSrc = r.settings?.hum_source || '';
this._roomNameScale = clampScale(r.settings?.name_scale);
@@ -12977,6 +13004,7 @@ class HouseplanCard extends LitElement {
private _roomSettingsFromDialog(): RoomCfg['settings'] {
const st: any = {};
if (this._roomFill) st.fill_mode = this._roomFill;
if (this._roomCustomFill) st.custom_fill = this._roomCustomFill;
if (this._roomTempSrc) st.temp_source = this._roomTempSrc;
if (this._roomHumSrc) st.hum_source = this._roomHumSrc;
if (this._roomNameScale !== 1) st.name_scale = this._roomNameScale;
@@ -13002,6 +13030,8 @@ class HouseplanCard extends LitElement {
const next: any = { ...previous };
if (this._roomFill) next.fill_mode = this._roomFill;
else delete next.fill_mode;
if (this._roomCustomFill) next.custom_fill = this._roomCustomFill;
else delete next.custom_fill;
if (previous.fill_mode === 'glow' && typeof previous.glow !== 'boolean') next.glow = true;
if (this._roomTempSrc) next.temp_source = this._roomTempSrc;
else delete next.temp_source;
@@ -14895,7 +14925,25 @@ class HouseplanCard extends LitElement {
°C
</span>`
: nothing}
</label>`,
</label>
${v === 'custom' && d.fillMode === 'custom'
? html`<div class="colorrow gsrow">
<span class="gsl">${this._t('space.custom_fill')}</span>
<hp-color-opacity
.label=${this._t('space.custom_fill')}
.opacityLabel=${this._t('space.opacity')}
.color=${(d.customFill || DEFAULT_CUSTOM_FILL).c}
.opacity=${(d.customFill || DEFAULT_CUSTOM_FILL).a}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this._spaceDialog = { ...d, customFill: { c: e.detail.color, a: e.detail.opacity } };
}}></hp-color-opacity>
${d.customFill
? html`<button class="btn ghost" type="button"
@click=${() => (this._spaceDialog = { ...d, customFill: null })}>
${this._t('btn.reset')}</button>`
: nothing}
</div>`
: nothing}`,
)}
<label class="srcrow">
${this._boolInput(d.glowEnabled, (checked) => {
@@ -15018,6 +15066,9 @@ class HouseplanCard extends LitElement {
private _renderRoomDialog(): TemplateResult {
const edit = !!this._roomEditId;
const canSaveNew = !!this._areaSel || !!this._nameSel.trim();
const spaceDisplay = spaceDisplayOf(this._curSpaceCfg);
const effectiveFill = this._roomFill || spaceDisplay.fill;
const customFill = this._roomCustomFill || spaceDisplay.customFill;
// the free-areas list must include the edited room's CURRENT area
const areas = [...this._freeAreas];
if (edit && this._areaSel && !areas.some((a) => a.area_id === this._areaSel)) {
@@ -15055,6 +15106,26 @@ class HouseplanCard extends LitElement {
<span>${this._t(k as any)}</span>
</label>`,
)}
${effectiveFill === 'custom'
? html`<div class="colorrow gsrow">
<span class="gsl">${this._roomCustomFill
? this._t('room.custom_fill_own') : this._t('room.custom_fill_space')}</span>
<hp-color-opacity
.label=${this._roomCustomFill
? this._t('room.custom_fill_own') : this._t('room.custom_fill_space')}
.opacityLabel=${this._t('space.opacity')}
.color=${customFill.c}
.opacity=${customFill.a}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this._roomCustomFill = { c: e.detail.color, a: e.detail.opacity };
}}></hp-color-opacity>
${this._roomCustomFill
? html`<button class="btn ghost" type="button" @click=${() => {
this._roomCustomFill = null;
}}>${this._t('btn.reset')}</button>`
: nothing}
</div>`
: nothing}
${this._renderRoomSource('temp')}
${this._renderRoomSource('hum')}
+4
View File
@@ -327,6 +327,8 @@
"fill.none": "None",
"fill.lqi": "Zigbee signal",
"fill.light": "Lights",
"fill.custom": "Custom color",
"space.custom_fill": "Custom fill color",
"space.source_file": "I have a floor-plan image",
"space.source_draw": "No image — I'll outline rooms by hand",
"space.orientation": "Canvas",
@@ -493,6 +495,8 @@
"room.settings_section": "Room settings (override the space)",
"room.fill_label": "Fill in THIS room",
"fill.inherit": "As the space",
"room.custom_fill_space": "Space color",
"room.custom_fill_own": "Room color",
"room.temp_src_label": "Temperature source",
"room.hum_src_label": "Humidity source",
"room.src_average": "Average over the room's sensors (default)",
+4
View File
@@ -327,6 +327,8 @@
"fill.none": "Нет",
"fill.lqi": "По силе зигби-сигнала",
"fill.light": "По освещению",
"fill.custom": "Свой цвет",
"space.custom_fill": "Свой цвет заливки",
"space.source_file": "У меня есть картинка плана",
"space.source_draw": "Нет подложки — нарисую комнаты вручную",
"space.orientation": "Холст",
@@ -493,6 +495,8 @@
"room.settings_section": "Настройки комнаты (переопределяют пространство)",
"room.fill_label": "Заливка в ЭТОЙ комнате",
"fill.inherit": "Как у пространства",
"room.custom_fill_space": "Цвет пространства",
"room.custom_fill_own": "Цвет комнаты",
"room.temp_src_label": "Источник температуры",
"room.hum_src_label": "Источник влажности",
"room.src_average": "Средняя по датчикам комнаты (по умолчанию)",
+39 -11
View File
@@ -820,8 +820,8 @@ export function normalizeDeviceDisplay(value: unknown): DeviceDisplayMode {
}
export const TAP_ACTIONS = ['info', 'more-info', 'toggle', 'run', 'cover'] as const;
/** Current space-level data fills. Legacy `glow` is read separately as overlay. */
export const SPACE_FILL_MODES = ['none', 'lqi', 'light', 'temp'] as const;
export const ROOM_FILL_MODES = ['none', 'lqi', 'light', 'temp'] as const;
export const SPACE_FILL_MODES = ['none', 'lqi', 'light', 'temp', 'custom'] as const;
export const ROOM_FILL_MODES = ['none', 'lqi', 'light', 'temp', 'custom'] as const;
export const TOGGLE_SAFE_DOMAINS = new Set(['light', 'switch', 'fan', 'humidifier', 'cover', 'valve']);
@@ -1229,7 +1229,8 @@ export function subst(s: string, vars?: Record<string, string | number>): string
// ---------------- room fills & colors ----------------
export type RoomFillMode = 'none' | 'lqi' | 'light' | 'temp' | 'glow';
/** Effective data/static fill. Legacy persisted `glow` is projected separately. */
export type RoomFillMode = typeof SPACE_FILL_MODES[number];
/** Per-space display settings with their defaults resolved. */
export interface SpaceDisplay {
@@ -1238,6 +1239,8 @@ export interface SpaceDisplay {
color: string; // hex like #3ea6ff
opacity: number; // 0..1 — applied to borders, names and fills
fill: RoomFillMode;
/** Resolved space-level custom fill; rooms may override it. */
customFill: FillColorEntry;
/** Independent light-source overlay; legacy fill_mode:'glow' projects here. */
glow: boolean;
tempMin: number; // comfort range lower bound, °C
@@ -1273,6 +1276,29 @@ export const DEFAULT_ROOM_COLOR = '#55606c';
export const DEFAULT_ROOM_OPACITY = 0.55;
export const DEFAULT_TEMP_MIN = 20;
export const DEFAULT_TEMP_MAX = 25;
export const DEFAULT_CUSTOM_FILL: FillColorEntry = { c: '#607d8b', a: 0.18 };
/** Safe read boundary for a persisted custom fill. Invalid legacy/future data
* is projected to a known color without silently rewriting the config. */
export function customFillOf(value: unknown, fallback: FillColorEntry = DEFAULT_CUSTOM_FILL): FillColorEntry {
const raw = value && typeof value === 'object' ? value as any : null;
const alpha = raw?.a;
return {
c: safeStoredColor(raw?.c, fallback.c),
a: typeof alpha === 'number' && Number.isFinite(alpha)
? Math.min(1, Math.max(0, alpha)) : fallback.a,
};
}
/** Effective custom fill: explicit room value -> space value -> product default. */
export function roomCustomFillOf(
spaceCustom: unknown,
room: { settings?: { custom_fill?: unknown } | null } | null | undefined,
): FillColorEntry {
const spaceFill = customFillOf(spaceCustom);
const own = room?.settings?.custom_fill;
return own && typeof own === 'object' ? customFillOf(own, spaceFill) : spaceFill;
}
/** Resolve a space's display settings; spaces without a plan default to visible markup. */
export function spaceDisplayOf(spaceCfg: any): SpaceDisplay {
@@ -1284,7 +1310,8 @@ export function spaceDisplayOf(spaceCfg: any): SpaceDisplay {
showNames: s.show_names ?? noPlan,
color: safeStoredColor(s.room_color, DEFAULT_ROOM_COLOR),
opacity: typeof s.room_opacity === 'number' ? Math.min(1, Math.max(0, s.room_opacity)) : DEFAULT_ROOM_OPACITY,
fill: ['lqi', 'light', 'temp'].includes(s.fill_mode) ? s.fill_mode : 'none',
fill: ['lqi', 'light', 'temp', 'custom'].includes(s.fill_mode) ? s.fill_mode : 'none',
customFill: customFillOf(s.custom_fill),
// Explicit new data wins. Reading an old config never writes it back.
glow: typeof s.glow_enabled === 'boolean' ? s.glow_enabled : legacyGlow,
tempMin: typeof s.temp_min === 'number' ? s.temp_min : DEFAULT_TEMP_MIN,
@@ -1333,7 +1360,7 @@ export interface FillColors {
temp_hot: FillColorEntry;
lqi_low: FillColorEntry;
lqi_high: FillColorEntry;
/** Glow mode: uniform "darkness" over every room + default light color. */
/** Glow darkness for rooms whose effective data/static fill is `none`. */
glow_base: FillColorEntry;
glow_light: FillColorEntry;
/** Thick-wall body fill (docs/WALL-THICKNESS.md); default opaque white. */
@@ -1391,7 +1418,9 @@ export function roomFillStyle(
tempMin: number,
tempMax: number,
colors: FillColors,
customFill: FillColorEntry = DEFAULT_CUSTOM_FILL,
): FillColorEntry | null {
if (mode === 'custom') return customFillOf(customFill);
if (mode === 'lqi') {
if (lqi == null) return null;
const t = (lqi - 40) / 140;
@@ -1430,7 +1459,7 @@ export function roomFillStyle(
export interface ResolvedRoomFill {
color: string;
opacity: number;
mode: RoomFillMode;
mode: RoomFillMode | 'glow';
}
export function resolveEffectiveRoomFill(
@@ -1441,10 +1470,9 @@ export function resolveEffectiveRoomFill(
tempMin: number,
tempMax: number,
colors: FillColors,
customFill: FillColorEntry = DEFAULT_CUSTOM_FILL,
): ResolvedRoomFill | null {
const entry = mode === 'glow'
? colors.glow_base
: roomFillStyle(mode, lqi, lights, temp, tempMin, tempMax, colors);
const entry = roomFillStyle(mode, lqi, lights, temp, tempMin, tempMax, colors, customFill);
return entry ? { color: entry.c, opacity: entry.a, mode } : null;
}
@@ -1905,8 +1933,8 @@ export function roomFillModeOf(
room: { settings?: { fill_mode?: string | null } | null } | null | undefined,
): RoomFillMode {
const o = room?.settings?.fill_mode;
const inherited = spaceFill === 'glow' ? 'none' : spaceFill;
return o === 'none' || o === 'lqi' || o === 'light' || o === 'temp' ? o : inherited;
return o === 'none' || o === 'lqi' || o === 'light' || o === 'temp' || o === 'custom'
? o : spaceFill;
}
/** Effective room Glow, independently of its data fill. */
+22 -22
View File
@@ -8,7 +8,10 @@
*/
import { html, svg, nothing, type TemplateResult } from 'lit';
import { buildDevices, areaLqi, areaTemp, resolvedLightSources, resolvedLightState } from './devices';
import { spaceDisplayOf, roomFillStyle, fillColorsOf, roomFillModeOf, roomGlowOf, stageBgOf, paperRoomShapes } from './logic';
import {
spaceDisplayOf, fillColorsOf, roomFillModeOf, roomGlowOf,
roomCustomFillOf, resolveEffectiveRoomFill, stageBgOf, paperRoomShapes,
} from './logic';
import {
wallBodiesUnionPath, paperRoomShapesWithWalls, wallBodyNeedsSolid, type WallEntry,
} from './wall-thickness';
@@ -149,7 +152,7 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
const vb = [fr.x, fr.y, fr.w, fr.h];
const roomShapes = space.rooms
.filter((r) => r.area || disp.showBorders || roomFillModeOf(disp.fill, r) === 'light')
.filter((r) => r.area || disp.showBorders || roomFillModeOf(disp.fill, r) !== 'none')
.map((r) => {
let cls = 'room ' + (space.bg ? 'overlay' : 'yard');
let style = '';
@@ -159,26 +162,21 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
cls += ' styled';
const parts = [`--room-stroke:${disp.color}`, `--room-stroke-op:${disp.showBorders ? disp.opacity : 0}`];
// fill rendered exactly as configured on the full card (snapshot of current states)
const fillC = fill === 'light'
? roomFillStyle(
'light', null,
resolvedLightState(resolvedLightSources(planHass, spaceDevs, r)),
null, disp.tempMin, disp.tempMax, fillColorsOf(o.cfg?.settings),
)
: r.area
? roomFillStyle(
fill,
fill === 'lqi' ? areaLqi(planHass, spaceDevs, r.area) : null,
'none',
fill === 'temp' ? areaTemp(planHass, spaceDevs, r.area) : null,
disp.tempMin,
disp.tempMax,
fillColorsOf(o.cfg?.settings),
)
: null;
const fillC = resolveEffectiveRoomFill(
fill,
fill === 'lqi' && r.area ? areaLqi(planHass, spaceDevs, r.area) : null,
fill === 'light'
? resolvedLightState(resolvedLightSources(planHass, spaceDevs, r))
: 'none',
fill === 'temp' && r.area ? areaTemp(planHass, spaceDevs, r.area) : null,
disp.tempMin,
disp.tempMax,
colors,
roomCustomFillOf(disp.customFill, r),
);
if (fillC) {
cls += ' filled';
parts.push(`--room-fill:${fillC.c}`, `--room-fill-op:${fillC.a.toFixed(3)}`);
parts.push(`--room-fill:${fillC.color}`, `--room-fill-op:${fillC.opacity.toFixed(3)}`);
} else {
parts.push('--room-fill:transparent', '--room-fill-op:0');
}
@@ -200,7 +198,7 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
// The compact card intentionally has no live radial pools, but it shares the
// exact independent data/base projection with the full plan.
const glowBaseShapes = space.rooms
.filter((room) => roomGlowOf(disp.glow, room))
.filter((room) => roomGlowOf(disp.glow, room) && roomFillModeOf(disp.fill, room) === 'none')
.map((room) => room.poly
? svg`<polygon class="glow-base" aria-hidden="true" pointer-events="none"
data-room-id=${room.id || nothing}
@@ -306,7 +304,9 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
preserveAspectRatio="none" />`
: nothing}
${roomShapes}
<g class="glow-base-layer" aria-hidden="true" pointer-events="none">${glowBaseShapes}</g>
${glowBaseShapes.length
? svg`<g class="glow-base-layer" aria-hidden="true" pointer-events="none">${glowBaseShapes}</g>`
: nothing}
<g class="room-svg-labels" pointer-events="none">${staticSvgLabels}</g>
${wallUnion
? svg`<g class="wallbodies" style="--room-stroke:${wallStroke}">
+3 -1
View File
@@ -9,7 +9,9 @@ export interface RoomCfg {
settings?: {
/** Fill override; unset = inherit the space fill mode. */
/** `glow` is a permanent legacy read token. */
fill_mode?: 'none' | 'lqi' | 'light' | 'temp' | 'glow' | null;
fill_mode?: 'none' | 'lqi' | 'light' | 'temp' | 'custom' | 'glow' | null;
/** Explicit custom fill; absent/null uses the space custom fill. */
custom_fill?: { c: string; a: number } | null;
/** Independent per-room Glow override; null/absent inherits the space. */
glow?: boolean | null;
/** 'device:<id>' or 'entity:<eid>'; unset = average over the room sensors. */