fix: the summary panel's first paint no longer freezes or claims the sources are gone

Two halves of the same first paint. The panel showed «Source unavailable»
in every row until the lazy metrics chunk arrived, because value() could
not tell "not loaded yet" from "source is dead"; and metrics() ran inside
render, walking the HA registry and unioning the clean floor of every
space synchronously — 11 s on the large-house fixture.

- totalCleanFloorAreaM2 computes the space's masonry and junction
  topology once per SPACE and hands them to innerContourForRoom, which
  otherwise unions the whole space again for every room: 11 045 → 1 488 ms
  on that fixture, same 306.3 m². The card has always done this through
  its own _innerContour cache; the panel now does the same.
- Aggregates leave the render path: the first frame paints skeletons and
  the work starts right after the frame is shown (timeout → rAF →
  timeout, never requestIdleCallback, which under load would leave the
  skeleton up for seconds). Stale memo keeps the previous number on
  screen instead of flashing back to a skeleton.
- valueState() separates pending from unavailable; a pending row keeps
  the same plate, grid and height and carries a pulsing rectangle the
  height of the line, replaced by the value with a short fade. Reduced
  motion keeps the rectangle and drops the pulse.
- Panel enter/exit animation (#505, 190 ms) is now actually visible —
  the main thread is free — and the smoke witnesses it.

Mutants: summary-first-paint-shows-unavailable, summary-metrics-block-first-frame,
summary-area-recomputes-walls-per-room, summary-stale-metric-falls-back-to-skeleton.

Issue: #509
User-Visible: yes
This commit is contained in:
Codex
2026-09-10 11:24:10 +03:00
parent 9d375dcb40
commit 398281a93f
54 changed files with 951 additions and 463 deletions
@@ -1,309 +1,309 @@
{ {
"schema": 1, "schema": 1,
"fingerprint": "d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db", "fingerprint": "124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4",
"entry": "houseplan-card.js", "entry": "houseplan-card.js",
"panelEntry": "houseplan-panel.js", "panelEntry": "houseplan-panel.js",
"initialViewFiles": [ "initialViewFiles": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-card.js" "houseplan-card.js"
], ],
"initialViewGzipBytes": 298979, "initialViewGzipBytes": 298989,
"initialPanelFiles": [ "initialPanelFiles": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-card.js", "houseplan-card.js",
"houseplan-panel.js" "houseplan-panel.js"
], ],
"initialPanelGzipBytes": 301282, "initialPanelGzipBytes": 301295,
"initialPanelOnlyFiles": [ "initialPanelOnlyFiles": [
"houseplan-panel.js" "houseplan-panel.js"
], ],
"initialPanelOnlyGzipBytes": 2303, "initialPanelOnlyGzipBytes": 2306,
"lazyFiles": [ "lazyFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/fr-Ly6vS45N.js", "houseplan-assets/fr-B3kRJHIg.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js", "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"houseplan-assets/pdf-export-KB-7X51i.js", "houseplan-assets/pdf-export-BvVr5Fm8.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js", "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js", "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
], ],
"lazyGzipBytes": 417327, "lazyGzipBytes": 418074,
"lazyEditorFiles": [ "lazyEditorFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"lazyEditorGzipBytes": 205634, "lazyEditorGzipBytes": 205645,
"lazyOnboardingFiles": [ "lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js" "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js"
], ],
"lazyOnboardingGzipBytes": 14150, "lazyOnboardingGzipBytes": 14154,
"lazyLocaleFiles": [ "lazyLocaleFiles": [
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/fr-Ly6vS45N.js" "houseplan-assets/fr-B3kRJHIg.js"
], ],
"lazyLocaleGzipBytes": 54841, "lazyLocaleGzipBytes": 54855,
"lazyIsometricFiles": [ "lazyIsometricFiles": [
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js" "houseplan-assets/iso-scene-render-fC5qC2z8.js"
], ],
"lazyIsometricGzipBytes": 13456, "lazyIsometricGzipBytes": 13461,
"lazyFurnitureArtFiles": [ "lazyFurnitureArtFiles": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js" "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js"
], ],
"lazyFurnitureArtGzipBytes": 10255, "lazyFurnitureArtGzipBytes": 10253,
"lazyPdfFiles": [ "lazyPdfFiles": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/pdf-export-KB-7X51i.js" "houseplan-assets/pdf-export-BvVr5Fm8.js"
], ],
"lazyPdfGzipBytes": 120369, "lazyPdfGzipBytes": 120366,
"files": [ "files": [
{ {
"path": "houseplan-assets/backdrop-pick-XDWKpHH4.js", "path": "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"sha256": "46638493585e2deb0e00ff488abd9a7dcec1ec20a28287a3cd0e353282504908", "sha256": "8c1290550a7aedad05481dd351c1e90160f35443cc09fe74e948bd5dc56649ae",
"rawBytes": 21134, "rawBytes": 21134,
"gzipBytes": 7146, "gzipBytes": 7146,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/de-DMIrwayB.js", "path": "houseplan-assets/de-C0_-94h5.js",
"sha256": "059d0cecd47be11a49f6f30c53d19f5d517e5991491fc170e684247e6e8d85e8", "sha256": "1c75e62f0e5f4a967fc3c866d9cc26f78de1dff2049f24a97dac8db374384a98",
"rawBytes": 91441, "rawBytes": 91441,
"gzipBytes": 27704, "gzipBytes": 27711,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/editor-CJR8FDQR.js", "path": "houseplan-assets/editor-Cx3qhTny.js",
"sha256": "a6b651f712804da0508843995e3f1df697814ea21eb6e65f2bb9e18a3119cd75", "sha256": "7fb4171b406a5ca8abc125975767d6fced0ed63f16764d9f50b56c9579fae261",
"rawBytes": 3837, "rawBytes": 3837,
"gzipBytes": 1586, "gzipBytes": 1587,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/fr-Ly6vS45N.js", "path": "houseplan-assets/fr-B3kRJHIg.js",
"sha256": "4e5843cb9e774fb448538506039ac886cca4abb01d2135c2987d89529e507196", "sha256": "5ccec3ff6236a30e91f23fe54d747ca127f0dc33a206f64ecdc6ec04eed90e71",
"rawBytes": 93719, "rawBytes": 93719,
"gzipBytes": 27137, "gzipBytes": 27144,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "path": "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"sha256": "9cdab73ffe8f24cef9b134c25537e9522b4d9bcb3adb8afcf18089a8698d3392", "sha256": "4ba60d86b2f262a6a85f9f430d809d16993d57d8f49a96f86bf39c973578ea08",
"rawBytes": 33655, "rawBytes": 33655,
"gzipBytes": 10255, "gzipBytes": 10253,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/guard-BltubuVu.js", "path": "houseplan-assets/guard-Ccxtbz5-.js",
"sha256": "6b78d297a8a579ed31e71a61514a8e43f3dbd16c4a441160b7a676cb662dbabd", "sha256": "3dc8286b4e66e5ebac5aa85810138391969aca70b966a1353dfd9f658880d900",
"rawBytes": 518, "rawBytes": 518,
"gzipBytes": 369, "gzipBytes": 371,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/houseplan-card-COZ5IbS-.js", "path": "houseplan-assets/houseplan-card-Boux1KbX.js",
"sha256": "454d9d5304a939c75a60dd45cdbb0abc31977dee29848ba23fe2c6ae73fa4c1f", "sha256": "225d32ae91543df196cf5bc1721dad811532c552cee75f6ad5ee43ee6e8cbbf4",
"rawBytes": 1076670, "rawBytes": 1076670,
"gzipBytes": 298177, "gzipBytes": 298184,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/fr-Ly6vS45N.js", "houseplan-assets/fr-B3kRJHIg.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js", "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"houseplan-assets/pdf-export-KB-7X51i.js", "houseplan-assets/pdf-export-BvVr5Fm8.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js" "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js"
] ]
}, },
{ {
"path": "houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "path": "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"sha256": "1e22ee6c2d73c4627cb46d9f46c5845a1b1cbf6cd349a67c3619d283792f8b38", "sha256": "1e506f2e01f21d7e6694b2dd18e98c788d3917a9d329c3aa9d6dddd9d03c7f2e",
"rawBytes": 672001, "rawBytes": 672001,
"gzipBytes": 178854, "gzipBytes": 178861,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
] ]
}, },
{ {
"path": "houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "path": "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"sha256": "87a38c75f5568ed9f9a11fe395abdec5aba7bf01e822913a93eb65b78a86d721", "sha256": "3a3bc039b013d2ade091d1a1a428375397a563784f3ff1d4c2f271777d37a716",
"rawBytes": 28237, "rawBytes": 28237,
"gzipBytes": 7004, "gzipBytes": 7008,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "path": "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"sha256": "6244fd8c3401305f338e86b70f31609ddbb8a34b97b876aa4e90fa2431534172", "sha256": "74d7aec4beb83a1e1b231621ccc9100365590395cd4d7b291ce2bde6f2c8f412",
"rawBytes": 11034, "rawBytes": 11034,
"gzipBytes": 3727, "gzipBytes": 3732,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js", "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/iso-scene-render-7Sm7oRsl.js", "path": "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"sha256": "7b35c1a3fe092fbd4b148dd0af12407fab8671c304a5a149ff0a61f0897dbe75", "sha256": "c02a171d4f6394ec0f2b8712d6671bd4a78513d97c88aee82c85439f40b21a3a",
"rawBytes": 38562, "rawBytes": 38562,
"gzipBytes": 13087, "gzipBytes": 13090,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "path": "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"sha256": "1d4c10e1444f160b883178332c4efda0fd53b1d688e497283eda8fee4ea5390a", "sha256": "ed6fa8091e4ceead8f36d21e4f075982e34498d6a2b812fa9bfe9edc06a1bc74",
"rawBytes": 5196, "rawBytes": 5196,
"gzipBytes": 2127, "gzipBytes": 2128,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/pdf-export-KB-7X51i.js", "path": "houseplan-assets/pdf-export-BvVr5Fm8.js",
"sha256": "1d12e876077523881134623679887f4e9adb0c23b559cc1623f04808fac98d65", "sha256": "51f677d24b518d2d6f73962bb8b0c258e6ef6f597a7c6510986f0df1effc2d73",
"rawBytes": 237254, "rawBytes": 237254,
"gzipBytes": 110114, "gzipBytes": 110113,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-editor-yyfNxbu8.js", "path": "houseplan-assets/summary-panel-editor-BPgQgIH2.js",
"sha256": "7af1a14bd45c8a5d9343e3854e3687dfe6f6895e22678ef7404ab015c3672093", "sha256": "b8654d28d267af142f654b9751a222c93dee699b845b6e4940e07f4aaffff96a",
"rawBytes": 14824, "rawBytes": 14824,
"gzipBytes": 3397, "gzipBytes": 3403,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js" "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-metrics-CfpP9w2C.js", "path": "houseplan-assets/summary-panel-metrics-CwZEnD23.js",
"sha256": "d558adf07435cd9577ff3ef4cfc0fe54e37185ada17d7d910ce63420009aa994", "sha256": "ec78ccff631e9da45964cfd4137ae192ca48d85495591fcdce697582ff5fda99",
"rawBytes": 2195, "rawBytes": 2487,
"gzipBytes": 1120, "gzipBytes": 1245,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js", "path": "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js",
"sha256": "a071202b2f2d20965652c24780f4724b865269acf43ce8fbd38096a9d2144917", "sha256": "87dd3b74bed505b18f053fa02d1c9fe4a6703bc3b160eb040b5397fa27273735",
"rawBytes": 69530, "rawBytes": 72210,
"gzipBytes": 19032, "gzipBytes": 19737,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/summary-panel-editor-yyfNxbu8.js", "houseplan-assets/summary-panel-editor-BPgQgIH2.js",
"houseplan-assets/summary-panel-metrics-CfpP9w2C.js" "houseplan-assets/summary-panel-metrics-CwZEnD23.js"
] ]
}, },
{ {
"path": "houseplan-assets/zigbee-topology-bID5a_mf.js", "path": "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"sha256": "02b0ef86a64e1a37c29b79062bc0963f02505447968c3920393e89e8d4371c25", "sha256": "1e64afef3eb53b87f6789a363ab346379f373adb720b1e566e7d34b62a391f07",
"rawBytes": 18961, "rawBytes": 18961,
"gzipBytes": 7424, "gzipBytes": 7427,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js", "path": "houseplan-assets/zigbee-topology-runtime-eE629XsS.js",
"sha256": "42cfb4070068d93ee5ac76049dbd339144c8a4d8be39bd0722320e9c3ba4c5cd", "sha256": "fcd899ac58189a76cf8af3bc8726985edeb4218f44eb6977fd3e193d803ba96d",
"rawBytes": 3941, "rawBytes": 3941,
"gzipBytes": 1761, "gzipBytes": 1766,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-card.js", "path": "houseplan-card.js",
"sha256": "c1578f819ca9aa8b53eb663fd93a561610cd929a7977f4bdc9a2e568458973fe", "sha256": "e7c9486f6c6ec4ec4f4c2f833b48bad5ebff2d66534464e37bd08936421e2aa7",
"rawBytes": 1205, "rawBytes": 1205,
"gzipBytes": 802, "gzipBytes": 805,
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-panel.js", "path": "houseplan-panel.js",
"sha256": "254ba5aa6ca98abb0122ff7c606b2c8fe562aa7fe2e7b13ce65ba0ca14fc0e4e", "sha256": "3992fe51450904ad79a3f6861088662f4acbb6181935b65c298b9707e1bd4b47",
"rawBytes": 6124, "rawBytes": 6124,
"gzipBytes": 2303, "gzipBytes": 2306,
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"houseplan-card.js" "houseplan-card.js"
@@ -1,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{t as e,l as o,x as t,e5 as s,A as a,e6 as i,e7 as l,e8 as n,dW as r,b as c}from"./houseplan-card-COZ5IbS-.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},_=this._schema,f=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{t as e,l as o,x as t,e5 as s,A as a,e6 as i,e7 as l,e8 as n,dW as r,b as c}from"./houseplan-card-Boux1KbX.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this._formData} .data=${this._formData}
.schema=${e} .schema=${e}
.computeLabel=${e=>h[e.name]||e.name} .computeLabel=${e=>h[e.name]||e.name}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form>`;return c` ></ha-form>`;return c`
${d(_.slice(0,3))} ${d(f.slice(0,3))}
${f?c`<div class="default-floor-error" role="alert" ${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere"> style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:f})} ${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a} </div>`:a}
${d(_.slice(3))} ${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h); `}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{dU as r,dV as t,dW as e}from"./houseplan-card-COZ5IbS-.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i}; globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{dU as r,dV as t,dW as e}from"./houseplan-card-Boux1KbX.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{l as t,b2 as s,A as e,x as o,b as a,cl as l,cm as i,cn as h,co as c,a$ as n,cp as r,cu as p,cq as d,cr as _,cs as u,ct as g,cv as m,cw as b,dr as $,ds as v,dt as f,a_ as y,b0 as w,du as D}from"./houseplan-card-COZ5IbS-.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-XDWKpHH4.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l} globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{l as t,b2 as s,A as e,x as o,b as a,cl as l,cm as i,cn as h,co as c,a$ as n,cp as r,cu as p,cq as d,cr as _,cs as u,ct as g,cv as m,cw as b,dr as $,ds as v,dt as f,a_ as y,b0 as w,du as D}from"./houseplan-card-Boux1KbX.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-CfkYRcXV.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await k(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await S(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans"> .text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await k(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await S(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
${s.map(s=>{return a` ${s.map(s=>{return a`
<div class="savedplan ${s.url===t.planUrl?"cur":""}"> <div class="savedplan ${s.url===t.planUrl?"cur":""}">
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{b as a,A as e}from"./houseplan-card-COZ5IbS-.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-Ds4TmwhC.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show"> globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{b as a,A as e}from"./houseplan-card-Boux1KbX.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-Bw4D2-JH.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong> <span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span> <small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
<input id="summary-local-show" data-summary-local-show type="checkbox" <input id="summary-local-show" data-summary-local-show type="checkbox"
@@ -1 +0,0 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{az as e,eb as t,ec as n,bs as i,ed as r,P as s,a5 as a,M as o,aq as c,N as d,bq as l,bp as u,ee as f}from"./houseplan-card-COZ5IbS-.js";function g(t){if(!t.registry.authoritative)return null;const n=new Set,i=e(t.markers);for(const e of Object.values(t.registry.devices||{}))e?.id&&"service"!==e.entry_type&&!i.devices.has(e.id)&&e.area_id&&t.areaToSpace[e.area_id]&&n.add(e.id);for(const e of t.markers||[]){if(e.removed||"virtual"===e.binding)continue;const r=String(e.binding||"").indexOf(":");if(r<1)continue;const s=e.binding.slice(0,r),a=e.binding.slice(r+1),o="device"===s?t.registry.devices?.[a]:null,c="entity"===s?t.registry.entities?.[a]:null,d="device"===s?a:c?.device_id,l="device"===s?o?.area_id:c?.area_id||d&&t.registry.devices?.[d]?.area_id,u="string"==typeof e.room_id&&e.room_id.length>0&&null===e.area?"":e.area||l||"",f=u&&t.areaToSpace[u]||e.space||t.firstSpaceId;d&&t.registry.devices?.[d]&&t.spaceIds.has(f)&&(!i.devices.has(d)||"entity"===s&&i.liveEntities.has(a))&&n.add(d)}return n}function m(e,t){return e?.length?t?.length?f(e,t):e:t}function v(e,t){try{let n=0;for(const i of t){const t=e.spaces.find(e=>String(e?.id)===i.id);if(!t)continue;const f=r(t,i);let g=null;for(const e of i.rooms){if(!e.id)continue;const t=s(e);if(!t)continue;const n=a(i.rooms,e.id,f.walls,f.openCuts,o,f.cellCm,c,d)||t;g=m(g,l(n,f.physicalBodies))}const v=f.cellCm/c;n+=u(g)*v*v/1e4}return n}catch{return null}}function y(e,t,n,r){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:i(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(r||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function _(e,i){const r=e?.states?.[i];if(!r)return null;const s=t(e,i);return s?n(s,String(r.attributes?.unit_of_measurement||"")):null}export{g as representedHaDeviceIds,_ as summaryEntityValue,y as summarySystemValue,v as totalCleanFloorAreaM2};
@@ -0,0 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{az as e,a4 as t,N as n,aq as i,M as r,a3 as s,eb as o,ec as a,bs as c,ed as l,P as d,a5 as u,bq as f,bp as m,ee as g}from"./houseplan-card-Boux1KbX.js";function v(t){if(!t.registry.authoritative)return null;const n=new Set,i=e(t.markers);for(const e of Object.values(t.registry.devices||{}))e?.id&&"service"!==e.entry_type&&!i.devices.has(e.id)&&e.area_id&&t.areaToSpace[e.area_id]&&n.add(e.id);for(const e of t.markers||[]){if(e.removed||"virtual"===e.binding)continue;const r=String(e.binding||"").indexOf(":");if(r<1)continue;const s=e.binding.slice(0,r),o=e.binding.slice(r+1),a="device"===s?t.registry.devices?.[o]:null,c="entity"===s?t.registry.entities?.[o]:null,l="device"===s?o:c?.device_id,d="device"===s?a?.area_id:c?.area_id||l&&t.registry.devices?.[l]?.area_id,u="string"==typeof e.room_id&&e.room_id.length>0&&null===e.area?"":e.area||d||"",f=u&&t.areaToSpace[u]||e.space||t.firstSpaceId;l&&t.registry.devices?.[l]&&t.spaceIds.has(f)&&(!i.devices.has(l)||"entity"===s&&i.liveEntities.has(o))&&n.add(l)}return n}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,o){const a=t(e.rooms,o.walls,o.openCuts,[],r,o.cellCm,i,n);return{roomGeom:"ok"===a.status||"degraded-extra"===a.status?a.roomGeom:void 0,multiWallNodes:s(e.rooms,o.walls,o.openCuts,r,o.cellCm,i,n)}}function p(e,t,s=_){try{let o=0;for(const a of t){const t=e.spaces.find(e=>String(e?.id)===a.id);if(!t)continue;const c=l(t,a),g=s(a,c);let v=null;for(const e of a.rooms){if(!e.id)continue;const t=d(e);if(!t)continue;const s=u(a.rooms,e.id,c.walls,c.openCuts,r,c.cellCm,i,n,g.roomGeom,g.multiWallNodes)||t;v=y(v,f(s,c.physicalBodies))}const _=c.cellCm/i;o+=m(v)*_*_/1e4}return o}catch{return null}}function h(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:c(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function b(e,t){const n=e?.states?.[t];if(!n)return null;const i=o(e,t);return i?a(i,String(n.attributes?.unit_of_measurement||"")):null}export{v as representedHaDeviceIds,_ as spaceWallGeometry,b as summaryEntityValue,h as summarySystemValue,p as totalCleanFloorAreaM2};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{n as t,b as n}from"./zigbee-topology-bID5a_mf.js";import{F as e}from"./houseplan-card-COZ5IbS-.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot}; globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{n as t,b as n}from"./zigbee-topology-CLVlJ-cG.js";import{F as e}from"./houseplan-card-Boux1KbX.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(f(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,p=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const f=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&p?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{p as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";try{await import("./houseplan-assets/houseplan-card-COZ5IbS-.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)} globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";try{await import("./houseplan-assets/houseplan-card-Boux1KbX.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)}
File diff suppressed because one or more lines are too long
+149
View File
@@ -0,0 +1,149 @@
// #509: первый показ сводной панели на большом плане.
//
// Что здесь доказывается и почему смоком, а не юнитом. Обе половины дефекта
// видны только в живом кадре: «Source unavailable» вместо значений рисовался
// в первом же рендере панели, а подвисание возникало оттого, что агрегаты
// считались синхронно внутри render — юнит на чистой функции ни того, ни
// другого не замечает. План берётся тот же, на котором мерили в S2
// (demo/fixtures/large-house.mjs: 3 пространства, 60 комнат): на демо-доме
// расчёт дёшев и дефект не воспроизводится.
import { launch, check, finish } from './serve.mjs';
import { makeLargeHouseFixture } from './fixtures/large-house.mjs';
const fixture = makeLargeHouseFixture();
const BLOCK = {
id: 'first-paint', title: 'General', visible: true, scope: { type: 'all' },
values: [
{ id: 'devices', label: 'Devices', source: { type: 'system', key: 'device_count' } },
{ id: 'area', label: 'Floor area', source: { type: 'system', key: 'total_area' } },
{ id: 'clock', label: 'Date and time', source: { type: 'system', key: 'datetime' } },
],
};
/** Панель на большом плане: возвращает страницу и хелперы. */
async function openLargePlan(page, { reducedMotion = false } = {}) {
if (reducedMotion) await page.emulateMedia({ reducedMotion: 'reduce' });
await page.waitForFunction(() => !!window.__card?._summary);
return page.evaluate(async ({ config, block }) => {
const card = window.__card;
const root = () => card.shadowRoot || card.renderRoot;
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
const settle = async () => { await card.updateComplete; await frame(); await card.updateComplete; };
card._config = { ...card._config, language: 'en', kiosk: false };
card._serverCanWrite = true;
card._haSummaryPanelApi = 1;
card.narrow = false;
const next = structuredClone(config);
next.settings = {
...next.settings,
summary_panel: { version: 1, title: 'Summary', show_on_mobile: true, blocks: [block] },
};
card._serverCfg = next;
card._settings = next.settings;
card._cfgEpoch = (card._cfgEpoch || 0) + 1;
card._sendConfigCandidate = async () => {};
card._summary.updated();
card.requestUpdate();
await settle();
window.__firstPaint = {
card, root, frame, settle,
overlay: () => root().querySelector('.summary-overlay'),
rows: () => [...(root().querySelector('.summary-overlay')?.querySelectorAll('.summary-value') || [])],
texts: () => [...(root().querySelector('.summary-overlay')?.querySelectorAll('.summary-value strong') || [])]
.map((node) => node.textContent.trim()),
pending: () => (root().querySelector('.summary-overlay')?.querySelectorAll('.summary-value-pending') || []).length,
};
return { ready: !!card._summary, spaces: card._serverCfg.spaces.length };
}, { config: fixture.config, block: BLOCK });
}
// --- (а) первый показ: скелеты вместо текста ошибки, кадр не заблокирован ---
{
const { page, browser } = await launch({ width: 1280, height: 900 });
const opened = await openLargePlan(page);
check('largePlanReady', opened.ready && opened.spaces === 3);
const first = await page.evaluate(async () => {
const f = window.__firstPaint;
const toggle = f.root().querySelector('.summary-control')?.querySelector('button:last-child');
const started = performance.now();
toggle?.click();
// Ровно один кадр: именно его и видел человек как «зависание».
await f.card.updateComplete;
await f.frame();
const blockedMs = performance.now() - started;
return {
blockedMs, rows: f.rows().length, pending: f.pending(), texts: f.texts(),
hasOverlay: !!f.overlay(),
};
});
check('overlayShownOnFirstFrame', first.hasOverlay && first.rows === 3);
// AC1: до готовности — скелеты; ни одна строка не врёт про недоступный источник.
check('skeletonsInsteadOfError', first.pending >= 2);
check('noUnavailableOnFirstFrame', !first.texts.some((text) => /unavailable/i.test(text)));
// AC4: первый кадр не съеден расчётом. Порог с большим запасом от измеренного
// (S2: до правки один только расчёт площади занимал секунды на этом плане).
check('firstFrameNotBlocked', first.blockedMs < 400);
// AC8: скелет живёт ограниченное время — значения приходят следом.
const settled = await page.evaluate(async () => {
const f = window.__firstPaint;
const started = performance.now();
const deadline = started + 4000;
while (performance.now() < deadline && f.pending() > 0) {
await new Promise((resolve) => setTimeout(resolve, 25));
await f.card.updateComplete;
}
return { waitedMs: performance.now() - started, pending: f.pending(), texts: f.texts() };
});
check('skeletonReplacedByValues', settled.pending === 0);
check('valuesAreReal', settled.texts.every((text) => text && !/unavailable/i.test(text)));
check('replacedWithinBudget', settled.waitedMs < 3000);
// AC5: появление панели анимируется — кадры анимации реально существуют.
const motion = await page.evaluate(async () => {
const f = window.__firstPaint;
const toggle = f.root().querySelector('.summary-control')?.querySelector('button:last-child');
toggle?.click(); // скрыть
await f.card.updateComplete;
const exiting = (f.overlay()?.getAnimations?.() || []).map((a) => a.effect?.getTiming?.().duration);
await new Promise((resolve) => setTimeout(resolve, 320));
toggle?.click(); // показать снова
await f.card.updateComplete;
const entering = (f.overlay()?.getAnimations?.() || []).map((a) => a.effect?.getTiming?.().duration);
return { exiting, entering };
});
check('exitAnimated', motion.exiting.includes(190));
check('enterAnimated', motion.entering.includes(190));
await browser.close();
}
// --- (б) reduced motion: прямоугольник есть, моторики нет ---
{
const { page, browser } = await launch({ width: 1280, height: 900 });
await openLargePlan(page, { reducedMotion: true });
const calm = await page.evaluate(async () => {
const f = window.__firstPaint;
const toggle = f.root().querySelector('.summary-control')?.querySelector('button:last-child');
toggle?.click();
await f.card.updateComplete;
await f.frame();
const skeleton = f.overlay()?.querySelector('.summary-value-pending i');
const style = skeleton ? getComputedStyle(skeleton) : null;
const overlayAnimations = (f.overlay()?.getAnimations?.() || []).length;
return {
hasSkeleton: !!skeleton,
animationName: style?.animationName || '',
opacity: Number(style?.opacity || '0'),
overlayAnimations,
};
});
// AC6: прямоугольник остаётся видимым, пульсация выключена.
check('skeletonStillDrawn', calm.hasSkeleton && calm.opacity > 0.05);
check('skeletonDoesNotPulse', calm.animationName === 'none');
// AC5 (вторая половина): при reduced motion панель не анимируется вовсе.
check('panelNotAnimatedWhenReduced', calm.overlayAnimations === 0);
await browser.close();
}
finish();
+142 -142
View File
@@ -1,309 +1,309 @@
{ {
"schema": 1, "schema": 1,
"fingerprint": "d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db", "fingerprint": "124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4",
"entry": "houseplan-card.js", "entry": "houseplan-card.js",
"panelEntry": "houseplan-panel.js", "panelEntry": "houseplan-panel.js",
"initialViewFiles": [ "initialViewFiles": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-card.js" "houseplan-card.js"
], ],
"initialViewGzipBytes": 298979, "initialViewGzipBytes": 298989,
"initialPanelFiles": [ "initialPanelFiles": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-card.js", "houseplan-card.js",
"houseplan-panel.js" "houseplan-panel.js"
], ],
"initialPanelGzipBytes": 301282, "initialPanelGzipBytes": 301295,
"initialPanelOnlyFiles": [ "initialPanelOnlyFiles": [
"houseplan-panel.js" "houseplan-panel.js"
], ],
"initialPanelOnlyGzipBytes": 2303, "initialPanelOnlyGzipBytes": 2306,
"lazyFiles": [ "lazyFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/fr-Ly6vS45N.js", "houseplan-assets/fr-B3kRJHIg.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js", "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"houseplan-assets/pdf-export-KB-7X51i.js", "houseplan-assets/pdf-export-BvVr5Fm8.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js", "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js", "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
], ],
"lazyGzipBytes": 417327, "lazyGzipBytes": 418074,
"lazyEditorFiles": [ "lazyEditorFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"lazyEditorGzipBytes": 205634, "lazyEditorGzipBytes": 205645,
"lazyOnboardingFiles": [ "lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js" "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js"
], ],
"lazyOnboardingGzipBytes": 14150, "lazyOnboardingGzipBytes": 14154,
"lazyLocaleFiles": [ "lazyLocaleFiles": [
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/fr-Ly6vS45N.js" "houseplan-assets/fr-B3kRJHIg.js"
], ],
"lazyLocaleGzipBytes": 54841, "lazyLocaleGzipBytes": 54855,
"lazyIsometricFiles": [ "lazyIsometricFiles": [
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js" "houseplan-assets/iso-scene-render-fC5qC2z8.js"
], ],
"lazyIsometricGzipBytes": 13456, "lazyIsometricGzipBytes": 13461,
"lazyFurnitureArtFiles": [ "lazyFurnitureArtFiles": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js" "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js"
], ],
"lazyFurnitureArtGzipBytes": 10255, "lazyFurnitureArtGzipBytes": 10253,
"lazyPdfFiles": [ "lazyPdfFiles": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/pdf-export-KB-7X51i.js" "houseplan-assets/pdf-export-BvVr5Fm8.js"
], ],
"lazyPdfGzipBytes": 120369, "lazyPdfGzipBytes": 120366,
"files": [ "files": [
{ {
"path": "houseplan-assets/backdrop-pick-XDWKpHH4.js", "path": "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"sha256": "46638493585e2deb0e00ff488abd9a7dcec1ec20a28287a3cd0e353282504908", "sha256": "8c1290550a7aedad05481dd351c1e90160f35443cc09fe74e948bd5dc56649ae",
"rawBytes": 21134, "rawBytes": 21134,
"gzipBytes": 7146, "gzipBytes": 7146,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/de-DMIrwayB.js", "path": "houseplan-assets/de-C0_-94h5.js",
"sha256": "059d0cecd47be11a49f6f30c53d19f5d517e5991491fc170e684247e6e8d85e8", "sha256": "1c75e62f0e5f4a967fc3c866d9cc26f78de1dff2049f24a97dac8db374384a98",
"rawBytes": 91441, "rawBytes": 91441,
"gzipBytes": 27704, "gzipBytes": 27711,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/editor-CJR8FDQR.js", "path": "houseplan-assets/editor-Cx3qhTny.js",
"sha256": "a6b651f712804da0508843995e3f1df697814ea21eb6e65f2bb9e18a3119cd75", "sha256": "7fb4171b406a5ca8abc125975767d6fced0ed63f16764d9f50b56c9579fae261",
"rawBytes": 3837, "rawBytes": 3837,
"gzipBytes": 1586, "gzipBytes": 1587,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/fr-Ly6vS45N.js", "path": "houseplan-assets/fr-B3kRJHIg.js",
"sha256": "4e5843cb9e774fb448538506039ac886cca4abb01d2135c2987d89529e507196", "sha256": "5ccec3ff6236a30e91f23fe54d747ca127f0dc33a206f64ecdc6ec04eed90e71",
"rawBytes": 93719, "rawBytes": 93719,
"gzipBytes": 27137, "gzipBytes": 27144,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "path": "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"sha256": "9cdab73ffe8f24cef9b134c25537e9522b4d9bcb3adb8afcf18089a8698d3392", "sha256": "4ba60d86b2f262a6a85f9f430d809d16993d57d8f49a96f86bf39c973578ea08",
"rawBytes": 33655, "rawBytes": 33655,
"gzipBytes": 10255, "gzipBytes": 10253,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/guard-BltubuVu.js", "path": "houseplan-assets/guard-Ccxtbz5-.js",
"sha256": "6b78d297a8a579ed31e71a61514a8e43f3dbd16c4a441160b7a676cb662dbabd", "sha256": "3dc8286b4e66e5ebac5aa85810138391969aca70b966a1353dfd9f658880d900",
"rawBytes": 518, "rawBytes": 518,
"gzipBytes": 369, "gzipBytes": 371,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/houseplan-card-COZ5IbS-.js", "path": "houseplan-assets/houseplan-card-Boux1KbX.js",
"sha256": "454d9d5304a939c75a60dd45cdbb0abc31977dee29848ba23fe2c6ae73fa4c1f", "sha256": "225d32ae91543df196cf5bc1721dad811532c552cee75f6ad5ee43ee6e8cbbf4",
"rawBytes": 1076670, "rawBytes": 1076670,
"gzipBytes": 298177, "gzipBytes": 298184,
"isEntry": false, "isEntry": false,
"imports": [], "imports": [],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/de-DMIrwayB.js", "houseplan-assets/de-C0_-94h5.js",
"houseplan-assets/editor-CJR8FDQR.js", "houseplan-assets/editor-Cx3qhTny.js",
"houseplan-assets/fr-Ly6vS45N.js", "houseplan-assets/fr-B3kRJHIg.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"houseplan-assets/iso-scene-render-7Sm7oRsl.js", "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"houseplan-assets/pdf-export-KB-7X51i.js", "houseplan-assets/pdf-export-BvVr5Fm8.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js" "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js"
] ]
}, },
{ {
"path": "houseplan-assets/houseplan-editor-runtime-BQZkkibD.js", "path": "houseplan-assets/houseplan-editor-runtime-daXn-qEd.js",
"sha256": "1e22ee6c2d73c4627cb46d9f46c5845a1b1cbf6cd349a67c3619d283792f8b38", "sha256": "1e506f2e01f21d7e6694b2dd18e98c788d3917a9d329c3aa9d6dddd9d03c7f2e",
"rawBytes": 672001, "rawBytes": 672001,
"gzipBytes": 178854, "gzipBytes": 178861,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
] ]
}, },
{ {
"path": "houseplan-assets/houseplan-onboarding-runtime-Bw2ds9pT.js", "path": "houseplan-assets/houseplan-onboarding-runtime-TxrqWs69.js",
"sha256": "87a38c75f5568ed9f9a11fe395abdec5aba7bf01e822913a93eb65b78a86d721", "sha256": "3a3bc039b013d2ade091d1a1a428375397a563784f3ff1d4c2f271777d37a716",
"rawBytes": 28237, "rawBytes": 28237,
"gzipBytes": 7004, "gzipBytes": 7008,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/backdrop-pick-XDWKpHH4.js", "houseplan-assets/backdrop-pick-CfkYRcXV.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/hp-zigbee-topology-overlay-DKWMMSFx.js", "path": "houseplan-assets/hp-zigbee-topology-overlay-BsupAjYt.js",
"sha256": "6244fd8c3401305f338e86b70f31609ddbb8a34b97b876aa4e90fa2431534172", "sha256": "74d7aec4beb83a1e1b231621ccc9100365590395cd4d7b291ce2bde6f2c8f412",
"rawBytes": 11034, "rawBytes": 11034,
"gzipBytes": 3727, "gzipBytes": 3732,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js", "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js" "houseplan-assets/zigbee-topology-runtime-eE629XsS.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/iso-scene-render-7Sm7oRsl.js", "path": "houseplan-assets/iso-scene-render-fC5qC2z8.js",
"sha256": "7b35c1a3fe092fbd4b148dd0af12407fab8671c304a5a149ff0a61f0897dbe75", "sha256": "c02a171d4f6394ec0f2b8712d6671bd4a78513d97c88aee82c85439f40b21a3a",
"rawBytes": 38562, "rawBytes": 38562,
"gzipBytes": 13087, "gzipBytes": 13090,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/guard-BltubuVu.js", "houseplan-assets/guard-Ccxtbz5-.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/live-interaction-runtime-GkWuD1hK.js", "path": "houseplan-assets/live-interaction-runtime-C0AGopGh.js",
"sha256": "1d4c10e1444f160b883178332c4efda0fd53b1d688e497283eda8fee4ea5390a", "sha256": "ed6fa8091e4ceead8f36d21e4f075982e34498d6a2b812fa9bfe9edc06a1bc74",
"rawBytes": 5196, "rawBytes": 5196,
"gzipBytes": 2127, "gzipBytes": 2128,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/pdf-export-KB-7X51i.js", "path": "houseplan-assets/pdf-export-BvVr5Fm8.js",
"sha256": "1d12e876077523881134623679887f4e9adb0c23b559cc1623f04808fac98d65", "sha256": "51f677d24b518d2d6f73962bb8b0c258e6ef6f597a7c6510986f0df1effc2d73",
"rawBytes": 237254, "rawBytes": 237254,
"gzipBytes": 110114, "gzipBytes": 110113,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/furniture-plan-art.generated-DPe1A9An.js", "houseplan-assets/furniture-plan-art.generated-Yh429YO5.js",
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-editor-yyfNxbu8.js", "path": "houseplan-assets/summary-panel-editor-BPgQgIH2.js",
"sha256": "7af1a14bd45c8a5d9343e3854e3687dfe6f6895e22678ef7404ab015c3672093", "sha256": "b8654d28d267af142f654b9751a222c93dee699b845b6e4940e07f4aaffff96a",
"rawBytes": 14824, "rawBytes": 14824,
"gzipBytes": 3397, "gzipBytes": 3403,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js" "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-metrics-CfpP9w2C.js", "path": "houseplan-assets/summary-panel-metrics-CwZEnD23.js",
"sha256": "d558adf07435cd9577ff3ef4cfc0fe54e37185ada17d7d910ce63420009aa994", "sha256": "ec78ccff631e9da45964cfd4137ae192ca48d85495591fcdce697582ff5fda99",
"rawBytes": 2195, "rawBytes": 2487,
"gzipBytes": 1120, "gzipBytes": 1245,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/summary-panel-runtime-loaded-Ds4TmwhC.js", "path": "houseplan-assets/summary-panel-runtime-loaded-Bw4D2-JH.js",
"sha256": "a071202b2f2d20965652c24780f4724b865269acf43ce8fbd38096a9d2144917", "sha256": "87dd3b74bed505b18f053fa02d1c9fe4a6703bc3b160eb040b5397fa27273735",
"rawBytes": 69530, "rawBytes": 72210,
"gzipBytes": 19032, "gzipBytes": 19737,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [ "dynamicImports": [
"houseplan-assets/summary-panel-editor-yyfNxbu8.js", "houseplan-assets/summary-panel-editor-BPgQgIH2.js",
"houseplan-assets/summary-panel-metrics-CfpP9w2C.js" "houseplan-assets/summary-panel-metrics-CwZEnD23.js"
] ]
}, },
{ {
"path": "houseplan-assets/zigbee-topology-bID5a_mf.js", "path": "houseplan-assets/zigbee-topology-CLVlJ-cG.js",
"sha256": "02b0ef86a64e1a37c29b79062bc0963f02505447968c3920393e89e8d4371c25", "sha256": "1e64afef3eb53b87f6789a363ab346379f373adb720b1e566e7d34b62a391f07",
"rawBytes": 18961, "rawBytes": 18961,
"gzipBytes": 7424, "gzipBytes": 7427,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-assets/zigbee-topology-runtime-BzjnMR5f.js", "path": "houseplan-assets/zigbee-topology-runtime-eE629XsS.js",
"sha256": "42cfb4070068d93ee5ac76049dbd339144c8a4d8be39bd0722320e9c3ba4c5cd", "sha256": "fcd899ac58189a76cf8af3bc8726985edeb4218f44eb6977fd3e193d803ba96d",
"rawBytes": 3941, "rawBytes": 3941,
"gzipBytes": 1761, "gzipBytes": 1766,
"isEntry": false, "isEntry": false,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js", "houseplan-assets/houseplan-card-Boux1KbX.js",
"houseplan-assets/zigbee-topology-bID5a_mf.js" "houseplan-assets/zigbee-topology-CLVlJ-cG.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-card.js", "path": "houseplan-card.js",
"sha256": "c1578f819ca9aa8b53eb663fd93a561610cd929a7977f4bdc9a2e568458973fe", "sha256": "e7c9486f6c6ec4ec4f4c2f833b48bad5ebff2d66534464e37bd08936421e2aa7",
"rawBytes": 1205, "rawBytes": 1205,
"gzipBytes": 802, "gzipBytes": 805,
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"houseplan-assets/houseplan-card-COZ5IbS-.js" "houseplan-assets/houseplan-card-Boux1KbX.js"
], ],
"dynamicImports": [] "dynamicImports": []
}, },
{ {
"path": "houseplan-panel.js", "path": "houseplan-panel.js",
"sha256": "254ba5aa6ca98abb0122ff7c606b2c8fe562aa7fe2e7b13ce65ba0ca14fc0e4e", "sha256": "3992fe51450904ad79a3f6861088662f4acbb6181935b65c298b9707e1bd4b47",
"rawBytes": 6124, "rawBytes": 6124,
"gzipBytes": 2303, "gzipBytes": 2306,
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"houseplan-card.js" "houseplan-card.js"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{t as e,l as o,x as t,e5 as s,A as a,e6 as i,e7 as l,e8 as n,dW as r,b as c}from"./houseplan-card-COZ5IbS-.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},_=this._schema,f=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{t as e,l as o,x as t,e5 as s,A as a,e6 as i,e7 as l,e8 as n,dW as r,b as c}from"./houseplan-card-Boux1KbX.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this._formData} .data=${this._formData}
.schema=${e} .schema=${e}
.computeLabel=${e=>h[e.name]||e.name} .computeLabel=${e=>h[e.name]||e.name}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-form>`;return c` ></ha-form>`;return c`
${d(_.slice(0,3))} ${d(f.slice(0,3))}
${f?c`<div class="default-floor-error" role="alert" ${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere"> style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:f})} ${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a} </div>`:a}
${d(_.slice(3))} ${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h); `}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{dU as r,dV as t,dW as e}from"./houseplan-card-COZ5IbS-.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i}; globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{dU as r,dV as t,dW as e}from"./houseplan-card-Boux1KbX.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{l as t,b2 as s,A as e,x as o,b as a,cl as l,cm as i,cn as h,co as c,a$ as n,cp as r,cu as p,cq as d,cr as _,cs as u,ct as g,cv as m,cw as b,dr as $,ds as v,dt as f,a_ as y,b0 as w,du as D}from"./houseplan-card-COZ5IbS-.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-XDWKpHH4.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l} globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{l as t,b2 as s,A as e,x as o,b as a,cl as l,cm as i,cn as h,co as c,a$ as n,cp as r,cu as p,cq as d,cr as _,cs as u,ct as g,cv as m,cw as b,dr as $,ds as v,dt as f,a_ as y,b0 as w,du as D}from"./houseplan-card-Boux1KbX.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-CfkYRcXV.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await k(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await S(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans"> .text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await k(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await S(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
${s.map(s=>{return a` ${s.map(s=>{return a`
<div class="savedplan ${s.url===t.planUrl?"cur":""}"> <div class="savedplan ${s.url===t.planUrl?"cur":""}">
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{b as a,A as e}from"./houseplan-card-COZ5IbS-.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-Ds4TmwhC.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show"> globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{b as a,A as e}from"./houseplan-card-Boux1KbX.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-Bw4D2-JH.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong> <span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span> <small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
<input id="summary-local-show" data-summary-local-show type="checkbox" <input id="summary-local-show" data-summary-local-show type="checkbox"
@@ -1 +0,0 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{az as e,eb as t,ec as n,bs as i,ed as r,P as s,a5 as a,M as o,aq as c,N as d,bq as l,bp as u,ee as f}from"./houseplan-card-COZ5IbS-.js";function g(t){if(!t.registry.authoritative)return null;const n=new Set,i=e(t.markers);for(const e of Object.values(t.registry.devices||{}))e?.id&&"service"!==e.entry_type&&!i.devices.has(e.id)&&e.area_id&&t.areaToSpace[e.area_id]&&n.add(e.id);for(const e of t.markers||[]){if(e.removed||"virtual"===e.binding)continue;const r=String(e.binding||"").indexOf(":");if(r<1)continue;const s=e.binding.slice(0,r),a=e.binding.slice(r+1),o="device"===s?t.registry.devices?.[a]:null,c="entity"===s?t.registry.entities?.[a]:null,d="device"===s?a:c?.device_id,l="device"===s?o?.area_id:c?.area_id||d&&t.registry.devices?.[d]?.area_id,u="string"==typeof e.room_id&&e.room_id.length>0&&null===e.area?"":e.area||l||"",f=u&&t.areaToSpace[u]||e.space||t.firstSpaceId;d&&t.registry.devices?.[d]&&t.spaceIds.has(f)&&(!i.devices.has(d)||"entity"===s&&i.liveEntities.has(a))&&n.add(d)}return n}function m(e,t){return e?.length?t?.length?f(e,t):e:t}function v(e,t){try{let n=0;for(const i of t){const t=e.spaces.find(e=>String(e?.id)===i.id);if(!t)continue;const f=r(t,i);let g=null;for(const e of i.rooms){if(!e.id)continue;const t=s(e);if(!t)continue;const n=a(i.rooms,e.id,f.walls,f.openCuts,o,f.cellCm,c,d)||t;g=m(g,l(n,f.physicalBodies))}const v=f.cellCm/c;n+=u(g)*v*v/1e4}return n}catch{return null}}function y(e,t,n,r){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:i(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(r||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function _(e,i){const r=e?.states?.[i];if(!r)return null;const s=t(e,i);return s?n(s,String(r.attributes?.unit_of_measurement||"")):null}export{g as representedHaDeviceIds,_ as summaryEntityValue,y as summarySystemValue,v as totalCleanFloorAreaM2};
@@ -0,0 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{az as e,a4 as t,N as n,aq as i,M as r,a3 as s,eb as o,ec as a,bs as c,ed as l,P as d,a5 as u,bq as f,bp as m,ee as g}from"./houseplan-card-Boux1KbX.js";function v(t){if(!t.registry.authoritative)return null;const n=new Set,i=e(t.markers);for(const e of Object.values(t.registry.devices||{}))e?.id&&"service"!==e.entry_type&&!i.devices.has(e.id)&&e.area_id&&t.areaToSpace[e.area_id]&&n.add(e.id);for(const e of t.markers||[]){if(e.removed||"virtual"===e.binding)continue;const r=String(e.binding||"").indexOf(":");if(r<1)continue;const s=e.binding.slice(0,r),o=e.binding.slice(r+1),a="device"===s?t.registry.devices?.[o]:null,c="entity"===s?t.registry.entities?.[o]:null,l="device"===s?o:c?.device_id,d="device"===s?a?.area_id:c?.area_id||l&&t.registry.devices?.[l]?.area_id,u="string"==typeof e.room_id&&e.room_id.length>0&&null===e.area?"":e.area||d||"",f=u&&t.areaToSpace[u]||e.space||t.firstSpaceId;l&&t.registry.devices?.[l]&&t.spaceIds.has(f)&&(!i.devices.has(l)||"entity"===s&&i.liveEntities.has(o))&&n.add(l)}return n}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,o){const a=t(e.rooms,o.walls,o.openCuts,[],r,o.cellCm,i,n);return{roomGeom:"ok"===a.status||"degraded-extra"===a.status?a.roomGeom:void 0,multiWallNodes:s(e.rooms,o.walls,o.openCuts,r,o.cellCm,i,n)}}function p(e,t,s=_){try{let o=0;for(const a of t){const t=e.spaces.find(e=>String(e?.id)===a.id);if(!t)continue;const c=l(t,a),g=s(a,c);let v=null;for(const e of a.rooms){if(!e.id)continue;const t=d(e);if(!t)continue;const s=u(a.rooms,e.id,c.walls,c.openCuts,r,c.cellCm,i,n,g.roomGeom,g.multiWallNodes)||t;v=y(v,f(s,c.physicalBodies))}const _=c.cellCm/i;o+=m(v)*_*_/1e4}return o}catch{return null}}function h(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:c(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function b(e,t){const n=e?.states?.[t];if(!n)return null;const i=o(e,t);return i?a(i,String(n.attributes?.unit_of_measurement||"")):null}export{v as representedHaDeviceIds,_ as spaceWallGeometry,b as summaryEntityValue,h as summarySystemValue,p as totalCleanFloorAreaM2};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";import{n as t,b as n}from"./zigbee-topology-bID5a_mf.js";import{F as e}from"./houseplan-card-COZ5IbS-.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot}; globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";import{n as t,b as n}from"./zigbee-topology-CLVlJ-cG.js";import{F as e}from"./houseplan-card-Boux1KbX.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(f(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,p=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const f=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&p?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{p as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d400624eeb3db04eb31dcbe3699a028bd6d523224ea026292d43df657cabc9db";try{await import("./houseplan-assets/houseplan-card-COZ5IbS-.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)} globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="124fb0df371c29dfe42379078c6da169f3d9b99562e1e922b8307fa9c6752bd4";try{await import("./houseplan-assets/houseplan-card-Boux1KbX.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)}
+1 -1
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -2,6 +2,12 @@
## Unreleased ## Unreleased
- The summary panel no longer freezes the first frame on a large plan, and it
no longer says «Source unavailable» while its values are still being
computed: rows show a pulsing placeholder that is replaced by the numbers as
they arrive, and the panel fades in and out again
([#509](https://github.com/Matysh/houseplan-card/issues/509)).
- The summary panel settings dialog scrolls again in Home Assistant — with the - The summary panel settings dialog scrolls again in Home Assistant — with the
mouse wheel on desktop and with a swipe on phones; the header and the action mouse wheel on desktop and with a swipe on phones; the header and the action
buttons stay in place buttons stay in place
+6
View File
@@ -8,6 +8,12 @@
## Не выпущено ## Не выпущено
- Сводная панель больше не подвешивает первый кадр на большом плане и не пишет
«Source unavailable», пока значения ещё считаются: в строках появляются
пульсирующие плашки, которые плавно сменяются числами, а сама панель снова
появляется и исчезает плавно
([#509](https://github.com/Matysh/houseplan-card/issues/509)).
- Диалог настроек сводной панели в Home Assistant снова прокручивается — - Диалог настроек сводной панели в Home Assistant снова прокручивается —
колесом мыши на десктопе и свайпом на телефоне; шапка и кнопки остаются на колесом мыши на десктопе и свайпом на телефоне; шапка и кнопки остаются на
месте ([#508](https://github.com/Matysh/houseplan-card/issues/508)). месте ([#508](https://github.com/Matysh/houseplan-card/issues/508)).
+12 -12
View File
@@ -3,7 +3,7 @@
"fixture": "synthetic-only", "fixture": "synthetic-only",
"chromium": "151.0.7922.34", "chromium": "151.0.7922.34",
"oxipng": "oxipng 10.2.0", "oxipng": "oxipng 10.2.0",
"sourceFingerprint": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceFingerprint": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"captureScriptSha256": "9e4b0ae533407fe003a129078aec020f2c83d67cdb6fc6da696ce3d829beaeb5", "captureScriptSha256": "9e4b0ae533407fe003a129078aec020f2c83d67cdb6fc6da696ce3d829beaeb5",
"command": "npm run build && node demo/docs/capture.mjs", "command": "npm run build && node demo/docs/capture.mjs",
"scenarios": { "scenarios": {
@@ -15,7 +15,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "76c35a87810e3dc6890bda7b43da133f57d8860031204a62cda7115c15950090" "imageSha256": "76c35a87810e3dc6890bda7b43da133f57d8860031204a62cda7115c15950090"
}, },
"view-touch": { "view-touch": {
@@ -26,7 +26,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "795585acb09488236ebb8028a7fcbd918404c5fe01560bb9b4559de13d441625" "imageSha256": "795585acb09488236ebb8028a7fcbd918404c5fe01560bb9b4559de13d441625"
}, },
"space-create": { "space-create": {
@@ -37,7 +37,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "9f0970de09772674c3156b9fc5b2d49de0ceb22be11ecf8b136047d37d100a84" "imageSha256": "9f0970de09772674c3156b9fc5b2d49de0ceb22be11ecf8b136047d37d100a84"
}, },
"room-contour-close": { "room-contour-close": {
@@ -48,7 +48,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "0c354a79f9c3660788b6c5e889a27b6bd78b1f209dc0eef6bb43e2c1344028c4" "imageSha256": "0c354a79f9c3660788b6c5e889a27b6bd78b1f209dc0eef6bb43e2c1344028c4"
}, },
"plan-context-tray": { "plan-context-tray": {
@@ -59,7 +59,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "64169c543291bc4df806dcf8ff484b42c8ea55eb343323d945344e0e28f2ce97" "imageSha256": "64169c543291bc4df806dcf8ff484b42c8ea55eb343323d945344e0e28f2ce97"
}, },
"device-editor": { "device-editor": {
@@ -70,7 +70,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "3c8703175605fcf0387d654cd30783ecfd33e21743e70b45190a5ae1a8d355c2" "imageSha256": "3c8703175605fcf0387d654cd30783ecfd33e21743e70b45190a5ae1a8d355c2"
}, },
"device-display-preview": { "device-display-preview": {
@@ -81,7 +81,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "5d6193202102f5e6d85567f1111fa105895b933979fbf369c6ba0b1029deb823" "imageSha256": "5d6193202102f5e6d85567f1111fa105895b933979fbf369c6ba0b1029deb823"
}, },
"background-editor": { "background-editor": {
@@ -92,7 +92,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "375900a29aa62632883ede6727b7dbb8b48cabe9d7d0e16425f9779c837f3dd9" "imageSha256": "375900a29aa62632883ede6727b7dbb8b48cabe9d7d0e16425f9779c837f3dd9"
}, },
"room-card": { "room-card": {
@@ -103,7 +103,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "3d85424676c823ddca3fa7dab3cce089b6030e48c05d16d2d48d922c66c0cbd2" "imageSha256": "3d85424676c823ddca3fa7dab3cce089b6030e48c05d16d2d48d922c66c0cbd2"
}, },
"device-info": { "device-info": {
@@ -114,7 +114,7 @@
}, },
"theme": "dark", "theme": "dark",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "ac962820f8e1c5cb5b042d39b18dc95905fa840cb2d4749060d991be4f6977c2" "imageSha256": "ac962820f8e1c5cb5b042d39b18dc95905fa840cb2d4749060d991be4f6977c2"
}, },
"pdf-export": { "pdf-export": {
@@ -125,7 +125,7 @@
}, },
"theme": "light", "theme": "light",
"language": "en", "language": "en",
"sourceSha256": "665914b588e8a49715b175992296ad2f660d4110614da104ac2e7bc83fb5f464", "sourceSha256": "559c5407c93322ab80a3729a09523e12fa466f49fa3cff7c286b4e84c9dd4cc7",
"imageSha256": "4537692fd57a3ed95528eddd748eee5cb6e456ffab7cc49169853d62459252cd" "imageSha256": "4537692fd57a3ed95528eddd748eee5cb6e456ffab7cc49169853d62459252cd"
} }
}, },
+44
View File
@@ -8110,6 +8110,50 @@ const MUTANT_DEFINITIONS = [
replace: " return relevant.sort((a, b) => stamp(a) - stamp(b) || Number(a.id || 0) - Number(b.id || 0))[0] || null; // mutant: oldest", replace: " return relevant.sort((a, b) => stamp(a) - stamp(b) || Number(a.id || 0) - Number(b.id || 0))[0] || null; // mutant: oldest",
}], }],
}, },
{
id: 'summary-first-paint-shows-unavailable',
guard: 'node demo/smoke_summary_first_paint.mjs',
because: 'до прихода ленивого чанка метрик значения ещё НЕ известны: текст «источник недоступен» '
+ 'здесь — ложь, которую человек видит в первый же кадр панели (#509 AC1)',
patches: [{
file: 'src/summary-panel-runtime-loaded.ts',
find: " if (!module) return { kind: 'pending' };",
replace: " if (!module) return { kind: 'unavailable' }; // mutant: loading looks like a dead source",
}],
},
{
id: 'summary-metrics-block-first-frame',
guard: 'node demo/smoke_summary_first_paint.mjs',
because: 'агрегаты по всей геометрии и реестру, посчитанные внутри render, замораживают первый '
+ 'показ панели на сотни миллисекунд — ровно симптом #509 (AC4)',
patches: [{
file: 'src/summary-panel-runtime-loaded.ts',
find: " const needsAggregate = value.source.key === 'device_count' || value.source.key === 'total_area';",
replace: " this.computeMetrics(); // mutant: aggregates back inside render\n const needsAggregate = value.source.key === 'device_count' || value.source.key === 'total_area';",
}],
},
{
id: 'summary-area-recomputes-walls-per-room',
guard: 'node --test test/summary-panel.test.mjs',
because: 'innerContourForRoom без общей геометрии объединяет кладку пространства заново для КАЖДОЙ '
+ 'комнаты: 176 мс на комнату и 11 с на большом плане (#509 AC3)',
patches: [{
file: 'src/summary-panel-metrics.ts',
find: " shared.roomGeom, shared.multiWallNodes,",
replace: " // mutant: no shared masonry",
}],
},
{
id: 'summary-stale-metric-falls-back-to-skeleton',
guard: 'node --test test/summary-panel-runtime.test.mjs',
because: 'правка конфигурации не должна возвращать панель в скелет: прежнее число остаётся до '
+ 'прихода нового, иначе мигание возвращается с другой стороны (#509 AC9)',
patches: [{
file: 'src/summary-panel-runtime-loaded.ts',
find: " if (needsAggregate && !known) {",
replace: " if (needsAggregate && (!known || !this.metricsFresh())) {",
}],
},
{ {
id: 'summary-dialog-drops-flex-content', id: 'summary-dialog-drops-flex-content',
guard: 'node demo/smoke_summary_dialog_scroll.mjs', guard: 'node demo/smoke_summary_dialog_scroll.mjs',
+31 -1
View File
@@ -5,7 +5,7 @@ import { geometryArea, floorMinusBodies } from './physical-geometry';
import { prepareSpacePhysicalGeometryInputs } from './plan-geometry-preflight'; import { prepareSpacePhysicalGeometryInputs } from './plan-geometry-preflight';
import { GRID_PITCH, GRID_STEP_N, NORM_W } from './space-geometry'; import { GRID_PITCH, GRID_STEP_N, NORM_W } from './space-geometry';
import { hassValue, roomPoly, valueWithUnit } from './logic'; import { hassValue, roomPoly, valueWithUnit } from './logic';
import { innerContourForRoom } from './wall-thickness'; import { innerContourForRoom, multiWallNodesForGeometry, wallBodiesGeometry } from './wall-thickness';
import type { Marker, ServerConfig, SpaceModel, SummaryPanelSource } from './types'; import type { Marker, ServerConfig, SpaceModel, SummaryPanelSource } from './types';
import type { HaRegistrySnapshot } from './ha-binding-status'; import type { HaRegistrySnapshot } from './ha-binding-status';
import type { SummaryHass } from './summary-panel-host'; import type { SummaryHass } from './summary-panel-host';
@@ -55,10 +55,37 @@ function unionGeometry(current: Geom | null, next: Geom): Geom {
return union(current, next); return union(current, next);
} }
/**
* Wall masonry and junction topology of one space, computed once (#509).
*
* `innerContourForRoom` accepts both as optional arguments and, without them,
* unions the whole space's masonry again for EVERY room: on the large-house
* fixture that was 176 ms per room and 11 s for the panel's first frame. The
* card has always passed them from its own render cache (`_innerContour`);
* the panel now does the same, one pass per space instead of one per room.
*/
export function spaceWallGeometry(
space: SpaceModel,
prepared: ReturnType<typeof prepareSpacePhysicalGeometryInputs>,
): { roomGeom: unknown; multiWallNodes: ReturnType<typeof multiWallNodesForGeometry> } {
const united = wallBodiesGeometry(
space.rooms, prepared.walls, prepared.openCuts, [],
GRID_STEP_N, prepared.cellCm, GRID_PITCH, NORM_W,
);
return {
roomGeom: united.status === 'ok' || united.status === 'degraded-extra' ? united.roomGeom : undefined,
multiWallNodes: multiWallNodesForGeometry(
space.rooms, prepared.walls, prepared.openCuts,
GRID_STEP_N, prepared.cellCm, GRID_PITCH, NORM_W,
),
};
}
/** Canonical clean-floor union, in physical square metres, for every space. */ /** Canonical clean-floor union, in physical square metres, for every space. */
export function totalCleanFloorAreaM2( export function totalCleanFloorAreaM2(
config: ServerConfig, config: ServerConfig,
models: readonly SpaceModel[], models: readonly SpaceModel[],
geometryOf: typeof spaceWallGeometry = spaceWallGeometry,
): number | null { ): number | null {
try { try {
let total = 0; let total = 0;
@@ -66,6 +93,8 @@ export function totalCleanFloorAreaM2(
const raw = config.spaces.find((item) => String(item?.id) === space.id); const raw = config.spaces.find((item) => String(item?.id) === space.id);
if (!raw) continue; if (!raw) continue;
const prepared = prepareSpacePhysicalGeometryInputs(raw, space); const prepared = prepareSpacePhysicalGeometryInputs(raw, space);
// Один проход на пространство, не на комнату (#509).
const shared = geometryOf(space, prepared);
let spaceFloor: Geom | null = null; let spaceFloor: Geom | null = null;
for (const room of space.rooms) { for (const room of space.rooms) {
if (!room.id) continue; if (!room.id) continue;
@@ -74,6 +103,7 @@ export function totalCleanFloorAreaM2(
const inner = innerContourForRoom( const inner = innerContourForRoom(
space.rooms, room.id, prepared.walls, prepared.openCuts, space.rooms, room.id, prepared.walls, prepared.openCuts,
GRID_STEP_N, prepared.cellCm, GRID_PITCH, NORM_W, GRID_STEP_N, prepared.cellCm, GRID_PITCH, NORM_W,
shared.roomGeom, shared.multiWallNodes,
) || poly; ) || poly;
const clean = floorMinusBodies(inner, prepared.physicalBodies) as Geom; const clean = floorMinusBodies(inner, prepared.physicalBodies) as Geom;
spaceFloor = unionGeometry(spaceFloor, clean); spaceFloor = unionGeometry(spaceFloor, clean);
+95 -14
View File
@@ -85,6 +85,10 @@ export class LoadedSummaryPanelRuntime {
private editorLoad: Promise<SummaryPanelEditorRenderer> | null = null; private editorLoad: Promise<SummaryPanelEditorRenderer> | null = null;
private metricsModule: typeof import('./summary-panel-metrics') | null = null; private metricsModule: typeof import('./summary-panel-metrics') | null = null;
private metricsLoad: Promise<typeof import('./summary-panel-metrics')> | null = null; private metricsLoad: Promise<typeof import('./summary-panel-metrics')> | null = null;
// Тяжёлые агрегаты считаются ПОСЛЕ кадра (#509): на большом плане обход
// геометрии занимал сотни миллисекунд прямо в render, и первый показ панели
// выглядел зависанием. Здесь — только флаг «пересчёт уже запланирован».
private metricsRefresh = 0;
private styleSheet: CSSStyleSheet | null = null; private styleSheet: CSSStyleSheet | null = null;
private entityIndex: SummaryEntityIndex | null = null; private entityIndex: SummaryEntityIndex | null = null;
private lifecycleGeneration = 0; private lifecycleGeneration = 0;
@@ -209,9 +213,17 @@ export class LoadedSummaryPanelRuntime {
<div class="summary-scroll"> <div class="summary-scroll">
${blocks.length ? blocks.map((block) => html`<section class="summary-block"> ${blocks.length ? blocks.map((block) => html`<section class="summary-block">
<h3>${block.title}</h3> <h3>${block.title}</h3>
${block.values.length ? block.values.map((value) => html`<div class="summary-value"> ${block.values.length ? block.values.map((value) => {
<span>${value.label}</span><strong>${this.value(value)}</strong> const state = this.valueState(value);
</div>`) : html`<div class="summary-empty">${this.t('summary.empty_block')}</div>`} // Скелет вместо текста ошибки, пока значения нет (#509): та же
// плашка и та же высота строки, пульсирующий прямоугольник на
// месте значения, плавная замена по готовности.
return html`<div class="summary-value">
<span>${value.label}</span>${state.kind === 'pending'
? html`<strong class="summary-value-pending" aria-hidden="true"><i></i></strong>`
: html`<strong>${state.kind === 'ready' ? state.text : this.t('summary.unavailable')}</strong>`}
</div>`;
}) : html`<div class="summary-empty">${this.t('summary.empty_block')}</div>`}
</section>`) : html`<div class="summary-empty">${this.t('summary.empty_space')}</div>`} </section>`) : html`<div class="summary-empty">${this.t('summary.empty_space')}</div>`}
</div> </div>
</aside>`; </aside>`;
@@ -684,9 +696,48 @@ export class LoadedSummaryPanelRuntime {
if (this.current(generation)) this.host.requestUpdate(); if (this.current(generation)) this.host.requestUpdate();
} }
private metrics(): { deviceCount: number | null; areaM2: number | null; now: Date } { /** Свежи ли мемо-агрегаты для текущей конфигурации и реестра. */
/** @internal — читается рендером и тестами (#509). */
public metricsFresh(): boolean {
return !!this.deviceMemo && !!this.areaMemo
&& this.deviceMemo.cfgEpoch === this.host._cfgEpoch
&& this.deviceMemo.layoutRev === this.host._layoutRev
&& this.deviceMemo.registryRev === this.host._haRegistry.revision
&& this.areaMemo.cfgEpoch === this.host._cfgEpoch;
}
/**
* Пересчёт агрегатов ПОСЛЕ кадра (#509).
*
* Обход реестра и объединение чистого пола по всем пространствам стоят
* сотни миллисекунд на большом плане; в render это была заморозка первого
* показа. Кадр рисуется скелетами, работа идёт следом, и по её завершении
* панель перерисовывается уже числами. Устаревшее мемо при этом остаётся
* на экране до прихода нового — прыжка «значение → скелет → значение» нет.
*/
private scheduleMetrics(): void {
if (this.metricsRefresh || !this.metricsModule || this.metricsFresh()) return;
const generation = this.lifecycleGeneration;
const view = typeof window === 'undefined' ? undefined : window;
const run = () => {
this.metricsRefresh = 0;
if (!this.current(generation)) return;
this.computeMetrics();
this.host.requestUpdate();
};
// Кадр сначала должен быть показан: rAF отдаёт нам момент ПОСЛЕ отрисовки,
// таймер — фолбэк для окружений без rAF (тесты, скрытая вкладка).
this.metricsRefresh = view?.setTimeout(() => {
if (typeof view.requestAnimationFrame === 'function') {
view.requestAnimationFrame(() => view.setTimeout(run, 0));
} else run();
}, 0) as unknown as number || 0;
}
/** @internal — фоновый пересчёт агрегатов; тесты вызывают напрямую (#509). */
public computeMetrics(): void {
const module = this.metricsModule; const module = this.metricsModule;
if (!module) return { deviceCount: null, areaM2: null, now: this.clock }; if (!module) return;
if (!this.deviceMemo || this.deviceMemo.cfgEpoch !== this.host._cfgEpoch if (!this.deviceMemo || this.deviceMemo.cfgEpoch !== this.host._cfgEpoch
|| this.deviceMemo.layoutRev !== this.host._layoutRev || this.deviceMemo.layoutRev !== this.host._layoutRev
|| this.deviceMemo.registryRev !== this.host._haRegistry.revision) { || this.deviceMemo.registryRev !== this.host._haRegistry.revision) {
@@ -709,18 +760,48 @@ export class LoadedSummaryPanelRuntime {
value: this.host._serverCfg ? module.totalCleanFloorAreaM2(this.host._serverCfg, this.host._model) : null, value: this.host._serverCfg ? module.totalCleanFloorAreaM2(this.host._serverCfg, this.host._model) : null,
}; };
} }
return { deviceCount: this.deviceMemo.value, areaM2: this.areaMemo.value, now: this.clock }; }
private metrics(): { deviceCount: number | null; areaM2: number | null; now: Date } {
return {
deviceCount: this.deviceMemo ? this.deviceMemo.value : null,
areaM2: this.areaMemo ? this.areaMemo.value : null,
now: this.clock,
};
}
/**
* Состояние одного значения панели (#509).
*
* `pending` — значение ещё не посчитано: ленивый чанк метрик не пришёл либо
* агрегат считается после кадра. Это НЕ «источник недоступен»: до правки оба
* состояния рисовались текстом `summary.unavailable`, и первый показ панели
* выглядел как ошибка всех источников сразу.
*/
public valueState(value: SummaryPanelValue): { kind: 'ready'; text: string } | { kind: 'pending' } | { kind: 'unavailable' } {
const module = this.metricsModule;
if (!module) return { kind: 'pending' };
if (value.source.type === 'entity') {
const resolved = module.summaryEntityValue(this.host.hass, value.source.entity_id);
return resolved === null ? { kind: 'unavailable' } : { kind: 'ready', text: resolved };
}
const needsAggregate = value.source.key === 'device_count' || value.source.key === 'total_area';
const known = value.source.key === 'device_count' ? this.deviceMemo : this.areaMemo;
if (needsAggregate && !known) {
this.scheduleMetrics();
return { kind: 'pending' };
}
if (needsAggregate) this.scheduleMetrics();
const resolved = module.summarySystemValue(
value.source, this.metrics(), this.host.hass,
langOf(this.host.hass, this.host._config?.language),
);
return resolved === null ? { kind: 'unavailable' } : { kind: 'ready', text: resolved };
} }
private value(value: SummaryPanelValue): string { private value(value: SummaryPanelValue): string {
const module = this.metricsModule; const state = this.valueState(value);
const resolved = !module ? null : value.source.type === 'entity' return state.kind === 'ready' ? state.text : this.t('summary.unavailable');
? module.summaryEntityValue(this.host.hass, value.source.entity_id)
: module.summarySystemValue(
value.source, this.metrics(), this.host.hass,
langOf(this.host.hass, this.host._config?.language),
);
return resolved ?? this.t('summary.unavailable');
} }
private ensureMetrics(): void { private ensureMetrics(): void {
+33
View File
@@ -142,6 +142,39 @@ export const summaryPanelCss = String.raw`
.summary-value strong { min-width: 0; overflow-wrap: anywhere; } .summary-value strong { min-width: 0; overflow-wrap: anywhere; }
.summary-value span { color: var(--secondary-text-color); } .summary-value span { color: var(--secondary-text-color); }
.summary-value strong { text-align: right; font-weight: 700; } .summary-value strong { text-align: right; font-weight: 700; }
/* Значение появляется плавно, а не скачком после скелета (#509). */
.summary-value strong:not(.summary-value-pending) { animation: summary-value-in 140ms ease both; }
/* Скелет: прямоугольник в высоту строки на месте значения. Плашка, сетка и
высота строки те же, что с готовым значением, — панель не прыгает. */
.summary-value-pending {
display: flex;
align-items: center;
justify-content: flex-end;
min-height: 1.2em;
}
.summary-value-pending i {
display: block;
width: 4.5em;
max-width: 100%;
height: 1.2em;
border-radius: 5px;
background: var(--secondary-text-color, #7a7f87);
opacity: 0.18;
animation: summary-skeleton-pulse 1200ms ease-in-out infinite;
}
@keyframes summary-skeleton-pulse {
0%, 100% { opacity: 0.12; }
50% { opacity: 0.28; }
}
@keyframes summary-value-in {
from { opacity: 0; }
to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
/* Прямоугольник остаётся — исчезает только моторика (#509). */
.summary-value-pending i { animation: none; opacity: 0.18; }
.summary-value strong:not(.summary-value-pending) { animation: none; }
}
.summary-empty { margin: 0; padding: 10px 11px; color: var(--secondary-text-color); } .summary-empty { margin: 0; padding: 10px 11px; color: var(--secondary-text-color); }
.summary-measure { .summary-measure {
position: fixed; position: fixed;
+43
View File
@@ -222,3 +222,46 @@ test('#493 late reload and confirmation completions stay outside a new lifecycle
browser.restore(); browser.restore();
} }
}); });
test('#509 AC1/AC2/AC9: значение показывает скелет до готовности, ошибку — только для недоступного источника', () => {
const browser = installBrowserGlobals();
try {
const host = hostFixture();
host._cfgEpoch = 1;
const runtime = new LoadedSummaryPanelRuntime(host);
const state = (value) => runtime.valueState(value);
const deviceCount = { id: 'v1', label: 'Devices', source: { type: 'system', key: 'device_count' } };
const entity = { id: 'v2', label: 'Lamp', source: { type: 'entity', entity_id: 'light.a' } };
// AC1: ленивый чанк ещё не пришёл — скелет, а не «источник недоступен».
assert.deepEqual(state(deviceCount), { kind: 'pending' });
assert.deepEqual(state(entity), { kind: 'pending' });
// Модуль пришёл, агрегат ещё не посчитан — по-прежнему скелет.
runtime.metricsModule = {
summaryEntityValue: (hass, id) => (id === 'light.a' ? 'On' : null),
summarySystemValue: (source, values) => (source.key === 'device_count'
? (values.deviceCount === null ? null : String(values.deviceCount)) : null),
representedHaDeviceIds: () => new Set(['d1', 'd2']),
totalCleanFloorAreaM2: () => 12.5,
};
assert.deepEqual(state(deviceCount), { kind: 'pending' });
// AC2: живая сущность отвечает сразу; отсутствующая — честная ошибка.
assert.deepEqual(state(entity), { kind: 'ready', text: 'On' });
assert.deepEqual(state({ ...entity, source: { type: 'entity', entity_id: 'light.gone' } }), { kind: 'unavailable' });
// Расчёт после кадра: имитируем его завершение.
runtime.computeMetrics();
assert.deepEqual(state(deviceCount), { kind: 'ready', text: '2' });
// AC9: конфигурация изменилась — мемо устарело, но на экране остаётся
// прежнее число, а не скелет; пересчёт запланирован.
host._cfgEpoch = 2;
assert.equal(runtime.metricsFresh(), false);
assert.deepEqual(state(deviceCount), { kind: 'ready', text: '2' });
runtime.computeMetrics();
assert.equal(runtime.metricsFresh(), true);
} finally {
browser.restore();
}
});
+30
View File
@@ -21,6 +21,7 @@ import {
summaryEntityValue, summaryEntityValue,
summarySystemValue, summarySystemValue,
totalCleanFloorAreaM2, totalCleanFloorAreaM2,
spaceWallGeometry,
} from '../test-build/summary-panel-metrics.js'; } from '../test-build/summary-panel-metrics.js';
import { summaryPanelDictionaries, summaryPanelText } from '../test-build/summary-panel-i18n.js'; import { summaryPanelDictionaries, summaryPanelText } from '../test-build/summary-panel-i18n.js';
import { stableSummaryPlacementSlot } from '../test-build/summary-panel-identity.js'; import { stableSummaryPlacementSlot } from '../test-build/summary-panel-identity.js';
@@ -356,3 +357,32 @@ test('#437 entity and system sources preserve real zero and delegate HA formatti
{ deviceCount: 0, areaM2: 0, now: new Date('2026-01-01T00:00:00Z') }, hass, 'en', { deviceCount: 0, areaM2: 0, now: new Date('2026-01-01T00:00:00Z') }, hass, 'en',
), '0.0 m²'); ), '0.0 m²');
}); });
test('#509 AC3: площадь считает геометрию стен один раз на пространство, результат не меняется', () => {
const roomsOf = (prefix) => [
{ id: `${prefix}1`, name: 'A', poly: [[0, 0], [100, 0], [100, 100], [0, 100]] },
{ id: `${prefix}2`, name: 'B', poly: [[50, 0], [150, 0], [150, 100], [50, 100]] },
];
const space = (id, prefix) => ({
id, title: id, cellCm: 5, vb: [0, 0, 1000, 1000], bg: null,
rooms: roomsOf(prefix), wall_segments: [], partitions: [], wall_columns: [],
});
const raw = (id, prefix) => ({
id, title: id, cell_cm: 5, view_box: [0, 0, 1, 1], rooms: roomsOf(prefix),
wall_segments: [], partitions: [], wall_columns: [], openings: [], walls: [],
});
const model = [space('f1', 'r'), space('f2', 'q')];
const config = { spaces: [raw('f1', 'r'), raw('f2', 'q')], markers: [], settings: {} };
const calls = [];
const spy = (spaceModel, prepared) => {
calls.push(spaceModel.id);
return spaceWallGeometry(spaceModel, prepared);
};
const withSpy = totalCleanFloorAreaM2(config, model, spy);
// Один вызов на ПРОСТРАНСТВО, а не на комнату: без этого innerContourForRoom
// объединял кладку заново для каждой комнаты (11 с на большом доме, #509).
assert.deepEqual(calls, ['f1', 'f2']);
assert.equal(withSpy, totalCleanFloorAreaM2(config, model), 'общая геометрия не меняет результат');
assert.equal(withSpy, 4.32);
});