mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
feat: validate vacuum map routes and clean them up with their space
User-Visible: no Issue: #162
This commit is contained in:
@@ -101,7 +101,8 @@ ERROR_CODES: frozenset[str] = frozenset({
|
||||
"invalid_light_entity", "invalid_marker_control", "invalid_name",
|
||||
"invalid_partition_opening_host",
|
||||
"invalid_partition_opening_jamb_margin", "invalid_passage_fields",
|
||||
"invalid_space_id", "invalid_toggle_entity", "invalid_value_badge",
|
||||
"invalid_space_id", "invalid_toggle_entity", "invalid_vacuum_map_route",
|
||||
"invalid_value_badge",
|
||||
"invalid_value_badge_attribute", "invalid_value_badge_position",
|
||||
"invalid_value_badge_source", "invalid_value_source",
|
||||
"invalid_value_source_attribute", "io_error", "marker_control_cycle",
|
||||
|
||||
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06",
|
||||
"fingerprint": "781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 292859,
|
||||
"initialViewGzipBytes": 292979,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
],
|
||||
"lazyGzipBytes": 219508,
|
||||
"lazyGzipBytes": 219759,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 163600,
|
||||
"lazyEditorGzipBytes": 163722,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14138,
|
||||
"lazyOnboardingGzipBytes": 14234,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js"
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48949,
|
||||
"lazyLocaleGzipBytes": 49082,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"sha256": "b63841500de9009bdab1dda314af5c1ca3eead311f1dd05f1830f0da589b0f4c",
|
||||
"rawBytes": 20996,
|
||||
"gzipBytes": 7179,
|
||||
"path": "houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"sha256": "8be84f207f5d68f15f792323ab0c4f3a689132d154d3a68a47b04b6112d7fe93",
|
||||
"rawBytes": 21378,
|
||||
"gzipBytes": 7279,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"sha256": "cacdb232ff6403d2fdfb40a304df67aee139fbba4a6a6a2992a176e733ea4edb",
|
||||
"rawBytes": 81940,
|
||||
"gzipBytes": 24744,
|
||||
"path": "houseplan-assets/de-DZwnjt2u.js",
|
||||
"sha256": "b5d15c4488fda2e435fb6220ccaaa2696cbee3991540885a18d3bbcbb31cf5e4",
|
||||
"rawBytes": 82140,
|
||||
"gzipBytes": 24809,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-CxXW-QO8.js",
|
||||
"sha256": "3cda68688d097f4198daa63b06d46eeb60c60ceb9399c4a83c586adae61852e3",
|
||||
"path": "houseplan-assets/editor-CHYZKnjE.js",
|
||||
"sha256": "0eb13886c6e6814cca1ce7d791c92968c9823c50abb31f0895a74c039fe7b9e5",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1584,
|
||||
"gzipBytes": 1582,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CDWnrdfn.js",
|
||||
"sha256": "74c6d85b8e5ae259fa0d40948e19848a3010caf717edea225a8f2129d7cf2227",
|
||||
"rawBytes": 84059,
|
||||
"gzipBytes": 24205,
|
||||
"path": "houseplan-assets/fr-C7WePbhg.js",
|
||||
"sha256": "fe7b6e9bd8dbdd7b3e175bdc6e58b8927538f4078f89a306bed1fc78857d520d",
|
||||
"rawBytes": 84267,
|
||||
"gzipBytes": 24273,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BTXh6SvG.js",
|
||||
"sha256": "a2278cdaf370fb864adb820cfdbd884671ffa4b4ea61785f375ed384907d8562",
|
||||
"rawBytes": 1039135,
|
||||
"gzipBytes": 292060,
|
||||
"path": "houseplan-assets/houseplan-card-CuVuAiJj.js",
|
||||
"sha256": "dc8ac636f240e73c54f26bfff9ca62d5a6516e37850e2498e913637f85ee7f6a",
|
||||
"rawBytes": 1039585,
|
||||
"gzipBytes": 292182,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"sha256": "ccca4dc1b21b421cf2fb37a1bb27ab417492cf1d3478fa2ad8b0f555d9496798",
|
||||
"rawBytes": 589868,
|
||||
"gzipBytes": 154837,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"sha256": "66d51bf9c6363689f5ddf47a87b30e12ddfc970ff726596e5f1324b3d1f2bbab",
|
||||
"rawBytes": 589941,
|
||||
"gzipBytes": 154861,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js",
|
||||
"sha256": "be730d372a4390126890a15fee9af9ae154ac94d7df4d7dd51a3b639128cab91",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js",
|
||||
"sha256": "16c760331824dbd047763b6a8381ec6589da01f95e0bcfcab7801f0ad08e142e",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6959,
|
||||
"gzipBytes": 6955,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ac8fe2593f1937bd0bdc86f131b0b58ac668f59092c70f0b3af4092edba54138",
|
||||
"sha256": "c8bf46999b6b15e83a03786879ed075813d25f6532ff22d3362eaefbcc0d226b",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 799,
|
||||
"gzipBytes": 797,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+9
-9
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";import{g as e,l as o,t,dm as s,A as a,dn as i,dp as l,dq as n,E as r,c}from"./houseplan-card-BTXh6SvG.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__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";import{g as e,l as o,t,dm as s,A as a,dn as i,dp as l,dq as n,E as r,c}from"./houseplan-card-CuVuAiJj.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
+3
-3
File diff suppressed because one or more lines are too long
+53
-53
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";import{l as t,aK as s,A as e,t as o,c as a,ca as l,cb as i,cc as h,cd as c,aH as n,ce as r,cj as p,cf as d,cg as _,ch as u,ci as g,db as m,aG as b,aI as $,dc as v}from"./houseplan-card-BTXh6SvG.js";import{i as f,c as y,e as w,r as D,a as k,b as S,s as C,t as M}from"./backdrop-pick-DxKIBNjn.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";import{l as t,aK as s,A as e,t as o,c as a,ca as l,cb as i,cc as h,cd as c,aH as n,ce as r,cj as p,cf as d,cg as _,ch as u,ci as g,db as m,aG as b,aI as $,dc as v}from"./houseplan-card-CuVuAiJj.js";import{i as f,c as y,e as w,r as D,a as k,b as S,d as C,t as M}from"./backdrop-pick-BJ59BurK.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
|
||||
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...f(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await y(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 w(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return D(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
|
||||
${s.map(s=>{return a`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";try{await import("./houseplan-assets/houseplan-card-BTXh6SvG.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__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";try{await import("./houseplan-assets/houseplan-card-CuVuAiJj.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)}
|
||||
|
||||
@@ -52,6 +52,7 @@ from .validation import (
|
||||
sanitize_marker_id,
|
||||
validate_marker_controls,
|
||||
validate_marker_light_entities,
|
||||
validate_marker_vacuum_routes,
|
||||
validate_marker_value_badges,
|
||||
validate_opening_passages,
|
||||
validate_partition_opening_hosts,
|
||||
@@ -1580,6 +1581,7 @@ def build_space_merge(
|
||||
validate_marker_controls(merged_config, current_config)
|
||||
validate_marker_light_entities(merged_config, current_config)
|
||||
validate_marker_value_badges(merged_config, current_config)
|
||||
validate_marker_vacuum_routes(merged_config, current_config)
|
||||
validate_opening_passages(merged_config, current_config)
|
||||
validate_partition_opening_hosts(merged_config, current_config)
|
||||
except (
|
||||
@@ -1875,6 +1877,7 @@ def create_preview(
|
||||
validate_marker_controls(incoming_config, validate_all=True)
|
||||
validate_marker_light_entities(incoming_config, validate_all=True)
|
||||
validate_marker_value_badges(incoming_config, validate_all=True)
|
||||
validate_marker_vacuum_routes(incoming_config, validate_all=True)
|
||||
except MarkerControlError as err:
|
||||
raise ImportFailure(err.code, str(err)) from err
|
||||
content, confirmation = _content_state(document, same_source, config_root)
|
||||
|
||||
@@ -17,6 +17,7 @@ from custom_components.houseplan.coordinate_canonicalization import (
|
||||
canonicalize_layout_geometry,
|
||||
canonicalize_position,
|
||||
)
|
||||
from custom_components.houseplan.vacuum_routes import validate_marker_routes
|
||||
|
||||
# ---------- limits and extension sets ----------
|
||||
PLAN_EXTENSIONS = {"svg": "image/svg+xml", "png": "image/png", "jpg": "image/jpeg", "webp": "image/webp"}
|
||||
@@ -882,6 +883,50 @@ def validate_marker_value_badges(
|
||||
validate_source(source, "value_source")
|
||||
|
||||
|
||||
def validate_marker_vacuum_routes(
|
||||
config: dict, previous: dict | None = None, *, validate_all: bool = False
|
||||
) -> None:
|
||||
"""Validate changed vacuum map routes; dormant future data round-trips.
|
||||
|
||||
Change-aware like the badge validator above (#162): an untouched legacy or
|
||||
future-shaped block must not block an unrelated save, but any edit to
|
||||
routing has to leave the marker in a state the resolver can answer for —
|
||||
unique identities, six-number matrices and target spaces that exist.
|
||||
"""
|
||||
markers = config.get("markers") or []
|
||||
spaces = config.get("spaces")
|
||||
space_ids = None
|
||||
if isinstance(spaces, list):
|
||||
space_ids = {
|
||||
str(space.get("id")) for space in spaces
|
||||
if isinstance(space, dict) and isinstance(space.get("id"), str)
|
||||
}
|
||||
old_markers = (previous or {}).get("markers") or []
|
||||
old_by_id = {str(marker.get("id")): marker for marker in old_markers}
|
||||
by_id = {str(marker.get("id")): marker for marker in markers}
|
||||
new_ids = set(by_id)
|
||||
consumed_old_ids: set[str] = set()
|
||||
for marker_id, marker in by_id.items():
|
||||
old_marker = _matching_previous_marker(
|
||||
marker, marker_id, old_by_id, old_markers, new_ids,
|
||||
consumed_old_ids, validate_all,
|
||||
)
|
||||
routes = (marker.get("vacuum") or {}).get("map_routes")
|
||||
old_routes = None if validate_all else ((old_marker or {}).get("vacuum") or {}).get("map_routes")
|
||||
if not validate_all and routes == old_routes:
|
||||
continue
|
||||
problems = validate_marker_routes(marker_id, routes, space_ids)
|
||||
if problems:
|
||||
reasons = ", ".join(sorted({problem["reason"] for problem in problems}))
|
||||
# Literal, not the constant: the #42 scanner proves the emitted
|
||||
# code against ERROR_CODES by reading the source, and it cannot
|
||||
# follow a name. The pair is pinned by a test below.
|
||||
raise MarkerControlError(
|
||||
"invalid_vacuum_map_route",
|
||||
f"Invalid vacuum map routes on {marker_id}: {reasons}",
|
||||
)
|
||||
|
||||
|
||||
def validate_marker_light_entities(
|
||||
config: dict, previous: dict | None = None, *, validate_all: bool = False
|
||||
) -> None:
|
||||
|
||||
@@ -113,6 +113,7 @@ from .validation import (
|
||||
valid_space_id,
|
||||
validate_marker_controls,
|
||||
validate_marker_light_entities,
|
||||
validate_marker_vacuum_routes,
|
||||
validate_marker_value_badges,
|
||||
validate_opening_passages,
|
||||
validate_partition_opening_hosts,
|
||||
@@ -1578,6 +1579,7 @@ async def ws_config_set(hass: HomeAssistant, connection, msg: dict[str, Any]) ->
|
||||
validate_marker_controls(msg["config"], data.get("config"))
|
||||
validate_marker_light_entities(msg["config"], data.get("config"))
|
||||
validate_marker_value_badges(msg["config"], data.get("config"))
|
||||
validate_marker_vacuum_routes(msg["config"], data.get("config"))
|
||||
validate_opening_passages(msg["config"], data.get("config"))
|
||||
validate_partition_opening_hosts(msg["config"], data.get("config"))
|
||||
return validate_junction_limits(
|
||||
@@ -1936,6 +1938,7 @@ async def ws_plan_optimize(hass: HomeAssistant, connection, msg: dict[str, Any])
|
||||
validate_marker_controls(msg["config"], config_data.get("config"))
|
||||
validate_marker_light_entities(msg["config"], config_data.get("config"))
|
||||
validate_marker_value_badges(msg["config"], config_data.get("config"))
|
||||
validate_marker_vacuum_routes(msg["config"], config_data.get("config"))
|
||||
validate_opening_passages(msg["config"], config_data.get("config"))
|
||||
validate_partition_opening_hosts(
|
||||
msg["config"], config_data.get("config"),
|
||||
|
||||
Vendored
+60
-60
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06",
|
||||
"fingerprint": "781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 292859,
|
||||
"initialViewGzipBytes": 292979,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
],
|
||||
"lazyGzipBytes": 219508,
|
||||
"lazyGzipBytes": 219759,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 163600,
|
||||
"lazyEditorGzipBytes": 163722,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14138,
|
||||
"lazyOnboardingGzipBytes": 14234,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js"
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48949,
|
||||
"lazyLocaleGzipBytes": 49082,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"sha256": "b63841500de9009bdab1dda314af5c1ca3eead311f1dd05f1830f0da589b0f4c",
|
||||
"rawBytes": 20996,
|
||||
"gzipBytes": 7179,
|
||||
"path": "houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"sha256": "8be84f207f5d68f15f792323ab0c4f3a689132d154d3a68a47b04b6112d7fe93",
|
||||
"rawBytes": 21378,
|
||||
"gzipBytes": 7279,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"sha256": "cacdb232ff6403d2fdfb40a304df67aee139fbba4a6a6a2992a176e733ea4edb",
|
||||
"rawBytes": 81940,
|
||||
"gzipBytes": 24744,
|
||||
"path": "houseplan-assets/de-DZwnjt2u.js",
|
||||
"sha256": "b5d15c4488fda2e435fb6220ccaaa2696cbee3991540885a18d3bbcbb31cf5e4",
|
||||
"rawBytes": 82140,
|
||||
"gzipBytes": 24809,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-CxXW-QO8.js",
|
||||
"sha256": "3cda68688d097f4198daa63b06d46eeb60c60ceb9399c4a83c586adae61852e3",
|
||||
"path": "houseplan-assets/editor-CHYZKnjE.js",
|
||||
"sha256": "0eb13886c6e6814cca1ce7d791c92968c9823c50abb31f0895a74c039fe7b9e5",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1584,
|
||||
"gzipBytes": 1582,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CDWnrdfn.js",
|
||||
"sha256": "74c6d85b8e5ae259fa0d40948e19848a3010caf717edea225a8f2129d7cf2227",
|
||||
"rawBytes": 84059,
|
||||
"gzipBytes": 24205,
|
||||
"path": "houseplan-assets/fr-C7WePbhg.js",
|
||||
"sha256": "fe7b6e9bd8dbdd7b3e175bdc6e58b8927538f4078f89a306bed1fc78857d520d",
|
||||
"rawBytes": 84267,
|
||||
"gzipBytes": 24273,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BTXh6SvG.js",
|
||||
"sha256": "a2278cdaf370fb864adb820cfdbd884671ffa4b4ea61785f375ed384907d8562",
|
||||
"rawBytes": 1039135,
|
||||
"gzipBytes": 292060,
|
||||
"path": "houseplan-assets/houseplan-card-CuVuAiJj.js",
|
||||
"sha256": "dc8ac636f240e73c54f26bfff9ca62d5a6516e37850e2498e913637f85ee7f6a",
|
||||
"rawBytes": 1039585,
|
||||
"gzipBytes": 292182,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-Cm5MlI_Y.js",
|
||||
"houseplan-assets/editor-CxXW-QO8.js",
|
||||
"houseplan-assets/fr-CDWnrdfn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js"
|
||||
"houseplan-assets/de-DZwnjt2u.js",
|
||||
"houseplan-assets/editor-CHYZKnjE.js",
|
||||
"houseplan-assets/fr-C7WePbhg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CmI5Go3m.js",
|
||||
"sha256": "ccca4dc1b21b421cf2fb37a1bb27ab417492cf1d3478fa2ad8b0f555d9496798",
|
||||
"rawBytes": 589868,
|
||||
"gzipBytes": 154837,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-o7FB8qC-.js",
|
||||
"sha256": "66d51bf9c6363689f5ddf47a87b30e12ddfc970ff726596e5f1324b3d1f2bbab",
|
||||
"rawBytes": 589941,
|
||||
"gzipBytes": 154861,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BB8OHZOL.js",
|
||||
"sha256": "be730d372a4390126890a15fee9af9ae154ac94d7df4d7dd51a3b639128cab91",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-CAJfA2x6.js",
|
||||
"sha256": "16c760331824dbd047763b6a8381ec6589da01f95e0bcfcab7801f0ad08e142e",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6959,
|
||||
"gzipBytes": 6955,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DxKIBNjn.js",
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/backdrop-pick-BJ59BurK.js",
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ac8fe2593f1937bd0bdc86f131b0b58ac668f59092c70f0b3af4092edba54138",
|
||||
"sha256": "c8bf46999b6b15e83a03786879ed075813d25f6532ff22d3362eaefbcc0d226b",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 799,
|
||||
"gzipBytes": 797,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BTXh6SvG.js"
|
||||
"houseplan-assets/houseplan-card-CuVuAiJj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+9
-9
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";import{g as e,l as o,t,dm as s,A as a,dn as i,dp as l,dq as n,E as r,c}from"./houseplan-card-BTXh6SvG.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__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";import{g as e,l as o,t,dm as s,A as a,dn as i,dp as l,dq as n,E as r,c}from"./houseplan-card-CuVuAiJj.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}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+53
-53
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";import{l as t,aK as s,A as e,t as o,c as a,ca as l,cb as i,cc as h,cd as c,aH as n,ce as r,cj as p,cf as d,cg as _,ch as u,ci as g,db as m,aG as b,aI as $,dc as v}from"./houseplan-card-BTXh6SvG.js";import{i as f,c as y,e as w,r as D,a as k,b as S,s as C,t as M}from"./backdrop-pick-DxKIBNjn.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";import{l as t,aK as s,A as e,t as o,c as a,ca as l,cb as i,cc as h,cd as c,aH as n,ce as r,cj as p,cf as d,cg as _,ch as u,ci as g,db as m,aG as b,aI as $,dc as v}from"./houseplan-card-CuVuAiJj.js";import{i as f,c as y,e as w,r as D,a as k,b as S,d as C,t as M}from"./backdrop-pick-BJ59BurK.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";class L{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
|
||||
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:p,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...f(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await y(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 w(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return D(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const o=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),a=this.host._spaceDialog,l=a?.saved?.find(s=>s.name===t);if(o&&a&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==a.planUrl)try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
|
||||
${s.map(s=>{return a`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="bd7029808eb6094140d9fa8245f50816cb452430a353f4231d16e02c97078a06";try{await import("./houseplan-assets/houseplan-card-BTXh6SvG.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__="781b6de4892612660b01762cd408049137fa0d00417a43ef5bbdfb1ef37dc9a5";try{await import("./houseplan-assets/houseplan-card-CuVuAiJj.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)}
|
||||
|
||||
@@ -354,6 +354,33 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " case 'нет такого':\n return resolution.kind as any;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'vacuum-route-validation-accepts-a-dead-space',
|
||||
guard: 'node scripts/backend-test-guard.mjs '
|
||||
+ 'invalid_routes_are_rejected '
|
||||
+ 'tests_backend/test_vacuum_route_validation.py',
|
||||
because: 'a route saved against a space that does not exist can never resolve, so the '
|
||||
+ 'write has to be refused at the boundary rather than discovered as a missing robot '
|
||||
+ '(#162, M-D backend half)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/vacuum_routes.py',
|
||||
find: ' elif space_ids is not None and space not in space_ids:\n problems.append(issue(route_id, "unknown_space"))',
|
||||
replace: ' elif False:\n problems.append(issue(route_id, "unknown_space"))',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'space-delete-keeps-foreign-vacuum-routes',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="уносит только свои маршруты" '
|
||||
+ 'test/space-deletion.test.mjs',
|
||||
because: 'a deleted space must take the robot map assignments that pointed at it; leaving '
|
||||
+ 'them behind keeps a route nothing can resolve and a robot that never appears (#162, AC16)',
|
||||
patches: [{
|
||||
file: 'src/space-deletion.ts',
|
||||
find: ' const kept = routes.filter((route: any) => route?.space !== spaceId);',
|
||||
replace: ' const kept = routes;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'area-snapshot-cleanup-ignores-authority',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
@@ -1397,8 +1424,8 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'unregistered-error hole #42 closes (AC5 m1)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/const.py',
|
||||
find: ' "invalid_space_id", "invalid_toggle_entity", "invalid_value_badge",',
|
||||
replace: ' "invalid_toggle_entity", "invalid_value_badge",',
|
||||
find: ' "invalid_space_id", "invalid_toggle_entity", "invalid_vacuum_map_route",',
|
||||
replace: ' "invalid_toggle_entity", "invalid_vacuum_map_route",',
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -226,7 +226,7 @@ import {
|
||||
} from './zero-walls';
|
||||
import { snapNearAxisEndpoint } from './near-axis';
|
||||
import type { SpaceReferenceRepairContext } from './space-reference-repair';
|
||||
import { collectSpaceMarkerDependencies } from './space-deletion';
|
||||
import { collectSpaceMarkerDependencies, spaceDeletionMessage } from './space-deletion';
|
||||
import {
|
||||
checkSpacePhysicalGeometry,
|
||||
checkOptimizeGeometry,
|
||||
@@ -8863,7 +8863,8 @@ public async _deleteSpace(): Promise<void> {
|
||||
key: 'delete-space',
|
||||
kind: 'destructive',
|
||||
title: this.host._t('confirm.delete_space_title'),
|
||||
message: this.host._t('confirm.delete_space_body'),
|
||||
message: spaceDeletionMessage(this.host._t('confirm.delete_space_body'),
|
||||
this.host._t('confirm.delete_space_vac_routes'), dependencies.routeCount),
|
||||
objectName: sp.title,
|
||||
confirmLabel: this.host._t('btn.delete'),
|
||||
cancelLabel: this.host._t('btn.cancel'),
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"confirm.erase_decor": "Das Objekt „{kind}“ löschen? Diese Aktion kann im Editor rückgängig gemacht werden.",
|
||||
"confirm.delete_space_title": "Bereich löschen?",
|
||||
"confirm.delete_space_body": "Der Bereich, alle seine Räume und die gesamte Planmarkierung werden gelöscht.",
|
||||
"confirm.delete_space_vac_routes": "Zuordnungen von Roboterkarten werden ebenfalls entfernt: {count}.",
|
||||
"space.delete_blocked": "Dieser Bereich wird noch von {n} Gerät(en) verwendet. Die Geräte zuerst in einen anderen Bereich verschieben oder löschen.",
|
||||
"toast.pos_save_failed": "Position nicht gespeichert: {err}",
|
||||
"toast.no_entity": "Das Gerät hat keine geeignete Entität",
|
||||
@@ -1153,6 +1154,7 @@
|
||||
"backup.error.invalid_value_badge_attribute": "Ungültiges Wert-Badge-Attribut",
|
||||
"backup.error.invalid_value_badge_position": "Ungültige Wert-Badge-Position",
|
||||
"backup.error.invalid_value_badge_source": "Ungültige Wert-Badge-Quelle",
|
||||
"backup.error.invalid_vacuum_map_route": "Prüfen Sie die Zuordnung der Roboterkarten zu Räumen.",
|
||||
"backup.error.invalid_value_source": "Ungültige Wertquelle",
|
||||
"backup.error.invalid_value_source_attribute": "Ungültiges Wertquellen-Attribut",
|
||||
"backup.error.io_error": "Eine Dateioperation auf dem Server ist fehlgeschlagen",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"confirm.erase_decor": "Erase the {kind} object? You can undo this action from the editor history.",
|
||||
"confirm.delete_space_title": "Delete space?",
|
||||
"confirm.delete_space_body": "The space, all its rooms and all its plan markup will be deleted.",
|
||||
"confirm.delete_space_vac_routes": "Robot map assignments will also be removed: {count}.",
|
||||
"space.delete_blocked": "This space is still used by {n} device(s). Move them to another space or delete them first.",
|
||||
"toast.pos_save_failed": "Failed to save position: {err}",
|
||||
"toast.no_entity": "The device has no suitable entity",
|
||||
@@ -1153,6 +1154,7 @@
|
||||
"backup.error.invalid_value_badge_attribute": "Invalid value badge attribute",
|
||||
"backup.error.invalid_value_badge_position": "Invalid value badge position",
|
||||
"backup.error.invalid_value_badge_source": "Invalid value badge source",
|
||||
"backup.error.invalid_vacuum_map_route": "Check how the robot's maps are assigned to spaces.",
|
||||
"backup.error.invalid_value_source": "Invalid value source",
|
||||
"backup.error.invalid_value_source_attribute": "Invalid value source attribute",
|
||||
"backup.error.io_error": "A file operation failed on the server",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"confirm.erase_decor": "Effacer l’objet {kind} ? Vous pouvez annuler cette action depuis l’historique de l’éditeur.",
|
||||
"confirm.delete_space_title": "Supprimer l’espace ?",
|
||||
"confirm.delete_space_body": "L’espace, toutes ses pièces et tout le tracé du plan seront supprimés.",
|
||||
"confirm.delete_space_vac_routes": "Les affectations de cartes de robots seront aussi supprimées : {count}.",
|
||||
"space.delete_blocked": "Cet espace est encore utilisé par {n} appareil(s). Déplacez-les vers un autre espace ou supprimez-les d’abord.",
|
||||
"toast.pos_save_failed": "Échec de l’enregistrement de la position : {err}",
|
||||
"toast.no_entity": "L’appareil n’a aucune entité adaptée",
|
||||
@@ -1153,6 +1154,7 @@
|
||||
"backup.error.invalid_value_badge_attribute": "Attribut de badge de valeur invalide",
|
||||
"backup.error.invalid_value_badge_position": "Position de badge de valeur invalide",
|
||||
"backup.error.invalid_value_badge_source": "Source de badge de valeur invalide",
|
||||
"backup.error.invalid_vacuum_map_route": "Vérifiez l'affectation des cartes du robot aux espaces.",
|
||||
"backup.error.invalid_value_source": "Source de valeur invalide",
|
||||
"backup.error.invalid_value_source_attribute": "Attribut de source de valeur invalide",
|
||||
"backup.error.io_error": "Une opération de fichier a échoué sur le serveur",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"confirm.erase_decor": "Стереть объект «{kind}»? Действие можно отменить из истории редактора.",
|
||||
"confirm.delete_space_title": "Удалить пространство?",
|
||||
"confirm.delete_space_body": "Пространство, все его комнаты и вся разметка плана будут удалены.",
|
||||
"confirm.delete_space_vac_routes": "Также будет снято сопоставление карт роботов: {count}.",
|
||||
"space.delete_blocked": "Это пространство всё ещё используется устройствами: {n}. Сначала перенесите их в другое пространство или удалите.",
|
||||
"toast.pos_save_failed": "Не удалось сохранить позицию: {err}",
|
||||
"toast.no_entity": "У устройства нет подходящей сущности",
|
||||
@@ -1153,6 +1154,7 @@
|
||||
"backup.error.invalid_value_badge_attribute": "Некорректный атрибут бейджа значения",
|
||||
"backup.error.invalid_value_badge_position": "Некорректная позиция бейджа значения",
|
||||
"backup.error.invalid_value_badge_source": "Некорректный источник бейджа значения",
|
||||
"backup.error.invalid_vacuum_map_route": "Проверьте сопоставление карт робота пространствам.",
|
||||
"backup.error.invalid_value_source": "Некорректный источник значения",
|
||||
"backup.error.invalid_value_source_attribute": "Некорректный атрибут источника значения",
|
||||
"backup.error.io_error": "Файловая операция на сервере не удалась",
|
||||
|
||||
+42
-1
@@ -3,8 +3,21 @@
|
||||
export interface SpaceDeletionDependencyReport {
|
||||
markerIds: string[];
|
||||
count: number;
|
||||
/**
|
||||
* Markers that live on ANOTHER floor but route one of their robot maps here
|
||||
* (#162). They do not block the deletion — the dock is not in this space —
|
||||
* but the user has to be told how many map assignments disappear with it.
|
||||
*/
|
||||
routeMarkerIds: string[];
|
||||
routeCount: number;
|
||||
}
|
||||
|
||||
/** Ids of routes this marker points at the space being deleted. */
|
||||
const routesIntoSpace = (marker: any, spaceId: string): string[] =>
|
||||
(marker?.vacuum?.map_routes || [])
|
||||
.filter((route: any) => route && route.space === spaceId && typeof route.id === 'string')
|
||||
.map((route: any) => route.id);
|
||||
|
||||
const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value));
|
||||
|
||||
export function collectSpaceMarkerDependencies(
|
||||
@@ -23,7 +36,27 @@ export function collectSpaceMarkerDependencies(
|
||||
|| layout?.[marker.id]?.s === spaceId)
|
||||
.map((marker: any) => marker.id))]
|
||||
.sort((a: string, b: string) => a.localeCompare(b));
|
||||
return { markerIds, count: markerIds.length };
|
||||
const routeMarkerIds = (config?.markers || [])
|
||||
.filter((marker: any) => marker?.removed !== true && typeof marker?.id === 'string')
|
||||
.filter((marker: any) => !markerIds.includes(marker.id))
|
||||
.filter((marker: any) => routesIntoSpace(marker, spaceId).length > 0)
|
||||
.map((marker: any) => marker.id)
|
||||
.sort((a: string, b: string) => a.localeCompare(b));
|
||||
return {
|
||||
markerIds, count: markerIds.length,
|
||||
routeMarkerIds, routeCount: routeMarkerIds.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm text for a space delete: the base warning, plus how many robot map
|
||||
* assignments go with it (#162). Composed here so the count cannot quietly
|
||||
* fall out of the dialog when the wording changes.
|
||||
*/
|
||||
export function spaceDeletionMessage(
|
||||
base: string, routesTemplate: string, routeCount: number,
|
||||
): string {
|
||||
return routeCount ? `${base} ${routesTemplate.replace('{count}', String(routeCount))}` : base;
|
||||
}
|
||||
|
||||
export function createSpaceDeletionCandidate(
|
||||
@@ -61,5 +94,13 @@ export function createSpaceDeletionCandidate(
|
||||
for (const [key, position] of Object.entries(layout)) {
|
||||
if ((position as any)?.s === spaceId) delete layout[key];
|
||||
}
|
||||
// #162: a robot docked elsewhere keeps its dock and its other maps; only the
|
||||
// routes that pointed here go, in the same logical operation as the space.
|
||||
for (const marker of config.markers || []) {
|
||||
const routes = marker?.vacuum?.map_routes;
|
||||
if (!Array.isArray(routes)) continue;
|
||||
const kept = routes.filter((route: any) => route?.space !== spaceId);
|
||||
if (kept.length !== routes.length) marker.vacuum.map_routes = kept;
|
||||
}
|
||||
return { config, layout, dependencies };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import {
|
||||
collectSpaceMarkerDependencies, createSpaceDeletionCandidate,
|
||||
collectSpaceMarkerDependencies, createSpaceDeletionCandidate, spaceDeletionMessage,
|
||||
} from '../test-build/space-deletion.js';
|
||||
|
||||
test('issue 244 space dependency count deduplicates all three reference paths', () => {
|
||||
@@ -20,7 +20,7 @@ test('issue 244 space dependency count deduplicates all three reference paths',
|
||||
all: { s: 'f1' }, room: { s: 'f2' }, position: { s: 'f1' }, removed: { s: 'f1' },
|
||||
};
|
||||
assert.deepEqual(collectSpaceMarkerDependencies(config, layout, 'f1'), {
|
||||
markerIds: ['all', 'position', 'room'], count: 3,
|
||||
markerIds: ['all', 'position', 'room'], count: 3, routeMarkerIds: [], routeCount: 0,
|
||||
});
|
||||
const blocked = createSpaceDeletionCandidate(config, layout, 'f1');
|
||||
assert.deepEqual(blocked.config, config);
|
||||
@@ -109,3 +109,57 @@ test('issue 244 deleting the last occupied space detaches placement but preserve
|
||||
assert.equal(config.markers[0].space, 'only', 'input config is immutable');
|
||||
assert.equal(layout.direct.s, 'only', 'input layout is immutable');
|
||||
});
|
||||
|
||||
// --- #162: карты робота, назначенные удаляемому пространству -----------------
|
||||
|
||||
const routeCfg = () => ({
|
||||
spaces: [{ id: 'floor1', rooms: [] }, { id: 'floor2', rooms: [] }],
|
||||
markers: [{
|
||||
id: 'robot', space: 'floor1',
|
||||
vacuum: {
|
||||
source: 'camera.robot',
|
||||
map_routes: [
|
||||
{ id: 'r1', source: 'camera.robot', map_id: 'm1', space: 'floor1', calibration: [1, 0, 0, 0, 1, 0] },
|
||||
{ id: 'r2', source: 'camera.robot', map_id: 'm2', space: 'floor2', calibration: [2, 0, 0, 0, 2, 0] },
|
||||
],
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
||||
test('#162 удаление пространства считает чужие карты роботов отдельно', () => {
|
||||
const report = collectSpaceMarkerDependencies(routeCfg(), {}, 'floor2');
|
||||
assert.deepEqual(report.markerIds, [], 'док робота живёт на другом этаже и удалению не мешает');
|
||||
assert.deepEqual(report.routeMarkerIds, ['robot']);
|
||||
assert.equal(report.routeCount, 1);
|
||||
});
|
||||
|
||||
test('#162 маркер В удаляемом пространстве не считается дважды', () => {
|
||||
const report = collectSpaceMarkerDependencies(routeCfg(), {}, 'floor1');
|
||||
assert.deepEqual(report.markerIds, ['robot']);
|
||||
assert.deepEqual(report.routeMarkerIds, [], 'он уже в блокирующем списке');
|
||||
});
|
||||
|
||||
test('#162 удаление уносит только свои маршруты, док и соседние карты целы', () => {
|
||||
const { config } = createSpaceDeletionCandidate(routeCfg(), {}, 'floor2');
|
||||
const marker = config.markers[0];
|
||||
assert.equal(marker.space, 'floor1', 'док остался на своём этаже');
|
||||
assert.deepEqual(marker.vacuum.map_routes.map((r) => r.id), ['r1']);
|
||||
assert.equal(marker.vacuum.source, 'camera.robot', 'корневой источник не тронут');
|
||||
});
|
||||
|
||||
test('#162 легаси-калибровка удалением пространства не трогается', () => {
|
||||
const cfg = {
|
||||
spaces: [{ id: 'floor1', rooms: [] }, { id: 'floor2', rooms: [] }],
|
||||
markers: [{ id: 'robot', space: 'floor1', vacuum: { source: 'camera.robot', calibration: { m1: [1, 0, 0, 0, 1, 0] } } }],
|
||||
};
|
||||
const { config } = createSpaceDeletionCandidate(cfg, {}, 'floor2');
|
||||
assert.deepEqual(config.markers[0].vacuum.calibration, { m1: [1, 0, 0, 0, 1, 0] });
|
||||
});
|
||||
|
||||
test('#162 текст подтверждения называет число карт, а без них не меняется', () => {
|
||||
const base = 'Пространство будет удалено.';
|
||||
const template = 'Также будет снято сопоставление карт роботов: {count}.';
|
||||
assert.equal(spaceDeletionMessage(base, template, 0), base);
|
||||
assert.equal(spaceDeletionMessage(base, template, 2),
|
||||
'Пространство будет удалено. Также будет снято сопоставление карт роботов: 2.');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Семантическая валидация маршрутов карт при config/set и импорте (#162)."""
|
||||
import pytest
|
||||
|
||||
from custom_components.houseplan.validation import (
|
||||
MarkerControlError,
|
||||
validate_marker_vacuum_routes,
|
||||
)
|
||||
from custom_components.houseplan.vacuum_routes import VAC_ROUTE_ERROR
|
||||
|
||||
IDENTITY = [1, 0, 0, 0, 1, 0]
|
||||
|
||||
|
||||
def _config(routes, spaces=("floor1", "floor2")):
|
||||
return {
|
||||
"spaces": [{"id": space} for space in spaces],
|
||||
"markers": [{"id": "m1", "space": "floor1", "vacuum": {"map_routes": routes}}],
|
||||
}
|
||||
|
||||
|
||||
def _route(**over):
|
||||
base = {"id": "r1", "source": "camera.robot", "map_id": "m1",
|
||||
"space": "floor1", "calibration": IDENTITY}
|
||||
base.update(over)
|
||||
return base
|
||||
|
||||
|
||||
def test_valid_routes_pass():
|
||||
validate_marker_vacuum_routes(_config([_route(), _route(id="r2", map_id="m2", space="floor2")]))
|
||||
|
||||
|
||||
def test_error_code_is_the_public_contract_code():
|
||||
assert VAC_ROUTE_ERROR == "invalid_vacuum_map_route"
|
||||
with pytest.raises(MarkerControlError) as excinfo:
|
||||
validate_marker_vacuum_routes(_config([_route(space="нет такого")]))
|
||||
assert excinfo.value.code == VAC_ROUTE_ERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize("routes", [
|
||||
[_route(space="удалённое")],
|
||||
[_route(), _route(id="r2")],
|
||||
[_route(), _route(map_id="m2")],
|
||||
[_route(calibration=[1, 2, 3])],
|
||||
[_route(source="не-entity")],
|
||||
[_route(id="")],
|
||||
["строка вместо объекта"],
|
||||
])
|
||||
def test_invalid_routes_are_rejected(routes):
|
||||
with pytest.raises(MarkerControlError):
|
||||
validate_marker_vacuum_routes(_config(routes))
|
||||
|
||||
|
||||
def test_untouched_invalid_routes_do_not_block_an_unrelated_save():
|
||||
"""Смена другого поля не обязана чинить чужие/будущие данные."""
|
||||
broken = _config([_route(space="исчезнувшее")])
|
||||
previous = {"markers": [dict(broken["markers"][0])], "spaces": broken["spaces"]}
|
||||
validate_marker_vacuum_routes(broken, previous)
|
||||
# но правка самих маршрутов обязана оставить их валидными
|
||||
edited = _config([_route(space="исчезнувшее"), _route(id="r2", map_id="m2")])
|
||||
with pytest.raises(MarkerControlError):
|
||||
validate_marker_vacuum_routes(edited, previous)
|
||||
|
||||
|
||||
def test_import_validates_everything():
|
||||
broken = _config([_route(space="исчезнувшее")])
|
||||
with pytest.raises(MarkerControlError):
|
||||
validate_marker_vacuum_routes(broken, None, validate_all=True)
|
||||
|
||||
|
||||
def test_missing_spaces_list_skips_the_referential_check_only():
|
||||
config = {"markers": [{"id": "m1", "vacuum": {"map_routes": [_route(space="floor9")]}}]}
|
||||
validate_marker_vacuum_routes(config)
|
||||
config["markers"][0]["vacuum"]["map_routes"] = [_route(space="floor9", calibration=[1])]
|
||||
with pytest.raises(MarkerControlError):
|
||||
validate_marker_vacuum_routes(config)
|
||||
|
||||
|
||||
def test_legacy_marker_without_routes_is_untouched():
|
||||
config = {
|
||||
"spaces": [{"id": "floor1"}],
|
||||
"markers": [{"id": "m1", "space": "floor1",
|
||||
"vacuum": {"source": "camera.robot", "calibration": {"m1": IDENTITY}}}],
|
||||
}
|
||||
validate_marker_vacuum_routes(config, None, validate_all=True)
|
||||
Reference in New Issue
Block a user