fix: preserve live resize validation on large plans

Issue: #451
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-05 01:46:26 +03:00
parent d7e97687ce
commit 5692a2880f
33 changed files with 606 additions and 323 deletions
@@ -1,182 +1,182 @@
{
"schema": 1,
"fingerprint": "ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597",
"fingerprint": "0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 296727,
"initialViewGzipBytes": 296724,
"lazyFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/fr-BKs-Jkxs.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"houseplan-assets/live-interaction-runtime-lggEtuED.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js",
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/fr-BnpRKP3b.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"houseplan-assets/live-interaction-runtime-DCdT8M15.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js",
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
],
"lazyGzipBytes": 239616,
"lazyGzipBytes": 240679,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"lazyEditorGzipBytes": 177269,
"lazyEditorGzipBytes": 178333,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js"
],
"lazyOnboardingGzipBytes": 14240,
"lazyOnboardingGzipBytes": 14242,
"lazyLocaleFiles": [
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/fr-BKs-Jkxs.js"
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/fr-BnpRKP3b.js"
],
"lazyLocaleGzipBytes": 49121,
"lazyLocaleGzipBytes": 49117,
"files": [
{
"path": "houseplan-assets/backdrop-pick-3--jwtiu.js",
"sha256": "09661250c6c0e13c040a6748f200d29cc9e38ee5ea76970c61551bd598e3d2bd",
"path": "houseplan-assets/backdrop-pick-BU_929Qj.js",
"sha256": "0bc4d8e2d315d45ea4a95e448103871ff78f0a3ec786573bcd4a5099f647e8be",
"rawBytes": 21417,
"gzipBytes": 7283,
"gzipBytes": 7281,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-BdA6QNeW.js",
"sha256": "0492698526e3618b755b023b22f922a92ffb89fff3144653b224a69605544244",
"path": "houseplan-assets/de-3nbAlBRV.js",
"sha256": "450c902d21041bdbb79e6473e00a259e5208a8e5a12da365a15c7f7f7bb66c16",
"rawBytes": 82182,
"gzipBytes": 24830,
"gzipBytes": 24828,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-CxD1MzZV.js",
"sha256": "67857fdb93e7d25c4d39948d8ac678b1ff97beef92db98a7baeea7732e19f528",
"path": "houseplan-assets/editor-B-KmowKP.js",
"sha256": "eac8636f90e745939687bb7c1cae5f18e5ed9003b0745f39bf35a5016b9eb7bf",
"rawBytes": 3831,
"gzipBytes": 1584,
"gzipBytes": 1583,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-BKs-Jkxs.js",
"sha256": "046058752a3f71a01dbb4b17dc6ecefc04d3c866af1cc8705b3af136cddfbf89",
"path": "houseplan-assets/fr-BnpRKP3b.js",
"sha256": "f396a1c74fbf7353a1c4f7f43dfb9111ecbe970596c52b4d15ef93c9a82e00fb",
"rawBytes": 84312,
"gzipBytes": 24291,
"gzipBytes": 24289,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-BL0VKqIs.js",
"sha256": "d86d0acddcc1d0437b14c144deca50fef492ecab089a4f0f512c901ba2ab5d42",
"path": "houseplan-assets/houseplan-card-CV9tmn2h.js",
"sha256": "4a7d954f55bc45135a28ef3afbd4b6dbe59fa0802927da417dea4c15b02b2653",
"rawBytes": 1053904,
"gzipBytes": 295930,
"gzipBytes": 295928,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/fr-BKs-Jkxs.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"houseplan-assets/live-interaction-runtime-lggEtuED.js"
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/fr-BnpRKP3b.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"houseplan-assets/live-interaction-runtime-DCdT8M15.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"sha256": "ede5aa1553c5fdf0c9db4d06cfd360ed61db2c12a77d43741d90c1799df72dcc",
"rawBytes": 619056,
"gzipBytes": 163554,
"path": "houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"sha256": "95be5027257d2249a1a7073ba01ce39ca7ffd81cacec9c00214d10347984c560",
"rawBytes": 622513,
"gzipBytes": 164623,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"sha256": "4611eefcde2a723a5ed449ef05886f64e54944fd84dba4d6a37c7a8f7ca92eaf",
"path": "houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"sha256": "d02425fc462a82fe378a7253e3716c284e8d3b819c77a9c10aab64df034fa33c",
"rawBytes": 28088,
"gzipBytes": 6957,
"gzipBytes": 6961,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"sha256": "61a3c17e910f1cf2f2f3870dd0bce7e38f1bba8aff7d1bef838394207cc1fa4f",
"path": "houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"sha256": "f7902ecdcf4baa732ac354217caa5bd474704fadd99e6bca09e29006982d316c",
"rawBytes": 6006,
"gzipBytes": 2376,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js",
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js",
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-lggEtuED.js",
"sha256": "b9a6b81d8de15608afa74cbf7bf4326e72a0c55e8b67c5c2014ccda8c8a3f105",
"path": "houseplan-assets/live-interaction-runtime-DCdT8M15.js",
"sha256": "e5e346c774c87a1cbcf4522d2e1f503bb41f2705c455903ca9914470ffdbe4d5",
"rawBytes": 5196,
"gzipBytes": 2128,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-iEvoOICM.js",
"sha256": "90cdb5a950f34faa82733d04ea056bc36f010b3c0dc6be67a46b77f66f6d1909",
"path": "houseplan-assets/zigbee-topology-CJoMsB_b.js",
"sha256": "8b7d337345c5dd33437928dd2203d3262e0b792a146f224e8146f149e73f5530",
"rawBytes": 12382,
"gzipBytes": 4848,
"gzipBytes": 4846,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js",
"sha256": "9f7fcfeefa803da5a072d38770448d49fdc67cb2f97036ef6314486aeed49f26",
"path": "houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js",
"sha256": "fe51ea04e0509ffdf2a0ddef2b7c3efd6d27f8bb7a1c20f4bd199572841ab9a4",
"rawBytes": 3941,
"gzipBytes": 1765,
"gzipBytes": 1764,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "eee711af5b32a40d46a8e19deb75a3da4a8b0bc93302f8b5b3cd52ac362174ed",
"sha256": "c2e0487961680f1e212b4fc0751f7cf7e692cdd42daa20c02d981c257576a099",
"rawBytes": 1183,
"gzipBytes": 797,
"gzipBytes": 796,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";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-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";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-CV9tmn2h.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}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";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-BL0VKqIs.js";import{a,r as h,t as d}from"./zigbee-topology-iEvoOICM.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-CZ-n5ilU.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`
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";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-CV9tmn2h.js";import{a,r as h,t as d}from"./zigbee-topology-CJoMsB_b.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-DdBA3cDf.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}
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";import{n as t,b as n}from"./zigbee-topology-iEvoOICM.js";import{I as e}from"./houseplan-card-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";import{n as t,b as n}from"./zigbee-topology-CJoMsB_b.js";import{I as e}from"./houseplan-card-CV9tmn2h.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__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";try{await import("./houseplan-assets/houseplan-card-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";try{await import("./houseplan-assets/houseplan-card-CV9tmn2h.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)}
+19
View File
@@ -216,12 +216,21 @@ try {
const loadLongTaskResult = await loadLongTasks.stop();
let fullRenderCount = 0;
let diagnosticsScanCount = 0;
let physicalPreflightCount = 0;
let physicalPreflightMs = 0;
const fullRenderReasons = [];
if (interaction) {
const renderBody = card._renderBody.bind(card);
card._renderBody = (...args) => { fullRenderCount++; return renderBody(...args); };
const bindingStatus = card._bindingStatus.bind(card);
card._bindingStatus = (...args) => { diagnosticsScanCount++; return bindingStatus(...args); };
const checkSpacePhysicalGeometry = card._checkSpacePhysicalGeometry.bind(card);
card._checkSpacePhysicalGeometry = (...args) => {
physicalPreflightCount++;
const started = performance.now();
try { return checkSpacePhysicalGeometry(...args); }
finally { physicalPreflightMs += performance.now() - started; }
};
const willUpdate = card.willUpdate.bind(card);
card.willUpdate = (changed) => {
fullRenderReasons.push([...changed.keys()].map(String));
@@ -532,9 +541,15 @@ try {
card._rszEdgeDown(resizeEvent, resizeRoom.id, 1);
const resizePlan = card._resize?.plan || card._rszDrag?.plan;
if (!resizePlan) throw new Error('interaction resize plan was not created');
const resizePreflightBefore = physicalPreflightCount;
const resizePreflightMsBefore = physicalPreflightMs;
editorStage = card.renderRoot.querySelector('.stage');
editorRect = editorStage.getBoundingClientRect();
editorView = card._viewOr(card._baseVb());
// A browser delivers pointerdown and pointermove as separate tasks.
// Keep the synthetic harness from attributing both handlers to one
// impossible long task while still timing every move-side preflight.
await new Promise((done) => setTimeout(done, 0));
editorPartLongTasks = startLongTaskWindow();
partStarted = performance.now();
editorMoveBefore = fullRenderCount;
@@ -552,6 +567,8 @@ try {
}
editorTerminalBefore = fullRenderCount;
editorMoveRenders.push(editorTerminalBefore - editorMoveBefore);
deltas.resizeLivePreflightChecks = physicalPreflightCount - resizePreflightBefore;
deltas.resizeLivePreflightMs = physicalPreflightMs - resizePreflightMsBefore;
card._rszCancelDrag();
await card.updateComplete;
await frame();
@@ -613,6 +630,7 @@ try {
deltas.editorLivePaints = (card._liveEditorPaintCount || 0) - editorPaintBefore;
deltas.editorConfigStable = JSON.stringify(card._serverCfg) === editorConfigBefore;
deltas.editorWsWrites = wsCalls - editorCallsBefore;
deltas.editorLongTaskParts = editorLongTaskWindows;
card._setMode('view', false);
await card.updateComplete;
@@ -639,6 +657,7 @@ try {
|| deltas.cameraMoveFullRenders !== 0 || deltas.cameraTerminalFullRenders !== 1
|| deltas.editorMoveFullRenders.some((count) => count !== 0)
|| deltas.editorTerminalFullRenders.some((count) => count > 1)
|| deltas.resizeLivePreflightChecks < 1
|| deltas.editorLivePaints < 3 || !deltas.editorConfigStable || deltas.editorWsWrites !== 0
|| !deltas.viewBoxMoved || deltas.overlayErrorPx > 1 || !deltas.heavyNodeStable
|| deltas.diagnosticsScans !== 0
+1
View File
@@ -19,6 +19,7 @@ export const LARGE_HOUSE_CARD_CONTRACT = Object.freeze({
'_baseVb',
'_bindingStatus',
'_cancelDecorGesture',
'_checkSpacePhysicalGeometry',
'_decorBoxOf',
'_dtMeasure',
'_dtMove',
+82 -82
View File
@@ -1,182 +1,182 @@
{
"schema": 1,
"fingerprint": "ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597",
"fingerprint": "0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 296727,
"initialViewGzipBytes": 296724,
"lazyFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/fr-BKs-Jkxs.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"houseplan-assets/live-interaction-runtime-lggEtuED.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js",
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/fr-BnpRKP3b.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"houseplan-assets/live-interaction-runtime-DCdT8M15.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js",
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
],
"lazyGzipBytes": 239616,
"lazyGzipBytes": 240679,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"lazyEditorGzipBytes": 177269,
"lazyEditorGzipBytes": 178333,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js"
],
"lazyOnboardingGzipBytes": 14240,
"lazyOnboardingGzipBytes": 14242,
"lazyLocaleFiles": [
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/fr-BKs-Jkxs.js"
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/fr-BnpRKP3b.js"
],
"lazyLocaleGzipBytes": 49121,
"lazyLocaleGzipBytes": 49117,
"files": [
{
"path": "houseplan-assets/backdrop-pick-3--jwtiu.js",
"sha256": "09661250c6c0e13c040a6748f200d29cc9e38ee5ea76970c61551bd598e3d2bd",
"path": "houseplan-assets/backdrop-pick-BU_929Qj.js",
"sha256": "0bc4d8e2d315d45ea4a95e448103871ff78f0a3ec786573bcd4a5099f647e8be",
"rawBytes": 21417,
"gzipBytes": 7283,
"gzipBytes": 7281,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-BdA6QNeW.js",
"sha256": "0492698526e3618b755b023b22f922a92ffb89fff3144653b224a69605544244",
"path": "houseplan-assets/de-3nbAlBRV.js",
"sha256": "450c902d21041bdbb79e6473e00a259e5208a8e5a12da365a15c7f7f7bb66c16",
"rawBytes": 82182,
"gzipBytes": 24830,
"gzipBytes": 24828,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-CxD1MzZV.js",
"sha256": "67857fdb93e7d25c4d39948d8ac678b1ff97beef92db98a7baeea7732e19f528",
"path": "houseplan-assets/editor-B-KmowKP.js",
"sha256": "eac8636f90e745939687bb7c1cae5f18e5ed9003b0745f39bf35a5016b9eb7bf",
"rawBytes": 3831,
"gzipBytes": 1584,
"gzipBytes": 1583,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-BKs-Jkxs.js",
"sha256": "046058752a3f71a01dbb4b17dc6ecefc04d3c866af1cc8705b3af136cddfbf89",
"path": "houseplan-assets/fr-BnpRKP3b.js",
"sha256": "f396a1c74fbf7353a1c4f7f43dfb9111ecbe970596c52b4d15ef93c9a82e00fb",
"rawBytes": 84312,
"gzipBytes": 24291,
"gzipBytes": 24289,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-BL0VKqIs.js",
"sha256": "d86d0acddcc1d0437b14c144deca50fef492ecab089a4f0f512c901ba2ab5d42",
"path": "houseplan-assets/houseplan-card-CV9tmn2h.js",
"sha256": "4a7d954f55bc45135a28ef3afbd4b6dbe59fa0802927da417dea4c15b02b2653",
"rawBytes": 1053904,
"gzipBytes": 295930,
"gzipBytes": 295928,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-BdA6QNeW.js",
"houseplan-assets/editor-CxD1MzZV.js",
"houseplan-assets/fr-BKs-Jkxs.js",
"houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"houseplan-assets/live-interaction-runtime-lggEtuED.js"
"houseplan-assets/de-3nbAlBRV.js",
"houseplan-assets/editor-B-KmowKP.js",
"houseplan-assets/fr-BnpRKP3b.js",
"houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"houseplan-assets/live-interaction-runtime-DCdT8M15.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-Hmf_xZ7m.js",
"sha256": "ede5aa1553c5fdf0c9db4d06cfd360ed61db2c12a77d43741d90c1799df72dcc",
"rawBytes": 619056,
"gzipBytes": 163554,
"path": "houseplan-assets/houseplan-editor-runtime-DKklgiy8.js",
"sha256": "95be5027257d2249a1a7073ba01ce39ca7ffd81cacec9c00214d10347984c560",
"rawBytes": 622513,
"gzipBytes": 164623,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-BaA82WmZ.js",
"sha256": "4611eefcde2a723a5ed449ef05886f64e54944fd84dba4d6a37c7a8f7ca92eaf",
"path": "houseplan-assets/houseplan-onboarding-runtime-Cm-zppbN.js",
"sha256": "d02425fc462a82fe378a7253e3716c284e8d3b819c77a9c10aab64df034fa33c",
"rawBytes": 28088,
"gzipBytes": 6957,
"gzipBytes": 6961,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-3--jwtiu.js",
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/backdrop-pick-BU_929Qj.js",
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-CrVCE5XW.js",
"sha256": "61a3c17e910f1cf2f2f3870dd0bce7e38f1bba8aff7d1bef838394207cc1fa4f",
"path": "houseplan-assets/hp-zigbee-topology-overlay-ftCf_TFt.js",
"sha256": "f7902ecdcf4baa732ac354217caa5bd474704fadd99e6bca09e29006982d316c",
"rawBytes": 6006,
"gzipBytes": 2376,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js",
"houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js",
"houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-lggEtuED.js",
"sha256": "b9a6b81d8de15608afa74cbf7bf4326e72a0c55e8b67c5c2014ccda8c8a3f105",
"path": "houseplan-assets/live-interaction-runtime-DCdT8M15.js",
"sha256": "e5e346c774c87a1cbcf4522d2e1f503bb41f2705c455903ca9914470ffdbe4d5",
"rawBytes": 5196,
"gzipBytes": 2128,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-iEvoOICM.js",
"sha256": "90cdb5a950f34faa82733d04ea056bc36f010b3c0dc6be67a46b77f66f6d1909",
"path": "houseplan-assets/zigbee-topology-CJoMsB_b.js",
"sha256": "8b7d337345c5dd33437928dd2203d3262e0b792a146f224e8146f149e73f5530",
"rawBytes": 12382,
"gzipBytes": 4848,
"gzipBytes": 4846,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-CZ-n5ilU.js",
"sha256": "9f7fcfeefa803da5a072d38770448d49fdc67cb2f97036ef6314486aeed49f26",
"path": "houseplan-assets/zigbee-topology-runtime-DdBA3cDf.js",
"sha256": "fe51ea04e0509ffdf2a0ddef2b7c3efd6d27f8bb7a1c20f4bd199572841ab9a4",
"rawBytes": 3941,
"gzipBytes": 1765,
"gzipBytes": 1764,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js",
"houseplan-assets/zigbee-topology-iEvoOICM.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js",
"houseplan-assets/zigbee-topology-CJoMsB_b.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "eee711af5b32a40d46a8e19deb75a3da4a8b0bc93302f8b5b3cd52ac362174ed",
"sha256": "c2e0487961680f1e212b4fc0751f7cf7e692cdd42daa20c02d981c257576a099",
"rawBytes": 1183,
"gzipBytes": 797,
"gzipBytes": 796,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-BL0VKqIs.js"
"houseplan-assets/houseplan-card-CV9tmn2h.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";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-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";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-CV9tmn2h.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}
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,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";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-BL0VKqIs.js";import{a,r as h,t as d}from"./zigbee-topology-iEvoOICM.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-CZ-n5ilU.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`
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";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-CV9tmn2h.js";import{a,r as h,t as d}from"./zigbee-topology-CJoMsB_b.js";import{zigbeeTopologyRuntimeSnapshot as p,subscribeZigbeeTopology as c}from"./zigbee-topology-runtime-DdBA3cDf.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}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";import{n as t,b as n}from"./zigbee-topology-iEvoOICM.js";import{I as e}from"./houseplan-card-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";import{n as t,b as n}from"./zigbee-topology-CJoMsB_b.js";import{I as e}from"./houseplan-card-CV9tmn2h.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
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ef5a5d97bf3ee137f49700014533195c13e361a1e576611930e1691e9b897597";try{await import("./houseplan-assets/houseplan-card-BL0VKqIs.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__="0d5ff9930a326df40a69665b8ab60fd2338148edc8b55031d3b6867d104a33d5";try{await import("./houseplan-assets/houseplan-card-CV9tmn2h.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 -2
View File
@@ -7801,7 +7801,7 @@ export class HouseplanCard extends LitElement {
/** #329: violations this write would ADD; inherited ones stay untouched. */
private _junctionLimitsIntroduced(
candidate: any, previousConfig: any, spaceId: string,
candidate: ServerConfig, previousConfig: ServerConfig, spaceId: string,
): JunctionLimitViolation[] {
return this._editorRuntimeOrThrow()._junctionLimitsIntroduced(candidate, previousConfig, spaceId);
}
@@ -9194,7 +9194,7 @@ export class HouseplanCard extends LitElement {
space: SpaceModel,
roomId: string,
openCuts: number[][] = this._openCuts(),
roomWalls = this._wallUnionGeometry()?.roomGeom,
roomWalls: ReturnType<typeof wallBodiesGeometry>['roomGeom'] = this._wallUnionGeometry()?.roomGeom,
multiWallNodes = this._wallUnionGeometry()?.multiWallNodes,
): number[][] | null {
const cutsKey = openCuts.map((cut) => cut.join(',')).join(';');
+13 -13
View File
@@ -58,9 +58,10 @@ import {
type SafeResizePlan, type SafeResizeReason, type SafeResizeResolution,
} from './resize';
import {
ResizeController, resizeLivePreflightAllowed,
ResizeController,
type ResizeProjectionResult,
} from './resize-controller';
import { resizeLiveCandidateSpace, resizeLiveJunctionRoomIds } from './resize-live-preflight';
import {
placeResizeAreaLabel, resizeMeasuredEdges,
type ResizeAreaPlacement,
@@ -972,7 +973,7 @@ export interface HouseplanEditorHostPort {
_importQueue: string[];
_importTotal: number;
_infoCard: DevItem | null;
_innerRoomContour: (space: SpaceModel, roomId: string, openCuts?: number[][], roomWalls?: any, multiWallNodes?: MultiWallNodeMap | null | undefined) => number[][] | null;
_innerRoomContour: (space: SpaceModel, roomId: string, openCuts?: number[][], roomWalls?: ReturnType<typeof wallBodiesGeometry>['roomGeom'], multiWallNodes?: MultiWallNodeMap | null | undefined) => number[][] | null;
_junctionLimitViolations: (config: unknown, spaceId: string,
sharedGeometry?: JunctionSharedGeometry | null, roomIds?: ReadonlySet<string>) => JunctionLimitViolation[];
_isVacDev: (d: DevItem) => boolean;
@@ -2059,13 +2060,13 @@ public _junctionLimitViolations(
public _junctionLimitLabel(violation: JunctionLimitViolation): string {
const round = (value: number) => String(Math.round(value * 10) / 10);
return this.host._t(`junction.limit_${violation.rule}` as any, {
return this.host._t(`junction.limit_${violation.rule}` as I18nKey, {
actual: round(violation.actual), limit: round(violation.limit),
});
}
public _junctionLimitsIntroduced(
candidate: any, previousConfig: any, spaceId: string,
candidate: ServerConfig, previousConfig: ServerConfig, spaceId: string,
candidateGeometry?: JunctionSharedGeometry | null,
affectedRoomIds?: readonly string[],
): JunctionLimitViolation[] {
@@ -2077,8 +2078,8 @@ public _junctionLimitsIntroduced(
// materialized, while legacy documents cross that barrier once.
const affected = affectedRoomIds?.length ? new Set(affectedRoomIds) : undefined;
let inherited: JunctionLimitViolation[] | undefined;
const previousSpace = (previousConfig?.spaces || [])
.find((space: any) => space?.id === spaceId);
const previousSpace = (previousConfig.spaces || [])
.find((space) => space?.id === spaceId);
let fingerprint = '';
try { fingerprint = spacePhysicalGeometryFingerprint(previousSpace); }
catch { fingerprint = ''; }
@@ -3650,8 +3651,9 @@ public _rszProjectPreview(
}
if (wallRecordCarrierViolations(changedWalls, wallCarriers, this.host._wallKeyPitch,
NORM_W, s.walls || []).length) return { ok: false, reason: 'wall-metadata' };
const preflight = resizeLivePreflightAllowed(sp.rooms) ? this._rszSpaceCandidateGeometry(this.host._space, sp) : null;
if (preflight && !preflight.ok) return { ok: false, reason: 'physical-geometry' };
const liveSpace = resizeLiveCandidateSpace(sp, changedRoomIds);
if (!liveSpace || !this._rszSpaceCandidateGeometry(this.host._space, liveSpace).ok)
return { ok: false, reason: 'physical-geometry' };
// #329 AC7a: a step that would ADD a junction-limit violation is never
// projected, so the drag stops at the last allowed position instead of
// committing an impossible plan.
@@ -3661,9 +3663,9 @@ public _rszProjectPreview(
(space) => (space?.id === this.host._space ? sp : space),
),
};
try {
try { const junctionSpace = resizeLiveCandidateSpace(sp, resizeLiveJunctionRoomIds(sp.rooms || [], changedRoomIds));
this._resizePreviewNodes = multiWallNodesForGeometry(
sp.rooms || [], sp.walls || [], [], GRID_STEP_N,
junctionSpace?.rooms || [], (junctionSpace?.walls || []) as unknown as WallEntry[], [], GRID_STEP_N,
this.host._cellCm, this.host._gridPitch, NORM_W,
);
} catch { this._resizePreviewNodes = null; }
@@ -3687,12 +3689,10 @@ public _rszProjectPreview(
value: {
preview: { space: this.host._space, sp },
beforeWalls: s.walls || [],
afterWalls: sp.walls || [],
artifact: preflight?.wallGeometry || null,
afterWalls: sp.walls || [], artifact: null,
},
};
}
public _rszAcceptPreview(
preview: ResizePreview | null, wallGeometry: ResizeWallArtifact | null,
): void {
-12
View File
@@ -15,18 +15,6 @@ export interface ResizeControllerRoom {
wall_ids?: string[];
}
/**
* Exact wall union is useful live feedback only while it stays below one-frame
* work. Large plans keep the cheap fixed-topology preview checks and run the
* same fail-closed physical preflight unconditionally at pointerup. Authored
* contour edges are a stable complexity estimate independent of screen size.
*/
export function resizeLivePreflightAllowed(
rooms: readonly { poly?: readonly unknown[] }[], edgeBudget = 64,
): boolean {
return rooms.reduce((total, room) => total + (room.poly?.length || 4), 0) <= edgeBudget;
}
export interface ResizeProjection<TPreview, TArtifact> {
preview: TPreview;
beforeWalls: readonly WallRecordLike[];
+210
View File
@@ -0,0 +1,210 @@
import { GRID_STEP_N } from './space-geometry';
type DataRecord = Record<string, unknown>;
export interface ResizeLiveBounds {
minX: number;
minY: number;
maxX: number;
maxY: number;
}
const asRecord = (value: unknown): DataRecord | null =>
typeof value === 'object' && value !== null ? value as DataRecord : null;
const recordsOf = (value: unknown): DataRecord[] =>
Array.isArray(value) ? value.flatMap((item) => {
const record = asRecord(item);
return record ? [record] : [];
}) : [];
const pointOf = (value: unknown): number[] | null => {
if (!Array.isArray(value)) return null;
const x = Number(value[0]), y = Number(value[1]);
return Number.isFinite(x) && Number.isFinite(y) ? [x, y] : null;
};
const pointsOf = (value: unknown): number[][] =>
Array.isArray(value) ? value.flatMap((item) => {
const point = pointOf(item);
return point ? [point] : [];
}) : [];
function roomPoints(room: DataRecord): number[][] {
const poly = pointsOf(room.poly);
if (poly.length >= 3) return poly;
const x = Number(room.x), y = Number(room.y);
const w = Number(room.w), h = Number(room.h);
if (![x, y, w, h].every(Number.isFinite)) return [];
return [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
}
function boundsOf(points: readonly number[][]): ResizeLiveBounds | null {
if (!points.length) return null;
const xs = points.map((point) => point[0]);
const ys = points.map((point) => point[1]);
return {
minX: Math.min(...xs), minY: Math.min(...ys),
maxX: Math.max(...xs), maxY: Math.max(...ys),
};
}
/**
* Live validation follows the resize solver's affected-room identity instead
* of the size of the floor. Cross-room safety is still checked against the
* complete space before commit; the pointer loop proves the moving rooms and
* their nearby independent physical bodies without rebuilding remote rooms.
*/
export function resizeLiveRoomIds(
roomsInput: readonly unknown[], changedRoomIds: readonly string[],
): string[] {
const rooms = recordsOf(roomsInput);
const changed = new Set(changedRoomIds);
return rooms.flatMap((room) => {
const id = String(room.id || '');
return id && changed.has(id) ? [id] : [];
});
}
/** One non-transitive neighbour layer is enough to retain junction rays. */
export function resizeLiveJunctionRoomIds(
roomsInput: readonly unknown[], changedRoomIds: readonly string[], epsilon = 1e-8,
): string[] {
const rooms = recordsOf(roomsInput);
const changed = new Set(changedRoomIds);
const changedBounds = rooms.flatMap((room) => {
if (!changed.has(String(room.id || ''))) return [];
const bounds = boundsOf(roomPoints(room));
return bounds ? [bounds] : [];
});
return rooms.flatMap((room) => {
const id = String(room.id || '');
const bounds = boundsOf(roomPoints(room));
const touches = !!bounds && changedBounds.some((candidate) =>
bounds.minX <= candidate.maxX + epsilon && bounds.maxX + epsilon >= candidate.minX
&& bounds.minY <= candidate.maxY + epsilon && bounds.maxY + epsilon >= candidate.minY);
return id && (changed.has(id) || touches) ? [id] : [];
});
}
/** Build the exact local physical component checked on every live resize frame. */
export function resizeLiveCandidateSpace(
spaceInput: unknown, changedRoomIds: readonly string[],
): (DataRecord & { rooms: DataRecord[]; walls: DataRecord[] }) | null {
const space = asRecord(spaceInput);
if (!space) return null;
const allRooms = recordsOf(space.rooms);
const ids = new Set(resizeLiveRoomIds(allRooms, changedRoomIds));
const rooms = allRooms.filter((room) => ids.has(String(room.id || '')));
const points = rooms.flatMap(roomPoints);
const core = boundsOf(points);
if (!core || !rooms.length) return null;
const rawCellCm = Number(space.cell_cm);
const cellCm = Number.isFinite(rawCellCm) && rawCellCm > 0 ? rawCellCm : 5;
const roomEdges = rooms.flatMap((room) => {
const poly = roomPoints(room);
return poly.map((point, index) => [point, poly[(index + 1) % poly.length]]);
});
const edgeEpsilon = GRID_STEP_N * 0.02;
const distanceToSegment = (point: number[], a: number[], b: number[]): number => {
const dx = b[0] - a[0], dy = b[1] - a[1];
const denominator = dx * dx + dy * dy;
const t = denominator > 0 ? Math.max(0, Math.min(1,
((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / denominator)) : 0;
return Math.hypot(point[0] - a[0] - t * dx, point[1] - a[1] - t * dy);
};
const segmentDistance = (a: number[], b: number[], c: number[], d: number[]): number => {
const cross = (p: number[], q: number[], r: number[]) =>
(q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]);
const abC = cross(a, b, c), abD = cross(a, b, d);
const cdA = cross(c, d, a), cdB = cross(c, d, b);
if (abC * abD <= edgeEpsilon * edgeEpsilon
&& cdA * cdB <= edgeEpsilon * edgeEpsilon
&& Math.min(a[0], b[0]) <= Math.max(c[0], d[0]) + edgeEpsilon
&& Math.min(c[0], d[0]) <= Math.max(a[0], b[0]) + edgeEpsilon
&& Math.min(a[1], b[1]) <= Math.max(c[1], d[1]) + edgeEpsilon
&& Math.min(c[1], d[1]) <= Math.max(a[1], b[1]) + edgeEpsilon) return 0;
return Math.min(
distanceToSegment(a, c, d), distanceToSegment(b, c, d),
distanceToSegment(c, a, b), distanceToSegment(d, a, b),
);
};
const boundaryDistance = (a: number[], b: number[]): number => Math.min(
...roomEdges.map(([c, d]) => segmentDistance(a, b, c, d)),
);
const overlapsRoomBoundary = (a: number[], b: number[]): boolean =>
roomEdges.some(([c, d]) => {
const dx = d[0] - c[0], dy = d[1] - c[1];
const length = Math.hypot(dx, dy);
if (!(length > edgeEpsilon)) return false;
const lineDistance = (point: number[]) => Math.abs(
(point[0] - c[0]) * dy - (point[1] - c[1]) * dx) / length;
if (lineDistance(a) > edgeEpsilon || lineDistance(b) > edgeEpsilon) return false;
const project = (point: number[]) => (
(point[0] - c[0]) * dx + (point[1] - c[1]) * dy) / (length * length);
const ta = project(a), tb = project(b);
return Math.min(1, Math.max(ta, tb)) - Math.max(0, Math.min(ta, tb)) > edgeEpsilon;
});
const wallOverlaps = (item: DataRecord): boolean => {
const a = pointOf(item.a), b = pointOf(item.b);
if (a && b) return overlapsRoomBoundary(a, b);
const [midpoint] = String(item.key || '').split('@');
const coordinates = midpoint?.split(',').map(Number) || [];
if (coordinates.length === 2 && coordinates.every(Number.isFinite)) {
return roomEdges.some(([start, end]) => distanceToSegment(coordinates, start, end)
<= edgeEpsilon);
}
// Unknown legacy data remains in the proof so malformed local records
// cannot be hidden merely because their position cannot be established.
return true;
};
const walls = recordsOf(space.walls).filter(wallOverlaps);
const wallSegments = recordsOf(space.wall_segments).filter(wallOverlaps);
const roomWallHalf = Math.max(0, ...[...walls, ...wallSegments]
.map((item) => Number(item.cm)).filter(Number.isFinite)) / cellCm * GRID_STEP_N / 2;
const partitions = recordsOf(space.partitions).filter((item) => {
const a = pointOf(item.a), b = pointOf(item.b);
if (!a || !b) return true;
const half = Math.max(0, Number(item.cm) || 0) / cellCm * GRID_STEP_N / 2;
return boundaryDistance(a, b) <= roomWallHalf + half + edgeEpsilon;
});
const roomDrafts = recordsOf(space.room_drafts).filter((item) => {
const draftPoints = pointsOf(item.points);
if (draftPoints.length < 2) return true;
const segments = recordsOf(item.segments);
return draftPoints.some((point, index) => {
if (index + 1 >= draftPoints.length) return false;
const half = Math.max(0, Number(segments[index]?.cm) || 0)
/ cellCm * GRID_STEP_N / 2;
return boundaryDistance(point, draftPoints[index + 1])
<= roomWallHalf + half + edgeEpsilon;
});
});
const wallColumns = recordsOf(space.wall_columns).filter((item) => {
const center = pointOf(item.center);
if (!center) return true;
const radius = Math.max(0, Number(item.cm) || 0) / cellCm * GRID_STEP_N;
return roomEdges.some(([start, end]) => distanceToSegment(center, start, end)
<= roomWallHalf + radius + edgeEpsilon);
});
const partitionIds = new Set(partitions.map((item) => String(item.id || '')).filter(Boolean));
const openings = recordsOf(space.openings).filter((item) => {
const host = asRecord(item.host);
if (host?.kind === 'partition') return partitionIds.has(String(host.id || ''));
const center = pointOf([item.x, item.y]);
if (!center) return true;
return roomEdges.some(([start, end]) => distanceToSegment(center, start, end)
<= edgeEpsilon);
});
return {
...space,
rooms,
walls,
wall_segments: wallSegments,
open_spans: recordsOf(space.open_spans).filter(wallOverlaps),
partitions,
room_drafts: roomDrafts,
wall_columns: wallColumns,
openings,
};
}
+71 -6
View File
@@ -1,7 +1,10 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { ResizeController, resizeLivePreflightAllowed } from '../test-build/resize-controller.js';
import { ResizeController } from '../test-build/resize-controller.js';
import {
resizeLiveCandidateSpace, resizeLiveJunctionRoomIds, resizeLiveRoomIds,
} from '../test-build/resize-live-preflight.js';
import { resolveSafeResize } from '../test-build/resize.js';
const room = () => ({
@@ -138,9 +141,71 @@ test('#264 cancel restores retained masonry only for the same snapshot', () => {
});
});
test('#451 live resize preflight is bounded by authored contour complexity', () => {
const rectangle = { poly: [[0, 0], [1, 0], [1, 1], [0, 1]] };
assert.equal(resizeLivePreflightAllowed(Array(16).fill(rectangle)), true);
assert.equal(resizeLivePreflightAllowed(Array(17).fill(rectangle)), false);
assert.equal(resizeLivePreflightAllowed([{ poly: Array(65).fill([0, 0]) }]), false);
test('#451 live resize preflight keeps only affected rooms at any plan size', () => {
const rect = (id, x, y, w = 1, h = 1) => ({
id, poly: [[x, y], [x + w, y], [x + w, y + h], [x, y + h]],
});
const rooms = Array.from({ length: 100 }, (_, index) =>
rect(`room-${index}`, index % 10, Math.floor(index / 10)));
assert.deepEqual(
resizeLiveRoomIds(rooms, ['room-44']),
['room-44'],
);
assert.deepEqual(resizeLiveRoomIds([
rect('changed', 0, 0, 2, 2), rect('nested', 0.5, 0.5, 0.5, 0.5),
rect('endpoint', 2, 2), rect('remote', 10, 10),
], ['changed']), ['changed']);
assert.deepEqual(resizeLiveJunctionRoomIds([
rect('changed', 0, 0, 2, 2), rect('nested', 0.5, 0.5, 0.5, 0.5),
rect('endpoint', 2, 2), rect('remote', 10, 10),
], ['changed']), ['changed', 'nested', 'endpoint']);
});
test('#451 live resize candidate keeps nearby physical bodies and excludes remote work', () => {
const rect = (id, x, y) => ({
id, poly: [[x, y], [x + 1, y], [x + 1, y + 1], [x, y + 1]],
});
const rooms = Array.from({ length: 100 }, (_, index) =>
rect(`room-${index}`, index % 10, Math.floor(index / 10)));
const candidate = resizeLiveCandidateSpace({
id: 'large', cell_cm: 5, rooms,
walls: [
{ id: 'near', key: '4.5,4.0@0.0000', a: [4, 4], b: [5, 4] },
{ id: 'remote', key: '90.5,90.0@0.0000', a: [90, 90], b: [91, 90] },
{ id: 'unknown-legacy-data', cm: 15 },
],
wall_segments: [
{ id: 'near', a: [4, 4], b: [5, 4] },
{ id: 'remote', a: [90, 90], b: [91, 90] },
],
open_spans: [
{ id: 'near', a: [4, 4], b: [5, 4] },
{ id: 'remote', a: [90, 90], b: [91, 90] },
],
partitions: [
{ id: 'near', a: [4.5, 4.5], b: [5, 5] },
{ id: 'remote', a: [90, 90], b: [91, 91] },
],
room_drafts: [
{ id: 'near', points: [[3, 3], [4, 3], [4, 4]] },
{ id: 'remote', points: [[90, 90], [91, 90], [91, 91]] },
],
wall_columns: [
{ id: 'near', center: [4, 4.5], cm: 15 },
{ id: 'remote', center: [90, 90], cm: 15 },
],
openings: [
{ id: 'near', x: 4.5, y: 4, length: 1 },
{ id: 'remote', x: 90, y: 90, length: 1 },
],
}, ['room-44']);
assert.ok(candidate);
assert.equal(candidate.rooms.length, 1);
assert.deepEqual(candidate.walls.map((item) => item.id), ['near', 'unknown-legacy-data']);
assert.deepEqual(candidate.wall_segments.map((item) => item.id), ['near']);
assert.deepEqual(candidate.open_spans.map((item) => item.id), ['near']);
assert.deepEqual(candidate.partitions.map((item) => item.id), ['near']);
assert.deepEqual(candidate.room_drafts.map((item) => item.id), ['near']);
assert.deepEqual(candidate.wall_columns.map((item) => item.id), ['near']);
assert.deepEqual(candidate.openings.map((item) => item.id), ['near']);
});