mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Завершение четырёх серий по референсу docs/design/600-settings-dialogs/: - из eager-листов dialogs.styles.ts и chrome.styles.ts сняты стили прежней разметки диалогов (.bindsel/.bindharow/.entcheck, .ctrlchip*, .markerhelp*, .helpfieldlabel, .markerradios, .markerlightdisabled, .markerbadgetechnical, .markerglowvalue, .fileupload, .descin, .pdfedit/.pdftag, .gsrow, .ripple-*row, .radaradditional, .togglehint, футеры marker/room) — 5,8 КБ исходника; с разметки сняты уже пустые классы; - «Additional actions» карточки Details — подзаголовок и строка действий набора, без раскрывашки (radar-section.ts, только additional-placement); - Q3 уточнён по кадру: hp-device-preview стоит без обёртки — у него свой заголовок «Display preview · Now», второй над ним удваивался; пустое состояние — блок на тинте; - §6.1: «?» у комнаты стоит у поля Home Assistant area, а не у заголовка Basics; подписи сегмента заливки — None · Zigbee · Lights · Temperature · Custom (два коротких ключа room.fill_seg_*), источники — «Average of the room’s sensors / Specific device or entity», режим свечения — «Colour and brightness» (тексты прежних ключей укорочены под сегменты); - подпись поля с «?» и без стоят на одной линии в сетке (min-height 32 по кнопке hp-help); иконка внутри подсказки — inline; - парные кадры: demo/capture_design_pairs_600.mjs снимает четыре диалога в двух темах на 560 в состояниях макетов → docs/design/…/pairs/ (8 PNG, палитра 256, ~0,5 МБ); ACCEPTANCE.md — статус по диалогам, расхождения с референсом по каждому со ссылкой на пункт SPEC и принятые адаптации (шапка hp-dialog по §3, тумблеры набора по К7, секции радара/пылесоса по §7.2); - changelog EN/RU («Не выпущено»), USER-GUIDE ru/en: раздел «Как устроены диалоги настроек», таблица настроек пространства по новым карточкам, диалог комнаты, диалог устройства, ссылка «Как у пространства»; - бюджет: стартовый граф 289 853 — потолок 291 600 → 290 900 (снятые стили); ленивый редакторский 243 149 — итоговый потолок 243 900 с разбором по сериям в bundle-budget.mjs. Golden: 11 сцен диалогов (device-dialog-*, room-temperature-dialog-*, toggle-entity-dialog-*, device-help-popover-light-ru, general-color-popover-desktop-en, settings-help-zoom-200-*, device-ripple-color-popover-mobile-ru, space-room-color-popover-desktop-ru) прогнаны диагностически (capture, HP_ALLOW_FOREIGN_CAPTURE, только artifacts/): харнесс проходит все пути без исключений, кадры «different» — эталоны переснимаются на Linux CI / машине владельца по #455. Скриншоты документации (06-device-editor, 06-device-display-preview и др.) устарели по отпечатку — пересъёмка перед кандидатом беты (#479). Гейты: typecheck, build, bundle:sync (три копии байт в байт), npm test 2793 pass по четырём частям, mutation-gate --check, check-inputs --coverage, фокусные смоки четырёх форм и смоки диалога устройства. Issue: #600 User-Visible: yes
103 lines
5.7 KiB
JavaScript
103 lines
5.7 KiB
JavaScript
// #600 AC1: парные кадры продукта для сравнения с референсом
|
||
// docs/design/600-settings-dialogs/ (ACCEPTANCE.md). Диагностическая съёмка, не
|
||
// golden: кадры не принимаются golden:accept и не участвуют в golden:verify.
|
||
//
|
||
// node demo/capture_design_pairs_600.mjs [--out=docs/design/600-settings-dialogs/pairs]
|
||
//
|
||
// Четыре диалога × две темы, ширина поверхности 560, скроллер раскрыт на всю
|
||
// высоту, чтобы кадр показывал форму целиком, как макет референса. Состояния —
|
||
// те же, что в макетах: пространство с температурной заливкой и своим севером,
|
||
// общие настройки со статическим фоном, комната с эффективной температурной
|
||
// заливкой, устройство — привязанный светильник с ролью Always и фиксированным
|
||
// свечением.
|
||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||
import { resolve } from 'node:path';
|
||
import { launch } from './serve.mjs';
|
||
|
||
const out = resolve(process.argv.find((a) => a.startsWith('--out='))?.slice(6) || 'docs/design/600-settings-dialogs/pairs');
|
||
mkdirSync(out, { recursive: true });
|
||
|
||
const THEMES = {
|
||
light: {
|
||
'--primary-color': '#0b73b8', '--primary-text-color': '#202124', '--secondary-text-color': '#5f6368',
|
||
'--card-background-color': '#ffffff', '--ha-card-background': '#ffffff', '--divider-color': '#d7d9de',
|
||
'--secondary-background-color': '#e7eaee', '--primary-background-color': '#eef1f4',
|
||
},
|
||
dark: {
|
||
'--primary-color': '#3ea6ff', '--primary-text-color': '#e6e7eb', '--secondary-text-color': '#9aa4ad',
|
||
'--card-background-color': '#202126', '--ha-card-background': '#202126', '--divider-color': '#3a3d45',
|
||
'--secondary-background-color': '#2b2d33', '--primary-background-color': '#11151b',
|
||
},
|
||
};
|
||
|
||
const { page, browser } = await launch({ width: 1280, height: 3600 });
|
||
|
||
const applyTheme = (theme) => page.evaluate(({ vars, theme }) => {
|
||
for (const [k, v] of Object.entries(vars)) document.documentElement.style.setProperty(k, v);
|
||
document.documentElement.style.colorScheme = theme;
|
||
document.body.style.background = theme === 'light' ? '#eef1f4' : '#11151b';
|
||
const c = window.__card;
|
||
c.hass = { ...c.hass, themes: { ...(c.hass.themes || {}), darkMode: theme === 'dark' } };
|
||
}, { vars: THEMES[theme], theme });
|
||
|
||
const openAndExpand = (which) => page.evaluate(async (w) => {
|
||
const c = window.__card;
|
||
const upd = async () => { c.requestUpdate(); await c.updateComplete; await new Promise((r) => setTimeout(r, 40)); };
|
||
c._settingsDialog = null; c._spaceDialog = null; c._markerDialog = null; if (c._roomDialog) c._roomDialogCancel();
|
||
await upd();
|
||
const spId = c._space;
|
||
if (w === 'space') {
|
||
c._setMode('view'); c._openSpaceDialog('edit', spId); await upd();
|
||
c._spaceDialog = { ...c._spaceDialog, fillMode: 'temp', northMode: 'custom', northDeg: 35 }; await upd();
|
||
}
|
||
if (w === 'general') {
|
||
c._openSettingsDialog(); await upd();
|
||
c._settingsDialog = { ...c._settingsDialog, bgMode: 'static', northDeg: 45 }; await upd();
|
||
}
|
||
if (w === 'room') {
|
||
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== spId ? s
|
||
: ({ ...s, settings: { ...(s.settings || {}), fill_mode: 'temp', show_names: true } })) };
|
||
c._setMode('plan'); await upd();
|
||
c._openRoomEdit(c._curSpaceCfg.rooms[0]); await upd();
|
||
c._roomFill = 'temp'; c._roomTempMin = '19'; await upd();
|
||
}
|
||
if (w === 'device') {
|
||
c._setMode('devices'); await upd();
|
||
const d = c._devices.find((x) => x.id === 'd_light1') || c._devices.find((x) => x.bindingKind === 'device') || c._devices[0];
|
||
c._openMarkerDialog(d); await upd();
|
||
c._markerDialog = { ...c._markerDialog, lightRole: 'always', lightRoleTouched: true, glowMode: 'fixed', glowTouched: true }; await upd();
|
||
}
|
||
const sr = c.shadowRoot || c.renderRoot;
|
||
const dlg = sr.querySelector('hp-dialog[form-shell]');
|
||
const surface = dlg?.shadowRoot?.querySelector('.surface');
|
||
const content = dlg?.shadowRoot?.querySelector('.content');
|
||
if (content) { content.style.maxHeight = 'none'; content.style.overflow = 'visible'; }
|
||
if (surface) { surface.style.maxHeight = 'none'; surface.style.height = 'auto'; }
|
||
const backdrop = dlg?.shadowRoot?.querySelector('.backdrop, .scrim');
|
||
if (backdrop) backdrop.style.alignItems = 'flex-start';
|
||
// Автофокус первого поля и всплывшая по фокусу подсказка «?» — не часть макета.
|
||
let active = document.activeElement;
|
||
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
||
active?.blur?.();
|
||
await new Promise((r) => setTimeout(r, 250));
|
||
const b = surface?.getBoundingClientRect();
|
||
return b ? { x: b.x, y: b.y, w: b.width, h: b.height } : null;
|
||
}, which);
|
||
|
||
const report = [];
|
||
for (const theme of ['light', 'dark']) {
|
||
await applyTheme(theme);
|
||
for (const which of ['space', 'general', 'room', 'device']) {
|
||
const b = await openAndExpand(which);
|
||
if (!b || !b.w) { report.push(`${which}/${theme}: нет поверхности`); continue; }
|
||
const file = resolve(out, `${which}-${theme}.png`);
|
||
const shot = await page.screenshot({ clip: {
|
||
x: Math.max(0, b.x - 4), y: Math.max(0, b.y - 4), width: Math.min(b.w + 8, 1280), height: Math.min(b.h + 8, 3600),
|
||
} });
|
||
writeFileSync(file, shot);
|
||
report.push(`${which}/${theme}: ${Math.round(b.w)}×${Math.round(b.h)} → ${file}`);
|
||
}
|
||
}
|
||
console.log(report.join('\n'));
|
||
await browser.close();
|