mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: keep vacuum routes pending until a floor is chosen
Issue: #441 User-Visible: yes
This commit is contained in:
committed by
claude[bot]
parent
79e922203a
commit
fa41d2981c
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9",
|
||||
"fingerprint": "c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 292984,
|
||||
"initialViewGzipBytes": 292981,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
],
|
||||
"lazyGzipBytes": 220425,
|
||||
"lazyGzipBytes": 221159,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 164374,
|
||||
"lazyEditorGzipBytes": 165116,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14238,
|
||||
"lazyOnboardingGzipBytes": 14235,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js"
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 49093,
|
||||
"lazyLocaleGzipBytes": 49089,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"sha256": "96c1bdff6d868047b7493712c8fe6c9bbae2b1746bfceaef9a4099b5fcd9a63a",
|
||||
"path": "houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"sha256": "418abed95e5fe5029e8e1b63cdb63fa7c334d6a5ba7673786363d596975ca2fb",
|
||||
"rawBytes": 21418,
|
||||
"gzipBytes": 7280,
|
||||
"gzipBytes": 7281,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-8ycErMrc.js",
|
||||
"sha256": "7ba3842af5b58f96a7ffec9b01937aa5d7219577dbb25259a6f33fd06d6d002c",
|
||||
"path": "houseplan-assets/de-Bk5XCaFW.js",
|
||||
"sha256": "26becc9124786aa6a0e221214c6019ff1d0d6e6c24ee3d387f063ca6aa4a616d",
|
||||
"rawBytes": 82140,
|
||||
"gzipBytes": 24815,
|
||||
"gzipBytes": 24814,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Btgdn4cS.js",
|
||||
"sha256": "e6c4ef572f15ca3e8418ee1872b55e3d0c85f2d33c25bcd2660687f2ac62360c",
|
||||
"path": "houseplan-assets/editor-DV9aaxh3.js",
|
||||
"sha256": "a62e2706d2f58371e5056aab0a4a96051493ae1441fa7b419e95058b8c34202a",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1584,
|
||||
"gzipBytes": 1583,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-DESFTmoo.js",
|
||||
"sha256": "2806c8942afed7e7cbf87b344605685b886dbcfbef059c8b78a0867745ca66aa",
|
||||
"path": "houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"sha256": "e75de097700b0db364d2d3c0bca4a10f22343d17c9eaa6dbeba7a789e235af05",
|
||||
"rawBytes": 84267,
|
||||
"gzipBytes": 24278,
|
||||
"gzipBytes": 24275,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-KiAYj10F.js",
|
||||
"sha256": "939a6e9dcbb126cb53925d30c8a4193ddc301b419d9f14959b536fbbb78b861f",
|
||||
"path": "houseplan-assets/houseplan-card-DTTdyKSp.js",
|
||||
"sha256": "8f045ff75d013ccdab48546a218d9cbe15f2cdec038060cdf018c1915a68400c",
|
||||
"rawBytes": 1039585,
|
||||
"gzipBytes": 292186,
|
||||
"gzipBytes": 292185,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"sha256": "506cd2dc007e85e6b676e96bc0bbe37ac998f3289ec8ced749fd03bedf014db6",
|
||||
"rawBytes": 592159,
|
||||
"gzipBytes": 155510,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"sha256": "9ddf1cf2fcc3c446fea56579599cdd1104736e55f2404837be9064109dbc1f29",
|
||||
"rawBytes": 595044,
|
||||
"gzipBytes": 156252,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js",
|
||||
"sha256": "49b2e0228b4c4af5743344552eca63821fd148ca3e5fa195a38fdb868a192f81",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js",
|
||||
"sha256": "a9e31ea033c1df4c50f28dc6ccd013a1a12335176c2ce358b1b7144c9c70e14c",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6958,
|
||||
"gzipBytes": 6954,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "937b428bc4e8c0edab1ea83c5f026c895da89484810fe40731e28d0d33fca31d",
|
||||
"sha256": "77de82b5156123bd48fa0ab2d385ee50404135c634f0208c1c6e453690cc6aca",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 798,
|
||||
"gzipBytes": 796,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9";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-KiAYj10F.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__="c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a";import{k 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-DTTdyKSp.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
+185
-163
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9";try{await import("./houseplan-assets/houseplan-card-KiAYj10F.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__="c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a";try{await import("./houseplan-assets/houseplan-card-DTTdyKSp.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)}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// #441: adding a second vacuum map is a UI draft, never an invalid route with
|
||||
// `space: ''`. The same smoke covers both add entry points and rejected saves.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
const { page, browser } = await launch();
|
||||
|
||||
const out = await page.evaluate(async () => {
|
||||
const c = window.__card;
|
||||
const o = {};
|
||||
const sr = () => c.shadowRoot || c.renderRoot;
|
||||
const waitFor = async (read, timeout = 2000) => {
|
||||
const started = Date.now();
|
||||
while (Date.now() - started < timeout) {
|
||||
const value = read();
|
||||
if (value) return value;
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 650));
|
||||
c._saveConfigDebounced?.flush?.();
|
||||
await c._writeChain;
|
||||
|
||||
const originalCallWS = c.hass.callWS;
|
||||
c.hass = {
|
||||
...c.hass,
|
||||
entities: {
|
||||
...c.hass.entities,
|
||||
'camera.mower_map': {
|
||||
entity_id: 'camera.mower_map', device_id: 'd_mower', platform: 'demo', disabled_by: null,
|
||||
},
|
||||
'camera.mower_upstairs': {
|
||||
entity_id: 'camera.mower_upstairs', device_id: 'd_mower', platform: 'demo', disabled_by: null,
|
||||
},
|
||||
},
|
||||
states: {
|
||||
...c.hass.states,
|
||||
'camera.mower_map': {
|
||||
state: 'idle', attributes: {
|
||||
friendly_name: 'Main map', map_name: 'm1', vacuum_position: { x: 1, y: 2 },
|
||||
},
|
||||
},
|
||||
'camera.mower_upstairs': {
|
||||
state: 'idle', attributes: {
|
||||
friendly_name: 'Upstairs map', map_name: 'm2', vacuum_position: { x: 1, y: 2 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
c._serverCfg.markers = [{
|
||||
id: 'd_mower', binding: 'device:d_mower', space: 'garden', area: 'garden',
|
||||
vacuum: { source: 'camera.mower_map', map_routes: [] },
|
||||
}];
|
||||
c._regSignature = '';
|
||||
c._maybeRebuildDevices();
|
||||
c._setMode('devices');
|
||||
await c.updateComplete;
|
||||
c._openMarkerDialog(c._devices.find((device) => device.id === 'd_mower'));
|
||||
await c.updateComplete;
|
||||
|
||||
const routes = () => c._serverCfg.markers.find((marker) => marker.id === 'd_mower')
|
||||
?.vacuum?.map_routes || [];
|
||||
const addCurrent = () => sr().querySelector('.vacroute-add-current');
|
||||
const pending = () => sr().querySelector('.vacroute.pending');
|
||||
|
||||
// The first route is a draft too, but its only safe default is the dock floor.
|
||||
addCurrent()?.click();
|
||||
await c.updateComplete;
|
||||
o.firstRoutePreselectsDock = pending()?.querySelector('.vacroute-draft-space')?.value === 'garden';
|
||||
o.firstDraftDoesNotWrite = routes().length === 0;
|
||||
pending()?.querySelector('button.ghostbtn')?.click();
|
||||
await c.updateComplete;
|
||||
|
||||
// Start with one accepted route, then exercise the separate-source entry.
|
||||
c._serverCfg.markers[0].vacuum.map_routes = [{
|
||||
id: 'r1', source: 'camera.mower_map', map_id: 'm1', space: 'garden', calibration: null,
|
||||
}];
|
||||
c._regSignature = '';
|
||||
c._maybeRebuildDevices();
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
const sourceDetails = sr().querySelector('details.vacroute-sources');
|
||||
if (sourceDetails) sourceDetails.open = true;
|
||||
await c.updateComplete;
|
||||
[...(sourceDetails?.querySelectorAll('button.vacsource') || [])]
|
||||
.find((button) => button.textContent.includes('camera.mower_upstairs'))?.click();
|
||||
await c.updateComplete;
|
||||
o.separateSourceStartsBlank = pending()?.querySelector('.vacroute-draft-space')?.value === '';
|
||||
o.blankDraftCannotSave = pending()?.querySelector('.vacroute-draft-save')?.disabled === true;
|
||||
o.separateSourceDoesNotWrite = routes().length === 1;
|
||||
pending()?.querySelector('button.ghostbtn')?.click();
|
||||
await c.updateComplete;
|
||||
o.cancelLeavesAcceptedRoutes = !pending() && routes().length === 1;
|
||||
|
||||
// The same source reports another map. Confirm once against a rejecting
|
||||
// backend, then retry successfully without reopening the dialog.
|
||||
c.hass = { ...c.hass, states: { ...c.hass.states,
|
||||
'camera.mower_map': {
|
||||
state: 'idle', attributes: {
|
||||
friendly_name: 'Main map', map_name: 'm2', vacuum_position: { x: 1, y: 2 },
|
||||
},
|
||||
},
|
||||
} };
|
||||
c._regSignature = '';
|
||||
c._maybeRebuildDevices();
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
addCurrent()?.click();
|
||||
await c.updateComplete;
|
||||
o.secondCurrentStartsBlank = pending()?.querySelector('.vacroute-draft-space')?.value === '';
|
||||
const select = pending()?.querySelector('.vacroute-draft-space');
|
||||
if (select) {
|
||||
select.value = 'f1';
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
await c.updateComplete;
|
||||
o.validFloorEnablesSave = pending()?.querySelector('.vacroute-draft-save')?.disabled === false;
|
||||
|
||||
const writes = [];
|
||||
let rejectNext = true;
|
||||
c.hass = { ...c.hass, callWS: async (message) => {
|
||||
if (message.type === 'houseplan/config/set') {
|
||||
writes.push(message);
|
||||
if (rejectNext) {
|
||||
rejectNext = false;
|
||||
throw new Error('synthetic route rejection');
|
||||
}
|
||||
}
|
||||
return originalCallWS(message);
|
||||
} };
|
||||
pending()?.querySelector('.vacroute-draft-save')?.click();
|
||||
await waitFor(() => writes.length === 1 && pending()?.querySelector('.vacroute-draft-save')?.disabled === false);
|
||||
o.rejectionRestoresAcceptedMarker = routes().length === 1 && routes()[0].id === 'r1';
|
||||
o.rejectionKeepsEditorUsable = !!pending()
|
||||
&& pending().querySelector('.vacroute-draft-space')?.value === 'f1';
|
||||
o.rejectedPayloadWasValid = writes[0]?.config?.markers
|
||||
?.find((marker) => marker.id === 'd_mower')?.vacuum?.map_routes
|
||||
?.every((route) => !!route.space) === true;
|
||||
|
||||
pending()?.querySelector('.vacroute-draft-save')?.click();
|
||||
await waitFor(() => routes().length === 2 && !pending());
|
||||
const added = routes().find((route) => route.map_id === 'm2');
|
||||
o.retryAddsExactlyOneRoute = routes().length === 2;
|
||||
o.retryKeepsExactIdentity = added?.source === 'camera.mower_map'
|
||||
&& added?.space === 'f1' && added?.calibration === null;
|
||||
o.noEmptySpaceEverSubmitted = writes.every((write) => write.config.markers
|
||||
.find((marker) => marker.id === 'd_mower').vacuum.map_routes
|
||||
.every((route) => !!route.space));
|
||||
|
||||
c.hass = { ...c.hass, callWS: originalCallWS };
|
||||
return o;
|
||||
});
|
||||
|
||||
checkAll(out);
|
||||
await finish(browser, out);
|
||||
Vendored
+56
-56
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9",
|
||||
"fingerprint": "c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 292984,
|
||||
"initialViewGzipBytes": 292981,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
],
|
||||
"lazyGzipBytes": 220425,
|
||||
"lazyGzipBytes": 221159,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 164374,
|
||||
"lazyEditorGzipBytes": 165116,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14238,
|
||||
"lazyOnboardingGzipBytes": 14235,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js"
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 49093,
|
||||
"lazyLocaleGzipBytes": 49089,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"sha256": "96c1bdff6d868047b7493712c8fe6c9bbae2b1746bfceaef9a4099b5fcd9a63a",
|
||||
"path": "houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"sha256": "418abed95e5fe5029e8e1b63cdb63fa7c334d6a5ba7673786363d596975ca2fb",
|
||||
"rawBytes": 21418,
|
||||
"gzipBytes": 7280,
|
||||
"gzipBytes": 7281,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-8ycErMrc.js",
|
||||
"sha256": "7ba3842af5b58f96a7ffec9b01937aa5d7219577dbb25259a6f33fd06d6d002c",
|
||||
"path": "houseplan-assets/de-Bk5XCaFW.js",
|
||||
"sha256": "26becc9124786aa6a0e221214c6019ff1d0d6e6c24ee3d387f063ca6aa4a616d",
|
||||
"rawBytes": 82140,
|
||||
"gzipBytes": 24815,
|
||||
"gzipBytes": 24814,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Btgdn4cS.js",
|
||||
"sha256": "e6c4ef572f15ca3e8418ee1872b55e3d0c85f2d33c25bcd2660687f2ac62360c",
|
||||
"path": "houseplan-assets/editor-DV9aaxh3.js",
|
||||
"sha256": "a62e2706d2f58371e5056aab0a4a96051493ae1441fa7b419e95058b8c34202a",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1584,
|
||||
"gzipBytes": 1583,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-DESFTmoo.js",
|
||||
"sha256": "2806c8942afed7e7cbf87b344605685b886dbcfbef059c8b78a0867745ca66aa",
|
||||
"path": "houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"sha256": "e75de097700b0db364d2d3c0bca4a10f22343d17c9eaa6dbeba7a789e235af05",
|
||||
"rawBytes": 84267,
|
||||
"gzipBytes": 24278,
|
||||
"gzipBytes": 24275,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-KiAYj10F.js",
|
||||
"sha256": "939a6e9dcbb126cb53925d30c8a4193ddc301b419d9f14959b536fbbb78b861f",
|
||||
"path": "houseplan-assets/houseplan-card-DTTdyKSp.js",
|
||||
"sha256": "8f045ff75d013ccdab48546a218d9cbe15f2cdec038060cdf018c1915a68400c",
|
||||
"rawBytes": 1039585,
|
||||
"gzipBytes": 292186,
|
||||
"gzipBytes": 292185,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-8ycErMrc.js",
|
||||
"houseplan-assets/editor-Btgdn4cS.js",
|
||||
"houseplan-assets/fr-DESFTmoo.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js"
|
||||
"houseplan-assets/de-Bk5XCaFW.js",
|
||||
"houseplan-assets/editor-DV9aaxh3.js",
|
||||
"houseplan-assets/fr-Bc8GcNwg.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BMOXcbvu.js",
|
||||
"sha256": "506cd2dc007e85e6b676e96bc0bbe37ac998f3289ec8ced749fd03bedf014db6",
|
||||
"rawBytes": 592159,
|
||||
"gzipBytes": 155510,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Cv3Tj4ec.js",
|
||||
"sha256": "9ddf1cf2fcc3c446fea56579599cdd1104736e55f2404837be9064109dbc1f29",
|
||||
"rawBytes": 595044,
|
||||
"gzipBytes": 156252,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D0HESg_j.js",
|
||||
"sha256": "49b2e0228b4c4af5743344552eca63821fd148ca3e5fa195a38fdb868a192f81",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B0SCo751.js",
|
||||
"sha256": "a9e31ea033c1df4c50f28dc6ccd013a1a12335176c2ce358b1b7144c9c70e14c",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6958,
|
||||
"gzipBytes": 6954,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BSlJmdKA.js",
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/backdrop-pick-38iG-EgB.js",
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "937b428bc4e8c0edab1ea83c5f026c895da89484810fe40731e28d0d33fca31d",
|
||||
"sha256": "77de82b5156123bd48fa0ab2d385ee50404135c634f0208c1c6e453690cc6aca",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 798,
|
||||
"gzipBytes": 796,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-KiAYj10F.js"
|
||||
"houseplan-assets/houseplan-card-DTTdyKSp.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9";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-KiAYj10F.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__="c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a";import{k 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-DTTdyKSp.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
+185
-163
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="657e45b11db7e7ff870131c04228933d27e226165848926d21fd2d5f25651aa9";try{await import("./houseplan-assets/houseplan-card-KiAYj10F.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__="c53b73316a258130996655c414c72f311c57727d88a947b8ef0feaf9ab6d127a";try{await import("./houseplan-assets/houseplan-card-DTTdyKSp.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,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- A robot vacuum's second and later maps can now be added: the editor asks for
|
||||
the target floor before saving the route, and a Home Assistant rejection no
|
||||
longer leaves a phantom local route
|
||||
([#441](https://github.com/Matysh/houseplan-card/issues/441)).
|
||||
|
||||
## v1.71.0-beta.3 — 2026-09-03
|
||||
|
||||
- When a robot vacuum is cleaning but its current map has no floor assigned, is
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Вторую и последующие карты робота теперь можно добавить: сначала
|
||||
редактор просит выбрать этаж и только потом сохраняет маршрут,
|
||||
а отказ Home Assistant не оставляет ложную локальную запись
|
||||
([#441](https://github.com/Matysh/houseplan-card/issues/441)).
|
||||
|
||||
## v1.71.0-beta.3 — 2026-09-03
|
||||
|
||||
- Если робот убирает, а его текущая карта не сопоставлена этажу, не
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
|
||||
/** @type {SmokeLink[]} */
|
||||
export const SMOKE_LINKS = [
|
||||
{
|
||||
symbols: [
|
||||
'beginVacuumRouteDraft', 'chooseVacuumRouteSpace', 'commitVacuumRouteDraft',
|
||||
'VacuumRouteDraft', 'VacuumMapsHost', 'VacuumMapsCardHost',
|
||||
],
|
||||
smokes: ['smoke_vacuum_route_draft.mjs'],
|
||||
because: 'the #441 production-bundle scenario drives both add-map controls, first-route '
|
||||
+ 'defaulting, required floor selection, cancel, a rejected config write with rollback and '
|
||||
+ 'a successful retry; those UI and transport effects are outside the pure draft helpers',
|
||||
},
|
||||
{
|
||||
symbols: ['showRoomTooltipOf'],
|
||||
smokes: ['smoke_room_tooltip_toggle.mjs'],
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
*/
|
||||
import { TemplateResult, html, nothing } from 'lit';
|
||||
import type { HpConfirmRequest } from '../danger-confirm';
|
||||
import type { DevItem } from '../types';
|
||||
import type { DevItem, Marker, ServerConfig } from '../types';
|
||||
import type { VacSourceCandidate, VacSourceResolution } from '../vacuum';
|
||||
import { langOf, type I18nKey } from '../i18n';
|
||||
import { supportT, type SupportI18nKey } from '../i18n/support';
|
||||
import { VacuumMapRoute, effectiveRoutes, observedMapIds, resolveRoute } from '../vacuum-routes';
|
||||
import {
|
||||
addRoute, changeRouteSpace, convertLegacyRoutes, newRouteId, removeRoute,
|
||||
beginVacuumRouteDraft, changeRouteSpace, chooseVacuumRouteSpace,
|
||||
commitVacuumRouteDraft, convertLegacyRoutes, newRouteId, removeRoute,
|
||||
type VacuumRouteDraft,
|
||||
} from '../vacuum-route-edit';
|
||||
import { optimisticAttempt, rollbackOptimistic } from '../serialized-write-queue';
|
||||
import { contentFingerprint } from '../visual-continuity';
|
||||
|
||||
interface SpaceRow { id?: unknown; name?: unknown }
|
||||
|
||||
@@ -23,20 +27,30 @@ interface SpaceRow { id?: unknown; name?: unknown }
|
||||
export interface VacuumMapsCardHost {
|
||||
hass?: { states?: Record<string, unknown> } | null;
|
||||
_config?: { language?: string | null } | null;
|
||||
_serverCfg?: { spaces?: SpaceRow[] } | null;
|
||||
_serverCfg: (ServerConfig & { spaces: SpaceRow[] }) | null;
|
||||
_cfgContentFingerprint: string;
|
||||
_cfgRev: number;
|
||||
_saveConfigDebounced: { pending: () => boolean; cancel: () => void };
|
||||
_regSignature: string;
|
||||
_t: (key: I18nKey, vars?: Record<string, string | number>) => string;
|
||||
_errText: (error: unknown) => string;
|
||||
_maybeRebuildDevices: () => void;
|
||||
_showToast: (message: string) => void;
|
||||
_vacSource: (dev: DevItem) => string | null;
|
||||
_vacObservedMapId: (dev: DevItem, source: string) => string | undefined;
|
||||
_confirmDanger: (request: HpConfirmRequest) => Promise<boolean>;
|
||||
requestUpdate: () => void;
|
||||
}
|
||||
|
||||
export interface VacuumMapsHost {
|
||||
host: VacuumMapsCardHost;
|
||||
_saveConfig: () => void;
|
||||
_saveConfigNow: () => Promise<void>;
|
||||
_vacAutoCalibrate: (dev: DevItem) => void;
|
||||
_vacStartFit: (dev: DevItem, routeId?: string) => void;
|
||||
}
|
||||
|
||||
const pendingRoute = new WeakMap<VacuumMapsHost, VacuumRouteDraft & { saving?: boolean }>();
|
||||
|
||||
const spaceName = (host: VacuumMapsCardHost, spaceId: string): string => {
|
||||
const space = (host._serverCfg?.spaces || []).find((item) => item?.id === spaceId);
|
||||
return String(space?.name || space?.id || spaceId);
|
||||
@@ -45,12 +59,11 @@ const spaceName = (host: VacuumMapsCardHost, spaceId: string): string => {
|
||||
/**
|
||||
* Render the block, and own every routing edit it offers.
|
||||
*
|
||||
* `setVac` is the same patch writer the rest of the vacuum section uses, so a
|
||||
* routing change goes through one save path with everything else.
|
||||
* Additions stay in module-owned UI state until the floor is valid; accepted
|
||||
* edits use the card's serialized config writer and roll back on rejection.
|
||||
*/
|
||||
export function renderVacuumMapsSection(
|
||||
runtime: VacuumMapsHost, dev: DevItem, setVac: (patch: Record<string, unknown>) => void,
|
||||
sources: VacSourceResolution,
|
||||
runtime: VacuumMapsHost, dev: DevItem, sources: VacSourceResolution,
|
||||
): TemplateResult | typeof nothing {
|
||||
const host = runtime.host;
|
||||
const lang = langOf(host.hass, host._config?.language);
|
||||
@@ -72,30 +85,63 @@ export function renderVacuumMapsSection(
|
||||
|| resolution.kind === 'missing_space') ? resolution.route.id : '';
|
||||
const currentMapId = rootSource ? observed[rootSource] : undefined;
|
||||
|
||||
/** Persist route changes atomically, restoring the last accepted config on rejection. */
|
||||
const persistRoutes = async (nextRoutes: VacuumMapRoute[]): Promise<boolean> => {
|
||||
const previous = host._serverCfg;
|
||||
if (!previous) return false;
|
||||
const nextConfig = JSON.parse(JSON.stringify(previous)) as ServerConfig;
|
||||
nextConfig.markers = nextConfig.markers || [];
|
||||
let marker = nextConfig.markers.find((item) => item.id === dev.id);
|
||||
if (!marker) {
|
||||
if ((dev.bindingKind !== 'device' && dev.bindingKind !== 'entity') || !dev.bindingRef) return false;
|
||||
marker = {
|
||||
id: dev.id, binding: `${dev.bindingKind}:${dev.bindingRef}`,
|
||||
space: dev.space || null, area: dev.area || null, hidden: dev.hidden ? true : false,
|
||||
} as Marker;
|
||||
nextConfig.markers.push(marker);
|
||||
}
|
||||
marker.vacuum = { ...(marker.vacuum || {}), map_routes: nextRoutes };
|
||||
delete marker.vacuum.calibration;
|
||||
const attempt = optimisticAttempt(previous, nextConfig, host._cfgContentFingerprint,
|
||||
host._cfgRev, contentFingerprint);
|
||||
host._serverCfg = nextConfig;
|
||||
host._regSignature = '';
|
||||
host._maybeRebuildDevices();
|
||||
host.requestUpdate();
|
||||
if (host._saveConfigDebounced.pending()) host._saveConfigDebounced.cancel();
|
||||
try {
|
||||
await runtime._saveConfigNow();
|
||||
return true;
|
||||
} catch (error) {
|
||||
rollbackOptimistic(host, attempt, contentFingerprint);
|
||||
host._regSignature = '';
|
||||
host._maybeRebuildDevices();
|
||||
host.requestUpdate();
|
||||
host._showToast(host._t('toast.cfg_save_failed', { err: host._errText(error) }));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** Every routing edit converts legacy data first — all of it, or none. */
|
||||
const writeRoutes = (next: (current: VacuumMapRoute[]) => VacuumMapRoute[]): boolean => {
|
||||
const writeRoutes = async (
|
||||
next: (current: VacuumMapRoute[]) => VacuumMapRoute[] | null,
|
||||
): Promise<boolean> => {
|
||||
let base: VacuumMapRoute[] | null = explicit ? (vacuum.map_routes ?? null) : null;
|
||||
if (!base) {
|
||||
base = convertLegacyRoutes(vacuum, dev.space, rootSource,
|
||||
(taken) => newRouteId(taken)) ?? [];
|
||||
if (!rootSource && Object.keys(vacuum.calibration || {}).length) return false;
|
||||
}
|
||||
setVac({ map_routes: next(base), calibration: undefined });
|
||||
return true;
|
||||
const candidate = next(base);
|
||||
return candidate ? persistRoutes(candidate) : false;
|
||||
};
|
||||
const takenIds = () => new Set(routes.map((route) => route.id));
|
||||
|
||||
const addCurrent = () => {
|
||||
if (!rootSource || currentMapId === undefined) return;
|
||||
writeRoutes((current) => addRoute(
|
||||
current,
|
||||
{
|
||||
source: rootSource,
|
||||
map_id: currentMapId,
|
||||
space: current.length ? '' : dev.space,
|
||||
},
|
||||
newRouteId(takenIds()),
|
||||
));
|
||||
pendingRoute.set(runtime,
|
||||
beginVacuumRouteDraft(dev.id, routes, dev.space, rootSource, currentMapId));
|
||||
host.requestUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -109,11 +155,9 @@ export function renderVacuumMapsSection(
|
||||
const addSource = (candidate: VacSourceCandidate) => {
|
||||
const mapId = host._vacObservedMapId(dev, candidate.entityId);
|
||||
if (mapId === undefined) return;
|
||||
writeRoutes((current) => addRoute(
|
||||
current,
|
||||
{ source: candidate.entityId, map_id: mapId, space: current.length ? '' : dev.space },
|
||||
newRouteId(takenIds()),
|
||||
));
|
||||
pendingRoute.set(runtime,
|
||||
beginVacuumRouteDraft(dev.id, routes, dev.space, candidate.entityId, mapId));
|
||||
host.requestUpdate();
|
||||
};
|
||||
|
||||
const spare = sources.candidates.filter((candidate) => candidate.entityId !== rootSource
|
||||
@@ -131,7 +175,7 @@ export function renderVacuumMapsSection(
|
||||
cancelLabel: host._t('btn.cancel'),
|
||||
});
|
||||
if (!accepted) return;
|
||||
writeRoutes((current) => changeRouteSpace(current, route.id, space, newRouteId(takenIds())));
|
||||
await writeRoutes((current) => changeRouteSpace(current, route.id, space, newRouteId(takenIds())));
|
||||
};
|
||||
|
||||
const drop = async (route: VacuumMapRoute) => {
|
||||
@@ -145,7 +189,7 @@ export function renderVacuumMapsSection(
|
||||
cancelLabel: host._t('btn.cancel'),
|
||||
});
|
||||
if (!accepted) return;
|
||||
writeRoutes((current) => removeRoute(current, route.id));
|
||||
await writeRoutes((current) => removeRoute(current, route.id));
|
||||
};
|
||||
|
||||
// Space order, then map id, then source: a stable reading order that never
|
||||
@@ -166,6 +210,50 @@ export function renderVacuumMapsSection(
|
||||
const canAddCurrent = !!rootSource && currentMapId !== undefined
|
||||
&& !routes.some((route) => route.source === rootSource && route.map_id === currentMapId);
|
||||
|
||||
let draft = pendingRoute.get(runtime);
|
||||
const draftSource = draft?.source || '';
|
||||
const draftMapId = draft?.mapId || '';
|
||||
const draftIdentityExists = draft
|
||||
? routes.some((route) => route.source === draftSource && route.map_id === draftMapId)
|
||||
: false;
|
||||
if (draft && (draft.markerId !== dev.id
|
||||
|| host._vacObservedMapId(dev, draft.source) !== draft.mapId
|
||||
|| (draftIdentityExists && !draft.saving))) {
|
||||
pendingRoute.delete(runtime);
|
||||
draft = undefined;
|
||||
}
|
||||
if (draft?.space && !spaceIds.has(draft.space)) {
|
||||
draft = chooseVacuumRouteSpace(draft, '', spaceIds);
|
||||
pendingRoute.set(runtime, draft);
|
||||
}
|
||||
const selectDraftSpace = (space: string) => {
|
||||
const current = pendingRoute.get(runtime);
|
||||
if (!current || current.saving) return;
|
||||
pendingRoute.set(runtime, chooseVacuumRouteSpace(current, space, spaceIds));
|
||||
host.requestUpdate();
|
||||
};
|
||||
const cancelDraft = () => {
|
||||
if (pendingRoute.get(runtime)?.saving) return;
|
||||
pendingRoute.delete(runtime);
|
||||
host.requestUpdate();
|
||||
};
|
||||
const confirmDraft = async () => {
|
||||
const current = pendingRoute.get(runtime);
|
||||
if (!current || current.saving || !spaceIds.has(current.space)) return;
|
||||
pendingRoute.set(runtime, { ...current, saving: true });
|
||||
host.requestUpdate();
|
||||
const saved = await writeRoutes((base) => commitVacuumRouteDraft(
|
||||
base, current, spaceIds, newRouteId(new Set(base.map((route) => route.id))),
|
||||
));
|
||||
const live = pendingRoute.get(runtime);
|
||||
if (live?.markerId === current.markerId && live.source === current.source
|
||||
&& live.mapId === current.mapId) {
|
||||
if (saved) pendingRoute.delete(runtime);
|
||||
else pendingRoute.set(runtime, current);
|
||||
}
|
||||
host.requestUpdate();
|
||||
};
|
||||
|
||||
return html`
|
||||
<label>${t('vac.routes_section')}</label>
|
||||
<div class="bindbox vacroutes">
|
||||
@@ -174,7 +262,28 @@ export function renderVacuumMapsSection(
|
||||
? t('vac.routes_no_map') : (currentMapId || t('vac.route_map_default'))}</b></div>
|
||||
<div><span>${t('vac.routes_status')}</span><b>${t(`vac.routes_state_${resolution.kind}`)}</b></div>
|
||||
</div>
|
||||
${rows.length ? html`<ul class="vacroute-list">
|
||||
${rows.length || draft ? html`<ul class="vacroute-list">
|
||||
${draft ? html`
|
||||
<li class="vacroute pending">
|
||||
<div class="vacroute-head"><b>${draft.mapId || t('vac.route_map_default')}</b></div>
|
||||
<small>${draft.source}</small>
|
||||
<label class="srcrow">
|
||||
<span>${t('vac.route_space')}</span>
|
||||
<select class="areasel vacroute-draft-space" ?disabled=${!!draft.saving}
|
||||
@change=${(event: Event) => selectDraftSpace((event.target as HTMLSelectElement).value)}>
|
||||
<option value="" ?selected=${!draft.space}>${t('vac.route_space_none')}</option>
|
||||
${spaces.map((space) => html`
|
||||
<option value=${space.id} ?selected=${space.id === draft!.space}>${space.name}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
<div class="vacbtns">
|
||||
<button type="button" class="btn ghostbtn" ?disabled=${!!draft.saving}
|
||||
@click=${cancelDraft}>${host._t('btn.cancel')}</button>
|
||||
<button type="button" class="btn vacroute-draft-save"
|
||||
?disabled=${!!draft.saving || !spaceIds.has(draft.space)}
|
||||
@click=${() => void confirmDraft()}>${host._t('btn.save')}</button>
|
||||
</div>
|
||||
</li>` : nothing}
|
||||
${rows.map((route) => html`
|
||||
<li class="vacroute ${route.id === activeId ? 'on' : ''}">
|
||||
<div class="vacroute-head">
|
||||
@@ -206,7 +315,7 @@ export function renderVacuumMapsSection(
|
||||
</li>`)}
|
||||
</ul>` : html`<div class="rhint">${t('vac.routes_empty')}</div>`}
|
||||
<div class="vacbtns">
|
||||
<button type="button" class="btn" ?disabled=${!canAddCurrent}
|
||||
<button type="button" class="btn vacroute-add-current" ?disabled=${!canAddCurrent || !!draft}
|
||||
@click=${addCurrent}>${t('vac.route_add_current')}</button>
|
||||
</div>
|
||||
${spare.length ? html`<details class="vacroute-sources">
|
||||
@@ -215,7 +324,8 @@ export function renderVacuumMapsSection(
|
||||
<div class="vacsource-list">
|
||||
${spare.map((candidate) => {
|
||||
const mapId = host._vacObservedMapId(dev, candidate.entityId);
|
||||
return html`<button type="button" class="vacsource" ?disabled=${mapId === undefined}
|
||||
return html`<button type="button" class="vacsource"
|
||||
?disabled=${mapId === undefined || !!draft}
|
||||
@click=${() => addSource(candidate)}>
|
||||
<span><b>${candidate.name}</b><small>${candidate.entityId}</small></span>
|
||||
<span class="vacsource-meta">${mapId === undefined
|
||||
|
||||
@@ -10970,7 +10970,7 @@ public _renderVacSection(dlg: any): TemplateResult | typeof nothing {
|
||||
${(['never', 'cleaning', 'always'] as const).map((mv) => html`
|
||||
<option value=${mv} ?selected=${vacTrailMode(v) === mv}>${this.host._t(('vac.trail_' + mv) as any)}</option>`)}
|
||||
</select>
|
||||
${renderVacuumMapsSection(this, dev, setVac, resolution)}
|
||||
${renderVacuumMapsSection(this, dev, resolution)}
|
||||
` : nothing}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,47 @@ import {
|
||||
Affine, VacuumMapRoute, VacuumRouteMarkerCfg, effectiveRoutes, normalizeRouteMatrix,
|
||||
} from './vacuum-routes';
|
||||
|
||||
/** A route which exists only in the editor until a real space is confirmed. */
|
||||
export interface VacuumRouteDraft {
|
||||
markerId: string;
|
||||
source: string;
|
||||
mapId: string;
|
||||
space: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin the add-route transaction without creating an invalid persisted row.
|
||||
* Only the very first route may inherit the dock floor; every later map must
|
||||
* be assigned explicitly by the user (#162 §9.2, #441).
|
||||
*/
|
||||
export function beginVacuumRouteDraft(
|
||||
markerId: string, routes: readonly VacuumMapRoute[], dockSpace: string,
|
||||
source: string, mapId: string,
|
||||
): VacuumRouteDraft {
|
||||
return { markerId, source, mapId, space: routes.length ? '' : dockSpace };
|
||||
}
|
||||
|
||||
/** Unknown/deleted spaces are represented as no selection, never persisted. */
|
||||
export function chooseVacuumRouteSpace(
|
||||
draft: VacuumRouteDraft, space: string, spaceIds: ReadonlySet<string>,
|
||||
): VacuumRouteDraft {
|
||||
return { ...draft, space: spaceIds.has(space) ? space : '' };
|
||||
}
|
||||
|
||||
/** Materialise exactly one valid route, or refuse to produce a candidate. */
|
||||
export function commitVacuumRouteDraft(
|
||||
routes: VacuumMapRoute[], draft: VacuumRouteDraft,
|
||||
spaceIds: ReadonlySet<string>, id: string,
|
||||
): VacuumMapRoute[] | null {
|
||||
if (!draft.source || !spaceIds.has(draft.space)
|
||||
|| routes.some((route) => route.source === draft.source && route.map_id === draft.mapId)) {
|
||||
return null;
|
||||
}
|
||||
return addRoute(routes, {
|
||||
source: draft.source, map_id: draft.mapId, space: draft.space,
|
||||
}, id);
|
||||
}
|
||||
|
||||
/** A marker-local id that no existing route uses. */
|
||||
export function newRouteId(existing: Iterable<string>, seed: () => number = Math.random): string {
|
||||
const taken = new Set(existing);
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
} from '../test-build/vacuum-routes.js';
|
||||
import {
|
||||
newRouteId, addRoute, removeRoute, changeRouteSpace, saveRouteCalibration, convertLegacyRoutes,
|
||||
writeVacuumMatrix, planVacuumFit, calibrationTarget,
|
||||
writeVacuumMatrix, planVacuumFit, calibrationTarget, beginVacuumRouteDraft,
|
||||
chooseVacuumRouteSpace, commitVacuumRouteDraft,
|
||||
} from '../test-build/vacuum-route-edit.js';
|
||||
|
||||
const fixture = (name) => JSON.parse(readFileSync(
|
||||
@@ -275,6 +276,30 @@ test('добавление, удаление и запись матрицы ма
|
||||
assert.deepEqual(removeRoute(calibrated, 'нет такого'), calibrated);
|
||||
});
|
||||
|
||||
test('новый маршрут живёт в черновике до выбора этажа (#441 AC1)', () => {
|
||||
const existing = [route()];
|
||||
const first = beginVacuumRouteDraft('mk', [], 'floor1', 'camera.robot', 'm1');
|
||||
const second = beginVacuumRouteDraft('mk', existing, 'floor1', 'camera.robot', 'm2');
|
||||
assert.equal(first.space, 'floor1', 'первый маршрут наследует этаж дока');
|
||||
assert.equal(second.space, '', 'второй маршрут не угадывает этаж');
|
||||
assert.equal(commitVacuumRouteDraft(existing, second, spaces('floor1', 'floor2'), 'r2'), null);
|
||||
assert.equal(existing.length, 1, 'черновик не меняет маршруты');
|
||||
});
|
||||
|
||||
test('черновик материализует ровно один валидный маршрут (#441 AC2)', () => {
|
||||
const existing = [route()];
|
||||
const draft = beginVacuumRouteDraft('mk', existing, 'floor1', 'camera.robot', 'm2');
|
||||
const unknown = chooseVacuumRouteSpace(draft, 'gone', spaces('floor1', 'floor2'));
|
||||
assert.equal(unknown.space, '');
|
||||
const chosen = chooseVacuumRouteSpace(draft, 'floor2', spaces('floor1', 'floor2'));
|
||||
const committed = commitVacuumRouteDraft(existing, chosen, spaces('floor1', 'floor2'), 'r2');
|
||||
assert.deepEqual(committed, [existing[0], {
|
||||
id: 'r2', source: 'camera.robot', map_id: 'm2', space: 'floor2', calibration: null,
|
||||
}]);
|
||||
assert.equal(commitVacuumRouteDraft(committed, chosen, spaces('floor1', 'floor2'), 'r3'), null,
|
||||
'одну карту нельзя добавить дважды');
|
||||
});
|
||||
|
||||
test('конверсия легаси переносит ВСЕ матрицы или ничего (AC13)', () => {
|
||||
const marker = { source: 'camera.robot', calibration: { m1: M1, m2: M2, bad: [1, 2] } };
|
||||
let n = 0;
|
||||
|
||||
Reference in New Issue
Block a user