mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
The puck teleports on view changes; the trail never outruns it
Two owner reports. One: zoom, space switch or a tab return animated the puck's left/top through the viewport change — it looked like the robot driving across the whole plan. The view signature now forces the jump class for that render, and a visibilitychange listener covers returning to the browser tab. Two: a trail segment appeared the moment new telemetry arrived, ahead of the still-gliding icon — the self-recorded trail now lags exactly one point behind (the previous target is what the puck has just reached), and an integration path is trimmed of its live tail while moving.
This commit is contained in:
File diff suppressed because one or more lines are too long
+10
-2
@@ -56,8 +56,15 @@ const out = await page.evaluate(async () => {
|
||||
await c.updateComplete; await new Promise((r) => setTimeout(r, 30));
|
||||
}
|
||||
const trail = sr().querySelector('.vactrail polyline');
|
||||
o.trailDrawn = !!trail && trail.getAttribute('points').split(' ').length >= 3;
|
||||
// the trail LAGS one point behind the puck: after 3 telemetry points only
|
||||
// the first two are drawn — a segment never outruns the icon (owner)
|
||||
o.trailDrawn = !!trail && trail.getAttribute('points').split(' ').length === 2;
|
||||
o.trailScaledByMatrix = !!trail && trail.getAttribute('points').startsWith('300.0,400.0');
|
||||
o.trailBehindPuck = !!trail && !trail.getAttribute('points').includes('450.0,550.0');
|
||||
// a zoom/view change TELEPORTS the puck instead of gliding across the plan
|
||||
c._applyView(1.35); c.requestUpdate(); await c.updateComplete;
|
||||
o.zoomTeleports = sr().querySelector('.vacpuck').classList.contains('jump');
|
||||
c._applyView(1); c.requestUpdate(); await c.updateComplete;
|
||||
// casing pair: dark halo + light core with identical geometry — the trail
|
||||
// must survive any room fill underneath (owner 2026-07-31)
|
||||
const tcase = sr().querySelector('.vactrail .case');
|
||||
@@ -137,7 +144,8 @@ checkAll(out, {
|
||||
dockedNoPuck: true, baseMarkerThere: true, puckAppears: true, puckRound: true,
|
||||
puckPlateMatchesDev: true, puck80pct: true,
|
||||
noWedge: true, iconCentred: true, baseStaysDuringCleaning: true, trailDrawn: true,
|
||||
trailScaledByMatrix: true, trailCasing: true, trailToggleOff: true,
|
||||
trailScaledByMatrix: true, trailBehindPuck: true, zoomTeleports: true,
|
||||
trailCasing: true, trailToggleOff: true,
|
||||
puckGoneWhenDocked: true, trailLingers: true, hiddenNoPuck: true,
|
||||
unknownMapNoPuck: true,
|
||||
wizardDevFound: true, wizardBanner: true, wizardSavedMatrix: true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Vendored
+13
-13
File diff suppressed because one or more lines are too long
+38
-9
@@ -347,7 +347,18 @@ class HouseplanCard extends LitElement {
|
||||
private _kioskDialog = false;
|
||||
/** live-vacuum runtime per marker: RAW robot coords (matrix applied at render) */
|
||||
private _vacRt = new Map<string, { trail: VacPt[]; lastKey: string; lastTs: number;
|
||||
moving: boolean; jump: boolean; endedTs: number }>();
|
||||
moving: boolean; jump: boolean; endedTs: number; lastPos: VacPt | null }>();
|
||||
/** view signature of the previous vacuum render: a changed view (zoom, pan,
|
||||
space switch) or a tab return must TELEPORT the puck — animating left/top
|
||||
through a viewport change reads as «едет через весь план» (owner). */
|
||||
private _vacViewKey = '';
|
||||
private _vacJumpOnce = false;
|
||||
private _vacVisHandler = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
this._vacJumpOnce = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
};
|
||||
private _vacCal: { markerId: string; source: string; mapId: string;
|
||||
pairs: Array<[VacPt, VacPt]> } | null = null;
|
||||
private _kioskDots = false;
|
||||
@@ -428,6 +439,7 @@ class HouseplanCard extends LitElement {
|
||||
};
|
||||
|
||||
public connectedCallback(): void {
|
||||
document.addEventListener('visibilitychange', this._vacVisHandler);
|
||||
super.connectedCallback();
|
||||
window.addEventListener('keydown', this._keyHandler);
|
||||
// signatures expire (24 h); refresh well before that on long-lived screens
|
||||
@@ -440,6 +452,7 @@ class HouseplanCard extends LitElement {
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
document.removeEventListener('visibilitychange', this._vacVisHandler);
|
||||
window.removeEventListener('keydown', this._keyHandler);
|
||||
clearInterval(this._cycleTimer);
|
||||
clearTimeout(this._kioskDotsTimer);
|
||||
@@ -4350,11 +4363,17 @@ class HouseplanCard extends LitElement {
|
||||
const tele = readVacTelemetry(this.hass.states[src]?.attributes);
|
||||
let rt = this._vacRt.get(d.id);
|
||||
if (!rt) {
|
||||
rt = { trail: [], lastKey: '', lastTs: 0, moving: false, jump: false, endedTs: 0 };
|
||||
rt = { trail: [], lastKey: '', lastTs: 0, moving: false, jump: false, endedTs: 0, lastPos: null };
|
||||
this._vacRt.set(d.id, rt);
|
||||
}
|
||||
if (moving && !rt.moving) rt.trail = []; // a fresh run clears the old trail
|
||||
if (!moving && rt.moving) rt.endedTs = Date.now();
|
||||
if (moving && !rt.moving) { rt.trail = []; rt.lastPos = null; } // a fresh run
|
||||
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;
|
||||
}
|
||||
rt.moving = moving;
|
||||
const pos = tele?.pos;
|
||||
if (moving && pos) {
|
||||
@@ -4365,9 +4384,11 @@ class HouseplanCard extends LitElement {
|
||||
rt.jump = rt.lastTs > 0 && now - rt.lastTs > VAC_TELEPORT_GAP_MS;
|
||||
rt.lastKey = key;
|
||||
rt.lastTs = now;
|
||||
if (d.marker?.vacuum?.trail !== false && !tele!.path) {
|
||||
rt.trail = pushTrailPoint(rt.trail, [pos.x, pos.y], 40);
|
||||
}
|
||||
// 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);
|
||||
rt.lastPos = [pos.x, pos.y];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4520,6 +4541,10 @@ class HouseplanCard extends LitElement {
|
||||
/** 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;
|
||||
const viewKey = this._space + '|' + view.x + '|' + view.y + '|' + view.w + '|' + view.h;
|
||||
const jumpAll = this._vacJumpOnce || viewKey !== this._vacViewKey;
|
||||
this._vacViewKey = viewKey;
|
||||
this._vacJumpOnce = false;
|
||||
const pucks: TemplateResult[] = [];
|
||||
const trails: TemplateResult[] = [];
|
||||
for (const d of devs) {
|
||||
@@ -4535,7 +4560,11 @@ class HouseplanCard extends LitElement {
|
||||
const lingering = !moving && rt?.endedTs && Date.now() - rt.endedTs < VAC_TRAIL_LINGER_MS;
|
||||
// trail: integration path wins (it predates the card being opened)
|
||||
if (d.marker?.vacuum?.trail !== false && (moving || lingering)) {
|
||||
const raw: VacPt[] = tele.path || rt?.trail || [];
|
||||
// an integration path ends at the CURRENT target — trim the live
|
||||
// tail while moving so the line never runs ahead of the puck
|
||||
const raw: VacPt[] = tele.path
|
||||
? (moving && tele.path.length > 1 ? tele.path.slice(0, -1) : tele.path)
|
||||
: rt?.trail || [];
|
||||
if (raw.length > 1) {
|
||||
const ptsStr = raw.map(([x, y]) => {
|
||||
const [cx, cy] = applyAffine(matrix, x, y);
|
||||
@@ -4554,7 +4583,7 @@ class HouseplanCard extends LitElement {
|
||||
const stale = rt && rt.lastTs > 0 && Date.now() - rt.lastTs > VAC_STALE_MS;
|
||||
const icon = d.marker?.icon || d.icon || 'mdi:robot-vacuum';
|
||||
pucks.push(html`<div
|
||||
class="vacpuck ${rt?.jump ? 'jump' : ''} ${stale ? 'stale' : ''}"
|
||||
class="vacpuck ${rt?.jump || jumpAll ? 'jump' : ''} ${stale ? 'stale' : ''}"
|
||||
style="left:${left}%;top:${top}%"
|
||||
title=${d.name}
|
||||
@click=${(e: Event) => { e.stopPropagation(); const ve = this._vacEntity(d); if (ve) this._openMoreInfo(ve); }}>
|
||||
|
||||
Reference in New Issue
Block a user