v1.49.0: content-fit zoom, swipe animation, wording, and the v1.47.0 review

Owner's batch:
- zoom now opens on what is DRAWN (rooms + 5% margin) for spaces with no
  background image; with one the image is the plan and still fits whole. A small
  plan on the square canvas no longer opens as a speck.
- swiping between spaces, and the kiosk carousel, slide sideways; honours
  prefers-reduced-motion.
- the room settings button reads 'Room settings' and lightens on hover.
- 'curation' is filtering everywhere: UI strings, docs, code.

Checked the yard while I was there: its drawing sits off-centre because it was
drawn that way — before the migration x spanned 0.12..0.54 with 0.12 and 0.46 of
margin. The migration added 0.1465 on each side, symmetrically. Content-fit zoom
makes it moot anyway.

From the v1.47.0 review:
- HP-1470-02: the picker let you delete the plan you had just selected — it is
  not in the stored config yet, so the server rightly called it free, and the
  save then stored a url with no file. The button is disabled, and since two
  clients can do this in either order, config/set now verifies every internal
  plan url against the disk under the write lock and answers .
  External and legacy urls are not ours to police.
- HP-1470-01: growth is bounded at the door rather than by deleting old files —
  that mistake cost real plans twice. check_quota refuses an upload that would
  push the store past 256 MB / 200 plans (1 GB / 1000 attachments) or leave less
  than 512 MB free. The plan list is capped at 60 newest with a total, and
  thumbnails load lazily.
- HP-1470-03: picking a saved plan waited for nothing and stored a fallback
  ratio when the signature had not arrived — a square plan came out stretched.
  It waits for the signature, binds the result to the dialog that asked, and the
  dialog preview is signed too.
- report §5: the last lifecycle comments still described age-based collection.

Not released yet — the owner asked for a release once the batch is done.
This commit is contained in:
Matysh
2026-07-28 22:44:09 +03:00
parent e1e730560d
commit f5e6c0318d
27 changed files with 638 additions and 140 deletions
+36 -1
View File
@@ -57,7 +57,42 @@ export function spaceModels(cfg: ServerConfig | null): SpaceModel[] {
});
}
/** Bounding rectangle of a room (rect or polygon) in render units. */
/**
* What the plan actually occupies, padded by `pad` of the larger side.
*
* The canvas is a square big enough for any house, so a small hand-drawn plan
* used to open as a speck in the middle of it. Zooming to the CONTENT instead
* of the canvas is what people expect — but only when there is no background
* image: with one, the image is the plan, and cropping to the rooms would hide
* the parts of it nobody has outlined yet.
*
* Returns null when there is nothing drawn, so the caller keeps the full canvas.
*/
export function contentBounds(
space: SpaceModel, pad = 0.05,
): { x: number; y: number; w: number; h: number } | null {
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
const add = (x: number, y: number) => {
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
};
for (const r of space.rooms || []) {
if (r.poly) for (const p of r.poly) add(p[0], p[1]);
else if (r.x != null && r.y != null) {
add(r.x, r.y);
add(r.x + (r.w || 0), r.y + (r.h || 0));
}
}
if (minX > maxX || minY > maxY) return null;
const m = Math.max(maxX - minX, maxY - minY) * pad;
const x = minX - m, y = minY - m;
return { x, y, w: (maxX - minX) + m * 2, h: (maxY - minY) + m * 2 };
}
/** Bounding rectangle of a room (rect or polygon) in render units. *//** Bounding rectangle of a room (rect or polygon) in render units. */
export function roomBounds(r: RoomCfg): { x: number; y: number; w: number; h: number } {
if (r.poly && r.poly.length) {
const xs = r.poly.map((p) => p[0]);