mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 21:58:56 +00:00
@@ -1,182 +1,182 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406",
|
||||
"fingerprint": "533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 297478,
|
||||
"initialViewGzipBytes": 297613,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"houseplan-assets/live-interaction-runtime-BLkoEJ76.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"houseplan-assets/live-interaction-runtime-D_Cpramq.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
],
|
||||
"lazyGzipBytes": 244317,
|
||||
"lazyGzipBytes": 246104,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 181158,
|
||||
"lazyEditorGzipBytes": 182097,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14317,
|
||||
"lazyOnboardingGzipBytes": 14314,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js"
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 49942,
|
||||
"lazyLocaleGzipBytes": 49944,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"sha256": "2d2f927c8cb8b5e89217dbb4f4493250618eb85c52142ea97100dbd99159f298",
|
||||
"path": "houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"sha256": "c756470e82ef38e899c948896a49dd42cfc830edf400e345cacf6dd6ae3b3708",
|
||||
"rawBytes": 21592,
|
||||
"gzipBytes": 7359,
|
||||
"gzipBytes": 7356,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CFOjLpp6.js",
|
||||
"sha256": "80bf83a74e97e2715bde9b7a65c9202e013d7864ab24997855b2eff8eeb1e46c",
|
||||
"path": "houseplan-assets/de-BcyCnUvK.js",
|
||||
"sha256": "8895e7e4ae7e81197a335d4f27ed2d7576a233c9df3d0ed8532775af9b719302",
|
||||
"rawBytes": 83907,
|
||||
"gzipBytes": 25246,
|
||||
"gzipBytes": 25247,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-DsfH0y9C.js",
|
||||
"sha256": "622afb272e44b005bb5070b8acb6f76c469069fbcd6e6399c0c1437425490017",
|
||||
"path": "houseplan-assets/editor-BmoXysmF.js",
|
||||
"sha256": "eb7e71dffa805f11f3ec6d44263a9719a310ec26585602e67517e3ec4b97aa75",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1583,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-D_LHTTjd.js",
|
||||
"sha256": "1a5256323034c314188f078265e27fb7453af4b4f75c38c833eca3685c637fef",
|
||||
"path": "houseplan-assets/fr-BQv8XYpX.js",
|
||||
"sha256": "772df4e7681d72395c6dfb16a99d53b738857792f92f158bf929dc717c01a33f",
|
||||
"rawBytes": 85967,
|
||||
"gzipBytes": 24696,
|
||||
"gzipBytes": 24697,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"sha256": "6bf3f534f13750e9a667c534e6b389314a6fe9332ae96ed4502c6ac8a6c72b12",
|
||||
"rawBytes": 1058121,
|
||||
"gzipBytes": 296682,
|
||||
"path": "houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"sha256": "ec413ccf39fb459a872cad19feba2a26bb5e4918aed30231b4226be29a13279d",
|
||||
"rawBytes": 1058523,
|
||||
"gzipBytes": 296817,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"houseplan-assets/live-interaction-runtime-BLkoEJ76.js"
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"houseplan-assets/live-interaction-runtime-D_Cpramq.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"sha256": "453615ec48b49e236b31662c34545f1ce10f7e564b9c176ddcefd4d021ebeb23",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"sha256": "f33eacfaeb67ebd970cd896a5c735546544ab681b9a8d70db3292e3e5f381c47",
|
||||
"rawBytes": 630790,
|
||||
"gzipBytes": 167369,
|
||||
"gzipBytes": 167372,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"sha256": "162cf2cd86f30e0201a52c01e75347a12fda32b6b37c49985fec792aa9825cab",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"sha256": "166dadd4a99098e6e87ed060612f18acaae35bfb9151a9b48320aa47ca0ffdff",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6958,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"sha256": "6f88cb32ee1fe3080e55489e7363e32e9e0dac2954cc69de943f62bd57de988e",
|
||||
"rawBytes": 6006,
|
||||
"gzipBytes": 2371,
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"sha256": "3b63bfe2f80678fa073e0ef48d5a240270b3a88048991604a5cef42a21d3da2b",
|
||||
"rawBytes": 8593,
|
||||
"gzipBytes": 3217,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-BLkoEJ76.js",
|
||||
"sha256": "9b808162204030f58ff08fb21879d8b633fe701d1c1b5c6113d102093289a99e",
|
||||
"path": "houseplan-assets/live-interaction-runtime-D_Cpramq.js",
|
||||
"sha256": "3ee5dbdc02acc76aa11e1320914fef02e6c71bf919c7720b6cfe7bfe04555306",
|
||||
"rawBytes": 5196,
|
||||
"gzipBytes": 2126,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"sha256": "688604a9e2f9348c00619e20aeff3a6cb3024ca331fa655995cd714105ebba99",
|
||||
"rawBytes": 12382,
|
||||
"gzipBytes": 4847,
|
||||
"path": "houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"sha256": "c3242a28cb4ca5e141cef9b25ef6f26811bd484bb1277ddfbfacae138d52413b",
|
||||
"rawBytes": 15252,
|
||||
"gzipBytes": 5786,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js",
|
||||
"sha256": "cf3c4a14ae1eecd545c2cf09fcce42fec057f9498ed761fe295eaa087c9e4466",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js",
|
||||
"sha256": "3a4346efc23ebe2ad722bc61dbccaa21531da9baadb23e052dbcded193b07458",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1762,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "9412b5f3b0c7fd701d32528f84d3c74277e63c9f1ea0ff73edbc7de930af213e",
|
||||
"sha256": "0dd55163b547b8bcc1caff73c34f7a8cb6c8c4cc66fed7f5d7b8a7eada664cfb",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 796,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-5IGoG-3Y.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-BfEhiIMd.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}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+22
-21
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
-36
@@ -1,36 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{v as e,A as t,dx as i,w as s,c as o,l as r,u as n}from"./houseplan-card-5IGoG-3Y.js";import{a,r as h,t as d}from"./zigbee-topology-rpeu0cwQ.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-D_aGI6IY.js";const l={revision:0,topologies:[],states:{}};class v extends e{constructor(){super(...arguments),this.devices=[],this.currentSpace="",this._runtime=l,this._hovered="",this._pointerOver=e=>{if(!this._mouseAllowed(e))return void this._clear();const t=this._deviceFromEvent(e),i=t?.dataset.id||"";i&&i!==this._hovered&&(this._hovered=i,this.requestUpdate())},this._pointerOut=e=>{const t=this._deviceFromEvent(e);if(!t||t.dataset.id!==this._hovered)return;const i=e.relatedTarget instanceof Element?e.relatedTarget.closest('[data-hp="device"]'):null;i?.dataset.id!==this._hovered&&this._clear()},this._pointerDown=e=>{"mouse"!==e.pointerType&&this._clear()}}connectedCallback(){super.connectedCallback();const e=this.getRootNode();e.host&&"undefined"!=typeof MutationObserver&&(this._hoverGateObserver=new MutationObserver(()=>{e.host.hasAttribute("data-pointer-hover")||this._clear()}),this._hoverGateObserver.observe(e.host,{attributes:!0,attributeFilter:["data-pointer-hover"]})),queueMicrotask(()=>this._connectParent())}disconnectedCallback(){this._release?.(),this._release=void 0,this._disconnectParent(),this._hoverGateObserver?.disconnect(),this._hoverGateObserver=void 0,this._hovered="",super.disconnectedCallback()}updated(e){e.has("hass")&&(this._release?.(),this._runtime=p(this.hass),this._mappedMemo=void 0,this._release=c(this.hass,()=>{this._runtime=p(this.hass),this._mappedMemo=void 0,this.requestUpdate()})),(e.has("currentSpace")||e.has("devices")||e.has("registry"))&&(this._mappedMemo=void 0,this.devices.some(e=>e.id===this._hovered&&e.space===this.currentSpace)||(this._hovered=""))}_connectParent(){const e=this.parentElement;e&&e!==this._parent&&(this._disconnectParent(),this._parent=e,e.addEventListener("pointerover",this._pointerOver),e.addEventListener("pointerout",this._pointerOut),e.addEventListener("pointerdown",this._pointerDown,!0))}_disconnectParent(){this._parent?.removeEventListener("pointerover",this._pointerOver),this._parent?.removeEventListener("pointerout",this._pointerOut),this._parent?.removeEventListener("pointerdown",this._pointerDown,!0),this._parent=void 0}_deviceFromEvent(e){for(const t of e.composedPath())if(t instanceof HTMLElement&&"device"===t.dataset.hp)return t;return null}_mouseAllowed(e){const t=this.getRootNode();return"mouse"===e.pointerType&&t.host?.hasAttribute?.("data-pointer-hover")}_clear(){this._hovered&&(this._hovered="",this.requestUpdate())}_position(e){const t=[...this._parent?.querySelectorAll(".dev[data-id]")||[]].find(t=>t.dataset.id===e);if(!t)return null;const i=Number.parseFloat(t.style.left),s=Number.parseFloat(t.style.top),o=t.getBoundingClientRect();return Number.isFinite(i)&&Number.isFinite(s)?{x:i,y:s,width:o.width,height:o.height}:null}render(){if(!this._hovered||!this.registry||!this._runtime.topologies.length)return t;const e=this._mappedMemo,n=e&&e.runtime===this._runtime&&e.devices===this.devices&&e.registry===this.registry?e.mapped:a(this._runtime.topologies,this.devices,this.registry);e&&n===e.mapped||(this._mappedMemo={runtime:this._runtime,devices:this.devices,registry:this.registry,mapped:n});const p=h(n,this.currentSpace,this._hovered),c=this._position(this._hovered);if(!c)return t;const l=p.lines.map(e=>({...e,point:this._position(e.neighborMarkerId)})).filter(e=>!!e.point);return o`
|
||||
${l.length?s`<svg viewBox="0 0 100 100" preserveAspectRatio="none"
|
||||
aria-hidden="true" data-hp="zigbee-topology-lines">
|
||||
${l.map(e=>s`<line x1=${c.x} y1=${c.y}
|
||||
x2=${e.point.x} y2=${e.point.y}
|
||||
stroke=${void 0===e.lqi?"rgba(145,155,165,.85)":i(e.lqi)}
|
||||
stroke-width=${void 0===e.lqi?2:2.2}
|
||||
stroke-dasharray=${void 0===e.lqi?"5 5":t} opacity=".9"></line>`)}
|
||||
</svg>`:t}
|
||||
${l.map(e=>o`<div class="halo" data-hp="zigbee-topology-neighbor"
|
||||
data-id=${e.neighborMarkerId} style="left:${e.point.x}%;top:${e.point.y}%;width:${1.22*e.point.width}px;height:${1.22*e.point.height}px"></div>`)}
|
||||
${p.remoteCount?o`<div class="remote" data-hp="zigbee-topology-remote"
|
||||
style="left:${c.x}%;top:${c.y}%">${d(r(this.hass),"remote_count",{n:p.remoteCount})}</div>`:t}
|
||||
`}}v.properties={hass:{attribute:!1},devices:{attribute:!1},registry:{attribute:!1},currentSpace:{type:String,attribute:"current-space"},viewKey:{attribute:!1}},v.styles=n`
|
||||
:host { position: absolute; inset: 0; z-index: 5; display: block; pointer-events: none; }
|
||||
svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
||||
line { vector-effect: non-scaling-stroke; stroke-linecap: round; }
|
||||
.halo {
|
||||
position: absolute; width: calc(var(--device-base-size, 4cqw) * 1.22);
|
||||
height: calc(var(--device-base-size, 4cqw) * 1.22); transform: translate(-50%, -50%);
|
||||
box-sizing: border-box; border: 2px solid rgba(120, 190, 220, .82); border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(120, 190, 220, .16);
|
||||
}
|
||||
.remote {
|
||||
position: absolute; transform: translate(12px, calc(-100% - 12px));
|
||||
border: 1px solid rgba(255,255,255,.7); border-radius: 999px;
|
||||
padding: 3px 7px; color: #fff; background: rgba(28,31,36,.88);
|
||||
box-shadow: 0 2px 7px rgba(0,0,0,.28); white-space: nowrap;
|
||||
font: 600 11px/1.2 system-ui, sans-serif;
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
line { stroke: Highlight !important; }
|
||||
.halo { border-color: Highlight; box-shadow: none; }
|
||||
.remote { color: CanvasText; background: Canvas; border-color: CanvasText; }
|
||||
}
|
||||
`,customElements.get("hp-zigbee-topology-overlay")||customElements.define("hp-zigbee-topology-overlay",v);export{v as HpZigbeeTopologyOverlay};
|
||||
+52
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{n as t,b as n}from"./zigbee-topology-rpeu0cwQ.js";import{I as e}from"./houseplan-card-5IGoG-3Y.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 m(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 g(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,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=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&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(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 m(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,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";import{n as t,b as n}from"./zigbee-topology-Bvg-A1og.js";import{I as e}from"./houseplan-card-BfEhiIMd.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 m(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 g(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,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=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&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(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 m(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,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";try{await import("./houseplan-assets/houseplan-card-5IGoG-3Y.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 entry: the main chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";try{await import("./houseplan-assets/houseplan-card-BfEhiIMd.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 entry: the main chunk is unavailable",e)}
|
||||
|
||||
@@ -4,11 +4,11 @@ const { page, browser } = await launch();
|
||||
const result = await page.evaluate(async () => {
|
||||
const card = window.__card;
|
||||
const root = () => card.shadowRoot || card.renderRoot;
|
||||
const wait = (predicate, timeout = 3000) => new Promise((resolve, reject) => {
|
||||
const wait = (predicate, label, timeout = 3000) => new Promise((resolve, reject) => {
|
||||
const started = performance.now();
|
||||
const tick = () => {
|
||||
if (predicate()) return resolve();
|
||||
if (performance.now() - started > timeout) return reject(new Error('topology smoke timeout'));
|
||||
if (performance.now() - started > timeout) return reject(new Error(`topology smoke timeout: ${label}`));
|
||||
setTimeout(tick, 20);
|
||||
};
|
||||
tick();
|
||||
@@ -29,37 +29,46 @@ const result = await page.evaluate(async () => {
|
||||
if (message.type !== 'zha/devices') return originalHass.callWS(message);
|
||||
zhaCalls++;
|
||||
return [
|
||||
{ ieee: '00124b0000000001', device_reg_id: 'd_light1', neighbors: [
|
||||
{ ieee: '00124b0000000002', lqi: 180 },
|
||||
{ ieee: '00124b0000000003', lqi: 80 },
|
||||
{ ieee: '00124b0000000001', device_reg_id: 'd_light1', device_type: 'Router', neighbors: [
|
||||
{ ieee: '00124b0000000002', lqi: 180, relationship: 'Parent' },
|
||||
{ ieee: '00124b0000000003', lqi: 80, relationship: 'Child' },
|
||||
] },
|
||||
{ ieee: '00124b0000000002', device_reg_id: 'd_lamp', neighbors: [] },
|
||||
{ ieee: '00124b0000000003', device_reg_id: 'd_mower', neighbors: [] },
|
||||
{ ieee: '00124b0000000004', device_reg_id: 'd_temp', neighbors: [
|
||||
{ ieee: '00124b0000000005', lqi: 20 },
|
||||
{ ieee: '00124b0000000002', device_reg_id: 'd_lamp', device_type: 'Coordinator', neighbors: [
|
||||
{ ieee: '00124b0000000001', lqi: 170, relationship: 'Child' },
|
||||
{ ieee: '00124b0000000006', lqi: 140, relationship: 'Child' },
|
||||
] },
|
||||
{ ieee: '00124b0000000003', device_reg_id: 'd_mower', device_type: 'EndDevice', neighbors: [
|
||||
{ ieee: '00124b0000000001', lqi: 75, relationship: 'Parent' },
|
||||
] },
|
||||
{ ieee: '00124b0000000004', device_reg_id: 'd_temp', device_type: 'EndDevice', neighbors: [
|
||||
{ ieee: '00124b0000000006', lqi: 20, relationship: 'Parent' },
|
||||
] },
|
||||
{ ieee: '00124b0000000005', device_reg_id: 'd_tv', device_type: 'EndDevice', neighbors: [] },
|
||||
{ ieee: '00124b0000000006', device_reg_id: 'not_on_plan', device_type: 'Router', neighbors: [
|
||||
{ ieee: '00124b0000000002', lqi: 130, relationship: 'Parent' },
|
||||
{ ieee: '00124b0000000004', lqi: 25, relationship: 'Child' },
|
||||
] },
|
||||
{ ieee: '00124b0000000005', device_reg_id: 'd_tv', neighbors: [] },
|
||||
];
|
||||
} };
|
||||
await card._ensureEditorRuntime();
|
||||
card._openSettingsDialog();
|
||||
await card.updateComplete;
|
||||
let settings = root().querySelector('hp-zigbee-topology-settings');
|
||||
await wait(() => !!settings?.shadowRoot);
|
||||
await wait(() => !!settings?.shadowRoot, 'settings mounted');
|
||||
settings._emit({ enabled: true, z2mBaseTopics: [] });
|
||||
await card.updateComplete;
|
||||
await card._saveSettingsDialog();
|
||||
await card.updateComplete;
|
||||
await wait(() => !!root().querySelector('hp-zigbee-topology-overlay'));
|
||||
await wait(() => !!root().querySelector('hp-zigbee-topology-overlay'), 'overlay mounted');
|
||||
out.settingPersists = card._serverCfg.settings.zigbee_topology?.enabled === true;
|
||||
out.enableDoesNotFetch = zhaCalls === 0;
|
||||
|
||||
card._openSettingsDialog();
|
||||
await card.updateComplete;
|
||||
settings = root().querySelector('hp-zigbee-topology-settings');
|
||||
await wait(() => !!settings?.shadowRoot?.querySelector('button'));
|
||||
await wait(() => !!settings?.shadowRoot?.querySelector('button'), 'settings button');
|
||||
await settings._readZha();
|
||||
await wait(() => settings._snapshot?.states?.zha?.phase === 'ready');
|
||||
await wait(() => settings._snapshot?.states?.zha?.phase === 'ready', 'ZHA ready');
|
||||
out.explicitZhaRead = zhaCalls === 1;
|
||||
card._settingsDialog = null;
|
||||
card.requestUpdate();
|
||||
@@ -68,25 +77,72 @@ const result = await page.evaluate(async () => {
|
||||
const source = root().querySelector('.dev[data-id="d_light1"]');
|
||||
source.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => root().querySelector('hp-zigbee-topology-overlay')?.shadowRoot
|
||||
?.querySelector('[data-hp="zigbee-topology-lines"]'));
|
||||
?.querySelector('[data-hp="zigbee-topology-lines"]'), 'local route');
|
||||
const overlay = root().querySelector('hp-zigbee-topology-overlay');
|
||||
out.incidentOnly = overlay.shadowRoot.querySelectorAll('line').length === 1
|
||||
&& overlay.shadowRoot.querySelectorAll('[data-hp="zigbee-topology-neighbor"]').length === 1;
|
||||
const routeLine = overlay.shadowRoot.querySelector('line[data-direction="toward-neighbor"]');
|
||||
const routeArrow = overlay.shadowRoot.querySelector('[data-hp="zigbee-topology-arrow"]');
|
||||
const lineBox = routeLine?.getBoundingClientRect();
|
||||
const arrowBox = routeArrow?.getBoundingClientRect();
|
||||
out.localRouteArrow = !!routeLine && routeArrow?.getAttribute('data-direction') === 'toward-neighbor'
|
||||
&& !!lineBox && !!arrowBox && arrowBox.width > 3 && arrowBox.height > 3;
|
||||
out.crossSpaceCount = overlay.shadowRoot.querySelector('[data-hp="zigbee-topology-remote"]')
|
||||
?.textContent.trim() === '+1 in other spaces';
|
||||
out.pointerTransparent = getComputedStyle(overlay).pointerEvents === 'none'
|
||||
&& getComputedStyle(overlay.shadowRoot.querySelector('[data-hp="zigbee-topology-remote"]')).pointerEvents === 'none';
|
||||
|
||||
source.dispatchEvent(mouse('pointerout', root().querySelector('.stage')));
|
||||
card._commitSpace('garden', true);
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
out.leaveClears = !overlay.shadowRoot.querySelector('line');
|
||||
source.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => overlay.shadowRoot.querySelector('line'));
|
||||
await wait(() => root().querySelector('.dev[data-id="d_mower"]'), 'garden marker');
|
||||
const remoteChild = root().querySelector('.dev[data-id="d_mower"]');
|
||||
remoteChild.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => root().querySelector('hp-zigbee-topology-overlay')?.shadowRoot
|
||||
?.querySelector('[data-hp="zigbee-topology-parent-bubble"]'), 'remote parent bubble');
|
||||
let activeOverlay = root().querySelector('hp-zigbee-topology-overlay');
|
||||
const firstTitle = card._serverCfg.spaces.find((space) => space.id === 'f1')?.title;
|
||||
out.remoteParentBubble = activeOverlay.shadowRoot
|
||||
.querySelector('[data-hp="zigbee-topology-parent-bubble"]')?.textContent.trim() === firstTitle
|
||||
&& activeOverlay.shadowRoot.querySelector('[data-hp="zigbee-topology-parent-bubble"]')
|
||||
?.getAttribute('data-kind') === 'remote-space'
|
||||
&& !!activeOverlay.shadowRoot.querySelector('[data-hp="zigbee-topology-parent-arrow"]')
|
||||
&& !activeOverlay.shadowRoot.querySelector('[data-hp="zigbee-topology-remote"]');
|
||||
|
||||
card._commitSpace('f1', true);
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
await wait(() => root().querySelector('.dev[data-id="d_temp"]'), 'temperature marker');
|
||||
const temperature = root().querySelector('.dev[data-id="d_temp"]');
|
||||
temperature.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => root().querySelector('hp-zigbee-topology-overlay')?.shadowRoot
|
||||
?.querySelector('[data-kind="unplaced-device"]'), 'unplaced device bubble');
|
||||
activeOverlay = root().querySelector('hp-zigbee-topology-overlay');
|
||||
out.unplacedDeviceBubble = activeOverlay.shadowRoot.querySelector('[data-kind="unplaced-device"]')
|
||||
?.textContent.trim() === 'device is not on the plan';
|
||||
|
||||
activeOverlay.devices = activeOverlay.devices.map((device) => (
|
||||
device.id === 'd_lamp' ? { ...device, hidden: true } : device
|
||||
));
|
||||
await activeOverlay.updateComplete;
|
||||
const router = root().querySelector('.dev[data-id="d_light1"]');
|
||||
router.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => activeOverlay.shadowRoot.querySelector('[data-kind="unplaced-coordinator"]'),
|
||||
'unplaced coordinator bubble');
|
||||
out.unplacedCoordinatorBubble = activeOverlay.shadowRoot
|
||||
.querySelector('[data-kind="unplaced-coordinator"]')?.textContent.trim()
|
||||
=== 'coordinator is not on the plan';
|
||||
|
||||
router.dispatchEvent(mouse('pointerout', root().querySelector('.stage')));
|
||||
await activeOverlay.updateComplete;
|
||||
out.leaveClears = !activeOverlay.shadowRoot.querySelector('line');
|
||||
router.dispatchEvent(mouse('pointerover'));
|
||||
await wait(() => activeOverlay.shadowRoot.querySelector('line'), 'route restored');
|
||||
root().querySelector('.stage').dispatchEvent(new PointerEvent('pointermove', {
|
||||
pointerType: 'touch', bubbles: true, composed: true, pointerId: 7,
|
||||
}));
|
||||
await wait(() => !overlay.shadowRoot.querySelector('line'));
|
||||
out.touchClears = !overlay.shadowRoot.querySelector('line');
|
||||
await wait(() => !activeOverlay.shadowRoot.querySelector('line'), 'touch clears');
|
||||
out.touchClears = !activeOverlay.shadowRoot.querySelector('line');
|
||||
|
||||
card._setMode('plan');
|
||||
await card.updateComplete;
|
||||
|
||||
Vendored
+81
-81
@@ -1,182 +1,182 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406",
|
||||
"fingerprint": "533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 297478,
|
||||
"initialViewGzipBytes": 297613,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"houseplan-assets/live-interaction-runtime-BLkoEJ76.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"houseplan-assets/live-interaction-runtime-D_Cpramq.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
],
|
||||
"lazyGzipBytes": 244317,
|
||||
"lazyGzipBytes": 246104,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 181158,
|
||||
"lazyEditorGzipBytes": 182097,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14317,
|
||||
"lazyOnboardingGzipBytes": 14314,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js"
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 49942,
|
||||
"lazyLocaleGzipBytes": 49944,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"sha256": "2d2f927c8cb8b5e89217dbb4f4493250618eb85c52142ea97100dbd99159f298",
|
||||
"path": "houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"sha256": "c756470e82ef38e899c948896a49dd42cfc830edf400e345cacf6dd6ae3b3708",
|
||||
"rawBytes": 21592,
|
||||
"gzipBytes": 7359,
|
||||
"gzipBytes": 7356,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CFOjLpp6.js",
|
||||
"sha256": "80bf83a74e97e2715bde9b7a65c9202e013d7864ab24997855b2eff8eeb1e46c",
|
||||
"path": "houseplan-assets/de-BcyCnUvK.js",
|
||||
"sha256": "8895e7e4ae7e81197a335d4f27ed2d7576a233c9df3d0ed8532775af9b719302",
|
||||
"rawBytes": 83907,
|
||||
"gzipBytes": 25246,
|
||||
"gzipBytes": 25247,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-DsfH0y9C.js",
|
||||
"sha256": "622afb272e44b005bb5070b8acb6f76c469069fbcd6e6399c0c1437425490017",
|
||||
"path": "houseplan-assets/editor-BmoXysmF.js",
|
||||
"sha256": "eb7e71dffa805f11f3ec6d44263a9719a310ec26585602e67517e3ec4b97aa75",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1583,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-D_LHTTjd.js",
|
||||
"sha256": "1a5256323034c314188f078265e27fb7453af4b4f75c38c833eca3685c637fef",
|
||||
"path": "houseplan-assets/fr-BQv8XYpX.js",
|
||||
"sha256": "772df4e7681d72395c6dfb16a99d53b738857792f92f158bf929dc717c01a33f",
|
||||
"rawBytes": 85967,
|
||||
"gzipBytes": 24696,
|
||||
"gzipBytes": 24697,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"sha256": "6bf3f534f13750e9a667c534e6b389314a6fe9332ae96ed4502c6ac8a6c72b12",
|
||||
"rawBytes": 1058121,
|
||||
"gzipBytes": 296682,
|
||||
"path": "houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"sha256": "ec413ccf39fb459a872cad19feba2a26bb5e4918aed30231b4226be29a13279d",
|
||||
"rawBytes": 1058523,
|
||||
"gzipBytes": 296817,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CFOjLpp6.js",
|
||||
"houseplan-assets/editor-DsfH0y9C.js",
|
||||
"houseplan-assets/fr-D_LHTTjd.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"houseplan-assets/live-interaction-runtime-BLkoEJ76.js"
|
||||
"houseplan-assets/de-BcyCnUvK.js",
|
||||
"houseplan-assets/editor-BmoXysmF.js",
|
||||
"houseplan-assets/fr-BQv8XYpX.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"houseplan-assets/live-interaction-runtime-D_Cpramq.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-DcWBzfnw.js",
|
||||
"sha256": "453615ec48b49e236b31662c34545f1ce10f7e564b9c176ddcefd4d021ebeb23",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-OM_-QLOC.js",
|
||||
"sha256": "f33eacfaeb67ebd970cd896a5c735546544ab681b9a8d70db3292e3e5f381c47",
|
||||
"rawBytes": 630790,
|
||||
"gzipBytes": 167369,
|
||||
"gzipBytes": 167372,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DCANv9te.js",
|
||||
"sha256": "162cf2cd86f30e0201a52c01e75347a12fda32b6b37c49985fec792aa9825cab",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-C6c_QeHE.js",
|
||||
"sha256": "166dadd4a99098e6e87ed060612f18acaae35bfb9151a9b48320aa47ca0ffdff",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6958,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-j3BiTfHY.js",
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/backdrop-pick-CqP55HFV.js",
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-BrSsp4IO.js",
|
||||
"sha256": "6f88cb32ee1fe3080e55489e7363e32e9e0dac2954cc69de943f62bd57de988e",
|
||||
"rawBytes": 6006,
|
||||
"gzipBytes": 2371,
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-Q8RXHre6.js",
|
||||
"sha256": "3b63bfe2f80678fa073e0ef48d5a240270b3a88048991604a5cef42a21d3da2b",
|
||||
"rawBytes": 8593,
|
||||
"gzipBytes": 3217,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-BLkoEJ76.js",
|
||||
"sha256": "9b808162204030f58ff08fb21879d8b633fe701d1c1b5c6113d102093289a99e",
|
||||
"path": "houseplan-assets/live-interaction-runtime-D_Cpramq.js",
|
||||
"sha256": "3ee5dbdc02acc76aa11e1320914fef02e6c71bf919c7720b6cfe7bfe04555306",
|
||||
"rawBytes": 5196,
|
||||
"gzipBytes": 2126,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-rpeu0cwQ.js",
|
||||
"sha256": "688604a9e2f9348c00619e20aeff3a6cb3024ca331fa655995cd714105ebba99",
|
||||
"rawBytes": 12382,
|
||||
"gzipBytes": 4847,
|
||||
"path": "houseplan-assets/zigbee-topology-Bvg-A1og.js",
|
||||
"sha256": "c3242a28cb4ca5e141cef9b25ef6f26811bd484bb1277ddfbfacae138d52413b",
|
||||
"rawBytes": 15252,
|
||||
"gzipBytes": 5786,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-D_aGI6IY.js",
|
||||
"sha256": "cf3c4a14ae1eecd545c2cf09fcce42fec057f9498ed761fe295eaa087c9e4466",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-B2u6gOwO.js",
|
||||
"sha256": "3a4346efc23ebe2ad722bc61dbccaa21531da9baadb23e052dbcded193b07458",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1762,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js",
|
||||
"houseplan-assets/zigbee-topology-rpeu0cwQ.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js",
|
||||
"houseplan-assets/zigbee-topology-Bvg-A1og.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "9412b5f3b0c7fd701d32528f84d3c74277e63c9f1ea0ff73edbc7de930af213e",
|
||||
"sha256": "0dd55163b547b8bcc1caff73c34f7a8cb6c8c4cc66fed7f5d7b8a7eada664cfb",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 796,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-5IGoG-3Y.js"
|
||||
"houseplan-assets/houseplan-card-BfEhiIMd.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-5IGoG-3Y.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-BfEhiIMd.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}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+22
-21
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
@@ -1,36 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{v as e,A as t,dx as i,w as s,c as o,l as r,u as n}from"./houseplan-card-5IGoG-3Y.js";import{a,r as h,t as d}from"./zigbee-topology-rpeu0cwQ.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-D_aGI6IY.js";const l={revision:0,topologies:[],states:{}};class v extends e{constructor(){super(...arguments),this.devices=[],this.currentSpace="",this._runtime=l,this._hovered="",this._pointerOver=e=>{if(!this._mouseAllowed(e))return void this._clear();const t=this._deviceFromEvent(e),i=t?.dataset.id||"";i&&i!==this._hovered&&(this._hovered=i,this.requestUpdate())},this._pointerOut=e=>{const t=this._deviceFromEvent(e);if(!t||t.dataset.id!==this._hovered)return;const i=e.relatedTarget instanceof Element?e.relatedTarget.closest('[data-hp="device"]'):null;i?.dataset.id!==this._hovered&&this._clear()},this._pointerDown=e=>{"mouse"!==e.pointerType&&this._clear()}}connectedCallback(){super.connectedCallback();const e=this.getRootNode();e.host&&"undefined"!=typeof MutationObserver&&(this._hoverGateObserver=new MutationObserver(()=>{e.host.hasAttribute("data-pointer-hover")||this._clear()}),this._hoverGateObserver.observe(e.host,{attributes:!0,attributeFilter:["data-pointer-hover"]})),queueMicrotask(()=>this._connectParent())}disconnectedCallback(){this._release?.(),this._release=void 0,this._disconnectParent(),this._hoverGateObserver?.disconnect(),this._hoverGateObserver=void 0,this._hovered="",super.disconnectedCallback()}updated(e){e.has("hass")&&(this._release?.(),this._runtime=p(this.hass),this._mappedMemo=void 0,this._release=c(this.hass,()=>{this._runtime=p(this.hass),this._mappedMemo=void 0,this.requestUpdate()})),(e.has("currentSpace")||e.has("devices")||e.has("registry"))&&(this._mappedMemo=void 0,this.devices.some(e=>e.id===this._hovered&&e.space===this.currentSpace)||(this._hovered=""))}_connectParent(){const e=this.parentElement;e&&e!==this._parent&&(this._disconnectParent(),this._parent=e,e.addEventListener("pointerover",this._pointerOver),e.addEventListener("pointerout",this._pointerOut),e.addEventListener("pointerdown",this._pointerDown,!0))}_disconnectParent(){this._parent?.removeEventListener("pointerover",this._pointerOver),this._parent?.removeEventListener("pointerout",this._pointerOut),this._parent?.removeEventListener("pointerdown",this._pointerDown,!0),this._parent=void 0}_deviceFromEvent(e){for(const t of e.composedPath())if(t instanceof HTMLElement&&"device"===t.dataset.hp)return t;return null}_mouseAllowed(e){const t=this.getRootNode();return"mouse"===e.pointerType&&t.host?.hasAttribute?.("data-pointer-hover")}_clear(){this._hovered&&(this._hovered="",this.requestUpdate())}_position(e){const t=[...this._parent?.querySelectorAll(".dev[data-id]")||[]].find(t=>t.dataset.id===e);if(!t)return null;const i=Number.parseFloat(t.style.left),s=Number.parseFloat(t.style.top),o=t.getBoundingClientRect();return Number.isFinite(i)&&Number.isFinite(s)?{x:i,y:s,width:o.width,height:o.height}:null}render(){if(!this._hovered||!this.registry||!this._runtime.topologies.length)return t;const e=this._mappedMemo,n=e&&e.runtime===this._runtime&&e.devices===this.devices&&e.registry===this.registry?e.mapped:a(this._runtime.topologies,this.devices,this.registry);e&&n===e.mapped||(this._mappedMemo={runtime:this._runtime,devices:this.devices,registry:this.registry,mapped:n});const p=h(n,this.currentSpace,this._hovered),c=this._position(this._hovered);if(!c)return t;const l=p.lines.map(e=>({...e,point:this._position(e.neighborMarkerId)})).filter(e=>!!e.point);return o`
|
||||
${l.length?s`<svg viewBox="0 0 100 100" preserveAspectRatio="none"
|
||||
aria-hidden="true" data-hp="zigbee-topology-lines">
|
||||
${l.map(e=>s`<line x1=${c.x} y1=${c.y}
|
||||
x2=${e.point.x} y2=${e.point.y}
|
||||
stroke=${void 0===e.lqi?"rgba(145,155,165,.85)":i(e.lqi)}
|
||||
stroke-width=${void 0===e.lqi?2:2.2}
|
||||
stroke-dasharray=${void 0===e.lqi?"5 5":t} opacity=".9"></line>`)}
|
||||
</svg>`:t}
|
||||
${l.map(e=>o`<div class="halo" data-hp="zigbee-topology-neighbor"
|
||||
data-id=${e.neighborMarkerId} style="left:${e.point.x}%;top:${e.point.y}%;width:${1.22*e.point.width}px;height:${1.22*e.point.height}px"></div>`)}
|
||||
${p.remoteCount?o`<div class="remote" data-hp="zigbee-topology-remote"
|
||||
style="left:${c.x}%;top:${c.y}%">${d(r(this.hass),"remote_count",{n:p.remoteCount})}</div>`:t}
|
||||
`}}v.properties={hass:{attribute:!1},devices:{attribute:!1},registry:{attribute:!1},currentSpace:{type:String,attribute:"current-space"},viewKey:{attribute:!1}},v.styles=n`
|
||||
:host { position: absolute; inset: 0; z-index: 5; display: block; pointer-events: none; }
|
||||
svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
||||
line { vector-effect: non-scaling-stroke; stroke-linecap: round; }
|
||||
.halo {
|
||||
position: absolute; width: calc(var(--device-base-size, 4cqw) * 1.22);
|
||||
height: calc(var(--device-base-size, 4cqw) * 1.22); transform: translate(-50%, -50%);
|
||||
box-sizing: border-box; border: 2px solid rgba(120, 190, 220, .82); border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(120, 190, 220, .16);
|
||||
}
|
||||
.remote {
|
||||
position: absolute; transform: translate(12px, calc(-100% - 12px));
|
||||
border: 1px solid rgba(255,255,255,.7); border-radius: 999px;
|
||||
padding: 3px 7px; color: #fff; background: rgba(28,31,36,.88);
|
||||
box-shadow: 0 2px 7px rgba(0,0,0,.28); white-space: nowrap;
|
||||
font: 600 11px/1.2 system-ui, sans-serif;
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
line { stroke: Highlight !important; }
|
||||
.halo { border-color: Highlight; box-shadow: none; }
|
||||
.remote { color: CanvasText; background: Canvas; border-color: CanvasText; }
|
||||
}
|
||||
`,customElements.get("hp-zigbee-topology-overlay")||customElements.define("hp-zigbee-topology-overlay",v);export{v as HpZigbeeTopologyOverlay};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";import{n as t,b as n}from"./zigbee-topology-rpeu0cwQ.js";import{I as e}from"./houseplan-card-5IGoG-3Y.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 m(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 g(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,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=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&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(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 m(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,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";import{n as t,b as n}from"./zigbee-topology-Bvg-A1og.js";import{I as e}from"./houseplan-card-BfEhiIMd.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 m(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 g(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,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=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&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(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 m(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,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406";try{await import("./houseplan-assets/houseplan-card-5IGoG-3Y.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 entry: the main chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="533869cf64f877c1c61b0449fdb3106eecd4ce3c08188e9e7694ee3c11478520";try{await import("./houseplan-assets/houseplan-card-BfEhiIMd.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 entry: the main chunk is unavailable",e)}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Zigbee topology links now show arrows towards the coordinator on hover;
|
||||
incoming arrows identify devices behind a router, while a short bubble
|
||||
truthfully ends the path when the next node is in another space or is not
|
||||
placed ([#457](https://github.com/Matysh/houseplan-card/issues/457)).
|
||||
- Space settings can now create a room-free copy of the current space under a
|
||||
new name, preserving walls, openings, columns, Background objects, image
|
||||
transform, scale and display settings while leaving device placement behind;
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- На линиях топологии Zigbee при наведении теперь видны стрелки в сторону
|
||||
координатора; входящие стрелки показывают устройства за роутером, а если
|
||||
следующий узел находится на другом плане или не размещён, короткая подпись
|
||||
честно завершает путь ([#457](https://github.com/Matysh/houseplan-card/issues/457)).
|
||||
- В настройках пространства теперь можно создать под новым именем копию без
|
||||
комнат: стены, проёмы, колонны, элементы подложки, положение фонового плана,
|
||||
масштаб и настройки отображения сохраняются, а размещение устройств не
|
||||
|
||||
@@ -73,6 +73,12 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
subset, stale controls never fall back, secure targets are no-op, and legacy
|
||||
`cover`/absent light defaults round-trip losslessly. **Lights toggle by
|
||||
default** (v1.39); other devices still default to the House Plan card.
|
||||
- **Contextual Zigbee topology** (#54, refined by #457 on current dev): an
|
||||
opt-in admin-only mouse hover shows direct neighbours without scanning. A
|
||||
deterministic per-provider uplink tree now adds arrows towards the
|
||||
coordinator while keeping non-route neighbour lines; a short bubble names a
|
||||
remote space or explains that the next device/coordinator is not on the plan.
|
||||
Touch, kiosk, editors and the static card remain unchanged.
|
||||
- **Plan geometry**: polyline split (v1.32), island rooms w/ evenodd holes
|
||||
(v1.34), smart guides + 45° angle badge (v1.40), opening hover preview.
|
||||
Openings support doors, windows and compact wide gates; gates retain door
|
||||
|
||||
+12
-3
@@ -211,9 +211,18 @@ may take 10 seconds to 2 minutes and can temporarily slow the Zigbee network.
|
||||
After data is loaded, moving a real mouse over a mapped Zigbee marker shows
|
||||
only its observed direct neighbours. Links to markers on the current space are
|
||||
lines; drawable neighbours on other spaces are summarized as a temporary
|
||||
count. This is a snapshot, not the route currently used by every packet. The
|
||||
layer does not appear on touch/pen, in kiosk, in editors or in the static card,
|
||||
and hovering never starts a scan.
|
||||
count. An arrow on a line shows the next step towards the coordinator: an
|
||||
ordinary device points to its parent, while arrows pointing into a router show
|
||||
devices whose path goes through it. Neighbour links outside the derived path
|
||||
tree remain plain lines.
|
||||
|
||||
If the next step is in another space, a short bubble names that space. If the
|
||||
needed router or coordinator is not placed on the plan, the bubble says so. If
|
||||
the snapshot has no coordinator or the graph is disconnected, House Plan does
|
||||
not invent a direction and leaves the link without an arrow. This is a stable
|
||||
path approximation derived from the neighbour snapshot, not the route used by
|
||||
every current packet. The layer does not appear on touch/pen, in kiosk, in
|
||||
editors or in the static card, and hovering never starts a scan.
|
||||
|
||||
Each editor has a stable primary toolbar. Tool parameters and selected-object
|
||||
actions appear in a context tray over the top of the canvas. On a narrow screen
|
||||
|
||||
+13
-3
@@ -223,9 +223,19 @@ Zigbee2MQTT для каждого указанного базового топи
|
||||
После загрузки данных наведение реальной мыши на сопоставленный Zigbee-маркер
|
||||
показывает только его наблюдаемых прямых соседей. Соседи в текущем пространстве
|
||||
соединяются линиями, а доступные маркеры в других пространствах учитываются во
|
||||
временном счётчике. Это снимок соседства, а не маршрут каждого текущего пакета.
|
||||
На touch/pen, в киоске, редакторах и статической карточке слой не появляется;
|
||||
само наведение никогда не запускает сканирование.
|
||||
временном счётчике. Стрелка на линии показывает следующий шаг в сторону
|
||||
координатора: у обычного устройства она направлена к родителю, а входящие
|
||||
стрелки к роутеру показывают устройства, чей путь проходит через него. Связи,
|
||||
не вошедшие в вычисленное дерево пути, остаются линиями без стрелок.
|
||||
|
||||
Если следующий шаг находится в другом пространстве, рядом с устройством
|
||||
появляется его название. Если нужный роутер или координатор не размещён на
|
||||
плане, короткая подпись сообщает об этом. При отсутствии координатора в снимке
|
||||
или разрыве графа House Plan не выдумывает направление и оставляет линию без
|
||||
стрелки. Это устойчивое приближение пути по снимку соседства, а не фактический
|
||||
маршрут каждого текущего пакета. На touch/pen, в киоске, редакторах и
|
||||
статической карточке слой не появляется; само наведение никогда не запускает
|
||||
сканирование.
|
||||
|
||||
В редакторах сетка продолжается по всему рабочему холсту. В режиме просмотра сетка не показывается.
|
||||
|
||||
|
||||
@@ -5837,6 +5837,39 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: ' try { void unsubscribe; } catch { /* cleanup is best effort */ }',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'zigbee-route-parent-keeps-bfs-level',
|
||||
guard: 'node --test --test-name-pattern="uplink tree is deterministic" test/zigbee-topology.test.mjs',
|
||||
because: 'every parent must be one BFS level nearer the coordinator; accepting a same-level '
|
||||
+ 'neighbour can create a cycle and break the defining #457 AC1 invariant',
|
||||
patches: [{
|
||||
file: 'src/zigbee-topology.ts',
|
||||
find: ' .filter(({ neighborKey }) => distances.get(neighborKey) === distance - 1)',
|
||||
replace: ' .filter(({ neighborKey }) => distances.get(neighborKey) === distance)',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'zigbee-route-local-arrow-not-inverted',
|
||||
guard: 'node --test --test-name-pattern="hover projects local route directions" test/zigbee-topology.test.mjs',
|
||||
because: 'the arrow on the hovered device uplink must point to its parent rather than away '
|
||||
+ 'from the coordinator (#457 AC4)',
|
||||
patches: [{
|
||||
file: 'src/zigbee-topology.ts',
|
||||
find: " const direction = isParent ? 'toward-neighbor'",
|
||||
replace: " const direction = isParent ? 'toward-origin'",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'zigbee-route-parent-not-counted-twice',
|
||||
guard: 'node --test --test-name-pattern="hover projects local route directions" test/zigbee-topology.test.mjs',
|
||||
because: 'a remote parent already has a named route bubble and must not also inflate the '
|
||||
+ 'legacy cross-space neighbour count (#457 AC6)',
|
||||
patches: [{
|
||||
file: 'src/zigbee-topology.ts',
|
||||
find: ' if (!isParent) remote.add(other.markerId);',
|
||||
replace: ' remote.add(other.markerId);',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'double-fit-free-background-owner-removed',
|
||||
guard: 'node --test --test-name-pattern="#449 only" test/room-fit.test.mjs',
|
||||
|
||||
@@ -11779,7 +11779,7 @@ export class HouseplanCard extends LitElement {
|
||||
<svg class="iso-walls-svg" data-hp-live-viewbox="camera" viewBox="${view.x} ${view.y} ${view.w} ${view.h}"
|
||||
preserveAspectRatio="xMidYMid meet" aria-hidden="true" pointer-events="none">
|
||||
${this._renderIsoWalls(isoLayers, isoPanels)}
|
||||
</svg>` : nothing}${renderZigbeeTopologyOverlay({ hass: this.hass, settings: this._settings, devices: this._renderDevices, registry: this._haRegistry, currentSpace: space.id, viewKey: view, view: this._mode === 'view', kiosk: this._kiosk })}
|
||||
</svg>` : nothing}${renderZigbeeTopologyOverlay({ hass: this.hass, settings: this._settings, devices: this._renderDevices, registry: this._haRegistry, currentSpace: space.id, spaces: this._serverCfg?.spaces || [], viewKey: view, view: this._mode === 'view', kiosk: this._kiosk })}
|
||||
${''/* docs/CANVAS.md §6: an icon is a percentage of the PLAN and
|
||||
scales with it when you zoom — the behaviour the card always
|
||||
had, restored by the owner. `iconCqw` is `iconPct * iconUnit
|
||||
|
||||
@@ -3,8 +3,10 @@ import { langOf } from './i18n';
|
||||
import { lqiColor } from './logic';
|
||||
import { topologyT } from './i18n/topology';
|
||||
import {
|
||||
mapTopologies, resolveMappedTopologyHover, type ZigbeeHoverResolution, type ZigbeeMappedTopology,
|
||||
mapTopologies, resolveMappedTopologyHover, type ZigbeeMappedTopology,
|
||||
type ZigbeeParentTarget,
|
||||
} from './zigbee-topology';
|
||||
import { zigbeeArrowGeometry, type ZigbeePixelPoint } from './zigbee-topology-geometry';
|
||||
import {
|
||||
subscribeZigbeeTopology, zigbeeTopologyRuntimeSnapshot,
|
||||
type ZigbeeTopologyHass, type ZigbeeTopologyRuntimeSnapshot,
|
||||
@@ -13,7 +15,7 @@ import type { HaRegistrySnapshot } from './ha-binding-status';
|
||||
import type { DevItem } from './types';
|
||||
|
||||
const EMPTY_RUNTIME: ZigbeeTopologyRuntimeSnapshot = { revision: 0, topologies: [], states: {} };
|
||||
const EMPTY_HOVER: ZigbeeHoverResolution = { lines: [], remoteCount: 0, omittedCount: 0 };
|
||||
type MarkerPosition = ZigbeePixelPoint & { width: number; height: number };
|
||||
|
||||
export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
static properties = {
|
||||
@@ -21,6 +23,7 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
devices: { attribute: false },
|
||||
registry: { attribute: false },
|
||||
currentSpace: { type: String, attribute: 'current-space' },
|
||||
spaceTitles: { attribute: false },
|
||||
viewKey: { attribute: false },
|
||||
};
|
||||
|
||||
@@ -28,6 +31,7 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
devices: readonly DevItem[] = [];
|
||||
registry!: HaRegistrySnapshot;
|
||||
currentSpace = '';
|
||||
spaceTitles: Readonly<Record<string, string>> = {};
|
||||
viewKey: unknown;
|
||||
private _runtime = EMPTY_RUNTIME;
|
||||
private _hovered = '';
|
||||
@@ -41,23 +45,27 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
:host { position: absolute; inset: 0; z-index: 5; display: block; pointer-events: none; }
|
||||
svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
||||
line { vector-effect: non-scaling-stroke; stroke-linecap: round; }
|
||||
.route-arrow { vector-effect: non-scaling-stroke; }
|
||||
.halo {
|
||||
position: absolute; width: calc(var(--device-base-size, 4cqw) * 1.22);
|
||||
height: calc(var(--device-base-size, 4cqw) * 1.22); transform: translate(-50%, -50%);
|
||||
box-sizing: border-box; border: 2px solid rgba(120, 190, 220, .82); border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(120, 190, 220, .16);
|
||||
}
|
||||
.remote {
|
||||
.remote, .parent-bubble {
|
||||
position: absolute; transform: translate(12px, calc(-100% - 12px));
|
||||
border: 1px solid rgba(255,255,255,.7); border-radius: 999px;
|
||||
padding: 3px 7px; color: #fff; background: rgba(28,31,36,.88);
|
||||
box-shadow: 0 2px 7px rgba(0,0,0,.28); white-space: nowrap;
|
||||
font: 600 11px/1.2 system-ui, sans-serif;
|
||||
}
|
||||
.parent-bubble.right { transform: translate(0, -50%); }
|
||||
.parent-bubble.left { transform: translate(-100%, -50%); }
|
||||
@media (forced-colors: active) {
|
||||
line { stroke: Highlight !important; }
|
||||
.route-arrow { fill: Highlight !important; }
|
||||
.halo { border-color: Highlight; box-shadow: none; }
|
||||
.remote { color: CanvasText; background: Canvas; border-color: CanvasText; }
|
||||
.remote, .parent-bubble { color: CanvasText; background: Canvas; border-color: CanvasText; }
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -157,7 +165,7 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _position(id: string): { x: number; y: number; width: number; height: number } | null {
|
||||
private _position(id: string, width: number, height: number): MarkerPosition | null {
|
||||
const marker = [...(this._parent?.querySelectorAll<HTMLElement>('.dev[data-id]') || [])]
|
||||
.find((item) => item.dataset.id === id);
|
||||
if (!marker) return null;
|
||||
@@ -165,7 +173,24 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
const y = Number.parseFloat(marker.style.top);
|
||||
const rect = marker.getBoundingClientRect();
|
||||
return Number.isFinite(x) && Number.isFinite(y)
|
||||
? { x, y, width: rect.width, height: rect.height } : null;
|
||||
? { x: x * width / 100, y: y * height / 100, width: rect.width, height: rect.height } : null;
|
||||
}
|
||||
|
||||
private _targetText(target: ZigbeeParentTarget): string {
|
||||
if (target.kind === 'remote-space') {
|
||||
return this.spaceTitles[target.spaceId]?.trim()
|
||||
|| topologyT(langOf(this.hass), 'route_other_space');
|
||||
}
|
||||
return topologyT(langOf(this.hass), target.kind === 'unplaced-coordinator'
|
||||
? 'route_coordinator_not_on_plan' : 'route_device_not_on_plan');
|
||||
}
|
||||
|
||||
private _markerClearance(position: MarkerPosition): number {
|
||||
return Math.max(position.width, position.height) * 0.61 + 3;
|
||||
}
|
||||
|
||||
private _points(points: readonly ZigbeePixelPoint[]): string {
|
||||
return points.map((point) => `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(' ');
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -175,23 +200,58 @@ export class HpZigbeeTopologyOverlay extends LitElement {
|
||||
&& memo.registry === this.registry ? memo.mapped : mapTopologies(this._runtime.topologies, this.devices, this.registry);
|
||||
if (!memo || mapped !== memo.mapped) this._mappedMemo = { runtime: this._runtime, devices: this.devices, registry: this.registry, mapped };
|
||||
const hover = resolveMappedTopologyHover(mapped, this.currentSpace, this._hovered);
|
||||
const origin = this._position(this._hovered);
|
||||
const width = this.clientWidth || this._parent?.clientWidth || 1;
|
||||
const height = this.clientHeight || this._parent?.clientHeight || 1;
|
||||
const origin = this._position(this._hovered, width, height);
|
||||
if (!origin) return nothing;
|
||||
const lines = hover.lines.map((line) => ({ ...line, point: this._position(line.neighborMarkerId) }))
|
||||
.filter((line) => !!line.point);
|
||||
const lines = hover.lines.map((line) => ({
|
||||
...line,
|
||||
point: this._position(line.neighborMarkerId, width, height),
|
||||
})).filter((line) => !!line.point).map((line) => {
|
||||
const color = line.lqi === undefined ? 'rgba(145,155,165,.85)' : lqiColor(line.lqi);
|
||||
const arrow = line.routeDirection ? zigbeeArrowGeometry(
|
||||
origin, line.point!, this._markerClearance(origin), this._markerClearance(line.point!),
|
||||
line.routeDirection,
|
||||
) : null;
|
||||
return { ...line, color, arrow };
|
||||
});
|
||||
const placeLeft = origin.x > width / 2;
|
||||
const bubbleGap = this._markerClearance(origin) + 18;
|
||||
const bubbles = hover.parentTargets.map((target, index) => {
|
||||
const offset = (index - (hover.parentTargets.length - 1) / 2) * 30;
|
||||
const point = {
|
||||
x: origin.x + (placeLeft ? -bubbleGap : bubbleGap),
|
||||
y: Math.max(14, Math.min(height - 14, origin.y + offset)),
|
||||
};
|
||||
return { target, point, arrow: zigbeeArrowGeometry(
|
||||
origin, point, this._markerClearance(origin), 0, 'toward-neighbor',
|
||||
) };
|
||||
});
|
||||
return html`
|
||||
${lines.length ? svg`<svg viewBox="0 0 100 100" preserveAspectRatio="none"
|
||||
${lines.length || bubbles.length ? svg`<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none"
|
||||
aria-hidden="true" data-hp="zigbee-topology-lines">
|
||||
${lines.map((line) => svg`<line x1=${origin.x} y1=${origin.y}
|
||||
x2=${line.point!.x} y2=${line.point!.y}
|
||||
stroke=${line.lqi === undefined ? 'rgba(145,155,165,.85)' : lqiColor(line.lqi)}
|
||||
stroke=${line.color} data-direction=${line.routeDirection || 'none'}
|
||||
stroke-width=${line.lqi === undefined ? 2 : 2.2}
|
||||
stroke-dasharray=${line.lqi === undefined ? '5 5' : nothing} opacity=".9"></line>`)}
|
||||
stroke-dasharray=${line.lqi === undefined ? '5 5' : nothing} opacity=".9"></line>
|
||||
${line.arrow ? svg`<polygon class="route-arrow" data-hp="zigbee-topology-arrow"
|
||||
data-direction=${line.routeDirection} points=${this._points(line.arrow.points)}
|
||||
fill=${line.color}></polygon>` : nothing}`)}
|
||||
${bubbles.map((bubble) => svg`<line class="parent-route" x1=${origin.x} y1=${origin.y}
|
||||
x2=${bubble.point.x} y2=${bubble.point.y} stroke="rgba(145,155,165,.92)"
|
||||
stroke-width="2" opacity=".9"></line>
|
||||
${bubble.arrow ? svg`<polygon class="route-arrow" data-hp="zigbee-topology-parent-arrow"
|
||||
data-direction="toward-neighbor" points=${this._points(bubble.arrow.points)}
|
||||
fill="rgba(145,155,165,.92)"></polygon>` : nothing}`)}
|
||||
</svg>` : nothing}
|
||||
${lines.map((line) => html`<div class="halo" data-hp="zigbee-topology-neighbor"
|
||||
data-id=${line.neighborMarkerId} style="left:${line.point!.x}%;top:${line.point!.y}%;width:${line.point!.width * 1.22}px;height:${line.point!.height * 1.22}px"></div>`)}
|
||||
data-id=${line.neighborMarkerId} style="left:${line.point!.x}px;top:${line.point!.y}px;width:${line.point!.width * 1.22}px;height:${line.point!.height * 1.22}px"></div>`)}
|
||||
${bubbles.map((bubble) => html`<div class="parent-bubble ${placeLeft ? 'left' : 'right'}"
|
||||
data-hp="zigbee-topology-parent-bubble" data-kind=${bubble.target.kind}
|
||||
style="left:${bubble.point.x}px;top:${bubble.point.y}px">${this._targetText(bubble.target)}</div>`)}
|
||||
${hover.remoteCount ? html`<div class="remote" data-hp="zigbee-topology-remote"
|
||||
style="left:${origin.x}%;top:${origin.y}%">${topologyT(
|
||||
style="left:${origin.x}px;top:${origin.y}px">${topologyT(
|
||||
langOf(this.hass), 'remote_count', { n: hover.remoteCount },
|
||||
)}</div>` : nothing}
|
||||
`;
|
||||
|
||||
@@ -23,5 +23,8 @@
|
||||
"error_invalid_topic": "Prüfe das Zigbee2MQTT-Basistopic.",
|
||||
"error_invalid_payload": "Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",
|
||||
"error_provider": "Topologiedaten konnten nicht geladen werden.",
|
||||
"remote_count": "+{n} in anderen Bereichen"
|
||||
"remote_count": "+{n} in anderen Bereichen",
|
||||
"route_device_not_on_plan": "Gerät ist nicht im Plan",
|
||||
"route_coordinator_not_on_plan": "Koordinator ist nicht im Plan",
|
||||
"route_other_space": "anderer Bereich"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,8 @@
|
||||
"error_invalid_topic": "Check the Zigbee2MQTT base topic.",
|
||||
"error_invalid_payload": "The provider returned unsupported topology data.",
|
||||
"error_provider": "Topology data could not be loaded.",
|
||||
"remote_count": "+{n} in other spaces"
|
||||
"remote_count": "+{n} in other spaces",
|
||||
"route_device_not_on_plan": "device is not on the plan",
|
||||
"route_coordinator_not_on_plan": "coordinator is not on the plan",
|
||||
"route_other_space": "another space"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,8 @@
|
||||
"error_invalid_topic": "Vérifiez le topic de base Zigbee2MQTT.",
|
||||
"error_invalid_payload": "Le fournisseur a renvoyé des données de topologie non prises en charge.",
|
||||
"error_provider": "Impossible de charger les données de topologie.",
|
||||
"remote_count": "+{n} dans d’autres espaces"
|
||||
"remote_count": "+{n} dans d’autres espaces",
|
||||
"route_device_not_on_plan": "appareil absent du plan",
|
||||
"route_coordinator_not_on_plan": "coordinateur absent du plan",
|
||||
"route_other_space": "autre espace"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,8 @@
|
||||
"error_invalid_topic": "Проверьте базовый топик Zigbee2MQTT.",
|
||||
"error_invalid_payload": "Провайдер вернул неподдерживаемые данные топологии.",
|
||||
"error_provider": "Не удалось загрузить данные топологии.",
|
||||
"remote_count": "+{n} в других пространствах"
|
||||
"remote_count": "+{n} в других пространствах",
|
||||
"route_device_not_on_plan": "устройства нет на плане",
|
||||
"route_coordinator_not_on_plan": "координатора нет на плане",
|
||||
"route_other_space": "другое пространство"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
export type ZigbeePixelPoint = { x: number; y: number };
|
||||
|
||||
export type ZigbeeArrowGeometry = {
|
||||
tip: ZigbeePixelPoint;
|
||||
points: [ZigbeePixelPoint, ZigbeePixelPoint, ZigbeePixelPoint];
|
||||
};
|
||||
|
||||
/** A screen-pixel arrowhead whose tip stops before the destination marker or bubble. */
|
||||
export function zigbeeArrowGeometry(
|
||||
origin: ZigbeePixelPoint,
|
||||
neighbor: ZigbeePixelPoint,
|
||||
originClearance: number,
|
||||
neighborClearance: number,
|
||||
direction: 'toward-neighbor' | 'toward-origin',
|
||||
preferredLength = 9,
|
||||
preferredHalfWidth = 4.5,
|
||||
): ZigbeeArrowGeometry | null {
|
||||
const dx = neighbor.x - origin.x;
|
||||
const dy = neighbor.y - origin.y;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
const usable = distance - Math.max(0, originClearance) - Math.max(0, neighborClearance);
|
||||
if (!Number.isFinite(distance) || distance <= 0 || usable < 4) return null;
|
||||
const ux = dx / distance;
|
||||
const uy = dy / distance;
|
||||
const towardNeighbor = direction === 'toward-neighbor';
|
||||
const tip = towardNeighbor
|
||||
? { x: neighbor.x - ux * Math.max(0, neighborClearance),
|
||||
y: neighbor.y - uy * Math.max(0, neighborClearance) }
|
||||
: { x: origin.x + ux * Math.max(0, originClearance),
|
||||
y: origin.y + uy * Math.max(0, originClearance) };
|
||||
const length = Math.min(preferredLength, Math.max(4, usable * 0.45));
|
||||
const halfWidth = Math.min(preferredHalfWidth, length * 0.5);
|
||||
const backSign = towardNeighbor ? -1 : 1;
|
||||
const base = { x: tip.x + ux * length * backSign, y: tip.y + uy * length * backSign };
|
||||
const nx = -uy;
|
||||
const ny = ux;
|
||||
return {
|
||||
tip,
|
||||
points: [
|
||||
tip,
|
||||
{ x: base.x + nx * halfWidth, y: base.y + ny * halfWidth },
|
||||
{ x: base.x - nx * halfWidth, y: base.y - ny * halfWidth },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -4,19 +4,36 @@ import type { HaRegistrySnapshot } from './ha-binding-status';
|
||||
import type { DevItem } from './types';
|
||||
import type { ZigbeeTopologyHass } from './zigbee-topology-runtime';
|
||||
|
||||
const spaceTitleMemo = new WeakMap<object, { signature: string; titles: Readonly<Record<string, string>> }>();
|
||||
|
||||
function spaceTitlesOf(spaces: readonly { id?: unknown; title?: unknown }[]): Readonly<Record<string, string>> {
|
||||
const signature = spaces.map((space) => `${String(space.id)}\u0000${String(space.title)}`).join('\u0001');
|
||||
const cached = spaceTitleMemo.get(spaces);
|
||||
if (cached?.signature === signature) return cached.titles;
|
||||
const projected = Object.fromEntries(spaces.flatMap((space) => (
|
||||
typeof space.id === 'string' && typeof space.title === 'string'
|
||||
? [[space.id, space.title] as const] : []
|
||||
)));
|
||||
spaceTitleMemo.set(spaces, { signature, titles: projected });
|
||||
return projected;
|
||||
}
|
||||
|
||||
export function renderZigbeeTopologyOverlay(input: {
|
||||
hass: ZigbeeTopologyHass;
|
||||
settings: unknown;
|
||||
devices: readonly DevItem[];
|
||||
registry: HaRegistrySnapshot;
|
||||
currentSpace: string;
|
||||
spaces: readonly { id?: unknown; title?: unknown }[];
|
||||
viewKey: unknown;
|
||||
view: boolean;
|
||||
kiosk: boolean;
|
||||
}): TemplateResult | typeof nothing {
|
||||
if (!input.view || input.kiosk || input.hass?.user?.is_admin !== true
|
||||
|| !zigbeeTopologySettingsOf(input.settings).enabled) return nothing;
|
||||
const spaceTitles = spaceTitlesOf(input.spaces);
|
||||
void import('./hp-zigbee-topology-overlay');
|
||||
return html`<hp-zigbee-topology-overlay data-hp-live-layer="camera" .hass=${input.hass} .devices=${input.devices}
|
||||
.registry=${input.registry} .currentSpace=${input.currentSpace} .viewKey=${input.viewKey}></hp-zigbee-topology-overlay>`;
|
||||
.registry=${input.registry} .currentSpace=${input.currentSpace} .spaceTitles=${spaceTitles}
|
||||
.viewKey=${input.viewKey}></hp-zigbee-topology-overlay>`;
|
||||
}
|
||||
|
||||
+135
-11
@@ -52,12 +52,25 @@ export interface ZigbeeTopology {
|
||||
export interface ZigbeeHoverLine {
|
||||
neighborMarkerId: string;
|
||||
lqi?: number;
|
||||
routeDirection?: 'toward-neighbor' | 'toward-origin';
|
||||
}
|
||||
|
||||
export type ZigbeeParentTarget =
|
||||
| { kind: 'remote-space'; spaceId: string }
|
||||
| { kind: 'unplaced-device' }
|
||||
| { kind: 'unplaced-coordinator' };
|
||||
|
||||
export interface ZigbeeRouteTree {
|
||||
coordinatorKey?: string;
|
||||
distances: Map<string, number>;
|
||||
parents: Map<string, string>;
|
||||
}
|
||||
|
||||
export interface ZigbeeHoverResolution {
|
||||
lines: ZigbeeHoverLine[];
|
||||
remoteCount: number;
|
||||
omittedCount: number;
|
||||
parentTargets: ZigbeeParentTarget[];
|
||||
}
|
||||
|
||||
export const TOPOLOGY_STALE_MS = 5 * 60 * 1000;
|
||||
@@ -94,11 +107,26 @@ function lqiOf(value: unknown): number | undefined {
|
||||
return Number.isFinite(n) && n >= 0 && n <= 255 ? Math.round(n) : undefined;
|
||||
}
|
||||
|
||||
const Z2M_RELATIONSHIPS: Readonly<Record<number, string>> = {
|
||||
0: 'parent', 1: 'child', 2: 'sibling', 3: 'none', 4: 'previous_child',
|
||||
};
|
||||
|
||||
function relationshipOf(value: unknown): string | undefined {
|
||||
if (typeof value === 'number' && Number.isInteger(value)) return Z2M_RELATIONSHIPS[value];
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const compact = value.trim().toLowerCase().replace(/[\s_-]+/g, '');
|
||||
if (!compact) return undefined;
|
||||
if (compact === 'previouschild') return 'previous_child';
|
||||
if (compact === 'parent' || compact === 'child' || compact === 'sibling' || compact === 'none') {
|
||||
return compact;
|
||||
}
|
||||
return value.trim().toLowerCase().slice(0, 40) || undefined;
|
||||
}
|
||||
|
||||
function observation(value: unknown): ZigbeeDirectionalObservation {
|
||||
const record = recordOf(value);
|
||||
const lqi = lqiOf(record?.lqi ?? record?.linkquality ?? record?.link_quality);
|
||||
const relationship = typeof record?.relationship === 'string'
|
||||
? record.relationship.slice(0, 40) : undefined;
|
||||
const relationship = relationshipOf(record?.relationship);
|
||||
return { ...(lqi === undefined ? {} : { lqi }), ...(relationship ? { relationship } : {}) };
|
||||
}
|
||||
|
||||
@@ -270,7 +298,69 @@ function drawable(device: DevItem): boolean {
|
||||
}
|
||||
|
||||
export type ZigbeeNodePlacement = { markerId: string; space: string };
|
||||
export type ZigbeeMappedTopology = { topology: ZigbeeTopology; placements: Map<string, ZigbeeNodePlacement> };
|
||||
export type ZigbeeMappedTopology = {
|
||||
topology: ZigbeeTopology;
|
||||
placements: Map<string, ZigbeeNodePlacement>;
|
||||
routes: ZigbeeRouteTree;
|
||||
};
|
||||
|
||||
type ZigbeeAdjacentLink = { neighborKey: string; observation?: ZigbeeDirectionalObservation };
|
||||
|
||||
function adjacencyOf(topology: ZigbeeTopology): Map<string, ZigbeeAdjacentLink[]> {
|
||||
const nodeKeys = new Set(topology.nodes.map((node) => node.key));
|
||||
const adjacency = new Map<string, ZigbeeAdjacentLink[]>();
|
||||
for (const key of [...nodeKeys].sort()) adjacency.set(key, []);
|
||||
for (const link of [...topology.links].sort((left, right) => (
|
||||
`${left.a}\u0000${left.b}`.localeCompare(`${right.a}\u0000${right.b}`)
|
||||
))) {
|
||||
if (!nodeKeys.has(link.a) || !nodeKeys.has(link.b)) continue;
|
||||
adjacency.get(link.a)!.push({ neighborKey: link.b, observation: link.aToB });
|
||||
adjacency.get(link.b)!.push({ neighborKey: link.a, observation: link.bToA });
|
||||
}
|
||||
for (const neighbors of adjacency.values()) {
|
||||
neighbors.sort((left, right) => left.neighborKey.localeCompare(right.neighborKey));
|
||||
}
|
||||
return adjacency;
|
||||
}
|
||||
|
||||
/** Build one deterministic shortest-path uplink tree without trusting stale provider direction. */
|
||||
export function buildZigbeeRouteTree(topology: ZigbeeTopology): ZigbeeRouteTree {
|
||||
const distances = new Map<string, number>();
|
||||
const parents = new Map<string, string>();
|
||||
const coordinators = topology.nodes.filter((node) => node.role === 'coordinator')
|
||||
.map((node) => node.key).sort();
|
||||
if (coordinators.length !== 1) return { distances, parents };
|
||||
const coordinatorKey = coordinators[0];
|
||||
const adjacency = adjacencyOf(topology);
|
||||
if (!adjacency.has(coordinatorKey)) return { distances, parents };
|
||||
distances.set(coordinatorKey, 0);
|
||||
const queue = [coordinatorKey];
|
||||
for (let index = 0; index < queue.length; index++) {
|
||||
const current = queue[index];
|
||||
const nextDistance = distances.get(current)! + 1;
|
||||
for (const { neighborKey } of adjacency.get(current) || []) {
|
||||
if (distances.has(neighborKey)) continue;
|
||||
distances.set(neighborKey, nextDistance);
|
||||
queue.push(neighborKey);
|
||||
}
|
||||
}
|
||||
for (const nodeKey of [...distances.keys()].sort()) {
|
||||
const distance = distances.get(nodeKey)!;
|
||||
if (distance === 0) continue;
|
||||
const candidates = (adjacency.get(nodeKey) || [])
|
||||
.filter(({ neighborKey }) => distances.get(neighborKey) === distance - 1)
|
||||
.sort((left, right) => {
|
||||
const leftParent = left.observation?.relationship === 'parent' ? 1 : 0;
|
||||
const rightParent = right.observation?.relationship === 'parent' ? 1 : 0;
|
||||
if (leftParent !== rightParent) return rightParent - leftParent;
|
||||
const leftLqi = left.observation?.lqi ?? -1;
|
||||
const rightLqi = right.observation?.lqi ?? -1;
|
||||
return rightLqi - leftLqi || left.neighborKey.localeCompare(right.neighborKey);
|
||||
});
|
||||
if (candidates[0]) parents.set(nodeKey, candidates[0].neighborKey);
|
||||
}
|
||||
return { coordinatorKey, distances, parents };
|
||||
}
|
||||
|
||||
/** Exact registry binding only; names, models and friendly names are never identities. */
|
||||
export function mapTopologyNodes(
|
||||
@@ -300,36 +390,70 @@ export function mapTopologies(
|
||||
topologies: readonly ZigbeeTopology[], devices: readonly DevItem[], registry: HaRegistrySnapshot,
|
||||
): ZigbeeMappedTopology[] {
|
||||
return topologies.map((topology) => ({
|
||||
topology, placements: mapTopologyNodes(topology, devices, registry).placements,
|
||||
topology,
|
||||
placements: mapTopologyNodes(topology, devices, registry).placements,
|
||||
routes: buildZigbeeRouteTree(topology),
|
||||
}));
|
||||
}
|
||||
|
||||
export function resolveMappedTopologyHover(
|
||||
mappedTopologies: readonly ZigbeeMappedTopology[], currentSpace: string, hoveredMarkerId: string,
|
||||
): ZigbeeHoverResolution {
|
||||
const lines = new Map<string, ZigbeeHoverLine>();
|
||||
const lines = new Map<string, { line: ZigbeeHoverLine;
|
||||
directions: Set<NonNullable<ZigbeeHoverLine['routeDirection']>> }>();
|
||||
const remote = new Set<string>();
|
||||
const parentTargets = new Map<string, ZigbeeParentTarget>();
|
||||
let omittedCount = 0;
|
||||
for (const { topology, placements } of mappedTopologies) {
|
||||
for (const { topology, placements, routes } of mappedTopologies) {
|
||||
const hoveredNodes = new Set([...placements]
|
||||
.filter(([, placement]) => placement.markerId === hoveredMarkerId && placement.space === currentSpace)
|
||||
.map(([key]) => key));
|
||||
if (!hoveredNodes.size) continue;
|
||||
for (const hoveredNode of hoveredNodes) {
|
||||
const parentKey = routes.parents.get(hoveredNode);
|
||||
if (!parentKey) continue;
|
||||
const parentPlacement = placements.get(parentKey);
|
||||
if (parentPlacement?.space === currentSpace) continue;
|
||||
let target: ZigbeeParentTarget;
|
||||
if (parentPlacement) target = { kind: 'remote-space', spaceId: parentPlacement.space };
|
||||
else target = topology.nodes.find((node) => node.key === parentKey)?.role === 'coordinator'
|
||||
? { kind: 'unplaced-coordinator' } : { kind: 'unplaced-device' };
|
||||
const targetKey = target.kind === 'remote-space' ? `${target.kind}:${target.spaceId}` : target.kind;
|
||||
parentTargets.set(targetKey, target);
|
||||
}
|
||||
for (const link of topology.links) {
|
||||
const fromA = hoveredNodes.has(link.a);
|
||||
const fromB = hoveredNodes.has(link.b);
|
||||
if (!fromA && !fromB) continue;
|
||||
const other = placements.get(fromA ? link.b : link.a);
|
||||
const hoveredNode = fromA ? link.a : link.b;
|
||||
const otherKey = fromA ? link.b : link.a;
|
||||
const other = placements.get(otherKey);
|
||||
if (!other || other.markerId === hoveredMarkerId) { omittedCount++; continue; }
|
||||
if (other.space !== currentSpace) { remote.add(other.markerId); continue; }
|
||||
const isParent = routes.parents.get(hoveredNode) === otherKey;
|
||||
if (other.space !== currentSpace) {
|
||||
if (!isParent) remote.add(other.markerId);
|
||||
continue;
|
||||
}
|
||||
const obs = fromA ? link.aToB : link.bToA;
|
||||
const direction = isParent ? 'toward-neighbor'
|
||||
: routes.parents.get(otherKey) === hoveredNode ? 'toward-origin' : undefined;
|
||||
const existing = lines.get(other.markerId);
|
||||
if (!existing || (existing.lqi === undefined && obs?.lqi !== undefined)) {
|
||||
lines.set(other.markerId, { neighborMarkerId: other.markerId, lqi: obs?.lqi });
|
||||
if (!existing) {
|
||||
lines.set(other.markerId, { line: { neighborMarkerId: other.markerId, lqi: obs?.lqi },
|
||||
directions: new Set(direction ? [direction] : []) });
|
||||
} else {
|
||||
if (existing.line.lqi === undefined && obs?.lqi !== undefined) existing.line.lqi = obs.lqi;
|
||||
if (direction) existing.directions.add(direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { lines: [...lines.values()], remoteCount: remote.size, omittedCount };
|
||||
return {
|
||||
lines: [...lines.values()].map(({ line, directions }) => directions.size === 1
|
||||
? { ...line, routeDirection: [...directions][0] } : line),
|
||||
remoteCount: remote.size,
|
||||
omittedCount,
|
||||
parentTargets: [...parentTargets.values()],
|
||||
};
|
||||
}
|
||||
|
||||
/** Resolve only the edges incident to the marker currently under the mouse. */
|
||||
|
||||
@@ -2,9 +2,10 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import {
|
||||
mapTopologyNodes, normalizeIeee, normalizeZ2mTopology,
|
||||
buildZigbeeRouteTree, mapTopologyNodes, normalizeIeee, normalizeZ2mTopology,
|
||||
normalizeZhaTopology, resolveTopologyHover,
|
||||
} from '../test-build/zigbee-topology.js';
|
||||
import { zigbeeArrowGeometry } from '../test-build/zigbee-topology-geometry.js';
|
||||
import {
|
||||
normalizeZ2mBaseTopic, writeZigbeeTopologySettings, zigbeeTopologySettingsOf,
|
||||
} from '../test-build/zigbee-topology-settings.js';
|
||||
@@ -82,6 +83,9 @@ test('Z2M normalization accepts a real anonymized camelCase raw network map', ()
|
||||
topology.links.map((link) => link.aToB?.lqi ?? link.bToA?.lqi).sort((a, b) => a - b),
|
||||
[97, 182],
|
||||
);
|
||||
assert.ok(topology.links.some((link) => (
|
||||
link.aToB?.relationship === 'sibling' || link.bToA?.relationship === 'sibling'
|
||||
)));
|
||||
assert.deepEqual(topology.warnings, []);
|
||||
});
|
||||
|
||||
@@ -123,9 +127,11 @@ test('exact device/entity mapping yields local lines and a deduplicated remote c
|
||||
]);
|
||||
assert.deepEqual(resolveTopologyHover([topology], devices, registry, 'one', 'ma'), {
|
||||
lines: [{ neighborMarkerId: 'mb', lqi: 180 }], remoteCount: 1, omittedCount: 0,
|
||||
parentTargets: [],
|
||||
});
|
||||
assert.deepEqual(resolveTopologyHover([topology], devices, registry, 'one', 'mb'), {
|
||||
lines: [{ neighborMarkerId: 'ma', lqi: undefined }], remoteCount: 0, omittedCount: 0,
|
||||
parentTargets: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,10 +144,145 @@ test('hidden and ambiguous placements fail closed', () => {
|
||||
assert.equal(mapTopologyNodes(topology, ambiguous, registry).placements.has(topology.nodes[0].key), false);
|
||||
const hidden = devices.map((item) => item.id === 'mb' ? { ...item, hidden: true } : item);
|
||||
assert.deepEqual(resolveTopologyHover([topology], hidden, registry, 'one', 'ma'), {
|
||||
lines: [], remoteCount: 0, omittedCount: 1,
|
||||
lines: [], remoteCount: 0, omittedCount: 1, parentTargets: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('uplink tree is deterministic, acyclic and always reaches the sole coordinator', () => {
|
||||
const topology = {
|
||||
provider: 'zha', instanceId: 'zha', obtainedAt: 1, freshness: 'provider-cache', warnings: [],
|
||||
nodes: [
|
||||
{ key: 'c', ieee: '0000000000000001', role: 'coordinator' },
|
||||
{ key: 'a', ieee: '0000000000000002', role: 'router' },
|
||||
{ key: 'b', ieee: '0000000000000003', role: 'router' },
|
||||
{ key: 'd', ieee: '0000000000000004', role: 'end' },
|
||||
{ key: 'x', ieee: '0000000000000005', role: 'router' },
|
||||
],
|
||||
links: [
|
||||
{ a: 'c', b: 'a', bToA: { lqi: 90 } },
|
||||
{ a: 'c', b: 'b', bToA: { lqi: 220 } },
|
||||
{ a: 'a', b: 'b', aToB: { relationship: 'parent', lqi: 255 } },
|
||||
{ a: 'a', b: 'd', bToA: { relationship: 'parent', lqi: 40 } },
|
||||
{ a: 'b', b: 'd', bToA: { relationship: 'sibling', lqi: 240 } },
|
||||
],
|
||||
};
|
||||
const tree = buildZigbeeRouteTree(topology);
|
||||
assert.equal(tree.coordinatorKey, 'c');
|
||||
assert.deepEqual(Object.fromEntries(tree.distances), { c: 0, a: 1, b: 1, d: 2 });
|
||||
assert.deepEqual(Object.fromEntries(tree.parents), { a: 'c', b: 'c', d: 'a' });
|
||||
assert.equal(tree.parents.has('x'), false);
|
||||
for (const key of tree.parents.keys()) {
|
||||
const visited = new Set();
|
||||
let current = key;
|
||||
while (current !== tree.coordinatorKey) {
|
||||
assert.equal(visited.has(current), false, `cycle from ${key}`);
|
||||
visited.add(current);
|
||||
const parent = tree.parents.get(current);
|
||||
assert.ok(parent, `missing parent from ${current}`);
|
||||
assert.equal(tree.distances.get(parent), tree.distances.get(current) - 1);
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
const permuted = buildZigbeeRouteTree({
|
||||
...topology, nodes: [...topology.nodes].reverse(), links: [...topology.links].reverse(),
|
||||
});
|
||||
assert.deepEqual(Object.fromEntries(permuted.parents), Object.fromEntries(tree.parents));
|
||||
});
|
||||
|
||||
test('uplink parent tie-break uses direct LQI then stable key and ambiguous roots fail closed', () => {
|
||||
const base = {
|
||||
provider: 'zha', instanceId: 'zha', obtainedAt: 1, freshness: 'provider-cache', warnings: [],
|
||||
nodes: [
|
||||
{ key: 'c', ieee: '0000000000000001', role: 'coordinator' },
|
||||
{ key: 'a', ieee: '0000000000000002', role: 'router' },
|
||||
{ key: 'b', ieee: '0000000000000003', role: 'router' },
|
||||
{ key: 'd', ieee: '0000000000000004', role: 'end' },
|
||||
],
|
||||
links: [
|
||||
{ a: 'c', b: 'a' }, { a: 'c', b: 'b' },
|
||||
{ a: 'a', b: 'd', bToA: { lqi: 100 } },
|
||||
{ a: 'b', b: 'd', bToA: { lqi: 150 } },
|
||||
],
|
||||
};
|
||||
assert.equal(buildZigbeeRouteTree(base).parents.get('d'), 'b');
|
||||
const tied = { ...base, links: base.links.map((link) => (
|
||||
link.a === 'b' && link.b === 'd' ? { ...link, bToA: { lqi: 100 } } : link
|
||||
)) };
|
||||
assert.equal(buildZigbeeRouteTree(tied).parents.get('d'), 'a');
|
||||
assert.equal(buildZigbeeRouteTree({ ...base, nodes: base.nodes.filter((node) => node.key !== 'c') })
|
||||
.parents.size, 0);
|
||||
assert.equal(buildZigbeeRouteTree({
|
||||
...base, nodes: [...base.nodes, { key: 'c2', ieee: '0000000000000005', role: 'coordinator' }],
|
||||
}).parents.size, 0);
|
||||
});
|
||||
|
||||
test('hover projects local route directions and keeps remote children in the old count', () => {
|
||||
const topology = normalizeZhaTopology([
|
||||
{ ieee: '00124b0000000001', device_reg_id: 'da', device_type: 'Coordinator',
|
||||
neighbors: [{ ieee: '00124b0000000002', lqi: 180 }] },
|
||||
{ ieee: '00124b0000000002', device_reg_id: 'db', device_type: 'Router', neighbors: [
|
||||
{ ieee: '00124b0000000001', lqi: 150, relationship: 'Parent' },
|
||||
{ ieee: '00124b0000000003', lqi: 70, relationship: 'Child' },
|
||||
] },
|
||||
{ ieee: '00124b0000000003', device_reg_id: 'dc', device_type: 'EndDevice',
|
||||
neighbors: [{ ieee: '00124b0000000002', lqi: 60, relationship: 'Parent' }] },
|
||||
]);
|
||||
assert.deepEqual(resolveTopologyHover([topology], devices, registry, 'one', 'mb'), {
|
||||
lines: [{ neighborMarkerId: 'ma', lqi: 150, routeDirection: 'toward-neighbor' }],
|
||||
remoteCount: 1, omittedCount: 0, parentTargets: [],
|
||||
});
|
||||
assert.deepEqual(resolveTopologyHover([topology], devices, registry, 'one', 'ma'), {
|
||||
lines: [{ neighborMarkerId: 'mb', lqi: 180, routeDirection: 'toward-origin' }],
|
||||
remoteCount: 0, omittedCount: 0, parentTargets: [],
|
||||
});
|
||||
assert.deepEqual(resolveTopologyHover([topology], devices, registry, 'two', 'mc'), {
|
||||
lines: [], remoteCount: 0, omittedCount: 0,
|
||||
parentTargets: [{ kind: 'remote-space', spaceId: 'one' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('hover distinguishes an unplaced coordinator from an unplaced router and never bubbles a child', () => {
|
||||
const topology = normalizeZhaTopology([
|
||||
{ ieee: '00124b0000000001', device_reg_id: 'missing-coordinator', device_type: 'Coordinator',
|
||||
neighbors: [{ ieee: '00124b0000000002', lqi: 180 }] },
|
||||
{ ieee: '00124b0000000002', device_reg_id: 'missing-router', device_type: 'Router', neighbors: [
|
||||
{ ieee: '00124b0000000001', lqi: 150, relationship: 'parent' },
|
||||
{ ieee: '00124b0000000003', lqi: 70 },
|
||||
] },
|
||||
{ ieee: '00124b0000000003', device_reg_id: 'dc', device_type: 'EndDevice',
|
||||
neighbors: [{ ieee: '00124b0000000002', lqi: 60, relationship: 'parent' }] },
|
||||
]);
|
||||
const routerDevice = { ...devices[1], bindingKind: 'device', bindingRef: 'missing-router' };
|
||||
const localRegistry = { ...registry, devices: {
|
||||
...registry.devices,
|
||||
'missing-coordinator': { id: 'missing-coordinator' },
|
||||
'missing-router': { id: 'missing-router' },
|
||||
} };
|
||||
assert.deepEqual(resolveTopologyHover([topology], [routerDevice, devices[2]], localRegistry, 'one', 'mb'), {
|
||||
lines: [], remoteCount: 1, omittedCount: 1,
|
||||
parentTargets: [{ kind: 'unplaced-coordinator' }],
|
||||
});
|
||||
assert.deepEqual(resolveTopologyHover([topology], [devices[2]], localRegistry, 'two', 'mc'), {
|
||||
lines: [], remoteCount: 0, omittedCount: 1,
|
||||
parentTargets: [{ kind: 'unplaced-device' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('screen-pixel arrow geometry points at the requested endpoint and respects clearance', () => {
|
||||
const origin = { x: 0, y: 20 };
|
||||
const neighbor = { x: 100, y: 20 };
|
||||
const outgoing = zigbeeArrowGeometry(origin, neighbor, 10, 12, 'toward-neighbor');
|
||||
assert.deepEqual(outgoing?.tip, { x: 88, y: 20 });
|
||||
assert.equal(outgoing?.points[1].x, 79);
|
||||
assert.equal(outgoing?.points[2].x, 79);
|
||||
const incoming = zigbeeArrowGeometry(origin, neighbor, 10, 12, 'toward-origin');
|
||||
assert.deepEqual(incoming?.tip, { x: 10, y: 20 });
|
||||
assert.equal(incoming?.points[1].x, 19);
|
||||
const diagonal = zigbeeArrowGeometry({ x: 10, y: 10 }, { x: 70, y: 90 }, 5, 7, 'toward-neighbor');
|
||||
assert.ok(diagonal && diagonal.tip.x < 70 && diagonal.tip.y < 90);
|
||||
assert.equal(zigbeeArrowGeometry(origin, { x: 20, y: 20 }, 10, 8, 'toward-neighbor'), null);
|
||||
});
|
||||
|
||||
test('ZHA runtime is explicit, admin-only and deduplicates concurrent reads', async () => {
|
||||
let calls = 0;
|
||||
let release;
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"src/color.ts", "src/styles.ts", "src/styles/*.styles.ts", "src/logic.ts", "src/i18n.ts", "src/i18n/registry.ts", "src/i18n/language-runtime.ts", "src/glow-blend.ts", "src/grid-scale.ts", "src/device-visual.ts", "src/device-pulse.ts", "src/device-presentation.ts", "src/device-marker-geometry.ts",
|
||||
"src/device-face.ts", "src/device-toggle.ts", "src/marker-toggle-entity.ts", "src/activity-runtime.ts", "src/device-area-relocation.ts",
|
||||
"src/ha-binding-status.ts",
|
||||
"src/zigbee-topology-settings.ts", "src/zigbee-topology.ts", "src/zigbee-topology-runtime.ts",
|
||||
"src/zigbee-topology-settings.ts", "src/zigbee-topology.ts", "src/zigbee-topology-geometry.ts", "src/zigbee-topology-runtime.ts",
|
||||
"src/integration-provider.ts", "src/vacuum.ts", "src/vacuum-routes.ts", "src/vacuum-route-edit.ts",
|
||||
"src/sun.ts",
|
||||
"src/light-visibility.ts", "src/glow-scene.ts", "src/space-render.ts",
|
||||
|
||||
Reference in New Issue
Block a user