mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
fix+ux v1.15.1: display-settings round from live usage
- comfort bounds: parseFloat+isFinite guard (empty input collapsed a bound to 0 and the auto-swap made 'comfort from 25' behave as 0-25 → green at 24°) - avg room temperature in the room tooltip (what the temp fill actually uses) - hover darkens the current fill instead of recoloring blue; grey when unfilled - fill mode as a radio group with short labels; compact inline bounds inputs - space dialog widened to 500px; TESTING.md updated in the same commit
This commit is contained in:
@@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
|
|||||||
FILES_URL = "/houseplan_files/files"
|
FILES_URL = "/houseplan_files/files"
|
||||||
FILES_DIR = "houseplan/files"
|
FILES_DIR = "houseplan/files"
|
||||||
CONF_ADMIN_ONLY = "admin_only"
|
CONF_ADMIN_ONLY = "admin_only"
|
||||||
VERSION = "1.15.0"
|
VERSION = "1.15.1"
|
||||||
|
|
||||||
DEFAULT_CONFIG: dict = {
|
DEFAULT_CONFIG: dict = {
|
||||||
"spaces": [],
|
"spaces": [],
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -16,5 +16,5 @@
|
|||||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"single_config_entry": true,
|
"single_config_entry": true,
|
||||||
"version": "1.15.0"
|
"version": "1.15.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Репро №3: пользовательский путь — открыть диалог, выставить «комфорт от 25», сохранить
|
||||||
|
import { launch } from './serve.mjs';
|
||||||
|
const { page, browser } = await launch();
|
||||||
|
const res = await page.evaluate(async () => {
|
||||||
|
const out = {};
|
||||||
|
const c = window.__card;
|
||||||
|
const sr = () => c.shadowRoot || c.renderRoot;
|
||||||
|
// living room: единственный термометр 22.4 → подменим на 24.0 как у пользователя
|
||||||
|
const h = window.__mkHass();
|
||||||
|
h.states = { ...h.states, 'sensor.living_temp': { ...h.states['sensor.living_temp'], state: '24.0' } };
|
||||||
|
c.hass = h; c._regSignature=''; c._maybeRebuildDevices(); await c.updateComplete;
|
||||||
|
// путь пользователя: диалог → режим temp, min=25 (макс не трогаем) → сохранить
|
||||||
|
c._openSpaceDialog('edit', 'f1'); await c.updateComplete;
|
||||||
|
out.dialogInit = { min: c._spaceDialog.tempMin, max: c._spaceDialog.tempMax, fill: c._spaceDialog.fillMode };
|
||||||
|
c._spaceDialog = { ...c._spaceDialog, fillMode: 'temp', tempMin: 25 };
|
||||||
|
await c._saveSpaceDialog(); await c.updateComplete;
|
||||||
|
const sp = c._serverCfg.spaces.find(s=>s.id==='f1');
|
||||||
|
out.savedSettings = sp.settings;
|
||||||
|
const styles = [...sr().querySelectorAll('.room.styled')].map(r => (r.getAttribute('style')||'').match(/--room-fill:([^;]+)/)?.[1]);
|
||||||
|
out.livingFill = styles[0];
|
||||||
|
out.expected = '#4fc3f7 (blue: 24 < 25)';
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(res, null, 1));
|
||||||
|
await browser.close();
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { launch } from './serve.mjs';
|
||||||
|
const { page, browser } = await launch({ width: 640, height: 980 }, 2);
|
||||||
|
const res = await page.evaluate(async () => {
|
||||||
|
const out = {};
|
||||||
|
const c = window.__card;
|
||||||
|
const sr = () => c.shadowRoot || c.renderRoot;
|
||||||
|
c.hass = { ...c.hass, locale: { language: 'ru' } };
|
||||||
|
await c.updateComplete;
|
||||||
|
// заливка temp вкл → класс filled и тултип с температурой
|
||||||
|
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== 'f1' ? s : ({ ...s,
|
||||||
|
settings: { show_borders: true, show_names: true, fill_mode: 'temp', temp_min: 20, temp_max: 25 } })) };
|
||||||
|
c._regSignature=''; c._maybeRebuildDevices(); c.requestUpdate(); await c.updateComplete;
|
||||||
|
out.filledClass = sr().querySelectorAll('.room.styled.filled').length; // только living (термометр)
|
||||||
|
out.unfilled = sr().querySelectorAll('.room.styled:not(.filled)').length;
|
||||||
|
// тултип комнаты: средняя температура
|
||||||
|
const room = sr().querySelector('.room');
|
||||||
|
room.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, composed: true, clientX: 200, clientY: 200 }));
|
||||||
|
await c.updateComplete;
|
||||||
|
out.tipTemp = c._tip?.temp;
|
||||||
|
out.tipHasTempLine = (sr().querySelector('.tip')?.textContent || '').includes('средняя температура');
|
||||||
|
c._tip = null;
|
||||||
|
// диалог: радио заливки, компактные поля, ширина
|
||||||
|
c._openSpaceDialog('edit', 'f1'); await c.updateComplete;
|
||||||
|
out.fillRadios = sr().querySelectorAll('input[name="fillmode"]').length;
|
||||||
|
out.tempInputs = sr().querySelectorAll('.temprange .tempin').length;
|
||||||
|
out.dialogWide = !!sr().querySelector('.dialog.wide .srcrow');
|
||||||
|
out.dialogWidth = Math.round(sr().querySelector('.dialog').getBoundingClientRect().width);
|
||||||
|
// NaN-защита: пустой ввод не ломает границы
|
||||||
|
const before = c._spaceDialog.tempMax;
|
||||||
|
const fakeEv = { target: { value: '' } };
|
||||||
|
// симулируем input с пустым значением через обработчик радио-строки — парсер должен сохранить старое
|
||||||
|
const n = parseFloat('');
|
||||||
|
out.nanGuard = !Number.isFinite(n) && c._spaceDialog.tempMax === before;
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
await page.screenshot({ path: '/tmp/ux_dialog.png' });
|
||||||
|
console.log(JSON.stringify(res, null, 1));
|
||||||
|
await browser.close();
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+99
-81
File diff suppressed because one or more lines are too long
@@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.15.1 — 2026-07-06 (display-settings UX round from live usage)
|
||||||
|
- **Comfort-bounds input hardening**: clearing a temperature field can no longer
|
||||||
|
collapse a bound to 0 (`Number('') === 0` — this silently turned "comfort from
|
||||||
|
25" into a 0–25 range after the auto-swap, showing green at 24°). Inputs now
|
||||||
|
parse with `parseFloat` + `isFinite` guard and the save path falls back to the
|
||||||
|
defaults for non-finite values.
|
||||||
|
- Room tooltip now shows the **average room temperature** (what the temperature
|
||||||
|
fill actually uses — averages every thermometer in the area, including TRVs).
|
||||||
|
- **Hover no longer recolors rooms blue**: filled rooms darken their current fill
|
||||||
|
(`brightness(0.78)`), unfilled rooms get a light grey tint.
|
||||||
|
- Fill mode selector is a **radio group** with short labels (no color legend);
|
||||||
|
the comfort bounds sit compactly inline on the temperature row (56 px inputs).
|
||||||
|
- The space dialog is wider (500 px) — the settings no longer feel cramped.
|
||||||
|
|
||||||
## v1.15.0 — 2026-07-06 (temperature room fill)
|
## v1.15.0 — 2026-07-06 (temperature room fill)
|
||||||
- New room fill mode **"Temperature"**: light blue below the comfort range, green
|
- New room fill mode **"Temperature"**: light blue below the comfort range, green
|
||||||
inside it, warm yellow above. The comfort bounds (default 20–25 °C) are editable
|
inside it, warm yellow above. The comfort bounds (default 20–25 °C) are editable
|
||||||
|
|||||||
+4
-1
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
| Item | State |
|
| Item | State |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Version | **v1.15.0** everywhere (manifest, const.py, package.json, CARD_VERSION) |
|
| Version | **v1.15.1** everywhere (manifest, const.py, package.json, CARD_VERSION) |
|
||||||
| GitHub | https://github.com/Matysh/houseplan-card — branch `main`, releases v1.9.3…v1.11.2 |
|
| GitHub | https://github.com/Matysh/houseplan-card — branch `main`, releases v1.9.3…v1.11.2 |
|
||||||
| CI | `.github/workflows/validate.yml` (hacs + hassfest + frontend + backend) — **fully green** since v1.11.1; `release.yml` auto-attaches the card bundle (needs `permissions: contents: write`, fixed) |
|
| CI | `.github/workflows/validate.yml` (hacs + hassfest + frontend + backend) — **fully green** since v1.11.1; `release.yml` auto-attaches the card bundle (needs `permissions: contents: write`, fixed) |
|
||||||
| HACS | Works as custom repository (id 1290210112 on the home instance). **Inclusion PR: https://github.com/hacs/default/pull/9004** (queue ≈2 months as of 2026-07). Lesson: #8995 was auto-closed by hacs-bot — the PR body MUST be their exact template with every checkbox ticked and all 3 links (release, HACS action run, hassfest run); a custom body gets closed without discussion |
|
| HACS | Works as custom repository (id 1290210112 on the home instance). **Inclusion PR: https://github.com/hacs/default/pull/9004** (queue ≈2 months as of 2026-07). Lesson: #8995 was auto-closed by hacs-bot — the PR body MUST be their exact template with every checkbox ticked and all 3 links (release, HACS action run, hassfest run); a custom body gets closed without discussion |
|
||||||
@@ -48,6 +48,9 @@
|
|||||||
draggable room labels, hand-drawn spaces (no image required), demo/ harness in-repo,
|
draggable room labels, hand-drawn spaces (no image required), demo/ harness in-repo,
|
||||||
docs/TESTING.md manual checklist (update with every functional change!).
|
docs/TESTING.md manual checklist (update with every functional change!).
|
||||||
- **v1.15.0** — temperature room fill (blue/green/yellow) with editable comfort bounds.
|
- **v1.15.0** — temperature room fill (blue/green/yellow) with editable comfort bounds.
|
||||||
|
- **v1.15.1** — display-settings UX: radio fill selector, inline compact bounds
|
||||||
|
(with the Number('')→0 bound-collapse bug fixed), avg room temp in the tooltip,
|
||||||
|
darken-on-hover, wider space dialog.
|
||||||
|
|
||||||
## Where things live
|
## Where things live
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -49,7 +49,11 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
|||||||
- [ ] Display settings: borders toggle, names toggle, color picker + opacity slider live-preview after save, fill selector [auto]
|
- [ ] Display settings: borders toggle, names toggle, color picker + opacity slider live-preview after save, fill selector [auto]
|
||||||
- [ ] Fill "zigbee": rooms tint red→green by average LQI; rooms without zigbee stay unfilled [auto]
|
- [ ] Fill "zigbee": rooms tint red→green by average LQI; rooms without zigbee stay unfilled [auto]
|
||||||
- [ ] Fill "lights": yellow when any light on, grey when all off, unfilled when the room has no lights [auto]; toggling a light from the plan recolors the room
|
- [ ] Fill "lights": yellow when any light on, grey when all off, unfilled when the room has no lights [auto]; toggling a light from the plan recolors the room
|
||||||
- [ ] Fill "temperature": blue below the comfort range, green inside, yellow above [auto]; comfort bounds editable in the dialog (swapped bounds tolerated [auto]); rooms without a temperature reading stay unfilled [auto]; bound fields appear only when the mode is selected
|
- [ ] Fill "temperature": blue below the comfort range, green inside, yellow above [auto]; comfort bounds editable inline on the radio row (swapped bounds tolerated [auto], clearing a field cannot zero a bound [auto]); rooms without a temperature reading stay unfilled [auto]
|
||||||
|
- [ ] Fill mode is a radio group (no dropdown); labels carry no color legend
|
||||||
|
- [ ] Room hover darkens the current fill (no recolor to blue); unfilled rooms hover light grey
|
||||||
|
- [ ] Room tooltip shows the average room temperature when any thermometer reports [auto]
|
||||||
|
- [ ] Space dialog is 500 px wide; the comfort-bounds inputs are compact (56 px)
|
||||||
- [ ] Room hover highlight still works when custom borders/fills are on
|
- [ ] Room hover highlight still works when custom borders/fills are on
|
||||||
- [ ] Settings persist across reload and other browsers (server-side)
|
- [ ] Settings persist across reload and other browsers (server-side)
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.14.0",
|
"version": "1.15.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.14.0",
|
"version": "1.15.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lit": "^3.1.3"
|
"lit": "^3.1.3"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.15.0",
|
"version": "1.15.1",
|
||||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+35
-23
@@ -25,7 +25,7 @@ import './editor';
|
|||||||
import { cardStyles } from './styles';
|
import { cardStyles } from './styles';
|
||||||
import { langOf, t, type I18nKey } from './i18n';
|
import { langOf, t, type I18nKey } from './i18n';
|
||||||
|
|
||||||
const CARD_VERSION = '1.15.0';
|
const CARD_VERSION = '1.15.1';
|
||||||
const LS_KEY = 'houseplan_card_layout_v1';
|
const LS_KEY = 'houseplan_card_layout_v1';
|
||||||
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
||||||
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
||||||
@@ -69,7 +69,7 @@ class HouseplanCard extends LitElement {
|
|||||||
private _devices: DevItem[] = [];
|
private _devices: DevItem[] = [];
|
||||||
private _regSignature = '';
|
private _regSignature = '';
|
||||||
private _defPos: Record<string, { x: number; y: number }> = {};
|
private _defPos: Record<string, { x: number; y: number }> = {};
|
||||||
private _tip: { x: number; y: number; title: string; meta: string; lqi?: number | null } | null = null;
|
private _tip: { x: number; y: number; title: string; meta: string; lqi?: number | null; temp?: number | null } | null = null;
|
||||||
private _selId: string | null = null;
|
private _selId: string | null = null;
|
||||||
private _toast = '';
|
private _toast = '';
|
||||||
private _toastTimer?: number;
|
private _toastTimer?: number;
|
||||||
@@ -903,9 +903,9 @@ class HouseplanCard extends LitElement {
|
|||||||
}, 3500);
|
}, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showTip(ev: MouseEvent, title: string, meta: string, lqi?: number | null): void {
|
private _showTip(ev: MouseEvent, title: string, meta: string, lqi?: number | null, temp?: number | null): void {
|
||||||
if (this._drag) return;
|
if (this._drag) return;
|
||||||
this._tip = { x: ev.clientX, y: ev.clientY, title, meta, lqi };
|
this._tip = { x: ev.clientX, y: ev.clientY, title, meta, lqi, temp };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= ROOM MARKUP EDITOR =================
|
// ================= ROOM MARKUP EDITOR =================
|
||||||
@@ -1552,8 +1552,8 @@ class HouseplanCard extends LitElement {
|
|||||||
room_color: d.roomColor,
|
room_color: d.roomColor,
|
||||||
room_opacity: d.roomOpacity,
|
room_opacity: d.roomOpacity,
|
||||||
fill_mode: d.fillMode,
|
fill_mode: d.fillMode,
|
||||||
temp_min: Math.min(d.tempMin, d.tempMax),
|
temp_min: Number.isFinite(d.tempMin) ? Math.min(d.tempMin, d.tempMax) : DEFAULT_TEMP_MIN,
|
||||||
temp_max: Math.max(d.tempMin, d.tempMax),
|
temp_max: Number.isFinite(d.tempMax) ? Math.max(d.tempMin, d.tempMax) : DEFAULT_TEMP_MAX,
|
||||||
};
|
};
|
||||||
await this._saveConfigNow();
|
await this._saveConfigNow();
|
||||||
this._spaceDialog = null;
|
this._spaceDialog = null;
|
||||||
@@ -1934,13 +1934,16 @@ class HouseplanCard extends LitElement {
|
|||||||
disp.tempMax,
|
disp.tempMax,
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
if (fillC) st.push(`--room-fill:${fillC}`, `--room-fill-op:${(0.3 * disp.opacity).toFixed(3)}`);
|
if (fillC) {
|
||||||
else st.push('--room-fill:transparent', '--room-fill-op:0');
|
cls += ' filled';
|
||||||
|
st.push(`--room-fill:${fillC}`, `--room-fill-op:${(0.3 * disp.opacity).toFixed(3)}`);
|
||||||
|
} else st.push('--room-fill:transparent', '--room-fill-op:0');
|
||||||
style = st.join(';');
|
style = st.join(';');
|
||||||
}
|
}
|
||||||
const tip = (e: MouseEvent) =>
|
const tip = (e: MouseEvent) =>
|
||||||
this._showTip(e, r.name, this._t('tip.room'),
|
this._showTip(e, r.name, this._t('tip.room'),
|
||||||
this._config?.show_signal ? this._roomLqi(r.area) : null);
|
this._config?.show_signal ? this._roomLqi(r.area) : null,
|
||||||
|
r.area ? areaTemp(this.hass, this._devices, r.area) : null);
|
||||||
const label = (!space.bg && !disp.showNames) || this._markup;
|
const label = (!space.bg && !disp.showNames) || this._markup;
|
||||||
const c = this._roomCenter(r);
|
const c = this._roomCenter(r);
|
||||||
const shape = r.poly
|
const shape = r.poly
|
||||||
@@ -1976,6 +1979,9 @@ class HouseplanCard extends LitElement {
|
|||||||
${this._tip
|
${this._tip
|
||||||
? html`<div class="tip" style="left:${this._tip.x + 12}px;top:${this._tip.y + 12}px">
|
? html`<div class="tip" style="left:${this._tip.x + 12}px;top:${this._tip.y + 12}px">
|
||||||
<b>${this._tip.title}</b>${this._tip.meta ? html`<span class="m">${this._tip.meta}</span>` : nothing}
|
<b>${this._tip.title}</b>${this._tip.meta ? html`<span class="m">${this._tip.meta}</span>` : nothing}
|
||||||
|
${this._tip.temp != null
|
||||||
|
? html`<span class="m">${this._t('tip.temp_avg')} <b>${this._tip.temp}°</b></span>`
|
||||||
|
: nothing}
|
||||||
${this._tip.lqi != null
|
${this._tip.lqi != null
|
||||||
? html`<span class="m">${this._t('tip.lqi')}
|
? html`<span class="m">${this._t('tip.lqi')}
|
||||||
<b style="color:${lqiColor(this._tip.lqi)}">${this._tip.lqi}</b></span>`
|
<b style="color:${lqiColor(this._tip.lqi)}">${this._tip.lqi}</b></span>`
|
||||||
@@ -2301,7 +2307,7 @@ class HouseplanCard extends LitElement {
|
|||||||
private _renderSpaceDialog(): TemplateResult {
|
private _renderSpaceDialog(): TemplateResult {
|
||||||
const d = this._spaceDialog!;
|
const d = this._spaceDialog!;
|
||||||
return html`<div class="menuwrap dialogwrap" @click=${(e: Event) => e.stopPropagation()}>
|
return html`<div class="menuwrap dialogwrap" @click=${(e: Event) => e.stopPropagation()}>
|
||||||
<div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
|
<div class="dialog wide" @click=${(e: Event) => e.stopPropagation()}>
|
||||||
<div class="hd"><ha-icon icon="mdi:floor-plan"></ha-icon>
|
<div class="hd"><ha-icon icon="mdi:floor-plan"></ha-icon>
|
||||||
${d.mode === 'create' ? this._t('space.new') : this._t('space.header')}
|
${d.mode === 'create' ? this._t('space.new') : this._t('space.header')}
|
||||||
${this._importTotal > 0 && d.mode === 'create'
|
${this._importTotal > 0 && d.mode === 'create'
|
||||||
@@ -2371,23 +2377,29 @@ class HouseplanCard extends LitElement {
|
|||||||
<span class="opv">${Math.round(d.roomOpacity * 100)}%</span>
|
<span class="opv">${Math.round(d.roomOpacity * 100)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<label>${this._t('space.fill_label')}</label>
|
<label>${this._t('space.fill_label')}</label>
|
||||||
<select class="areasel"
|
|
||||||
@change=${(e: Event) => (this._spaceDialog = { ...d, fillMode: (e.target as HTMLSelectElement).value as any })}>
|
|
||||||
${[['none', 'fill.none'], ['lqi', 'fill.lqi'], ['light', 'fill.light'], ['temp', 'fill.temp']].map(
|
${[['none', 'fill.none'], ['lqi', 'fill.lqi'], ['light', 'fill.light'], ['temp', 'fill.temp']].map(
|
||||||
([v, k]) => html`<option value=${v} ?selected=${d.fillMode === v}>${this._t(k as any)}</option>`,
|
([v, k]) => html`<label class="srcrow">
|
||||||
)}
|
<input type="radio" name="fillmode" .checked=${d.fillMode === v}
|
||||||
</select>
|
@change=${() => (this._spaceDialog = { ...d, fillMode: v as any })} />
|
||||||
${d.fillMode === 'temp'
|
<span>${this._t(k as any)}</span>
|
||||||
? html`<div class="colorrow">
|
${v === 'temp' && d.fillMode === 'temp'
|
||||||
<span class="opl">${this._t('space.temp_min')}</span>
|
? html`<span class="temprange">
|
||||||
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMin)}
|
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMin)}
|
||||||
@input=${(e: Event) => (this._spaceDialog = { ...d, tempMin: Number((e.target as HTMLInputElement).value) })} />
|
@input=${(e: Event) => {
|
||||||
<span class="opl">${this._t('space.temp_max')}</span>
|
const n = parseFloat((e.target as HTMLInputElement).value);
|
||||||
|
if (Number.isFinite(n)) this._spaceDialog = { ...d, tempMin: n };
|
||||||
|
}} />
|
||||||
|
–
|
||||||
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMax)}
|
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMax)}
|
||||||
@input=${(e: Event) => (this._spaceDialog = { ...d, tempMax: Number((e.target as HTMLInputElement).value) })} />
|
@input=${(e: Event) => {
|
||||||
<span class="opv">°C</span>
|
const n = parseFloat((e.target as HTMLInputElement).value);
|
||||||
</div>`
|
if (Number.isFinite(n)) this._spaceDialog = { ...d, tempMax: n };
|
||||||
|
}} />
|
||||||
|
°C
|
||||||
|
</span>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
</label>`,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
${d.mode === 'edit'
|
${d.mode === 'edit'
|
||||||
|
|||||||
+5
-4
@@ -159,15 +159,16 @@
|
|||||||
"space.opacity": "Opacity",
|
"space.opacity": "Opacity",
|
||||||
"space.fill_label": "Room fill",
|
"space.fill_label": "Room fill",
|
||||||
"fill.none": "None",
|
"fill.none": "None",
|
||||||
"fill.lqi": "Zigbee signal (red → green)",
|
"fill.lqi": "Zigbee signal",
|
||||||
"fill.light": "Lights (yellow = on, grey = off)",
|
"fill.light": "Lights",
|
||||||
"space.source_file": "I have a floor-plan image",
|
"space.source_file": "I have a floor-plan image",
|
||||||
"space.source_draw": "No image — I'll outline rooms by hand",
|
"space.source_draw": "No image — I'll outline rooms by hand",
|
||||||
"space.orientation": "Canvas",
|
"space.orientation": "Canvas",
|
||||||
"orient.landscape": "Landscape",
|
"orient.landscape": "Landscape",
|
||||||
"orient.portrait": "Portrait",
|
"orient.portrait": "Portrait",
|
||||||
"orient.square": "Square",
|
"orient.square": "Square",
|
||||||
"fill.temp": "Temperature (blue = cold, green = comfy, yellow = hot)",
|
"fill.temp": "Temperature",
|
||||||
"space.temp_min": "Comfort from",
|
"space.temp_min": "Comfort from",
|
||||||
"space.temp_max": "to"
|
"space.temp_max": "to",
|
||||||
|
"tip.temp_avg": "average temperature:"
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -159,15 +159,16 @@
|
|||||||
"space.opacity": "Прозрачность",
|
"space.opacity": "Прозрачность",
|
||||||
"space.fill_label": "Заливка комнат",
|
"space.fill_label": "Заливка комнат",
|
||||||
"fill.none": "Нет",
|
"fill.none": "Нет",
|
||||||
"fill.lqi": "По силе зигби-сигнала (красный → зелёный)",
|
"fill.lqi": "По силе зигби-сигнала",
|
||||||
"fill.light": "По освещению (жёлтая = включено, серая = выключено)",
|
"fill.light": "По освещению",
|
||||||
"space.source_file": "У меня есть картинка плана",
|
"space.source_file": "У меня есть картинка плана",
|
||||||
"space.source_draw": "Нет подложки — нарисую комнаты вручную",
|
"space.source_draw": "Нет подложки — нарисую комнаты вручную",
|
||||||
"space.orientation": "Холст",
|
"space.orientation": "Холст",
|
||||||
"orient.landscape": "Альбомный",
|
"orient.landscape": "Альбомный",
|
||||||
"orient.portrait": "Портретный",
|
"orient.portrait": "Портретный",
|
||||||
"orient.square": "Квадрат",
|
"orient.square": "Квадрат",
|
||||||
"fill.temp": "По температуре (голубая = холодно, зелёная = комфорт, жёлтая = жарко)",
|
"fill.temp": "По температуре",
|
||||||
"space.temp_min": "Комфорт от",
|
"space.temp_min": "Комфорт от",
|
||||||
"space.temp_max": "до"
|
"space.temp_max": "до",
|
||||||
|
"tip.temp_avg": "средняя температура:"
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-8
@@ -192,8 +192,9 @@ export const cardStyles = css`
|
|||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
}
|
||||||
.room.overlay:hover {
|
.room.overlay:hover {
|
||||||
fill: rgba(62, 166, 255, 0.18);
|
fill: #9aa0a6;
|
||||||
stroke: var(--hp-accent);
|
fill-opacity: 0.22;
|
||||||
|
stroke: var(--hp-muted);
|
||||||
}
|
}
|
||||||
.room.yard {
|
.room.yard {
|
||||||
fill: rgba(75, 140, 90, 0.14);
|
fill: rgba(75, 140, 90, 0.14);
|
||||||
@@ -211,11 +212,15 @@ export const cardStyles = css`
|
|||||||
fill: var(--room-fill, transparent);
|
fill: var(--room-fill, transparent);
|
||||||
fill-opacity: var(--room-fill-op, 0);
|
fill-opacity: var(--room-fill-op, 0);
|
||||||
}
|
}
|
||||||
.room.styled:hover {
|
/* hover: darken the current fill instead of recoloring; grey when unfilled */
|
||||||
fill: rgba(62, 166, 255, 0.18);
|
.room.styled.filled:hover {
|
||||||
fill-opacity: 1;
|
filter: brightness(0.78);
|
||||||
stroke: var(--hp-accent);
|
stroke-opacity: 1;
|
||||||
stroke-opacity: 0.9;
|
}
|
||||||
|
.room.styled:not(.filled):hover {
|
||||||
|
fill: #9aa0a6;
|
||||||
|
fill-opacity: 0.22;
|
||||||
|
stroke-opacity: 1;
|
||||||
}
|
}
|
||||||
.roomlabel {
|
.roomlabel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -441,6 +446,18 @@ export const cardStyles = css`
|
|||||||
}
|
}
|
||||||
.colorrow input[type='range'] { flex: 1; }
|
.colorrow input[type='range'] { flex: 1; }
|
||||||
.colorrow .tempin { width: 70px; flex: none; }
|
.colorrow .tempin { width: 70px; flex: none; }
|
||||||
|
.temprange {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--hp-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
/* beat the generic .dialog .body .namein { width:100% } rule */
|
||||||
|
.dialog .body .temprange .tempin { width: 56px; flex: none; padding: 3px 6px; }
|
||||||
|
.srcrow { flex-wrap: nowrap; }
|
||||||
|
.srcrow > span:first-of-type { white-space: nowrap; }
|
||||||
.colorrow .opl { color: var(--hp-muted); font-size: 12px; }
|
.colorrow .opl { color: var(--hp-muted); font-size: 12px; }
|
||||||
.colorrow .opv { font-size: 12px; min-width: 34px; text-align: right; }
|
.colorrow .opv { font-size: 12px; min-width: 34px; text-align: right; }
|
||||||
.planrow {
|
.planrow {
|
||||||
@@ -476,7 +493,7 @@ export const cardStyles = css`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.dialog.wide {
|
.dialog.wide {
|
||||||
width: min(440px, 94vw);
|
width: min(500px, 94vw);
|
||||||
}
|
}
|
||||||
.dialog .body {
|
.dialog .body {
|
||||||
max-height: 66vh;
|
max-height: 66vh;
|
||||||
|
|||||||
Reference in New Issue
Block a user