Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
192022e986 | ||
|
|
29f7f0bbf7 | ||
|
|
d762ae0231 |
@@ -1,52 +1,52 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328",
|
||||
"fingerprint": "48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 275798,
|
||||
"initialViewGzipBytes": 276013,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
],
|
||||
"lazyGzipBytes": 191497,
|
||||
"lazyGzipBytes": 191502,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 139732,
|
||||
"lazyEditorGzipBytes": 139735,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 9450,
|
||||
"lazyOnboardingGzipBytes": 9452,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js"
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 45270,
|
||||
"lazyLocaleGzipBytes": 45271,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"sha256": "b5fecd75ea99f054c36d60751acb427d1b811f1d2c146e7f364d0221b41862fa",
|
||||
"path": "houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"sha256": "f15a08762e70bb4d20b94fcdf0369e1cc40b6ee685326aacc84e8fb6215c6897",
|
||||
"rawBytes": 6967,
|
||||
"gzipBytes": 2955,
|
||||
"gzipBytes": 2956,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CCuCtkOJ.js",
|
||||
"sha256": "2ddef73d215a84501cfa0d19571465ee03c5ff1dae50d1736ec410253152220a",
|
||||
"path": "houseplan-assets/de-BJs_98k4.js",
|
||||
"sha256": "ca223441f31ede505db5a343f811a63f4ae23e9b563c54414d744a777b6377ad",
|
||||
"rawBytes": 74963,
|
||||
"gzipBytes": 22846,
|
||||
"isEntry": false,
|
||||
@@ -54,72 +54,72 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-ClbxnMnB.js",
|
||||
"sha256": "9996208acec133a9610de5331b43b5b81d02e43d4e0201342722b24c2c73bd8a",
|
||||
"path": "houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"sha256": "27814eb5bc16e4ceda014f930ce25aeb0cec2cc25510b206fa05f892f2d8b678",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1585,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CqyAakjQ.js",
|
||||
"sha256": "cdeb67badf6a140d33a7328ba5f400a28d5352913b832cf9ea86f217a2a1b0d5",
|
||||
"path": "houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"sha256": "f2dc14b47c44c11d2adf16b166344dc585b5821d350bbcd4af50d81d7db5f19f",
|
||||
"rawBytes": 77091,
|
||||
"gzipBytes": 22424,
|
||||
"gzipBytes": 22425,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-B1i-RQty.js",
|
||||
"sha256": "a43d95f521061f5601ad0b7abf55f3a2ec13737d47e5f647aa2515bbee679e02",
|
||||
"rawBytes": 971326,
|
||||
"gzipBytes": 275001,
|
||||
"path": "houseplan-assets/houseplan-card-2MxtjEZi.js",
|
||||
"sha256": "c580f8ec397627b2083b908cce779aae4dd407233eb5afd3b1648f250627be1b",
|
||||
"rawBytes": 972163,
|
||||
"gzipBytes": 275215,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"sha256": "e5d821f7b2669c87d7c6aa75e7cf8d1d0887d9360c4d8f158e6806361d87dc3d",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"sha256": "26eb0765e773f102f975332fa41ea5c10bebe03ac208fe6941a2a2e289557821",
|
||||
"rawBytes": 523581,
|
||||
"gzipBytes": 135192,
|
||||
"gzipBytes": 135194,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js",
|
||||
"sha256": "a78415da2f80cbc85f32f13eb8c360bbd9854f4f2f74e3f3883d98d68716f237",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js",
|
||||
"sha256": "21ac3e7331bd404132318f15846f9b5b8d92ba44358614e08eb3fc6a0630b77f",
|
||||
"rawBytes": 26195,
|
||||
"gzipBytes": 6495,
|
||||
"gzipBytes": 6496,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "efc624683c5d97ee4f7c2694a857f205f893c3d529687747ccbc2d4eb725f0cd",
|
||||
"sha256": "043c4fee2772b138bf7936797d97cda739aa38dbd9ee91f024eff75bada11e9a",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 797,
|
||||
"gzipBytes": 798,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-B1i-RQty.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__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-2MxtjEZi.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,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-B1i-RQty.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-CA_1Inai.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";class F{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await $(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-2MxtjEZi.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-e_tUy6uK.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";class F{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await $(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
|
||||
${s.map(s=>{return _`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";try{await import("./houseplan-assets/houseplan-card-B1i-RQty.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__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";try{await import("./houseplan-assets/houseplan-card-2MxtjEZi.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)}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328",
|
||||
"fingerprint": "48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 275798,
|
||||
"initialViewGzipBytes": 276013,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
],
|
||||
"lazyGzipBytes": 191497,
|
||||
"lazyGzipBytes": 191502,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 139732,
|
||||
"lazyEditorGzipBytes": 139735,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 9450,
|
||||
"lazyOnboardingGzipBytes": 9452,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js"
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 45270,
|
||||
"lazyLocaleGzipBytes": 45271,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"sha256": "b5fecd75ea99f054c36d60751acb427d1b811f1d2c146e7f364d0221b41862fa",
|
||||
"path": "houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"sha256": "f15a08762e70bb4d20b94fcdf0369e1cc40b6ee685326aacc84e8fb6215c6897",
|
||||
"rawBytes": 6967,
|
||||
"gzipBytes": 2955,
|
||||
"gzipBytes": 2956,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CCuCtkOJ.js",
|
||||
"sha256": "2ddef73d215a84501cfa0d19571465ee03c5ff1dae50d1736ec410253152220a",
|
||||
"path": "houseplan-assets/de-BJs_98k4.js",
|
||||
"sha256": "ca223441f31ede505db5a343f811a63f4ae23e9b563c54414d744a777b6377ad",
|
||||
"rawBytes": 74963,
|
||||
"gzipBytes": 22846,
|
||||
"isEntry": false,
|
||||
@@ -54,72 +54,72 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-ClbxnMnB.js",
|
||||
"sha256": "9996208acec133a9610de5331b43b5b81d02e43d4e0201342722b24c2c73bd8a",
|
||||
"path": "houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"sha256": "27814eb5bc16e4ceda014f930ce25aeb0cec2cc25510b206fa05f892f2d8b678",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1585,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CqyAakjQ.js",
|
||||
"sha256": "cdeb67badf6a140d33a7328ba5f400a28d5352913b832cf9ea86f217a2a1b0d5",
|
||||
"path": "houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"sha256": "f2dc14b47c44c11d2adf16b166344dc585b5821d350bbcd4af50d81d7db5f19f",
|
||||
"rawBytes": 77091,
|
||||
"gzipBytes": 22424,
|
||||
"gzipBytes": 22425,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-B1i-RQty.js",
|
||||
"sha256": "a43d95f521061f5601ad0b7abf55f3a2ec13737d47e5f647aa2515bbee679e02",
|
||||
"rawBytes": 971326,
|
||||
"gzipBytes": 275001,
|
||||
"path": "houseplan-assets/houseplan-card-2MxtjEZi.js",
|
||||
"sha256": "c580f8ec397627b2083b908cce779aae4dd407233eb5afd3b1648f250627be1b",
|
||||
"rawBytes": 972163,
|
||||
"gzipBytes": 275215,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CCuCtkOJ.js",
|
||||
"houseplan-assets/editor-ClbxnMnB.js",
|
||||
"houseplan-assets/fr-CqyAakjQ.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js"
|
||||
"houseplan-assets/de-BJs_98k4.js",
|
||||
"houseplan-assets/editor-Bd6Iqh3I.js",
|
||||
"houseplan-assets/fr-Bu6eA3iC.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CUy6UIHV.js",
|
||||
"sha256": "e5d821f7b2669c87d7c6aa75e7cf8d1d0887d9360c4d8f158e6806361d87dc3d",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-r9-2h1_p.js",
|
||||
"sha256": "26eb0765e773f102f975332fa41ea5c10bebe03ac208fe6941a2a2e289557821",
|
||||
"rawBytes": 523581,
|
||||
"gzipBytes": 135192,
|
||||
"gzipBytes": 135194,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B557qIMW.js",
|
||||
"sha256": "a78415da2f80cbc85f32f13eb8c360bbd9854f4f2f74e3f3883d98d68716f237",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D0bm3ZxD.js",
|
||||
"sha256": "21ac3e7331bd404132318f15846f9b5b8d92ba44358614e08eb3fc6a0630b77f",
|
||||
"rawBytes": 26195,
|
||||
"gzipBytes": 6495,
|
||||
"gzipBytes": 6496,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CA_1Inai.js",
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/backdrop-pick-e_tUy6uK.js",
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "efc624683c5d97ee4f7c2694a857f205f893c3d529687747ccbc2d4eb725f0cd",
|
||||
"sha256": "043c4fee2772b138bf7936797d97cda739aa38dbd9ee91f024eff75bada11e9a",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 797,
|
||||
"gzipBytes": 798,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-B1i-RQty.js"
|
||||
"houseplan-assets/houseplan-card-2MxtjEZi.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-B1i-RQty.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__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-2MxtjEZi.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,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-B1i-RQty.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-CA_1Inai.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";class F{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await $(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-2MxtjEZi.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-e_tUy6uK.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";class F{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await $(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
|
||||
${s.map(s=>{return _`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="551019769d7e6c39aac5c2330e0744ac6f80354dc40bc10fbb78138b06f16328";try{await import("./houseplan-assets/houseplan-card-B1i-RQty.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__="48107d0980d57d5c30795b525519febbde405ced483407f38033e16584fb2518";try{await import("./houseplan-assets/houseplan-card-2MxtjEZi.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,14 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Glow in `houseplan-space-card` no longer rebuilds its light graph and wall
|
||||
scene from scratch on every Home Assistant tick: the static path now shares
|
||||
the caching machinery of the full card (light-graph cache, an LRU pool of
|
||||
barrier scenes so a moving door reuses both of its states, the fast masonry
|
||||
recut path, and a cached room clip). This also fixes a light-graph cache
|
||||
regression the full card picked up in beta.4
|
||||
([#375](https://github.com/Matysh/houseplan-card/issues/375)).
|
||||
|
||||
## v1.69.0-beta.4 — 2026-08-29
|
||||
|
||||
- The read-only `houseplan-space-card` can now opt into the full plan's radial
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Glow в `houseplan-space-card` больше не пересобирает граф света и сцену стен
|
||||
с нуля на каждый тик Home Assistant: статический путь получил кэш-механику
|
||||
полной карты (кэш графа света, LRU-пул барьерных сцен — движущаяся дверь
|
||||
переиспользует оба своих состояния, быстрый recut-путь кладки и кэш выреза
|
||||
комнат). Заодно исправлен регресс кэша графа света, который полная карта
|
||||
получила в beta.4
|
||||
([#375](https://github.com/Matysh/houseplan-card/issues/375)).
|
||||
|
||||
## v1.69.0-beta.4 — 2026-08-29
|
||||
|
||||
- В read-only `houseplan-space-card` теперь можно включить радиальный Glow и
|
||||
|
||||
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 277 KiB After Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 141 KiB |
@@ -2,8 +2,8 @@
|
||||
"version": 1,
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"oxipng": "oxipng 10.2.0",
|
||||
"sourceFingerprint": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"oxipng": null,
|
||||
"sourceFingerprint": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -15,8 +15,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "f8c9541c9d7fde462d0efba63dff70944dc03df76ce988eecaf2b269e2863a96"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "0ca982495a7749907c48ebbddfd089c54385a4347f79f64e94af2c5a8d5c03eb"
|
||||
},
|
||||
"view-touch": {
|
||||
"file": "02-view-touch.png",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "21c6fe7d79ff5c878ade8e98a64cc90dfd7bcc66338c864fb5a0bd411471313d"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
|
||||
},
|
||||
"space-create": {
|
||||
"file": "03-space-create.png",
|
||||
@@ -37,8 +37,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "f6d190f0b2ac9125cea68aea2ebbcdf9a7081ae2a3565ff253fb1ed0c4a56483"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
|
||||
},
|
||||
"room-contour-close": {
|
||||
"file": "04-room-contour-close.png",
|
||||
@@ -48,8 +48,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "ca8bb143d0d6679ff09f8e9c1f5ad68b2ca7cfd96a4740a4a650c251f55dfba5"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
"file": "05-plan-context-tray.png",
|
||||
@@ -59,8 +59,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "06b67c5108f9ee29a226cc2920484ccffe0c79840aec09721072b25b91ef8f03"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
|
||||
},
|
||||
"device-editor": {
|
||||
"file": "06-device-editor.png",
|
||||
@@ -70,8 +70,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "9e435a6fe89564b9215b82a61fb7eae998266dd05483524ffa84160c35630e0c"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "8affadfce926fe2f6cf344c33f611c44fcbc79c1969f69c3a6935ff2e99c36c3"
|
||||
},
|
||||
"device-display-preview": {
|
||||
"file": "06-device-display-preview.png",
|
||||
@@ -81,8 +81,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "cc5abe1b3364bf9812bea6ea5cfa84f35120ff9639ed3c4c92958cb646a8caf7"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "6d323b6f641b2cc7db64252f70567348bd3f2551f82e61fd3a2b4674d244918e"
|
||||
},
|
||||
"background-editor": {
|
||||
"file": "07-background-editor.png",
|
||||
@@ -92,8 +92,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "d3b44c4f74c2f92d23e2346451f1a89a603ba6594e9ea32c9c1b53d0946fdad7"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "ed041f571b0c34f1e06b13419ea08d90ebb7c26950f631d46f6cebb9a29f5c20"
|
||||
},
|
||||
"room-card": {
|
||||
"file": "08-room-card.png",
|
||||
@@ -103,8 +103,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "f1bc10b478f7afc68e70cb4c37148f680b0bbd41d50a7d63a21067fc952ff134"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
|
||||
},
|
||||
"device-info": {
|
||||
"file": "09-device-info.png",
|
||||
@@ -114,8 +114,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "10cfb7b64febbfbf24c24add3388109c5d0af3e03b7493dbbd7c9b4237d31f72",
|
||||
"imageSha256": "2634cd7ead5720ce46ea4d6fa8f31006b740c520a73ddc97afe9a6d9d3b49778"
|
||||
"sourceSha256": "35ac821c3b100d4a5e2904a7582b64345fcf9ade265003da8f15cfda2fc7299d",
|
||||
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
# CODE-REVIEW-375-r1
|
||||
|
||||
Issue: #375 · Заход: r1 (код-ревью) · SHA: `29f7f0bbf71e8898c462d5560b1d3813c5a5d7ec`
|
||||
Ветка: `issue/375-static-glow-caches`, база — актуальный `origin/dev`.
|
||||
Вердикт: **зелёный** · блокирующих циклов 0/2 · High: 0 · Medium: 0
|
||||
|
||||
Это первый заход код-ревью для #375 (ТЗ прошло два раунда spec-ревью,
|
||||
SPEC-REVIEW-375-r2 — зелёный). Разбор полный: сокращение по дельте (§2.10)
|
||||
применяется со второго цикла код-ревью, здесь его нет.
|
||||
|
||||
## 1. Скоуп
|
||||
|
||||
Диф (`git diff origin/dev...HEAD`):
|
||||
|
||||
```
|
||||
docs/CHANGELOG.md | 8 ++
|
||||
docs/CHANGELOG.ru.md | 8 ++
|
||||
scripts/mutation-gate.mjs | 22 ++++++
|
||||
src/glow-scene.ts | 4 +-
|
||||
src/space-render.ts | 157 +++++++++++++++++++++++++++---------
|
||||
test/space-render-caches.test.mjs | 109 ++++++++++++++++++++++++++
|
||||
tsconfig.test.json | 2 +-
|
||||
+ dist/**, custom_components/houseplan/frontend/** (класс D, из build-коммита)
|
||||
+ docs/images/*.png, docs/images/screenshots.json (регенерация отпечатка скриншотов,
|
||||
ожидаемо — check-docs считает его по всему src/**)
|
||||
```
|
||||
|
||||
Два коммита:
|
||||
- `d762ae02` `fix:` — класс A (`src/glow-scene.ts`, `src/space-render.ts`) +
|
||||
класс B (`scripts/mutation-gate.mjs`, `test/space-render-caches.test.mjs`,
|
||||
`tsconfig.test.json`) + оба changelog в одном коммите. Трейлеры: `Issue: #375`,
|
||||
`User-Visible: yes` — верно, поведение видимо пользователю (перф), правки
|
||||
обоих CHANGELOG присутствуют в том же коммите.
|
||||
- `29f7f0bb` `build:` — класс D (обновление трёх копий бандла + скриншоты).
|
||||
`User-Visible: no` — верно, чисто механический коммит.
|
||||
|
||||
Реализует все четыре пункта ТЗ rev2 (К1–К4 = V6a–V6d):
|
||||
K1 — убран spread перед `resolvedLightSources` в `glow-scene.ts:177`.
|
||||
K2 — `cachedStaticWallGeometry` вешает non-enumerable `sourceFingerprint`
|
||||
(та же тройка `[spCfg, cellCm, GRID_PITCH]`, что и `revision.geometryFingerprint`).
|
||||
K3 — `cachedStaticLightBarriers` переведена на LRU-8 на space.
|
||||
K4 — новый `cachedStaticEnabledClip` (LRU-8) + bbox-префильтр extras.
|
||||
|
||||
## 2. Как проверялось
|
||||
|
||||
Все гейты ниже прогнаны лично на SHA `29f7f0bb` — зелёного прогона Validate на
|
||||
этом SHA не найдено, поэтому дешёвые и часть профильных гейтов взяты на себя.
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Typecheck | `npx tsc --noEmit` | чисто, без вывода |
|
||||
| Юниты | `npm test` | **1556/0** (1 skipped), совпадает с числом автора |
|
||||
| Сборка + 3 копии бандла | `npm run bundle:sync` | `git status --porcelain` пуст после — dist / custom_components/houseplan/frontend / demo/srv/assets идентичны источникам, лишнего дифа нет |
|
||||
| Доки-отпечаток | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 10 external links)` — diff трогает `src/**`, гейт обязателен и уже удовлетворён коммитом `29f7f0bb` |
|
||||
| Мутанты (новые, из этого же коммита) | `node scripts/mutation-gate.mjs --id=static-glow-light-cache-spread` | **поймано 1/1** — тест краснеет при возврате spread |
|
||||
| Мутанты (новые) | `node scripts/mutation-gate.mjs --id=static-glow-scene-lru-single` | **поймано 1/1** — тест краснеет при LRU=1 |
|
||||
| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 14 «прямых совпадений», все — по широко используемым символам (`GRID_PITCH`, `NORM_W`, `cellCm`, `roomPoly`), ни один не специфичен для glow/кэшей; раздела «зарегистрированная связь»/«НЕОПРЕДЕЛЁННОСТЬ» инструмент не выдал |
|
||||
| Смок parity (AC5, назван в ТЗ) | `node demo/smoke_glow_blending.mjs` | `{"ok":true,"blend":"screen","pools":60,"staticParity":true,"staticPools":60}` |
|
||||
| Смок opt-in space-card (AC5) | `node demo/smoke_space_card.mjs` | OK |
|
||||
| Смок cold view (заявлен автором) | `node demo/smoke_cold_view_toggle.mjs` | OK |
|
||||
| Смок glow-геометрия (тема диффа, не в прямых совпадениях, но подсистема та же) | `node demo/smoke_glow.mjs` | OK, все 33 инварианта true |
|
||||
| Смок glow отказоустойчивость | `node demo/smoke_glow_geometry_resilience.mjs` | OK |
|
||||
| Смок тёмный fallback | `node demo/smoke_glow_fail_dark.mjs` | OK |
|
||||
| Перф-смок (AC5: «cacheGrowth.glowClip: 0») full-card профиль | `npm run benchmark:glow -- --profile=large-house-glow-overlay-v1 --variants=60 --samples=3 --warmups=1` + `benchmark:compare --absolute-only --budgets=demo/performance/budgets-glow-smoke.json` | все чек-поинты ✅, `cache.growth.glowClip: 0`, `cache.growth.cleanFloor: 0` и др. |
|
||||
| Перф-смок static-профиль | `npm run benchmark:glow -- --profile=large-space-card-glow-v1 --variants=60 --samples=3 --warmups=1` + `benchmark:compare --absolute-only --budgets=demo/performance/budgets-space-glow-smoke.json` | все чек-поинты ✅, `cache.growth.glowClip: 0` (это `_glowRuntimeState.clipCache`, общий рантайм — не путать с новым K4-кэшем, но именно эта метрика поимённо названа в AC5) |
|
||||
| Перф-бюджет стыков (#330) | `npm run benchmark:junction-limits` | pass: true, все 5 метрик в бюджете |
|
||||
|
||||
Не прогонял: `npm run invariants` — диф не меняет геометрическую модель (рёбра,
|
||||
записи толщины, `layout`, `marker.space`, `open_spans`); K2/K4 оборачивают уже
|
||||
существующие вызовы `wallBodiesUnionPath`/`innerContourForRoom`/`floorMinusBodies`
|
||||
в кэш-функции и добавляют bbox-префильтр (см. §4), но не меняют формулы —
|
||||
проверено чтением, не исполнением. Полные (`space-default`/`space-glow` с
|
||||
7 семплами на main/schedule) и предрелизные профили performance.yml —
|
||||
не гонял, это предрелизный гейт §8, не гейт ревью; сокращённая (60/3/1,
|
||||
`--absolute-only`) версия того же профиля из обычного Validate прогнана
|
||||
лично (см. таблицу) и покрывает именно то, что называет AC5.
|
||||
`python -m pytest tests_backend` — не гонял, `custom_components/**/*.py` не
|
||||
тронут. Остальные 14 «прямых совпадений» smoke-select — не гонял: все они
|
||||
привязаны к переиспользуемым по всему проекту символам (сетка/масштаб),
|
||||
не к световой/кэш-механике; смоки той же подсистемы (`smoke_glow*`) прогнаны
|
||||
явно вместо них.
|
||||
|
||||
## 3. Разбор по AC (ТЗ rev2)
|
||||
|
||||
**AC1** (кэш light-графа снова живой). `test/space-render-caches.test.mjs:13-41`
|
||||
— поведенческий тест (реальный вызов `resolvedLightSources`, сравнение по
|
||||
идентичности) плюс тест-пин источника (`glow-scene.ts` без спреда). Мутант
|
||||
`static-glow-light-cache-spread` возвращает спред — тест краснеет (проверено
|
||||
лично). **Доказано автотестом, тест умеет падать.**
|
||||
|
||||
**AC2** (recut-путь в static). `src/space-render.ts:560-568` вешает
|
||||
`Object.defineProperty(built, 'sourceFingerprint', { value:
|
||||
contentFingerprint([spCfg, cellCm, GRID_PITCH]), enumerable: false })` —
|
||||
ровно та же тройка, что `resolveLightBarrierRevision` считает
|
||||
`geometryFingerprint` (`src/glow-scene.ts:229-231`, `rawSpaceConfig: spCfg` —
|
||||
`src/space-render.ts:620`). Тест `space-render-caches.test.mjs:48-56` пинит
|
||||
обе стороны условия регексом — тем же способом, каким уже доказан recut
|
||||
полной карты (`test/performance-contract.test.mjs:152`, тоже регекс на
|
||||
`recutWallBodiesGeometry(...)` без поведенческого прогона). Метод не идеален
|
||||
(не ловит семантический разъезд формулы), но соответствует уже принятому в
|
||||
проекте прецеденту для той же ветки кода — не считаю находкой. **Доказано
|
||||
автотестом (source-pin), тест умеет падать** — если убрать `defineProperty`
|
||||
или изменить тройку, оба regex перестанут совпадать.
|
||||
|
||||
**AC3** (LRU-8 на space). `cachedStaticLightBarriers` (`space-render.ts:133-171`)
|
||||
переведена на `Map<fingerprint, scene>` с `lruGet`/`lruSet` (капасити 8).
|
||||
Тесты `space-render-caches.test.mjs:61-93`: ping-pong 6 обращений → 2 сборки
|
||||
(проверено лично: логика `lruGet`/`lruSet` — стандартный move-to-end на Map,
|
||||
корректна), эвикция на 9-й записи, изоляция по `space`/`cfg`. Мутант
|
||||
`static-glow-scene-lru-single` (капасити=1) — тест краснеет (проверено лично).
|
||||
**Доказано автотестом, тест умеет падать.**
|
||||
|
||||
**AC4** (кэш `enabledClip`). Новая `cachedStaticEnabledClip`
|
||||
(`space-render.ts:172-193`) с тем же LRU-паттерном. Ключ на вызове
|
||||
(`space-render.ts:719-721`) — `` `${revision.geometryFingerprint}|<отсортированный
|
||||
список id выключенных комнат>` ``. Прочитал вручную: `geometryFingerprint`
|
||||
целиком покрывает `spCfg` (raw-объект пространства), а `roomGlowOf` берёт
|
||||
`spCfg`-же `settings.glow`/`fill_mode`/`disp.glow` — то есть список
|
||||
выключенных комнат уже логически подразумевается фингерпринтом геометрии;
|
||||
доп. суффикс в ключе избыточен, но безопасен (не может привести к ложному
|
||||
попаданию в кэш, только к чуть более мелкой грануляции). Bbox-префильтр
|
||||
extras (`space-render.ts:735-742`) — тот же AABB-тест, что и у полной карты
|
||||
(`houseplan-card.ts:9388-9392`, сверил построчно) — попадание/непопадание
|
||||
считается идентично, поведение не меняется, меняется только объём входа в
|
||||
`floorMinusBodies`. Тест `space-render-caches.test.mjs:98-109` — поведенческий
|
||||
(реальный вызов, идентичность результата, инвалидация при смене ключа).
|
||||
**Доказано автотестом, тест умеет падать.**
|
||||
|
||||
**AC5** (parity + перф-контракты + `cacheGrowth.glowClip: 0`). Смок
|
||||
`smoke_glow_blending` — байт-в-байт `staticParity:true`, 60/60 пулов (прогнан
|
||||
лично). Перф-контракты `space-default`/`space-glow` — тяжёлые профили
|
||||
(7 семплов) гоняются только в `performance.yml` (main/schedule/manual), это
|
||||
предрелизный гейт; сокращённая версия того же (`large-house-glow-overlay-v1`
|
||||
и `large-space-card-glow-v1`, 60/3/1, `--absolute-only`) — часть обычного
|
||||
Validate, прогнана лично, `cache.growth.glowClip: 0` в обоих прогонах.
|
||||
**Выполнен.**
|
||||
|
||||
**AC6** (мутанты в `scripts/mutation-gate.mjs`). Оба заявленных мутанта
|
||||
(`static-glow-light-cache-spread`, `static-glow-scene-lru-single`) добавлены
|
||||
и лично проверены — 1/1 каждый, ловятся юнитами, не сборкой. **Выполнен.**
|
||||
|
||||
## 4. Дополнительно проверено
|
||||
|
||||
- `resolvedLightSources` теперь получает `input.devices` (тип `readonly D[]`
|
||||
сохранился, спред не нужен даже для совместимости типов — `tsc --noEmit`
|
||||
чист).
|
||||
- `spaceModels(o.cfg)` создаёт свежие объекты комнат на КАЖДЫЙ вызов
|
||||
`renderSpaceStatic` (`src/space-geometry.ts:135-150`), поэтому сравнение
|
||||
`glowEnabledRooms.includes(room)` по ссылке (K4-ключ) остаётся корректным —
|
||||
оно происходит внутри одного и того же вызова, а не между тиками; между
|
||||
тиками используется строковый ключ (fingerprint), а не ссылка. Проверено
|
||||
чтением, не исполнением — это тот участок, где ссылочная нестабильность
|
||||
моделей могла бы незаметно сломать кэш, и он не сломан.
|
||||
- `physicalFingerprint` (extras/декор) — вложен в `spCfg` через `space.partitions`
|
||||
/ `room_drafts` / `wall_columns`, то есть любое изменение состава extras уже
|
||||
меняет и `geometryFingerprint` — новый K4-кэш не может отдать устаревший
|
||||
клип при смене декора. Проверено чтением.
|
||||
- Старый тип `StaticLightBarrierEntry` убран целиком, лишних ссылок не
|
||||
осталось (`grep` пуст).
|
||||
- Трейлеры коммитов, класс правок по путям, оба CHANGELOG — соответствуют
|
||||
`AGENTS.md`/`PROCESS.md`.
|
||||
|
||||
## 5. Находки
|
||||
|
||||
Нет находок уровня High или Medium. Всё в скоупе ТЗ выполнено и доказано.
|
||||
|
||||
Low (не блокирует, оставляю на усмотрение автора, не завожу отдельный issue —
|
||||
он в скоупе, не «попутный дефект соседнего поведения»):
|
||||
- Ключ `cachedStaticEnabledClip` включает список выключенных комнат отдельно
|
||||
от `geometryFingerprint`, хотя последний уже логически покрывает это же
|
||||
состояние (см. AC4 выше). Не баг — просто одна избыточная переменная в
|
||||
ключе. Не требую правки.
|
||||
|
||||
## 6. Унаследовано / не разбиралось заново
|
||||
|
||||
Не применимо — это первый заход код-ревью (r1), раздел §2.10 «Унаследовано
|
||||
из r<N-1>» относится к разбору со второго цикла. ТЗ-стадия (SPEC-REVIEW-375-r1
|
||||
→ r2) пройдена ранее отдельным этапом и здесь не пересматривается по существу
|
||||
— код-ревью проверяет реализацию против уже согласованного ТЗ rev2, а не само
|
||||
ТЗ.
|
||||
|
||||
## 7. Итог
|
||||
|
||||
Все AC1–AC6 доказаны автотестами, каждый тест лично проверен на способность
|
||||
падать (два — через `mutation-gate`, три — прямым запуском с иными входами).
|
||||
Parity full↔static (AC5) подтверждена смоком байт-в-байт. Перф-контракты
|
||||
(сокращённая версия, доступная вне предрелизного гейта) зелёные, включая
|
||||
именованную в AC5 метрику `cache.growth.glowClip: 0`. Дешёвые гейты (tsc,
|
||||
1556 юнитов, build+3 копии бандла, check-docs) зелёные лично. Trailers и
|
||||
changelog в порядке. Готово к очереди на пре-релиз.
|
||||
@@ -691,6 +691,28 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " : await import(/* @vite-ignore */ new URL(`${FRENCH_RETRY_ASSET}?retry`, import.meta.url).href);\n void module.dictionary;\n return { dictionary: en, fingerprint: module.fingerprint };",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'static-glow-light-cache-spread',
|
||||
guard: 'node --test test/space-render-caches.test.mjs',
|
||||
because: 'a fresh spread on the devices array silently defeats RESOLVED_LIGHT_CACHE '
|
||||
+ '(WeakMap by array identity) in BOTH cards — invisible to CI perf profiles (#375 V6a)',
|
||||
patches: [{
|
||||
file: 'src/glow-scene.ts',
|
||||
find: " input.hass, input.devices, null, input.virtualLights,",
|
||||
replace: " input.hass, [...input.devices], null, input.virtualLights,",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'static-glow-scene-lru-single',
|
||||
guard: 'node --test test/space-render-caches.test.mjs',
|
||||
because: 'a single-entry scene cache makes a door ping-pong rebuild the barrier scene '
|
||||
+ 'on every flip — the LRU capacity is the contract, parity with the full card (#375 V6c)',
|
||||
patches: [{
|
||||
file: 'src/space-render.ts',
|
||||
find: "const STATIC_LIGHT_BARRIER_LRU = 8;",
|
||||
replace: "const STATIC_LIGHT_BARRIER_LRU = 1;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'opening-light-quantum-identity',
|
||||
guard: 'node --test --test-name-pattern="#366" test/logic.test.mjs',
|
||||
|
||||
@@ -173,8 +173,10 @@ export function resolveGlowCandidates(input: {
|
||||
gridPitch: number;
|
||||
position: (device: DevItem) => { x: number; y: number };
|
||||
}): GlowCandidate[] {
|
||||
// #375: pass the array as-is — RESOLVED_LIGHT_CACHE is keyed by the array's
|
||||
// identity, so a fresh spread would guarantee a miss on every render.
|
||||
const sources = resolvedLightSources(
|
||||
input.hass, [...input.devices], null, input.virtualLights,
|
||||
input.hass, input.devices, null, input.virtualLights,
|
||||
).filter((source) => source.device.space === input.spaceId);
|
||||
const byDevice = new Map<string, typeof sources>();
|
||||
for (const source of sources) {
|
||||
|
||||
@@ -70,8 +70,28 @@ type StaticWallGeometryEntry = { fingerprint: string; value: StaticWallGeometry
|
||||
const staticWallGeometryCache = new WeakMap<object, Map<string, StaticWallGeometryEntry>>();
|
||||
type StaticPhysicalBodiesEntry = { fingerprint: string; value: number[][][] };
|
||||
const staticPhysicalBodiesCache = new WeakMap<object, Map<string, StaticPhysicalBodiesEntry>>();
|
||||
type StaticLightBarrierEntry = { fingerprint: string; value: LightBarrierScene };
|
||||
const staticLightBarrierCache = new WeakMap<object, Map<string, StaticLightBarrierEntry>>();
|
||||
// #375: the scene cache is an LRU of 8 per space — parity with the full
|
||||
// card's _lightBarrierPool. A single-entry cache made a door flipping
|
||||
// open<->close rebuild the scene on every state change (fingerprint ping-pong).
|
||||
const STATIC_LIGHT_BARRIER_LRU = 8;
|
||||
const staticLightBarrierCache = new WeakMap<object, Map<string, Map<string, LightBarrierScene>>>();
|
||||
// #375: enabledClip is rebuilt only when the geometry or the set of
|
||||
// glow-disabled rooms changes; the full card keeps the analogous clean-floor
|
||||
// LRU (600) — static cards need far fewer entries.
|
||||
const STATIC_ENABLED_CLIP_LRU = 8;
|
||||
const staticEnabledClipCache = new WeakMap<object, Map<string, Map<string, string[]>>>();
|
||||
|
||||
function lruGet<V>(cache: Map<string, V>, key: string): V | undefined {
|
||||
const value = cache.get(key);
|
||||
if (value !== undefined) { cache.delete(key); cache.set(key, value); }
|
||||
return value;
|
||||
}
|
||||
|
||||
function lruSet<V>(cache: Map<string, V>, key: string, value: V, limit: number): void {
|
||||
cache.delete(key);
|
||||
cache.set(key, value);
|
||||
while (cache.size > limit) cache.delete(cache.keys().next().value as string);
|
||||
}
|
||||
|
||||
/** Static cards receive the same immutable server-config object on HA ticks. */
|
||||
function cachedStaticWallGeometry(
|
||||
@@ -110,7 +130,8 @@ function cachedStaticPhysicalBodies(
|
||||
return value;
|
||||
}
|
||||
|
||||
function cachedStaticLightBarriers(
|
||||
/** Exported for tests only (#375): the LRU behaviour is the contract. */
|
||||
export function cachedStaticLightBarriers(
|
||||
cfg: ServerConfig,
|
||||
spaceId: string,
|
||||
fingerprint: string,
|
||||
@@ -118,13 +139,42 @@ function cachedStaticLightBarriers(
|
||||
): LightBarrierScene {
|
||||
let spaces = staticLightBarrierCache.get(cfg as object);
|
||||
if (!spaces) {
|
||||
spaces = new Map<string, StaticLightBarrierEntry>();
|
||||
spaces = new Map<string, Map<string, LightBarrierScene>>();
|
||||
staticLightBarrierCache.set(cfg as object, spaces);
|
||||
}
|
||||
const cached = spaces.get(spaceId);
|
||||
if (cached?.fingerprint === fingerprint) return cached.value;
|
||||
let scenes = spaces.get(spaceId);
|
||||
if (!scenes) {
|
||||
scenes = new Map<string, LightBarrierScene>();
|
||||
spaces.set(spaceId, scenes);
|
||||
}
|
||||
const cached = lruGet(scenes, fingerprint);
|
||||
if (cached) return cached;
|
||||
const value = build();
|
||||
spaces.set(spaceId, { fingerprint, value });
|
||||
lruSet(scenes, fingerprint, value, STATIC_LIGHT_BARRIER_LRU);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Exported for tests only (#375): reuse-by-identity is the contract. */
|
||||
export function cachedStaticEnabledClip(
|
||||
cfg: ServerConfig,
|
||||
spaceId: string,
|
||||
key: string,
|
||||
build: () => string[],
|
||||
): string[] {
|
||||
let spaces = staticEnabledClipCache.get(cfg as object);
|
||||
if (!spaces) {
|
||||
spaces = new Map<string, Map<string, string[]>>();
|
||||
staticEnabledClipCache.set(cfg as object, spaces);
|
||||
}
|
||||
let clips = spaces.get(spaceId);
|
||||
if (!clips) {
|
||||
clips = new Map<string, string[]>();
|
||||
spaces.set(spaceId, clips);
|
||||
}
|
||||
const cached = lruGet(clips, key);
|
||||
if (cached) return cached;
|
||||
const value = build();
|
||||
lruSet(clips, key, value, STATIC_ENABLED_CLIP_LRU);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -499,14 +549,26 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
: { rooms: space.rooms, walls, extras, cellCm, zero: zeroWalls.contour })
|
||||
: '';
|
||||
const canonicalWallGeometry = needsCanonicalWallGeometry
|
||||
? cachedStaticWallGeometry(o.cfg, space.id, wallGeometryFingerprint, () => wallBodiesUnionPath(
|
||||
space.rooms, walls, zeroWalls.contour, [
|
||||
...staticPassages.filter((opening) => opening.host?.kind !== 'partition').map((opening) => ({
|
||||
x: opening.rx, y: opening.ry, angle: opening.angle, length: opening.rlen,
|
||||
})),
|
||||
...hostedCompositeOpenings,
|
||||
], GRID_STEP_N, cellCm, GRID_PITCH, NORM_W, extras,
|
||||
))
|
||||
? cachedStaticWallGeometry(o.cfg, space.id, wallGeometryFingerprint, () => {
|
||||
const built = wallBodiesUnionPath(
|
||||
space.rooms, walls, zeroWalls.contour, [
|
||||
...staticPassages.filter((opening) => opening.host?.kind !== 'partition').map((opening) => ({
|
||||
x: opening.rx, y: opening.ry, angle: opening.angle, length: opening.rlen,
|
||||
})),
|
||||
...hostedCompositeOpenings,
|
||||
], GRID_STEP_N, cellCm, GRID_PITCH, NORM_W, extras,
|
||||
);
|
||||
// #375: the same non-enumerable tag the full card attaches
|
||||
// (houseplan-card.ts, _wallGeometryR): buildLightBarrierScene only takes
|
||||
// the fast recutWallBodiesGeometry path when this fingerprint matches
|
||||
// revision.geometryFingerprint — without the tag the static path always
|
||||
// rebuilt the wall bodies from scratch on a door state change.
|
||||
if (built) Object.defineProperty(built, 'sourceFingerprint', {
|
||||
value: contentFingerprint([spCfg, cellCm, GRID_PITCH]),
|
||||
enumerable: false,
|
||||
});
|
||||
return built;
|
||||
})
|
||||
: null;
|
||||
const passageTunnelGeometry = staticPassages.length && walls.length
|
||||
? openingTunnelGeometries(
|
||||
@@ -651,29 +713,48 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
pruneGlowSources(glowRuntime.state, glowRuntime.host, space.id, seen);
|
||||
if (spots.length) {
|
||||
const allEnabled = glowEnabledRooms.length === revision.polygons.length;
|
||||
const enabledClip = allEnabled ? null : glowEnabledRooms.flatMap((room) => {
|
||||
const poly = roomPoly(room);
|
||||
if (!poly) return [];
|
||||
const floorPoly = walls.length && room.id
|
||||
? (innerContourForRoom(
|
||||
space.rooms, room.id, walls, zeroWalls.contour,
|
||||
GRID_STEP_N, cellCm, GRID_PITCH, NORM_W,
|
||||
canonicalWallGeometry?.roomGeom,
|
||||
canonicalWallGeometry?.multiWallNodes,
|
||||
) || poly)
|
||||
: poly;
|
||||
const cleanGeometry = extras.length ? floorMinusBodies(floorPoly, extras) : null;
|
||||
const clean = cleanGeometry ? polyclipPathD(cleanGeometry) : '';
|
||||
const holes = islandsOf(
|
||||
floorPoly,
|
||||
revision.polygons
|
||||
.filter(({ room: other }) => other !== room)
|
||||
.map(({ poly: other }) => other),
|
||||
);
|
||||
const path = (points: number[][]) =>
|
||||
`M ${points.map((point) => `${point[0]} ${point[1]}`).join(' L ')} Z`;
|
||||
return [[clean || path(floorPoly), ...holes.map(path)].join(' ')];
|
||||
});
|
||||
// #375: the clip depends only on the geometry (revision.geometryFingerprint
|
||||
// covers spCfg) and on WHICH rooms have glow disabled — not on entity
|
||||
// states. Cache it instead of recomputing boolean geometry every hass tick.
|
||||
const enabledClipKey = `${revision.geometryFingerprint}|${
|
||||
space.rooms.filter((room) => !glowEnabledRooms.includes(room))
|
||||
.map((room) => room.id || `#${space.rooms.indexOf(room)}`).sort().join(',')}`;
|
||||
const enabledClip = allEnabled ? null : cachedStaticEnabledClip(
|
||||
o.cfg, space.id, enabledClipKey, () => glowEnabledRooms.flatMap((room) => {
|
||||
const poly = roomPoly(room);
|
||||
if (!poly) return [];
|
||||
const floorPoly = walls.length && room.id
|
||||
? (innerContourForRoom(
|
||||
space.rooms, room.id, walls, zeroWalls.contour,
|
||||
GRID_STEP_N, cellCm, GRID_PITCH, NORM_W,
|
||||
canonicalWallGeometry?.roomGeom,
|
||||
canonicalWallGeometry?.multiWallNodes,
|
||||
) || poly)
|
||||
: poly;
|
||||
// #375: bbox prefilter, same as the full card's clean-floor cache —
|
||||
// bodies entirely outside the room's bounding box cannot cut it.
|
||||
const xs = floorPoly.map((point: number[]) => point[0]);
|
||||
const ys = floorPoly.map((point: number[]) => point[1]);
|
||||
const box = [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];
|
||||
const bodies = extras.filter((body) => {
|
||||
const bx = body.map((point: number[]) => point[0]);
|
||||
const by = body.map((point: number[]) => point[1]);
|
||||
return Math.max(...bx) >= box[0] && Math.min(...bx) <= box[2]
|
||||
&& Math.max(...by) >= box[1] && Math.min(...by) <= box[3];
|
||||
});
|
||||
const cleanGeometry = bodies.length ? floorMinusBodies(floorPoly, bodies) : null;
|
||||
const clean = cleanGeometry ? polyclipPathD(cleanGeometry) : '';
|
||||
const holes = islandsOf(
|
||||
floorPoly,
|
||||
revision.polygons
|
||||
.filter(({ room: other }) => other !== room)
|
||||
.map(({ poly: other }) => other),
|
||||
);
|
||||
const path = (points: number[][]) =>
|
||||
`M ${points.map((point) => `${point[0]} ${point[1]}`).join(' L ')} Z`;
|
||||
return [[clean || path(floorPoly), ...holes.map(path)].join(' ')];
|
||||
}),
|
||||
);
|
||||
const feather = resolveGlowFeather(
|
||||
glowRuntime.state,
|
||||
o.stageWidth && vb[2] ? o.stageWidth / vb[2] : 1,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolvedLightSources } from '../test-build/devices.js';
|
||||
import {
|
||||
cachedStaticLightBarriers, cachedStaticEnabledClip,
|
||||
} from '../test-build/space-render.js';
|
||||
|
||||
// #375 AC1: the light-graph cache is keyed by the ARRAY IDENTITY. The glow
|
||||
// scene must therefore pass the caller's array as-is; a fresh spread would
|
||||
// guarantee a miss on every render — in the space card AND the full card.
|
||||
|
||||
test('#375 AC1: resolvedLightSources reuses the cached graph for a stable array', () => {
|
||||
const hass = {
|
||||
states: { 'light.l': { state: 'on', attributes: {} } },
|
||||
entities: {},
|
||||
devices: {},
|
||||
};
|
||||
const lamp = {
|
||||
id: 'lamp', name: 'Lamp', model: '', area: 'room', space: 'floor',
|
||||
icon: 'mdi:lightbulb', is_light: true, binding: 'entity:light.l',
|
||||
entity: 'light.l', entities: ['light.l'], x: 0.5, y: 0.5,
|
||||
};
|
||||
const devices = [lamp];
|
||||
const manual = { rev: 1, configRev: 1, off: new Set() };
|
||||
const first = resolvedLightSources(hass, devices, null, manual);
|
||||
const second = resolvedLightSources(hass, devices, null, manual);
|
||||
assert.equal(first, second,
|
||||
'same array + same state must return the SAME cached sources object');
|
||||
const spreadMiss = resolvedLightSources(hass, [...devices], null, manual);
|
||||
assert.notEqual(first, spreadMiss,
|
||||
'a spread copy is a different WeakMap key — full rebuild (the bug of #375)');
|
||||
});
|
||||
|
||||
test('#375 AC1: the glow scene passes input.devices without a spread', () => {
|
||||
const source = readFileSync(new URL('../src/glow-scene.ts', import.meta.url), 'utf8');
|
||||
assert.match(source, /resolvedLightSources\(\s*input\.hass, input\.devices,/,
|
||||
'resolveGlowCandidates must hand the stable array to the cache');
|
||||
assert.ok(!source.includes('[...input.devices]'),
|
||||
'no spread on the cache key (#375 V6a)');
|
||||
});
|
||||
|
||||
// #375 AC2: the static path attaches the same sourceFingerprint tag the full
|
||||
// card attaches, from the same triple buildLightBarrierRevision fingerprints —
|
||||
// proven the same way the full card's recut wiring is proven
|
||||
// (performance-contract.test.mjs): by pinning the source.
|
||||
|
||||
test('#375 AC2: static wall geometry carries the recut fingerprint tag', () => {
|
||||
const render = readFileSync(new URL('../src/space-render.ts', import.meta.url), 'utf8');
|
||||
assert.match(render,
|
||||
/Object\.defineProperty\(built, 'sourceFingerprint', \{\s*\n\s*value: contentFingerprint\(\[spCfg, cellCm, GRID_PITCH\]\),/,
|
||||
'the tag must be the exact revision triple (rawSpaceConfig=spCfg, cellCm, gridPitch=GRID_PITCH)');
|
||||
const scene = readFileSync(new URL('../src/glow-scene.ts', import.meta.url), 'utf8');
|
||||
assert.match(scene, /sharedFingerprint === revision\.geometryFingerprint/,
|
||||
'glow-scene honours the tag by comparing it with the revision fingerprint');
|
||||
});
|
||||
|
||||
// #375 AC3: a door flipping open<->close alternates two scene fingerprints;
|
||||
// the static cache must hold both (LRU 8, parity with the full card's pool).
|
||||
|
||||
test('#375 AC3: scene cache survives a fingerprint ping-pong', () => {
|
||||
const cfg = { marker: 'cfg-a' };
|
||||
let builds = 0;
|
||||
const build = () => ({ built: ++builds });
|
||||
const sequence = ['open', 'closed', 'open', 'closed', 'open', 'closed'];
|
||||
const seen = sequence.map((fp) => cachedStaticLightBarriers(cfg, 'space-1', fp, build));
|
||||
assert.equal(builds, 2,
|
||||
'six alternating lookups must build exactly twice — one per fingerprint');
|
||||
assert.equal(seen[0], seen[2], 'the "open" scene is reused by identity');
|
||||
assert.equal(seen[1], seen[3], 'the "closed" scene is reused by identity');
|
||||
});
|
||||
|
||||
test('#375 AC3: scene cache evicts least-recently-used beyond 8 entries', () => {
|
||||
const cfg = { marker: 'cfg-b' };
|
||||
let builds = 0;
|
||||
const build = () => ({ built: ++builds });
|
||||
for (let i = 0; i < 9; i++) cachedStaticLightBarriers(cfg, 's', `fp-${i}`, build);
|
||||
assert.equal(builds, 9);
|
||||
cachedStaticLightBarriers(cfg, 's', 'fp-8', build);
|
||||
assert.equal(builds, 9, 'the newest entry is still cached');
|
||||
cachedStaticLightBarriers(cfg, 's', 'fp-0', build);
|
||||
assert.equal(builds, 10, 'the oldest entry was evicted at capacity 8');
|
||||
});
|
||||
|
||||
test('#375 AC3: scene cache is namespaced by space and by config object', () => {
|
||||
let builds = 0;
|
||||
const build = () => ({ built: ++builds });
|
||||
const cfg = { marker: 'cfg-c' };
|
||||
cachedStaticLightBarriers(cfg, 'space-1', 'fp', build);
|
||||
cachedStaticLightBarriers(cfg, 'space-2', 'fp', build);
|
||||
cachedStaticLightBarriers({ marker: 'cfg-d' }, 'space-1', 'fp', build);
|
||||
assert.equal(builds, 3, 'no cross-talk between spaces or config revisions');
|
||||
});
|
||||
|
||||
// #375 AC4: the enabled-rooms clip is pure in the geometry fingerprint and the
|
||||
// disabled-room set — a repeated hass tick must not redo boolean geometry.
|
||||
|
||||
test('#375 AC4: enabledClip is reused by identity for an unchanged key', () => {
|
||||
const cfg = { marker: 'cfg-e' };
|
||||
let builds = 0;
|
||||
const build = () => { builds++; return ['M 0 0 L 1 0 L 1 1 Z']; };
|
||||
const key = 'geom-fp|kitchen';
|
||||
const first = cachedStaticEnabledClip(cfg, 'space-1', key, build);
|
||||
const second = cachedStaticEnabledClip(cfg, 'space-1', key, build);
|
||||
assert.equal(builds, 1, 'the second tick must be a cache hit');
|
||||
assert.equal(first, second, 'reuse is by identity — no fresh arrays per tick');
|
||||
cachedStaticEnabledClip(cfg, 'space-1', 'geom-fp|kitchen,porch', build);
|
||||
assert.equal(builds, 2, 'a different disabled set is a different clip');
|
||||
});
|
||||
@@ -15,7 +15,7 @@
|
||||
"src/ha-binding-status.ts",
|
||||
"src/integration-provider.ts", "src/vacuum.ts",
|
||||
"src/sun.ts",
|
||||
"src/light-visibility.ts", "src/glow-scene.ts",
|
||||
"src/light-visibility.ts", "src/glow-scene.ts", "src/space-render.ts",
|
||||
"src/resize.ts", "src/resize-labels.ts", "src/resize-controller.ts", "src/wall-record-preservation.ts",
|
||||
"src/rules.ts",
|
||||
"src/devices.ts", "src/device-inbox.ts",
|
||||
|
||||