feat v1.8.3: мгновенный рендер плана и значков из кэша конфига (stale-while-revalidate), свежие данные в фоне

This commit is contained in:
JB
2026-07-05 11:13:45 +03:00
parent 5f6eee64d9
commit ccc04e135b
7 changed files with 53 additions and 12 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ PLANS_DIR = "houseplan/plans" # относительно каталога ко
FILES_URL = "/houseplan_files/files"
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.8.2"
VERSION = "1.8.3"
DEFAULT_CONFIG: dict = {
"spaces": [],
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -15,5 +15,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/justbusiness/houseplan-card/issues",
"requirements": [],
"version": "1.8.2"
"version": "1.8.3"
}
+4 -4
View File
File diff suppressed because one or more lines are too long
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## v1.8.3 — 2026-07-04 (мгновенный старт: кэш конфига, данные в фоне)
- Иконки и план появлялись с задержкой — карточка ждала ответа сервера (WS `config/get`
+ ретраи прогрева соединения), до этого модель пространств была пуста.
- Внедрён кэш «stale-while-revalidate»: снимок серверного конфига + rev + раскладки
сохраняется в `localStorage['houseplan_card_cfg_v1']`. В `setConfig` (синхронно, до прихода
hass) он восстанавливается — план и значки рисуются сразу из кэша (проверено: setConfig
наполняет модель за 0.2 мс без сервера), а свежие данные догружаются в фоне и обновляют
карточку. Живые состояния/температуры/сигнал подтягиваются по мере прогрева hass.
- Кэш обновляется после каждой успешной загрузки, live-ресинка и сохранения позиций иконок,
поэтому при следующем открытии виден актуальный снимок. При недоступном сервере показывается
последний известный план вместо пустого онбординга.
## v1.8.2 — 2026-07-04 (векторно-чёткий зум через viewBox + полная ширина сцены)
- Зум переведён с CSS `transform: scale()` на манипуляцию `viewBox` SVG. Раньше слой
растрировался и потом масштабировался — при приближении всё «мылилось». Теперь браузер
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "houseplan-card",
"version": "1.8.2",
"version": "1.8.3",
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
"license": "MIT",
"type": "module",
+30 -1
View File
@@ -13,8 +13,9 @@ import {
} from './logic';
import './editor';
const CARD_VERSION = '1.8.2';
const CARD_VERSION = '1.8.3';
const LS_KEY = 'houseplan_card_layout_v1';
const LS_CFG = 'houseplan_card_cfg_v1'; // кэш серверного конфига+раскладки для мгновенного рендера
const LS_ZOOM = 'houseplan_card_zoom_v1';
const NORM_W = 1000; // ширина рендер-пространства для нормированных конфигов
@@ -283,6 +284,31 @@ class HouseplanCard extends LitElement {
} catch {
this._zoomBySpace = {};
}
// мгновенный рендер из кэша (stale-while-revalidate): показать план и значки
// сразу, не дожидаясь ответа сервера — свежие данные догрузятся в фоне.
try {
const c = JSON.parse(localStorage.getItem(LS_CFG) || 'null');
if (c && c.config && Array.isArray(c.config.spaces)) {
this._serverCfg = c.config;
this._cfgRev = c.rev || 0;
this._layout = c.layout || {};
this._serverStorage = true;
if (config.default_floor) this._space = config.default_floor;
else if (!this._model.find((sp) => sp.id === this._space)) this._space = this._model[0]?.id || this._space;
}
} catch {
/* ignore */
}
}
/** Сохранить снимок конфига+раскладки в localStorage для мгновенного старта. */
private _cacheSnapshot(): void {
if (!this._serverCfg) return;
try {
localStorage.setItem(LS_CFG, JSON.stringify({ config: this._serverCfg, rev: this._cfgRev, layout: this._layout }));
} catch {
/* ignore */
}
}
public getCardSize(): number {
@@ -384,6 +410,7 @@ class HouseplanCard extends LitElement {
if (this._norm && !this._model.find((s) => s.id === this._space)) {
this._space = this._model[0]?.id || this._space;
}
this._cacheSnapshot();
this._restoreZoom();
} catch (e) {
// не последняя попытка — молча ждём следующего обновления hass (прогрев WS)
@@ -409,6 +436,7 @@ class HouseplanCard extends LitElement {
const cfg = resp?.config;
this._serverCfg = cfg && Array.isArray(cfg.spaces) ? cfg : null;
this._cfgRev = resp?.rev || 0;
this._cacheSnapshot();
this._regSignature = '';
this._maybeRebuildDevices();
this.requestUpdate();
@@ -431,6 +459,7 @@ class HouseplanCard extends LitElement {
.callWS({ type: 'houseplan/layout/update', device_id: id, pos })
.catch((e: any) => this._showToast('Не удалось сохранить позицию: ' + (e?.message || e)));
}
this._cacheSnapshot();
} else {
localStorage.setItem(LS_KEY, JSON.stringify(this._layout));
}