Files
houseplan-card/demo/shot_cover_states.mjs
T
Matysh 661eb784fb Fix beta.8 validation regressions
Issue: #75
Issue: #95
Issue: #98
User-Visible: yes
2026-08-12 19:38:34 +03:00

71 lines
3.6 KiB
JavaScript

// Capture: one curtain in the four states an owner sees — closed, open,
// opening, closing (owner's contract 2026-08-04). The plate is the plain
// neutral badge in ALL of them; open/closed is told by the icon morph alone,
// and the two travelling ones add the breathing transition ring (frozen at a
// visible frame so the shot is deterministic).
import { launch } from './serve.mjs';
const { page, browser } = await launch({ width: 900, height: 520 }, 2);
const STATES = ['closed', 'open', 'opening', 'closing'];
await page.evaluate(async (STATES) => {
const c = window.__card;
const devices = {}; const entities = {}; const states = {};
STATES.forEach((s, i) => {
const id = 'd_cur' + i;
devices[id] = { id, name: 'Curtain ' + s, model: 'Roller shade driver E1',
area_id: 'garden', identifiers: [['demo', id]], entry_type: null, via_device_id: null };
entities['cover.cur' + i] = { entity_id: 'cover.cur' + i, device_id: id, platform: 'demo' };
states['cover.cur' + i] = { entity_id: 'cover.cur' + i, state: s,
attributes: { friendly_name: 'Curtain ' + s, device_class: 'curtain' } };
});
c.hass = { ...c.hass,
devices: { ...c.hass.devices, ...devices },
entities: { ...c.hass.entities, ...entities },
states: { ...c.hass.states, ...states } };
c._serverCfg = { ...c._serverCfg, markers: [
...STATES.map((s, i) => ({ id: 'm_cur' + i, binding: 'device:d_cur' + i,
tap_action: 'cover', display: 'icon_ripple' })),
{ id: 'm_mower', binding: 'device:d_mower', hidden: true },
{ id: 'm_gate', binding: 'device:d_gate', hidden: true },
] };
c._cfgEpoch++; c._regSignature = ''; c._maybeRebuildDevices();
const layout = { ...c._layout };
STATES.forEach((s, i) => {
const d = c._devices.find((x) => x.bindingRef === 'd_cur' + i);
if (d) layout[d.id] = { s: 'garden', x: 0.18 + i * 0.215, y: 0.5 };
});
c._layout = layout;
c._setMode('view'); c._space = 'garden';
c.requestUpdate();
await c.updateComplete;
const st = document.createElement('style');
st.textContent = '.device-pulse.continuous.transition i:first-child{animation-delay:-1.1s!important;animation-play-state:paused!important;}';
(c.shadowRoot || c.renderRoot).appendChild(st);
}, STATES);
await page.waitForTimeout(500);
// captions under each badge, so the four states are readable in the file
const box = await page.evaluate((STATES) => {
const c = window.__card;
const els = [...(c.shadowRoot || c.renderRoot).querySelectorAll('.devlayer .dev')]
.sort((a, b) => a.getBoundingClientRect().x - b.getBoundingClientRect().x);
let minX = 1e9, maxX = -1e9, minY = 1e9, maxY = -1e9;
els.forEach((el, i) => {
const r = el.getBoundingClientRect();
const cap = document.createElement('div');
cap.textContent = STATES[i];
cap.style.cssText = `position:fixed;left:${r.x + r.width / 2}px;top:${r.y + r.height + 10}px;`
+ 'transform:translateX(-50%);font:600 13px system-ui,sans-serif;color:#1c2530;'
+ 'letter-spacing:.02em;z-index:9999;padding:2px 7px;border-radius:6px;'
+ 'background:rgba(255,255,255,0.88);white-space:nowrap;';
document.body.appendChild(cap);
minX = Math.min(minX, r.x); maxX = Math.max(maxX, r.x + r.width);
minY = Math.min(minY, r.y); maxY = Math.max(maxY, r.y + r.height);
});
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}, STATES);
const pad = 55;
await page.screenshot({ path: process.argv[2] || '/tmp/cover_states.png',
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad),
width: box.w + pad * 2, height: box.h + pad * 2 + 22 } });
await browser.close();
console.log('shot ok');