From 0fd0ba408dd0f2a5c02fe9ce3207911b60592756 Mon Sep 17 00:00:00 2001 From: Matysh Date: Mon, 27 Jul 2026 10:44:58 +0300 Subject: [PATCH 1/4] =?UTF-8?q?fix=20v1.43.0:=20external=20audit=20P0=20?= =?UTF-8?q?=E2=80=94=20data=20loss,=20split=20geometry,=20auth,=20dialog?= =?UTF-8?q?=20zombies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L2 (silent data loss): debounce gains flush()/pending(); _reloadConfigOnly flushes a pending write and defers while one is in flight; conflict path forces; failed reload now toasts instead of an empty catch; teardown flushes. G1 (split corruption): same-edge cuts carve the niche properly instead of walking the outline twice; partition invariant (parts sum to the original) rejects anything else; +1 unit test covering 5 niche shapes and both legacy cut shapes. B1 (unauthenticated content): plans and marker files move to HouseplanContentView (/api/houseplan/content/..., requires_auth); only the card bundle stays static; contentUrl() rewrites legacy URLs on read (no storage migration); repairs.py accepts both prefixes; +1 unit test. L3 (dialog zombies): all four save catch-blocks guard against a closed dialog; the card no longer blanks when a save fails after Esc. smokes: smoke_save_race, smoke_dialog_zombie; docs (TESTING/CHANGELOG/ ARCHITECTURE incl. the optimistic-UI note) same-commit --- custom_components/houseplan/__init__.py | 13 ++- custom_components/houseplan/const.py | 4 +- .../houseplan/frontend/houseplan-card.js | 40 +++---- custom_components/houseplan/http_api.py | 57 +++++++++- custom_components/houseplan/manifest.json | 2 +- custom_components/houseplan/repairs.py | 12 +- custom_components/houseplan/websocket_api.py | 4 +- demo/smoke_dialog_zombie.mjs | 55 +++++++++ demo/smoke_save_race.mjs | 36 ++++++ demo/srv/assets/houseplan-card.js | 40 +++---- dist/houseplan-card.js | 40 +++---- docs/ARCHITECTURE.md | 17 +++ docs/CHANGELOG.md | 27 +++++ docs/TESTING.md | 13 +++ package.json | 2 +- src/houseplan-card.ts | 105 +++++++++++++++--- src/i18n/en.json | 3 +- src/i18n/ru.json | 3 +- src/logic.ts | 50 ++++++++- src/space-geometry.ts | 4 +- test/logic.test.mjs | 35 ++++++ 21 files changed, 464 insertions(+), 98 deletions(-) create mode 100644 demo/smoke_dialog_zombie.mjs create mode 100644 demo/smoke_save_race.mjs diff --git a/custom_components/houseplan/__init__.py b/custom_components/houseplan/__init__.py index 3a09e7c..1a1ebc2 100755 --- a/custom_components/houseplan/__init__.py +++ b/custom_components/houseplan/__init__.py @@ -28,9 +28,10 @@ async def async_setup(hass: HomeAssistant, config) -> bool: """Register global handlers (survive config-entry reloads): WS commands, HTTP view.""" hass.data.setdefault(DOMAIN, {}) hp_ws.async_register(hass) - from .http_api import HouseplanUploadView + from .http_api import HouseplanContentView, HouseplanUploadView hass.http.register_view(HouseplanUploadView()) + hass.http.register_view(HouseplanContentView()) return True @@ -61,14 +62,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) -> if card_path.exists(): static_paths.append(StaticPathConfig(FRONTEND_URL, str(card_path), cache_headers=False)) - static_paths.append(StaticPathConfig(PLANS_URL, str(plans_path), cache_headers=True)) - static_paths.append(StaticPathConfig(FILES_URL, str(files_path), cache_headers=True)) - await hass.http.async_register_static_paths(static_paths) + # NOTE (audit B1): plans and marker files are NO LONGER static. + # They are served by HouseplanContentView, which requires auth. + # Only the card bundle stays public — Lovelace resources must be. + if static_paths: + await hass.http.async_register_static_paths(static_paths) except ImportError: # very old HA versions if card_path.exists(): hass.http.register_static_path(FRONTEND_URL, str(card_path), cache_headers=False) - hass.http.register_static_path(PLANS_URL, str(plans_path), cache_headers=True) - hass.http.register_static_path(FILES_URL, str(files_path), cache_headers=True) if not card_path.exists(): _LOGGER.warning("houseplan-card.js not found next to the integration: %s", card_path) diff --git a/custom_components/houseplan/const.py b/custom_components/houseplan/const.py index ac826af..f6059b9 100755 --- a/custom_components/houseplan/const.py +++ b/custom_components/houseplan/const.py @@ -9,9 +9,11 @@ FRONTEND_URL = "/houseplan_files/houseplan-card.js" PLANS_URL = "/houseplan_files/plans" PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory FILES_URL = "/houseplan_files/files" +# authenticated read path (audit B1): /api/houseplan/content/// +CONTENT_URL = "/api/houseplan/content" FILES_DIR = "houseplan/files" CONF_ADMIN_ONLY = "admin_only" -VERSION = "1.42.2" +VERSION = "1.43.0" DEFAULT_CONFIG: dict = { "spaces": [], diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index 393bed4..095e859 100755 --- a/custom_components/houseplan/frontend/houseplan-card.js +++ b/custom_components/houseplan/frontend/houseplan-card.js @@ -1,10 +1,10 @@ -const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function ci(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const hi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function pi(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function di(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function ui(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>di(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function _i(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function mi(t,e){for(const i of e){if(!di(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function gi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function fi(t,e){for(const i of e){if(!gi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function bi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function vi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function yi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=pi(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=bi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=ui(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=mi(e,a)),x.primary&&gi(e,x.primary)&&(x.hum=fi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?bi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=ui(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),vi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=bi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,[o])),gi(e,o)&&(d.hum=fi(e,[o])),vi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};vi(o,t),f.push(o)}}return f}function wi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function xi(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?mi(t,e):fi(t,e)}return null}function $i(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var ki={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room"};const Si={en:ki,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная"}};function Mi(t,e){if(e&&e in Si)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Di(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Si[t][e]??ki[e]??e,i)}class Ci extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Di(e,"editor.lang_auto")},{value:"en",label:Di(e,"editor.lang_en")},{value:"ru",label:Di(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Di(t,"editor.title"),default_floor:Di(t,"editor.default_floor"),language:Di(t,"editor.language"),icon_size:Di(t,"editor.icon_size"),show_temperature:Di(t,"editor.show_temperature"),live_states:Di(t,"editor.live_states"),show_signal:Di(t,"editor.show_signal"),kiosk:Di(t,"editor.kiosk"),cycle:Di(t,"editor.cycle")};return B`{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t){return t?t.startsWith("/houseplan_files/plans/")?"/api/houseplan/content/plans/_/"+t.slice(23):t.startsWith("/houseplan_files/files/")?"/api/houseplan/content/files/"+t.slice(23):t:""}function ci(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function hi(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const pi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function di(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function ui(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function _i(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>ui(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function mi(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function gi(t,e){for(const i of e){if(!ui(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function fi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function bi(t,e){for(const i of e){if(!fi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function vi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function yi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function wi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=di(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=vi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=_i(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=gi(e,a)),x.primary&&fi(e,x.primary)&&(x.hum=bi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?vi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=_i(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),yi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=vi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,[o])),fi(e,o)&&(d.hum=bi(e,[o])),yi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};yi(o,t),f.push(o)}}return f}function xi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function $i(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?gi(t,e):bi(t,e)}return null}function ki(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=gi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var Si={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}"};const Mi={en:Si,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}"}};function Di(t,e){if(e&&e in Mi)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Ci(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Mi[t][e]??Si[e]??e,i)}class Ti extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Ci(e,"editor.lang_auto")},{value:"en",label:Ci(e,"editor.lang_en")},{value:"ru",label:Ci(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Ci(t,"editor.title"),default_floor:Ci(t,"editor.default_floor"),language:Ci(t,"editor.language"),icon_size:Ci(t,"editor.icon_size"),show_temperature:Ci(t,"editor.show_temperature"),live_states:Ci(t,"editor.live_states"),show_signal:Ci(t,"editor.show_signal"),kiosk:Ci(t,"editor.kiosk"),cycle:Ci(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ci.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ci);const Ti=n` + >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ti.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ti);const zi=n` :host { --hp-bg: var(--card-background-color, #16212e); --hp-line: var(--divider-color, #2b3d4f); @@ -1443,9 +1443,9 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow z-index: 120; max-width: 90vw; } - `,zi=1e3;function Ei(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*zi,t.view_box[1]*e,t.view_box[2]*zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:zi,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*zi,t[1]*e]):void 0}))}}):[]}function Pi(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ai(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ri(t){const e=Ei(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=yi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Di(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*zi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Pi(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=_i(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?wi(t.hass,p,e.area):"none","temp"===o.fill?$i(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ai(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*zi,y:n.y*(zi/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Ei=1e3;function Pi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Ei/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ei,t.view_box[1]*e,t.view_box[2]*Ei,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Ei,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*Ei:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ei:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ei,t[1]*e]):void 0}))}}):[]}function Ai(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ri(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ni(t){const e=Pi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=wi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Ci(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*Ei*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ai(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?xi(t.hass,p,e.area):"none","temp"===o.fill?ki(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ri(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*Ei,y:n.y*(Ei/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*zi,y:o.y*(zi/t)}}const n=Ai(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B` +
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*Ei,y:o.y*(Ei/t)}}const n=Ri(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B`
${i.bg?j``:G} @@ -1453,13 +1453,13 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${_}${m}
- `}let Ni=null,Ii=null,Oi=!1;const Li=new Set;function qi(){if(Ni)return Ni;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function Fi(t){return Ni?Promise.resolve(Ni):Ii||(Ii=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Ni={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!Oi&&t.connection?.subscribeEvents){Oi=!0;try{await t.connection.subscribeEvents(()=>{Ni=null,Li.forEach(t=>t())},"houseplan_config_updated")}catch{Oi=!1}}return Ni}(t).finally(()=>{Ii=null}),Ii)}class Hi extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Di(t,"editor.space"),title:Di(t,"editor.title"),show_button:Di(t,"editor.show_button"),button_label:Di(t,"editor.button_label"),button_target:Di(t,"editor.button_target"),aspect_ratio:Di(t,"editor.aspect_ratio"),icon_size:Di(t,"editor.icon_size")};return B`{Ii=null,qi.forEach(t=>t())},"houseplan_config_updated")}catch{Li=!1}}return Ii}(t).finally(()=>{Oi=null}),Oi)}class Ui extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Ci(t,"editor.space"),title:Ci(t,"editor.title"),show_button:Ci(t,"editor.show_button"),button_label:Ci(t,"editor.button_label"),button_target:Ci(t,"editor.button_target"),aspect_ratio:Ci(t,"editor.aspect_ratio"),icon_size:Ci(t,"editor.icon_size")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Hi.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Hi);const Ui=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class Bi extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Ui(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=qi();return{type:"custom:houseplan-space-card",space:Ei(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||qi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},Li.add(t),()=>Li.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Fi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Mi(this.hass,this._config?.language)}getCardSize(){const t=Ei(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Di(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ri({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Di(this._lang,"space_card.not_found",{id:e}));const s=Ei(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Di(this._lang,"space_card.button");return B` + >
`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ui.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Ui);const Bi=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class ji extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Bi(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=Fi();return{type:"custom:houseplan-space-card",space:Pi(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||Fi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},qi.add(t),()=>qi.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Hi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Di(this.hass,this._config?.language)}getCardSize(){const t=Pi(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Ci(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ni({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Ci(this._lang,"space_card.not_found",{id:e}));const s=Pi(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Ci(this._lang,"space_card.button");return B` ${o?B`
${o}
`:G} ${i} @@ -1467,7 +1467,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow `:G}
- `}}Bi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Bi.styles=[Ti,n` + `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",Bi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const ji="houseplan_card_layout_v1",Wi="houseplan_card_cfg_v1",Gi="houseplan_card_zoom_v1",Vi="houseplan_card_nav_v1",Ki="houseplan_card_kiosk_v1",Zi=1e3,Yi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Ji=(t,e)=>{let i;return(...s)=>{clearTimeout(i),i=window.setTimeout(()=>t(...s),e)}};class Xi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Ji(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(ji,JSON.stringify(this._layout))},600),this._saveConfig=Ji(()=>{this._serverCfg&&(this._dropLegacySegments(),this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1}).catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly()):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Ki)||"null");this._kioskScale={icon:li(t?.icon),font:li(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Wi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Wi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Zi,t.view_box[1]*e,t.view_box[2]*Zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:Zi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Zi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(ji)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(){try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch{}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Mi(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=yi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=_i(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Zi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Zi,y:n/(Zi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:mi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&gi(this.hass,t.primary)?fi(this.hass,t.entities):null}_openMoreInfo(t){t?Yi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Di(Mi(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Gi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Yi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Xi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Zi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Zi/t.aspect:Zi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Zi,t[1]*e,t[2]*Zi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Vi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Vi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Zi,ry:t.y*e,rlen:t.length*Zi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Zi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Zi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Zi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Zi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Zi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Zi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Zi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Zi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)},c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Zi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Zi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Zi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Zi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog={...this._markerDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Zi,y:i/(Zi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog={...this._spaceDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B` - `}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog={...this._rulesDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog&&(this._rulesDialog={...this._rulesDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Ki,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1795,7 +1795,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?wi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @mousemove=${a} @mouseleave=${()=>this._tip=null}>`:t.poly?j`${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?_i(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&hi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?xi(this.hass,e,"temp"):t.area?$i(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?xi(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=fi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=li(t.settings?.name_scale),this._roomLabelScale=li(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Zi,y:o.y/(Zi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Zi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Zi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2078,7 +2078,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${t.link}
`:G} ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} - ${t.pdfs.map(t=>B` + ${t.pdfs.map(t=>B` ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} @@ -2245,7 +2245,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${t.pdfs.map(t=>B` - ${t.name} + ${t.name} this._removeMarkerPdf(t.url)}>`)}
- `}}Xi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Xi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Xi.styles=Ti,customElements.get("houseplan-card")||customElements.define("houseplan-card",Xi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.42.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Qi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Qi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Qi.styles=zi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Qi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.0 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/custom_components/houseplan/http_api.py b/custom_components/houseplan/http_api.py index 092bd5b..c79752a 100644 --- a/custom_components/houseplan/http_api.py +++ b/custom_components/houseplan/http_api.py @@ -18,7 +18,7 @@ except ImportError: # older HA versions KEY_HASS = "hass" # type: ignore[assignment] from homeassistant.core import HomeAssistant -from .const import CONF_ADMIN_ONLY, FILES_DIR, FILES_URL +from .const import CONF_ADMIN_ONLY, CONTENT_URL, FILES_DIR, FILES_URL, PLANS_DIR from .store import get_entry from .validation import ( FILE_EXTENSIONS, @@ -32,6 +32,59 @@ _LOGGER = logging.getLogger(__name__) _CHUNK = 64 * 1024 +_MIME = { + ".pdf": "application/pdf", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".svg": "image/svg+xml", + ".webp": "image/webp", + ".gif": "image/gif", + ".txt": "text/plain", +} + + +class HouseplanContentView(HomeAssistantView): + """Authenticated read access to plans and marker files (audit B1). + + The directories used to be exposed as unauthenticated static paths, so + anyone who could reach the HA endpoint could pull floor plans and uploaded + manuals without logging in. This view keeps the same URLs but requires a + Home Assistant session (or a signed path, which the frontend uses for + inside the SVG). + """ + + url = "/api/houseplan/content/{kind}/{sub}/{name}" + name = "api:houseplan:content" + requires_auth = True + + async def get(self, request: web.Request, kind: str, sub: str, name: str) -> web.StreamResponse: + hass: HomeAssistant = request.app[KEY_HASS] + if kind not in ("plans", "files"): + return web.Response(status=404) + safe_sub = sanitize_marker_id(sub) + safe_name = sanitize_filename(name) + if not safe_sub or not safe_name: + return web.Response(status=404) + base = Path(hass.config.path(PLANS_DIR if kind == "plans" else FILES_DIR)).resolve() + # plans live flat in one directory: the sub segment is a placeholder ("_") + path = (base / safe_name if kind == "plans" else base / safe_sub / safe_name).resolve() + # defence in depth: the sanitizers already strip separators + if not str(path).startswith(str(base)): + return web.Response(status=404) + + def _read() -> bytes | None: + return path.read_bytes() if path.is_file() else None + + blob = await hass.async_add_executor_job(_read) + if blob is None: + return web.Response(status=404) + return web.Response( + body=blob, + content_type=_MIME.get(path.suffix.lower(), "application/octet-stream"), + headers={"Cache-Control": "private, max-age=3600"}, + ) + class HouseplanUploadView(HomeAssistantView): """POST /api/houseplan/upload — save a marker file, return its URL.""" @@ -99,5 +152,5 @@ class HouseplanUploadView(HomeAssistantView): mtime = await hass.async_add_executor_job(_write) return web.json_response( - {"ok": True, "url": f"{FILES_URL}/{marker_id}/{safe_name}?v={mtime}", "name": filename} + {"ok": True, "url": f"{CONTENT_URL}/files/{marker_id}/{safe_name}?v={mtime}", "name": filename} ) diff --git a/custom_components/houseplan/manifest.json b/custom_components/houseplan/manifest.json index 269ecb1..782696f 100755 --- a/custom_components/houseplan/manifest.json +++ b/custom_components/houseplan/manifest.json @@ -16,5 +16,5 @@ "issue_tracker": "https://github.com/Matysh/houseplan-card/issues", "requirements": [], "single_config_entry": true, - "version": "1.42.2" + "version": "1.43.0" } diff --git a/custom_components/houseplan/repairs.py b/custom_components/houseplan/repairs.py index c2dbcef..6773bee 100644 --- a/custom_components/houseplan/repairs.py +++ b/custom_components/houseplan/repairs.py @@ -11,7 +11,7 @@ from pathlib import Path from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir -from .const import DOMAIN, PLANS_DIR, PLANS_URL +from .const import CONTENT_URL, DOMAIN, PLANS_DIR, PLANS_URL from .store import HouseplanConfigEntry @@ -25,9 +25,15 @@ async def async_check_plan_files(hass: HomeAssistant, entry: HouseplanConfigEntr res = [] for sp in spaces: url = sp.get("plan_url") or "" - if not url.startswith(PLANS_URL + "/"): + # both the legacy static URL and the authenticated content URL + prefix = None + if url.startswith(PLANS_URL + "/"): + prefix = PLANS_URL + "/" + elif url.startswith(CONTENT_URL + "/plans/_/"): + prefix = CONTENT_URL + "/plans/_/" + if prefix is None: continue # external/legacy URL — not ours to verify - fname = url[len(PLANS_URL) + 1 :].split("?", 1)[0] + fname = url[len(prefix) :].split("?", 1)[0] if not (plans_dir / fname).is_file(): res.append((sp.get("id", "?"), fname)) return res diff --git a/custom_components/houseplan/websocket_api.py b/custom_components/houseplan/websocket_api.py index fb879e4..a5086df 100755 --- a/custom_components/houseplan/websocket_api.py +++ b/custom_components/houseplan/websocket_api.py @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant, callback from .const import ( CONF_ADMIN_ONLY, DEFAULT_CONFIG, - PLANS_DIR, PLANS_URL, + CONTENT_URL, PLANS_DIR, PLANS_URL, ) from .store import HouseplanData, get_data, get_entry from .validation import ( @@ -290,5 +290,5 @@ async def ws_plan_set(hass: HomeAssistant, connection, msg: dict[str, Any]) -> N mtime = await hass.async_add_executor_job(_write) connection.send_result( - msg["id"], {"ok": True, "url": f"{PLANS_URL}/{space_id}.{msg['ext']}?v={mtime}"} + msg["id"], {"ok": True, "url": f"{CONTENT_URL}/plans/_/{space_id}.{msg['ext']}?v={mtime}"} ) diff --git a/demo/smoke_dialog_zombie.mjs b/demo/smoke_dialog_zombie.mjs new file mode 100644 index 0000000..613eb0b --- /dev/null +++ b/demo/smoke_dialog_zombie.mjs @@ -0,0 +1,55 @@ +import { launch } from './serve.mjs'; +const { page, browser } = await launch(); +const res = await page.evaluate(async () => { + const out = {}; + const c = window.__card; + const sr = () => c.shadowRoot || c.renderRoot; + let pageError = null; + window.addEventListener('error', (e) => { pageError = String(e.message); }); + // сохранение падает, а диалог закрыт до ответа — карточка не должна умереть + c.hass = { ...c.hass, callWS: async (msg) => { + if (String(msg.type).endsWith('/set')) { await new Promise((r) => setTimeout(r, 60)); throw new Error('boom'); } + return { config: c._serverCfg, rev: c._cfgRev }; + } }; + await c.updateComplete; + // 1) диалог общих настроек + c._openSettingsDialog(); await c.updateComplete; + const p = c._saveSettingsDialog(); + c._settingsDialog = null; // Esc во время сохранения + await c.updateComplete; + await p.catch(() => {}); + await new Promise((r) => setTimeout(r, 120)); + await c.updateComplete; + out.settingsStaysClosed = c._settingsDialog === null; + out.cardAliveAfterSettings = !!sr().querySelector('.stage'); + // 2) диалог правил + c._openRulesDialog(); await c.updateComplete; + const p2 = c._saveRules(); + c._rulesDialog = null; await c.updateComplete; + await p2.catch(() => {}); + await new Promise((r) => setTimeout(r, 120)); + await c.updateComplete; + out.rulesStaysClosed = c._rulesDialog === null; + out.cardAliveAfterRules = !!sr().querySelector('.stage'); + // 3) диалог устройства + c._setMode('devices'); await c.updateComplete; + c._openMarkerDialog(c._devices[0]); await c.updateComplete; + const p3 = c._saveMarker(); + c._markerDialog = null; await c.updateComplete; + await p3.catch(() => {}); + await new Promise((r) => setTimeout(r, 120)); + await c.updateComplete; + out.markerStaysClosed = c._markerDialog === null; + out.cardAliveAfterMarker = !!sr().querySelector('.stage'); + out.noPageError = pageError === null; + // 4) при ОТКРЫТОМ диалоге ошибка снимает busy (поведение сохранено) + c._openSettingsDialog(); await c.updateComplete; + const p4 = c._saveSettingsDialog(); + await p4.catch(() => {}); + await new Promise((r) => setTimeout(r, 120)); + out.busyClearedWhenOpen = c._settingsDialog !== null && c._settingsDialog.busy === false; + c._settingsDialog = null; + return out; +}); +console.log(JSON.stringify(res, null, 1)); +await browser.close(); diff --git a/demo/smoke_save_race.mjs b/demo/smoke_save_race.mjs new file mode 100644 index 0000000..b994411 --- /dev/null +++ b/demo/smoke_save_race.mjs @@ -0,0 +1,36 @@ +import { launch } from './serve.mjs'; +const { page, browser } = await launch(); +const res = await page.evaluate(async () => { + const out = {}; + const c = window.__card; + const sent = []; + // перехват WS: config/set логируем, config/get отдаёт "серверную" копию БЕЗ локальной правки + const server = { cfg: JSON.parse(JSON.stringify(c._serverCfg)), rev: c._cfgRev }; + c.hass = { ...c.hass, callWS: async (msg) => { + if (msg.type === 'houseplan/config/set') { + sent.push(JSON.parse(JSON.stringify(msg.config))); + server.cfg = JSON.parse(JSON.stringify(msg.config)); + server.rev = (msg.expected_rev ?? server.rev) + 1; + return { rev: server.rev }; + } + if (msg.type === 'houseplan/config/get') return { config: JSON.parse(JSON.stringify(server.cfg)), rev: server.rev }; + return {}; + } }; + await c.updateComplete; + // локальная правка (как разметка комнаты) + дебаунс + const sp = c._curSpaceCfg; + sp.rooms.push({ id: 'race_room', name: 'RACE', area: null, poly: [[0.8, 0.8], [0.9, 0.8], [0.9, 0.9], [0.8, 0.9]] }); + c._saveConfig(); + out.pending = c._saveConfig.pending(); + // через 100 мс приходит событие о чужой ревизии — раньше это стирало правку + c._cfgRev = server.rev; // симулируем: наша ревизия отстала + await c._reloadConfigOnly(); + await new Promise((r) => setTimeout(r, 900)); + // правка обязана уцелеть и уйти на сервер + out.editSent = sent.some((cf) => cf.spaces.some((s) => s.rooms?.some((r) => r.id === 'race_room'))); + out.editInMemory = c._serverCfg.spaces.some((s) => s.rooms?.some((r) => r.id === 'race_room')); + out.serverHasIt = server.cfg.spaces.some((s) => s.rooms?.some((r) => r.id === 'race_room')); + return out; +}); +console.log(JSON.stringify(res, null, 1)); +await browser.close(); diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js index 393bed4..095e859 100755 --- a/demo/srv/assets/houseplan-card.js +++ b/demo/srv/assets/houseplan-card.js @@ -1,10 +1,10 @@ -const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function ci(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const hi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function pi(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function di(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function ui(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>di(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function _i(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function mi(t,e){for(const i of e){if(!di(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function gi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function fi(t,e){for(const i of e){if(!gi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function bi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function vi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function yi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=pi(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=bi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=ui(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=mi(e,a)),x.primary&&gi(e,x.primary)&&(x.hum=fi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?bi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=ui(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),vi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=bi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,[o])),gi(e,o)&&(d.hum=fi(e,[o])),vi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};vi(o,t),f.push(o)}}return f}function wi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function xi(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?mi(t,e):fi(t,e)}return null}function $i(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var ki={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room"};const Si={en:ki,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная"}};function Mi(t,e){if(e&&e in Si)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Di(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Si[t][e]??ki[e]??e,i)}class Ci extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Di(e,"editor.lang_auto")},{value:"en",label:Di(e,"editor.lang_en")},{value:"ru",label:Di(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Di(t,"editor.title"),default_floor:Di(t,"editor.default_floor"),language:Di(t,"editor.language"),icon_size:Di(t,"editor.icon_size"),show_temperature:Di(t,"editor.show_temperature"),live_states:Di(t,"editor.live_states"),show_signal:Di(t,"editor.show_signal"),kiosk:Di(t,"editor.kiosk"),cycle:Di(t,"editor.cycle")};return B`{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t){return t?t.startsWith("/houseplan_files/plans/")?"/api/houseplan/content/plans/_/"+t.slice(23):t.startsWith("/houseplan_files/files/")?"/api/houseplan/content/files/"+t.slice(23):t:""}function ci(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function hi(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const pi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function di(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function ui(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function _i(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>ui(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function mi(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function gi(t,e){for(const i of e){if(!ui(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function fi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function bi(t,e){for(const i of e){if(!fi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function vi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function yi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function wi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=di(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=vi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=_i(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=gi(e,a)),x.primary&&fi(e,x.primary)&&(x.hum=bi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?vi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=_i(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),yi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=vi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,[o])),fi(e,o)&&(d.hum=bi(e,[o])),yi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};yi(o,t),f.push(o)}}return f}function xi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function $i(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?gi(t,e):bi(t,e)}return null}function ki(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=gi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var Si={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}"};const Mi={en:Si,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}"}};function Di(t,e){if(e&&e in Mi)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Ci(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Mi[t][e]??Si[e]??e,i)}class Ti extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Ci(e,"editor.lang_auto")},{value:"en",label:Ci(e,"editor.lang_en")},{value:"ru",label:Ci(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Ci(t,"editor.title"),default_floor:Ci(t,"editor.default_floor"),language:Ci(t,"editor.language"),icon_size:Ci(t,"editor.icon_size"),show_temperature:Ci(t,"editor.show_temperature"),live_states:Ci(t,"editor.live_states"),show_signal:Ci(t,"editor.show_signal"),kiosk:Ci(t,"editor.kiosk"),cycle:Ci(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ci.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ci);const Ti=n` + >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ti.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ti);const zi=n` :host { --hp-bg: var(--card-background-color, #16212e); --hp-line: var(--divider-color, #2b3d4f); @@ -1443,9 +1443,9 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow z-index: 120; max-width: 90vw; } - `,zi=1e3;function Ei(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*zi,t.view_box[1]*e,t.view_box[2]*zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:zi,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*zi,t[1]*e]):void 0}))}}):[]}function Pi(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ai(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ri(t){const e=Ei(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=yi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Di(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*zi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Pi(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=_i(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?wi(t.hass,p,e.area):"none","temp"===o.fill?$i(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ai(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*zi,y:n.y*(zi/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Ei=1e3;function Pi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Ei/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ei,t.view_box[1]*e,t.view_box[2]*Ei,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Ei,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*Ei:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ei:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ei,t[1]*e]):void 0}))}}):[]}function Ai(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ri(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ni(t){const e=Pi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=wi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Ci(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*Ei*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ai(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?xi(t.hass,p,e.area):"none","temp"===o.fill?ki(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ri(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*Ei,y:n.y*(Ei/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*zi,y:o.y*(zi/t)}}const n=Ai(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B` +
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*Ei,y:o.y*(Ei/t)}}const n=Ri(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B`
${i.bg?j``:G} @@ -1453,13 +1453,13 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${_}${m}
- `}let Ni=null,Ii=null,Oi=!1;const Li=new Set;function qi(){if(Ni)return Ni;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function Fi(t){return Ni?Promise.resolve(Ni):Ii||(Ii=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Ni={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!Oi&&t.connection?.subscribeEvents){Oi=!0;try{await t.connection.subscribeEvents(()=>{Ni=null,Li.forEach(t=>t())},"houseplan_config_updated")}catch{Oi=!1}}return Ni}(t).finally(()=>{Ii=null}),Ii)}class Hi extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Di(t,"editor.space"),title:Di(t,"editor.title"),show_button:Di(t,"editor.show_button"),button_label:Di(t,"editor.button_label"),button_target:Di(t,"editor.button_target"),aspect_ratio:Di(t,"editor.aspect_ratio"),icon_size:Di(t,"editor.icon_size")};return B`{Ii=null,qi.forEach(t=>t())},"houseplan_config_updated")}catch{Li=!1}}return Ii}(t).finally(()=>{Oi=null}),Oi)}class Ui extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Ci(t,"editor.space"),title:Ci(t,"editor.title"),show_button:Ci(t,"editor.show_button"),button_label:Ci(t,"editor.button_label"),button_target:Ci(t,"editor.button_target"),aspect_ratio:Ci(t,"editor.aspect_ratio"),icon_size:Ci(t,"editor.icon_size")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Hi.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Hi);const Ui=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class Bi extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Ui(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=qi();return{type:"custom:houseplan-space-card",space:Ei(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||qi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},Li.add(t),()=>Li.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Fi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Mi(this.hass,this._config?.language)}getCardSize(){const t=Ei(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Di(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ri({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Di(this._lang,"space_card.not_found",{id:e}));const s=Ei(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Di(this._lang,"space_card.button");return B` + >
`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ui.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Ui);const Bi=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class ji extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Bi(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=Fi();return{type:"custom:houseplan-space-card",space:Pi(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||Fi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},qi.add(t),()=>qi.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Hi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Di(this.hass,this._config?.language)}getCardSize(){const t=Pi(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Ci(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ni({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Ci(this._lang,"space_card.not_found",{id:e}));const s=Pi(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Ci(this._lang,"space_card.button");return B` ${o?B`
${o}
`:G} ${i} @@ -1467,7 +1467,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow `:G}
- `}}Bi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Bi.styles=[Ti,n` + `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",Bi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const ji="houseplan_card_layout_v1",Wi="houseplan_card_cfg_v1",Gi="houseplan_card_zoom_v1",Vi="houseplan_card_nav_v1",Ki="houseplan_card_kiosk_v1",Zi=1e3,Yi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Ji=(t,e)=>{let i;return(...s)=>{clearTimeout(i),i=window.setTimeout(()=>t(...s),e)}};class Xi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Ji(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(ji,JSON.stringify(this._layout))},600),this._saveConfig=Ji(()=>{this._serverCfg&&(this._dropLegacySegments(),this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1}).catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly()):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Ki)||"null");this._kioskScale={icon:li(t?.icon),font:li(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Wi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Wi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Zi,t.view_box[1]*e,t.view_box[2]*Zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:Zi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Zi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(ji)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(){try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch{}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Mi(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=yi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=_i(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Zi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Zi,y:n/(Zi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:mi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&gi(this.hass,t.primary)?fi(this.hass,t.entities):null}_openMoreInfo(t){t?Yi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Di(Mi(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Gi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Yi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Xi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Zi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Zi/t.aspect:Zi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Zi,t[1]*e,t[2]*Zi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Vi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Vi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Zi,ry:t.y*e,rlen:t.length*Zi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Zi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Zi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Zi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Zi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Zi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Zi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Zi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Zi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)},c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Zi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Zi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Zi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Zi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog={...this._markerDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Zi,y:i/(Zi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog={...this._spaceDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B` - `}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog={...this._rulesDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog&&(this._rulesDialog={...this._rulesDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Ki,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1795,7 +1795,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?wi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @mousemove=${a} @mouseleave=${()=>this._tip=null}>`:t.poly?j`${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?_i(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&hi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?xi(this.hass,e,"temp"):t.area?$i(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?xi(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=fi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=li(t.settings?.name_scale),this._roomLabelScale=li(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Zi,y:o.y/(Zi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Zi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Zi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2078,7 +2078,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${t.link}
`:G} ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} - ${t.pdfs.map(t=>B` + ${t.pdfs.map(t=>B` ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} @@ -2245,7 +2245,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${t.pdfs.map(t=>B` - ${t.name} + ${t.name} this._removeMarkerPdf(t.url)}>`)}
- `}}Xi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Xi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Xi.styles=Ti,customElements.get("houseplan-card")||customElements.define("houseplan-card",Xi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.42.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Qi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Qi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Qi.styles=zi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Qi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.0 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js index 393bed4..095e859 100755 --- a/dist/houseplan-card.js +++ b/dist/houseplan-card.js @@ -1,10 +1,10 @@ -const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function ci(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const hi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function pi(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function di(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function ui(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>di(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function _i(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function mi(t,e){for(const i of e){if(!di(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function gi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function fi(t,e){for(const i of e){if(!gi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function bi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function vi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function yi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=pi(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=bi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=ui(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=mi(e,a)),x.primary&&gi(e,x.primary)&&(x.hum=fi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?bi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=ui(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),d.primary&&gi(e,d.primary)&&(d.hum=fi(e,h)),vi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=bi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=mi(e,[o])),gi(e,o)&&(d.hum=fi(e,[o])),vi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};vi(o,t),f.push(o)}}return f}function wi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function xi(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?mi(t,e):fi(t,e)}return null}function $i(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var ki={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room"};const Si={en:ki,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная"}};function Mi(t,e){if(e&&e in Si)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Di(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Si[t][e]??ki[e]??e,i)}class Ci extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Di(e,"editor.lang_auto")},{value:"en",label:Di(e,"editor.lang_en")},{value:"ru",label:Di(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Di(t,"editor.title"),default_floor:Di(t,"editor.default_floor"),language:Di(t,"editor.language"),icon_size:Di(t,"editor.icon_size"),show_temperature:Di(t,"editor.show_temperature"),live_states:Di(t,"editor.live_states"),show_signal:Di(t,"editor.show_signal"),kiosk:Di(t,"editor.kiosk"),cycle:Di(t,"editor.cycle")};return B`{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:p,getPrototypeOf:d}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,b={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>!a(t,e),y={attribute:!0,type:String,converter:b,reflect:!1,useDefault:!1,hasChanged:v};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=d(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:b).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:b;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??v)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,$=t=>t,k=x.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,E=()=>z.createComment(""),P=t=>null===t||"object"!=typeof t&&"function"!=typeof t,A=Array.isArray,R="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,I=/-->/g,O=/>/g,L=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!A(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const Y=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=N;for(let e=0;e"===l[0]?(r=o??N,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?L:'"'===l[3]?F:q):r===F||r===q?r=L:r===I||r===O?r=N:(r=L,o=void 0);const p=r===L&&t[e+1].startsWith("/>")?" ":"";n+=r===N?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+D+p):i+D+(-2===c?e:p)}return[Z(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class J{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=Y(t,e);if(this.el=J.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=k?k.emptyScript:"";for(let i=0;iA(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&P(this._$AH)?this._$AA.nextSibling.data=t:this.T(z.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=J.createElement(Z(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new J(t)),e}k(t){A(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(E()),this.O(E()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(E(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return W}}lt._$litElement$=!0,lt.finalized=!0,at.litElementHydrateSupport?.({LitElement:lt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:lt}),(at.litElementVersions??=[]).push("4.2.2");const ht=new Set(["hacs","sun","backup","hassio","met","telegram_bot","mobile_app","systemmonitor","better_thermostat","adaptive_lighting","yandex_pogoda","upnp_serial_number"]),pt=[{pattern:"протечк|leak|water sensor",icon:"mdi:water-alert"},{pattern:"клапан|valve",icon:"mdi:pipe-valve"},{pattern:"дым|smoke",icon:"mdi:smoke-detector"},{pattern:"термоголов|trv|radiator",icon:"mdi:radiator"},{pattern:"температ|temperature|climate sensor",icon:"mdi:thermometer"},{pattern:"qingping|air monitor|молекул|air quality",icon:"mdi:air-filter"},{pattern:"штор|curtain|blind|shade",icon:"mdi:roller-shade"},{pattern:"розетк|plug|socket|outlet",icon:"mdi:power-socket-de"},{pattern:"выключат|switch",icon:"mdi:light-switch"},{pattern:"лампа|лампочк|bulb|gx53|светильник|rgb|lamp|light strip",icon:"mdi:lightbulb"},{pattern:"камер|camera",icon:"mdi:cctv"},{pattern:"замок|ttlock|lock|sn609|sn9161",icon:"mdi:lock"},{pattern:"ворота|garage|gate",icon:"mdi:garage-variant"},{pattern:"калитк|door|открыт|contact",icon:"mdi:door"},{pattern:"счётчик|счетчик|kws|meter",icon:"mdi:meter-electric"},{pattern:"вводный автомат|breaker|wifimcbn",icon:"mdi:electric-switch"},{pattern:"myheat|котёл|котел|boiler|отоплен|heating",icon:"mdi:water-boiler"},{pattern:"холодильник|fridge",icon:"mdi:fridge"},{pattern:"стиральн|washer|washing",icon:"mdi:washing-machine"},{pattern:"сушилк|dryer",icon:"mdi:tumble-dryer"},{pattern:"пылесос|vacuum|dreame|roborock",icon:"mdi:robot-vacuum"},{pattern:"soundbar",icon:"mdi:soundbar"},{pattern:"колонк|станц|speaker|яндекс|yandex|алиса|alice",icon:"mdi:speaker"},{pattern:"tv|телевизор|hyundaitv|mitv|television",icon:"mdi:television"},{pattern:"keenetic|роутер|router|mesh|access point",icon:"mdi:router-wireless"},{pattern:"ибп|ups|kirpich",icon:"mdi:battery-charging-high"},{pattern:"slzb|координат|zigbee|coordinator",icon:"mdi:zigbee"},{pattern:"motion|движен|presence|присутств",icon:"mdi:motion-sensor"},{pattern:"humidity|влажн",icon:"mdi:water-percent"}];function dt(t){const e=[];for(const i of t)if(i&&"string"==typeof i.pattern&&i.icon)try{e.push({re:new RegExp(i.pattern,"i"),icon:i.icon})}catch{}return e}const ut=dt(pt),_t={temperature:"mdi:thermometer",humidity:"mdi:water-percent",motion:"mdi:motion-sensor",occupancy:"mdi:motion-sensor",door:"mdi:door",window:"mdi:window-closed",garage_door:"mdi:garage-variant",smoke:"mdi:smoke-detector",moisture:"mdi:water-alert",gas:"mdi:gas-cylinder",power:"mdi:meter-electric",energy:"mdi:meter-electric",illuminance:"mdi:brightness-5",co2:"mdi:molecule-co2",pm25:"mdi:air-filter",battery:"mdi:battery"},mt="mdi:chip";function gt(t,e,i){const s=((t||"")+" "+(e||"")).toLowerCase();for(const{re:t,icon:e}of i??ut)if(t.test(s))return e;return mt}const ft=["light","switch","cover","valve","lock","climate","fan","media_player","camera","vacuum","humidifier","water_heater","alarm_control_panel","sensor","binary_sensor","event","button","number","select","update"];var bt=/^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,vt=Math.ceil,yt=Math.floor,wt="[BigNumber Error] ",xt=wt+"Number primitive has more than 15 significant digits: ",$t=1e14,kt=14,St=9007199254740991,Mt=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],Dt=1e7,Ct=1e9;function Tt(t){var e=0|t;return t>0||t===e?e:e-1}function zt(t){for(var e,i,s=1,o=t.length,n=t[0]+"";sc^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;rn[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Pt(t,e,i,s){if(ti||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?ti?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function At(t){var e=t.c.length-1;return Tt(t.e/kt)==e&&t.c[e]%2!=0}function Rt(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(e<0?"e":"e+")+e}function Nt(t,e,i){var s,o;if(e<0){for(o=i+".";++e;o+=i);t=o+t}else if(++e>(s=t.length)){for(o=i,e-=s;--e;o+=i);t+=o}else ev?d.c=d.e=null:t.e=10;l/=10,a++);return void(a>v?d.c=d.e=null:(d.e=a,d.c=[t]))}p=String(t)}else{if(!bt.test(p=String(t)))return o(d,p,c);d.s=45==p.charCodeAt(0)?(p=p.slice(1),-1):1}(a=p.indexOf("."))>-1&&(p=p.replace(".","")),(l=p.search(/e/i))>0?(a<0&&(a=l),a+=+p.slice(l+1),p=p.substring(0,l)):a<0&&(a=p.length)}else{if(Pt(e,2,k.length,"Base"),10==e&&S)return z(d=new M(t),_+d.e+1,m);if(p=String(t),c="number"==typeof t){if(0*t!=0)return o(d,p,c,e);if(d.s=1/t<0?(p=p.slice(1),-1):1,M.DEBUG&&p.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else d.s=45===p.charCodeAt(0)?(p=p.slice(1),-1):1;for(i=k.slice(0,e),a=l=0,h=p.length;la){a=h;continue}}else if(!r&&(p==p.toUpperCase()&&(p=p.toLowerCase())||p==p.toLowerCase()&&(p=p.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(d,String(t),c,e)}c=!1,(a=(p=s(p,e,10,d.s)).indexOf("."))>-1?p=p.replace(".",""):a=p.length}for(l=0;48===p.charCodeAt(l);l++);for(h=p.length;48===p.charCodeAt(--h););if(p=p.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+d.s*t);if((a=a-l-1)>v)d.c=d.e=null;else if(a=f)?Rt(l,r):Nt(l,r,"0");else if(n=(t=z(new M(t),e,i)).e,a=(l=zt(t.c)).length,1==s||2==s&&(e<=n||n<=g)){for(;ar),l=Nt(l,n,"0"),n+1>a){if(--e>0)for(l+=".";e--;l+="0");}else if((e+=n-a)>0)for(n+1==a&&(l+=".");e--;l+="0");return t.s<0&&o?"-"+l:l}function C(t,e){for(var i,s,o=1,n=new M(t[0]);o=10;o/=10,s++);return(i=s+i*kt-1)>v?t.c=t.e=null:i=10;a/=10,o++);if((n=e-o)<0)n+=kt,r=e,l=p[c=0],h=yt(l/d[o-r-1]%10);else if((c=vt((n+1)/kt))>=p.length){if(!s)break t;for(;p.length<=c;p.push(0));l=h=0,o=1,r=(n%=kt)-kt+1}else{for(l=a=p[c],o=1;a>=10;a/=10,o++);h=(r=(n%=kt)-kt+o)<0?0:yt(l/d[o-r-1]%10)}if(s=s||e<0||null!=p[c+1]||(r<0?l:l%d[o-r-1]),s=i<4?(h||s)&&(0==i||i==(t.s<0?3:2)):h>5||5==h&&(4==i||s||6==i&&(n>0?r>0?l/d[o-r]:0:p[c-1])%10&1||i==(t.s<0?8:7)),e<1||!p[0])return p.length=0,s?(e-=t.e+1,p[0]=d[(kt-e%kt)%kt],t.e=-e||0):p[0]=t.e=0,t;if(0==n?(p.length=c,a=1,c--):(p.length=c+1,a=d[kt-n],p[c]=r>0?yt(l/d[o-r]%d[r])*a:0),s)for(;;){if(0==c){for(n=1,r=p[0];r>=10;r/=10,n++);for(r=p[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,p[0]==$t&&(p[0]=1));break}if(p[c]+=a,p[c]!=$t)break;p[c--]=0,a=1}for(n=p.length;0===p[--n];p.pop());}t.e>v?t.c=t.e=null:t.e=f?Rt(e,i):Nt(e,i,"0"),t.s<0?"-"+e:e)}return M.clone=t,M.ROUND_UP=0,M.ROUND_DOWN=1,M.ROUND_CEIL=2,M.ROUND_FLOOR=3,M.ROUND_HALF_UP=4,M.ROUND_HALF_DOWN=5,M.ROUND_HALF_EVEN=6,M.ROUND_HALF_CEIL=7,M.ROUND_HALF_FLOOR=8,M.EUCLID=9,M.config=M.set=function(t){var e,i;if(null!=t){if("object"!=typeof t)throw Error(wt+"Object expected: "+t);if(t.hasOwnProperty(e="DECIMAL_PLACES")&&(Pt(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Pt(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Pt(i[0],-Ct,0,e),Pt(i[1],0,Ct,e),g=i[0],f=i[1]):(Pt(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Pt(i[0],-Ct,-1,e),Pt(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Pt(i,-Ct,Ct,e),!i)throw Error(wt+e+" cannot be zero: "+i);b=-(v=i<0?-i:i)}if(t.hasOwnProperty(e="CRYPTO")){if((i=t[e])!==!!i)throw Error(wt+e+" not true or false: "+i);if(i){if("undefined"==typeof crypto||!crypto||!crypto.getRandomValues&&!crypto.randomBytes)throw y=!i,Error(wt+"crypto unavailable");y=i}else y=i}if(t.hasOwnProperty(e="MODULO_MODE")&&(Pt(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Pt(i=t[e],0,Ct,e),x=i),t.hasOwnProperty(e="FORMAT")){if("object"!=typeof(i=t[e]))throw Error(wt+e+" not an object: "+i);$=i}if(t.hasOwnProperty(e="ALPHABET")){if("string"!=typeof(i=t[e])||/^.?$|[+\-.\s]|(.).*\1/.test(i))throw Error(wt+e+" invalid: "+i);S="0123456789"==i.slice(0,10),k=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:$,ALPHABET:k}},M.isBigNumber=function(t){if(!t||!0!==t._isBigNumber)return!1;if(!M.DEBUG)return!0;var e,i,s=t.c,o=t.e,n=t.s;t:if("[object Array]"=={}.toString.call(s)){if((1===n||-1===n)&&o>=-Ct&&o<=Ct&&o===yt(o)){if(0===s[0]){if(0===o&&1===s.length)return!0;break t}if((e=(o+1)%kt)<1&&(e+=kt),String(s[0]).length==e){for(e=0;e=$t||i!==yt(i))break t;if(0!==i)return!0}}}else if(null===s&&null===o&&(null===n||1===n||-1===n))return!0;throw Error(wt+"Invalid BigNumber: "+t)},M.maximum=M.max=function(){return C(arguments,-1)},M.minimum=M.min=function(){return C(arguments,1)},M.random=(n=9007199254740992,r=Math.random()*n&2097151?function(){return yt(Math.random()*n)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)},function(t){var e,i,s,o,n,a=0,l=[],c=new M(u);if(null==t?t=_:Pt(t,0,Ct),o=vt(t/kt),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a>>11))>=9e15?(i=crypto.getRandomValues(new Uint32Array(2)),e[a]=i[0],e[a+1]=i[1]):(l.push(n%1e14),a+=2);a=o/2}else{if(!crypto.randomBytes)throw y=!1,Error(wt+"crypto unavailable");for(e=crypto.randomBytes(o*=7);a=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a=10;n/=10,a++);ai-1&&(null==r[o+1]&&(r[o+1]=0),r[o+1]+=r[o]/i|0,r[o]%=i)}return r.reverse()}return function(s,o,n,r,a){var l,c,h,p,d,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(p=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=p,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=p=(g=e(s,o,n,a?(l=k,t):(l=t,k))).length;0==g[--p];g.pop());if(!g[0])return l.charAt(0);if(b<0?--h:(u.c=g,u.e=h,u.s=r,g=(u=i(u,f,v,y,n)).c,d=u.r,h=u.e),b=g[c=h+v+1],p=n/2,d=d||c<0||null!=g[c+1],d=y<4?(null!=b||d)&&(0==y||y==(u.s<0?3:2)):b>p||b==p&&(4==y||d||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=d?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,d)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(p=g.length;!g[--p];);for(b=0,s="";b<=p;s+=l.charAt(g[b++]));s=Nt(s,h,l.charAt(0))}return s}}(),i=function(){function t(t,e,i){var s,o,n,r,a=0,l=t.length,c=e%Dt,h=e/Dt|0;for(t=t.slice();l--;)a=((o=c*(n=t[l]%Dt)+(s=h*n+(r=t[l]/Dt|0)*c)%Dt*Dt+a)/i|0)+(s/Dt|0)+h*r,t[l]=o%i;return a&&(t=[a].concat(t)),t}function e(t,e,i,s){var o,n;if(i!=s)n=i>s?1:-1;else for(o=n=0;oe[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,p,d,u,_,m,g,f,b,v,y,w,x,$,k,S=s.s==o.s?1:-1,D=s.c,C=o.c;if(!(D&&D[0]&&C&&C[0]))return new M(s.s&&o.s&&(D?!C||D[0]!=C[0]:C)?D&&0==D[0]||!C?0*S:S/0:NaN);for(g=(m=new M(S)).c=[],S=n+(c=s.e-o.e)+1,a||(a=$t,c=Tt(s.e/kt)-Tt(o.e/kt),S=S/kt|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),p=!0;else{for(w=D.length,$=C.length,h=0,S+=2,(d=yt(a/(C[0]+1)))>1&&(C=t(C,d,a),D=t(D,d,a),$=C.length,w=D.length),y=$,b=(f=D.slice(0,$)).length;b<$;f[b++]=0);k=C.slice(),k=[0].concat(k),x=C[0],C[1]>=a/2&&x++;do{if(d=0,(l=e(C,f,$,b))<0){if(v=f[0],$!=b&&(v=v*a+(f[1]||0)),(d=yt(v/x))>1)for(d>=a&&(d=a-1),_=(u=t(C,d,a)).length,b=f.length;1==e(u,f,_,b);)d--,i(u,$<_?k:C,_,a),_=u.length,l=1;else 0==d&&(l=d=1),_=(u=C.slice()).length;if(_=10;S/=10,h++);z(m,n+(m.e=h+c*kt-1)+1,r,p)}else m.e=c,m.r=+p;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,p=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(p,"");if(h.test(n))t.s=isNaN(n)?null:n<0?-1:1;else{if(!i&&(n=n.replace(a,function(t,e,i){return o="x"==(i=i.toLowerCase())?16:"b"==i?2:8,s&&s!=o?t:e}),s&&(o=s,n=n.replace(l,"$1").replace(c,"0.$1")),e!=n))return new M(n,o);if(M.DEBUG)throw Error(wt+"Not a"+(s?" base "+s:"")+" number: "+e);t.s=null}t.c=t.e=null},d.absoluteValue=d.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},d.comparedTo=function(t,e){return Et(this,new M(t,e))},d.decimalPlaces=d.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Pt(t,0,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t+n.e+1,e);if(!(i=n.c))return null;if(s=((o=i.length-1)-Tt(this.e/kt))*kt,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},d.dividedBy=d.div=function(t,e){return i(this,new M(t,e),_,m)},d.dividedToIntegerBy=d.idiv=function(t,e){return i(this,new M(t,e),0,1)},d.exponentiatedBy=d.pow=function(t,e){var i,s,o,n,r,a,l,c,h=this;if((t=new M(t)).c&&!t.isInteger())throw Error(wt+"Exponent not an integer: "+E(t));if(null!=e&&(e=new M(e)),r=t.e>14,!h.c||!h.c[0]||1==h.c[0]&&!h.e&&1==h.c.length||!t.c||!t.c[0])return c=new M(Math.pow(+E(h),r?t.s*(2-At(t)):+E(t))),e?c.mod(e):c;if(a=t.s<0,e){if(e.c?!e.c[0]:!e.s)return new M(NaN);(s=!a&&h.isInteger()&&e.isInteger())&&(h=h.mod(e))}else{if(t.e>9&&(h.e>0||h.e<-1||(0==h.e?h.c[0]>1||r&&h.c[1]>=24e7:h.c[0]<8e13||r&&h.c[0]<=9999975e7)))return n=h.s<0&&At(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/kt+2))}for(r?(i=new M(.5),a&&(t.s=1),l=At(t)):l=(o=Math.abs(+E(t)))%2,c=new M(u);;){if(l){if(!(c=c.times(h)).c)break;n?c.c.length>n&&(c.c.length=n):s&&(c=c.mod(e))}if(o){if(0===(o=yt(o/2)))break;l=o%2}else if(z(t=t.times(i),t.e+1,1),t.e>14)l=At(t);else{if(0===(o=+E(t)))break;l=o%2}h=h.times(h),n?h.c&&h.c.length>n&&(h.c.length=n):s&&(h=h.mod(e))}return s?c:(a&&(c=u.div(c)),e?c.mod(e):n?z(c,x,m,void 0):c)},d.integerValue=function(t){var e=new M(this);return null==t?t=m:Pt(t,0,8),z(e,e.e+1,t)},d.isEqualTo=d.eq=function(t,e){return 0===Et(this,new M(t,e))},d.isFinite=function(){return!!this.c},d.isGreaterThan=d.gt=function(t,e){return Et(this,new M(t,e))>0},d.isGreaterThanOrEqualTo=d.gte=function(t,e){return 1===(e=Et(this,new M(t,e)))||0===e},d.isInteger=function(){return!!this.c&&Tt(this.e/kt)>this.c.length-2},d.isLessThan=d.lt=function(t,e){return Et(this,new M(t,e))<0},d.isLessThanOrEqualTo=d.lte=function(t,e){return-1===(e=Et(this,new M(t,e)))||0===e},d.isNaN=function(){return!this.s},d.isNegative=function(){return this.s<0},d.isPositive=function(){return this.s>0},d.isZero=function(){return!!this.c&&0==this.c[0]},d.minus=function(t,e){var i,s,o,n,r=this,a=r.s;if(e=(t=new M(t,e)).s,!a||!e)return new M(NaN);if(a!=e)return t.s=-e,r.plus(t);var l=r.e/kt,c=t.e/kt,h=r.c,p=t.c;if(!l||!c){if(!h||!p)return h?(t.s=-e,t):new M(p?r:NaN);if(!h[0]||!p[0])return p[0]?(t.s=-e,t):new M(h[0]?r:3==m?-0:0)}if(l=Tt(l),c=Tt(c),h=h.slice(),a=l-c){for((n=a<0)?(a=-a,o=h):(c=l,o=p),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=p.length))?a:e,a=e=0;e0)for(;e--;h[i++]=0);for(e=$t-1;s>a;){if(h[--s]=0;){for(i=0,d=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=d*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*d)%g*g+_[n]+i)/m|0)+(a/g|0)+u*h,_[n--]=c%m;_[n]=i}return i?++s:_.splice(0,1),T(t,_,s)},d.negated=function(){var t=new M(this);return t.s=-t.s||null,t},d.plus=function(t,e){var i,s=this,o=s.s;if(e=(t=new M(t,e)).s,!o||!e)return new M(NaN);if(o!=e)return t.s=-e,s.minus(t);var n=s.e/kt,r=t.e/kt,a=s.c,l=t.c;if(!n||!r){if(!a||!l)return new M(o/0);if(!a[0]||!l[0])return l[0]?t:new M(a[0]?s:0*o)}if(n=Tt(n),r=Tt(r),a=a.slice(),o=n-r){for(o>0?(r=n,i=l):(o=-o,i=a),i.reverse();o--;i.push(0));i.reverse()}for((o=a.length)-(e=l.length)<0&&(i=l,l=a,a=i,e=o),o=0;e;)o=(a[--e]=a[e]+l[e]+o)/$t|0,a[e]=$t===a[e]?0:a[e]%$t;return o&&(a=[o].concat(a),++r),T(t,a,r)},d.precision=d.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Pt(t,1,Ct),null==e?e=m:Pt(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*kt+1,o=i[o]){for(;o%10==0;o/=10,s--);for(o=i[0];o>=10;o/=10,s++);}return t&&n.e+1>s&&(s=n.e+1),s},d.shiftedBy=function(t){return Pt(t,-9007199254740991,St),this.times("1e"+t)},d.squareRoot=d.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,p=new M("0.5");if(1!==l||!a||!a[0])return new M(!l||l<0&&(!a||a[0])?NaN:a?r:1/0);if(0==(l=Math.sqrt(+E(r)))||l==1/0?(((e=zt(a)).length+c)%2==0&&(e+="0"),l=Math.sqrt(+e),c=Tt((c+1)/2)-(c<0||c%2),s=new M(e=l==1/0?"5e"+c:(e=l.toExponential()).slice(0,e.indexOf("e")+1)+c)):s=new M(l+""),s.c[0])for((l=(c=s.e)+h)<3&&(l=0);;)if(n=s,s=p.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e0&&_>0){for(n=_%a||a,h=u.substr(0,n);n<_;n+=a)h+=c+u.substr(n,a);l>0&&(h+=c+u.slice(n)),d&&(h="-"+h)}s=p?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?p.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):p):h}return(i.prefix||"")+s+(i.suffix||"")},d.toFraction=function(t){var e,s,o,n,r,a,l,c,h,p,d,_,g=this,f=g.c;if(null!=t&&(!(l=new M(t)).isInteger()&&(l.c||1!==l.s)||l.lt(u)))throw Error(wt+"Argument "+(l.isInteger()?"out of range: ":"not an integer: ")+E(l));if(!f)return new M(g);for(e=new M(u),h=s=new M(u),o=c=new M(u),_=zt(f),r=e.e=_.length-g.e-1,e.c[0]=Mt[(a=r%kt)<0?kt+a:a],t=!t||l.comparedTo(e)>0?r>0?e:h:l,a=v,v=1/0,l=new M(_),c.c[0]=0;p=i(l,e,0,1),1!=(n=s.plus(p.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(p.times(n=h)),c=n,e=l.minus(p.times(n=e)),l=n;return n=i(t.minus(s),o,0,1),c=c.plus(n.times(h)),s=s.plus(n.times(o)),c.s=h.s=g.s,d=i(h,o,r*=2,m).minus(g).abs().comparedTo(i(c,s,r,m).minus(g).abs())<1?[h,o]:[c,s],v=a,d},d.toNumber=function(){return+E(this)},d.toPrecision=function(t,e){return null!=t&&Pt(t,1,Ct),D(this,t,e,2)},d.toString=function(t){var e,i=this,o=i.s,n=i.e;return null===n?o?(e="Infinity",o<0&&(e="-"+e)):e="NaN":(null==t?e=n<=g||n>=f?Rt(zt(i.c),n):Nt(zt(i.c),n,"0"):10===t&&S?e=Nt(zt((i=z(new M(i),_+n+1,m)).c),i.e,"0"):(Pt(t,2,k.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},d.valueOf=d.toJSON=function(){return E(this)},d._isBigNumber=!0,d[Symbol.toStringTag]="BigNumber",d[Symbol.for("nodejs.util.inspect.custom")]=d.valueOf,null!=e&&M.set(e),M}(),Ot=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends Ot{constructor(t){super(t)}},qt=class{size=0;modificationCount=0;splayCount=0;splay(t){const e=this.root;if(null==e)return this.compare(t,t),-1;let i=null,s=null,o=null,n=null,r=e;const a=this.compare;let l;for(;;)if(l=a(r.key,t),l>0){let e=r.left;if(null==e)break;if(l=a(e.key,t),l>0&&(r.left=e.right,e.right=r,r=e,e=r.left,null==e))break;null==i?s=r:i.left=r,i=r,r=e}else{if(!(l<0))break;{let e=r.right;if(null==e)break;if(l=a(e.key,t),l<0&&(r.right=e.left,e.left=r,r=e,e=r.right,null==e))break;null==o?n=r:o.right=r,o=r,r=e}}return null!=o&&(o.right=r.left,r.left=n),null!=i&&(i.left=r.right,r.right=s),this.root!==r&&(this.root=r,this.splayCount++),l}splayMin(t){let e=t,i=e.left;for(;null!=i;){const t=i;e.left=t.right,t.right=e,e=t,i=e.left}return e}splayMax(t){let e=t,i=e.right;for(;null!=i;){const t=i;e.right=t.left,t.left=e,e=t,i=e.right}return e}_delete(t){if(null==this.root)return null;if(0!=this.splay(t))return null;let e=this.root;const i=e,s=e.left;if(this.size--,null==s)this.root=e.right;else{const t=e.right;e=this.splayMax(s),e.right=t,this.root=e}return this.modificationCount++,i}addNewRoot(t,e){this.size++,this.modificationCount++;const i=this.root;null!=i?(e<0?(t.left=i,t.right=i.right,i.right=null):(t.right=i,t.left=i.left,i.left=null),this.root=t):this.root=t}_first(){const t=this.root;return null==t?null:(this.root=this.splayMin(t),this.root)}_last(){const t=this.root;return null==t?null:(this.root=this.splayMax(t),this.root)}clear(){this.root=null,this.size=0,this.modificationCount++}has(t){return this.validKey(t)&&0==this.splay(t)}defaultCompare(){return(t,e)=>te?1:0}wrap(){return{getRoot:()=>this.root,setRoot:t=>{this.root=t},getSize:()=>this.size,getModificationCount:()=>this.modificationCount,getSplayCount:()=>this.splayCount,setSplayCount:t=>{this.splayCount=t},splay:t=>this.splay(t),has:t=>this.has(t)}}},Ft=class t extends qt{root=null;compare;validKey;constructor(t,e){super(),this.compare=t??this.defaultCompare(),this.validKey=e??(t=>null!=t&&null!=t)}delete(t){return!!this.validKey(t)&&null!=this._delete(t)}deleteAll(t){for(const e of t)this.delete(e)}forEach(t){const e=this[Symbol.iterator]();let i;for(;i=e.next(),!i.done;)t(i.value,i.value,this)}add(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this}addAndReturn(t){const e=this.splay(t);return 0!=e&&this.addNewRoot(new Lt(t),e),this.root.key}addAll(t){for(const e of t)this.add(e)}isEmpty(){return null==this.root}isNotEmpty(){return null!=this.root}single(){if(0==this.size)throw"Bad state: No element";if(this.size>1)throw"Bad state: Too many element";return this.root.key}first(){if(0==this.size)throw"Bad state: No element";return this._first().key}last(){if(0==this.size)throw"Bad state: No element";return this._last().key}lastBefore(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)<0)return this.root.key;let e=this.root.left;if(null==e)return null;let i=e.right;for(;null!=i;)e=i,i=e.right;return e.key}firstAfter(t){if(null==t)throw"Invalid arguments(s)";if(null==this.root)return null;if(this.splay(t)>0)return this.root.key;let e=this.root.right;if(null==e)return null;let i=e.left;for(;null!=i;)e=i,i=e.left;return e.key}retainAll(e){const i=new t(this.compare,this.validKey),s=this.modificationCount;for(const t of e){if(s!=this.modificationCount)throw"Concurrent modification during iteration.";this.validKey(t)&&0==this.splay(t)&&i.add(this.root.key)}i.size!=this.size&&(this.root=i.root,this.size=i.size,this.modificationCount++)}lookup(t){if(!this.validKey(t))return null;return 0!=this.splay(t)?null:this.root.key}intersection(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)&&i.add(t);return i}difference(e){const i=new t(this.compare,this.validKey);for(const t of this)e.has(t)||i.add(t);return i}union(t){const e=this.clone();return e.addAll(t),e}clone(){const e=new t(this.compare,this.validKey);return e.size=this.size,e.root=this.copyNode(this.root),e}copyNode(t){if(null==t)return null;const e=new Lt(t.key);return function t(e,i){let s,o;do{if(s=e.left,o=e.right,null!=s){const e=new Lt(s.key);i.left=e,t(s,e)}if(null!=o){const t=new Lt(o.key);i.right=t,e=o,i=t}}while(null!=o)}(t,e),e}toSet(){return this.clone()}entries(){return new Bt(this.wrap())}keys(){return this[Symbol.iterator]()}values(){return this[Symbol.iterator]()}[Symbol.iterator](){return new Ut(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ht=class{tree;path=new Array;modificationCount=null;splayCount;constructor(t){this.tree=t,this.splayCount=t.getSplayCount()}[Symbol.iterator](){return this}next(){return this.moveNext()?{done:!1,value:this.current()}:{done:!0,value:null}}current(){if(!this.path.length)return null;const t=this.path[this.path.length-1];return this.getValue(t)}rebuildPath(t){this.path.splice(0,this.path.length),this.tree.splay(t),this.path.push(this.tree.getRoot()),this.splayCount=this.tree.getSplayCount()}findLeftMostDescendent(t){for(;null!=t;)this.path.push(t),t=t.left}moveNext(){if(this.modificationCount!=this.tree.getModificationCount()){if(null==this.modificationCount){this.modificationCount=this.tree.getModificationCount();let t=this.tree.getRoot();for(;null!=t;)this.path.push(t),t=t.left;return this.path.length>0}throw"Concurrent modification during iteration."}if(!this.path.length)return!1;this.splayCount!=this.tree.getSplayCount()&&this.rebuildPath(this.path[this.path.length-1].key);let t=this.path[this.path.length-1],e=t.right;if(null!=e){for(;null!=e;)this.path.push(e),e=e.left;return!0}for(this.path.pop();this.path.length&&this.path[this.path.length-1].right===t;)t=this.path.pop();return this.path.length>0}},Ut=class extends Ht{getValue(t){return t.key}},Bt=class extends Ht{getValue(t){return[t.key,t.key]}},jt=t=>()=>t,Wt=t=>{const e=t?(e,i)=>i.minus(e).abs().isLessThanOrEqualTo(t):jt(!1);return(t,i)=>e(t,i)?0:t.comparedTo(i)};function Gt(t){const e=t?(e,i,s,o,n)=>e.exponentiatedBy(2).isLessThanOrEqualTo(o.minus(i).exponentiatedBy(2).plus(n.minus(s).exponentiatedBy(2)).times(t)):jt(!1);return(t,i,s)=>{const o=t.x,n=t.y,r=s.x,a=s.y,l=n.minus(a).times(i.x.minus(r)).minus(o.minus(r).times(i.y.minus(a)));return e(l,o,n,r,a)?0:l.comparedTo(0)}}var Vt=t=>t,Kt=t=>{if(t){const e=new Ft(Wt(t)),i=new Ft(Wt(t)),s=(t,e)=>e.addAndReturn(t),o=t=>({x:s(t.x,e),y:s(t.y,i)});return o({x:new It(0),y:new It(0)}),o}return Vt},Zt=t=>({set:t=>{Yt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Yt=Zt(),Jt=(t,e)=>t.ll.x.isLessThanOrEqualTo(e.x)&&e.x.isLessThanOrEqualTo(t.ur.x)&&t.ll.y.isLessThanOrEqualTo(e.y)&&e.y.isLessThanOrEqualTo(t.ur.y),Xt=(t,e)=>{if(e.ur.x.isLessThan(t.ll.x)||t.ur.x.isLessThan(e.ll.x)||e.ur.y.isLessThan(t.ll.y)||t.ur.y.isLessThan(e.ll.y))return null;const i=t.ll.x.isLessThan(e.ll.x)?e.ll.x:t.ll.x,s=t.ur.x.isLessThan(e.ur.x)?t.ur.x:e.ur.x;return{ll:{x:i,y:t.ll.y.isLessThan(e.ll.y)?e.ll.y:t.ll.y},ur:{x:s,y:t.ur.y.isLessThan(e.ur.y)?t.ur.y:e.ur.y}}},Qt=(t,e)=>t.x.times(e.y).minus(t.y.times(e.x)),te=(t,e)=>t.x.times(e.x).plus(t.y.times(e.y)),ee=t=>te(t,t).sqrt(),ie=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return Qt(o,s).div(ee(o)).div(ee(s))},se=(t,e,i)=>{const s={x:e.x.minus(t.x),y:e.y.minus(t.y)},o={x:i.x.minus(t.x),y:i.y.minus(t.y)};return te(o,s).div(ee(o)).div(ee(s))},oe=(t,e,i)=>e.y.isZero()?null:{x:t.x.plus(e.x.div(e.y).times(i.minus(t.y))),y:i},ne=(t,e,i)=>e.x.isZero()?null:{x:i,y:t.y.plus(e.y.div(e.x).times(i.minus(t.x)))},re=class t{point;isLeft;segment;otherSE;consumedBy;static compare(e,i){const s=t.comparePoints(e.point,i.point);return 0!==s?s:(e.point!==i.point&&e.link(i),e.isLeft!==i.isLeft?e.isLeft?1:-1:_e.compare(e.segment,i.segment))}static comparePoints(t,e){return t.x.isLessThan(e.x)?-1:t.x.isGreaterThan(e.x)?1:t.y.isLessThan(e.y)?-1:t.y.isGreaterThan(e.y)?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,i=e.length;t{const s=i.otherSE;e.set(i,{sine:ie(this.point,t.point,s.point),cosine:se(this.point,t.point,s.point)})};return(t,s)=>{e.has(t)||i(t),e.has(s)||i(s);const{sine:o,cosine:n}=e.get(t),{sine:r,cosine:a}=e.get(s);return o.isGreaterThanOrEqualTo(0)&&r.isGreaterThanOrEqualTo(0)?n.isLessThan(a)?1:n.isGreaterThan(a)?-1:0:o.isLessThan(0)&&r.isLessThan(0)?n.isLessThan(a)?-1:n.isGreaterThan(a)?1:0:r.isLessThan(o)?-1:r.isGreaterThan(o)?1:0}}},ae=class t{events;poly;_isExteriorRing;_enclosingRing;static factory(e){const i=[];for(let s=0,o=e.length;s0&&(t=i)}let e=t.segment.prevInResult(),i=e?e.prevInResult():null;for(;;){if(!e)return null;if(!i)return e.ringOut;if(i.ringOut!==e.ringOut)return i.ringOut?.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut?.enclosingRing();e=i.prevInResult(),i=e?e.prevInResult():null}}},le=class{exteriorRing;interiorRings;constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=this.exteriorRing.getGeom();if(null===t)return null;const e=[t];for(let t=0,i=this.interiorRings.length;t0?(this.tree.delete(e),i.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&o){const t=s.getIntersection(o);if(null!==t){if(!s.isAnEndpoint(t)){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t0)return-1;const s=e.comparePoint(t.rightSE.point);return 0!==s?s:-1}if(i.isGreaterThan(s)){if(r.isLessThan(a)&&r.isLessThan(c))return-1;if(r.isGreaterThan(a)&&r.isGreaterThan(c))return 1;const i=e.comparePoint(t.leftSE.point);if(0!==i)return i;const s=t.comparePoint(e.rightSE.point);return s<0?1:s>0?-1:1}if(r.isLessThan(a))return-1;if(r.isGreaterThan(a))return 1;if(o.isLessThan(n)){const i=e.comparePoint(t.rightSE.point);if(0!==i)return i}if(o.isGreaterThan(n)){const i=t.comparePoint(e.rightSE.point);if(i<0)return 1;if(i>0)return-1}if(!o.eq(n)){const t=l.minus(r),e=o.minus(i),h=c.minus(a),p=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(p))return 1;if(t.isLessThan(e)&&h.isGreaterThan(p))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.ide.id?1:0}constructor(t,e,i,s){this.id=++ue,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=i,this.windings=s}static fromRing(e,i,s){let o,n,r;const a=re.comparePoints(e,i);if(a<0)o=e,n=i,r=1;else{if(!(a>0))throw new Error(`Tried to create degenerate segment at [${e.x}, ${e.y}]`);o=i,n=e,r=-1}const l=new re(o,!0),c=new re(n,!1);return new t(l,c,[s],[r])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:t.isLessThan(e)?t:e},ur:{x:this.rightSE.point.x,y:t.isGreaterThan(e)?t:e}}}vector(){return{x:this.rightSE.point.x.minus(this.leftSE.point.x),y:this.rightSE.point.y.minus(this.leftSE.point.y)}}isAnEndpoint(t){return t.x.eq(this.leftSE.point.x)&&t.y.eq(this.leftSE.point.y)||t.x.eq(this.rightSE.point.x)&&t.y.eq(this.rightSE.point.y)}comparePoint(t){return Yt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Xt(e,i);if(null===s)return null;const o=this.leftSE.point,n=this.rightSE.point,r=t.leftSE.point,a=t.rightSE.point,l=Jt(e,r)&&0===this.comparePoint(r),c=Jt(i,o)&&0===t.comparePoint(o),h=Jt(e,a)&&0===this.comparePoint(a),p=Jt(i,n)&&0===t.comparePoint(n);if(c&&l)return p&&!h?n:!p&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return p&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(p&&h)return null;if(p)return n;if(h)return a;const d=((t,e,i,s)=>{if(e.x.isZero())return ne(i,s,t.x);if(s.x.isZero())return ne(t,e,i.x);if(e.y.isZero())return oe(i,s,t.y);if(s.y.isZero())return oe(t,e,i.y);const o=Qt(e,s);if(o.isZero())return null;const n={x:i.x.minus(t.x),y:i.y.minus(t.y)},r=Qt(n,e).div(o),a=Qt(n,s).div(o),l=t.x.plus(a.times(e.x)),c=i.x.plus(r.times(s.x)),h=t.y.plus(a.times(e.y)),p=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(p).div(2)}})(o,this.vector(),r,t.vector());return null===d?null:Jt(s,d)?Yt.snap(d):null}split(e){const i=[],s=void 0!==e.events,o=new re(e,!0),n=new re(e,!1),r=this.rightSE;this.replaceRightSE(n),i.push(n),i.push(o);const a=new t(o,r,this.rings.slice(),this.windings.slice());return re.comparePoints(a.leftSE.point,a.rightSE.point)>0&&a.swapEvents(),re.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),s&&(o.checkForConsuming(),n.checkForConsuming()),i}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=i;i=s,s=t}if(i.prev===s){const t=i;i=s,s=t}for(let t=0,e=s.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=i(t)!==i(e);break}}return this._isInResult}},me=class{poly;isExterior;segments;bbox;constructor(t,e,i){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=i,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const s=Yt.snap({x:new It(t[0][0]),y:new It(t[0][1])});this.bbox={ll:{x:s.x,y:s.y},ur:{x:s.x,y:s.y}};let o=s;for(let e=1,i=t.length;e=3?t.poly:t&&null!=t.x&&null!=t.y&&null!=t.w&&null!=t.h?[[t.x,t.y],[t.x+t.w,t.y],[t.x+t.w,t.y+t.h],[t.x,t.y+t.h]]:null}function xe(t){const e=[],i=new Set;for(const s of t||[]){const t=we(s);if(t)for(let s=0;s=90?t-=180:t<-90&&(t+=180),s={x:d[0],y:d[1],angle:t}}}return s}function ke(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])t[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}function De(t,e,i){const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;let r=n?((t[0]-e[0])*s+(t[1]-e[1])*o)/n:0;return r=Math.max(0,Math.min(1,r)),Math.hypot(t[0]-(e[0]+r*s),t[1]-(e[1]+r*o))}function Ce(t,e){if(!e||e.length<2)return null;let i=null,s=1/0;for(let o=0;oo&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Ae(t,e,i){let s=!0;for(const o of t){if(ze(o,e,i))return!0;Te(o,e,i)||(s=!1)}if(s){const s=[t.reduce((t,e)=>t+e[0],0)/t.length,t.reduce((t,e)=>t+e[1],0)/t.length];return ze(s,e,i)}return!1}function Re(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;it+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length];return ze(s,t,i)&&Ne(e)[t[0],t[1]]),[t[0][0],t[0][1]]]]}function Oe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>de.run("union",t,e))(Ie(t),Ie(e));if(1!==i.length)return null;if(1!==i[0].length)return null;const s=i[0][0].slice(0,-1).map(t=>[t[0],t[1]]);return s.length>=3?s:null}function Le(t,e,i){for(let s=0;s1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function Fe(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function He(t,e){if(e>t[2]/t[3]){const i=t[3],s=t[3]*e;return{x:t[0]-(s-t[2])/2,y:t[1],w:s,h:i}}const i=t[2],s=t[2]/e;return{x:t[0],y:t[1]-(s-t[3])/2,w:i,h:s}}function Ue(t,e,i,s){if(t.length<2)return;const o=e.x+s,n=e.x+e.w-s,r=e.y+s,a=e.y+e.h-s;for(let e=0;e<60;e++){let e=!1;for(let s=0;s0?Math.min(3,Math.max(.5,e.card_font_scale)):1,labelTemp:!0===e.label_temp,labelHum:!0===e.label_hum,labelLqi:!0===e.label_lqi,labelLight:!0===e.label_light}}const Ze={light_on:{c:"#ffd45c",a:.18},light_off:{c:"#9aa0a6",a:.14},light_none:{c:"#6b7480",a:0},temp_cold:{c:"#4fc3f7",a:.18},temp_ok:{c:"#66d17a",a:.18},temp_hot:{c:"#ffd45c",a:.18},lqi_low:{c:"#f25a4a",a:.18},lqi_high:{c:"#4bd28f",a:.18},glow_base:{c:"#0d1b2a",a:.5},glow_light:{c:"#ffd9a0",a:.85}},Ye=/^#[0-9a-f]{6}$/i;function Je(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(Ze)){const s=Ze[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&Ye.test(o.c)?o.c:s.c,a:o&&"number"==typeof o.a?Math.min(1,Math.max(0,o.a)):s.a}}return e}function Xe(t,e,i){const s=Math.min(1,Math.max(0,i)),o=[1,3,5].map(e=>parseInt(t.slice(e,e+2),16)),n=[1,3,5].map(t=>parseInt(e.slice(t,t+2),16)),r=o.map((t,e)=>Math.round(t+(n[e]-t)*s));return"#"+r.map(t=>t.toString(16).padStart(2,"0")).join("")}function Qe(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:Xe(r.lqi_low.c,r.lqi_high.c,t),a:r.lqi_low.a+(r.lqi_high.a-r.lqi_low.a)*Math.min(1,Math.max(0,t))}}if("light"===t)return"none"===i?r.light_none.a>0?r.light_none:null:"on"===i?r.light_on:r.light_off;if("temp"===t){if(null==s)return null;const t=Math.min(o,n),e=Math.max(o,n);return se?r.temp_hot:r.temp_ok}return null}function ti(t){if(!t||"on"!==t.state)return null;const e=t.attributes?.rgb_color;return Array.isArray(e)&&e.length>=3&&e.every(t=>Number.isFinite(t))?`rgb(${e[0]}, ${e[1]}, ${e[2]})`:null}function ei(t,e){if(!t||"on"!==t.state)return null;const i=t.attributes||{},s=Number(i.brightness),o=Number.isFinite(s)&&s>0?Math.max(.15,Math.min(1,s/255)):1,n=i.rgb_color;if(Array.isArray(n)&&n.length>=3&&n.every(t=>Number.isFinite(t)))return{c:`rgb(${n[0]}, ${n[1]}, ${n[2]})`,bri:o};const r=Number(i.color_temp_kelvin)||(Number(i.color_temp)>0?1e6/Number(i.color_temp):NaN);if(Number.isFinite(r)&&r>0){const[t,e,i]=function(t){const e=Math.min(4e4,Math.max(1e3,t))/100,i=e<=66?255:329.698727446*Math.pow(e-60,-.1332047592),s=e<=66?99.4708025861*Math.log(e)-161.1195681661:288.1221695283*Math.pow(e-60,-.0755148492),o=e>=66?255:e<=19?0:138.5177312231*Math.log(e-10)-305.0447927307,n=t=>Math.round(Math.min(255,Math.max(0,t)));return[n(i),n(s),n(o)]}(r);return{c:`rgb(${t}, ${e}, ${i})`,bri:o}}return{c:e,bri:o}}function ii(t,e,i,s,o=170){const n=Math.hypot(e[0]-t[0],e[1]-t[1]),r=Math.hypot(i[0]-t[0],i[1]-t[1]);if(n<1e-6||r<1e-6||Math.min(n,r)>=s)return null;let a=Math.atan2(e[1]-t[1],e[0]-t[0]),l=Math.atan2(i[1]-t[1],i[0]-t[0])-a;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;const c=o*Math.PI/180;if(Math.abs(l)>c){const t=a+l/2;l=c*Math.sign(l),a=t-l/2}const h=[[t[0],t[1]]];for(let e=0;e<=8;e++){const i=a+l*e/8;h.push([t[0]+Math.cos(i)*s,t[1]+Math.sin(i)*s])}return h}function si(t,e,i,s,o){const n=e*Math.PI/180,r=[-Math.sin(n),Math.cos(n)],a=(i[0]-t[0])*r[0]+(i[1]-t[1])*r[1]>0?-1:1,l=[t[0]+r[0]*o*a,t[1]+r[1]*o*a];return s.some(t=>ze(l,t,1e-9))}function oi(t){return t.startsWith("light.")||t.startsWith("switch.")}function ni(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;od||l>d)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*p,_=(r[0]-n[0])*h+(r[1]-n[1])*p,m=Math.max(0,Math.min(u,_)),g=Math.min(c,Math.max(u,_));g-m>i&&s.push([n[0]+h*m,n[1]+p*m,n[0]+h*g,n[1]+p*g])}}return s}function ri(t,e){const i=new Set([t]),s=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);let o=!0;for(;o;){o=!1;for(const t of e)if(t.id&&!i.has(t.id))for(const n of e)if(n.id&&i.has(n.id)&&s(t,n)){i.add(t.id),o=!0;break}}return i}function ai(t,e,i=1e-6){const s=[];for(const o of t){const t=[o[0],o[1]],n=[o[2],o[3]],r=n[0]-t[0],a=n[1]-t[1],l=Math.hypot(r,a);if(ln||o>n)continue;const r=(s[0]-t[0])*c+(s[1]-t[1])*h,a=(s[2]-t[0])*c+(s[3]-t[1])*h,d=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-d>i&&p.push([d,u])}if(!p.length){s.push([t[0],t[1],n[0],n[1]]);continue}p.sort((t,e)=>t[0]-e[0]);let d=0;for(const[e,o]of p)e-d>i&&s.push([t[0]+c*d,t[1]+h*d,t[0]+c*e,t[1]+h*e]),d=Math.max(d,o);l-d>i&&s.push([t[0]+c*d,t[1]+h*d,n[0],n[1]])}return s}function li(t){return t?t.startsWith("/houseplan_files/plans/")?"/api/houseplan/content/plans/_/"+t.slice(23):t.startsWith("/houseplan_files/files/")?"/api/houseplan/content/files/"+t.slice(23):t:""}function ci(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function hi(t,e){const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!o)return Math.hypot(t[0]-e[0],t[1]-e[1]);let n=((t[0]-e[0])*i+(t[1]-e[1])*s)/o;return n=Math.max(0,Math.min(1,n)),Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))}const pi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);function di(t,e,i){if(e.identifiers?.[0]?.[0])return e.identifiers[0][0];for(const e of i){const i=t.entities[e]?.platform;if(i)return i}return""}function ui(t,e){if(/_device_temperature$/.test(e))return!1;if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_temperature$/.test(e);const s=i.attributes||{};return"temperature"===s.device_class||/°C|°F/.test(s.unit_of_measurement||"")||/_temperature$/.test(e)}function _i(t,e,i){const s=e.map(e=>({eid:e,reg:t.entities[e],st:t.states[e]})).filter(t=>t.reg),o=[s.filter(t=>!t.reg.hidden&&!t.reg.entity_category),s.filter(t=>!t.reg.hidden),s.filter(t=>!t.reg.entity_category),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>ui(t,e.eid));if(i)return i.eid}for(const t of ft)for(const e of o){const i=e.find(e=>e.eid.split(".")[0]===t);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function mi(t,e){const i=[];for(const s of e){const e=t.states[s];if(!e)continue;const o=(e.attributes?.unit_of_measurement||"").toLowerCase();if(/_(linkquality|lqi)$/.test(s)||"lqi"===o){const t=parseFloat(e.state);isNaN(t)||i.push(t);continue}const n=e.attributes?.linkquality??e.attributes?.lqi;if(null!=n){const t=parseFloat(n);isNaN(t)||i.push(t)}}return Fe(i)}function gi(t,e){for(const i of e){if(!ui(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(10*s)/10}return null}function fi(t,e){if(t.entities?.[e]?.entity_category)return!1;const i=t.states[e];if(!i)return/_humidity$/.test(e);const s=i.attributes||{};return"humidity"===s.device_class||"%"===s.unit_of_measurement&&/_humidity$/.test(e)||/_humidity$/.test(e)}function bi(t,e){for(const i of e){if(!fi(t,i))continue;const e=t.states[i];if(!e)continue;const s=parseFloat(e.state);if(!isNaN(s))return Math.round(s)}return null}function vi(t,e,i,s,o){const n=gt(e,i,o);if(n!==mt)return n;const r=[];for(const e of s){const i=t.states[e]?.attributes?.device_class;i&&r.push(i)}return function(t){for(const e of t){const t=_t[e];if(t)return t}return null}(r)??mt}function yi(t,e){t.marker=e,e.name&&(t.name=e.name),e.icon&&(t.icon=e.icon),null!=e.model&&(t.model=e.model),t.link=e.link??null,t.description=e.description??null,t.pdfs=e.pdfs||[],t.tapAction=e.tap_action??null}function wi(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,showAll:r,firstSpaceId:a,loc:l,iconRules:c}=t,h=!1!==o.group_lights,p=function(t,e){if(!e)return[];const i=[];for(const[e,s]of Object.entries(t.entities)){if(!e.startsWith("light.")||s.hidden)continue;let o=null;if("group"===s.platform)o=s.area_id||null;else{if(!s.device_id)continue;{const e=t.devices[s.device_id];if("Group"!==e?.model)continue;o=e.area_id||s.area_id||null}}if(!o)continue;const n=t.states[e];i.push({eid:e,name:s.name||n?.attributes?.friendly_name||e,area:o})}return i}(e,h),d=new Set(p.map(t=>t.area)),u=function(t){const e={};for(const[i,s]of Object.entries(t.entities))s?.device_id&&(e[s.device_id]=e[s.device_id]||[]).push(i);return e}(e),_=new Set;for(const t of s){const[e,i]=t.binding.split(":");"device"!==e&&"entity"!==e||!i||_.add(t.binding)}const m=(t,e)=>s.find(i=>i.binding===t+":"+e),g={},f=[];for(const t of Object.values(e.devices)){const s=t.area_id;if(!s||!i[s])continue;if("service"===t.entry_type)continue;if(_.has("device:"+t.id))continue;const o=m("device",t.id);if(o&&o.hidden)continue;const a=u[t.id]||[],p=di(e,t,a);if(!r){if(n.has(p))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===p&&t.via_device_id)continue}const b=(t.name_by_user||t.name||l("device.unnamed")).trim(),v=b+"|"+s;let y=vi(e,b,t.model,a,c);if(a.some(t=>t.startsWith("lock."))&&(y="mdi:lock"),!r&&h&&"mdi:lightbulb"===y&&d.has(s))continue;g[v]=(g[v]||0)+1;const w=g[v]>1?b+" "+g[v]:b,x={id:t.id,name:w,model:t.model||"",area:s,space:i[s],icon:y,entities:a,bindingKind:"device",bindingRef:t.id,pdfs:[]};x.primary=_i(e,a,y),"mdi:thermometer"!==y&&"mdi:air-filter"!==y||(x.temp=gi(e,a)),x.primary&&fi(e,x.primary)&&(x.hum=bi(e,a)),f.push(x)}for(const t of p)i[t.area]&&(_.has("entity:"+t.eid)||f.push({id:"lg_"+t.eid,name:t.name,model:l("device.light_group"),area:t.area,space:i[t.area],icon:"mdi:lightbulb-group",entities:[t.eid],primary:t.eid,bindingKind:"entity",bindingRef:t.eid,pdfs:[]}));for(const t of s){if(t.hidden)continue;const[s,o]=t.binding.split(":");if("device"===s){const s=e.devices[o],n=t.area||s?.area_id||"",r=n&&i[n]||t.space||a,h=s&&u[s.id]||[];let p=s?vi(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(p="mdi:lock");const d={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:n,space:r,icon:p,entities:h,bindingKind:"device",bindingRef:o};d.primary=_i(e,h,p),"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),d.primary&&fi(e,d.primary)&&(d.hum=bi(e,h)),yi(d,t),f.push(d)}else if("entity"===s){const s=e.entities[o],n=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=n&&i[n]||t.space||a,l=e.states[o],h=s?.name||l?.attributes?.friendly_name||o;let p=vi(e,h,"",[o],c);o.startsWith("lock.")&&(p="mdi:lock");const d={id:t.id,name:h,model:"",area:n,space:r,icon:p,entities:[o],primary:o,bindingKind:"entity",bindingRef:o};"mdi:thermometer"!==p&&"mdi:air-filter"!==p||(d.temp=gi(e,[o])),fi(e,o)&&(d.hum=bi(e,[o])),yi(d,t),f.push(d)}else{const e=t.area||"",s=t.space||e&&i[e]||a,o={id:t.id,name:t.name||l("device.virtual"),model:t.model||"",area:e,space:s,icon:t.icon||"mdi:map-marker",entities:[],bindingKind:"virtual",virtual:!0};yi(o,t),f.push(o)}}return f}function xi(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function $i(t,e,i){if(!e)return null;const s=e.indexOf(":");if(s<0)return null;const o=e.slice(0,s),n=e.slice(s+1);if(!n)return null;if("entity"===o){const e=parseFloat(t.states[n]?.state);return Number.isFinite(e)?"temp"===i?Math.round(10*e)/10:Math.round(e):null}if("device"===o){const e=Object.entries(t.entities).filter(([,t])=>t.device_id===n).map(([t])=>t);return"temp"===i?gi(t,e):bi(t,e)}return null}function ki(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=gi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}var Si={"card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.remove":"Remove","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","btn.no_area":"No area","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_reset":"Reset zoom","title.add_device":"Add a device to the plan","title.show_all":"Show all area devices (no curation)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Add a room: connect grid dots with lines until the outline closes","title.markup_merge":"Merge rooms: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete a room: click inside the room","title.no_area_room":"Decorative room without an HA area (e.g. a hallway)","title.choose_area":"Select a Home Assistant area","title.need_plan":"Upload a floor-plan image","markup.add":"Add","markup.merge":"Merge","markup.split":"Split","markup.opening":"Opening","title.markup_opening":"Doors & windows: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Door / window","opening.door":"Door","opening.window":"Window","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one","markup.hint_start":"click a grid dot to start the outline","tip.room":"room — open the area","tip.lqi":"average zigbee signal:","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon badge","display.ripple":"Ripple only","display.icon_ripple":"Icon + ripple","marker.ripple_size":"Ripple size","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.remove_marker":'Remove "{name}" from the plan?',"confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device removed from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle (lights/switches)","marker.tap_label":"Tap action for this device","tap.toggle_note":"Toggle never applies to locks and alarms; hold the icon to open the info card.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","editor.aspect_ratio":"Aspect ratio (e.g. 16:9 or auto)","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value instead of an icon","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Show all","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.color":"Color","decor.width":"Line width","decor.w_thin":"Thin","decor.w_mid":"Medium","decor.w_thick":"Thick","decor.fill":"Fill","decor.text_title":"Text label","decor.text_label":"Text","decor.text_size":"Size","decor.size_s":"Small","decor.size_m":"Medium","decor.size_l":"Large","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors and windows, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","fill.glow":"Light sources (dark house, glowing lamps)","gs.glow_group":"Light-sources fill","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls light sources","marker.controls_hint":"With tap action “Toggle”, a click flips all bound lights at once (any on → all off). The icon mirrors their state.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius (light-sources fill)","marker.glow_radius_hint":"empty = default from general settings","markup.openwall":"Open boundary","title.markup_openwall":"Open boundary — click a wall shared by two rooms to make it virtual: light flows through, the line turns dashed. Click again to close it.","toast.openwall_pick":"Click a wall shared by two rooms","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}"};const Mi={en:Si,ru:{"card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.remove":"Убрать","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","btn.no_area":"Без зоны","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_reset":"Сбросить масштаб","title.add_device":"Добавить устройство на план","title.show_all":"Показывать все устройства зоны (без курирования)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Добавить комнату: соединяйте точки сетки линиями до замкнутого контура","title.markup_merge":"Объединить комнаты: клик по одной, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: клик внутри комнаты","title.no_area_room":"Декоративная комната без привязки к зоне (например, холл)","title.choose_area":"Выберите зону Home Assistant","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Добавить","markup.merge":"Объединить","markup.split":"Разделить","markup.opening":"Проём","title.markup_opening":"Двери и окна: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Дверь / окно","opening.door":"Дверь","opening.window":"Окно","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.room":"комната — открыть зону","tip.lqi":"средний сигнал zigbee:","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок","display.ripple":"Только пульсация","display.icon_ripple":"Значок + пульсация","marker.ripple_size":"Размер пульсации","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.remove_marker":"Убрать «{name}» с плана?","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство убрано с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить (свет/розетки)","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Toggle никогда не применяется к замкам и сигнализациям; долгое нажатие всегда открывает инфо-карточку.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","editor.aspect_ratio":"Соотношение сторон (напр. 16:9 или auto)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение вместо иконки","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Показать все","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.color":"Цвет","decor.width":"Толщина линии","decor.w_thin":"Тонкая","decor.w_mid":"Средняя","decor.w_thick":"Толстая","decor.fill":"Залить","decor.text_title":"Надпись","decor.text_label":"Текст","decor.text_size":"Размер","decor.size_s":"Мелкий","decor.size_m":"Средний","decor.size_l":"Крупный","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери и окна, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","fill.glow":"Свет по источникам (тёмный дом, пятна света)","gs.glow_group":"Заливка «Свет по источникам»","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все привязанные источники (горит хоть один → выключить все). Значок отражает их состояние.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения (заливка «Свет по источникам»)","marker.glow_radius_hint":"пусто = по умолчанию из общих настроек","markup.openwall":"Открытая граница","title.markup_openwall":"Открытая граница — клик по общей стене двух комнат делает её условной: свет проходит насквозь, линия становится пунктирной. Повторный клик закрывает.","toast.openwall_pick":"Кликните по стене, разделяющей две комнаты","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}"}};function Di(t,e){if(e&&e in Mi)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Ci(t,e,i){return function(t,e){if(!e)return t;let i=t;for(const[t,s]of Object.entries(e))i=i.split("{"+t+"}").join(String(s));return i}(Mi[t][e]??Si[e]??e,i)}class Ti extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:Ci(e,"editor.lang_auto")},{value:"en",label:Ci(e,"editor.lang_en")},{value:"ru",label:Ci(e,"editor.lang_ru")}]}}},{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 G;this._loadSpaces();const t=this._lang,e={title:Ci(t,"editor.title"),default_floor:Ci(t,"editor.default_floor"),language:Ci(t,"editor.language"),icon_size:Ci(t,"editor.icon_size"),show_temperature:Ci(t,"editor.show_temperature"),live_states:Ci(t,"editor.live_states"),show_signal:Ci(t,"editor.show_signal"),kiosk:Ci(t,"editor.kiosk"),cycle:Ci(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ci.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ci);const Ti=n` + >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ti.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",Ti);const zi=n` :host { --hp-bg: var(--card-background-color, #16212e); --hp-line: var(--divider-color, #2b3d4f); @@ -1443,9 +1443,9 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow z-index: 120; max-width: 90vw; } - `,zi=1e3;function Ei(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*zi,t.view_box[1]*e,t.view_box[2]*zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:zi,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*zi,t[1]*e]):void 0}))}}):[]}function Pi(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ai(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ri(t){const e=Ei(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=yi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Di(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*zi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Pi(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=_i(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?wi(t.hass,p,e.area):"none","temp"===o.fill?$i(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ai(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*zi,y:n.y*(zi/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Ei=1e3;function Pi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Ei/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ei,t.view_box[1]*e,t.view_box[2]*Ei,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Ei,h:e}:null,rooms:(t.rooms||[]).map(t=>({id:t.id,name:t.name,area:t.area??null,x:null!=t.x?t.x*Ei:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ei:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ei,t[1]*e]):void 0}))}}):[]}function Ai(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Ri(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Ni(t){const e=Pi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=Ke(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=dt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:pt),h=wi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Ci(t.lang,e),iconRules:c}),p=h.filter(e=>e.space===t.spaceId),d=function(t,e,i){const s={},o=i/100*Ei*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ai(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,p=l/Math.max(Math.ceil(e.length/c),1),d=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));Ue(d,n,o,.5*r),e.forEach((t,e)=>s[t.id]=d[e])}return s}(p,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";if(o.showBorders||"none"!==o.fill){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],r=e.area?Qe(o.fill,"lqi"===o.fill?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=mi(t,o.entities);null!=e&&s.push(e)}return Fe(s)}(t.hass,p,e.area):null,"light"===o.fill?xi(t.hass,p,e.area):"none","temp"===o.fill?ki(t.hass,p,e.area):null,o.tempMin,o.tempMax,Je(t.cfg?.settings)):null;r?(s+=" filled",i.push(`--room-fill:${r.c}`,`--room-fill-op:${r.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const r=!i.bg&&!o.showNames,a=Ri(e),l=e.poly?j``:j``;return j`${l}${r?j`${e.name}`:G}`}),_=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space){const e=i.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:n.x*Ei,y:n.y*(Ei/e)}}if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,d,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*zi,y:o.y*(zi/t)}}const n=Ai(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B` +
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i,s){const o=i["rl_"+(t.id||"")];if(o&&o.s===e){const t=s.spaces.find(t=>t.id===e)?.aspect||1;return{x:o.x*Ei,y:o.y*(Ei/t)}}const n=Ri(t);return{x:n[0],y:n[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[];return B`
${i.bg?j``:G} @@ -1453,13 +1453,13 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${_}${m}
- `}let Ni=null,Ii=null,Oi=!1;const Li=new Set;function qi(){if(Ni)return Ni;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function Fi(t){return Ni?Promise.resolve(Ni):Ii||(Ii=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Ni={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!Oi&&t.connection?.subscribeEvents){Oi=!0;try{await t.connection.subscribeEvents(()=>{Ni=null,Li.forEach(t=>t())},"houseplan_config_updated")}catch{Oi=!1}}return Ni}(t).finally(()=>{Ii=null}),Ii)}class Hi extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Mi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Di(t,"editor.space"),title:Di(t,"editor.title"),show_button:Di(t,"editor.show_button"),button_label:Di(t,"editor.button_label"),button_target:Di(t,"editor.button_target"),aspect_ratio:Di(t,"editor.aspect_ratio"),icon_size:Di(t,"editor.icon_size")};return B`{Ii=null,qi.forEach(t=>t())},"houseplan_config_updated")}catch{Li=!1}}return Ii}(t).finally(()=>{Oi=null}),Oi)}class Ui extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Di(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Ci(t,"editor.space"),title:Ci(t,"editor.title"),show_button:Ci(t,"editor.show_button"),button_label:Ci(t,"editor.button_label"),button_target:Ci(t,"editor.button_target"),aspect_ratio:Ci(t,"editor.aspect_ratio"),icon_size:Ci(t,"editor.icon_size")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Hi.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Hi);const Ui=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class Bi extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Ui(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=qi();return{type:"custom:houseplan-space-card",space:Ei(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||qi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},Li.add(t),()=>Li.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Fi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Mi(this.hass,this._config?.language)}getCardSize(){const t=Ei(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Di(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ri({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Di(this._lang,"space_card.not_found",{id:e}));const s=Ei(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Di(this._lang,"space_card.button");return B` + >
`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}Ui.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",Ui);const Bi=t=>{history.pushState(null,"",t),((t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)})(window,"location-changed",{replace:!1})};class ji extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");Bi(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=Fi();return{type:"custom:houseplan-space-card",space:Pi(e?.config||null)[0]?.id||"",show_button:!0}}setConfig(t){if(!t||!t.space)throw new Error('houseplan-space-card: "space" is required');this._config={show_button:!0,button_target:"/plan-doma",...t},this._snap=this._snap||Fi()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},qi.add(t),()=>qi.delete(t))}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,super.disconnectedCallback()}willUpdate(t){!this.hass||this._loading||this._snap&&!t.has("hass")||this._snap&&this._loadedOnce||this._load()}async _load(){if(this.hass&&!this._loading){this._loading=!0;try{const t=await Hi(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Di(this.hass,this._config?.language)}getCardSize(){const t=Pi(this._snap?.config||null).find(t=>t.id===this._config?.space);if(t){const e=t.vb[3]/t.vb[2];return Math.max(3,Math.round(8*e))+(!1===this._config?.show_button?0:1)}return 6}_errorCard(t){return B`
${t}
`}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`
${Ci(this._lang,"space_card.loading")}
`;const e=this._config.space,i=Ni({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang});if(!i)return this._errorCard(Ci(this._lang,"space_card.not_found",{id:e}));const s=Pi(t).find(t=>t.id===e),o=void 0!==this._config.title?this._config.title:s?.title||"",n=!1!==this._config.show_button,r=this._config.button_label||Ci(this._lang,"space_card.button");return B` ${o?B`
${o}
`:G} ${i} @@ -1467,7 +1467,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow `:G}
- `}}Bi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Bi.styles=[Ti,n` + `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",Bi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const ji="houseplan_card_layout_v1",Wi="houseplan_card_cfg_v1",Gi="houseplan_card_zoom_v1",Vi="houseplan_card_nav_v1",Ki="houseplan_card_kiosk_v1",Zi=1e3,Yi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Ji=(t,e)=>{let i;return(...s)=>{clearTimeout(i),i=window.setTimeout(()=>t(...s),e)}};class Xi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Ji(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(ji,JSON.stringify(this._layout))},600),this._saveConfig=Ji(()=>{this._serverCfg&&(this._dropLegacySegments(),this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1}).catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly()):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Ki)||"null");this._kioskScale={icon:li(t?.icon),font:li(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Wi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Wi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Zi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Zi,t.view_box[1]*e,t.view_box[2]*Zi,t.view_box[3]*e],bg:t.plan_url?{href:t.plan_url,x:0,y:0,w:Zi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Zi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Zi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Zi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(ji)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(){try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch{}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Mi(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=yi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=_i(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Zi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Zi,y:n/(Zi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:mi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&gi(this.hass,t.primary)?fi(this.hass,t.entities):null}_openMoreInfo(t){t?Yi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Di(Mi(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Gi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Yi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Xi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Zi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Zi/t.aspect:Zi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Zi,t[1]*e,t[2]*Zi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Vi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Vi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Zi,ry:t.y*e,rlen:t.length*Zi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Zi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Zi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Zi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Zi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Zi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Zi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Zi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Zi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)},c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Zi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Zi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Zi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Zi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog={...this._markerDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Zi,y:i/(Zi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog={...this._spaceDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B` - `}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog={...this._rulesDialog,busy:!1},this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_rulesSet(t){this._rulesDialog={...this._rulesDialog,rules:t}}async _saveRules(){const t=this._rulesDialog;if(!t||t.busy)return;const e=t.rules.filter(t=>t.pattern.trim()&&t.icon.trim());this._rulesDialog={...t,busy:!0};try{const t=this._serverCfg,i=JSON.stringify(e)===JSON.stringify(pt),s={...t.settings};i?delete s.icon_rules:s.icon_rules=e,this._serverCfg={...t,settings:s},await this._saveConfigNow(),this._rulesDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("rules.saved"))}catch(t){this._rulesDialog&&(this._rulesDialog={...this._rulesDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_renderRulesDialog(){const t=this._rulesDialog,e=dt(t.rules),i=t.test.trim()?gt(t.test,"",e):null,s=(e,i)=>{const s=[...t.rules],o=e+i;o<0||o>=s.length||([s[e],s[o]]=[s[o],s[e]],this._rulesSet(s))};return B``}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Ki,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1795,7 +1795,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?wi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @mousemove=${a} @mouseleave=${()=>this._tip=null}>`:t.poly?j`${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?_i(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&hi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?xi(this.hass,e,"temp"):t.area?$i(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?xi(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=fi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=li(t.settings?.name_scale),this._roomLabelScale=li(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Zi,y:i.y*(Zi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Zi,y:o.y/(Zi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Zi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Zi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2078,7 +2078,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${t.link}
`:G} ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} - ${t.pdfs.map(t=>B` + ${t.pdfs.map(t=>B` ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} @@ -2245,7 +2245,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
${t.pdfs.map(t=>B` - ${t.name} + ${t.name} this._removeMarkerPdf(t.url)}>`)}
- `}}Xi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Xi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Xi.styles=Ti,customElements.get("houseplan-card")||customElements.define("houseplan-card",Xi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.42.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Qi.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Qi._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,Qi.styles=zi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Qi),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.0 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8cf60d2..b981e22 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -255,3 +255,20 @@ more specific tier overrides the more general one; "unset" always means The UI will later be unified around this model; until then each tier keeps its own dialog (general settings gear / space gear / room-card gear / marker dialog). + + +## Audit follow-ups (2026-07-27) + +- **Content is authenticated.** `/houseplan_files/…` now serves ONLY the card + bundle (a Lovelace resource must be public). Plans and marker files go + through `HouseplanContentView` (`/api/houseplan/content//…`, + `requires_auth`). `contentUrl()` rewrites legacy stored URLs on read, so no + storage migration is needed. Static paths cannot be unregistered — the old + routes survive until the next HA restart. +- **Optimistic UI, stated explicitly (audit L7).** `_serverCfg` is mutated in + place before a fallible save in ~22 places and there is no rollback: after a + rejected save the UI shows the edit until the next reload. This is a + deliberate optimistic-UI choice, not drift. Paths where it is unacceptable + need their own rollback. +- **Split invariant.** `splitRoomPath` guarantees a partition: the two parts' + areas sum to the original (within epsilon) or the cut is rejected. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0bc181e..dd44627 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## v1.43.0 — 2026-07-27 (external audit: P0 fixes) + +An external code audit of v1.41.1 found four critical issues. All four are fixed +and covered by regression tests. + +- **Silent data loss on save (L2).** A debounced config write read the config at + fire time, so a `houseplan_config_updated` event arriving in between replaced + it and the user's edit vanished with no error — reproducible in a single tab. + The debounce now supports `flush()`/`pending()`, a reload flushes the pending + write first and defers while a write is in flight, and a failed reload finally + reports instead of staying silent. +- **Split corrupted room geometry (G1).** A cut starting and ending on the SAME + wall (carving a niche — a natural action) produced two overlapping, + self-intersecting rooms whose areas summed to twice the original, and the + overlap guard did not catch it. Same-edge cuts now carve the niche correctly, + and a partition invariant (parts must sum to the original) rejects anything + else. +- **Plans and uploaded files were served without authentication (B1).** Anyone + who could reach the HA endpoint could fetch floor plans and attached manuals + without logging in. They are now served by an authenticated view; stored + legacy URLs are rewritten on read, so nothing breaks. **The old public paths + disappear after a Home Assistant restart.** +- **Dialogs could resurrect and blank the card (L3).** Closing a dialog while + its save was in flight, on a failed save, spread `null` into a truthy husk; + the renderer then threw and the card went blank until reload. Guarded in all + four save routines; the error toast still fires. + ## v1.42.2 — 2026-07-26 - Touch devices no longer pop hover tooltips on every tap (field feedback: "extra labels appear and get in the way on a tablet"). Hover tooltips are diff --git a/docs/TESTING.md b/docs/TESTING.md index 42873e0..119e0bf 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -140,6 +140,19 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi (explicit ripple color still wins); off/white lights unchanged [auto] - [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static +- [ ] Save race (v1.43.0, audit L2): make a markup edit, then press Save in any + dialog within 500 ms (or let another client save) — the markup edit must + survive and reach the server; a failed reload now shows a toast [auto] +- [ ] Niche split (v1.43.0, audit G1): a cut that starts AND ends on the same + wall carves a niche; the two parts' areas must sum to the original (the + invariant is enforced in code and asserted for every split test) [auto] +- [ ] Authenticated content (v1.43.0, audit B1): plan images and marker files + are only reachable through /api/houseplan/content/… with a session; the + old /houseplan_files/plans|files paths return 404 after a restart; old + stored URLs keep working (rewritten on read) [auto+manual] +- [ ] Dialog zombies (v1.43.0, audit L3): close a dialog (Esc) while its save is + in flight and let the save fail — the dialog stays closed, the card keeps + rendering, the error toast still fires [auto] - [ ] No hover tooltips on touch (v1.42.2): on hover-less devices (tablets, phones) taps never pop the room/device tooltip — the data lives in room cards and long-press; desktop hover tooltips unchanged [auto] diff --git a/package.json b/package.json index 6241e32..3005c97 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "houseplan-card", - "version": "1.42.2", + "version": "1.43.0", "description": "Interactive house plan Lovelace card for Home Assistant", "license": "MIT", "type": "module", diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts index dc822f6..0e0eff7 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -14,7 +14,7 @@ import { import { lqiColor, snapToGrid, samePoint, pointInPolygon, markerIdForBinding, segmentCm, formatLength, roomEdges, roomPoly, pointStrictlyInside, roomsOverlap, - pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, openZoneOf, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, + pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, openZoneOf, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, contentUrl, snapToWall, openingAmount, averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo, stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices, glowColorOf, doorSector, hasRoomBehind, controlsAction, isControllable, @@ -32,7 +32,7 @@ import './space-card'; import { cardStyles } from './styles'; import { langOf, t, type I18nKey } from './i18n'; -const CARD_VERSION = '1.42.2'; +const CARD_VERSION = '1.43.0'; const LS_KEY = 'houseplan_card_layout_v1'; const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering const LS_ZOOM = 'houseplan_card_zoom_v1'; @@ -54,12 +54,40 @@ const navigate = (path: string) => { fireEvent(window, 'location-changed', { replace: false }); }; -const debounce = void>(fn: T, ms: number) => { +/** + * Debounce with `flush()` and `pending`. Both are load-bearing: a pending + * config write MUST be flushed before the card adopts a server revision, + * otherwise the edit is silently dropped (audit L2, 2026-07-27). + */ +interface Debounced void> { + (...a: Parameters): void; + flush(): void; + pending(): boolean; +} + +const debounce = void>(fn: T, ms: number): Debounced => { let t: number | undefined; - return (...a: Parameters) => { + let last: Parameters | null = null; + const wrapped = ((...a: Parameters) => { clearTimeout(t); - t = window.setTimeout(() => fn(...a), ms); + last = a; + t = window.setTimeout(() => { + t = undefined; + const args = last; + last = null; + if (args) fn(...args); + }, ms); + }) as Debounced; + wrapped.flush = () => { + if (t === undefined) return; + clearTimeout(t); + t = undefined; + const args = last; + last = null; + if (args) fn(...args); }; + wrapped.pending = () => t !== undefined; + return wrapped; }; class HouseplanCard extends LitElement { @@ -331,6 +359,8 @@ class HouseplanCard extends LitElement { clearInterval(this._cycleTimer); clearTimeout(this._kioskDotsTimer); clearTimeout(this._kioskHoldTimer); + clearTimeout(this._reloadRetry); + this._saveConfig.flush(); // never leave an edit unsent on teardown window.removeEventListener('hashchange', this._onHashChange); clearTimeout(this._holdTimer); this._roViewport?.disconnect(); @@ -510,7 +540,7 @@ class HouseplanCard extends LitElement { id: s.id, title: s.title, vb: [s.view_box[0] * NORM_W, s.view_box[1] * H, s.view_box[2] * NORM_W, s.view_box[3] * H], - bg: s.plan_url ? { href: s.plan_url, x: 0, y: 0, w: NORM_W, h: H } : null, + bg: s.plan_url ? { href: contentUrl(s.plan_url), x: 0, y: 0, w: NORM_W, h: H } : null, rooms: s.rooms.map(scale), }; }); @@ -620,6 +650,9 @@ class HouseplanCard extends LitElement { // live sync: the config was changed in another window → re-read it if (!this._unsubCfg) { this._unsubCfg = await this.hass.connection.subscribeEvents((ev: any) => { + // Flush a pending local edit BEFORE adopting a remote revision: + // otherwise the debounced write reads a config that this reload has + // already replaced, and the user's edit vanishes (audit L2). if ((ev?.data?.rev ?? -1) !== this._cfgRev) this._reloadConfigOnly(); }, 'houseplan_config_updated'); } @@ -657,7 +690,22 @@ class HouseplanCard extends LitElement { this.requestUpdate(); } - private async _reloadConfigOnly(): Promise { + /** + * Adopt the server config. Any pending local write is flushed first and, if a + * write is still in flight, the reload is deferred — adopting a revision on + * top of an unsent edit is exactly how edits disappeared (audit L2). + * `force` skips the deferral (conflict path: the local edit already lost). + */ + private async _reloadConfigOnly(force = false): Promise { + if (!force) { + if (this._saveConfig.pending()) this._saveConfig.flush(); + if (this._cfgWriting) { + // retry once the in-flight write settles + clearTimeout(this._reloadRetry); + this._reloadRetry = window.setTimeout(() => this._reloadConfigOnly(), 400); + return; + } + } try { const resp = await this.hass.callWS({ type: 'houseplan/config/get' }); const cfg = resp?.config; @@ -667,11 +715,14 @@ class HouseplanCard extends LitElement { this._regSignature = ''; this._maybeRebuildDevices(); this.requestUpdate(); - } catch { - /* ignore */ + } catch (e: any) { + // a failed reload leaves the card on its last known config; tell the user + // rather than silently diverging from the server (audit L2 note) + this._showToast(this._t('toast.cfg_reload_failed', { err: this._errText(e) })); } } + private _reloadRetry?: number; private _dirtyPos = new Set(); private _persistLayout = debounce(() => { @@ -1383,19 +1434,25 @@ class HouseplanCard extends LitElement { for (const sp of this._serverCfg?.spaces || []) delete (sp as any).segments; } + /** A config write is in flight — the card must not adopt a server revision. */ + private _cfgWriting = false; + private _saveConfig = debounce(() => { if (!this._serverCfg) return; this._dropLegacySegments(); + this._cfgWriting = true; this.hass .callWS({ type: 'houseplan/config/set', config: this._serverCfg, expected_rev: this._cfgRev }) .then((r: any) => { this._cfgRev = r?.rev ?? this._cfgRev + 1; + this._cfgWriting = false; }) .catch((e: any) => { + this._cfgWriting = false; if (e?.code === 'conflict') { this._showToast(this._t('toast.conflict')); this._cancelPath(); - this._reloadConfigOnly(); + this._reloadConfigOnly(true); } else { this._showToast(this._t('toast.cfg_save_failed', { err: this._errText(e) })); } @@ -2577,7 +2634,11 @@ class HouseplanCard extends LitElement { this._maybeRebuildDevices(); this._showToast(this._t('toast.marker_saved')); } catch (e: any) { - this._markerDialog = { ...this._markerDialog!, busy: false }; + // audit L3: the dialog may have been closed (Esc) while the save was + // in flight — spreading null yields a truthy husk and the renderer + // then crashes, blanking the whole card. The toast below is the + // only remaining signal, so it must still fire. + if (this._markerDialog) this._markerDialog = { ...this._markerDialog, busy: false }; this._showToast(this._t('toast.error', { err: this._errText(e) })); } } @@ -2772,7 +2833,11 @@ class HouseplanCard extends LitElement { this._showToast(d.mode === 'create' ? this._t('toast.space_added') : this._t('toast.space_saved')); } } catch (e: any) { - this._spaceDialog = { ...this._spaceDialog!, busy: false }; + // audit L3: the dialog may have been closed (Esc) while the save was + // in flight — spreading null yields a truthy husk and the renderer + // then crashes, blanking the whole card. The toast below is the + // only remaining signal, so it must still fire. + if (this._spaceDialog) this._spaceDialog = { ...this._spaceDialog, busy: false }; this._showToast(this._t('toast.error', { err: this._errText(e) })); } } @@ -2930,7 +2995,11 @@ class HouseplanCard extends LitElement { this.requestUpdate(); this._showToast(this._t('gs.saved')); } catch (e: any) { - this._settingsDialog = { ...this._settingsDialog!, busy: false }; + // audit L3: the dialog may have been closed (Esc) while the save was + // in flight — spreading null yields a truthy husk and the renderer + // then crashes, blanking the whole card. The toast below is the + // only remaining signal, so it must still fire. + if (this._settingsDialog) this._settingsDialog = { ...this._settingsDialog, busy: false }; this._showToast(this._t('toast.error', { err: this._errText(e) })); } } @@ -3115,7 +3184,11 @@ class HouseplanCard extends LitElement { this._maybeRebuildDevices(); this._showToast(this._t('rules.saved')); } catch (e: any) { - this._rulesDialog = { ...this._rulesDialog!, busy: false }; + // audit L3: the dialog may have been closed (Esc) while the save was + // in flight — spreading null yields a truthy husk and the renderer + // then crashes, blanking the whole card. The toast below is the + // only remaining signal, so it must still fire. + if (this._rulesDialog) this._rulesDialog = { ...this._rulesDialog, busy: false }; this._showToast(this._t('toast.error', { err: this._errText(e) })); } } @@ -4330,7 +4403,7 @@ class HouseplanCard extends LitElement { ${d.pdfs && d.pdfs.length ? html`
${this._t('info.manuals')} ${d.pdfs.map( - (p) => html` + (p) => html` ${p.name}`, )}
` : nothing} @@ -4562,7 +4635,7 @@ class HouseplanCard extends LitElement {
${d.pdfs.map( (p) => html` - ${p.name} + ${p.name} this._removeMarkerPdf(p.url)}>`, )}
`:G} - `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` + `}}Wi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Wi.styles=[Ei,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",ji),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Gi="houseplan_card_layout_v1",Vi="houseplan_card_cfg_v1",Ki="houseplan_card_zoom_v1",Zi="houseplan_card_nav_v1",Yi="houseplan_card_kiosk_v1",Ji=1e3,Xi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Qi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class ts extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._cfgEpoch=0,this._modelCache=null,this._dirtyPos=new Set,this._persistLayout=Qi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Gi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfigDebounced=Qi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openPairsCache=null,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Ki)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Yi)||"null");this._kioskScale={icon:hi(t?.icon),font:hi(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Vi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgEpoch++,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Vi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}_cfgFingerprint(){const t=this._serverCfg?.spaces||[];let e=t.length+":";for(const i of t){e+=(i.id||"")+","+(i.aspect||"")+","+(i.plan_url||"").length+","+(i.rooms?.length||0)+","+(i.openings?.length||0)+","+(i.decor?.length||0)+";";for(const t of i.rooms||[])e+=(t.poly?.length||0)+"."+(t.id||"")+"."+(t.open_to||[]).join("+")+"."+(t.area||"")+"."+JSON.stringify(t.settings||0)+";"}return e}get _model(){if(!this._serverCfg)return[];const t=this._cfgEpoch+"|"+this._cfgFingerprint();if(this._modelCache&&this._modelCache.key===t)return this._modelCache.model;const e=this._buildModel();return this._modelCache={key:t,model:e},e}_buildModel(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Ji/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ji,t.view_box[1]*e,t.view_box[2]*Ji,t.view_box[3]*e],bg:t.plan_url?{href:ci(t.plan_url),x:0,y:0,w:Ji,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Ji:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ji:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ji,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Xe(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=xi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=gi(this.hass,i.entities);null!=s&&e.push(s)}return He(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Ji*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Be(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Ji,y:n/(Ji/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(ni);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:fi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&bi(this.hass,t.primary)?vi(this.hass,t.entities):null}_openMoreInfo(t){t?Xi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(ni);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||Ge.has(i)?"info":"toggle"===t||We.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ti(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:Ue(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=Ue(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=Ue(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=Ue(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Ki,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=Ue(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Xi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){ts._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Ji/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Ji/t.aspect:Ji}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Ji,t[1]*e,t[2]*Ji,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Zi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Zi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_saveConfig(){this._cfgEpoch++,this._saveConfigDebounced()}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Ji,ry:t.y*e,rlen:t.length*Ji}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Ji/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Ji,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Ji,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Ji,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Yi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Yi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Yi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Yi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)};let c,h;if(n===r){const r=qe([...e],i);if(r.length<3||Ne(r)<=i)return null;const a=[];for(let i=0;i=0?e:[...e].reverse();for(const t of i)a.push(t)}c=qe(a,i),h=r}else c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i||Math.abs(Ne(c)+Ne(h)-Ne(t))>Math.max(i,1e-6*Ne(t))?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Yi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Yi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Yi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Yi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Yi,y:i/(Yi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog&&(this._spaceDialog={...this._spaceDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B``}get _glowRadiusCm(){const t=Number(this._settings.glow_radius_cm);return Number.isFinite(t)&&t>0?t:300}get _imperial(){return"mi"===this.hass?.config?.unit_system?.length}get _glowRadiusPlaceholder(){const t=this._glowRadiusCm;return this._imperial?String(Math.round(t/30.48*10)/10):String(t/100)}_renderGlowLayer(t){const e=this._fillColors,i=this._glowRadiusCm/this._cellCm*this._gridPitch,s=this._gridPitch,o=t.rooms.map(t=>({r:t,poly:we(t)})).filter(t=>!!t.poly),n=this._openingsR.filter(t=>"door"===t.type),r=[];for(const a of this._devices){if(a.space!==t.id)continue;const l=a.entities.find(t=>t.startsWith("light.")&&"on"===this.hass.states[t]?.state);if(!l)continue;const c=ii(this.hass.states[l],e.glow_light.c);if(!c)continue;const h=Number(a.marker?.glow_radius_cm),p=Number.isFinite(h)&&h>0?h/this._cellCm*this._gridPitch:i,d=this._pos(a),u=[...o].reverse().find(t=>this._pointInRoom([d.x,d.y],t.r));let _=null;if(u){const e=u.r.id?ai(u.r.id,t.rooms):new Set([u.r.id]),i=o.filter(t=>t.r.id&&e.has(t.r.id)),r=i.length?i:[u],a=r.map(t=>"M "+t.poly.map(t=>t[0]+" "+t[1]).join(" L ")+" Z"),l=o.filter(t=>!r.includes(t)).map(t=>t.poly);for(const t of n){const e=r.some(e=>{const i=De([t.rx,t.ry],e.poly);return i&&Math.hypot(i[0]-t.rx,i[1]-t.ry)<=.75*s});if(!e)continue;const i=t.angle*Math.PI/180,o=Math.cos(i)*t.rlen/2,n=Math.sin(i)*t.rlen/2;if(!oi([t.rx,t.ry],t.angle,[d.x,d.y],l,.6*s))continue;const c=si([d.x,d.y],[t.rx-o,t.ry-n],[t.rx+o,t.ry+n],p);c&&a.push("M "+c.map(t=>t[0]+" "+t[1]).join(" L ")+" Z")}_=a}r.push({pos:d,c:c.c,alpha:e.glow_light.a*c.bri,clip:_,r:p})}return r.length?j` ${r.map((t,e)=>j` @@ -1648,7 +1648,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- @@ -1701,7 +1701,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Yi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1735,7 +1735,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._spaceDialog?this._renderSpaceDialog():G} ${this._importDialog?this._renderImportDialog():G} ${this._toast?B`
${this._toast}
`:G} - `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ke(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B` + `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ze(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B`
@@ -1795,17 +1795,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" - @mousemove=${a} - @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._tip=null}>`:j`this._tip=null}>`,m=p.length&&d?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${_}${g}${l?j`${t.name}`:G}`})} + ${(()=>{const t=this._openPairs(),i=new Map,s=t=>(i.has(t)||i.set(t,we(t)),i.get(t));return e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(i=>{let r="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||i.id!==this._mergeSel&&i.id!==this._splitSel?.roomId||(r+=" picked");let a="";const l=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,i);if(!this._markup&&(o.showBorders||"none"!==l)){r+=" styled";const t=[];t.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const e="glow"===l?this._fillColors.glow_base:"temp"===l?ti("temp",null,"none",this._roomTemp(i),o.tempMin,o.tempMax,this._fillColors):i.area?ti(l,"lqi"===l?this._roomLqi(i.area):null,"light"===l?$i(this.hass,this._devices,i.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;e?(r+=" filled",t.push(`--room-fill:${e.c}`,`--room-fill-op:${e.a.toFixed(3)}`)):t.push("--room-fill:transparent","--room-fill-op:0"),a=t.join(";")}const c=t=>this._showTip(t,i.name,this._t("tip.room"),n?this._roomLqi(i.area):null,this._roomTemp(i)),h=!e.bg&&!o.showNames&&!this._markup,p=this._roomCenter(i),d=this._markup&&(i.id===this._mergeSel||i.id===this._splitSel?.roomId),u=i.id&&!d?t.filter(t=>t.a.id===i.id||t.b.id===i.id).flatMap(t=>t.segs):[];u.length&&(r+=" noedge");const _=s(i),m=_?function(t,e,i=1e-6){const s=e.filter(e=>Ne(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Ne(e,t,i)))}(_,(g=i,e.rooms.filter(t=>t!==g).map(s).filter(Boolean))):[];var g;const f=m.length&&_?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + @mousemove=${c} + @mouseleave=${()=>this._tip=null}>`:i.poly?j`this._tip=null}>`:j`this._tip=null}>`,b=u.length&&_?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${f}${v}${h?j`${i.name}`:G}`})})()} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._editing?this._renderAlignGuides():G} @@ -1845,7 +1845,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._kioskDialog?this._renderKioskDialog():G} ${this._toast?B`
${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?mi(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ei(this.hass.states[t])).find(t=>t)||null:"light"===m?ei(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&di.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?ki(this.hass,e,"temp"):t.area?Si(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?ki(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=vi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=hi(t.settings?.name_scale),this._roomLabelScale=hi(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Ji,y:o.y/(Ji/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Ji,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Ji,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2000,7 +2000,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?ai(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` + `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?li(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` ${i.map(t=>j``)} ${s.length>1?j``:G} @@ -2068,17 +2068,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @click=${()=>this._setMode("view")}> -
`}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(oi);return B``}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(ni);return B``:G} - `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` + `}}Wi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Wi.styles=[Ei,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",ji),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Gi="houseplan_card_layout_v1",Vi="houseplan_card_cfg_v1",Ki="houseplan_card_zoom_v1",Zi="houseplan_card_nav_v1",Yi="houseplan_card_kiosk_v1",Ji=1e3,Xi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Qi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class ts extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._cfgEpoch=0,this._modelCache=null,this._dirtyPos=new Set,this._persistLayout=Qi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Gi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfigDebounced=Qi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openPairsCache=null,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Ki)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Yi)||"null");this._kioskScale={icon:hi(t?.icon),font:hi(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Vi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgEpoch++,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Vi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}_cfgFingerprint(){const t=this._serverCfg?.spaces||[];let e=t.length+":";for(const i of t){e+=(i.id||"")+","+(i.aspect||"")+","+(i.plan_url||"").length+","+(i.rooms?.length||0)+","+(i.openings?.length||0)+","+(i.decor?.length||0)+";";for(const t of i.rooms||[])e+=(t.poly?.length||0)+"."+(t.id||"")+"."+(t.open_to||[]).join("+")+"."+(t.area||"")+"."+JSON.stringify(t.settings||0)+";"}return e}get _model(){if(!this._serverCfg)return[];const t=this._cfgEpoch+"|"+this._cfgFingerprint();if(this._modelCache&&this._modelCache.key===t)return this._modelCache.model;const e=this._buildModel();return this._modelCache={key:t,model:e},e}_buildModel(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Ji/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ji,t.view_box[1]*e,t.view_box[2]*Ji,t.view_box[3]*e],bg:t.plan_url?{href:ci(t.plan_url),x:0,y:0,w:Ji,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Ji:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ji:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ji,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Xe(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=xi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=gi(this.hass,i.entities);null!=s&&e.push(s)}return He(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Ji*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Be(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Ji,y:n/(Ji/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(ni);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:fi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&bi(this.hass,t.primary)?vi(this.hass,t.entities):null}_openMoreInfo(t){t?Xi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(ni);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||Ge.has(i)?"info":"toggle"===t||We.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ti(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:Ue(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=Ue(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=Ue(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=Ue(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Ki,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=Ue(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Xi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){ts._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Ji/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Ji/t.aspect:Ji}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Ji,t[1]*e,t[2]*Ji,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Zi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Zi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_saveConfig(){this._cfgEpoch++,this._saveConfigDebounced()}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Ji,ry:t.y*e,rlen:t.length*Ji}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Ji/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Ji,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Ji,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Ji,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Yi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Yi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Yi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Yi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)};let c,h;if(n===r){const r=qe([...e],i);if(r.length<3||Ne(r)<=i)return null;const a=[];for(let i=0;i=0?e:[...e].reverse();for(const t of i)a.push(t)}c=qe(a,i),h=r}else c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i||Math.abs(Ne(c)+Ne(h)-Ne(t))>Math.max(i,1e-6*Ne(t))?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Yi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Yi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Yi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Yi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Yi,y:i/(Yi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog&&(this._spaceDialog={...this._spaceDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B``}get _glowRadiusCm(){const t=Number(this._settings.glow_radius_cm);return Number.isFinite(t)&&t>0?t:300}get _imperial(){return"mi"===this.hass?.config?.unit_system?.length}get _glowRadiusPlaceholder(){const t=this._glowRadiusCm;return this._imperial?String(Math.round(t/30.48*10)/10):String(t/100)}_renderGlowLayer(t){const e=this._fillColors,i=this._glowRadiusCm/this._cellCm*this._gridPitch,s=this._gridPitch,o=t.rooms.map(t=>({r:t,poly:we(t)})).filter(t=>!!t.poly),n=this._openingsR.filter(t=>"door"===t.type),r=[];for(const a of this._devices){if(a.space!==t.id)continue;const l=a.entities.find(t=>t.startsWith("light.")&&"on"===this.hass.states[t]?.state);if(!l)continue;const c=ii(this.hass.states[l],e.glow_light.c);if(!c)continue;const h=Number(a.marker?.glow_radius_cm),p=Number.isFinite(h)&&h>0?h/this._cellCm*this._gridPitch:i,d=this._pos(a),u=[...o].reverse().find(t=>this._pointInRoom([d.x,d.y],t.r));let _=null;if(u){const e=u.r.id?ai(u.r.id,t.rooms):new Set([u.r.id]),i=o.filter(t=>t.r.id&&e.has(t.r.id)),r=i.length?i:[u],a=r.map(t=>"M "+t.poly.map(t=>t[0]+" "+t[1]).join(" L ")+" Z"),l=o.filter(t=>!r.includes(t)).map(t=>t.poly);for(const t of n){const e=r.some(e=>{const i=De([t.rx,t.ry],e.poly);return i&&Math.hypot(i[0]-t.rx,i[1]-t.ry)<=.75*s});if(!e)continue;const i=t.angle*Math.PI/180,o=Math.cos(i)*t.rlen/2,n=Math.sin(i)*t.rlen/2;if(!oi([t.rx,t.ry],t.angle,[d.x,d.y],l,.6*s))continue;const c=si([d.x,d.y],[t.rx-o,t.ry-n],[t.rx+o,t.ry+n],p);c&&a.push("M "+c.map(t=>t[0]+" "+t[1]).join(" L ")+" Z")}_=a}r.push({pos:d,c:c.c,alpha:e.glow_light.a*c.bri,clip:_,r:p})}return r.length?j` ${r.map((t,e)=>j` @@ -1648,7 +1648,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- @@ -1701,7 +1701,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Yi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1735,7 +1735,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._spaceDialog?this._renderSpaceDialog():G} ${this._importDialog?this._renderImportDialog():G} ${this._toast?B`
${this._toast}
`:G} - `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ke(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B` + `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ze(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B`
@@ -1795,17 +1795,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" - @mousemove=${a} - @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._tip=null}>`:j`this._tip=null}>`,m=p.length&&d?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${_}${g}${l?j`${t.name}`:G}`})} + ${(()=>{const t=this._openPairs(),i=new Map,s=t=>(i.has(t)||i.set(t,we(t)),i.get(t));return e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(i=>{let r="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||i.id!==this._mergeSel&&i.id!==this._splitSel?.roomId||(r+=" picked");let a="";const l=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,i);if(!this._markup&&(o.showBorders||"none"!==l)){r+=" styled";const t=[];t.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const e="glow"===l?this._fillColors.glow_base:"temp"===l?ti("temp",null,"none",this._roomTemp(i),o.tempMin,o.tempMax,this._fillColors):i.area?ti(l,"lqi"===l?this._roomLqi(i.area):null,"light"===l?$i(this.hass,this._devices,i.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;e?(r+=" filled",t.push(`--room-fill:${e.c}`,`--room-fill-op:${e.a.toFixed(3)}`)):t.push("--room-fill:transparent","--room-fill-op:0"),a=t.join(";")}const c=t=>this._showTip(t,i.name,this._t("tip.room"),n?this._roomLqi(i.area):null,this._roomTemp(i)),h=!e.bg&&!o.showNames&&!this._markup,p=this._roomCenter(i),d=this._markup&&(i.id===this._mergeSel||i.id===this._splitSel?.roomId),u=i.id&&!d?t.filter(t=>t.a.id===i.id||t.b.id===i.id).flatMap(t=>t.segs):[];u.length&&(r+=" noedge");const _=s(i),m=_?function(t,e,i=1e-6){const s=e.filter(e=>Ne(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Ne(e,t,i)))}(_,(g=i,e.rooms.filter(t=>t!==g).map(s).filter(Boolean))):[];var g;const f=m.length&&_?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + @mousemove=${c} + @mouseleave=${()=>this._tip=null}>`:i.poly?j`this._tip=null}>`:j`this._tip=null}>`,b=u.length&&_?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${f}${v}${h?j`${i.name}`:G}`})})()} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._editing?this._renderAlignGuides():G} @@ -1845,7 +1845,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._kioskDialog?this._renderKioskDialog():G} ${this._toast?B`
${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?mi(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ei(this.hass.states[t])).find(t=>t)||null:"light"===m?ei(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&di.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?ki(this.hass,e,"temp"):t.area?Si(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?ki(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=vi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=hi(t.settings?.name_scale),this._roomLabelScale=hi(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Ji,y:o.y/(Ji/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Ji,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Ji,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2000,7 +2000,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?ai(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` + `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?li(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` ${i.map(t=>j``)} ${s.length>1?j``:G} @@ -2068,17 +2068,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @click=${()=>this._setMode("view")}> -
`}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(oi);return B``}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(ni);return B``:G} - `}}ji.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},ji.styles=[zi,n` + `}}Wi.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},Wi.styles=[Ei,n` .hp-static-title { font-weight: 700; padding: 10px 14px 6px; @@ -1516,7 +1516,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow text-align: center; color: var(--secondary-text-color, #9aa4ad); } - `],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",ji),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Wi="houseplan_card_layout_v1",Gi="houseplan_card_cfg_v1",Vi="houseplan_card_zoom_v1",Ki="houseplan_card_nav_v1",Zi="houseplan_card_kiosk_v1",Yi=1e3,Ji=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Xi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class Qi extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._dirtyPos=new Set,this._persistLayout=Xi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Wi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfig=Xi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfig.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Vi)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Zi)||"null");this._kioskScale={icon:ci(t?.icon),font:ci(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Gi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Gi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}get _model(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Yi/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Yi,t.view_box[1]*e,t.view_box[2]*Yi,t.view_box[3]*e],bg:t.plan_url?{href:li(t.plan_url),x:0,y:0,w:Yi,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Yi:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Yi:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Yi,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Je(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Wi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfig.pending()&&this._saveConfig.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=wi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=mi(this.hass,i.entities);null!=s&&e.push(s)}return Fe(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Yi*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Ue(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Yi,y:n/(Yi/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(oi);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:gi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&fi(this.hass,t.primary)?bi(this.hass,t.entities):null}_openMoreInfo(t){t?Ji(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(oi);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||We.has(i)?"info":"toggle"===t||je.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ci(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:He(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=He(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=He(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=He(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Vi,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=He(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Ji(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){Qi._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Yi/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Yi/t.aspect:Yi}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Yi,t[1]*e,t[2]*Yi,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Ki)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Ki,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Yi,ry:t.y*e,rlen:t.length*Yi}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Yi/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Yi,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Yi,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Yi,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j`"houseplan-space-card"===t.type)||window.customCards.push({type:"houseplan-space-card",name:"House Plan — Space (static)",description:"Read-only static schematic of a single houseplan space, with a deep-link button.",preview:!1,documentation:"https://github.com/Matysh/houseplan-card"});const Gi="houseplan_card_layout_v1",Vi="houseplan_card_cfg_v1",Ki="houseplan_card_zoom_v1",Zi="houseplan_card_nav_v1",Yi="houseplan_card_kiosk_v1",Ji=1e3,Xi=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Qi=(t,e)=>{let i,s=null;const o=(...o)=>{clearTimeout(i),s=o,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return o.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},o.pending=()=>void 0!==i,o};class ts extends lt{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._unsubCfg=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._selId=null,this._toast="",this._mode="view",this._decorTool="select",this._decorStyle={color:"#607d8b",width:3,fill:!1},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorTextDialog=null,this._tool="draw",this._path=[],this._cursorPt=null,this._mergeSel=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._pointers=new Map,this._panStart=null,this._pinchStart=null,this._suppressClick=!1,this._onboardingShown=!1,this._rulesDialog=null,this._settingsDialog=null,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._hashApplied=!1,this._navApplied=!1,this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._space=t,this._selId=null,this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._cfgEpoch=0,this._modelCache=null,this._dirtyPos=new Set,this._persistLayout=Qi(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)})))}this._cacheSnapshot()}else localStorage.setItem(Gi,JSON.stringify(this._layout))},600),this._cfgWriting=!1,this._saveConfigDebounced=Qi(()=>{this._serverCfg&&(this._dropLegacySegments(),this._cfgWriting=!0,this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev}).then(t=>{this._cfgRev=t?.rev??this._cfgRev+1,this._cfgWriting=!1}).catch(t=>{this._cfgWriting=!1,"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))}))},500),this._openPairsCache=null,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,busy:!1}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:pt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}}}get _canEdit(){return this._norm&&!1!==this.hass?.user?.is_admin}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}_cycleTick(){if(this._kiosk&&Number(this._config?.cycle)>0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._space=t[(e+1)%t.length],this._restoreZoom(),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_hashSpace(){const t=/(?:^|[#&])space=([^&]+)/.exec(window.location.hash||"");return t?decodeURIComponent(t[1]):""}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this._keyHandler),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange)}disconnectedCallback(){window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),super.disconnectedCallback()}_onKey(t){if("Escape"===t.key){if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void(this._infoCard=null);if(this._rulesDialog)return void(this._rulesDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0)}if("decor"===this._mode)return"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||t.target?.closest?.("input, textarea, select")?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel());if(!this._markup)return;const e="Escape"===t.key||(t.ctrlKey||t.metaKey)&&"z"===t.key.toLowerCase();if(e){if(this._roomDialog)return t.preventDefault(),void this._roomDialogCancel();if("draw"===this._tool&&this._path.length)return t.preventDefault(),void this._undoPoint();if(e)return"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):void("openwall"!==this._tool&&"opening"!==this._tool&&"delroom"!==this._tool||(t.preventDefault(),this._tool="draw"))}}_undoPoint(){this._path.length&&(this._path=this._path.slice(0,-1))}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(Ki)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(Yi)||"null");this._kioskScale={icon:hi(t?.icon),font:hi(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(Vi)||"null");if(e&&e.config&&Array.isArray(e.config.spaces)){this._serverCfg=e.config,this._cfgEpoch++,this._cfgRev=e.rev||0,this._layout=e.layout||{},this._serverStorage=!0;const i=this._hashSpace(),s=this._savedNav();i&&this._model.find(t=>t.id===i)?(this._space=i,this._hashApplied=!0):s?.space&&this._model.find(t=>t.id===s.space)?(this._space=s.space,this._navApplied=!0):t.default_floor?this._space=t.default_floor:this._model.find(t=>t.id===this._space)||(this._space=this._model[0]?.id||this._space),s?.mode&&"view"!==s.mode&&this._canEdit&&!t.kiosk&&(this._mode=s.mode)}}catch{}}_cacheSnapshot(){if(this._serverCfg)try{localStorage.setItem(Vi,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,layout:this._layout}))}catch{}}getCardSize(){return 12}get _norm(){return!(!this._serverCfg||!this._serverCfg.spaces.length)}_cfgFingerprint(){const t=this._serverCfg?.spaces||[];let e=t.length+":";for(const i of t){e+=(i.id||"")+","+(i.aspect||"")+","+(i.plan_url||"").length+","+(i.rooms?.length||0)+","+(i.openings?.length||0)+","+(i.decor?.length||0)+";";for(const t of i.rooms||[])e+=(t.poly?.length||0)+"."+(t.id||"")+"."+(t.open_to||[]).join("+")+"."+(t.area||"")+"."+JSON.stringify(t.settings||0)+";"}return e}get _model(){if(!this._serverCfg)return[];const t=this._cfgEpoch+"|"+this._cfgFingerprint();if(this._modelCache&&this._modelCache.key===t)return this._modelCache.model;const e=this._buildModel();return this._modelCache={key:t,model:e},e}_buildModel(){return this._serverCfg?this._serverCfg.spaces.map(t=>{const e=Ji/t.aspect;return{id:t.id,title:t.title,vb:[t.view_box[0]*Ji,t.view_box[1]*e,t.view_box[2]*Ji,t.view_box[3]*e],bg:t.plan_url?{href:ci(t.plan_url),x:0,y:0,w:Ji,h:e}:null,rooms:t.rooms.map(t=>({id:t.id,name:t.name,area:t.area??null,open_to:t.open_to||void 0,settings:t.settings||void 0,x:null!=t.x?t.x*Ji:void 0,y:null!=t.y?t.y*e:void 0,w:null!=t.w?t.w*Ji:void 0,h:null!=t.h?t.h*e:void 0,poly:t.poly?t.poly.map(t=>[t[0]*Ji,t[1]*e]):void 0}))}}):[]}_spaceModel(t){const e=this._model;return e.find(e=>e.id===(t??this._space))||e[0]}get _areaToSpace(){const t={};for(const e of this._model)for(const i of e.rooms)i.area&&(t[i.area]={space:e.id,room:i});return t}get _settings(){return this._serverCfg?.settings||{}}get _showAll(){return!!this._settings.show_all}_toggleShowAll(){this._serverCfg&&(this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._showAll}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())}get _iconRules(){const t=this._settings.icon_rules;if(!t||!Array.isArray(t)||!t.length)return;const e=JSON.stringify(t);return e!==this._rulesCompiledSrc&&(this._rulesCompiledSrc=e,this._rulesCompiled=dt(t)),this._rulesCompiled}get _fillColors(){return Xe(this._settings)}get _excluded(){const t=this._settings.exclude_integrations;return t?new Set(t):ht}willUpdate(t){t.has("hass")&&this.hass&&(!this._loadOk&&!this._loading&&this._loadTries<8&&this._loadFromServer(),this._maybeRebuildDevices())}updated(){const t=this._stageEl;if(t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t)),t&&!this._view&&this._refitView(),this._serverStorage&&this._loadOk&&0===this._model.length&&!this._spaceDialog&&!this._importDialog&&!this._onboardingShown){this._onboardingShown=!0;const t=function(t){const e=t?.floors;if(!e||"object"!=typeof e)return[];const i=[];for(const t of Object.values(e))t&&t.floor_id&&i.push({id:t.floor_id,name:t.name||t.floor_id,level:t.level??null});return i.sort((t,e)=>{const i=t.level??1e9,s=e.level??1e9;return i!==s?i-s:t.name.localeCompare(e.name)}),i}(this.hass);t.length?this._importDialog={floors:t.map(t=>({...t,checked:!0}))}:this._openSpaceDialog("create")}}async _loadFromServer(){this._loading=!0,this._loadTries++;try{const[t,e]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._loadOk=!0,this._serverStorage=!0;const i=t?.config;this._serverCfg=i&&Array.isArray(i.spaces)?i:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._layout=e?.layout||{},this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated"));const s=this._hashSpace(),o=this._savedNav();!this._hashApplied&&s&&this._model.find(t=>t.id===s)?(this._space=s,this._hashApplied=!0):o?.space&&!this._navApplied&&!this._hashApplied&&this._model.find(t=>t.id===o.space)?(this._space=o.space,this._navApplied=!0):this._norm&&!this._model.find(t=>t.id===this._space)&&(this._space=this._model[0]?.id||this._space),this._cacheSnapshot(),this._restoreZoom()}catch(t){if(this._loadTries>=8){this._serverStorage=!1,this._serverCfg=null;try{this._layout=JSON.parse(localStorage.getItem(Gi)||"{}")||{}}catch{this._layout={}}}}finally{this._loading=!1}this._regSignature="",this.requestUpdate()}async _reloadConfigOnly(t=!1){if(!t&&(this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting))return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(()=>this._reloadConfigOnly(),400));try{const t=await this.hass.callWS({type:"houseplan/config/get"}),e=t?.config;this._serverCfg=e&&Array.isArray(e.spaces)?e:null,this._cfgEpoch++,this._cfgRev=t?.rev||0,this._cacheSnapshot(),this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate()}catch(t){this._showToast(this._t("toast.cfg_reload_failed",{err:this._errText(t)}))}}_maybeRebuildDevices(){const t=this.hass;if(!t?.devices||!t?.entities||!t?.areas)return;const e=Object.keys(t.devices).length+":"+Object.keys(t.entities).length+":"+Object.keys(t.areas).length+":"+(this._norm?"n":"l")+":"+Di(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=xi({hass:t,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||"",loc:t=>this._t(t),iconRules:this._iconRules}),this._defPos=this._defaultPositions(),this._syncNewDevices())}_syncNewDevices(){if(!this._norm||!this._loadOk||!this._serverCfg)return;const t=this._devices.filter(t=>!t.marker&&!t.virtual).map(t=>t.id).sort(),e=t.join(",");if(e===this._newSyncKey)return;this._newSyncKey=e;const i=this._settings,{fresh:s,known:o}=function(t,e){if(!Array.isArray(e))return{fresh:[],known:[...t]};const i=new Set(e),s=t.filter(t=>!i.has(t));return{fresh:s,known:s.length?[...e,...s]:e}}(t,i.known_devices);if(!Array.isArray(i.known_devices)||s.length){const t=[...new Set([...i.new_device_ids||[],...s])];this._serverCfg={...this._serverCfg,settings:{...i,known_devices:o,new_device_ids:t}},this._saveConfig()}}get _newIds(){const t=this._settings.new_device_ids;return new Set(Array.isArray(t)?t:[])}_ackNewDevice(t){if(!this._newIds.has(t)||!this._serverCfg)return;const e=this._settings;this._serverCfg={...this._serverCfg,settings:{...e,new_device_ids:(e.new_device_ids||[]).filter(e=>e!==t)}},this._saveConfig(),this.requestUpdate()}get _markers(){return this._serverCfg?.markers||[]}_roomLqi(t){if(!t)return null;const e=[];for(const i of this._devices){if(i.area!==t||i.virtual)continue;const s=gi(this.hass,i.entities);null!=s&&e.push(s)}return He(e)}_roomBounds(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}_defaultPositions(){const t={},e=(this._config?.icon_size??2.5)/100*Ji*1.3;for(const i of this._model)for(const s of i.rooms){if(!s.area)continue;const o=this._devices.filter(t=>t.area===s.area&&t.space===i.id);if(!o.length)continue;const n=this._roomBounds(s),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(o.length*a/Math.max(l,1)))),h=Math.ceil(o.length/c),p=a/c,d=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+p*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));Be(u,n,e,.5*r),o.forEach((e,i)=>t[e.id]=u[i])}return t}_pos(t){const e=this._spaceModel(t.space),i=this._layout[t.id];if(i)if(this._norm){if(i.s===t.space){const e=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/e)}}}else if(void 0===i.s)return{x:i.x,y:i.y};if(this._defPos[t.id])return this._defPos[t.id];const s=e.vb;return{x:s[0]+s[2]/2,y:s[1]+s[3]/2}}_savePos(t,e,i){if(this._norm){const s=this._gridPitch,o=Math.round(e/s)*s,n=Math.round(i/s)*s,r=this._serverCfg.spaces.find(e=>e.id===t.space)?.aspect||1,a=this._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/Ji,y:n/(Ji/r),...a?{k:a}:{}}}}else this._layout={...this._layout,[t.id]:{x:Math.round(e),y:Math.round(i)}};this._dirtyPos.add(t.id),this._persistLayout()}_stateClass(t){if(!this._config?.live_states)return"";const e=(t.marker?.controls||[]).filter(ni);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";const i=t.primary?this.hass.states[t.primary]:void 0;if(!i)return"";if("unavailable"===i.state)return"unavail";const s=t.primary.split(".")[0];if(["light","switch","fan","humidifier"].includes(s))return"on"===i.state?"on":"";if("cover"===s||"valve"===s)return["open","opening"].includes(i.state)?"open":"";if("lock"===s)return["unlocked","open"].includes(i.state)?"open":"";if("binary_sensor"===s){const t=i.attributes?.device_class;if(["door","window","garage_door","opening","gas","smoke","moisture","problem"].includes(t))return"on"===i.state?"open":""}return"media_player"===s?["playing","on"].includes(i.state)?"on":"":"vacuum"===s&&["cleaning","returning"].includes(i.state)?"on":""}_liveTemp(t){return this._config?.show_temperature?"mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon?null:fi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&bi(this.hass,t.primary)?vi(this.hass,t.entities):null}_openMoreInfo(t){t?Xi(this,"hass-more-info",{entityId:t}):this._showToast(this._t("toast.no_entity"))}_ctxDevice(t,e){"view"===this._mode&&(t.preventDefault(),t.stopPropagation(),e.primary?this._openMoreInfo(e.primary):this._infoCard=e)}_clickDevice(t,e){if(t.stopPropagation(),this._drag?.moved||this._suppressClick||this._holdFired)return;if("plan"===this._mode)return;if("devices"===this._mode)return void this._openMarkerDialog(e);const i=e.primary?e.primary.split(".")[0]:null,s=(e.marker?.controls||[]).filter(ni);if("toggle"===e.tapAction&&s.length){const t=(o=s.map(t=>this.hass.states[t]?.state),o.some(t=>"on"===t)?"turn_off":"turn_on");return void this.hass.callService("homeassistant",t,{entity_id:s}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}var o;const n=function(t,e,i){const s=t||e||("light"===i?"toggle":"info");return"more-info"===s?"more-info":"toggle"!==s||!i||Ge.has(i)?"info":"toggle"===t||We.has(i)?"toggle":"info"}(e.tapAction,void 0,i);"toggle"===n&&e.primary?this.hass.callService("homeassistant","toggle",{entity_id:e.primary}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)}))):"more-info"===n&&e.primary?this._openMoreInfo(e.primary):this._infoCard=e}_t(t,e){return Ti(Di(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_stageAspect(){const t=this._stageEl,e=this._spaceModel().vb;return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:Ue(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._spaceModel().vb),o=i?.clientWidth||1,n=i?.clientHeight||1;return[s.x+t/o*s.w,s.y+e/n*s.h]}_clampView(t,e){return{w:t.w,h:t.h,x:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._spaceModel().vb,o=Ue(s,this._stageAspect()),n=Math.min(8,Math.max(1,t)),r=o.w/n,a=o.h/n,l=this._viewOr(s),c=e??l.x+l.w/2,h=i??l.y+l.h/2;this._zoom=n,this._view=this._clampView({x:c-r/2,y:h-a/2,w:r,h:a},o)}_refitView(){if(!this._stageEl)return;const t=this._view;this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}_zoomAt(t,e,i){const s=this._stageEl;if(!s)return;const o=Ue(this._spaceModel().vb,this._stageAspect()),n=Math.min(8,Math.max(1,i)),r=s.clientWidth,a=s.clientHeight,l=this._screenToVb(t,e),c=o.w/n,h=o.h/n;this._zoom=n,this._view=this._clampView({x:l[0]-t/r*c,y:l[1]-e/a*h,w:c,h:h},o)}_onWheel(t){const e=this._stageEl;if(!e)return;t.preventDefault();const i=e.getBoundingClientRect(),s=t.deltaY<0?1.15:1/1.15;this._zoomAt(t.clientX-i.left,t.clientY-i.top,this._zoom*s),this._saveZoom()}_stepZoom(t){const e=this._stageEl;e&&(this._zoomAt(e.clientWidth/2,e.clientHeight/2,this._zoom*(t>0?1.4:1/1.4)),this._saveZoom())}_resetZoom(){const t=this._spaceModel().vb;this._zoom=1,this._view=Ue(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(Ki,JSON.stringify(this._zoomBySpace))}catch{}}_restoreZoom(){const t=this._zoomBySpace[this._space]||1;this._zoom=t,this._view=null,requestAnimationFrame(()=>{if(!this._stageEl)return;const e=this._spaceModel().vb;this._applyView(t,e[0]+e[2]/2,e[1]+e[3]/2),this.requestUpdate()})}_stagePointerDown(t){if(this._kiosk&&(this._cyclePausedUntil=Date.now()+6e4,0===this._pointers.size?(this._swipeStart={x:t.clientX,y:t.clientY,id:t.pointerId},t.target.closest?.(".dev, .roomlabel, .oplock")||(clearTimeout(this._kioskHoldTimer),this._kioskHoldTimer=window.setTimeout(()=>{this._kioskDialog=!0,this._swipeStart=null},3e3))):(this._swipeStart=null,clearTimeout(this._kioskHoldTimer))),this._drag||this._markup)return;if("devices"===this._mode&&t.target.closest(".dev"))return;if("decor"===this._mode&&this._decorPointerDown(t))return;this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=this._viewOr(this._spaceModel().vb);if(1===this._pointers.size)this._panStart={sx:t.clientX,sy:t.clientY,vx:e.x,vy:e.y},this._suppressClick=!1;else if(2===this._pointers.size){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y);this._pinchStart={dist:e,zoom:this._zoom},this._panStart=null}}_stagePointerMove(t){if(this._decorDraft?.pid!==t.pointerId)if(this._decorMove?.pid!==t.pointerId)if(this._pointers.has(t.pointerId)){if(this._pointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),this._pinchStart&&this._pointers.size>=2){const t=[...this._pointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y)/(this._pinchStart.dist||1),i=this._stageEl.getBoundingClientRect(),s=(t[0].x+t[1].x)/2-i.left,o=(t[0].y+t[1].y)/2-i.top;this._zoomAt(s,o,this._pinchStart.zoom*e),this._suppressClick=!0,this._saveZoom()}else if(this._panStart){const e=t.clientX-this._panStart.sx,i=t.clientY-this._panStart.sy;if(Math.abs(e)+Math.abs(i)>4&&(this._suppressClick=!0,clearTimeout(this._holdTimer)),this._zoom>1&&this._view){const t=this._stageEl,s=this._view,o=Ue(this._spaceModel().vb,this._stageAspect());this._view=this._clampView({x:this._panStart.vx-e/t.clientWidth*s.w,y:this._panStart.vy-i/t.clientHeight*s.h,w:s.w,h:s.h},o)}}}else this._markupMove(t);else this._decorMoveUpdate(t);else this._decorDraft={...this._decorDraft,b:this._snap(this._svgPoint(t))}}_stagePointerUp(t){if(this._kiosk){clearTimeout(this._kioskHoldTimer);const e=this._swipeStart;if(this._swipeStart=null,e&&e.id===t.pointerId){const i=t.clientX-e.x,s=t.clientY-e.y;if(Math.abs(i)+Math.abs(s)<8){const t=Date.now();t-this._lastTap<350&&this._resetZoom(),this._lastTap=t}const o=function(t,e,i,s,o,n=60){if(i>1.001||s.length<2)return null;if(Math.abs(t)t.id),this._space);o&&(this._space=o,this._selId=null,this._restoreZoom(),this._saveNav(),this._suppressClick=!0,setTimeout(()=>this._suppressClick=!1,0),this._showKioskDots())}}if(this._decorDraft?.pid!==t.pointerId){if(this._decorMove?.pid===t.pointerId)return this._decorMove.moved&&this._saveConfig(),void(this._decorMove=null);this._pointers.delete(t.pointerId),this._pointers.size<2&&(this._pinchStart=null),0===this._pointers.size&&(this._panStart=null,setTimeout(()=>this._suppressClick=!1,0))}else this._decorCommitDraft()}_clickRoom(t){var e;!this._suppressClick&&t.area&&(e="/config/areas/area/"+t.area,history.pushState(null,"",e),Xi(window,"location-changed",{replace:!1}))}_pointerDown(t,e){if("plan"===this._mode)return;if("view"===this._mode)return this._holdFired=!1,clearTimeout(this._holdTimer),void(this._holdTimer=window.setTimeout(()=>{this._holdFired=!0,this._infoCard=e},600));t.preventDefault();const i=this._pos(e);this._drag={id:e.id,sx:t.clientX,sy:t.clientY,ox:i.x,oy:i.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_pointerMove(t,e){if(!this._drag||this._drag.id!==e.id)return;const i=this.renderRoot.querySelector(".stage");if(!i)return;const s=this._spaceModel().vb,o=i.getBoundingClientRect(),n=this._viewOr(s),r=(t.clientX-this._drag.sx)/o.width*n.w,a=(t.clientY-this._drag.sy)/o.height*n.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0,clearTimeout(this._holdTimer));const l=.008*Math.min(s[2],s[3]),c=Math.max(s[0]+l,Math.min(s[0]+s[2]-l,this._drag.ox+r)),h=Math.max(s[1]+l,Math.min(s[1]+s[3]-l,this._drag.oy+a));this._savePos(e,c,h)}_pointerUp(t,e){if(clearTimeout(this._holdTimer),!this._drag||this._drag.id!==e.id)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&(this._selId=e.id,window.setTimeout(()=>this._drag=null,0))}_showToast(t){this._toast=t,clearTimeout(this._toastTimer),this._toastTimer=window.setTimeout(()=>{this._toast=""},3500)}_showTip(t,e,i,s,o){ts._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return Ji/240}get _cellCm(){const t=Number(this._curSpaceCfg?.cell_cm);return Number.isFinite(t)&&t>0?t:5}_fmtLen(t,e){const i=function(t,e,i,s){return Math.hypot(e[0]-t[0],e[1]-t[1])/i*s}(t,e,this._gridPitch,this._cellCm);return function(t,e){if(e){const e=t/2.54;let i=Math.floor(e/12),s=Math.round(e-12*i);return 12===s&&(i+=1,s=0),`${i}′ ${s}″`}return`${(t/100).toFixed(2)} m`}(i,"mi"===this.hass?.config?.unit_system?.length)}get _curSpaceCfg(){return this._serverCfg?.spaces.find(t=>t.id===this._space)}get _spaceH(){const t=this._curSpaceCfg;return t?Ji/t.aspect:Ji}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*Ji,t[1]*e,t[2]*Ji,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(Zi)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(Zi,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){this._kiosk&&"view"!==t||this._mode!==t&&("plan"!==t&&"decor"!==t||this._norm?(this._mode=t,this._path=[],this._cursorPt=null,this._tool="draw",this._mergeSel=null,this._mergeDialog=null,this._splitSel=null,this._pendingSplit=null,this._selId=null,this._tip=null,this._decorDraft=null,this._decorSel=null,this._decorTool="select",this._saveNav()):this._showToast(this._t("toast.markup_needs_server")))}_svgPoint(t){const e=this.renderRoot.querySelector(".stage").getBoundingClientRect();return this._screenToVb(t.clientX-e.left,t.clientY-e.top)}_snap(t){const e=this._gridPitch;return[ve(t[0],e),ve(t[1],e)]}_samePt(t,e){return Se(t,e)}_dropLegacySegments(){for(const t of this._serverCfg?.spaces||[])delete t.segments}_saveConfig(){this._cfgEpoch++,this._saveConfigDebounced()}_roomAt(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&ze(t,i)})}_overlapRoom(t){return this._spaceModel().rooms.find(e=>{const i=we(e);return!!i&&function(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i=e.x&&t[0]<=e.x+e.w&&t[1]>=e.y&&t[1]<=e.y+e.h}_markupClick(t){if(!this._markup)return;if(this._drag||this._rlResize)return;if((t.composedPath?.()||[]).some(t=>t?.classList?.contains?.("roomlabel")||t?.classList?.contains?.("rlhandle")))return;const e=this._svgPoint(t);if("delroom"===this._tool){const t=[...this._spaceModel().rooms].reverse().find(t=>this._pointInRoom(e,t));if(!t)return;if(!confirm(this._t("confirm.delete_room",{name:t.name})))return;const i=this._curSpaceCfg;return i.rooms=i.rooms.filter(e=>e.id!==t.id),this._saveConfig(),this._regSignature="",this._maybeRebuildDevices(),void this.requestUpdate()}if("opening"===this._tool)return void this._openingClick(e);if("merge"===this._tool)return void this._mergeClick(e);if("openwall"===this._tool)return void this._openWallClick(e);if("split"===this._tool)return void this._splitClick(e);const i=this._snap(e),s=this._path.length>=3&&this._samePt(i,this._path[0]);if(!this._path.length)return void(this._path=[i]);const o=this._path[this._path.length-1];if(!this._samePt(i,o)){if(s){const t=this._overlapRoom(this._path);return t?void this._showToast(this._t("toast.room_overlap",{name:t.name||""})):(this._path=[...this._path,i],this._cursorPt=null,this._nameSel="",this._areaSel="",this._resetRoomDialogFields(),void(this._roomDialog=!0))}this._path=[...this._path,i]}}get _openingsR(){const t=this._curSpaceCfg,e=this._spaceH;return(t?.openings||[]).map(t=>({...t,rx:t.x*Ji,ry:t.y*e,rlen:t.length*Ji}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return Ji/(this._curSpaceCfg?.aspect||1)}_decorPointerDown(t){const e=this._decorTool,i=t.target.closest?.(".dshape");if(i)return!0;if("line"===e||"rect"===e||"ellipse"===e){t.preventDefault();const i=this._snap(this._svgPoint(t));return this._decorDraft={kind:e,a:i,b:i,pid:t.pointerId},t.target.setPointerCapture?.(t.pointerId),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/Ji,y:e[1]/this._decorH,text:"",size:"m",color:this._decorStyle.color},!0}return this._decorSel=null,!1}_decorCommitDraft(){const t=this._decorDraft;if(this._decorDraft=null,!t)return;const e=.5*this._gridPitch;if(Math.hypot(t.b[0]-t.a[0],t.b[1]-t.a[1])t.id!==e.id),this._decorSel===e.id&&(this._decorSel=null),this._saveConfig(),void this.requestUpdate()}"select"===this._decorTool&&(this._decorSel=e.id,this._decorMove={id:e.id,start:this._svgPoint(t),orig:JSON.parse(JSON.stringify(e)),pid:t.pointerId,moved:!1},t.target.setPointerCapture?.(t.pointerId))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch,o=ve(i[0]-e.start[0],s)/Ji,n=ve(i[1]-e.start[1],s)/this._decorH;(o||n)&&(e.moved=!0);this._curSpaceCfg.decor=this._decorList.map(t=>{if(t.id!==e.id)return t;const i=e.orig;return"line"===t.kind?{...t,x1:i.x1+o,y1:i.y1+n,x2:i.x2+o,y2:i.y2+n}:{...t,x:i.x+o,y:i.y+n}}),this.requestUpdate()}_decorShapeDbl(t){"decor"===this._mode&&"text"===t.kind&&(this._decorTextDialog={id:t.id,x:t.x,y:t.y,text:t.text,size:t.size||"m",color:t.color})}_decorSaveText(){const t=this._decorTextDialog;if(!t||!t.text.trim())return void(this._decorTextDialog=null);const e=this._curSpaceCfg;if(t.id)e.decor=this._decorList.map(e=>e.id===t.id?{...e,text:t.text.trim(),size:t.size,color:t.color}:e);else{const i="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);e.decor=[...this._decorList,{id:i,kind:"text",x:t.x,y:t.y,text:t.text.trim(),size:t.size,color:t.color}]}this._decorTextDialog=null,this._saveConfig(),this.requestUpdate()}_decorDeleteSel(){if(!this._decorSel)return;this._curSpaceCfg.decor=this._decorList.filter(t=>t.id!==this._decorSel),this._decorSel=null,this._saveConfig(),this.requestUpdate()}_renderDecorLayer(){const t=Ji,e=this._decorH,i={s:14,m:20,l:30},s="decor"===this._mode,o=this._decorList.map(o=>{const n="dshape"+(s&&this._decorSel===o.id?" dsel":""),r=t=>this._decorShapeDown(t,o);return"line"===o.kind?j``:"rect"===o.kind?j``:"ellipse"===o.kind?j``))} ${i?i.segs.map(t=>j``):G} - `}_openPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;it.id),i=6*this._gridPitch;let s=null;for(let o=0;ot.id===e.a.id),o=i.rooms.find(t=>t.id===e.b.id);if(!s||!o)return;(s.open_to||[]).includes(o.id)||(o.open_to||[]).includes(s.id)?(s.open_to=(s.open_to||[]).filter(t=>t!==o.id),o.open_to=(o.open_to||[]).filter(t=>t!==s.id),s.open_to.length||delete s.open_to,o.open_to.length||delete o.open_to,this._showToast(this._t("toast.openwall_closed",{a:s.name||"",b:o.name||""}))):(s.open_to=[...s.open_to||[],o.id],o.open_to=[...o.open_to||[],s.id],this._showToast(this._t("toast.openwall_opened",{a:s.name||"",b:o.name||""}))),this._saveConfig(),this.requestUpdate()}_openingClick(t){const e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return void this._editOpening(i);const s=$e(t,this._spaceModel().rooms,e);s?this._openingDialog={type:"door",lengthCm:90,contact:"",lock:"",invert:!1,flipH:!1,flipV:!1,x:s.x,y:s.y,angle:s.angle}:this._showToast(this._t("toast.opening_no_wall"))}_editOpening(t){this._openingDialog={id:t.id,type:t.type,lengthCm:Math.round(t.rlen/this._gridPitch*this._cellCm),contact:t.contact||"",lock:t.lock||"",invert:!!t.invert,flipH:!!t.flip_h,flipV:!!t.flip_v,x:t.rx,y:t.ry,angle:t.angle}}_opPointerDown(t,e){if("plan"===this._mode){t.preventDefault(),t.stopPropagation();try{t.target.setPointerCapture?.(t.pointerId)}catch{}this._opDrag={id:e.id,moved:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=$e(this._svgPoint(t),this._spaceModel().rooms,4*this._gridPitch);if(!i)return;this._opDrag.moved=!0;const s=this._curSpaceCfg,o=s?.openings?.find(t=>t.id===e.id);o&&(o.x=i.x/Yi,o.y=i.y/this._spaceH,o.angle=i.angle,this.requestUpdate())}_opPointerUp(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;const i=this._opDrag.moved;i&&this._saveConfig(),i?window.setTimeout(()=>this._opDrag=null,0):this._opDrag=null}_opClick(t,e){t.stopPropagation(),this._opDrag?.moved||"plan"===this._mode&&this._editOpening(e)}_saveOpening(){const t=this._openingDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s={id:t.id||"o"+Date.now().toString(36),type:t.type,x:t.x/Yi,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/Yi,contact:t.contact||null,lock:"door"===t.type&&t.lock||null,invert:t.invert||void 0,flip_h:t.flipH||void 0,flip_v:t.flipV||void 0};e.openings=e.openings||[];const o=e.openings.findIndex(t=>t.id===s.id);o>=0?e.openings[o]=s:e.openings.push(s),this._saveConfig(),this._openingDialog=null,this.requestUpdate()}_deleteOpening(){const t=this._openingDialog,e=this._curSpaceCfg;t?.id&&e?.openings&&(e.openings=e.openings.filter(e=>e.id!==t.id),this._saveConfig(),this._openingDialog=null,this.requestUpdate())}_contactCandidates(){const t=[];for(const e of Object.keys(this.hass.states)){const i=e.split(".")[0];if("binary_sensor"!==i&&"cover"!==i)continue;const s=this.hass.states[e],o=["door","window","opening","garage_door","garage"].includes(s?.attributes?.device_class||"");("cover"!==i||o)&&t.push([e,s?.attributes?.friendly_name||e,o?0:1])}return t.sort((t,e)=>t[2]-e[2]||t[1].localeCompare(e[1])).map(([t,e])=>({value:t,label:e}))}_lockCandidates(){return Object.keys(this.hass.states).filter(t=>t.startsWith("lock.")).map(t=>({value:t,label:this.hass.states[t]?.attributes?.friendly_name||t})).sort((t,e)=>t.label.localeCompare(e.label))}_mergeClick(t){const e=this._spaceModel().rooms,i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;const s=i.id;if(!this._mergeSel||this._mergeSel===s)return void(this._mergeSel=this._mergeSel===s?null:s);const o=e.find(t=>t.id===this._mergeSel),n=o?we(o):null,r=we(i),a=n&&r?Oe(n,r):null;if(!a)return this._showToast(this._t("toast.merge_not_adjacent")),void(this._mergeSel=null);this._mergeDialog={aId:this._mergeSel,bId:s,poly:a,pick:"a"},this._mergeSel=null}_commitMerge(){const t=this._mergeDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=this._spaceH,s="a"===t.pick?t.aId:t.bId,o="a"===t.pick?t.bId:t.aId,n=e.rooms.find(t=>t.id===s);n?(n.poly=t.poly.map(t=>[t[0]/Yi,t[1]/i]),delete n.x,delete n.y,delete n.w,delete n.h,e.rooms=e.rooms.filter(t=>t.id!==o),this._saveConfig(),this._mergeDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.rooms_merged",{name:n.name||""}))):this._mergeDialog=null}_splitClick(t){const e=this._spaceModel().rooms;if(!this._splitSel){const i=[...e].reverse().find(e=>this._pointInRoom(t,e));if(!i?.id)return;return void(this._splitSel={roomId:i.id,pts:[]})}const i=e.find(t=>t.id===this._splitSel.roomId),s=i?we(i):null;if(!i||!s)return void(this._splitSel=null);const o=.02*this._gridPitch,n=6*this._gridPitch,r=Ce(t,s),a=r&&Math.hypot(r[0]-t[0],r[1]-t[1])<=n?r:null,l=!!a&&Te(a,s,o),c=this._splitSel.pts;if(!c.length)return l?void(this._splitSel={...this._splitSel,pts:[a]}):void this._showToast(this._t("toast.split_pick_wall"));if(!l){const e=this._snap(t);return ze(e,s,o)?void(this._splitSel={...this._splitSel,pts:[...c,e]}):void this._showToast(this._t("toast.split_pick_inside"))}const h=function(t,e,i=1e-6){if(!t||t.length<3||!e||e.length<2)return null;const s=e[0],o=e[e.length-1];if(Se(s,o,i))return null;const n=Le(t,s,i),r=Le(t,o,i);if(n<0||r<0)return null;const a=e.slice(1,-1);for(const e of a)if(!ze(e,t,i))return null;for(let i=0;i{const r=[e];let a=(s+1)%t.length;for(let e=0;e<=t.length&&(r.push(t[a]),a!==n);e++)a=(a+1)%t.length;return r.push(o),qe(r,i)};let c,h;if(n===r){const r=qe([...e],i);if(r.length<3||Ne(r)<=i)return null;const a=[];for(let i=0;i=0?e:[...e].reverse();for(const t of i)a.push(t)}c=qe(a,i),h=r}else c=qe([...l(s,n,o,r),...[...a].reverse()],i),h=qe([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ne(c)<=i||Ne(h)<=i||Math.abs(Ne(c)+Ne(h)-Ne(t))>Math.max(i,1e-6*Ne(t))?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[p,d]=h,u=Ne(p)>=Ne(d)?p:d,_=u===p?d:p;this._pendingSplit={roomId:i.id,mainPoly:u,newPoly:_},this._cursorPt=null,this._nameSel="",this._areaSel="",this._roomDialog=!0}get _contourClosed(){return this._path.length>=4&&this._samePt(this._path[0],this._path[this._path.length-1])}_markupMove(t){if(!this._markup)return;if("opening"===this._tool||"openwall"===this._tool)return void(this._cursorPt=this._svgPoint(t));const e="draw"===this._tool&&this._path.length&&!this._contourClosed,i="split"===this._tool&&!!this._splitSel?.pts?.length;(e||i)&&(this._cursorPt=this._snap(this._svgPoint(t)))}get _openingPreview(){if("opening"!==this._tool||!this._cursorPt)return null;const t=this._cursorPt,e=1.5*this._gridPitch,i=this._openingsR.find(i=>Math.hypot(t[0]-i.rx,t[1]-i.ry)<=Math.max(i.rlen/2,e));if(i)return null;const s=$e(t,this._spaceModel().rooms,e);return s?{...s,rlen:this._cmToUnits(90)}:null}_saveRoom(){this._areaSel&&this._commitRoom()}_saveRoomNoArea(){this._nameSel.trim()&&(this._areaSel="",this._commitRoom())}_commitRoom(){const t=this._curSpaceCfg;if(!t)return;const e=this._spaceH;let i;if(this._pendingSplit){const s=t.rooms.find(t=>t.id===this._pendingSplit.roomId);if(!s)return this._pendingSplit=null,this._splitSel=null,void(this._roomDialog=!1);s.poly=this._pendingSplit.mainPoly.map(t=>[t[0]/Yi,t[1]/e]),delete s.x,delete s.y,delete s.w,delete s.h,i=this._pendingSplit.newPoly}else{if(!this._contourClosed)return;i=this._path.slice(0,-1)}const s=this._areaSel?this.hass.areas[this._areaSel]?.name:"";t.rooms.push({id:"r"+Date.now().toString(36),name:this._nameSel||s||this._t("room.default_name"),area:this._areaSel||null,poly:i.map(t=>[t[0]/Yi,t[1]/e]),...this._roomSettingsFromDialog()?{settings:this._roomSettingsFromDialog()}:{}}),this._saveConfig(),this._path=[],this._pendingSplit=null,this._splitSel=null;const o=this._areaSel;this._areaSel="",this._nameSel="",this._roomDialog=!1,this._regSignature="",this._maybeRebuildDevices();let n=0;if(o){const t=this._serverCfg?.spaces.find(t=>t.id===this._space)?.aspect||1,e=Yi/t,i={...this._layout};for(const t of this._devices){if(t.area!==o||t.space!==this._space)continue;if(n++,this._layout[t.id])continue;const s=this._defPos[t.id];s&&(i[t.id]={s:this._space,x:s.x/Yi,y:s.y/e},this._dirtyPos.add(t.id))}this._layout=i,this._persistLayout()}const r=this._model.find(t=>t.id===this._space)?.rooms.length||0;this._showToast(o?this._t("toast.room_saved",{n:r,added:n}):this._t("toast.room_saved_no_area",{n:r}))}_cancelPath(){this._path=[],this._cursorPt=null,this._roomDialog=!1,this._pendingSplit=null,this._splitSel=null,this._mergeSel=null,this._mergeDialog=null}_roomDialogCancel(){return this._roomDialog=!1,this._roomEditId?(this._roomEditId=null,this._nameSel="",void(this._areaSel="")):this._pendingSplit?(this._pendingSplit=null,void(this._splitSel=null)):void this._undoPoint()}get _freeAreas(){const t=new Set;for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area&&t.add(i.area);return Object.values(this.hass?.areas||{}).filter(e=>!t.has(e.area_id)).sort((t,e)=>(t.name||"").localeCompare(e.name||""))}_openMarkerDialog(t){t&&this._ackNewDevice(t.id),this._norm?this._markerDialog=t?{devId:t.id,name:t.name,binding:"virtual"===t.bindingKind?"virtual":t.bindingKind+":"+t.bindingRef,bindingMode:"virtual"===t.bindingKind?"virtual":"ha",bindingOpen:!1,showEntities:"entity"===t.bindingKind&&!!this.hass.entities[t.bindingRef||""]?.device_id,bindingFilter:"",icon:t.marker?.icon||"",autoIcon:t.icon||"",display:t.marker?.display||"badge",rippleColor:t.marker?.ripple_color||"",rippleSize:Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):3,size:Number(t.marker?.size)>0?Number(t.marker.size):1,angle:Number(t.marker?.angle)||0,tapAction:t.marker?.tap_action||"",defaultTap:"light"===t.primary?.split(".")[0]?"toggle":"info",controls:[...t.marker?.controls||[]],controlsFilter:"",glowRadius:Number(t.marker?.glow_radius_cm)>0?String(this._imperial?Math.round(Number(t.marker.glow_radius_cm)/30.48*10)/10:Math.round(Number(t.marker.glow_radius_cm))/100):"",model:t.model||"",link:t.link||"",description:t.description||"",pdfs:[...t.pdfs||[]],room:t.marker?.room_id?t.space+"#@"+t.marker.room_id:t.space&&t.area?t.space+"#"+t.area:"",busy:!1}:{name:"",binding:"virtual",bindingMode:"virtual",bindingOpen:!1,showEntities:!1,bindingFilter:"",icon:"",autoIcon:"",display:"badge",rippleColor:"",rippleSize:3,size:1,angle:0,tapAction:"",defaultTap:"info",controls:[],controlsFilter:"",glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",busy:!1}:this._showToast(this._t("toast.marker_needs_server"))}_bindingCandidates(){const t=this.hass,e=new Set;for(const t of this._devices)t.id!==this._markerDialog?.devId&&("device"===t.bindingKind&&t.bindingRef&&e.add("device:"+t.bindingRef),"entity"===t.bindingKind&&t.bindingRef&&e.add("entity:"+t.bindingRef));const i=new Set;for(const t of this._devices)"device"===t.bindingKind&&t.name&&i.add(t.name.trim()+"|"+(t.area||""));const s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;const t="device:"+o.id;if(e.has(t))continue;const n=(o.name_by_user||o.name||o.id).trim();t!==this._markerDialog?.binding&&i.has(n+"|"+(o.area_id||""))||s.push({value:t,label:n,sub:(o.model||this._t("marker.sub_device"))+("Group"===o.model?this._t("marker.sub_z2m_group"):"")})}const o=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);for(const[i,n]of Object.entries(t.entities)){const r="entity:"+i;if(e.has(r))continue;const a=o.has(n.platform),l="group"===n.platform;if(!a&&!l)continue;if(n.hidden)continue;const c=t.states[i];s.push({value:r,label:n.name||c?.attributes?.friendly_name||i,sub:i.split(".")[0]+" · "+("group"===n.platform?this._t("marker.sub_group"):this._t("marker.sub_helper"))})}if(this._markerDialog?.showEntities){const i=new Set(s.map(t=>t.value));for(const[o,n]of Object.entries(t.entities)){const r="entity:"+o;if(e.has(r)||i.has(r)||n.hidden)continue;const a=t.states[o],l=n.name||a?.attributes?.friendly_name||o,c=n.device_id?t.devices[n.device_id]:null,h=c&&(c.name_by_user||c.name)||"";s.push({value:r,label:l,sub:o.split(".")[0]+" · "+this._t("marker.sub_entity")+(h?" · "+h:"")})}}const n=(this._markerDialog?.bindingFilter||"").toLowerCase().trim(),r=n?s.filter(t=>(t.label+" "+t.sub+" "+t.value).toLowerCase().includes(n)):s;return r.sort((t,e)=>t.label.localeCompare(e.label)),r.slice(0,200)}_allRoomsFlat(){const t=[];for(const e of this._serverCfg?.spaces||[])for(const i of e.rooms||[])i.area?t.push({value:e.id+"#"+i.area,label:(e.title||e.id)+" · "+i.name}):i.id&&t.push({value:e.id+"#@"+i.id,label:(e.title||e.id)+" · "+i.name+" · "+this._t("marker.subarea")});return t}_errText(t){if(!t)return this._t("err.unknown");if("string"==typeof t)return t;if(t.message)return t.message;if(t.error)return t.error;if(null!=t.code)return this._t("err.code",{code:t.code});try{return JSON.stringify(t)}catch{return String(t)}}async _pickMarkerFiles(t){const e=t.target,i=e.files?[...e.files]:[];if(e.value="",!i.length||!this._markerDialog)return;const s=this._markerDialog.devId||"new",o=[];for(const t of i)try{const e=new FormData;e.append("marker_id",s),e.append("file",t,t.name);const i=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth("/api/houseplan/upload",{method:"POST",body:e}):await fetch("/api/houseplan/upload",{method:"POST",body:e,headers:this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}),n=await i.json().catch(()=>({}));if(!i.ok||n.error){const t={too_large:this._t("err.too_large",{mb:n.max_mb||50}),bad_ext:this._t("err.bad_ext"),unauthorized:this._t("err.unauthorized")};throw new Error(t[n.error]||n.error||"HTTP "+i.status)}o.push({name:n.name||t.name,url:n.url})}catch(e){this._showToast(this._t("toast.file_failed",{name:t.name,err:this._errText(e)}))}o.length&&this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:[...this._markerDialog.pdfs,...o]},this._showToast(this._t("toast.files_attached",{n:o.length})))}_removeMarkerPdf(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,pdfs:this._markerDialog.pdfs.filter(e=>e.url!==t)})}async _saveMarker(){const t=this._markerDialog;if(t&&!t.busy&&("ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding))if("virtual"!==t.binding||t.name.trim()){this._markerDialog={...t,busy:!0};try{const e=this._serverCfg;let i;e.markers=e.markers||[];const s=function(t){if(!t)return null;const e=t.indexOf("#");if(e<=0)return null;const i=t.slice(0,e),s=t.slice(e+1);if(!s)return null;if(s.startsWith("@")){const t=s.slice(1);return t?{space:i,area:null,roomId:t}:null}return{space:i,area:s,roomId:null}}(t.room);let o=s?.space||null,n=s?.area||null;const r=s?.roomId||null;"virtual"!==t.binding||o||(o=this._space),i=function(t,e,i){const[s,o]=t.split(":");return"device"===s?o:"entity"===s?"lg_"+o:e&&e.startsWith("v_")?e:i()}(t.binding,t.devId,()=>"v_"+Date.now().toString(36));const a=t.devId,l={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"badge"!==t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"badge"!==t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle?t.angle:null,tap_action:t.tapAction||null,controls:t.controls.length?t.controls:null,glow_radius_cm:(()=>{const e=parseFloat(t.glowRadius);return!Number.isFinite(e)||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs};("virtual"===t.binding||t.room)&&(l.space=o,l.area=n,l.room_id=r);const c=a?this._devices.find(t=>t.id===a):null,h=c?.marker?.room_id??null,p=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);a&&a!==i&&l.pdfs?.length&&(await this.hass.callWS({type:"houseplan/files/migrate",from_id:a,to_id:i}).catch(()=>{}),l.pdfs=function(t,e,i){if(!e||!i||e===i)return t;const s="/files/"+e+"/",o="/files/"+i+"/";return t.map(t=>t.url.includes(s)?{...t,url:t.url.split(s).join(o)}:t)}(l.pdfs,a,i)),e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let d=null;const u=o||c?.space||this._space,_=a?this._layout[a]:null,m=_?{s:_.s||c?.space||this._space,x:_.x,y:_.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(m&&m.s===u)i===a&&this._layout[i]&&!p||(d={s:m.s,x:m.x,y:m.y},this._layout={...this._layout,[i]:d});else if(!this._layout[i]||p){const t=this._spaceModel(o||void 0);let e=t.vb[0]+t.vb[2]/2,s=t.vb[1]+t.vb[3]/2;const a=r?t.rooms.find(t=>t.id===r):n?t.rooms.find(t=>t.area===n):void 0;a&&([e,s]=this._roomCenter(a)),d=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:d}}await this._saveConfigNow(),d&&await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:d}),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_saved"))}catch(t){this._markerDialog&&(this._markerDialog={...this._markerDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}else this._showToast(this._t("toast.virtual_name_required"))}async _deleteMarker(){const t=this._markerDialog;if(!t)return;const e=t.devId?this._devices.find(e=>e.id===t.devId):null,i=t.name||this._t("device.fallback");if(!confirm(this._t("confirm.remove_marker",{name:i})))return;const s=this._serverCfg;s.markers=s.markers||[],e&&"virtual"===e.bindingKind?s.markers=s.markers.filter(t=>t.id!==e.id):e&&e.marker?(s.markers=s.markers.filter(t=>t.id!==e.id),"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0})):e&&"device"===e.bindingKind&&e.bindingRef?s.markers.push({id:e.id,binding:"device:"+e.bindingRef,hidden:!0}):e&&"entity"===e.bindingKind&&e.bindingRef&&s.markers.push({id:e.id,binding:"entity:"+e.bindingRef,hidden:!0});try{await this._saveConfigNow(),e&&"virtual"===e.bindingKind&&this._layout[e.id]&&(delete this._layout[e.id],await this.hass.callWS({type:"houseplan/layout/delete",device_id:e.id}).catch(()=>{})),this._markerDialog=null,this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.marker_removed"))}catch(t){this._showToast(this._t("toast.error",{err:this._errText(t)}))}}_normPos(t,e,i){const s=this._serverCfg.spaces.find(e=>e.id===t)?.aspect||1;return{s:t,x:e/Yi,y:i/(Yi/s)}}_openSpaceDialog(t,e){if(this._serverStorage&&this._serverCfg)if("edit"===t){const i=this._serverCfg.spaces.find(t=>t.id===e);if(!i)return;const s=Ke(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",orientation:"landscape",showBorders:s.showBorders,showNames:s.showNames,roomColor:s.color,roomOpacity:s.opacity,fillMode:s.fill,tempMin:s.tempMin,tempMax:s.tempMax,showLqi:s.showLqi??this._config?.show_signal??!0,cardFontScale:s.cardFontScale,labelTemp:s.labelTemp,labelHum:s.labelHum,labelLqi:s.labelLqi,labelLight:s.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,busy:!1}}else this._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1};else this._showToast(this._t("toast.integration_missing"))}async _pickPlanFile(t){const e=t.target,i=e.files?.[0];if(!i||!this._spaceDialog)return;const s={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[i.type]||(i.name.toLowerCase().endsWith(".svg")?"svg":"");if(!s)return void this._showToast(this._t("toast.plan_formats"));const o=new Uint8Array(await i.arrayBuffer());let n="";for(let t=0;t{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:1.414),e.onerror=()=>t(1.414),e.src=a});URL.revokeObjectURL(a),this._spaceDialog={...this._spaceDialog,planFile:{ext:s,b64:r,aspect:l,name:i.name}}}async _saveSpaceDialog(){const t=this._spaceDialog;if(!t||t.busy||!t.title.trim())return;if("file"===t.source&&!t.planFile&&!t.planUrl)return void this._showToast(this._t("toast.plan_required"));const e="create"===t.mode&&0===(this._serverCfg?.spaces.length||0);this._spaceDialog={...t,busy:!0};try{const i=this._serverCfg;let s;const o="portrait"===t.orientation?.707:"square"===t.orientation?1:1.414;if("create"===t.mode?(s={id:"s"+Date.now().toString(36),title:t.title.trim(),plan_url:null,aspect:"draw"===t.source?o:1.414,view_box:[0,0,1,1],rooms:[]},i.spaces.push(s)):(s=i.spaces.find(e=>e.id===t.spaceId),s.title=t.title.trim()),"file"===t.source&&t.planFile){const e=await this.hass.callWS({type:"houseplan/plan/set",space_id:s.id,ext:t.planFile.ext,data:t.planFile.b64});s.plan_url=e.url,s.aspect=t.planFile.aspect}"draw"===t.source&&(s.plan_url=null);const n="draw"===t.source;s.settings={...s.settings||{},show_borders:!(!n||"create"!==t.mode)||t.showBorders,show_names:!(!n||"create"!==t.mode)||t.showNames,room_color:t.roomColor,room_opacity:t.roomOpacity,fill_mode:t.fillMode,temp_min:Number.isFinite(t.tempMin)?Math.min(t.tempMin,t.tempMax):20,temp_max:Number.isFinite(t.tempMax)?Math.max(t.tempMin,t.tempMax):25,show_lqi:t.showLqi,card_font_scale:1!==t.cardFontScale?t.cardFontScale:void 0,label_temp:t.labelTemp,label_hum:t.labelHum,label_lqi:t.labelLqi,label_light:t.labelLight},s.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=s.id),this._regSignature="",this._maybeRebuildDevices(),this._importQueue.length?this._openNextImport():e||this._importTotal>0?(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._tool="draw",this._path=[],this._cursorPt=null,this._showToast(this._t(e&&!this._importTotal?"toast.space_added_onboard":"import.done"))):this._showToast("create"===t.mode?this._t("toast.space_added"):this._t("toast.space_saved"))}catch(t){this._spaceDialog&&(this._spaceDialog={...this._spaceDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}async _deleteSpace(){const t=this._spaceDialog;if(!t||"edit"!==t.mode)return;const e=this._serverCfg.spaces.find(e=>e.id===t.spaceId);if(confirm(this._t("confirm.delete_space",{title:e.title}))){this._serverCfg.spaces=this._serverCfg.spaces.filter(e=>e.id!==t.spaceId);try{await this._saveConfigNow(),this._spaceDialog=null,this._space===t.spaceId&&(this._space=this._serverCfg.spaces[0]?.id||""),this._regSignature="",this._maybeRebuildDevices(),this._showToast(this._t("toast.space_deleted"))}catch(t){this._showToast(this._t("toast.delete_failed",{err:this._errText(t)}))}}}async _saveConfigNow(){this._dropLegacySegments();try{const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1}catch(t){throw"conflict"===t?.code&&await this._reloadConfigOnly(),t}}_startImport(){const t=this._importDialog;if(!t)return;const e=t.floors.filter(t=>t.checked).map(t=>t.name);this._importDialog=null,e.length?(this._importQueue=e,this._importTotal=e.length,this._openNextImport()):this._openSpaceDialog("create")}_openNextImport(){const t=this._importQueue.shift();void 0!==t&&(this._spaceDialog={mode:"create",title:t,planUrl:null,planFile:null,source:"file",orientation:"landscape",showBorders:!1,showNames:!1,roomColor:Ge,roomOpacity:Ve,fillMode:"none",tempMin:20,tempMax:25,showLqi:this._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:5,busy:!1})}_skipImport(){this._spaceDialog=null,this._importQueue.length?this._openNextImport():this._importTotal>0&&this._model.length&&(this._importTotal=0,this._space=this._serverCfg.spaces[0]?.id||this._space,this._mode="plan",this._showToast(this._t("import.done")))}_renderImportDialog(){const t=this._importDialog,e=t.floors.filter(t=>t.checked).length;return B``}get _glowRadiusCm(){const t=Number(this._settings.glow_radius_cm);return Number.isFinite(t)&&t>0?t:300}get _imperial(){return"mi"===this.hass?.config?.unit_system?.length}get _glowRadiusPlaceholder(){const t=this._glowRadiusCm;return this._imperial?String(Math.round(t/30.48*10)/10):String(t/100)}_renderGlowLayer(t){const e=this._fillColors,i=this._glowRadiusCm/this._cellCm*this._gridPitch,s=this._gridPitch,o=t.rooms.map(t=>({r:t,poly:we(t)})).filter(t=>!!t.poly),n=this._openingsR.filter(t=>"door"===t.type),r=[];for(const a of this._devices){if(a.space!==t.id)continue;const l=a.entities.find(t=>t.startsWith("light.")&&"on"===this.hass.states[t]?.state);if(!l)continue;const c=ii(this.hass.states[l],e.glow_light.c);if(!c)continue;const h=Number(a.marker?.glow_radius_cm),p=Number.isFinite(h)&&h>0?h/this._cellCm*this._gridPitch:i,d=this._pos(a),u=[...o].reverse().find(t=>this._pointInRoom([d.x,d.y],t.r));let _=null;if(u){const e=u.r.id?ai(u.r.id,t.rooms):new Set([u.r.id]),i=o.filter(t=>t.r.id&&e.has(t.r.id)),r=i.length?i:[u],a=r.map(t=>"M "+t.poly.map(t=>t[0]+" "+t[1]).join(" L ")+" Z"),l=o.filter(t=>!r.includes(t)).map(t=>t.poly);for(const t of n){const e=r.some(e=>{const i=De([t.rx,t.ry],e.poly);return i&&Math.hypot(i[0]-t.rx,i[1]-t.ry)<=.75*s});if(!e)continue;const i=t.angle*Math.PI/180,o=Math.cos(i)*t.rlen/2,n=Math.sin(i)*t.rlen/2;if(!oi([t.rx,t.ry],t.angle,[d.x,d.y],l,.6*s))continue;const c=si([d.x,d.y],[t.rx-o,t.ry-n],[t.rx+o,t.ry+n],p);c&&a.push("M "+c.map(t=>t[0]+" "+t[1]).join(" L ")+" Z")}_=a}r.push({pos:d,c:c.c,alpha:e.glow_light.a*c.bri,clip:_,r:p})}return r.length?j` ${r.map((t,e)=>j` @@ -1648,7 +1648,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- @@ -1701,7 +1701,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
- `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Zi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B` + `}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(Yi,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`
this._saveKioskScale({[e]:Number(t.target.value)/100})} /> @@ -1735,7 +1735,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._spaceDialog?this._renderSpaceDialog():G} ${this._importDialog?this._renderImportDialog():G} ${this._toast?B`
${this._toast}
`:G} - `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ke(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B` + `;const e=this._spaceModel(),i=e.vb,s=this._devices.filter(t=>t.space===e.id),o=Ze(this._curSpaceCfg),n=o.showLqi??this._config.show_signal??!0,r=this._config.icon_size??2.5,a=r>8?2.5:r,l=this._viewOr(i);return B`
@@ -1795,17 +1795,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";const r=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,t);if(!this._markup&&(o.showBorders||"none"!==r)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===r?this._fillColors.glow_base:"temp"===r?Qe("temp",null,"none",this._roomTemp(t),o.tempMin,o.tempMax,this._fillColors):t.area?Qe(r,"lqi"===r?this._roomLqi(t.area):null,"light"===r?xi(this.hass,this._devices,t.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,this._roomTemp(t)),l=!e.bg&&!o.showNames&&!this._markup,c=this._roomCenter(t),h=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),p=t.id&&!h?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];p.length&&(i+=" noedge");const d=we(t),u=d?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(d,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],_=u.length&&d?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" - @mousemove=${a} - @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._tip=null}>`:j`this._tip=null}>`,m=p.length&&d?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${_}${g}${l?j`${t.name}`:G}`})} + ${(()=>{const t=this._openPairs(),i=new Map,s=t=>(i.has(t)||i.set(t,we(t)),i.get(t));return e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(i=>{let r="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||i.id!==this._mergeSel&&i.id!==this._splitSel?.roomId||(r+=" picked");let a="";const l=function(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}(o.fill,i);if(!this._markup&&(o.showBorders||"none"!==l)){r+=" styled";const t=[];t.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const e="glow"===l?this._fillColors.glow_base:"temp"===l?ti("temp",null,"none",this._roomTemp(i),o.tempMin,o.tempMax,this._fillColors):i.area?ti(l,"lqi"===l?this._roomLqi(i.area):null,"light"===l?$i(this.hass,this._devices,i.area):"none",null,o.tempMin,o.tempMax,this._fillColors):null;e?(r+=" filled",t.push(`--room-fill:${e.c}`,`--room-fill-op:${e.a.toFixed(3)}`)):t.push("--room-fill:transparent","--room-fill-op:0"),a=t.join(";")}const c=t=>this._showTip(t,i.name,this._t("tip.room"),n?this._roomLqi(i.area):null,this._roomTemp(i)),h=!e.bg&&!o.showNames&&!this._markup,p=this._roomCenter(i),d=this._markup&&(i.id===this._mergeSel||i.id===this._splitSel?.roomId),u=i.id&&!d?t.filter(t=>t.a.id===i.id||t.b.id===i.id).flatMap(t=>t.segs):[];u.length&&(r+=" noedge");const _=s(i),m=_?function(t,e,i=1e-6){const s=e.filter(e=>Ne(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Ne(e,t,i)))}(_,(g=i,e.rooms.filter(t=>t!==g).map(s).filter(Boolean))):[];var g;const f=m.length&&_?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + @mousemove=${c} + @mouseleave=${()=>this._tip=null}>`:i.poly?j`this._tip=null}>`:j`this._tip=null}>`,b=u.length&&_?function(t,e,i=1e-6){const s=[];for(let e=0;e`:G;return j`${f}${v}${h?j`${i.name}`:G}`})})()} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._editing?this._renderAlignGuides():G} @@ -1845,7 +1845,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._kioskDialog?this._renderKioskDialog():G} ${this._toast?B`
${this._toast}
`:G} - `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?mi(this.hass,t.entities):null,h=t.marker,p=h?.display||"badge",d="ripple"===p||"icon_ripple"===p,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===p?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?function(t,e,i,s,o){if(o||!s||"unavailable"===s||"unknown"===s)return t;if("binary_sensor"===e){if("door"===i)return"on"===s?"mdi:door-open":"mdi:door-closed";if("window"===i)return"on"===s?"mdi:window-open":"mdi:window-closed";if("garage_door"===i)return"on"===s?"mdi:garage-open-variant":"mdi:garage-variant"}return"lock"===e?"locked"===s?"mdi:lock":"mdi:lock-open-variant":"light"===e&&"mdi:lightbulb"===t&&"on"===s?"mdi:lightbulb-on":t}(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(oi),b=this._config?.live_states?f.length?f.map(t=>ti(this.hass.states[t])).find(t=>t)||null:"light"===m?ti(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&pi.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
ei(this.hass.states[t])).find(t=>t)||null:"light"===m?ei(u):null:null,v=this._config?.live_states&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&di.has(e))}(m,u?.attributes?.device_class,u?.state),y=d&&!!t.primary&&ke(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,$=Number(h?.ripple_size)>0?Number(h.ripple_size):3,k=[`left:${o}%`,`top:${n}%`];return 1!==w&&k.push(`--dev-scale:${w}`),d&&(k.push(`--ripple-scale:${$}`),h?.ripple_color?k.push(`--ripple-color:${h.ripple_color}`):b&&k.push(`--ripple-color:${b}`)),b&&k.push(`--light-color:${b}`),B`
this._clickDevice(e,t)} @@ -1863,8 +1863,8 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${null!=a&&null==_?B`${a}°`:G} ${null!=l&&null==_?B`${l}%`:G} ${null!=c?B`${c}`:G} -
`}_roomTemp(t){const e=t.settings?.temp_source;return e?$i(this.hass,e,"temp"):t.area?ki(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?$i(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=bi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ci(t.settings?.name_scale),this._roomLabelScale=ci(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Yi,y:i.y*(Yi/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Yi,y:o.y/(Yi/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
`}_roomTemp(t){const e=t.settings?.temp_source;return e?ki(this.hass,e,"temp"):t.area?Si(this.hass,this._devices,t.area):null}_roomHum(t){const e=t.settings?.hum_source;return e?ki(this.hass,e,"hum"):t.area?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon&&"mdi:water-percent"!==o.icon)continue;const e=vi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length):null}(this.hass,this._devices,t.area):null}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){t.id&&(this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill=t.settings?.fill_mode||"",this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=hi(t.settings?.name_scale),this._roomLabelScale=hi(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0)}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=this._roomSettingsFromDialog();i?e.settings=i:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this.hass,e=this._roomSrcFilter.trim().toLowerCase(),i=[];for(const s of Object.values(t.devices)){if("service"===s.entry_type)continue;const t=(s.name_by_user||s.name||s.id).trim();e&&!t.toLowerCase().includes(e)||i.push({value:"device:"+s.id,label:t,sub:s.model||this._t("marker.sub_device")})}for(const[s,o]of Object.entries(t.entities)){if(!s.startsWith("sensor.")||o.hidden)continue;const n=o.name||t.states[s]?.attributes?.friendly_name||s;e&&!(n+" "+s).toLowerCase().includes(e)||i.push({value:"entity:"+s,label:n,sub:s})}return i.sort((t,e)=>t.label.localeCompare(e.label)),i.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this.hass.devices[s]?.name_by_user||this.hass.devices[s]?.name||s:this.hass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e){const t=this._serverCfg.spaces.find(t=>t.id===e)?.aspect||1;return{x:i.x*Ji,y:i.y*(Ji/t)}}const s=this._roomCenter(t);return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},t.target.setPointerCapture(t.pointerId),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=.008*Math.min(n[2],n[3]),p=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),d=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},p,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},t.target.setPointerCapture(t.pointerId)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space),n=this._serverCfg.spaces.find(t=>t.id===e.space)?.aspect||1;this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/Ji,y:o.y/(Ji/n),k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomLabel(t,e,i,s){if(!t.name)return G;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if((t.area||t.settings?.temp_source||t.settings?.hum_source)&&!this._markup){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight&&t.area){const e=function(t,e,i){const s=new Set;let o=0;for(const n of e)if(n.area===i)for(const e of n.entities)e.startsWith("light.")&&!s.has(e)&&(s.add(e),"on"===t.states[e]?.state&&o++);return s.size?{on:o,total:s.size}:null}(this.hass,this._devices,t.area);if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -1883,7 +1883,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @pointerup=${()=>this._rlResizeUp()} @pointercancel=${()=>this._rlResizeUp()}>`):G}
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"===this._tool&&this._path.length&&!this._contourClosed?this._path[this._path.length-1]:"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=function(t,e){let i=180*Math.atan2(e[1]-t[1],e[0]-t[0])/Math.PI;return i<0&&(i+=360),i}(e,i),r=Math.round(10*n)/10,a=function(t,e=.5){const i=(t%45+45)%45;return i<=e||45-i<=e}(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Yi,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Yi,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e + ${this._fmtLen(e,i)} · ${r}°
`}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=Ji,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const i=Ji,s=this._decorH,o=this._decorMove?.id;for(const e of this._decorList)e.id!==o&&("line"===e.kind?t.push([e.x1*i,e.y1*s],[e.x2*i,e.y2*s]):"text"===e.kind?t.push([e.x*i,e.y*s]):t.push([e.x*i,e.y*s],[(e.x+e.w)*i,e.y*s],[e.x*i,(e.y+e.h)*s],[(e.x+e.w)*i,(e.y+e.h)*s]));this._decorDraft&&t.push(this._decorDraft.a);for(const i of e.rooms){const e=we(i);if(e)for(const i of e)t.push(i)}return t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` `})} `}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact?this.hass.states[t.contact]?.state:null;return function(t,e,i=!1){return null==e||"unavailable"===e||"unknown"===e?"door"===t?1:0:ke(e)!==!!i?1:0}(t.type,e,!!t.invert)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color;return j`${e.map(t=>{const e=t.rlen/2,s=this._openingAmt(t),o=s>0&&!!t.contact?"var(--hp-open)":i,n=t.flip_h?-1:1,r=t.flip_v?-1:1;let a;if("window"===t.type){const t=Math.PI/2*e;a=j` @@ -2000,7 +2000,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?ai(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` + `}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?li(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j` ${i.map(t=>j``)} ${s.length>1?j``:G} @@ -2068,17 +2068,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow @click=${()=>this._setMode("view")}> -
`}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(oi);return B``}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?this.hass.formatEntityState?.(e)??e.state:null,s=(t.marker?.controls||[]).filter(ni);return B`
-
`}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.1 ","background:#3ea6ff;color:#04121f;font-weight:700",""); +
`}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/custom_components/houseplan/manifest.json b/custom_components/houseplan/manifest.json index 4a5ff41..4c161b7 100755 --- a/custom_components/houseplan/manifest.json +++ b/custom_components/houseplan/manifest.json @@ -16,5 +16,5 @@ "issue_tracker": "https://github.com/Matysh/houseplan-card/issues", "requirements": [], "single_config_entry": true, - "version": "1.43.1" + "version": "1.43.2" } diff --git a/demo/serve.mjs b/demo/serve.mjs index ecee82e..d9bb1c9 100644 --- a/demo/serve.mjs +++ b/demo/serve.mjs @@ -6,10 +6,45 @@ import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const ROOT = dirname(fileURLToPath(import.meta.url)) + '/srv'; const CT = { '.html': 'text/html', '.js': 'text/javascript', '.svg': 'image/svg+xml' }; +// ---- assertion harness (audit T1) -------------------------------------- +// Until 2026-07-27 the smokes printed booleans and always exited 0: a broken +// build reported success. `check()` accumulates named failures, `finish()` +// prints them and sets the exit code. +const _failures = []; +let _pageErrors = 0; + +/** Assert one named fact. `expected` defaults to true. */ +export function check(name, actual, expected = true) { + const ok = JSON.stringify(actual) === JSON.stringify(expected); + if (!ok) _failures.push(`${name}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + return ok; +} + +/** Assert a whole result object: every key must equal true unless listed. */ +export function checkAll(out, expected = {}) { + for (const [k, v] of Object.entries(out)) check(k, v, k in expected ? expected[k] : true); + return out; +} + +/** Print the result, report failures, close the browser, set the exit code. */ +export async function finish(browser, out) { + if (out !== undefined) console.log(JSON.stringify(out, null, 1)); + if (_pageErrors) _failures.push(`${_pageErrors} uncaught exception(s) inside the card`); + await browser?.close?.(); + if (_failures.length) { + console.error('\nFAILED (' + _failures.length + '):'); + for (const f of _failures) console.error(' - ' + f); + process.exitCode = 1; + } else { + console.log('OK'); + } +} + export async function launch(viewport = { width: 820, height: 760 }, scale = 1) { const browser = await chromium.launch({ args: ['--no-sandbox'] }); const page = await (await browser.newContext({ viewport, deviceScaleFactor: scale })).newPage(); - page.on('pageerror', (e) => console.log('EXC', e.message)); + // audit T1: an exception inside the card used to be logged and ignored + page.on('pageerror', (e) => { _pageErrors++; console.log('EXC', e.message); }); await page.route('**/*', (r) => { const u = new URL(r.request().url()); let p = decodeURIComponent(u.pathname); diff --git a/demo/smoke_align_guides.mjs b/demo/smoke_align_guides.mjs index b0e5a2d..e0de975 100644 --- a/demo/smoke_align_guides.mjs +++ b/demo/smoke_align_guides.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -52,5 +52,5 @@ const res = await page.evaluate(async () => { out.noneInView = guides() === 0; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_binding_ui.mjs b/demo/smoke_binding_ui.mjs index 028a14d..5fe8c95 100644 --- a/demo/smoke_binding_ui.mjs +++ b/demo/smoke_binding_ui.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -46,5 +46,5 @@ const res = await page.evaluate(async () => { c._markerDialog = null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_card_tool_conflict.mjs b/demo/smoke_card_tool_conflict.mjs index 6088fd0..7fe2e28 100644 --- a/demo/smoke_card_tool_conflict.mjs +++ b/demo/smoke_card_tool_conflict.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -47,5 +47,5 @@ const res = await page.evaluate(async () => { out.delroomIgnored = !confirmCalled; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_controls.mjs b/demo/smoke_controls.mjs index 5097daa..2ebc7b6 100644 --- a/demo/smoke_controls.mjs +++ b/demo/smoke_controls.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -57,5 +57,5 @@ const res = await page.evaluate(async () => { out.lockFiltered = JSON.stringify(last) === JSON.stringify(['homeassistant', 'turn_on', [lights[0]]]); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_decor.mjs b/demo/smoke_decor.mjs index 5cf64dd..3d0b7e9 100644 --- a/demo/smoke_decor.mjs +++ b/demo/smoke_decor.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -70,5 +70,5 @@ const res = await page.evaluate(async () => { out.deleteKey = c._decorList.length === n1 - 1; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_dialog_zombie.mjs b/demo/smoke_dialog_zombie.mjs index 613eb0b..6638b2e 100644 --- a/demo/smoke_dialog_zombie.mjs +++ b/demo/smoke_dialog_zombie.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -51,5 +51,5 @@ const res = await page.evaluate(async () => { c._settingsDialog = null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_edge_cases.mjs b/demo/smoke_edge_cases.mjs index ef7236d..e36d39c 100644 --- a/demo/smoke_edge_cases.mjs +++ b/demo/smoke_edge_cases.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, check, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -70,5 +70,18 @@ const res = await page.evaluate(async () => { out.bigRenderMs = Math.round(performance.now() - t1); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +// сборка 100+ устройств должна укладываться в бюджет (docs/TESTING.md) +// timings are asserted as budgets, not frozen values (CI machines vary) +check("bigBuildMs under 200ms", res.bigBuildMs < 200); +check("bigRenderMs under 100ms", res.bigRenderMs < 100); +delete res.bigBuildMs; +delete res.bigRenderMs; +checkAll(res, { + "emptyDevices": 0, + "emptyCount": "0 dev.", + "xssPwned": false, + "xssDeviceRendered": 1, + "bigCount": 162, +}); +await finish(browser, res); diff --git a/demo/smoke_editor_tabs.mjs b/demo/smoke_editor_tabs.mjs index 0ee36cf..d64b9f5 100644 --- a/demo/smoke_editor_tabs.mjs +++ b/demo/smoke_editor_tabs.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -35,5 +35,8 @@ const res = await page.evaluate(async () => { out.tabCrossWorks = c._mode === 'view'; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "labels": ["Plan editor", "Device editor", "Background editor"], +}); +await finish(browser, res); diff --git a/demo/smoke_esc_dialogs.mjs b/demo/smoke_esc_dialogs.mjs index 2760141..5aafe5c 100644 --- a/demo/smoke_esc_dialogs.mjs +++ b/demo/smoke_esc_dialogs.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -31,5 +31,5 @@ const res = await page.evaluate(async () => { await esc(); out.undoPointStillWorks = c._path.length === 1; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_font_scales.mjs b/demo/smoke_font_scales.mjs index f0776a6..d0cf1db 100644 --- a/demo/smoke_font_scales.mjs +++ b/demo/smoke_font_scales.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -54,5 +54,5 @@ const res = await page.evaluate(async () => { c._spaceDialog = null; await c.updateComplete; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_gear_tabs.mjs b/demo/smoke_gear_tabs.mjs index 437281d..45fae44 100644 --- a/demo/smoke_gear_tabs.mjs +++ b/demo/smoke_gear_tabs.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -31,5 +31,8 @@ const res = await page.evaluate(async () => { out.addInPlan = !!sr().querySelector('.tab.tabadd'); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "alignDelta": 0, +}); +await finish(browser, res); diff --git a/demo/smoke_general_settings.mjs b/demo/smoke_general_settings.mjs index 9ae47fb..9d8515d 100644 --- a/demo/smoke_general_settings.mjs +++ b/demo/smoke_general_settings.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -26,5 +26,12 @@ const res = await page.evaluate(async () => { out.lqiAfter = sr().querySelectorAll('.dev .lqi').length; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "rows": 11, + "groups": ["Fill: lights", "Fill: temperature", "Fill: zigbee signal", "Light-sources fill"], + "saved": {"c": "#ff00ff", "a": 0.5}, + "lqiBefore": 7, + "lqiAfter": 0, +}); +await finish(browser, res); diff --git a/demo/smoke_glow.mjs b/demo/smoke_glow.mjs index fce1141..dd686bc 100644 --- a/demo/smoke_glow.mjs +++ b/demo/smoke_glow.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -101,5 +101,8 @@ const res = await page.evaluate(async () => { out.radiusReacts = Math.abs(r600 / r300 - 2) < 0.01; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "spots": 1, +}); +await finish(browser, res); diff --git a/demo/smoke_grid_fade.mjs b/demo/smoke_grid_fade.mjs index ade64f4..315cbc9 100644 --- a/demo/smoke_grid_fade.mjs +++ b/demo/smoke_grid_fade.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -33,5 +33,5 @@ const res = await page.evaluate(async () => { out.notFadedInView = room3 ? Number(getComputedStyle(room3).opacity) > 0.9 : null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_gs_always.mjs b/demo/smoke_gs_always.mjs index 881f90f..703eb3e 100644 --- a/demo/smoke_gs_always.mjs +++ b/demo/smoke_gs_always.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -14,5 +14,5 @@ const res = await page.evaluate(async () => { out.opensInView = !!c._settingsDialog; return out; }); -console.log(JSON.stringify(res)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_icon_placeholder.mjs b/demo/smoke_icon_placeholder.mjs index f108bcc..13e9478 100644 --- a/demo/smoke_icon_placeholder.mjs +++ b/demo/smoke_icon_placeholder.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -27,5 +27,5 @@ const res = await page.evaluate(async () => { c._markerDialog = null; await c.updateComplete; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_inert_openings.mjs b/demo/smoke_inert_openings.mjs index b8d722b..6dd93b1 100644 --- a/demo/smoke_inert_openings.mjs +++ b/demo/smoke_inert_openings.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -43,5 +43,10 @@ const res = await page.evaluate(async () => { c._openingDialog = null; c._setMode('view'); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "lockBadgeInert": "no-badge", + "viewDevCursor": "pointer", + "devModeCursor": "grab", +}); +await finish(browser, res); diff --git a/demo/smoke_island_rooms.mjs b/demo/smoke_island_rooms.mjs index 30784d6..9e3194b 100644 --- a/demo/smoke_island_rooms.mjs +++ b/demo/smoke_island_rooms.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -55,5 +55,5 @@ const res = await page.evaluate(async () => { out.islandRendered = !!islandEl; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_kiosk.mjs b/demo/smoke_kiosk.mjs index 69e443f..444653f 100644 --- a/demo/smoke_kiosk.mjs +++ b/demo/smoke_kiosk.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -69,5 +69,5 @@ const res = await page.evaluate(async () => { c.remove(); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_light_default_tap.mjs b/demo/smoke_light_default_tap.mjs index 1f66e69..5352942 100644 --- a/demo/smoke_light_default_tap.mjs +++ b/demo/smoke_light_default_tap.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -30,5 +30,5 @@ const res = await page.evaluate(async () => { out.explicitInfoWins = calls.length === n2 && !!c._infoCard; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_lock_action.mjs b/demo/smoke_lock_action.mjs index c1e0c6b..8898436 100644 --- a/demo/smoke_lock_action.mjs +++ b/demo/smoke_lock_action.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -47,5 +47,5 @@ const res = await page.evaluate(async () => { } return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_marker_stay.mjs b/demo/smoke_marker_stay.mjs index 242b989..074381a 100644 --- a/demo/smoke_marker_stay.mjs +++ b/demo/smoke_marker_stay.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -42,5 +42,5 @@ const res = await page.evaluate(async () => { out.newCentered = vpos && Math.abs(vpos.x * 1000 - center[0]) < 1 && Math.abs(vpos.y * (1000 / aspect) - center[1]) < 1; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_merge_highlight.mjs b/demo/smoke_merge_highlight.mjs index 43c051a..bfe6c11 100644 --- a/demo/smoke_merge_highlight.mjs +++ b/demo/smoke_merge_highlight.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -20,5 +20,5 @@ const res = await page.evaluate(async () => { out.splitPicked = !!el2 && getComputedStyle(el2).stroke.includes('255, 193, 77'); return out; }); -console.log(JSON.stringify(res)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_merge_split.mjs b/demo/smoke_merge_split.mjs index a9fc501..04275b1 100644 --- a/demo/smoke_merge_split.mjs +++ b/demo/smoke_merge_split.mjs @@ -1,5 +1,5 @@ // Merge & split room ops (v1.21.0) via the card's markup handlers (norm coords). -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const snap = await page.evaluate(() => JSON.stringify(window.__card._serverCfg)); const restore = () => page.evaluate((s) => { @@ -79,5 +79,9 @@ await page.evaluate((p)=>window.__card._splitClick(p), await R(0.4,0.0625)); s = await S(); out.alongWallRefused = !s.roomDlg && !s.pendingSplit; -console.log(JSON.stringify(out,null,1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(out, { + "mergeRooms": 3, + "newRoom": "Cabinet", +}); +await finish(browser, out); diff --git a/demo/smoke_modes.mjs b/demo/smoke_modes.mjs index 7a727e5..8c0ace3 100644 --- a/demo/smoke_modes.mjs +++ b/demo/smoke_modes.mjs @@ -1,5 +1,5 @@ // UX modes shell (v1.25.0): view is display-only; plan/devices gate the tools. -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const out = {}; const q = (sel) => page.evaluate((s) => (window.__card.shadowRoot || window.__card.renderRoot).querySelectorAll(s).length, sel); @@ -75,5 +75,12 @@ out.devClickOpensEditor = await page.evaluate(async () => { // 5) назад в view await page.evaluate(() => window.__card._setMode('view')); out.backToView = (await st()).mode; -console.log(JSON.stringify(out, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(out, { + "start": {"mode": "view", "modeTabs": 3, "editBtns": 1, "gears": 2, "markupBar": false, "stageClass": "stage mode-view"}, + "viewDragMoved": false, + "plan": {"mode": "plan", "modeTabs": 3, "active": "Plan editor", "editBtns": 1, "gears": 2, "markupBar": true, "stageClass": "stage markup tool-draw mode-plan"}, + "devices": {"mode": "devices", "modeTabs": 3, "active": "Device editor", "editBtns": 1, "gears": 2, "markupBar": true, "stageClass": "stage mode-devices"}, + "backToView": "view", +}); +await finish(browser, out); diff --git a/demo/smoke_nav_persist.mjs b/demo/smoke_nav_persist.mjs index 92213ce..3aeaba2 100644 --- a/demo/smoke_nav_persist.mjs +++ b/demo/smoke_nav_persist.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -31,5 +31,5 @@ const res = await page.evaluate(async () => { c._setMode('view'); c._space = 'f1'; c._saveNav(); await c.updateComplete; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_new_device.mjs b/demo/smoke_new_device.mjs index af31b07..707aa7a 100644 --- a/demo/smoke_new_device.mjs +++ b/demo/smoke_new_device.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -28,5 +28,5 @@ const res = await page.evaluate(async () => { out.stillKnown = c._serverCfg.settings.known_devices.includes('d_new'); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_opening_preview.mjs b/demo/smoke_opening_preview.mjs index 4829dbc..641c418 100644 --- a/demo/smoke_opening_preview.mjs +++ b/demo/smoke_opening_preview.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -39,5 +39,5 @@ const res = await page.evaluate(async () => { out.noGhostOverExisting = !sr().querySelector('.opghost'); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_openwall.mjs b/demo/smoke_openwall.mjs index bce66c2..aecd4bb 100644 --- a/demo/smoke_openwall.mjs +++ b/demo/smoke_openwall.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -70,5 +70,5 @@ const res = await page.evaluate(async () => { } else out.transitive = 'no r3'; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_openwall_hover.mjs b/demo/smoke_openwall_hover.mjs index c7a96e3..5e8c18f 100644 --- a/demo/smoke_openwall_hover.mjs +++ b/demo/smoke_openwall_hover.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -32,5 +32,5 @@ const res = await page.evaluate(async () => { c._openWallClick([550, 0.25 * H]); await c.updateComplete; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_render_perf.mjs b/demo/smoke_render_perf.mjs index be1fe41..4844a7f 100644 --- a/demo/smoke_render_perf.mjs +++ b/demo/smoke_render_perf.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -32,5 +32,9 @@ const res = await page.evaluate(async () => { out.dashesStillRendered = (c.shadowRoot || c.renderRoot).querySelectorAll('.openwall').length > 0; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "pairsPerRender": 0, + "modelBuildsPer10Renders": 0, +}); +await finish(browser, res); diff --git a/demo/smoke_rgb_alarm.mjs b/demo/smoke_rgb_alarm.mjs index 6fa792e..afd09fc 100644 --- a/demo/smoke_rgb_alarm.mjs +++ b/demo/smoke_rgb_alarm.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -28,5 +28,8 @@ const res = await page.evaluate(async () => { out.outageSafe = sr().querySelectorAll('.dev.alarm').length === 0; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "alarmCount": 1, +}); +await finish(browser, res); diff --git a/demo/smoke_room_cards.mjs b/demo/smoke_room_cards.mjs index 1b01081..ed76ce4 100644 --- a/demo/smoke_room_cards.mjs +++ b/demo/smoke_room_cards.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -57,5 +57,11 @@ const res = await page.evaluate(async () => { out.dragKeepsScale = c._layout['rl_' + room.id].k <= 3 && c._layout['rl_' + room.id].k >= 2.9; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "labels": 4, + "cardsWithMetrics": 4, + "sampleMetrics": ["22.4°", "175", "1 of 2"], + "partialText": "1 of 2", +}); +await finish(browser, res); diff --git a/demo/smoke_room_link.mjs b/demo/smoke_room_link.mjs index 428bcd0..286865c 100644 --- a/demo/smoke_room_link.mjs +++ b/demo/smoke_room_link.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -34,5 +34,5 @@ const res = await page.evaluate(async () => { c._setMode('view'); await c.updateComplete; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_room_settings.mjs b/demo/smoke_room_settings.mjs index 6367322..e0e7e1e 100644 --- a/demo/smoke_room_settings.mjs +++ b/demo/smoke_room_settings.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -58,5 +58,5 @@ const res = await page.evaluate(async () => { out.glowOptOut = darkCount === areaRooms - 1 && darkCount === styledRooms.length; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_save_race.mjs b/demo/smoke_save_race.mjs index 7d607b0..81640e8 100644 --- a/demo/smoke_save_race.mjs +++ b/demo/smoke_save_race.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -32,5 +32,5 @@ const res = await page.evaluate(async () => { out.serverHasIt = server.cfg.spaces.some((s) => s.rooms?.some((r) => r.id === 'race_room')); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_space_settings.mjs b/demo/smoke_space_settings.mjs index 72cdb37..b416a1b 100644 --- a/demo/smoke_space_settings.mjs +++ b/demo/smoke_space_settings.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -27,7 +27,8 @@ const res = await page.evaluate(async () => { })}; c.requestUpdate(); await c.updateComplete; out.lqiFills = [...sr().querySelectorAll('.room.styled')].filter((r) => (r.getAttribute('style') || '').includes('hsl(')).length; - // 4) drag лейбла → layout rl_ + // 4) drag лейбла → layout rl_ (только в редакторе плана, с v1.25) + c._setMode('plan'); await c.updateComplete; const lbl = sr().querySelector('.roomlabel'); c._labelDown({ preventDefault(){}, stopPropagation(){}, clientX: 100, clientY: 100, target: { setPointerCapture(){} }, pointerId: 5 }, c._spaceModel().rooms[0], 'f1'); @@ -46,5 +47,15 @@ const res = await page.evaluate(async () => { out.atticNoPlan = attic ? attic.plan_url === null : null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "defaultStyled": 0, + "defaultLabels": 0, + "styled": 4, + "labels": ["Living room", "Kitchen", "Bedroom", "Hallway"], + "livingStyle": "--room-stroke:#ff8800;--room-stroke-op:0.8;--room-fill:#ffd45c;--room-fill-op:0.180", + "lqiFills": 0, + "atticAspect": 1, + "atticSettings": {"show_borders": true, "show_names": true, "room_color": "#3ea6ff", "room_opacity": 0.55, "fill_mode": "none", "temp_min": 20, "temp_max": 25, "show_lqi": true, "label_temp": false, "label_hum": false, "label_lqi": false, "label_light": false}, +}); +await finish(browser, res); diff --git a/demo/smoke_split_nonsnap.mjs b/demo/smoke_split_nonsnap.mjs index 4a59fa2..816d8b9 100644 --- a/demo/smoke_split_nonsnap.mjs +++ b/demo/smoke_split_nonsnap.mjs @@ -1,5 +1,5 @@ // Split now works on a non-grid-aligned room (imported/legacy polygons). -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const R = (nx, ny) => page.evaluate(([nx, ny]) => { const c = window.__card; const H = 1000 / c._curSpaceCfg.aspect; return [nx * 1000, ny * H]; @@ -17,5 +17,5 @@ await page.evaluate(()=>{const c=window.__card; c._roomDialog=false; c._pendingS await page.evaluate((p)=>window.__card._splitClick(p), await R(0.3,0.3)); // pick again await page.evaluate((p)=>window.__card._splitClick(p), await R(0.3,0.3)); // centre click = miss out.centreRefused = await page.evaluate(()=>window.__card._splitSel?.a == null); -console.log(JSON.stringify(out)); -await browser.close(); +checkAll(out); +await finish(browser, out); diff --git a/demo/smoke_split_polyline.mjs b/demo/smoke_split_polyline.mjs index 4a47481..1c8a400 100644 --- a/demo/smoke_split_polyline.mjs +++ b/demo/smoke_split_polyline.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -51,5 +51,8 @@ const res = await page.evaluate(async () => { await esc(); out.escExitsMerge = c._tool === 'draw'; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "partsPolys": [6, 4], +}); +await finish(browser, res); diff --git a/demo/smoke_state_value.mjs b/demo/smoke_state_value.mjs index f525048..03da30d 100644 --- a/demo/smoke_state_value.mjs +++ b/demo/smoke_state_value.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -30,5 +30,14 @@ const res = await page.evaluate(async () => { out.noSmallBadge = !vd[0]?.querySelector('.tval'); return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "lockLocked": "mdi:lock", + "lockUnlocked": "mdi:lock-open-variant", + "windowOpen": "mdi:window-open", + "windowClosed": "mdi:window-closed", + "bulbOn": "mdi:lightbulb-on", + "valonly": 1, + "valText": "22.4°", +}); +await finish(browser, res); diff --git a/demo/smoke_subarea.mjs b/demo/smoke_subarea.mjs index fe49f14..edaa0ab 100644 --- a/demo/smoke_subarea.mjs +++ b/demo/smoke_subarea.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -31,5 +31,9 @@ const res = await page.evaluate(async () => { c._markerDialog = null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "markerRoomId": "rc", + "reopenRoom": "f1#@rc", +}); +await finish(browser, res); diff --git a/demo/smoke_tap_ctx.mjs b/demo/smoke_tap_ctx.mjs index 8a255e9..1162d64 100644 --- a/demo/smoke_tap_ctx.mjs +++ b/demo/smoke_tap_ctx.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -6,7 +6,9 @@ const res = await page.evaluate(async () => { const sr = () => c.shadowRoot || c.renderRoot; // 1) в селекте действий 3 опции, дефолт «Карточка устройства» c._setMode('devices'); await c.updateComplete; - const dev = c._devices.find((d) => !d.virtual && d.primary); + // v1.39.0: у ЛАМП дефолт 'toggle', поэтому для проверки дефолта 'info' + // берём заведомо не-световое устройство + const dev = c._devices.find((d) => !d.virtual && d.primary && !d.primary.startsWith('light.')); c._openMarkerDialog(dev); await c.updateComplete; const sel = [...sr().querySelectorAll('.dialog select')].find((s) => [...s.options].some((o) => o.textContent === c._t('tap.toggle'))); @@ -41,14 +43,19 @@ const res = await page.evaluate(async () => { const calls = []; c.hass = { ...c.hass, callService: (d2, s2, data) => { calls.push([d2, s2, data]); return Promise.resolve(); } }; await c.updateComplete; - const plain = c._devices.find((d) => !d.virtual && d.primary?.startsWith('light.') && !d.tapAction); + // card-wide tap_action игнорируется: НЕ-световое устройство остаётся на инфо + const plain = c._devices.find((d) => !d.virtual && d.primary + && !d.primary.startsWith('light.') && !d.tapAction && !d.marker?.controls?.length); if (plain) { c._infoCard = null; c._clickDevice(new MouseEvent('click'), plain); out.cardTapIgnored = calls.length === 0 && !!c._infoCard; c._infoCard = null; - } else out.cardTapIgnored = 'no-plain-light'; + } else out.cardTapIgnored = 'no-plain-device'; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "virtInfo": "no-virt", +}); +await finish(browser, res); diff --git a/demo/smoke_temp_fill.mjs b/demo/smoke_temp_fill.mjs index c608e42..106ef78 100644 --- a/demo/smoke_temp_fill.mjs +++ b/demo/smoke_temp_fill.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); const res = await page.evaluate(async () => { const out = {}; @@ -26,5 +26,13 @@ const res = await page.evaluate(async () => { c._spaceDialog = null; return out; }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "comfy": ["#66d17a", "transparent", "transparent", "transparent"], + "cold": ["#4fc3f7", "transparent", "transparent", "transparent"], + "hot": ["#ffd45c", "transparent", "transparent", "transparent"], + "swapped": ["#66d17a", "transparent", "transparent", "transparent"], + "dialogTempFields": 3, + "dialogHiddenWhenNone": 1, +}); +await finish(browser, res); diff --git a/demo/smoke_touch_tips.mjs b/demo/smoke_touch_tips.mjs index 3a9ba4f..85a3897 100644 --- a/demo/smoke_touch_tips.mjs +++ b/demo/smoke_touch_tips.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch(); // эмуляция тач-устройства: переопределяем matchMedia ДО загрузки бандла await page.addInitScript(() => { @@ -19,5 +19,5 @@ const res = await page.evaluate(async () => { out.noTipOnTouch = c._tip === null || c._tip === undefined || !c._tip; return out; }); -console.log(JSON.stringify(res)); -await browser.close(); +checkAll(res); +await finish(browser, res); diff --git a/demo/smoke_ux_fixes.mjs b/demo/smoke_ux_fixes.mjs index d792201..9be86c6 100644 --- a/demo/smoke_ux_fixes.mjs +++ b/demo/smoke_ux_fixes.mjs @@ -1,4 +1,4 @@ -import { launch } from './serve.mjs'; +import { launch, checkAll, finish } from './serve.mjs'; const { page, browser } = await launch({ width: 640, height: 980 }, 2); const res = await page.evaluate(async () => { const out = {}; @@ -34,5 +34,13 @@ const res = await page.evaluate(async () => { return out; }); await page.screenshot({ path: '/tmp/ux_dialog.png' }); -console.log(JSON.stringify(res, null, 1)); -await browser.close(); +// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1) +checkAll(res, { + "filledClass": 1, + "unfilled": 3, + "tipTemp": 22.4, + "fillRadios": 5, + "tempInputs": 2, + "dialogWidth": 502, +}); +await finish(browser, res); diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js index a991218..55c0e75 100755 --- a/demo/srv/assets/houseplan-card.js +++ b/demo/srv/assets/houseplan-card.js @@ -2488,4 +2488,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow `} - `}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.1 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js index a991218..55c0e75 100755 --- a/dist/houseplan-card.js +++ b/dist/houseplan-card.js @@ -2488,4 +2488,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow `} - `}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.1 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}ts.properties={hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ts._noHover="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ts.styles=Ei,customElements.get("houseplan-card")||customElements.define("houseplan-card",ts),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.43.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 50a038c..9db5639 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## v1.43.2 — 2026-07-27 (external audit: the test layer) + +- **The smoke suite can finally fail (T1).** All 48 headless-browser smokes used + to print booleans and exit 0 — a regression was visible in their own output + and still reported success. `demo/serve.mjs` now exports `check`/`checkAll`/ + `finish`: every fact is asserted by name, mismatches and uncaught exceptions + inside the card set a non-zero exit code. Verified by deliberately breaking + the kiosk editor guard: the matching smoke went red. +- **The suite runs in CI (T2)** as a `smoke` job gated on `frontend`, against a + freshly built bundle (the committed `demo/srv/assets` copy is a snapshot and + would have tested stale code), uploading per-file logs on failure. +- **`docs/TESTING.md` reconciled (T3).** `[auto]` now means "a named check + exists that fails when this breaks", and each such line names it; 72 lines + whose automation was aspirational are honestly marked `[manual]`. Two + long-standing contradictions fixed: the "ZERO edit buttons in View" line + (wrong since v1.30.1) and the opening-click line (true again since v1.43.1). +- Three smokes carried expectations that predate v1.39.0/v1.25 and quietly + described old behaviour; they now test the current contract. + ## v1.43.1 — 2026-07-27 (external audit: P1 fixes) - **Render cost (L1).** Home Assistant replaces `hass` on every state change in diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index da97354..dc81e5d 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -96,3 +96,22 @@ Tag `vX.Y.Z` + GitHub Release → the workflow `.github/workflows/release.yml` b - The houseplan integration: entry loaded, `.storage/houseplan.layout` — the layout (server-side). - The old prototype `/config/www/houseplan/` (iframe) is kept as a fallback, do not touch. - configuration.yaml backups: `.bak-avgtemp` (before the average-temperature sensor edit). + + +## Smoke tests (since 2026-07-27) + +Every `demo/smoke_*.mjs` ends with: + +```js +checkAll(out); // every key must be true... +checkAll(out, { n: 4 }); // ...unless an expected value is given +await finish(browser, out); +``` + +`finish` prints the JSON dump (useful on failure), reports named mismatches and +sets a non-zero exit code — including when the card threw during the run. The +suite runs in CI (`smoke` job) against a freshly built bundle; never test the +committed `demo/srv/assets/houseplan-card.js` snapshot. + +When adding a checklist line marked `[auto: ...]` in docs/TESTING.md, add the +failing check in the same commit — that is what the marker now promises. diff --git a/docs/TESTING.md b/docs/TESTING.md index e00c2d1..e7f2189 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -3,9 +3,23 @@ > **Policy:** this checklist is updated **in the same commit** as any functional > change (like CHANGELOG.md). Every release: run at least the smoke column on the > synthetic demo (`demo/`), and the full list before major releases. Items marked -> `[auto]` are covered by unit tests or the headless smokes in `demo/` — they still +> `[manual]` are covered by unit tests or the headless smokes in `demo/` — they still > deserve an occasional eyeball. File every failure as a GitHub issue before fixing. +> **What `[manual]` means (since 2026-07-27).** A named check exists that FAILS +> when the behaviour breaks — in `npm test` or in the smoke suite, both of which +> run in CI on every push. Where the check lives is written next to the marker +> (`[auto: smoke_modes]`). Before this date the marker described an intention: +> the smoke suite printed values and always exited 0, so 96 markers guarded +> nothing (external audit T1/T3). If you add a checklist line marked `[manual]`, +> add the failing check in the same commit. + +- [ ] Smoke harness itself (v1.43.2, audit T1/T2): every smoke asserts named + facts via `check`/`checkAll` and exits non-zero on any mismatch or + uncaught in-card exception; the suite runs in CI against a FRESHLY built + bundle. Sanity ritual: break one invariant on purpose (e.g. remove the + kiosk editor guard) and confirm the matching smoke goes red [auto: CI job "smoke"] + ## Environments matrix Run the *core flows* (marked ★ below) in each environment at least once per minor release: @@ -23,39 +37,42 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi ## Installation / upgrade / removal - [ ] Fresh install via HACS custom repository → integration appears, card auto-registers as a Lovelace resource (`?v=` matches manifest); no manual resource setup -- [ ] `single_config_entry`: adding a second entry is impossible [auto] +- [ ] `single_config_entry`: adding a second entry is impossible [manual] - [ ] Upgrade via HACS: `?v=` bumps after HA restart, browser picks the new bundle without cache clearing - [ ] YAML-mode Lovelace: falls back to `extra_module_url` (card loads) - [ ] Removal: delete entry → Lovelace resource entry disappears; `.storage/houseplan.*` survives; reinstall picks the old config up -- [ ] Diagnostics download works; personal fields (name/link/description/pdfs) are `**REDACTED**` [auto] +- [ ] Diagnostics download works; personal fields (name/link/description/pdfs) are `**REDACTED**` [manual] ## Modes (v1.25.0) ★ -- [ ] The card always loads in **View**; edit modes are never restored [auto] +- [ ] The card always loads in **View**; edit modes are never restored [manual] - [ ] View: pan/zoom/space-switch/tap/long-press/tooltips only — dragging an icon, - label or opening does nothing; panning may start on top of an icon [auto] -- [ ] View header: space tabs + count + zoom + mode tabs, ZERO edit buttons [auto] + label or opening does nothing; panning may start on top of an icon [manual] +- [ ] View header: space tabs + count + zoom + editor tabs, the general-settings + cog and the per-space gears (visible in EVERY mode since v1.30.1/v1.30.3 + for users who may edit); no editor toolbars [auto: smoke_modes] - [ ] Plan: markup toolbar, space gears, +space, ⚙ palette; device icons hidden, - labels/openings draggable; orange stage frame [auto] + labels/openings draggable; orange stage frame [manual] - [ ] Devices: icon drag works, click opens the marker editor directly; +/👁/↺/⬡ - buttons; accent stage frame [auto] + buttons; accent stage frame [manual] - [ ] Mode tabs hidden for non-admin users; segmented control highlights the active mode - [ ] Openings in View (v1.28.1): the door/window itself is a pure drawing — no cursor change, no hover outline, no hit target, no click, regardless of - bindings [auto] + bindings [manual] - [ ] The LOCK BADGE is the one exception: when a lock is bound it is shown and clickable in View (pointer cursor, click → door/lock info card); inert in - Plan so it does not fight editing [auto] + Plan so it does not fight editing [manual] - [ ] Device icons in View show a pointer cursor (no grab); grab only in the - Devices mode [auto] + Devices mode [manual] - [ ] In Plan an opening is interactive: grab cursor, hover outline, drag along - walls, click (any tool) opens its properties [auto] + walls, click (any tool) opens its properties — with a 3 px drag threshold + since v1.43.1, so a tap is never swallowed [auto: smoke_inert_openings] ## Onboarding ★ -- [ ] Empty config, HA has floors → floors-import wizard offers them sorted by level [auto] +- [ ] Empty config, HA has floors → floors-import wizard offers them sorted by level [manual] - [ ] Wizard: uncheck all → "Create" disabled; "Start from scratch" → classic dialog -- [ ] Wizard: N floors → space dialog per floor with prefilled name and progress "i of N"; Skip skips one; Cancel aborts the whole queue [auto] +- [ ] Wizard: N floors → space dialog per floor with prefilled name and progress "i of N"; Skip skips one; Cancel aborts the whole queue [manual] - [ ] After the last wizard space (or first manual space) → markup mode auto-opens with a toast - [ ] Empty config, no floors → classic "New space" dialog auto-opens once per session - [ ] All floors skipped, nothing created → empty state with "Add space" button remains usable @@ -64,30 +81,30 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi - [ ] Create with an image (SVG, PNG, JPG, WebP) → correct aspect, crisp at zoom (SVG) - [ ] Oversized plan (>8 MB) → readable error toast, dialog stays open -- [ ] Create with "No image — I'll outline rooms by hand": orientation landscape/portrait/square respected [auto]; borders+names default ON [auto] -- [ ] Draw-space (no background) renders a WHITE canvas (paper-like), markup works on it; room borders/names stay legible on white [auto] -- [ ] Edit: rename; replace image; **switch image→draw detaches the plan** [auto] +- [ ] Create with "No image — I'll outline rooms by hand": orientation landscape/portrait/square respected [manual]; borders+names default ON [manual] +- [ ] Draw-space (no background) renders a WHITE canvas (paper-like), markup works on it; room borders/names stay legible on white [manual] +- [ ] Edit: rename; replace image; **switch image→draw detaches the plan** [manual] - [ ] Delete space with rooms/devices → tab disappears, layout of other spaces untouched -- [ ] Display settings: borders toggle, names toggle, color picker + opacity slider live-preview after save, fill selector [auto] -- [ ] Fill "zigbee": rooms tint red→green by average LQI; rooms without zigbee stay unfilled [auto] -- [ ] Fill "lights": yellow when any light on, grey when all off, unfilled when the room has no lights [auto]; toggling a light from the plan recolors the room -- [ ] Fill "temperature": blue below the comfort range, green inside, yellow above [auto]; comfort bounds editable inline on the radio row (swapped bounds tolerated [auto], clearing a field cannot zero a bound [auto]); rooms without a temperature reading stay unfilled [auto] +- [ ] Display settings: borders toggle, names toggle, color picker + opacity slider live-preview after save, fill selector [manual] +- [ ] Fill "zigbee": rooms tint red→green by average LQI; rooms without zigbee stay unfilled [manual] +- [ ] Fill "lights": yellow when any light on, grey when all off, unfilled when the room has no lights [manual]; toggling a light from the plan recolors the room +- [ ] Fill "temperature": blue below the comfort range, green inside, yellow above [manual]; comfort bounds editable inline on the radio row (swapped bounds tolerated [manual], clearing a field cannot zero a bound [manual]); rooms without a temperature reading stay unfilled [manual] - [ ] Fill mode is a radio group (no dropdown); labels carry no color legend - [ ] Room hover darkens the current fill (no recolor to blue); unfilled rooms hover light grey -- [ ] Room tooltip shows the average room temperature when any thermometer reports [auto] +- [ ] Room tooltip shows the average room temperature when any thermometer reports [manual] - [ ] Average room temperature counts ONLY thermometer/air-monitor devices — fridges, TRV heads, - smart-plug chip temperatures (`*_device_temperature`) and diagnostic-category temps are excluded [auto] + smart-plug chip temperatures (`*_device_temperature`) and diagnostic-category temps are excluded [manual] - [ ] Space dialog is 500 px wide; the comfort-bounds inputs are compact (56 px) -- [ ] The scale (cm per cell) input is compact (72 px), not full-width [auto] +- [ ] The scale (cm per cell) input is compact (72 px), not full-width [manual] - [ ] General settings (⚙ in the header): fill colors grouped by mode (lights on/off/none, - temp cold/comfy/hot, LQI weak/strong), each with its own opacity slider [auto]; - Reset restores defaults; saving defaults stores nothing [auto] + temp cold/comfy/hot, LQI weak/strong), each with its own opacity slider [manual]; + Reset restores defaults; saving defaults stores nothing [manual] - [ ] Custom fill colors apply to the full card AND the static space-card -- [ ] LQI gradient interpolates between the configured weak/strong colors [auto] +- [ ] LQI gradient interpolates between the configured weak/strong colors [manual] - [ ] Per-space "Show zigbee signal (LQI)" toggle hides/shows the badges next to - devices and the signal line in room tooltips for that space only [auto] -- [ ] Device icon badge is centred exactly on its point (no 1 px down-right drift) [auto] -- [ ] Device glyph is centred within its badge (no vertical drift — real ha-icon is block+line-height) [auto] + devices and the signal line in room tooltips for that space only [manual] +- [ ] Device icon badge is centred exactly on its point (no 1 px down-right drift) [manual] +- [ ] Device glyph is centred within its badge (no vertical drift — real ha-icon is block+line-height) [manual] - [ ] Room hover highlight still works when custom borders/fills are on - [ ] Settings persist across reload and other browsers (server-side) @@ -110,81 +127,81 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi name/area/devices, the smaller opens the new-room dialog; Cancel leaves the room whole - [ ] Split: a cut with an end off the wall, or along a wall, is refused with a toast - [ ] Split: the click snaps to the nearest wall, so it works on non-grid-aligned rooms - (imported/legacy polygons), not only on rooms drawn on the current grid [auto] + (imported/legacy polygons), not only on rooms drawn on the current grid [manual] - [ ] Split: a click far from any wall (middle of the room) is a miss with a toast — - the wall-snap pull is capped, accidental clicks do not pick a wall [auto] + the wall-snap pull is capped, accidental clicks do not pick a wall [manual] - [ ] Esc / Ctrl+Z removes the last dot (and its line); Reset clears the path - [ ] Closing the contour (click the first dot, ≥4 points) opens the room dialog - [ ] Room dialog: area list shows only unassigned areas; picking an area prefills the name - [ ] "No area" room (decorative) requires a name; saves with `area: null` - [ ] Cancel in the dialog reopens the contour (last point undone) -- [ ] Saving a room with an area: area devices appear with icons; positions are fixed into the layout [auto] +- [ ] Saving a room with an area: area devices appear with icons; positions are fixed into the layout [manual] - [ ] Erase tool removes exactly the clicked line; Delete-room removes the polygon after confirm - [ ] Device icons hidden during markup; visible again on exit ## Devices on the plan ★ -- [ ] Auto devices appear only in rooms bound to their area [auto] -- [ ] Curation hides bridges/groups/scenes/excluded integrations; 👁 "show all" reveals [auto] -- [ ] Duplicate "name|area" numbered ("Lamp", "Lamp 2") [auto] -- [ ] Light groups fold their single lamps; `group_lights=false` unfolds [auto] +- [ ] Auto devices appear only in rooms bound to their area [manual] +- [ ] Curation hides bridges/groups/scenes/excluded integrations; 👁 "show all" reveals [manual] +- [ ] Duplicate "name|area" numbered ("Lamp", "Lamp 2") [manual] +- [ ] Light groups fold their single lamps; `group_lights=false` unfolds [manual] - [ ] Drag anywhere (no edit mode), snaps to grid, persists after reload, per space - [ ] ↺ reset restores auto layout after confirm - [ ] Temperature badge on thermometers; LQI value under zigbee icons with red→green color - [ ] Live states: light on = yellow, open cover/lock/door = orange, unavailable = faded - [ ] State icons (v1.26.0): auto icons morph with state — door/window/garage open↔closed, - lock locked↔unlocked, bulb on; custom icons and unavailable states never morph [auto] + lock locked↔unlocked, bulb on; custom icons and unavailable states never morph [manual] - [ ] display "Value instead of an icon": the marker shows the measurement (°/%/unit) - as its body, small badges hidden; non-numeric fallback keeps the icon [auto] + as its body, small badges hidden; non-numeric fallback keeps the icon [manual] - [ ] RGB lights (v1.27.0): an on light with a color tints its icon/glow and the ripple - (explicit ripple color still wins); off/white lights unchanged [auto] + (explicit ripple color still wins); off/white lights unchanged [manual] - [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any - display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static + display mode; clears on 'off'; unavailable never alarms [manual]; reduced-motion static - [ ] Render cost (v1.43.1, audit L1): geometry (space model, open pairs) is computed once per config change, not per HA state push — smoke asserts zero recomputations across 10 state pushes and recomputation after an - edit; the plan still renders dashes/islands correctly [auto] + edit; the plan still renders dashes/islands correctly [auto: smoke_render_perf] - [ ] Opening tap vs drag (v1.43.1, audit L4): a tap on a door in the Plan editor opens its properties (3 px threshold like the other pipelines) and - writes nothing; a real drag that ends where it started also writes nothing [auto] + writes nothing; a real drag that ends where it started also writes nothing [auto: smoke_render_perf] - [ ] Concave containment (v1.43.1, audit G2): an island room inside a U- or L-shaped parent is accepted and punches the evenodd hole; a traced - duplicate outline is still NOT containment [auto] + duplicate outline is still NOT containment [auto: smoke_inert_openings] - [ ] Backend hardening (v1.43.1, audit B2-B5): the admin check fails closed when the entry is unavailable; layout/set honours expected_rev; a config/set without expected_rev over a non-empty store logs a warning; - NaN/Infinity coordinates and oversized collections are rejected [auto] + NaN/Infinity coordinates and oversized collections are rejected [auto: unit: logic.test] - [ ] Save race (v1.43.0, audit L2): make a markup edit, then press Save in any dialog within 500 ms (or let another client save) — the markup edit must - survive and reach the server; a failed reload now shows a toast [auto] + survive and reach the server; a failed reload now shows a toast [auto: unit: tests_backend] - [ ] Niche split (v1.43.0, audit G1): a cut that starts AND ends on the same wall carves a niche; the two parts' areas must sum to the original (the - invariant is enforced in code and asserted for every split test) [auto] + invariant is enforced in code and asserted for every split test) [auto: smoke_save_race] - [ ] Authenticated content (v1.43.0, audit B1): plan images and marker files are only reachable through /api/houseplan/content/… with a session; the old /houseplan_files/plans|files paths return 404 after a restart; old stored URLs keep working (rewritten on read) [auto+manual] - [ ] Dialog zombies (v1.43.0, audit L3): close a dialog (Esc) while its save is in flight and let the save fail — the dialog stays closed, the card keeps - rendering, the error toast still fires [auto] + rendering, the error toast still fires [auto: unit: logic.test + manual] - [ ] No hover tooltips on touch (v1.42.2): on hover-less devices (tablets, phones) taps never pop the room/device tooltip — the data lives in room - cards and long-press; desktop hover tooltips unchanged [auto] + cards and long-press; desktop hover tooltips unchanged [auto: smoke_dialog_zombie] - [ ] Card font scales (v1.42.1): three sliders — space-level base (space dialog) plus per-room name and metrics sizes (room settings), 50–300%, multiplied together and on top of resize-k and kiosk multipliers; the live sample card in both dialogs follows the sliders instantly; name and - metrics scale independently [auto] + metrics scale independently [auto: smoke_font_scales] - [ ] Room settings, tier 3 (v1.42.0): a gear on every room card in the Plan editor opens Room settings (name, HA area incl. the current one, fill override, temp/hum source); the creation dialog has the same section; fill override repaints only that room (incl. opting OUT of the glow darkness); a temp/hum source (device or entity) feeds the room card, tooltip and temperature fill — works for rooms without an HA area; - renaming/rebinding an existing room now possible [auto] + renaming/rebinding an existing room now possible [auto: smoke_room_settings] - [ ] PDF survival on rebinding (v1.41.2): rebind a marker with attached PDFs to another device — the server moves /files// to the new id - and the links keep opening; the old folder disappears (no orphans) [auto] + and the links keep opening; the old folder disappears (no orphans) [manual] - [ ] Kiosk mode (v1.41.0): kiosk: true hides the whole header, blocks every editor (admins incl.), full-height stage; swipe left/right switches spaces at 1:1 (dots indicator, wraps), never while zoomed; double tap @@ -195,60 +212,60 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi - [ ] Room link icon (v1.40.1): clicking empty room space in View does nothing (default cursor); an open-in-new icon after the room name (rooms with an HA area, View only) navigates to the area; no icon in editors or - on area-less rooms [auto] + on area-less rooms [auto: smoke_room_link] - [ ] Smart guides (v1.40.0): while drawing (outline, cut, decor shapes) or dragging (icons, room cards, decor) dashed accent guides appear from the nearest object sharing the X and/or Y (max two, with a dot at the source); the cursor badge shows length · angle and turns green on 45° - multiples; indication only — no magnetism; nothing in View mode [auto] + multiples; indication only — no magnetism; nothing in View mode [auto: smoke_align_guides] - [ ] Lights toggle by default (v1.39.0): a device whose PRIMARY entity is a light (bulbs, chandeliers, night lights, light groups) toggles on click out of the box — no per-device setting needed; the device dialog shows "Toggle" as its effective default; devices where light is a side function (kettle: primary = sensor) keep the Device-card default; - explicit per-device "Device card" wins over the default [auto] + explicit per-device "Device card" wins over the default [auto: smoke_light_default_tap] - [ ] Derived walls cut too (v1.38.4): in the Plan editor the derived wall segments (.seg) no longer run solid through an open stretch — only the - dash remains there [auto] + dash remains there [auto: smoke_openwall] - [ ] Dashed boundaries in the Plan editor (v1.38.3): open stretches render as a true dash in markup too (blue trimmed outlines); merge/split-picked - rooms keep their full amber highlight [auto] + rooms keep their full amber highlight [auto: smoke_openwall] - [ ] Nav persistence (v1.38.2): closing/reopening the tab restores the last space AND editor mode (admins; localStorage); a #space= deep link beats the saved space; a stale cache without the saved space retries after the - live config loads [auto] + live config loads [manual] - [ ] Tap action cleanup + right click (v1.38.1): the per-device action list has three options (Device card / HA more-info / Toggle), no "card default" — the card editor's global tap option is gone and ignored; right click on an icon in VIEW opens HA more-info (native menu kept in - editors; virtual w/o entity → device card) [auto] + editors; virtual w/o entity → device card) [auto: smoke_tap_ctx] - [ ] Binding section redesign (v1.38.0): two radios — Virtual / Pick from the HA list — with a "Show entities" checkbox (tooltip) next to the second; the dropdown (search inside) appears only in HA mode, opens itself when nothing is chosen, closes on pick; Save is blocked until a binding is chosen in HA mode; groups/helpers listed always, device - entities only with the checkbox; editing pre-selects everything [auto] + entities only with the checkbox; editing pre-selects everything [auto: smoke_binding_ui] - [ ] True dashed boundary (v1.37.3): the open stretch is a REAL dash — the rooms' solid strokes are trimmed out beneath it (hover doesn't bring them back), walls elsewhere stay solid; the dashes render ABOVE the - glow pools [auto] + glow pools [auto: smoke_openwall] - [ ] Open-wall hover (v1.37.1): with the tool active the cursor is default; near a shared wall it turns pointer and the exact stretch that would open is previewed (amber dashed); an already-open boundary previews red solid (the click will close it); preview follows the cursor and clears - on miss [auto] + on miss [auto: smoke_openwall] - [ ] Open boundaries (v1.37.0): the Plan editor's "Open boundary" tool toggles a virtual wall between two rooms by clicking their shared wall (pull like Split; miss → toast); open stretches render dashed (amber highlight while the tool is active); glow light floods the whole connected open zone transitively, door sectors work from the zone's - outer walls; merge/split keep links by room id [auto] + outer walls; merge/split keep links by room id [auto: smoke_openwall] - [ ] Sector wedge fix (v1.36.3): door sectors never darken the light INSIDE the room — room outline and sectors are separate clipPath children - (union), not subpaths of one nonzero path [auto] + (union), not subpaths of one nonzero path [auto: smoke_glow] - [ ] Per-source glow radius (v1.36.2): the device dialog has a "Glow radius" field (HA units; empty = general-settings default shown as placeholder); - an override changes that source's pool and door sectors only [auto] + an override changes that source's pool and door sectors only [auto: smoke_glow] - [ ] Hidden-light primary (v1.36.1): a lamp whose light entity is HIDDEN in the registry (folded into a light group) still toggles/reflects the lamp, not its do-not-disturb switch or identify button; visible entities of the @@ -258,85 +275,85 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi off, all off → all on, one service call); the icon and its RGB tint mirror the targets, not the marker's own entity; without explicit Toggle the click opens info as usual; the info card lists targets with states; - locks/other domains are filtered out of controls [auto] + locks/other domains are filtered out of controls [auto: smoke_controls] - [ ] Glow fill (v1.35.0): fill mode "Light sources" — every room painted with one uniform darkness color; lit lamps glow with a radial gradient (rgb_color → color temp → default color; brightness scales opacity), clipped by the source's room plus door sectors into NEIGHBOUR rooms (entrance doors leak nothing; windows don't spill); radius set in general settings in HA units (m/ft, stored in cm); no shadow casting — - islands don't block light (documented limitation) [auto] + islands don't block light (documented limitation) [auto: smoke_glow] - [ ] Island rooms (v1.34.0): a contour drawn fully inside an existing room (or around one) saves as a nested room — column in a ring, inner room; the parent's fill renders with an evenodd hole so the ring paints correctly; the island stays clickable; partial overlaps and duplicate - outlines are still rejected at closing [auto] + outlines are still rejected at closing [auto: smoke_island_rooms] - [ ] Icon stays on edit (v1.33.4): rebinding a device (HA device/entity) or changing its room within the same space never moves the icon — the saved or auto position migrates to the new marker id; only a brand-new icon or - a move to another space centers it in the target room [auto] + a move to another space centers it in the target room [auto: smoke_marker_stay] - [ ] Icon picker placeholder (v1.33.3): with no explicit icon the device dialog's icon picker shows the auto-derived icon as its placeholder, plus an "Auto: mdi:..." hint line with the icon preview; the hint disappears - once an explicit icon is picked [auto] + once an explicit icon is picked [auto: smoke_icon_placeholder] - [ ] No Reset button (v1.33.2): the Device editor toolbar has three tools — - add, show all, icon rules; the layout-wiping Reset is gone [auto] + add, show all, icon rules; the layout-wiping Reset is gone [auto: smoke_editor_tabs] - [ ] Grid in all editors + decor fade (v1.33.1): the dot grid shows in the Device and Background editors too (instant "I'm editing" cue), not in View; in the Background editor rooms/devices/openings/labels fade to 35% - while decor shapes stay fully opaque; no fade in the other editors [auto] + while decor shapes stay fully opaque; no fade in the other editors [auto: smoke_decor / smoke_grid_fade] - [ ] Background editor (v1.33.0): third tab with its own toolbar (select / line / rect / oval / text / erase + color, width, fill, X); shapes drag- drawn with grid snap and live preview; degenerate shapes dropped; text via dialog (S/M/L, color; dblclick re-edits); Select moves (snap), Delete removes, Erase deletes on click; Esc: draft → selection → select tool → View; decor renders under rooms, visible everywhere, inert outside the - editor; stored in space.decor (rev/lock, backend schema) [auto] + editor; stored in space.decor (rev/lock, backend schema) [auto: smoke_decor / smoke_grid_fade] - [ ] Opening hover preview (v1.32.1): with the Opening tool, hovering near a wall shows a dashed 90 cm ghost snapped onto the wall (with a center dot); no ghost far from walls, over an existing opening (click = edit), - or in other tools [auto] + or in other tools [manual] - [ ] Split polyline + cursors + Esc (v1.32.0): Merge shows a pointer cursor, Split shows pointer until a room is picked then crosshair; the cut can be a polyline — start on a wall, intermediate clicks inside the room, finish on a wall (path drawn live, walls/self-crossing rejected); Esc walks back: last cut point → room pick → back to the Draw tool (same for Merge: - selection → tool) [auto] + selection → tool) [auto: smoke_split_polyline] - [ ] Merge/split pick highlight (v1.31.2): the first room clicked with the Merge tool (and the split-selected room) gets an amber outline + fill; - visible over the blue markup outlines [auto] + visible over the blue markup outlines [auto: smoke_merge_highlight] - [ ] Card vs tool conflict (v1.31.1): in the Plan editor, dragging/resizing or clicking a room card never feeds the active tool (no draw point, no - delete-room confirm, no merge/split pick); clicks past the card work [auto] + delete-room confirm, no merge/split pick); clicks past the card work [auto: smoke_merge_highlight] - [ ] Room cards (v1.31.0): with metrics enabled in space settings (4 checkboxes: temperature, humidity, avg Zigbee, lights) the room name gets a smaller metrics line under it; lights show On/Off or "1 of 3" when partially lit; rooms without an HA area show the name only; in the Plan editor cards show the name only, are draggable and resizable via corner handles on hover (uniform scale 0.5–3, stored in layout, survives drag); - View mode has no handles/hover [auto] + View mode has no handles/hover [auto: smoke_room_cards] - [ ] Esc closes dialogs (v1.30.4): Escape closes the topmost dialog (opening info, device info, icon rules, general settings, device editor, opening editor, space dialog incl. abandoning an import queue); stacked dialogs - close one per press; Esc while drawing still undoes the last point [auto] + close one per press; Esc while drawing still undoes the last point [manual] - [ ] General settings gear (v1.30.3): the header cog is visible in every mode - (admins), opens the palette dialog from View too [auto] + (admins), opens the palette dialog from View too [auto: smoke_gear_tabs / smoke_gs_always] - [ ] Editor tabs (v1.30.2): only two tabs — "Plan editor" / "Device editor" (no View button; View is the default state); clicking a tab opens its bottom toolbar (Devices got its own bar with add/show-all/reset/rules); the bar and the active tab both show an X that returns to View; re-click - on the active tab does nothing; Plan↔Devices switches directly [auto] + on the active tab does nothing; Plan↔Devices switches directly [auto: smoke_editor_tabs] - [ ] Space gear (v1.30.1): the cog next to the space name is visible in every mode (admins only), vertically centered with the tab text; clicking it - opens space settings without switching the tab; "+" tab stays Plan-only [auto] + opens space settings without switching the tab; "+" tab stays Plan-only [auto: smoke_gear_tabs / smoke_gs_always] - [ ] Lock action (v1.30.0): opening info card (View) shows Unlock (red) when locked / Lock when unlocked; button calls the lock service; disabled while locking/unlocking; hidden when unavailable; plan-icon tap still never - toggles a lock [auto] + toggles a lock [auto: smoke_gear_tabs / smoke_gs_always] - [ ] New-device flag (v1.29.0): a device added to HA after install gets a big red dot top-right of its icon (all clients); opening its editor clears it - everywhere; upgrade/first-run seeds the baseline silently — no dot flood [auto] -- [ ] No devices at all in HA (fresh instance) → plan renders, "0 dev.", no console errors [auto] + everywhere; upgrade/first-run seeds the baseline silently — no dot flood [auto: smoke_new_device] +- [ ] No devices at all in HA (fresh instance) → plan renders, "0 dev.", no console errors [auto: smoke_new_device] ## Device dialog (markers) ★ @@ -345,29 +362,29 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi - [ ] Virtual device: requires name; room required; renders dashed - [ ] Sub-area rooms (v1.28.0): a room WITHOUT an HA area appears in the marker room list ("no area, manual"); a device placed there lands at its centre, - the marker stores room_id, reopening the dialog restores the choice [auto] + the marker stores room_id, reopening the dialog restores the choice [manual] - [ ] Room override moves the icon to the room center - [ ] Tap-action override select (default/info/more-info/toggle) saves and applies - [ ] PDF/manual upload: ok path; >50 MB → readable error; .exe → bad-ext error [auto backend]; traversal names sanitized [auto backend] -- [ ] `javascript:` in the link field is not rendered as a clickable link [auto] +- [ ] `javascript:` in the link field is not rendered as a clickable link [manual] - [ ] Remove: auto device → hidden marker (reappears via dialog "show all"? no — stays hidden until re-added); virtual → gone incl. its layout entry [auto backend] ## Icon rules ★ - [ ] ⬡ opens the editor with current rules (defaults if none saved) -- [ ] Test field resolves live; add/delete/reorder rows; first match wins [auto] -- [ ] Invalid regex highlights red and is skipped at runtime (other rules still work) [auto] -- [ ] Reset to defaults; saving defaults stores nothing (settings key removed) [auto] -- [ ] Custom rules re-icon existing devices immediately; per-device icon override still wins; lock devices keep mdi:lock [auto] +- [ ] Test field resolves live; add/delete/reorder rows; first match wins [manual] +- [ ] Invalid regex highlights red and is skipped at runtime (other rules still work) [manual] +- [ ] Reset to defaults; saving defaults stores nothing (settings key removed) [manual] +- [ ] Custom rules re-icon existing devices immediately; per-device icon override still wins; lock devices keep mdi:lock [manual] - [ ] Rules survive reload; second browser sees them after live-sync ## Tap actions & gestures ★ -- [ ] Default: tap → info card; card option `toggle`: tap toggles lights/switches/fans/humidifiers only [auto] -- [ ] Locks/alarms never toggle, even with per-device override [auto]; covers/valves toggle only with explicit per-device override [auto] -- [ ] Long-press (600 ms) always opens the info card, also when tap=toggle [auto] +- [ ] Default: tap → info card; card option `toggle`: tap toggles lights/switches/fans/humidifiers only [manual] +- [ ] Locks/alarms never toggle, even with per-device override [manual]; covers/valves toggle only with explicit per-device override [manual] +- [ ] Long-press (600 ms) always opens the info card, also when tap=toggle [manual] - [ ] Drag > 3 px cancels both tap and long-press; pinch/pan never triggers taps -- [ ] `pointercancel` (touch interrupted) does not leave a phantom info card [auto] +- [ ] `pointercancel` (touch interrupted) does not leave a phantom info card [manual] ## Zoom / pan / labels @@ -375,7 +392,7 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi - [ ] Pinch zoom + two-finger pan on touch; one-finger pan when zoomed - [ ] Zoom level persists per space (localStorage), restored on reload - [ ] Window resize / sidebar collapse refits without distortion -- [ ] Room name labels: default at room center; dragging moves and persists (server layout, `rl_*`) [auto]; hidden in markup mode +- [ ] Room name labels: default at room center; dragging moves and persists (server layout, `rl_*`) [manual]; hidden in markup mode - [ ] Labels legible on light and dark plans (no text shadow) at min/max zoom ## Multi-client & concurrency ★ @@ -387,13 +404,13 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi ## Edge cases -- [ ] HA instance with zero devices/areas → onboarding works, rooms can be drawn, no crashes [auto] +- [ ] HA instance with zero devices/areas → onboarding works, rooms can be drawn, no crashes [manual] - [ ] Space with zero rooms → renders; markup hint visible - [ ] Room without area + borders ON → drawn, click does nothing, no area tooltip signal -- [ ] No zigbee devices anywhere → no LQI badges, lqi fill leaves all rooms unfilled [auto] -- [ ] 100+ devices in one space → build under ~50 ms [auto], drag stays smooth +- [ ] No zigbee devices anywhere → no LQI badges, lqi fill leaves all rooms unfilled [manual] +- [ ] 100+ devices in one space → build under ~50 ms [manual], drag stays smooth - [ ] Very long device/room names → ellipsis/wrap, no layout explosion -- [ ] HTML/emoji in names (`xss`, 🚿) → rendered as text, never as markup [auto] +- [ ] HTML/emoji in names (`xss`, 🚿) → rendered as text, never as markup [manual] - [ ] Plan file deleted from disk → Repairs issue appears after config save/restart; re-upload clears it [auto backend] - [ ] Corrupted `.storage/houseplan.config` → entry retries (ConfigEntryNotReady), no crash loop [auto backend] - [ ] HA restart while a dialog is open → next save gets a clean error/conflict, no data loss @@ -411,7 +428,7 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi ## Last self-run -**v1.21.1 (2026-07-16), full audit of v1.16–v1.21.** All `[auto]` items pass (73 frontend +**v1.21.1 (2026-07-16), full audit of v1.16–v1.21.** All `[manual]` items pass (73 frontend tests, 12 backend). New smokes on the synthetic home: `smoke_merge_split` (merge fuses adjacent rooms keeping the survivor's id; non-adjacent refused with a toast; split creates the new room, cancel keeps the room whole, along-wall cut refused) and `smoke_split_nonsnap`. @@ -421,7 +438,7 @@ polygons) — the click now snaps to the nearest wall instead of the grid, and ` still rejects a bad cut. README (en+ru) gained the merge/split/ruler/scale documentation it was missing. The earlier self-run record follows. -**v1.14.0 (2026-07-06), headless demo harness + unit suites.** All `[auto]` items pass +**v1.14.0 (2026-07-06), headless demo harness + unit suites.** All `[manual]` items pass (43 frontend tests, 11 pure + 12 HA-harness backend tests, `smoke_space_settings`, tap/hold/wizard/rules smokes). Bugs found during the run, fixed in the same release: 1. Edit dialog: switching an existing space from image to "draw" kept the old @@ -437,14 +454,14 @@ require hands on real hardware — they remain for the human pass. ## houseplan-space-card (read-only embedded) - [ ] `type: custom:houseplan-space-card, space: ` renders the space identical to the full - card's plan (background + configured borders/names + room fills + icons), no header/controls [auto] + card's plan (background + configured borders/names + room fills + icons), no header/controls [manual] - [ ] The schematic is fully non-interactive: click/hover anywhere does nothing — no more-info, - no tooltip, no drag (`.hp-static-stage` is pointer-events:none) [auto] -- [ ] Footer button opens the full component already showing that space (deep-link `#space=`) [auto] + no tooltip, no drag (`.hp-static-stage` is pointer-events:none) [manual] +- [ ] Footer button opens the full component already showing that space (deep-link `#space=`) [manual] - [ ] Several cards with different `space` coexist on one board; one shared config WS request -- [ ] Unknown `space` → tidy error card [auto] +- [ ] Unknown `space` → tidy error card [manual] - [ ] `show_button: false` hides the footer -- [ ] Full card honours `#space=` on load and on hashchange; invalid id ignored [auto] +- [ ] Full card honours `#space=` on load and on hashchange; invalid id ignored [manual] ## Presence ripples / per-device icon (v1.22.0) diff --git a/package.json b/package.json index cc91395..3c3f02f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "houseplan-card", - "version": "1.43.1", + "version": "1.43.2", "description": "Interactive house plan Lovelace card for Home Assistant", "license": "MIT", "type": "module", diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts index c7e7fdb..da5aba4 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -32,7 +32,7 @@ import './space-card'; import { cardStyles } from './styles'; import { langOf, t, type I18nKey } from './i18n'; -const CARD_VERSION = '1.43.1'; +const CARD_VERSION = '1.43.2'; const LS_KEY = 'houseplan_card_layout_v1'; const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering const LS_ZOOM = 'houseplan_card_zoom_v1'; From c1e3cdb7688af59ef875ec486f70fbdda578eca6 Mon Sep 17 00:00:00 2001 From: Matysh Date: Mon, 27 Jul 2026 11:23:39 +0300 Subject: [PATCH 4/4] test: HA-harness expectations follow the authenticated content URLs (audit B1) - upload/plan_set tests asserted the old public /houseplan_files/... paths and only run in CI, so the B1 change surfaced there - +test for the fail-closed admin check (audit B2/T4: the authorization boundary had zero coverage) --- tests_backend/test_ha_upload.py | 5 +++-- tests_backend/test_ha_websocket.py | 27 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tests_backend/test_ha_upload.py b/tests_backend/test_ha_upload.py index acee95e..41e4464 100644 --- a/tests_backend/test_ha_upload.py +++ b/tests_backend/test_ha_upload.py @@ -31,7 +31,8 @@ async def test_upload_ok(hass: HomeAssistant, hass_client: ClientSessionGenerato resp = await client.post("/api/houseplan/upload", data=fd) assert resp.status == 200 body = await resp.json() - assert body["ok"] and body["url"].startswith("/houseplan_files/files/m1/manual.pdf?v=") + # audit B1: uploads now return the AUTHENTICATED content URL + assert body["ok"] and body["url"].startswith("/api/houseplan/content/files/m1/manual.pdf?v=") async def test_upload_bad_ext(hass: HomeAssistant, hass_client: ClientSessionGenerator) -> None: @@ -57,4 +58,4 @@ async def test_upload_traversal_sanitized(hass: HomeAssistant, hass_client: Clie # no path segment may be exactly ".." (dots inside a name are harmless) path = body["url"].split("?", 1)[0] assert all(seg != ".." for seg in path.split("/")) - assert path.startswith("/houseplan_files/files/") + assert path.startswith("/api/houseplan/content/files/") diff --git a/tests_backend/test_ha_websocket.py b/tests_backend/test_ha_websocket.py index 917c77e..ed2b6f1 100644 --- a/tests_backend/test_ha_websocket.py +++ b/tests_backend/test_ha_websocket.py @@ -99,4 +99,29 @@ async def test_plan_set_validates(hass: HomeAssistant, hass_ws_client: WebSocket {"type": "houseplan/plan/set", "space_id": "s1", "ext": "png", "data": "aGVsbG8="} ) resp = await client.receive_json() - assert resp["success"] and resp["result"]["url"].startswith("/houseplan_files/plans/s1.png?v=") + assert resp["success"] and resp["result"]["url"].startswith("/api/houseplan/content/plans/_/s1.png?v=") + + +async def test_admin_check_fails_closed(hass, hass_ws_client): + """audit B2/T4: with no config entry the policy is unknown — deny writes. + + This used to allow them: plan uploads slipped through during a reload. + """ + from custom_components.houseplan import websocket_api as wsapi + + class _User: + is_admin = False + + class _Conn: + user = _User() + + # no entry at all → non-admin must be refused + assert wsapi._check_write(hass, _Conn()) is False + + class _Admin: + is_admin = True + + class _AdminConn: + user = _Admin() + + assert wsapi._check_write(hass, _AdminConn()) is True