mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
feat v1.38.2: restore last space and editor mode across reloads
- LS_NAV stores {space, mode}; restored in setConfig from the cached
config, with a retry after the live config when the cache was stale;
deep-link hash wins; edit modes restored for admins only
- UX-MODES updated (owner reversed the 'always start in View' rule)
- smoke_nav_persist.mjs; TESTING/CHANGELOG 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.38.1"
|
VERSION = "1.38.2"
|
||||||
|
|
||||||
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.38.1"
|
"version": "1.38.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { launch } from './serve.mjs';
|
||||||
|
const { page, browser } = await launch();
|
||||||
|
const res = await page.evaluate(async () => {
|
||||||
|
const out = {};
|
||||||
|
const c = window.__card;
|
||||||
|
// выбрать f2 и редактор устройств
|
||||||
|
c._space = 'garden'; c._setMode('devices'); await c.updateComplete;
|
||||||
|
const nav = JSON.parse(localStorage.getItem('houseplan_card_nav_v1'));
|
||||||
|
out.saved = nav.space === 'garden' && nav.mode === 'devices';
|
||||||
|
// пересоздать карточку (эмуляция закрытия вкладки; кэш конфига в LS уже есть)
|
||||||
|
const c2 = document.createElement('houseplan-card');
|
||||||
|
c2.setConfig({ type: 'custom:houseplan-card' });
|
||||||
|
c2.hass = c.hass;
|
||||||
|
document.body.appendChild(c2);
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
c2.hass = { ...c.hass };
|
||||||
|
await c2.updateComplete;
|
||||||
|
out.spaceRestored = c2._space === 'garden';
|
||||||
|
out.modeRestored = c2._mode === 'devices';
|
||||||
|
// хэш приоритетнее сохранённого
|
||||||
|
location.hash = '#space=f1';
|
||||||
|
const c3 = document.createElement('houseplan-card');
|
||||||
|
c3.setConfig({ type: 'custom:houseplan-card' });
|
||||||
|
c3.hass = c.hass;
|
||||||
|
document.body.appendChild(c3);
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
out.hashWins = c3._space === 'f1';
|
||||||
|
location.hash = '';
|
||||||
|
c2.remove(); c3.remove();
|
||||||
|
// вернуть исходное
|
||||||
|
c._setMode('view'); c._space = 'f1'; c._saveNav(); await c.updateComplete;
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(res, null, 1));
|
||||||
|
await browser.close();
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+9
-9
File diff suppressed because one or more lines are too long
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.38.2 — 2026-07-23
|
||||||
|
- The card now **remembers where you were**: the selected space and the active
|
||||||
|
editor survive navigation and closing the tab (localStorage; edit modes are
|
||||||
|
restored for admins only). A `#space=` deep link still wins over the saved
|
||||||
|
space. This reverses the earlier "always start in View" rule — the owner's
|
||||||
|
call.
|
||||||
|
|
||||||
## v1.38.1 — 2026-07-23 (tap action cleanup, right-click more-info)
|
## v1.38.1 — 2026-07-23 (tap action cleanup, right-click more-info)
|
||||||
- The per-device action is now one of three: **Device card** (renamed from
|
- The per-device action is now one of three: **Device card** (renamed from
|
||||||
"Info card", the default), HA more-info, Toggle. The confusing "As the card
|
"Info card", the default), HA more-info, Toggle. The confusing "As the card
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
|||||||
(explicit ripple color still wins); off/white lights unchanged [auto]
|
(explicit ripple color still wins); off/white lights unchanged [auto]
|
||||||
- [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any
|
- [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any
|
||||||
display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static
|
display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static
|
||||||
|
- [ ] Nav persistence (v1.38.2): closing/reopening the tab restores the last
|
||||||
|
space AND editor mode (admins; localStorage); a #space= deep link beats
|
||||||
|
the saved space; a stale cache without the saved space retries after the
|
||||||
|
live config loads [auto]
|
||||||
- [ ] Tap action cleanup + right click (v1.38.1): the per-device action list
|
- [ ] Tap action cleanup + right click (v1.38.1): the per-device action list
|
||||||
has three options (Device card / HA more-info / Toggle), no "card
|
has three options (Device card / HA more-info / Toggle), no "card
|
||||||
default" — the card editor's global tap option is gone and ignored;
|
default" — the card editor's global tap option is gone and ignored;
|
||||||
|
|||||||
+5
-2
@@ -17,8 +17,11 @@ NO tab (since v1.30.2). The Background editor (v1.33.0) manages a purely visual
|
|||||||
decor layer (lines/rects/ovals/text in `space.decor`, drawn under the rooms,
|
decor layer (lines/rects/ovals/text in `space.decor`, drawn under the rooms,
|
||||||
inert everywhere outside its editor).
|
inert everywhere outside its editor).
|
||||||
|
|
||||||
- **View** is the implicit default state: no editor tab is active. It is the only
|
- **View** is the implicit default state: no editor tab is active. Since
|
||||||
state after every load (edit modes are never restored across reloads).
|
v1.38.2 the last space AND editor mode are restored across reloads
|
||||||
|
(localStorage, admins only for edit modes) — closing and reopening the tab
|
||||||
|
lands you where you were (owner's decision, reversing the earlier
|
||||||
|
"never restore" rule).
|
||||||
- Activating an editor tab highlights it and opens that editor's bottom toolbar
|
- Activating an editor tab highlights it and opens that editor's bottom toolbar
|
||||||
(both editors have one since v1.30.2). The toolbar and the active tab each
|
(both editors have one since v1.30.2). The toolbar and the active tab each
|
||||||
carry an **X** that closes the editor back to View; re-clicking the active tab
|
carry an **X** that closes the editor back to View; re-clicking the active tab
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.38.1",
|
"version": "1.38.2",
|
||||||
"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",
|
||||||
|
|||||||
+33
-1
@@ -32,10 +32,11 @@ import './space-card';
|
|||||||
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.38.1';
|
const CARD_VERSION = '1.38.2';
|
||||||
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';
|
||||||
|
const LS_NAV = 'houseplan_card_nav_v1'; // last space + editor mode (owner: restore where you were)
|
||||||
const NORM_W = 1000; // width of the render space for normalized configs
|
const NORM_W = 1000; // width of the render space for normalized configs
|
||||||
|
|
||||||
const GRID_N = 240; // grid points across the plan width (half the previous step; old nodes are a subset of the new ones, positions are preserved)
|
const GRID_N = 240; // grid points across the plan width (half the previous step; old nodes are a subset of the new ones, positions are preserved)
|
||||||
@@ -204,6 +205,7 @@ class HouseplanCard extends LitElement {
|
|||||||
} | null = null;
|
} | null = null;
|
||||||
private _keyHandler = (e: KeyboardEvent) => this._onKey(e);
|
private _keyHandler = (e: KeyboardEvent) => this._onKey(e);
|
||||||
private _hashApplied = false;
|
private _hashApplied = false;
|
||||||
|
private _navApplied = false; // the saved space was restored (or the user navigated)
|
||||||
/** Deep-link: read `#space=<id>` from the URL (used by embedded houseplan-space-card). */
|
/** Deep-link: read `#space=<id>` from the URL (used by embedded houseplan-space-card). */
|
||||||
private _hashSpace(): string {
|
private _hashSpace(): string {
|
||||||
const m = /(?:^|[#&])space=([^&]+)/.exec(window.location.hash || '');
|
const m = /(?:^|[#&])space=([^&]+)/.exec(window.location.hash || '');
|
||||||
@@ -386,9 +388,13 @@ class HouseplanCard extends LitElement {
|
|||||||
this._layout = c.layout || {};
|
this._layout = c.layout || {};
|
||||||
this._serverStorage = true;
|
this._serverStorage = true;
|
||||||
const hs = this._hashSpace();
|
const hs = this._hashSpace();
|
||||||
|
const nav = this._savedNav();
|
||||||
if (hs && this._model.find((sp) => sp.id === hs)) { this._space = hs; this._hashApplied = true; }
|
if (hs && this._model.find((sp) => sp.id === hs)) { this._space = hs; this._hashApplied = true; }
|
||||||
|
else if (nav?.space && this._model.find((sp) => sp.id === nav.space)) { this._space = nav.space; this._navApplied = true; }
|
||||||
else if (config.default_floor) this._space = config.default_floor;
|
else 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;
|
else if (!this._model.find((sp) => sp.id === this._space)) this._space = this._model[0]?.id || this._space;
|
||||||
|
// reopenning the tab lands you in the same editor you left (admins only)
|
||||||
|
if (nav?.mode && nav.mode !== 'view' && this._canEdit) this._mode = nav.mode;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
@@ -550,9 +556,16 @@ class HouseplanCard extends LitElement {
|
|||||||
}, 'houseplan_config_updated');
|
}, 'houseplan_config_updated');
|
||||||
}
|
}
|
||||||
const hs = this._hashSpace();
|
const hs = this._hashSpace();
|
||||||
|
const nav = this._savedNav();
|
||||||
if (!this._hashApplied && hs && this._model.find((s) => s.id === hs)) {
|
if (!this._hashApplied && hs && this._model.find((s) => s.id === hs)) {
|
||||||
this._space = hs;
|
this._space = hs;
|
||||||
this._hashApplied = true;
|
this._hashApplied = true;
|
||||||
|
} else if (nav?.space && !this._navApplied && !this._hashApplied
|
||||||
|
&& this._model.find((s) => s.id === nav.space)) {
|
||||||
|
// the cached config might have been stale (no such space) — retry once
|
||||||
|
// the live config is in
|
||||||
|
this._space = nav.space;
|
||||||
|
this._navApplied = true;
|
||||||
} else if (this._norm && !this._model.find((s) => s.id === this._space)) {
|
} else if (this._norm && !this._model.find((s) => s.id === this._space)) {
|
||||||
this._space = this._model[0]?.id || this._space;
|
this._space = this._model[0]?.id || this._space;
|
||||||
}
|
}
|
||||||
@@ -1187,6 +1200,22 @@ class HouseplanCard extends LitElement {
|
|||||||
return roomEdges(sp?.rooms || []).map((s) => [s[0] * NORM_W, s[1] * H, s[2] * NORM_W, s[3] * H]);
|
return roomEdges(sp?.rooms || []).map((s) => [s[0] * NORM_W, s[1] * H, s[2] * NORM_W, s[3] * H]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _savedNav(): { space?: string; mode?: 'view' | 'plan' | 'devices' | 'decor' } | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem(LS_NAV) || 'null');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _saveNav(): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LS_NAV, JSON.stringify({ space: this._space, mode: this._mode }));
|
||||||
|
} catch {
|
||||||
|
/* private mode etc. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _setMode(mode: 'view' | 'plan' | 'devices' | 'decor'): void {
|
private _setMode(mode: 'view' | 'plan' | 'devices' | 'decor'): void {
|
||||||
if (this._mode === mode) return;
|
if (this._mode === mode) return;
|
||||||
if ((mode === 'plan' || mode === 'decor') && !this._norm) {
|
if ((mode === 'plan' || mode === 'decor') && !this._norm) {
|
||||||
@@ -1206,6 +1235,7 @@ class HouseplanCard extends LitElement {
|
|||||||
this._decorDraft = null;
|
this._decorDraft = null;
|
||||||
this._decorSel = null;
|
this._decorSel = null;
|
||||||
this._decorTool = 'select';
|
this._decorTool = 'select';
|
||||||
|
this._saveNav();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3062,7 +3092,9 @@ class HouseplanCard extends LitElement {
|
|||||||
@click=${() => {
|
@click=${() => {
|
||||||
this._space = s.id;
|
this._space = s.id;
|
||||||
this._selId = null;
|
this._selId = null;
|
||||||
|
this._navApplied = true;
|
||||||
this._restoreZoom();
|
this._restoreZoom();
|
||||||
|
this._saveNav();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${s.title}${this._norm && this._canEdit
|
${s.title}${this._norm && this._canEdit
|
||||||
|
|||||||
Reference in New Issue
Block a user