zoom: editor zoom is a working tool — leaving any editor restores the view-mode viewport

Entering an editor snapshots the view-mode zoom+center (per space);
leaving back to view brings it back and re-saves it to LS_ZOOM, so wheel
zoom done inside the editors no longer leaks into the viewing zoom.
Editor-to-editor switches and the per-space view zoom keep working as
before. Smoke: smoke_zoom_out.mjs grows the 1.6 -> editor 2.5 -> 1.6
scenario (zoom, center, LS), the no-touch no-jump case and the
space-switch persistence guard.
This commit is contained in:
Matysh
2026-08-01 11:04:19 +03:00
parent 92ea8fa03c
commit 6d16f69f38
5 changed files with 96 additions and 3 deletions
File diff suppressed because one or more lines are too long
+60
View File
@@ -48,6 +48,66 @@ out.floorIsHalf = await page.evaluate(() => { window.__card._resetZoom(); const
c._applyView(0.1); return c._zoom; }) === 0.4; // clamped at the floor
await page.evaluate(() => window.__card._resetZoom());
// -- editor zoom is a working tool, not the viewing intent ----------------
// view 1.6 → devices editor 2.5 → back to view: the pre-editor viewport
// (zoom AND center) comes back; the editor keeps its own zoom while open.
out.editorZoomNotSaved = await page.evaluate(async () => {
const c = window.__card;
const raf2 = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
const center = () => { const v = c._view; return [v.x + v.w / 2, v.y + v.h / 2]; };
c._setMode('view');
c._resetZoom();
c._zoomAt(10, 10, 1.6); c._saveZoom(); // off-center on purpose
const want = { zoom: c._zoom, c: center() };
c._setMode('devices'); await raf2();
c._zoomAt(10, 10, 2.5); c._saveZoom(); // off-center too: the center must not leak either
const editorZoomFree = Math.abs(c._zoom - 2.5) < 0.01; // zooming inside stays
c._setMode('view'); await raf2();
const got = { zoom: c._zoom, c: center() };
const ls = JSON.parse(localStorage.getItem('houseplan_card_zoom_v1') || '{}');
return {
editorZoomFree,
zoomRestored: Math.abs(got.zoom - want.zoom) < 0.01,
centerRestored: Math.hypot(got.c[0] - want.c[0], got.c[1] - want.c[1]) < 0.02,
lsRestored: Math.abs((ls[c._space] || 1) - want.zoom) < 0.01,
};
});
out.editorZoomFree = out.editorZoomNotSaved.editorZoomFree;
out.viewZoomRestored = out.editorZoomNotSaved.zoomRestored;
out.viewCenterRestored = out.editorZoomNotSaved.centerRestored;
out.viewZoomBackInLs = out.editorZoomNotSaved.lsRestored;
delete out.editorZoomNotSaved;
// -- editor entered and left without touching zoom: no jump ---------------
out.untouchedEditorNoJump = await page.evaluate(async () => {
const c = window.__card;
const raf2 = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
c._setMode('view');
c._resetZoom();
c._zoomAt(30, 30, 1.6); c._saveZoom();
const before = { ...c._view };
c._setMode('plan'); await raf2();
c._setMode('view'); await raf2();
const v = c._view;
return Math.abs(c._zoom - 1.6) < 0.01
&& Math.abs(v.x - before.x) < 0.005 && Math.abs(v.y - before.y) < 0.005
&& Math.abs(v.w - before.w) < 0.005 && Math.abs(v.h - before.h) < 0.005;
});
// -- the view zoom still survives a space switch, per space ---------------
out.viewZoomSurvivesSpaceSwitch = await page.evaluate(async () => {
const c = window.__card;
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
c._setMode('view');
c._resetZoom(); c._applyView(1.7); c._saveZoom();
c._slideTo('garden', 'left'); await wait(350);
const gardenGotOwnZoom = Math.abs(c._zoom - (JSON.parse(
localStorage.getItem('houseplan_card_zoom_v1') || '{}').garden || 1)) < 0.01;
c._slideTo('f1', 'right'); await wait(350);
return gardenGotOwnZoom && Math.abs(c._zoom - 1.7) < 0.01;
});
await page.evaluate(() => window.__card._resetZoom());
// -- devices outside rooms stretch the default frame ---------------------
out.devicesStretchFrame = await page.evaluate(() => {
const c = window.__card;
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+33
View File
@@ -246,6 +246,12 @@ class HouseplanCard extends LitElement {
private _zoom = 1;
private _view: { x: number; y: number; w: number; h: number } | null = null; // current SVG viewBox (vb coordinates)
private _zoomBySpace: Record<string, number> = {};
/**
* View-mode viewport remembered on entering an editor. Editor zoom is a
* working tool (zoom in to grab a vertex), not the user's intention for
* viewing — leaving any editor brings the view-mode viewport back.
*/
private _viewModeSnap: { space: string; zoom: number; cx?: number; cy?: number } | null = null;
private _pointers = new Map<number, { x: number; y: number }>();
private _panStart: { sx: number; sy: number; vx: number; vy: number } | null = null;
private _pinchStart: { dist: number; zoom: number } | null = null;
@@ -1857,6 +1863,17 @@ class HouseplanCard extends LitElement {
return;
}
const baseChanges = !this._spaceModel().bg && (mode === 'view') !== (this._mode === 'view');
if (this._mode === 'view' && mode !== 'view') {
// remember the view-mode viewport: whatever zooming happens inside the
// editors is a working tool, not what the user wants to see afterwards
const v = this._view;
this._viewModeSnap = {
space: this._space,
zoom: this._zoom,
cx: v ? v.x + v.w / 2 : undefined,
cy: v ? v.y + v.h / 2 : undefined,
};
}
this._mode = mode;
if (baseChanges) {
// refit against the new base: the editors measure from the full square,
@@ -1865,6 +1882,22 @@ class HouseplanCard extends LitElement {
this._zoom = 1;
this._view = null; // updated() refits on the next frame
}
if (mode === 'view') {
const snap = this._viewModeSnap;
this._viewModeSnap = null;
// restore only for the space the snapshot was taken in — after a space
// switch inside the editor the saved per-space zoom already applies
if (snap && snap.space === this._space) {
this._zoom = snap.zoom;
this._view = null;
requestAnimationFrame(() => {
if (!this._stageEl || this._mode !== 'view' || this._space !== snap.space) return;
this._applyView(snap.zoom, snap.cx, snap.cy);
this._saveZoom(); // editor wheel zoom wrote itself to LS_ZOOM — put the view zoom back
this.requestUpdate();
});
}
}
this._path = [];
this._cursorPt = null;
this._tool = 'draw';