mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
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.
94 lines
4.4 KiB
JavaScript
94 lines
4.4 KiB
JavaScript
// «Уже загруженные»: план, который не удаляется за ненадобностью, обязан быть
|
|
// находимым. Иначе обещание «отцепил — файл остался» неполноценно: вернуть его
|
|
// из карточки было нельзя, старый URL нигде не хранится (HP-1466-02).
|
|
// Заодно это единственный способ удалить план — явным действием.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
const { page, browser } = await launch({ width: 900, height: 1000 }, 1);
|
|
const res = await page.evaluate(async () => {
|
|
const out = {};
|
|
const c = window.__card;
|
|
const sr = () => c.shadowRoot || c.renderRoot;
|
|
const base = c.hass.callWS;
|
|
|
|
let serverPlans = [
|
|
{ name: 'f1.aaa.png', url: '/api/houseplan/content/plans/_/f1.aaa.png', size: 121335, modified: 2, used_by: [] },
|
|
{ name: 'f2.bbb.png', url: '/api/houseplan/content/plans/_/f2.bbb.png', size: 26931, modified: 1, used_by: ['2 этаж'] },
|
|
];
|
|
const deleted = [];
|
|
c.hass = { ...c.hass, callWS: async (m) => {
|
|
if (m.type === 'houseplan/plans/list') return { plans: serverPlans };
|
|
if (m.type === 'houseplan/plans/delete') {
|
|
const p = serverPlans.find((x) => x.name === m.name);
|
|
if (p?.used_by.length) { const e = new Error('in_use'); e.code = 'in_use'; throw e; }
|
|
deleted.push(m.name);
|
|
serverPlans = serverPlans.filter((x) => x.name !== m.name);
|
|
return { ok: true, removed: true };
|
|
}
|
|
if (m.type === 'houseplan/content/sign') {
|
|
const urls = {}; for (const p of m.paths) urls[p] = p + '?authSig=X'; return { urls };
|
|
}
|
|
return base(m);
|
|
} };
|
|
window.confirm = () => true;
|
|
|
|
// пространство без плана — как после отцепления
|
|
c._openSpaceDialog('edit', 'f1'); await c.updateComplete;
|
|
c._spaceDialog = { ...c._spaceDialog, source: 'file', planUrl: null, planFile: null };
|
|
await c.updateComplete;
|
|
out.saveBlockedWithoutPlan = !!sr().querySelector('.dialog .btn.on[disabled]');
|
|
|
|
// открываем список сохранённых
|
|
await c._toggleServerPlans();
|
|
await new Promise((r) => setTimeout(r, 60));
|
|
await c.updateComplete;
|
|
const rows = [...sr().querySelectorAll('.savedplan')];
|
|
out.listed = rows.length;
|
|
out.showsUsage = (rows[1]?.textContent || '').includes('2 этаж');
|
|
out.deleteDisabledForUsed = !!rows[1]?.querySelector('.btn.danger[disabled]');
|
|
out.deleteEnabledForFree = !rows[0]?.querySelector('.btn.danger[disabled]');
|
|
out.thumbnailSigned = (rows[0]?.querySelector('img')?.getAttribute('src') || '').includes('authSig=');
|
|
|
|
// выбираем свободный план — он подставляется в диалог
|
|
c._useServerPlan(serverPlans[0].url);
|
|
await new Promise((r) => setTimeout(r, 80));
|
|
await c.updateComplete;
|
|
out.picked = c._spaceDialog.planUrl === '/api/houseplan/content/plans/_/f1.aaa.png';
|
|
out.listClosed = !c._spaceDialog.pickSaved;
|
|
out.saveEnabledAfterPick = !sr().querySelector('.dialog .btn.on[disabled]');
|
|
|
|
// выбранный в этом же диалоге удалить нельзя: сохранение записало бы ссылку
|
|
// на несуществующий файл (HP-1470-02)
|
|
await c._toggleServerPlans();
|
|
await new Promise((r) => setTimeout(r, 60));
|
|
await c.updateComplete;
|
|
const rows2 = [...sr().querySelectorAll('.savedplan')];
|
|
const picked = rows2.find((r) => r.textContent.includes('f1.aaa.png'));
|
|
out.deleteDisabledForPicked = !!picked?.querySelector('.btn.danger[disabled]');
|
|
c._spaceDialog = { ...c._spaceDialog, planUrl: null };
|
|
await c.updateComplete;
|
|
await c._deleteServerPlan('f2.bbb.png').catch(() => {});
|
|
out.usedNotDeleted = !deleted.includes('f2.bbb.png');
|
|
await c._deleteServerPlan('f1.aaa.png');
|
|
await c.updateComplete;
|
|
out.freeDeleted = deleted.includes('f1.aaa.png');
|
|
out.rowGone = !(c._spaceDialog.saved || []).some((p) => p.name === 'f1.aaa.png');
|
|
return out;
|
|
});
|
|
// зафиксировано прогоном на v1.47.0 и сверено с кодом
|
|
checkAll(res, {
|
|
saveBlockedWithoutPlan: true,
|
|
listed: 2,
|
|
showsUsage: true,
|
|
deleteDisabledForUsed: true,
|
|
deleteEnabledForFree: true,
|
|
thumbnailSigned: true,
|
|
picked: true,
|
|
listClosed: true,
|
|
saveEnabledAfterPick: true,
|
|
deleteDisabledForPicked: true,
|
|
usedNotDeleted: true,
|
|
freeDeleted: true,
|
|
rowGone: true,
|
|
});
|
|
await finish(browser);
|