mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
The trail survives a page reload
The self-recorded points now snapshot into localStorage per marker (raw robot coords, so recalibration does not invalidate them). Restore is gated: fresher than the linger window, same map, and never into a run that started after the snapshot ended — the old trail must not leak into a new cleanup. The smoke simulates the reload by wiping the runtime map and asserts both the restore and the new-run discard.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -103,6 +103,26 @@ const out = await page.evaluate(async () => {
|
||||
c._regSignature = ''; c.requestUpdate(); await c.updateComplete;
|
||||
o.unknownMapNoPuck = !sr().querySelector('.vacpuck');
|
||||
|
||||
// ---- the trail survives a page reload (localStorage snapshot) ----
|
||||
// simulate a reload: wipe the runtime map, keep LS, tick again
|
||||
for (const [x, y] of [[930, 1130], [950, 1150], [990, 1190]]) {
|
||||
c.hass = { ...c.hass, states: { ...c.hass.states,
|
||||
'vacuum.robo': { state: 'cleaning', attributes: { friendly_name: 'Робот' } },
|
||||
'camera.robo_map': { state: 'idle', attributes: { vacuum_position: { x, y, a: 0 }, map_name: 'm1' } } } };
|
||||
await c.updateComplete;
|
||||
}
|
||||
o.trailPersisted = !!localStorage.getItem('hp_vactrail_e_vacuum_robo');
|
||||
const savedTrailLen = (JSON.parse(localStorage.getItem('hp_vactrail_e_vacuum_robo') || '{}').t || []).length;
|
||||
c._vacRt.clear(); // ← the reload
|
||||
c.hass = { ...c.hass, states: { ...c.hass.states } }; await c.updateComplete;
|
||||
const restored = c._vacRt.get('e_vacuum_robo');
|
||||
o.trailRestoredAfterReload = !!restored && restored.trail.length >= savedTrailLen && savedTrailLen >= 1;
|
||||
// …but a NEW run after the reload discards the old snapshot
|
||||
c._vacRt.clear();
|
||||
localStorage.setItem('hp_vactrail_e_vacuum_robo', JSON.stringify({ t: [[1, 1], [2, 2]], ts: Date.now(), moving: false, mapId: 'm1' }));
|
||||
c.hass = { ...c.hass, states: { ...c.hass.states } }; await c.updateComplete;
|
||||
o.newRunDropsOldTrail = (c._vacRt.get('e_vacuum_robo')?.trail || []).length === 0;
|
||||
|
||||
// ---- the fit panel (drag + corner-stretch) end to end ----
|
||||
c._regSignature = ''; c.requestUpdate(); await c.updateComplete;
|
||||
const dev = c._devices.find((x) => x.id === 'e_vacuum_robo');
|
||||
@@ -182,6 +202,7 @@ checkAll(out, {
|
||||
trailCasing: true, trailToggleOff: true,
|
||||
puckGoneWhenDocked: true, trailLingers: true, hiddenNoPuck: true,
|
||||
unknownMapNoPuck: true,
|
||||
trailPersisted: true, trailRestoredAfterReload: true, newRunDropsOldTrail: true,
|
||||
fitDevFound: true, fitOverlay: true, fitGhostRooms: true, fitLabels: true,
|
||||
fitHandles: true, fitHandleHittable: true, fitGhostHittable: true, fitBar: true, fitMirrorDefault: true, fitDragMoves: true,
|
||||
fitRotateKeepsCentre: true, fitCornerScales: true, fitSavedMatrix: true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Vendored
+19
-19
File diff suppressed because one or more lines are too long
+1
-1
@@ -84,7 +84,7 @@ marker is edited as usual.
|
||||
Douglas-Peucker thinning, one SVG polyline.
|
||||
- Style fixed, no options: cartography casing — a dark translucent halo under a light core. Neutral (pure black/white alphas) and readable over any room fill; blend modes were rejected: each has a blind luminance where the line vanishes, and mix-blend-mode is costly on old kiosk WebViews. Lifecycle:
|
||||
appears on cleaning start, lives until dock + 10 min, dissolves;
|
||||
`docked → cleaning` clears the old trail. Ephemeral, never stored.
|
||||
`docked → cleaning` clears the old trail. Ephemeral on the server; the raw points snapshot into localStorage per marker so a page reload mid-run keeps the whole trail (freshness-gated by the same linger window; a new run or another map discards the snapshot).
|
||||
- Marker option «Показывать след уборки», on by default where data
|
||||
exists.
|
||||
|
||||
|
||||
+31
-2
@@ -4371,15 +4371,34 @@ class HouseplanCard extends LitElement {
|
||||
let rt = this._vacRt.get(d.id);
|
||||
if (!rt) {
|
||||
rt = { trail: [], lastKey: '', lastTs: 0, moving: false, jump: false, endedTs: 0, lastPos: null };
|
||||
// a page reload must not eat the run's trail (owner request): the raw
|
||||
// points live in localStorage per marker, freshness-gated by the same
|
||||
// linger window the on-screen trail obeys
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem('hp_vactrail_' + d.id) || 'null');
|
||||
if (raw && Array.isArray(raw.t) && Date.now() - raw.ts < VAC_TRAIL_LINGER_MS
|
||||
&& raw.mapId === (tele?.mapId ?? raw.mapId)
|
||||
// moving now but the snapshot had already ended → that is a NEW
|
||||
// run, the old trail must not leak into it
|
||||
&& !(moving && !raw.moving)) {
|
||||
rt.trail = raw.t.filter((q: unknown) => Array.isArray(q) && q.length === 2);
|
||||
rt.moving = moving && raw.moving;
|
||||
rt.endedTs = raw.moving ? 0 : raw.ts;
|
||||
}
|
||||
} catch { /* corrupt snapshot — start clean */ }
|
||||
this._vacRt.set(d.id, rt);
|
||||
}
|
||||
if (moving && !rt.moving) { rt.trail = []; rt.lastPos = null; } // a fresh run
|
||||
if (moving && !rt.moving) {
|
||||
rt.trail = []; rt.lastPos = null; // a fresh run
|
||||
try { localStorage.removeItem('hp_vactrail_' + d.id); } catch { /* quota */ }
|
||||
}
|
||||
const wantTrail = d.marker?.vacuum?.trail !== false && !tele?.path;
|
||||
if (!moving && rt.moving) {
|
||||
rt.endedTs = Date.now();
|
||||
// the run is over: the puck has arrived everywhere it was going
|
||||
if (wantTrail && rt.lastPos) rt.trail = pushTrailPoint(rt.trail, rt.lastPos, 40);
|
||||
rt.lastPos = null;
|
||||
this._vacPersistTrail(d.id, rt, tele?.mapId);
|
||||
}
|
||||
rt.moving = moving;
|
||||
const pos = tele?.pos;
|
||||
@@ -4394,7 +4413,10 @@ class HouseplanCard extends LitElement {
|
||||
// the trail lags ONE point behind: when a new target arrives the puck
|
||||
// has just (visually) reached the previous one — a segment must never
|
||||
// outrun the icon (owner report 2026-07-31)
|
||||
if (wantTrail && rt.lastPos) rt.trail = pushTrailPoint(rt.trail, rt.lastPos, 40);
|
||||
if (wantTrail && rt.lastPos) {
|
||||
rt.trail = pushTrailPoint(rt.trail, rt.lastPos, 40);
|
||||
this._vacPersistTrail(d.id, rt, tele!.mapId);
|
||||
}
|
||||
rt.lastPos = [pos.x, pos.y];
|
||||
}
|
||||
}
|
||||
@@ -4652,6 +4674,13 @@ class HouseplanCard extends LitElement {
|
||||
@pointercancel=${(e: PointerEvent) => this._vacFitPointer(e, view)}>${rects}${dot}${handles}</svg>`;
|
||||
}
|
||||
|
||||
private _vacPersistTrail(id: string, rt: { trail: VacPt[]; moving: boolean }, mapId?: string): void {
|
||||
try {
|
||||
localStorage.setItem('hp_vactrail_' + id,
|
||||
JSON.stringify({ t: rt.trail, ts: Date.now(), moving: rt.moving, mapId: mapId ?? 'default' }));
|
||||
} catch { /* quota/private mode — the trail just will not survive a reload */ }
|
||||
}
|
||||
|
||||
/** Puck + trail for every live vacuum of the space. */
|
||||
private _renderVacuums(devs: DevItem[], view: { x: number; y: number; w: number; h: number }): TemplateResult | typeof nothing {
|
||||
if (this._markup || this._mode === 'decor') return nothing;
|
||||
|
||||
Reference in New Issue
Block a user