mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
2676 lines
329 KiB
JavaScript
Executable File
2676 lines
329 KiB
JavaScript
Executable File
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:d,getPrototypeOf:p}=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=p(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),...d(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,k=t=>t,$=x.trustedTypes,S=$?$.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",D=`lit$${Math.random().toFixed(9).slice(2)}$`,C="?"+D,T=`<${C}>`,z=document,P=()=>z.createComment(""),E=t=>null===t||"object"!=typeof t&&"function"!=typeof t,R=Array.isArray,A="[ \t\n\f\r]",N=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,O=/-->/g,I=/>/g,L=RegExp(`>|${A}(?:([^\\s"'>=/]+)(${A}*=${A}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),q=/'/g,F=/"/g,U=/^(?:script|style|textarea|title)$/i,H=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=H(1),j=H(2),W=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=z.createTreeWalker(z,129);function Z(t,e){if(!R(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"<svg>":3===e?"<math>":"",r=N;for(let e=0;e<i;e++){const i=t[e];let a,l,c=-1,h=0;for(;h<i.length&&(r.lastIndex=h,l=r.exec(i),null!==l);)h=r.lastIndex,r===N?"!--"===l[1]?r=O:void 0!==l[1]?r=I:void 0!==l[2]?(U.test(l[2])&&(o=RegExp("</"+l[2],"g")),r=L):void 0!==l[3]&&(r=L):r===L?">"===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===O||r===I?r=N:(r=L,o=void 0);const d=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+d):i+D+(-2===c?e:d)}return[Z(t,n+(t[i]||"<?>")+(2===e?"</svg>":3===e?"</math>":"")),s]};class X{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]=J(t,e);if(this.el=X.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.length<r;){if(1===s.nodeType){if(s.hasAttributes())for(const t of s.getAttributeNames())if(t.endsWith(M)){const e=c[n++],i=s.getAttribute(t).split(D),r=/([.?@])?(.*)/.exec(e);a.push({type:1,index:o,name:r[2],strings:i,ctor:"."===r[1]?it:"?"===r[1]?st:"@"===r[1]?ot:et}),s.removeAttribute(t)}else t.startsWith(D)&&(a.push({type:6,index:o}),s.removeAttribute(t));if(U.test(s.tagName)){const t=s.textContent.split(D),e=t.length-1;if(e>0){s.textContent=$?$.emptyScript:"";for(let i=0;i<e;i++)s.append(t[i],P()),K.nextNode(),a.push({type:2,index:++o});s.append(t[e],P())}}}else if(8===s.nodeType)if(s.data===C)a.push({type:2,index:o});else{let t=-1;for(;-1!==(t=s.data.indexOf(D,t+1));)a.push({type:7,index:o}),t+=D.length-1}o++}}static createElement(t,e){const i=z.createElement("template");return i.innerHTML=t,i}}function Y(t,e,i=t,s){if(e===W)return e;let o=void 0!==s?i._$Co?.[s]:i._$Cl;const n=E(e)?void 0:e._$litDirective$;return o?.constructor!==n&&(o?._$AO?.(!1),void 0===n?o=void 0:(o=new n(t),o._$AT(t,i,s)),void 0!==s?(i._$Co??=[])[s]=o:i._$Cl=o),void 0!==o&&(e=Y(t,o._$AS(t,e.values),o,s)),e}class Q{constructor(t,e){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:e},parts:i}=this._$AD,s=(t?.creationScope??z).importNode(e,!0);K.currentNode=s;let o=K.nextNode(),n=0,r=0,a=i[0];for(;void 0!==a;){if(n===a.index){let e;2===a.type?e=new tt(o,o.nextSibling,this,t):1===a.type?e=new a.ctor(o,a.name,a.strings,this,t):6===a.type&&(e=new nt(o,this,t)),this._$AV.push(e),a=i[++r]}n!==a?.index&&(o=K.nextNode(),n++)}return K.currentNode=z,s}p(t){let e=0;for(const i of this._$AV)void 0!==i&&(void 0!==i.strings?(i._$AI(t,i,e),e+=i.strings.length-2):i._$AI(t[e])),e++}}class tt{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,e,i,s){this.type=2,this._$AH=G,this._$AN=void 0,this._$AA=t,this._$AB=e,this._$AM=i,this.options=s,this._$Cv=s?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode;const e=this._$AM;return void 0!==e&&11===t?.nodeType&&(t=e.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,e=this){t=Y(this,t,e),E(t)?t===G||null==t||""===t?(this._$AH!==G&&this._$AR(),this._$AH=G):t!==this._$AH&&t!==W&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):(t=>R(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&&E(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=X.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 X(t)),e}k(t){R(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(P()),this.O(P()),this,this.options)):i=e[s],i._$AI(o),s++;s<e.length&&(this._$AR(i&&i._$AB.nextSibling,s),e.length=s)}_$AR(t=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);t!==this._$AB;){const e=k(t).nextSibling;k(t).remove(),t=e}}setConnected(t){void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t))}}class et{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,e,i,s,o){this.type=1,this._$AH=G,this._$AN=void 0,this.element=t,this.name=e,this._$AM=s,this.options=o,i.length>2||""!==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=Y(this,t,e,0),n=!E(t)||t!==this._$AH&&t!==W,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r<o.length-1;r++)a=Y(this,s[i+r],e,r),a===W&&(a=this._$AH[r]),n||=!E(a)||a!==this._$AH[r],a===G?t=G:t!==G&&(t+=(a??"")+o[r+1]),this._$AH[r]=a}n&&!s&&this.j(t)}j(t){t===G?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}}class it extends et{constructor(){super(...arguments),this.type=3}j(t){this.element[this.name]=t===G?void 0:t}}class st extends et{constructor(){super(...arguments),this.type=4}j(t){this.element.toggleAttribute(this.name,!!t&&t!==G)}}class ot extends et{constructor(t,e,i,s,o){super(t,e,i,s,o),this.type=5}_$AI(t,e=this){if((t=Y(this,t,e,0)??G)===W)return;const i=this._$AH,s=t===G&&i!==G||t.capture!==i.capture||t.once!==i.once||t.passive!==i.passive,o=t!==G&&(i===G||s);s&&this.element.removeEventListener(this.name,this,i),o&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}}class nt{constructor(t,e,i){this.element=t,this.type=6,this._$AN=void 0,this._$AM=e,this.options=i}get _$AU(){return this._$AM._$AU}_$AI(t){Y(this,t)}}const rt=x.litHtmlPolyfillSupport;rt?.(X,tt),(x.litHtmlVersions??=[]).push("3.3.3");const at=globalThis;class lt extends w{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=((t,e,i)=>{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(P(),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"]),dt=[{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:"чайник|kettle|термопот",icon:"mdi:kettle"},{pattern:"сауна|sauna|harvia|парная|парилк",icon:"mdi:hot-tub"},{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 pt(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=pt(dt),_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: ",kt=1e14,$t=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]+"";s<o;){for(e=t[s++]+"",i=$t-e.length;i--;e="0"+e);n+=e}for(o=n.length;48===n.charCodeAt(--o););return n.slice(0,o+1||1)}function Pt(t,e){var i,s,o=t.c,n=e.c,r=t.s,a=e.s,l=t.e,c=e.e;if(!r||!a)return null;if(i=o&&!o[0],s=n&&!n[0],i||s)return i?s?0:-a:r;if(r!=a)return r;if(i=r<0,s=l==c,!o||!n)return s?0:!o^i?1:-1;if(!s)return l>c^i?1:-1;for(a=(l=o.length)<(c=n.length)?l:c,r=0;r<a;r++)if(o[r]!=n[r])return o[r]>n[r]^i?1:-1;return l==c?0:l>c^i?1:-1}function Et(t,e,i,s){if(t<e||t>i||t!==yt(t))throw Error(wt+(s||"Argument")+("number"==typeof t?t<e||t>i?" out of range: ":" not an integer: ":" not a primitive number: ")+String(t))}function Rt(t){var e=t.c.length-1;return Tt(t.e/$t)==e&&t.c[e]%2!=0}function At(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 e<s&&(t=t.slice(0,e)+"."+t.slice(e));return t}var Ot=function t(e){var i,s,o,n,r,a,l,c,h,d,p=M.prototype={constructor:M,toString:null,valueOf:null},u=new M(1),_=20,m=4,g=-7,f=21,b=-1e7,v=1e7,y=!1,w=1,x=0,k={prefix:"",groupSize:3,secondaryGroupSize:0,groupSeparator:",",decimalSeparator:".",fractionGroupSize:0,fractionGroupSeparator:" ",suffix:""},$="0123456789abcdefghijklmnopqrstuvwxyz",S=!0;function M(t,e){var i,n,r,a,l,c,h,d,p=this;if(!(p instanceof M))return new M(t,e);if(null==e){if(t&&!0===t._isBigNumber)return p.s=t.s,void(!t.c||t.e>v?p.c=p.e=null:t.e<b?p.c=[p.e=0]:(p.e=t.e,p.c=t.c.slice()));if((c="number"==typeof t)&&0*t==0){if(p.s=1/t<0?(t=-t,-1):1,t===~~t){for(a=0,l=t;l>=10;l/=10,a++);return void(a>v?p.c=p.e=null:(p.e=a,p.c=[t]))}d=String(t)}else{if(!bt.test(d=String(t)))return o(p,d,c);p.s=45==d.charCodeAt(0)?(d=d.slice(1),-1):1}(a=d.indexOf("."))>-1&&(d=d.replace(".","")),(l=d.search(/e/i))>0?(a<0&&(a=l),a+=+d.slice(l+1),d=d.substring(0,l)):a<0&&(a=d.length)}else{if(Et(e,2,$.length,"Base"),10==e&&S)return z(p=new M(t),_+p.e+1,m);if(d=String(t),c="number"==typeof t){if(0*t!=0)return o(p,d,c,e);if(p.s=1/t<0?(d=d.slice(1),-1):1,M.DEBUG&&d.replace(/^0\.0*|\./,"").length>15)throw Error(xt+t)}else p.s=45===d.charCodeAt(0)?(d=d.slice(1),-1):1;for(i=$.slice(0,e),a=l=0,h=d.length;l<h;l++)if(i.indexOf(n=d.charAt(l))<0){if("."==n){if(l>a){a=h;continue}}else if(!r&&(d==d.toUpperCase()&&(d=d.toLowerCase())||d==d.toLowerCase()&&(d=d.toUpperCase()))){r=!0,l=-1,a=0;continue}return o(p,String(t),c,e)}c=!1,(a=(d=s(d,e,10,p.s)).indexOf("."))>-1?d=d.replace(".",""):a=d.length}for(l=0;48===d.charCodeAt(l);l++);for(h=d.length;48===d.charCodeAt(--h););if(d=d.slice(l,++h)){if(h-=l,c&&M.DEBUG&&h>15&&(t>St||t!==yt(t)))throw Error(xt+p.s*t);if((a=a-l-1)>v)p.c=p.e=null;else if(a<b)p.c=[p.e=0];else{if(p.e=a,p.c=[],l=(a+1)%$t,a<0&&(l+=$t),l<h){for(l&&p.c.push(+d.slice(0,l)),h-=$t;l<h;)p.c.push(+d.slice(l,l+=$t));l=$t-(d=d.slice(l)).length}else l-=h;for(;l--;d+="0");p.c.push(+d)}}else p.c=[p.e=0]}function D(t,e,i,s){var o,n,r,a,l;if(null==i?i=m:Et(i,0,8),!t.c)return t.toString();if(o=t.c[0],r=t.e,null==e)l=zt(t.c),l=1==s||2==s&&(r<=g||r>=f)?At(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(;a<e;l+="0",a++);l=At(l,n)}else if(e-=r+(2===s&&n>r),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<t.length;o++)(!(s=new M(t[o])).s||(i=Pt(n,s))===e||0===i&&n.s===e)&&(n=s);return n}function T(t,e,i){for(var s=1,o=e.length;!e[--o];e.pop());for(o=e[0];o>=10;o/=10,s++);return(i=s+i*$t-1)>v?t.c=t.e=null:i<b?t.c=[t.e=0]:(t.e=i,t.c=e),t}function z(t,e,i,s){var o,n,r,a,l,c,h,d=t.c,p=Mt;if(d){t:{for(o=1,a=d[0];a>=10;a/=10,o++);if((n=e-o)<0)n+=$t,r=e,l=d[c=0],h=yt(l/p[o-r-1]%10);else if((c=vt((n+1)/$t))>=d.length){if(!s)break t;for(;d.length<=c;d.push(0));l=h=0,o=1,r=(n%=$t)-$t+1}else{for(l=a=d[c],o=1;a>=10;a/=10,o++);h=(r=(n%=$t)-$t+o)<0?0:yt(l/p[o-r-1]%10)}if(s=s||e<0||null!=d[c+1]||(r<0?l:l%p[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/p[o-r]:0:d[c-1])%10&1||i==(t.s<0?8:7)),e<1||!d[0])return d.length=0,s?(e-=t.e+1,d[0]=p[($t-e%$t)%$t],t.e=-e||0):d[0]=t.e=0,t;if(0==n?(d.length=c,a=1,c--):(d.length=c+1,a=p[$t-n],d[c]=r>0?yt(l/p[o-r]%p[r])*a:0),s)for(;;){if(0==c){for(n=1,r=d[0];r>=10;r/=10,n++);for(r=d[0]+=a,a=1;r>=10;r/=10,a++);n!=a&&(t.e++,d[0]==kt&&(d[0]=1));break}if(d[c]+=a,d[c]!=kt)break;d[c--]=0,a=1}for(n=d.length;0===d[--n];d.pop());}t.e>v?t.c=t.e=null:t.e<b&&(t.c=[t.e=0])}return t}function P(t){var e,i=t.e;return null===i?t.toString():(e=zt(t.c),e=i<=g||i>=f?At(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")&&(Et(i=t[e],0,Ct,e),_=i),t.hasOwnProperty(e="ROUNDING_MODE")&&(Et(i=t[e],0,8,e),m=i),t.hasOwnProperty(e="EXPONENTIAL_AT")&&((i=t[e])&&i.pop?(Et(i[0],-Ct,0,e),Et(i[1],0,Ct,e),g=i[0],f=i[1]):(Et(i,-Ct,Ct,e),g=-(f=i<0?-i:i))),t.hasOwnProperty(e="RANGE"))if((i=t[e])&&i.pop)Et(i[0],-Ct,-1,e),Et(i[1],1,Ct,e),b=i[0],v=i[1];else{if(Et(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")&&(Et(i=t[e],0,9,e),w=i),t.hasOwnProperty(e="POW_PRECISION")&&(Et(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);k=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),$=i}}return{DECIMAL_PLACES:_,ROUNDING_MODE:m,EXPONENTIAL_AT:[g,f],RANGE:[b,v],CRYPTO:y,MODULO_MODE:w,POW_PRECISION:x,FORMAT:k,ALPHABET:$}},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)%$t)<1&&(e+=$t),String(s[0]).length==e){for(e=0;e<s.length;e++)if((i=s[e])<0||i>=kt||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=_:Et(t,0,Ct),o=vt(t/$t),y)if(crypto.getRandomValues){for(e=crypto.getRandomValues(new Uint32Array(o*=2));a<o;)(n=131072*e[a]+(e[a+1]>>>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<o;)(n=281474976710656*(31&e[a])+1099511627776*e[a+1]+4294967296*e[a+2]+16777216*e[a+3]+(e[a+4]<<16)+(e[a+5]<<8)+e[a+6])>=9e15?crypto.randomBytes(7).copy(e,a):(l.push(n%1e14),a+=7);a=o/7}if(!y)for(;a<o;)(n=r())<9e15&&(l[a++]=n%1e14);for(o=l[--a],t%=$t,o&&t&&(n=Mt[$t-t],l[a]=yt(o/n)*n);0===l[a];l.pop(),a--);if(a<0)l=[s=0];else{for(s=-1;0===l[0];l.splice(0,1),s-=$t);for(a=1,n=l[0];n>=10;n/=10,a++);a<$t&&(s-=$t-a)}return c.e=s,c.c=l,c}),M.sum=function(){for(var t=1,e=arguments,i=new M(e[0]);t<e.length;)i=i.plus(e[t++]);return i},s=function(){var t="0123456789";function e(t,e,i,s){for(var o,n,r=[0],a=0,l=t.length;a<l;){for(n=r.length;n--;r[n]*=e);for(r[0]+=s.indexOf(t.charAt(a++)),o=0;o<r.length;o++)r[o]>i-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,d,p,u,g,f,b=s.indexOf("."),v=_,y=m;for(b>=0&&(d=x,x=0,s=s.replace(".",""),u=(f=new M(o)).pow(s.length-b),x=d,f.c=e(Nt(zt(u.c),u.e,"0"),10,n,t),f.e=f.c.length),h=d=(g=e(s,o,n,a?(l=$,t):(l=t,$))).length;0==g[--d];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,p=u.r,h=u.e),b=g[c=h+v+1],d=n/2,p=p||c<0||null!=g[c+1],p=y<4?(null!=b||p)&&(0==y||y==(u.s<0?3:2)):b>d||b==d&&(4==y||p||6==y&&1&g[c-1]||y==(u.s<0?8:7)),c<1||!g[0])s=p?Nt(l.charAt(1),-v,l.charAt(0)):l.charAt(0);else{if(g.length=c,p)for(--n;++g[--c]>n;)g[c]=0,c||(++h,g=[1].concat(g));for(d=g.length;!g[--d];);for(b=0,s="";b<=d;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;o<i;o++)if(t[o]!=e[o]){n=t[o]>e[o]?1:-1;break}return n}function i(t,e,i,s){for(var o=0;i--;)t[i]-=o,o=t[i]<e[i]?1:0,t[i]=o*s+t[i]-e[i];for(;!t[0]&&t.length>1;t.splice(0,1));}return function(s,o,n,r,a){var l,c,h,d,p,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=kt,c=Tt(s.e/$t)-Tt(o.e/$t),S=S/$t|0),h=0;C[h]==(D[h]||0);h++);if(C[h]>(D[h]||0)&&c--,S<0)g.push(1),d=!0;else{for(w=D.length,k=C.length,h=0,S+=2,(p=yt(a/(C[0]+1)))>1&&(C=t(C,p,a),D=t(D,p,a),k=C.length,w=D.length),y=k,b=(f=D.slice(0,k)).length;b<k;f[b++]=0);$=C.slice(),$=[0].concat($),x=C[0],C[1]>=a/2&&x++;do{if(p=0,(l=e(C,f,k,b))<0){if(v=f[0],k!=b&&(v=v*a+(f[1]||0)),(p=yt(v/x))>1)for(p>=a&&(p=a-1),_=(u=t(C,p,a)).length,b=f.length;1==e(u,f,_,b);)p--,i(u,k<_?$:C,_,a),_=u.length,l=1;else 0==p&&(l=p=1),_=(u=C.slice()).length;if(_<b&&(u=[0].concat(u)),i(f,u,b,a),b=f.length,-1==l)for(;e(C,f,k,b)<1;)p++,i(f,k<b?$:C,b,a),b=f.length}else 0===l&&(p++,f=[0]);g[h++]=p,f[0]?f[b++]=D[y]||0:(f=[D[y]],b=1)}while((y++<w||null!=f[0])&&S--);d=null!=f[0],g[0]||g.splice(0,1)}if(a==kt){for(h=1,S=g[0];S>=10;S/=10,h++);z(m,n+(m.e=h+c*$t-1)+1,r,d)}else m.e=c,m.r=+d;return m}}(),a=/^(-?)0([xbo])(?=\w[\w.]*$)/i,l=/^([^.]+)\.$/,c=/^\.([^.]+)$/,h=/^-?(Infinity|NaN)$/,d=/^\s*\+(?=[\w.])|^\s+|\s+$/g,o=function(t,e,i,s){var o,n=i?e:e.replace(d,"");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},p.absoluteValue=p.abs=function(){var t=new M(this);return t.s<0&&(t.s=1),t},p.comparedTo=function(t,e){return Pt(this,new M(t,e))},p.decimalPlaces=p.dp=function(t,e){var i,s,o,n=this;if(null!=t)return Et(t,0,Ct),null==e?e=m:Et(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/$t))*$t,o=i[o])for(;o%10==0;o/=10,s--);return s<0&&(s=0),s},p.dividedBy=p.div=function(t,e){return i(this,new M(t,e),_,m)},p.dividedToIntegerBy=p.idiv=function(t,e){return i(this,new M(t,e),0,1)},p.exponentiatedBy=p.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: "+P(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(+P(h),r?t.s*(2-Rt(t)):+P(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&&Rt(t)?-0:0,h.e>-1&&(n=1/n),new M(a?1/n:n);x&&(n=vt(x/$t+2))}for(r?(i=new M(.5),a&&(t.s=1),l=Rt(t)):l=(o=Math.abs(+P(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=Rt(t);else{if(0===(o=+P(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)},p.integerValue=function(t){var e=new M(this);return null==t?t=m:Et(t,0,8),z(e,e.e+1,t)},p.isEqualTo=p.eq=function(t,e){return 0===Pt(this,new M(t,e))},p.isFinite=function(){return!!this.c},p.isGreaterThan=p.gt=function(t,e){return Pt(this,new M(t,e))>0},p.isGreaterThanOrEqualTo=p.gte=function(t,e){return 1===(e=Pt(this,new M(t,e)))||0===e},p.isInteger=function(){return!!this.c&&Tt(this.e/$t)>this.c.length-2},p.isLessThan=p.lt=function(t,e){return Pt(this,new M(t,e))<0},p.isLessThanOrEqualTo=p.lte=function(t,e){return-1===(e=Pt(this,new M(t,e)))||0===e},p.isNaN=function(){return!this.s},p.isNegative=function(){return this.s<0},p.isPositive=function(){return this.s>0},p.isZero=function(){return!!this.c&&0==this.c[0]},p.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/$t,c=t.e/$t,h=r.c,d=t.c;if(!l||!c){if(!h||!d)return h?(t.s=-e,t):new M(d?r:NaN);if(!h[0]||!d[0])return d[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=d),o.reverse(),e=a;e--;o.push(0));o.reverse()}else for(s=(n=(a=h.length)<(e=d.length))?a:e,a=e=0;e<s;e++)if(h[e]!=d[e]){n=h[e]<d[e];break}if(n&&(o=h,h=d,d=o,t.s=-t.s),(e=(s=d.length)-(i=h.length))>0)for(;e--;h[i++]=0);for(e=kt-1;s>a;){if(h[--s]<d[s]){for(i=s;i&&!h[--i];h[i]=e);--h[i],h[s]+=kt}h[s]-=d[s]}for(;0==h[0];h.splice(0,1),--c);return h[0]?T(t,h,c):(t.s=3==m?-1:1,t.c=[t.e=0],t)},p.modulo=p.mod=function(t,e){var s,o,n=this;return t=new M(t,e),!n.c||!t.s||t.c&&!t.c[0]?new M(NaN):!t.c||n.c&&!n.c[0]?new M(n):(9==w?(o=t.s,t.s=1,s=i(n,t,0,3),t.s=o,s.s*=o):s=i(n,t,0,w),(t=n.minus(s.times(t))).c[0]||1!=w||(t.s=n.s),t)},p.multipliedBy=p.times=function(t,e){var i,s,o,n,r,a,l,c,h,d,p,u,_,m,g,f=this,b=f.c,v=(t=new M(t,e)).c;if(!(b&&v&&b[0]&&v[0]))return!f.s||!t.s||b&&!b[0]&&!v||v&&!v[0]&&!b?t.c=t.e=t.s=null:(t.s*=f.s,b&&v?(t.c=[0],t.e=0):t.c=t.e=null),t;for(s=Tt(f.e/$t)+Tt(t.e/$t),t.s*=f.s,(l=b.length)<(d=v.length)&&(_=b,b=v,v=_,o=l,l=d,d=o),o=l+d,_=[];o--;_.push(0));for(m=kt,g=Dt,o=d;--o>=0;){for(i=0,p=v[o]%g,u=v[o]/g|0,n=o+(r=l);n>o;)i=((c=p*(c=b[--r]%g)+(a=u*c+(h=b[r]/g|0)*p)%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)},p.negated=function(){var t=new M(this);return t.s=-t.s||null,t},p.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/$t,r=t.e/$t,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)/kt|0,a[e]=kt===a[e]?0:a[e]%kt;return o&&(a=[o].concat(a),++r),T(t,a,r)},p.precision=p.sd=function(t,e){var i,s,o,n=this;if(null!=t&&t!==!!t)return Et(t,1,Ct),null==e?e=m:Et(e,0,8),z(new M(n),t,e);if(!(i=n.c))return null;if(s=(o=i.length-1)*$t+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},p.shiftedBy=function(t){return Et(t,-9007199254740991,St),this.times("1e"+t)},p.squareRoot=p.sqrt=function(){var t,e,s,o,n,r=this,a=r.c,l=r.s,c=r.e,h=_+4,d=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(+P(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=d.times(n.plus(i(r,n,h,1))),zt(n.c).slice(0,l)===(e=zt(s.c)).slice(0,l)){if(s.e<c&&--l,"9999"!=(e=e.slice(l-3,l+1))&&(o||"4999"!=e)){+e&&(+e.slice(1)||"5"!=e.charAt(0))||(z(s,s.e+_+2,1),t=!s.times(s).eq(r));break}if(!o&&(z(n,n.e+_+2,0),n.times(n).eq(r))){s=n;break}h+=4,l+=4,o=1}return z(s,s.e+_+1,m,t)},p.toExponential=function(t,e){return null!=t&&(Et(t,0,Ct),t++),D(this,t,e,1)},p.toFixed=function(t,e){return null!=t&&(Et(t,0,Ct),t=t+this.e+1),D(this,t,e)},p.toFormat=function(t,e,i){var s,o=this;if(null==i)null!=t&&e&&"object"==typeof e?(i=e,e=null):t&&"object"==typeof t?(i=t,t=e=null):i=k;else if("object"!=typeof i)throw Error(wt+"Argument not an object: "+i);if(s=o.toFixed(t,e),o.c){var n,r=s.split("."),a=+i.groupSize,l=+i.secondaryGroupSize,c=i.groupSeparator||"",h=r[0],d=r[1],p=o.s<0,u=p?h.slice(1):h,_=u.length;if(l&&(n=a,a=l,l=n,_-=n),a>0&&_>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)),p&&(h="-"+h)}s=d?h+(i.decimalSeparator||"")+((l=+i.fractionGroupSize)?d.replace(new RegExp("\\d{"+l+"}\\B","g"),"$&"+(i.fractionGroupSeparator||"")):d):h}return(i.prefix||"")+s+(i.suffix||"")},p.toFraction=function(t){var e,s,o,n,r,a,l,c,h,d,p,_,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: ")+P(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%$t)<0?$t+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;d=i(l,e,0,1),1!=(n=s.plus(d.times(o))).comparedTo(t);)s=o,o=n,h=c.plus(d.times(n=h)),c=n,e=l.minus(d.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,p=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,p},p.toNumber=function(){return+P(this)},p.toPrecision=function(t,e){return null!=t&&Et(t,1,Ct),D(this,t,e,2)},p.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?At(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"):(Et(t,2,$.length,"Base"),e=s(Nt(zt(i.c),n,"0"),10,t,o,!0)),o<0&&i.c[0]&&(e="-"+e)),e},p.valueOf=p.toJSON=function(){return P(this)},p._isBigNumber=!0,p[Symbol.toStringTag]="BigNumber",p[Symbol.for("nodejs.util.inspect.custom")]=p.valueOf,null!=e&&M.set(e),M}(),It=class{key;left=null;right=null;constructor(t){this.key=t}},Lt=class extends It{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)=>t<e?-1:t>e?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 Ht(this.wrap())}[Symbol.toStringTag]="[object Set]"},Ut=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}},Ht=class extends Ut{getValue(t){return t.key}},Bt=class extends Ut{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 Ot(0),y:new Ot(0)}),o}return Vt},Zt=t=>({set:t=>{Jt=Zt(t)},reset:()=>Zt(t),compare:Wt(t),snap:Kt(t),orient:Gt(t)}),Jt=Zt(),Xt=(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),Yt=(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<i;t++){const i=e[t];this.point.events.push(i),i.point=this.point}this.checkForConsuming()}checkForConsuming(){const t=this.point.events.length;for(let e=0;e<t;e++){const i=this.point.events[e];if(void 0===i.segment.consumedBy)for(let s=e+1;s<t;s++){const t=this.point.events[s];void 0===t.consumedBy&&(i.otherSE.point.events===t.otherSE.point.events&&i.segment.consume(t.segment))}}}getAvailableLinkedEvents(){const t=[];for(let e=0,i=this.point.events.length;e<i;e++){const i=this.point.events[e];i!==this&&!i.segment.ringOut&&i.segment.isInResult()&&t.push(i)}return t}getLeftmostComparator(t){const e=new Map,i=i=>{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;s<o;s++){const o=e[s];if(!o.isInResult()||o.ringOut)continue;let n=null,r=o.leftSE,a=o.rightSE;const l=[r],c=r.point,h=[];for(;n=r,r=a,l.push(r),r.point!==c;)for(;;){const e=r.getAvailableLinkedEvents();if(0===e.length){const t=l[0].point,e=l[l.length-1].point;throw new Error(`Unable to complete output ring starting at [${t.x}, ${t.y}]. Last matching segment found ends at [${e.x}, ${e.y}].`)}if(1===e.length){a=e[0].otherSE;break}let s=null;for(let t=0,e=h.length;t<e;t++)if(h[t].point===r.point){s=t;break}if(null!==s){const e=h.splice(s)[0],o=l.splice(e.index);o.unshift(o[0].otherSE),i.push(new t(o.reverse()));continue}h.push({index:l.length,point:r.point});const o=r.getLeftmostComparator(n);a=e.sort(o)[0].otherSE;break}i.push(new t(l))}return i}constructor(t){this.events=t;for(let e=0,i=t.length;e<i;e++)t[e].segment.ringOut=this;this.poly=null}getGeom(){let t=this.events[0].point;const e=[t];for(let i=1,s=this.events.length-1;i<s;i++){const s=this.events[i].point,o=this.events[i+1].point;0!==Jt.orient(s,t,o)&&(e.push(s),t=s)}if(1===e.length)return null;const i=e[0],s=e[1];0===Jt.orient(i,t,s)&&e.shift(),e.push(e[0]);const o=this.isExteriorRing()?1:-1,n=this.isExteriorRing()?0:e.length-1,r=this.isExteriorRing()?e.length:-1,a=[];for(let t=n;t!=r;t+=o)a.push([e[t].x.toNumber(),e[t].y.toNumber()]);return a}isExteriorRing(){if(void 0===this._isExteriorRing){const t=this.enclosingRing();this._isExteriorRing=!t||!t.isExteriorRing()}return this._isExteriorRing}enclosingRing(){return void 0===this._enclosingRing&&(this._enclosingRing=this._calcEnclosingRing()),this._enclosingRing}_calcEnclosingRing(){let t=this.events[0];for(let e=1,i=this.events.length;e<i;e++){const i=this.events[e];re.compare(t,i)>0&&(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;t<i;t++){const i=this.interiorRings[t].getGeom();null!==i&&e.push(i)}return e}},ce=class{rings;polys;constructor(t){this.rings=t,this.polys=this._composePolys(t)}getGeom(){const t=[];for(let e=0,i=this.polys.length;e<i;e++){const i=this.polys[e].getGeom();null!==i&&t.push(i)}return t}_composePolys(t){const e=[];for(let i=0,s=t.length;i<s;i++){const s=t[i];if(!s.poly)if(s.isExteriorRing())e.push(new le(s));else{const t=s.enclosingRing();t?.poly||e.push(new le(t)),t?.poly?.addInterior(s)}}return e}},he=class{queue;tree;segments;constructor(t,e=_e.compare){this.queue=t,this.tree=new Ft(e),this.segments=[]}process(t){const e=t.segment,i=[];if(t.consumedBy)return t.isLeft?this.queue.delete(t.otherSE):this.tree.delete(e),i;t.isLeft&&this.tree.add(e);let s=e,o=e;do{s=this.tree.lastBefore(s)}while(null!=s&&null!=s.consumedBy);do{o=this.tree.firstAfter(o)}while(null!=o&&null!=o.consumedBy);if(t.isLeft){let n=null;if(s){const t=s.getIntersection(e);if(null!==t&&(e.isAnEndpoint(t)||(n=t),!s.isAnEndpoint(t))){const e=this._splitSafely(s,t);for(let t=0,s=e.length;t<s;t++)i.push(e[t])}}let r=null;if(o){const t=o.getIntersection(e);if(null!==t&&(e.isAnEndpoint(t)||(r=t),!o.isAnEndpoint(t))){const e=this._splitSafely(o,t);for(let t=0,s=e.length;t<s;t++)i.push(e[t])}}if(null!==n||null!==r){let t=null;if(null===n)t=r;else if(null===r)t=n;else{t=re.comparePoints(n,r)<=0?n:r}this.queue.delete(e.rightSE),i.push(e.rightSE);const s=e.split(t);for(let t=0,e=s.length;t<e;t++)i.push(s[t])}i.length>0?(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;t<s;t++)i.push(e[t])}if(!o.isAnEndpoint(t)){const e=this._splitSafely(o,t);for(let t=0,s=e.length;t<s;t++)i.push(e[t])}}}this.tree.delete(e)}return i}_splitSafely(t,e){this.tree.delete(t);const i=t.rightSE;this.queue.delete(i);const s=t.split(e);return s.push(i),void 0===t.consumedBy&&this.tree.add(t),s}},de=new class{type;numMultiPolys;run(t,e,i){de.type=t;const s=[new fe(e,!0)];for(let t=0,e=i.length;t<e;t++)s.push(new fe(i[t],!1));if(de.numMultiPolys=s.length,"difference"===de.type){const t=s[0];let e=1;for(;e<s.length;)null!==Yt(s[e].bbox,t.bbox)?e++:s.splice(e,1)}if("intersection"===de.type)for(let t=0,e=s.length;t<e;t++){const e=s[t];for(let i=t+1,o=s.length;i<o;i++)if(null===Yt(e.bbox,s[i].bbox))return[]}const o=new Ft(re.compare);for(let t=0,e=s.length;t<e;t++){const e=s[t].getSweepEvents();for(let t=0,i=e.length;t<i;t++)o.add(e[t])}const n=new he(o);let r=null;for(0!=o.size&&(r=o.first(),o.delete(r));r;){const t=n.process(r);for(let e=0,i=t.length;e<i;e++){const i=t[e];void 0===i.consumedBy&&o.add(i)}0!=o.size?(r=o.first(),o.delete(r)):r=null}Jt.reset();const a=ae.factory(n.segments);return new ce(a).getGeom()}},pe=de,ue=0,_e=class t{id;leftSE;rightSE;rings;windings;ringOut;consumedBy;prev;_prevInResult;_beforeState;_afterState;_isInResult;static compare(t,e){const i=t.leftSE.point.x,s=e.leftSE.point.x,o=t.rightSE.point.x,n=e.rightSE.point.x;if(n.isLessThan(i))return 1;if(o.isLessThan(s))return-1;const r=t.leftSE.point.y,a=e.leftSE.point.y,l=t.rightSE.point.y,c=e.rightSE.point.y;if(i.isLessThan(s)){if(a.isLessThan(r)&&a.isLessThan(l))return 1;if(a.isGreaterThan(r)&&a.isGreaterThan(l))return-1;const i=t.comparePoint(e.leftSE.point);if(i<0)return 1;if(i>0)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),d=n.minus(s);if(t.isGreaterThan(e)&&h.isLessThan(d))return 1;if(t.isLessThan(e)&&h.isGreaterThan(d))return-1}return o.isGreaterThan(n)?1:o.isLessThan(n)||l.isLessThan(c)?-1:l.isGreaterThan(c)?1:t.id<e.id?-1:t.id>e.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 Jt.orient(this.leftSE.point,t,this.rightSE.point)}getIntersection(t){const e=this.bbox(),i=t.bbox(),s=Yt(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=Xt(e,r)&&0===this.comparePoint(r),c=Xt(i,o)&&0===t.comparePoint(o),h=Xt(e,a)&&0===this.comparePoint(a),d=Xt(i,n)&&0===t.comparePoint(n);if(c&&l)return d&&!h?n:!d&&h?a:null;if(c)return h&&o.x.eq(a.x)&&o.y.eq(a.y)?null:o;if(l)return d&&n.x.eq(r.x)&&n.y.eq(r.y)?null:r;if(d&&h)return null;if(d)return n;if(h)return a;const p=((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)),d=i.y.plus(r.times(s.y));return{x:l.plus(c).div(2),y:h.plus(d).div(2)}})(o,this.vector(),r,t.vector());return null===p?null:Xt(s,p)?Jt.snap(p):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;t<e;t++)this.windings[t]*=-1}consume(e){let i=this,s=e;for(;i.consumedBy;)i=i.consumedBy;for(;s.consumedBy;)s=s.consumedBy;const o=t.compare(i,s);if(0!==o){if(o>0){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;t<e;t++){const e=s.rings[t],o=s.windings[t],n=i.rings.indexOf(e);-1===n?(i.rings.push(e),i.windings.push(o)):i.windings[n]+=o}s.rings=null,s.windings=null,s.consumedBy=i,s.leftSE.consumedBy=i.leftSE,s.rightSE.consumedBy=i.rightSE}}prevInResult(){return void 0!==this._prevInResult||(this.prev?this.prev.isInResult()?this._prevInResult=this.prev:this._prevInResult=this.prev.prevInResult():this._prevInResult=null),this._prevInResult}beforeState(){if(void 0!==this._beforeState)return this._beforeState;if(this.prev){const t=this.prev.consumedBy||this.prev;this._beforeState=t.afterState()}else this._beforeState={rings:[],windings:[],multiPolys:[]};return this._beforeState}afterState(){if(void 0!==this._afterState)return this._afterState;const t=this.beforeState();this._afterState={rings:t.rings.slice(0),windings:t.windings.slice(0),multiPolys:[]};const e=this._afterState.rings,i=this._afterState.windings,s=this._afterState.multiPolys;for(let t=0,s=this.rings.length;t<s;t++){const s=this.rings[t],o=this.windings[t],n=e.indexOf(s);-1===n?(e.push(s),i.push(o)):i[n]+=o}const o=[],n=[];for(let t=0,s=e.length;t<s;t++){if(0===i[t])continue;const s=e[t],r=s.poly;if(-1===n.indexOf(r))if(s.isExterior)o.push(r);else{-1===n.indexOf(r)&&n.push(r);const t=o.indexOf(s.poly);-1!==t&&o.splice(t,1)}}for(let t=0,e=o.length;t<e;t++){const e=o[t].multiPoly;-1===s.indexOf(e)&&s.push(e)}return this._afterState}isInResult(){if(this.consumedBy)return!1;if(void 0!==this._isInResult)return this._isInResult;const t=this.beforeState().multiPolys,e=this.afterState().multiPolys;switch(pe.type){case"union":{const i=0===t.length,s=0===e.length;this._isInResult=i!==s;break}case"intersection":{let i,s;t.length<e.length?(i=t.length,s=e.length):(i=e.length,s=t.length),this._isInResult=s===pe.numMultiPolys&&i<s;break}case"xor":{const i=Math.abs(t.length-e.length);this._isInResult=i%2==1;break}case"difference":{const i=t=>1===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=Jt.snap({x:new Ot(t[0][0]),y:new Ot(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<i;e++){if("number"!=typeof t[e][0]||"number"!=typeof t[e][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const i=Jt.snap({x:new Ot(t[e][0]),y:new Ot(t[e][1])});i.x.eq(o.x)&&i.y.eq(o.y)||(this.segments.push(_e.fromRing(o,i,this)),i.x.isLessThan(this.bbox.ll.x)&&(this.bbox.ll.x=i.x),i.y.isLessThan(this.bbox.ll.y)&&(this.bbox.ll.y=i.y),i.x.isGreaterThan(this.bbox.ur.x)&&(this.bbox.ur.x=i.x),i.y.isGreaterThan(this.bbox.ur.y)&&(this.bbox.ur.y=i.y),o=i)}s.x.eq(o.x)&&s.y.eq(o.y)||this.segments.push(_e.fromRing(o,s,this))}getSweepEvents(){const t=[];for(let e=0,i=this.segments.length;e<i;e++){const i=this.segments[e];t.push(i.leftSE),t.push(i.rightSE)}return t}},ge=class{multiPoly;exteriorRing;interiorRings;bbox;constructor(t,e){if(!Array.isArray(t))throw new Error("Input geometry is not a valid Polygon or MultiPolygon");this.exteriorRing=new me(t[0],this,!0),this.bbox={ll:{x:this.exteriorRing.bbox.ll.x,y:this.exteriorRing.bbox.ll.y},ur:{x:this.exteriorRing.bbox.ur.x,y:this.exteriorRing.bbox.ur.y}},this.interiorRings=[];for(let e=1,i=t.length;e<i;e++){const i=new me(t[e],this,!1);i.bbox.ll.x.isLessThan(this.bbox.ll.x)&&(this.bbox.ll.x=i.bbox.ll.x),i.bbox.ll.y.isLessThan(this.bbox.ll.y)&&(this.bbox.ll.y=i.bbox.ll.y),i.bbox.ur.x.isGreaterThan(this.bbox.ur.x)&&(this.bbox.ur.x=i.bbox.ur.x),i.bbox.ur.y.isGreaterThan(this.bbox.ur.y)&&(this.bbox.ur.y=i.bbox.ur.y),this.interiorRings.push(i)}this.multiPoly=e}getSweepEvents(){const t=this.exteriorRing.getSweepEvents();for(let e=0,i=this.interiorRings.length;e<i;e++){const i=this.interiorRings[e].getSweepEvents();for(let e=0,s=i.length;e<s;e++)t.push(i[e])}return t}},fe=class{isSubject;polys;bbox;constructor(t,e){if(!Array.isArray(t))throw new Error("Input geometry is not a valid Polygon or MultiPolygon");try{"number"==typeof t[0][0][0]&&(t=[t])}catch(t){}this.polys=[],this.bbox={ll:{x:new Ot(Number.POSITIVE_INFINITY),y:new Ot(Number.POSITIVE_INFINITY)},ur:{x:new Ot(Number.NEGATIVE_INFINITY),y:new Ot(Number.NEGATIVE_INFINITY)}};for(let e=0,i=t.length;e<i;e++){const i=new ge(t[e],this);i.bbox.ll.x.isLessThan(this.bbox.ll.x)&&(this.bbox.ll.x=i.bbox.ll.x),i.bbox.ll.y.isLessThan(this.bbox.ll.y)&&(this.bbox.ll.y=i.bbox.ll.y),i.bbox.ur.x.isGreaterThan(this.bbox.ur.x)&&(this.bbox.ur.x=i.bbox.ur.x),i.bbox.ur.y.isGreaterThan(this.bbox.ur.y)&&(this.bbox.ur.y=i.bbox.ur.y),this.polys.push(i)}this.isSubject=e}getSweepEvents(){const t=[];for(let e=0,i=this.polys.length;e<i;e++){const i=this.polys[e].getSweepEvents();for(let e=0,s=i.length;e<s;e++)t.push(i[e])}return t}};function be(t){const e=Math.max(0,Math.min(120,(t-40)/140*120));return`hsl(${Math.round(e)}, 85%, 55%)`}function ve(t,e){return Math.round(t/e)*e}function ye(t,e,i=1){const s=t[0].toFixed(i),o=t[1].toFixed(i),n=e[0].toFixed(i),r=e[1].toFixed(i),a=s<n||s===n&&o<=r,[l,c,h,d]=a?[s,o,n,r]:[n,r,s,o];return`${l},${c}-${h},${d}`}function we(t){return t?.poly?.length>=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<t.length;s++){const o=t[s],n=t[(s+1)%t.length],r=ye(o,n,5);i.has(r)||(i.add(r),e.push([o[0],o[1],n[0],n[1]]))}}return e}function ke(t,e,i){let s=null,o=i;for(const i of xe(e)){const[e,n,r,a]=i,l=r-e,c=a-n,h=l*l+c*c;if(!h)continue;let d=((t[0]-e)*l+(t[1]-n)*c)/h;d=Math.max(0,Math.min(1,d));const p=[e+d*l,n+d*c],u=Math.hypot(t[0]-p[0],t[1]-p[1]);if(u<o){o=u;let t=180*Math.atan2(c,l)/Math.PI;t>=90?t-=180:t<-90&&(t+=180),s={x:p[0],y:p[1],angle:t}}}return s}function $e(t){return["on","open","home","detected","playing","cleaning"].includes(String(t))}function Se(t,e,i=.001){return Math.abs(t[0]-e[0])<i&&Math.abs(t[1]-e[1])<i}function Me(t,e){let i=!1;for(let s=0,o=e.length-1;s<e.length;o=s++){const[n,r]=e[s],[a,l]=e[o];r>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;o<e.length;o++){const n=e[o],r=e[(o+1)%e.length],a=r[0]-n[0],l=r[1]-n[1],c=a*a+l*l;let h=c?((t[0]-n[0])*a+(t[1]-n[1])*l)/c:0;h=Math.max(0,Math.min(1,h));const d=[n[0]+h*a,n[1]+h*l],p=Math.hypot(t[0]-d[0],t[1]-d[1]);p<s&&(s=p,i=d)}return i}function Te(t,e,i=1e-6){if(!e||e.length<2)return!1;for(let s=0;s<e.length;s++)if(De(t,e[s],e[(s+1)%e.length])<=i)return!0;return!1}function ze(t,e,i=1e-6){return!(!e||e.length<3)&&(!Te(t,e,i)&&Me(t,e))}function Pe(t,e,i){return(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0])}function Ee(t,e,i,s,o=1e-9){const n=Pe(i,s,t),r=Pe(i,s,e),a=Pe(t,e,i),l=Pe(t,e,s);return(n>o&&r<-o||n<-o&&r>o)&&(a>o&&l<-o||a<-o&&l>o)}function Re(t,e=24){const i=t.map(t=>t[0]),s=t.map(t=>t[1]),o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1;let c=0,h=0,d=0;for(let e=0;e<t.length;e++){const i=t[e],s=t[(e+1)%t.length],o=i[0]*s[1]-s[0]*i[1];c+=o,h+=(i[0]+s[0])*o,d+=(i[1]+s[1])*o}const p=Math.abs(c)>1e-9?[h/(3*c),d/(3*c)]:[(o+n)/2,(r+a)/2],u=(e,i)=>{const s=((e,i)=>{if(!Me([e,i],t))return-1/0;let s=1/0;for(let o=0;o<t.length;o++){const n=t[o],r=t[(o+1)%t.length];s=Math.min(s,wi([e,i],[n[0],n[1],r[0],r[1]]))}return s})(e,i);return s===-1/0?s:s-.08*Math.hypot(e-p[0],i-p[1])-1e-4*l};let _=null,m=-1/0;for(let t=1;t<e;t++)for(let i=1;i<e;i++){const s=o+(n-o)*t/e,l=r+(a-r)*i/e,c=u(s,l);c>m&&(m=c,_=[s,l])}if(_){const[t,i]=_,s=(n-o)/e,l=(a-r)/e;for(let e=-4;e<=4;e++)for(let o=-4;o<=4;o++){const n=t+s*e/4,r=i+l*o/4,a=u(n,r);a>m&&(m=a,_=[n,r])}}return _||Ae(t)||t[0]}function Ae(t,e=1e-6){if(!t||t.length<3)return null;const i=t.length,s=[t.reduce((t,e)=>t+e[0],0)/i,t.reduce((t,e)=>t+e[1],0)/i];if(ze(s,t,e))return s;for(let s=0;s<i;s++){const o=t[(s-1+i)%i],n=t[s],r=t[(s+1)%i],a=[(o[0]+n[0]+r[0])/3,(o[1]+n[1]+r[1])/3];if(ze(a,t,e))return a}for(let s=0;s<i;s++)for(let o=s+2;o<i;o++){const i=[(t[s][0]+t[o][0])/2,(t[s][1]+t[o][1])/2];if(ze(i,t,e))return i}return null}function Ne(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=Ae(t,i);return!!s&&ze(s,e,i)}return!1}function Oe(t,e,i=1e-6){if(!t||!e||t.length<3||e.length<3)return!1;for(let i=0;i<e.length;i++)for(let s=0;s<t.length;s++)if(Ee(e[i],e[(i+1)%e.length],t[s],t[(s+1)%t.length]))return!1;for(const s of e)if(!ze(s,t,i)&&!Te(s,t,i))return!1;const s=Ae(e,i);return!!s&&ze(s,t,i)&&Ie(e)<Ie(t)-i}function Ie(t){if(!t||t.length<3)return 0;let e=0;for(let i=0;i<t.length;i++){const s=t[i],o=t[(i+1)%t.length];e+=s[0]*o[1]-o[0]*s[1]}return Math.abs(e)/2}function Le(t){return[[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]]}function qe(t,e){if(!t||!e||t.length<3||e.length<3)return null;const i=((t,...e)=>pe.run("union",t,e))(Le(t),Le(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 Fe(t,e,i){for(let s=0;s<t.length;s++)if(De(e,t[s],t[(s+1)%t.length])<=i)return s;return-1}function Ue(t,e){const i=[];for(const s of t)i.length&&Se(i[i.length-1],s,e)||i.push(s);return i.length>1&&Se(i[0],i[i.length-1],e)&&i.pop(),i}function He(t){return t.length?Math.round(t.reduce((t,e)=>t+e,0)/t.length):null}function Be(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 je(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;s<t.length;s++)for(let o=s+1;o<t.length;o++){const n=t[o].x-t[s].x,r=t[o].y-t[s].y,a=Math.hypot(n,r)||.001;if(a<i){const l=(i-a)/2,c=n/a,h=r/a;t[s].x-=c*l,t[s].y-=h*l,t[o].x+=c*l,t[o].y+=h*l,e=!0}}for(const e of t)e.x=Math.max(o,Math.min(n,e.x)),e.y=Math.max(r,Math.min(a,e.y));if(!e)break}}function We(t){if(!t)return null;const e=t.trim();return/^(https?:)?\/\//i.test(e)||e.startsWith("/")||/^[\w./#?=&%~-]+$/i.test(e)?/^[a-z][\w+.-]*:/i.test(e)&&!/^https?:/i.test(e)?null:e:null}Jt.set;const Ge=["badge","ripple","icon_ripple","value"],Ve=["info","more-info","toggle"],Ke=["none","lqi","light","temp","glow"],Ze=["none","lqi","light","temp"],Je=new Set(["light","switch","fan","humidifier"]),Xe=new Set(["lock","alarm_control_panel"]);const Ye="#3ea6ff",Qe=.55;function ti(t){const e=t?.settings||{},i=!t?.plan_url;return{showBorders:e.show_borders??i,showNames:e.show_names??i,color:"string"==typeof e.room_color&&/^#[0-9a-f]{6}$/i.test(e.room_color)?e.room_color:Ye,opacity:"number"==typeof e.room_opacity?Math.min(1,Math.max(0,e.room_opacity)):Qe,fill:["lqi","light","temp","glow"].includes(e.fill_mode)?e.fill_mode:"none",tempMin:"number"==typeof e.temp_min?e.temp_min:20,tempMax:"number"==typeof e.temp_max?e.temp_max:25,showLqi:"boolean"==typeof e.show_lqi?e.show_lqi:null,cardFontScale:"number"==typeof e.card_font_scale&&e.card_font_scale>0?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 ei={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}},ii=/^#[0-9a-f]{6}$/i;function si(t){const e={},i=t?.fill_colors||{};for(const t of Object.keys(ei)){const s=ei[t],o=i[t];e[t]={c:o&&"string"==typeof o.c&&ii.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 oi(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 ni(t,e,i,s,o,n,r){if("lqi"===t){if(null==e)return null;const t=(e-40)/140;return{c:oi(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 s<t?r.temp_cold:s>e?r.temp_hot:r.temp_ok}return null}function ri(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}function ai(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 li(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 ci(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 hi(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 di(t){return t.startsWith("light.")||t.startsWith("switch.")}function pi(t,e,i=1e-6){const s=[];if(!t||!e||t.length<3||e.length<3)return s;for(let o=0;o<t.length;o++){const n=t[o],r=t[(o+1)%t.length],a=r[0]-n[0],l=r[1]-n[1],c=Math.hypot(a,l);if(c<i)continue;const h=a/c,d=l/c;for(let t=0;t<e.length;t++){const o=e[t],r=e[(t+1)%e.length],a=Math.abs((o[0]-n[0])*d-(o[1]-n[1])*h),l=Math.abs((r[0]-n[0])*d-(r[1]-n[1])*h),p=Math.max(i,1e-6*c);if(a>p||l>p)continue;const u=(o[0]-n[0])*h+(o[1]-n[1])*d,_=(r[0]-n[0])*h+(r[1]-n[1])*d,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]+d*m,n[0]+h*g,n[1]+d*g])}}return s}function ui(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 _i(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(l<i)continue;const c=r/l,h=a/l,d=[];for(const s of e){const e=Math.abs((s[0]-t[0])*h-(s[1]-t[1])*c),o=Math.abs((s[2]-t[0])*h-(s[3]-t[1])*c),n=Math.max(i,1e-6*l);if(e>n||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,p=Math.max(0,Math.min(r,a)),u=Math.min(l,Math.max(r,a));u-p>i&&d.push([p,u])}if(!d.length){s.push([t[0],t[1],n[0],n[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let p=0;for(const[e,o]of d)e-p>i&&s.push([t[0]+c*p,t[1]+h*p,t[0]+c*e,t[1]+h*e]),p=Math.max(p,o);l-p>i&&s.push([t[0]+c*p,t[1]+h*p,n[0],n[1]])}return s}const mi=864e5,gi=576e5;function fi(t){const e=new Set,i=t=>{if("string"!=typeof t||!t)return;const i=bi(t);i.startsWith("/api/houseplan/content/")&&e.add(i)};for(const e of t?.spaces||[]){i(e?.plan_url);for(const t of e?.markers||[])for(const e of t?.pdfs||[])i(e?.url)}for(const e of t?.markers||[])for(const t of e?.pdfs||[])i(t?.url);return e}function bi(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 vi(t,e){const i=e?.settings?.fill_mode;return"none"===i||"lqi"===i||"light"===i||"temp"===i?i:t}function yi(t,e=1){const i=Number(t);return Number.isFinite(i)&&i>0?Math.min(3,Math.max(.5,i)):e}function wi(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 xi=new Set(["smoke","gas","carbon_monoxide","moisture","safety","tamper","problem"]);class ki{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.signed={},this.queued=new Set,this.inFlight=new Map,this.retry=new Map,this.disposed=!1}start(t,e){this.disposed=!1,this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),288e5)}dispose(){this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.queued.clear(),this.inFlight.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=bi(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.signed[i],o=s?this.now()-s.at:1/0;return o<gi?s.url:o<mi?(this.request(t,i),s.url):(s&&delete this.signed[i],this.request(t,i),"")}request(t,e){if(!t?.callWS||this.queued.has(e))return;const i=this.now(),s=this.inFlight.get(e);if(void 0!==s&&i-s<15e3)return;const o=this.retry.get(e);o&&i<o.notBefore||(this.queued.add(e),clearTimeout(this.batchTimer),this.batchTimer=setTimeout(()=>{const e=[...this.queued];this.queued.clear(),this.sign(t,e)},30))}sign(t,e){if(e.length&&t?.callWS)for(const i of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e<t.length;e+=i)s.push(t.slice(e,e+i));return s}(e,200)){const e=this.now();for(const t of i)this.inFlight.set(t,e);t.callWS({type:"houseplan/content/sign",paths:i}).then(t=>{if(this.disposed)return;const e=this.now(),s={...this.signed};let o=0;for(const n of i){const i=t?.urls?.[n];"string"==typeof i&&i?(s[n]={url:i,at:e},this.retry.delete(n),o++):this.backOff(n)}o&&(this.signed=s,this.onUpdate())}).catch(()=>{for(const t of i)this.backOff(t)}).finally(()=>{for(const t of i)this.inFlight.get(t)===e&&this.inFlight.delete(t)})}}backOff(t){const e=this.retry.get(t)?.delay||0,i=Math.min(6e4,e?2*e:2e3);this.retry.set(t,{notBefore:this.now()+i,delay:i})}resign(t,e){const i=this.now(),s={};for(const[t,o]of Object.entries(this.signed))e.has(t)&&i-o.at<mi&&(s[t]=o);this.signed=s,this.retry.clear(),this.sign(t,Object.keys(s))}get entries(){return this.signed}get inFlightUrls(){return[...this.inFlight.keys()]}}function $i(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}function Si(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 Mi(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 Di(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.entity_category),s.filter(t=>!t.reg.hidden),s];if("mdi:thermometer"===i||"mdi:air-filter"===i)for(const e of o){const i=e.find(e=>Mi(t,e.eid));if(i)return i.eid}for(const t of o)for(const e of ft){const i=t.find(t=>t.eid.split(".")[0]===e);if(i)return i.eid}for(const t of o)if(t.length)return t[0].eid}function Ci(t,e){const i=!0===e.marker?.is_light?[...e.marker?.controls||[],...e.entities]:e.entities.filter(t=>t.startsWith("light."));return i.find(e=>"on"===t.states[e]?.state)||null}function Ti(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 He(i)}function zi(t,e){for(const i of e){if(!Mi(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 Pi(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 Ei(t,e){for(const i of e){if(!Pi(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 Ri(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}function Ai(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 Ni(t,e){t.marker=e,e.hidden&&(t.hidden=!0),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 Oi(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,d=Ri(e,h),p=new Set(d.map(t=>t.area)),u=$i(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 a=m("device",t.id);if(a&&a.hidden&&!o.filter_seeded)continue;const d=u[t.id]||[],b=Si(e,t,d),v=!o.filter_seeded;if(v&&!r){if(n.has(b))continue;if("Group"===t.model)continue;if(/scene/i.test(t.model||""))continue;if(/bridge/i.test((t.model||"")+(t.name||"")))continue;if("myheat"===b&&t.via_device_id)continue}const y=(t.name_by_user||t.name||l("device.unnamed")).trim(),w=y+"|"+s;let x=Ai(e,y,t.model,d,c);if(d.some(t=>t.startsWith("lock."))&&(x="mdi:lock"),v&&!r&&h&&"mdi:lightbulb"===x&&p.has(s))continue;g[w]=(g[w]||0)+1;const k=g[w]>1?y+" "+g[w]:y,$={id:t.id,name:k,model:t.model||"",area:s,space:i[s],icon:x,entities:d,bindingKind:"device",bindingRef:t.id,pdfs:[]};$.primary=Di(e,d,x),"mdi:thermometer"!==x&&"mdi:air-filter"!==x||($.temp=zi(e,d)),$.primary&&Pi(e,$.primary)&&($.hum=Ei(e,d)),f.push($)}for(const t of d)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&&!o.filter_seeded)continue;const[s,n]=t.binding.split(":");if("device"===s){const s=e.devices[n],o=t.area||s?.area_id||"",r=o&&i[o]||t.space||a,h=s&&u[s.id]||[];let d=s?Ai(e,s.name_by_user||s.name||"",s.model,h,c):"mdi:help-circle";h.some(t=>t.startsWith("lock."))&&(d="mdi:lock");const p={id:t.id,name:s?.name_by_user||s?.name||l("device.fallback"),model:s?.model||"",area:o,space:r,icon:d,entities:h,bindingKind:"device",bindingRef:n};p.primary=Di(e,h,d),"mdi:thermometer"!==d&&"mdi:air-filter"!==d||(p.temp=zi(e,h)),p.primary&&Pi(e,p.primary)&&(p.hum=Ei(e,h)),p.primary&&Pi(e,p.primary)&&(p.hum=Ei(e,h)),Ni(p,t),f.push(p)}else if("entity"===s){const s=e.entities[n],o=t.area||s?.area_id||s?.device_id&&e.devices[s.device_id]?.area_id||"",r=o&&i[o]||t.space||a,l=e.states[n],h=s?.name||l?.attributes?.friendly_name||n;let d=Ai(e,h,"",[n],c);n.startsWith("lock.")&&(d="mdi:lock");const p={id:t.id,name:h,model:"",area:o,space:r,icon:d,entities:[n],primary:n,bindingKind:"entity",bindingRef:n};"mdi:thermometer"!==d&&"mdi:air-filter"!==d||(p.temp=zi(e,[n])),Pi(e,n)&&(p.hum=Ei(e,[n])),Ni(p,t),f.push(p)}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};Ni(o,t),f.push(o)}}return f}function Ii(t,e,i){let s=!1;for(const o of e)if(o.area===i&&!o.hidden)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}function Li(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?zi(t,e):Ei(t,e)}return null}const qi=new RegExp(["water","voda","coolant","flow_?temp","return_?temp","target","setpoint","chip","cpu","processor","board","core_temp","device_temp","batter","akkum","freezer","fridge","oven","kettle","boiler"].join("|"),"i");var Fi={"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 hidden devices (ghosted, this tab only)","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.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 hidden","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}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.is_light":"This device is a light source","marker.is_light_tip":"Makes the icon glow in the “Light sources” fill even without a light entity — for a smart switch driving ordinary fixtures. The glow follows the switch (or the lights bound above).","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide device from plan","marker.hide_tip":'The device is not drawn on the plan but still counts toward the room signal. To see it: the "Show hidden" button in the device editor.'};const Ui={en:Fi,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.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}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.is_light":"Это устройство — источник света","marker.is_light_tip":"Даёт ореол в заливке «Свет по источникам» даже без light-сущности — для умного выключателя с обычными светильниками. Ореол следует за выключателем (или за привязанными выше лампами).","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть устройство с плана","marker.hide_tip":"Устройство не отображается на плане, но участвует в расчёте сигнала комнаты. Показать: кнопка «Показать скрытые» в редакторе устройств."}};function Hi(t,e){if(e&&e in Ui)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Bi(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}(Ui[t][e]??Fi[e]??e,i)}class ji 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 Hi(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:Bi(e,"editor.lang_auto")},{value:"en",label:Bi(e,"editor.lang_en")},{value:"ru",label:Bi(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:Bi(t,"editor.title"),default_floor:Bi(t,"editor.default_floor"),language:Bi(t,"editor.language"),icon_size:Bi(t,"editor.icon_size"),show_temperature:Bi(t,"editor.show_temperature"),live_states:Bi(t,"editor.live_states"),show_signal:Bi(t,"editor.show_signal"),kiosk:Bi(t,"editor.kiosk"),cycle:Bi(t,"editor.cycle")};return B`<ha-form
|
||
.hass=${this.hass}
|
||
.data=${this._config}
|
||
.schema=${this._schema}
|
||
.computeLabel=${t=>e[t.name]||t.name}
|
||
@value-changed=${this._valueChanged}
|
||
></ha-form>`}_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)}}ji.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",ji);const Wi=n`
|
||
:host {
|
||
--hp-bg: var(--card-background-color, #16212e);
|
||
--hp-line: var(--divider-color, #2b3d4f);
|
||
--hp-txt: var(--primary-text-color, #e6edf3);
|
||
--hp-muted: var(--secondary-text-color, #8aa0b3);
|
||
--hp-accent: var(--primary-color, #3ea6ff);
|
||
--hp-on: #ffd45c;
|
||
--hp-open: #ff9f43;
|
||
}
|
||
ha-card {
|
||
overflow: visible; /* overflow:hidden breaks position:sticky on the header */
|
||
}
|
||
.empty {
|
||
padding: 40px 24px;
|
||
color: var(--hp-txt);
|
||
text-align: center;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.empty .big {
|
||
--mdc-icon-size: 56px;
|
||
color: var(--hp-accent);
|
||
opacity: 0.7;
|
||
}
|
||
.empty .muted {
|
||
color: var(--hp-muted);
|
||
font-size: 13px;
|
||
margin: 0;
|
||
}
|
||
.empty .btn {
|
||
margin-top: 8px;
|
||
}
|
||
.hdr {
|
||
position: sticky;
|
||
top: var(--header-height, 56px);
|
||
z-index: 20;
|
||
background: var(--card-background-color, var(--hp-bg));
|
||
border-radius: var(--ha-card-border-radius, 12px) var(--ha-card-border-radius, 12px) 0 0;
|
||
}
|
||
.head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 14px;
|
||
border-bottom: 1px solid var(--hp-line);
|
||
flex-wrap: wrap;
|
||
}
|
||
.title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
white-space: nowrap;
|
||
}
|
||
.title ha-icon {
|
||
color: var(--hp-accent);
|
||
--mdc-icon-size: 18px;
|
||
}
|
||
.tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
background: rgba(127, 127, 127, 0.12);
|
||
padding: 3px;
|
||
border-radius: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
@media (max-width: 620px) {
|
||
.head { gap: 6px; padding: 8px 10px; }
|
||
.head .count { display: none; }
|
||
.head .title { font-size: 14px; }
|
||
}
|
||
.tab {
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--hp-muted);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 6px 13px;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: 0.15s;
|
||
font-family: inherit;
|
||
}
|
||
.tab:hover {
|
||
color: var(--hp-txt);
|
||
}
|
||
.tab.active {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
}
|
||
.count {
|
||
font-size: 12px;
|
||
color: var(--hp-muted);
|
||
}
|
||
.spacer {
|
||
flex: 1;
|
||
}
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
border: 1px solid var(--hp-line);
|
||
background: transparent;
|
||
color: var(--hp-txt);
|
||
padding: 6px 10px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: 0.15s;
|
||
font-family: inherit;
|
||
font-size: 12.5px;
|
||
}
|
||
.btn ha-icon {
|
||
--mdc-icon-size: 17px;
|
||
}
|
||
.btn:hover {
|
||
border-color: var(--hp-accent);
|
||
}
|
||
.btn.on {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
border-color: var(--hp-accent);
|
||
}
|
||
.btn.ghost {
|
||
border: none;
|
||
}
|
||
.btn[disabled] {
|
||
opacity: 0.5;
|
||
pointer-events: none;
|
||
}
|
||
.stage.noplan {
|
||
background: #ffffff;
|
||
}
|
||
.stage {
|
||
position: relative;
|
||
width: 100%;
|
||
container-type: inline-size;
|
||
overflow: hidden;
|
||
touch-action: none; /* custom pinch/pan gestures */
|
||
background: var(--ha-card-background, var(--card-background-color, #111));
|
||
}
|
||
.zoomwrap {
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
.zoomctl {
|
||
display: inline-flex;
|
||
gap: 2px;
|
||
background: rgba(127, 127, 127, 0.12);
|
||
border-radius: 8px;
|
||
padding: 2px;
|
||
}
|
||
.zoomctl .zb {
|
||
border: none;
|
||
padding: 5px 7px;
|
||
}
|
||
.zoomctl .zb[disabled] {
|
||
opacity: 0.4;
|
||
pointer-events: none;
|
||
}
|
||
.zoombadge {
|
||
position: absolute;
|
||
left: 8px;
|
||
bottom: 8px;
|
||
background: var(--card-background-color, var(--hp-bg));
|
||
opacity: 0.92;
|
||
color: var(--hp-txt);
|
||
border: 1px solid var(--hp-accent);
|
||
border-radius: 8px;
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
pointer-events: none;
|
||
}
|
||
.stage svg {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
.room {
|
||
transition: 0.12s;
|
||
cursor: default; /* v1.40.1: rooms are not clickable — the label's link icon is */
|
||
}
|
||
.room.overlay {
|
||
fill: transparent;
|
||
stroke: transparent;
|
||
stroke-width: 2;
|
||
}
|
||
.room.overlay:not(.styled):hover {
|
||
fill: #9aa0a6;
|
||
fill-opacity: 0.22;
|
||
stroke: var(--hp-muted);
|
||
}
|
||
.room.yard {
|
||
fill: rgba(75, 140, 90, 0.14);
|
||
stroke: #4b8c5a;
|
||
stroke-width: 2;
|
||
}
|
||
.room.yard:not(.styled):hover {
|
||
fill: rgba(75, 140, 90, 0.24);
|
||
stroke: #6fbf86;
|
||
}
|
||
.room.styled {
|
||
stroke: var(--room-stroke, transparent);
|
||
stroke-opacity: var(--room-stroke-op, 0);
|
||
stroke-width: 2.5;
|
||
fill: var(--room-fill, transparent);
|
||
fill-opacity: var(--room-fill-op, 0);
|
||
}
|
||
/* hover: darken the current fill instead of recoloring; grey when unfilled */
|
||
.room.styled.filled:hover {
|
||
filter: brightness(0.78);
|
||
stroke-opacity: 1;
|
||
}
|
||
.room.styled:not(.filled):hover {
|
||
fill: #9aa0a6;
|
||
fill-opacity: 0.22;
|
||
stroke-opacity: 1;
|
||
}
|
||
/* doors & windows */
|
||
.op-leaf {
|
||
transition: transform 0.6s ease;
|
||
}
|
||
.op-arc {
|
||
stroke-width: 1.5;
|
||
transition: stroke-dashoffset 0.6s ease;
|
||
}
|
||
/* hover affordance: a rounded outline hugging the wall strip + a grab cursor */
|
||
.op-outline {
|
||
fill: none;
|
||
stroke: var(--hp-accent);
|
||
stroke-width: 1.5;
|
||
opacity: 0;
|
||
transition: opacity 0.15s;
|
||
pointer-events: none;
|
||
}
|
||
.stage.markup g.opening:hover .op-outline {
|
||
opacity: 0.9;
|
||
}
|
||
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
|
||
no hit target — View must not interact with them at all */
|
||
.op-hit {
|
||
fill: transparent;
|
||
pointer-events: none;
|
||
cursor: default;
|
||
}
|
||
.stage.markup .op-hit {
|
||
pointer-events: auto;
|
||
cursor: grab;
|
||
touch-action: none; /* drags, not scrolls, on touch */
|
||
}
|
||
.stage.markup .op-hit:active {
|
||
cursor: grabbing;
|
||
}
|
||
.oplock {
|
||
pointer-events: none; /* inert while editing; clickable in View (rule below) */
|
||
position: absolute;
|
||
transform: translate(-50%, -50%);
|
||
width: calc(var(--icon-size, 2.5cqw) * 0.62);
|
||
height: calc(var(--icon-size, 2.5cqw) * 0.62);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-line);
|
||
z-index: 1;
|
||
}
|
||
.stage.mode-view .oplock {
|
||
pointer-events: auto;
|
||
cursor: pointer;
|
||
}
|
||
.oplock ha-icon {
|
||
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.4);
|
||
display: flex;
|
||
line-height: 0;
|
||
}
|
||
.oplock.locked { color: #66d17a; border-color: #66d17a; }
|
||
.oplock.unlocked { color: var(--hp-open); border-color: var(--hp-open); }
|
||
.oplock.unknown { color: var(--hp-muted); }
|
||
.btn.lockact {
|
||
width: 100%;
|
||
justify-content: center;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-top: 8px;
|
||
}
|
||
.btn.lockact.warn {
|
||
color: var(--error-color, #d33);
|
||
border-color: var(--error-color, #d33);
|
||
}
|
||
.oprow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 0;
|
||
}
|
||
.oprow b { margin-left: auto; }
|
||
.oprow.ok b { color: #66d17a; }
|
||
.oprow.warn b { color: var(--hp-open); }
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.op-leaf, .op-arc { transition: none; }
|
||
}
|
||
/* presence ripples: opted into per device, drawn around the anchor point */
|
||
.dev.noicon {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
box-shadow: none;
|
||
}
|
||
.dev ha-icon {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
.ripple {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: calc(var(--dev-size) * var(--ripple-scale, 3));
|
||
height: calc(var(--dev-size) * var(--ripple-scale, 3));
|
||
transform: translate(-50%, -50%);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
.ripple i {
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: 50%;
|
||
border: 2px solid var(--ripple-color, var(--hp-accent));
|
||
opacity: 0;
|
||
}
|
||
.ripple.active i {
|
||
animation: hp-ripple 2.4s ease-out infinite;
|
||
}
|
||
.ripple.active i:nth-child(2) { animation-delay: 0.8s; }
|
||
.ripple.active i:nth-child(3) { animation-delay: 1.6s; }
|
||
/* idle: a faint dot keeps the spot marked without pulling the eye */
|
||
.ripple:not(.active) i:nth-child(n + 2) { display: none; }
|
||
.ripple:not(.active) i {
|
||
inset: calc(50% - 0.15 * var(--dev-size));
|
||
opacity: 0.3;
|
||
animation: none;
|
||
}
|
||
@keyframes hp-ripple {
|
||
0% { transform: scale(0.18); opacity: 0.7; }
|
||
70% { opacity: 0.22; }
|
||
100% { transform: scale(1); opacity: 0; }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.ripple.active i { animation: none; opacity: 0.3; }
|
||
.ripple.active i:nth-child(n + 2) { display: none; }
|
||
}
|
||
.roomlabel {
|
||
pointer-events: none; /* draggable only in plan mode (rule below) */
|
||
position: absolute;
|
||
transform: translate(-50%, -50%);
|
||
font-size: calc(var(--icon-size, 2.5cqw) * 0.5 * var(--rl-scale, 1) * var(--rl-font, 1) * var(--rl-space, 1));
|
||
font-weight: 700;
|
||
letter-spacing: 0.04em;
|
||
white-space: nowrap;
|
||
cursor: grab;
|
||
user-select: none;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.15em;
|
||
text-align: center;
|
||
}
|
||
.rlname {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25em;
|
||
font-size: calc(1em * var(--rl-name, 1));
|
||
}
|
||
/* Switching spaces by swipe or on the kiosk carousel: the plan flies out
|
||
the way the finger went and the next one arrives from the other side. */
|
||
@keyframes hp-slide-left {
|
||
0% { transform: translateX(22%); opacity: 0; }
|
||
100% { transform: translateX(0); opacity: 1; }
|
||
}
|
||
@keyframes hp-slide-right {
|
||
0% { transform: translateX(-22%); opacity: 0; }
|
||
100% { transform: translateX(0); opacity: 1; }
|
||
}
|
||
.zoomwrap.slide-left { animation: hp-slide-left 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }
|
||
.zoomwrap.slide-right { animation: hp-slide-right 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.zoomwrap.slide-left, .zoomwrap.slide-right { animation: none; }
|
||
}
|
||
/* The name is the anchor: the label box is centred on the room point, so
|
||
anything that takes part in its layout SHIFTS THE NAME. The gear button
|
||
and the metrics hang below as absolutes — the name renders in exactly
|
||
the same place in view mode and in the plan editor (owner's request),
|
||
and the button sits at the very bottom of the card. */
|
||
/* Standalone, centred on the room, sized from the device icon: icon-size
|
||
already rescales with the view, so the button zooms WITH the plan
|
||
instead of keeping a constant screen size (owner's spec). */
|
||
.rlgearbtn {
|
||
--gear-h: calc(var(--icon-size, 2.5cqw) * 0.77); /* owner: half the previous size */
|
||
position: absolute;
|
||
/* dead-centred on the room, both axes (owner's spec) */
|
||
transform: translate(-50%, -50%);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35em;
|
||
height: var(--gear-h);
|
||
padding: 0 calc(var(--gear-h) * 0.38);
|
||
border: 0;
|
||
border-radius: 999px;
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
font: inherit;
|
||
font-size: calc(var(--gear-h) * 0.42);
|
||
font-weight: 600;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
opacity: 0.92;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
||
z-index: 2;
|
||
}
|
||
.rlgearbtn { transition: opacity 0.15s, filter 0.15s; }
|
||
.rlgearbtn:hover { opacity: 1; filter: brightness(1.18); }
|
||
.rlgearbtn ha-icon { --mdc-icon-size: calc(var(--gear-h) * 0.55); display: inline-flex; }
|
||
.rlgear {
|
||
--mdc-icon-size: 0.9em;
|
||
display: inline-flex;
|
||
margin-right: 0.2em;
|
||
opacity: 0.6;
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
}
|
||
.rlgear:hover { opacity: 1; }
|
||
.rlgo {
|
||
--mdc-icon-size: 0.85em;
|
||
display: inline-flex;
|
||
opacity: 0.55;
|
||
}
|
||
.stage.mode-view .rlgo {
|
||
pointer-events: auto;
|
||
cursor: pointer;
|
||
}
|
||
.stage.mode-view .rlgo:hover { opacity: 1; }
|
||
.roomlabel .rlmetrics {
|
||
position: absolute; /* below the name, outside the centring math */
|
||
top: calc(100% + 0.15em);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
white-space: nowrap;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.55em;
|
||
font-size: calc(0.75em * var(--rl-meta, 1)); /* feedback: 0.62 was unreadable on a tablet */
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
opacity: 0.9;
|
||
}
|
||
.roomlabel .rlm {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.12em;
|
||
}
|
||
.roomlabel .rlm ha-icon {
|
||
--mdc-icon-size: 1.05em;
|
||
display: inline-flex;
|
||
}
|
||
.roomlabel .rlm.lit { opacity: 1; }
|
||
.bindharow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.bindharow .entcheck { opacity: 0.9; }
|
||
.dropbtn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
text-align: left;
|
||
border: 1px solid var(--hp-muted);
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: var(--hp-txt);
|
||
padding: 8px 10px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
margin-top: 6px;
|
||
}
|
||
.dropbtn .ref { color: var(--hp-muted); font-size: 11px; margin-left: auto; }
|
||
.dropbtn ha-icon { --mdc-icon-size: 18px; margin-left: 4px; }
|
||
.dropbtn.open { border-color: var(--hp-accent); }
|
||
.droppanel {
|
||
border: 1px solid var(--hp-accent);
|
||
border-top: none;
|
||
border-radius: 0 0 8px 8px;
|
||
padding: 6px;
|
||
margin-top: -4px;
|
||
}
|
||
.ctrlchips { display: flex; flex-wrap: wrap; gap: 5px; margin: 4px 0; }
|
||
.ctrlchip {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
background: var(--hp-accent); color: var(--text-primary-color, #fff);
|
||
border-radius: 12px; padding: 3px 8px; font-size: 12px;
|
||
}
|
||
.ctrlchip ha-icon { --mdc-icon-size: 14px; cursor: pointer; }
|
||
.ctrllist { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
|
||
.ctrlopt {
|
||
display: flex; align-items: center; gap: 7px; text-align: left;
|
||
border: 0; background: transparent; color: var(--hp-txt);
|
||
padding: 5px 7px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px;
|
||
}
|
||
.ctrlopt:hover { background: var(--secondary-background-color, rgba(128,128,128,0.15)); }
|
||
.ctrlopt .sub { color: var(--hp-muted); font-size: 11px; margin-left: auto; }
|
||
.ctrlopt ha-icon { --mdc-icon-size: 16px; }
|
||
.ctrlstates { display: flex; flex-direction: column; gap: 3px; }
|
||
.ctrlstate { display: inline-flex; align-items: center; gap: 5px; color: var(--hp-muted); }
|
||
.ctrlstate.on { color: var(--hp-txt); }
|
||
.ctrlstate ha-icon { --mdc-icon-size: 15px; }
|
||
.cardpreview {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
margin: 8px 0 2px;
|
||
padding: 10px;
|
||
border: 1px dashed var(--hp-muted);
|
||
border-radius: 8px;
|
||
}
|
||
.cardpreview .cpname { font-weight: 700; letter-spacing: 0.04em; }
|
||
.cardpreview .cpmeta {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3em;
|
||
font-weight: 600;
|
||
opacity: 0.85;
|
||
}
|
||
.cardpreview .cpmeta ha-icon { --mdc-icon-size: 1.05em; }
|
||
.iconauto {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
margin: 4px 0 0;
|
||
}
|
||
.iconauto ha-icon { --mdc-icon-size: 18px; }
|
||
.rlhandle {
|
||
display: none;
|
||
position: absolute;
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 2px;
|
||
background: var(--hp-accent);
|
||
border: 1px solid var(--card-background-color, #fff);
|
||
z-index: 2;
|
||
}
|
||
.rlhandle.tl { left: -6px; top: -6px; cursor: nwse-resize; }
|
||
.rlhandle.br { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
||
.rlhandle.tr { right: -6px; top: -6px; cursor: nesw-resize; }
|
||
.rlhandle.bl { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
||
.stage.markup .roomlabel:hover .rlhandle { display: block; }
|
||
.stage.markup .roomlabel { pointer-events: auto; }
|
||
.roomlabel:active { cursor: grabbing; }
|
||
.measurelayer {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
}
|
||
.alignline {
|
||
stroke: var(--hp-accent);
|
||
stroke-width: 1.2;
|
||
stroke-dasharray: 4 4;
|
||
pointer-events: none;
|
||
opacity: 0.9;
|
||
}
|
||
.aligndot {
|
||
fill: var(--hp-accent);
|
||
pointer-events: none;
|
||
}
|
||
.measurelabel.on45 {
|
||
color: #4bd28f;
|
||
border-color: #4bd28f;
|
||
}
|
||
.hdr.kioskhide { display: none; }
|
||
.kioskdots {
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: 14px;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
gap: 8px;
|
||
z-index: 5;
|
||
pointer-events: none;
|
||
}
|
||
.kdot {
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
background: var(--hp-muted);
|
||
opacity: 0.55;
|
||
}
|
||
.kdot.on { background: var(--hp-accent); opacity: 1; }
|
||
.measurelabel {
|
||
position: absolute;
|
||
transform: translate(12px, -150%);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
padding: 1px 6px;
|
||
border-radius: 6px;
|
||
background: rgba(0, 0, 0, 0.72);
|
||
color: #fff;
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
user-select: none;
|
||
z-index: 3;
|
||
}
|
||
/* decor (background) layer */
|
||
.decorlayer .dshape { pointer-events: none; }
|
||
.stage.mode-decor .decorlayer .dshape {
|
||
pointer-events: visiblePainted;
|
||
cursor: pointer;
|
||
}
|
||
.stage.mode-decor.dtool-select .decorlayer .dshape { cursor: move; }
|
||
.decorlayer .dsel {
|
||
filter: drop-shadow(0 0 3px var(--hp-accent));
|
||
}
|
||
.decorlayer .ddraft {
|
||
opacity: 0.75;
|
||
stroke-dasharray: 6 5;
|
||
pointer-events: none;
|
||
}
|
||
.decorlayer text {
|
||
font-weight: 600;
|
||
user-select: none;
|
||
dominant-baseline: middle;
|
||
text-anchor: middle;
|
||
}
|
||
.stage.mode-decor {
|
||
outline: 2px solid #26a69a;
|
||
outline-offset: -2px;
|
||
}
|
||
.stage.mode-decor.dtool-line, .stage.mode-decor.dtool-rect,
|
||
.stage.mode-decor.dtool-ellipse, .stage.mode-decor.dtool-text {
|
||
cursor: crosshair;
|
||
}
|
||
.stage.mode-decor .room, .stage.mode-decor .devlayer { pointer-events: none; }
|
||
.stage.mode-decor .oplock { pointer-events: none; }
|
||
/* decor mode: everything but the decor itself fades back */
|
||
.stage.mode-decor .room,
|
||
.stage.mode-decor .devlayer,
|
||
.stage.mode-decor .opening,
|
||
.stage.mode-decor .rlabel {
|
||
opacity: 0.35;
|
||
}
|
||
.decorbar .dcolor {
|
||
width: 30px; height: 26px; padding: 0; border: none; background: none; cursor: pointer;
|
||
}
|
||
.decorbar .dwidth {
|
||
font-family: inherit; font-size: 12px; border-radius: 6px;
|
||
background: var(--hp-bg2, transparent); color: var(--hp-txt); border: 1px solid var(--hp-muted);
|
||
padding: 3px 5px;
|
||
}
|
||
.decorbar .dfill {
|
||
display: inline-flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer;
|
||
}
|
||
.opghost {
|
||
stroke: var(--hp-open, #ff9800);
|
||
stroke-width: 5;
|
||
stroke-linecap: round;
|
||
stroke-dasharray: 7 6;
|
||
opacity: 0.85;
|
||
pointer-events: none;
|
||
}
|
||
.opghost-dot {
|
||
fill: var(--hp-open, #ff9800);
|
||
opacity: 0.85;
|
||
pointer-events: none;
|
||
}
|
||
.rlabel {
|
||
fill: var(--hp-muted);
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
pointer-events: none;
|
||
text-anchor: middle;
|
||
}
|
||
.stage.edit .room {
|
||
pointer-events: none;
|
||
}
|
||
.stage.markup {
|
||
cursor: crosshair;
|
||
}
|
||
/* room-picking stages: merge (both clicks) and split before a room is chosen */
|
||
.stage.markup.tool-merge,
|
||
.stage.markup.tool-split.pickstage,
|
||
.stage.markup.tool-delroom {
|
||
cursor: pointer;
|
||
}
|
||
/* open-wall tool: default until a shared wall is under the cursor */
|
||
.stage.markup.tool-openwall { cursor: default; }
|
||
.stage.markup.tool-openwall.wallhot { cursor: pointer; }
|
||
.openwall {
|
||
stroke: var(--ow-stroke, var(--hp-muted));
|
||
stroke-width: 2.5;
|
||
stroke-dasharray: 7 7;
|
||
stroke-linecap: butt;
|
||
pointer-events: none;
|
||
opacity: 0.9;
|
||
}
|
||
/* rooms with open stretches: the polygon's own stroke is fully off
|
||
(hover included) — the trimmed .room-outline path draws the walls */
|
||
.room.noedge {
|
||
stroke-opacity: 0 !important;
|
||
}
|
||
.room-outline {
|
||
fill: none;
|
||
stroke-width: 2.5;
|
||
pointer-events: none;
|
||
}
|
||
/* Plan editor: trimmed outlines use the markup blue */
|
||
.room-outline.outlined {
|
||
stroke: rgba(62, 166, 255, 0.55);
|
||
stroke-opacity: 1;
|
||
}
|
||
.openwalls.hot .openwall {
|
||
stroke: #ffc14d;
|
||
opacity: 1;
|
||
}
|
||
.openwall-preview {
|
||
stroke: #ffc14d;
|
||
stroke-width: 5;
|
||
stroke-dasharray: 7 7;
|
||
stroke-linecap: round;
|
||
pointer-events: none;
|
||
opacity: 0.95;
|
||
}
|
||
/* an already-open boundary under the cursor: the click will CLOSE it */
|
||
.openwall-preview.willclose {
|
||
stroke: #f25a4a;
|
||
stroke-dasharray: none;
|
||
}
|
||
.stage.markup .room {
|
||
pointer-events: none;
|
||
}
|
||
.stage.markup .devlayer .dev {
|
||
display: none; /* in plan mode the icons do not get in the way; labels stay */
|
||
}
|
||
/* mode frames: the edit modes are visible at a glance */
|
||
.stage.mode-plan {
|
||
outline: 2px solid #ffc14d;
|
||
outline-offset: -2px;
|
||
}
|
||
.stage.mode-devices {
|
||
outline: 2px solid var(--hp-accent);
|
||
outline-offset: -2px;
|
||
}
|
||
.modes {
|
||
display: inline-flex;
|
||
gap: 2px;
|
||
background: rgba(127, 127, 127, 0.12);
|
||
border-radius: 10px;
|
||
padding: 3px;
|
||
}
|
||
.modetab {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--hp-muted);
|
||
padding: 5px 10px;
|
||
border-radius: 8px;
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
}
|
||
.modetab ha-icon { --mdc-icon-size: 15px; }
|
||
.modetab .closex {
|
||
--mdc-icon-size: 13px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-left: 2px;
|
||
opacity: 0.75;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
}
|
||
.modetab .closex:hover { opacity: 1; }
|
||
.editbar .barclose {
|
||
padding: 4px 6px;
|
||
margin-left: 6px;
|
||
}
|
||
.modetab.active {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
}
|
||
@media (max-width: 720px) {
|
||
.modetab .ml { display: none; }
|
||
}
|
||
.room.outlined {
|
||
stroke: rgba(62, 166, 255, 0.55);
|
||
fill: rgba(62, 166, 255, 0.06);
|
||
}
|
||
/* AFTER .outlined: same specificity — source order decides (gotcha x4) */
|
||
.room.picked {
|
||
stroke: #ffc14d;
|
||
stroke-width: 3;
|
||
fill: rgba(255, 193, 77, 0.25);
|
||
}
|
||
.griddot {
|
||
fill: var(--hp-accent);
|
||
opacity: 0.75;
|
||
stroke: rgba(0, 0, 0, 0.35);
|
||
stroke-width: 0.4;
|
||
}
|
||
.seg {
|
||
stroke: var(--hp-accent);
|
||
stroke-width: 2.5;
|
||
stroke-linecap: round;
|
||
}
|
||
.pathline {
|
||
stroke: #ffc14d;
|
||
stroke-width: 3;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
.preview {
|
||
stroke: #ffc14d;
|
||
stroke-width: 2;
|
||
stroke-dasharray: 6 5;
|
||
opacity: 0.7;
|
||
}
|
||
.vertex {
|
||
fill: #ffc14d;
|
||
stroke: #4a2800;
|
||
stroke-width: 1;
|
||
}
|
||
.vertex.first {
|
||
fill: #4bd28f;
|
||
stroke: #04121f;
|
||
}
|
||
.areasel,
|
||
.namein {
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-line);
|
||
color: var(--hp-txt);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
font-size: 13px;
|
||
font-family: inherit;
|
||
}
|
||
.namein {
|
||
width: 130px;
|
||
}
|
||
.dev.valonly {
|
||
width: auto;
|
||
min-width: var(--dev-size, var(--icon-size, 2.5cqw));
|
||
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.16);
|
||
}
|
||
.dev.valonly .valtext {
|
||
font-size: calc(var(--icon-size, 2.5cqw) * 0.45);
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
}
|
||
/* RGB lights: the bulb takes the light's actual color */
|
||
.dev.rgb ha-icon { color: var(--light-color); }
|
||
.dev.rgb.on {
|
||
box-shadow: 0 0 10px var(--light-color);
|
||
border-color: var(--light-color);
|
||
background: var(--hp-bg);
|
||
color: var(--light-color);
|
||
}
|
||
/* alarms pulse red over everything */
|
||
.dev.alarm::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: calc(var(--icon-size, 2.5cqw) * -0.35);
|
||
border: 3px solid #f25a4a;
|
||
border-radius: 50%;
|
||
animation: hp-alarm 1s ease-out infinite;
|
||
pointer-events: none;
|
||
}
|
||
@keyframes hp-alarm {
|
||
0% { transform: scale(0.7); opacity: 1; }
|
||
100% { transform: scale(1.25); opacity: 0; }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.dev.alarm::after { animation: none; opacity: 0.9; }
|
||
}
|
||
.dev .newdot {
|
||
position: absolute;
|
||
top: calc(var(--icon-size, 2.5cqw) * -0.12);
|
||
right: calc(var(--icon-size, 2.5cqw) * -0.12);
|
||
width: calc(var(--icon-size, 2.5cqw) * 0.34);
|
||
height: calc(var(--icon-size, 2.5cqw) * 0.34);
|
||
border-radius: 50%;
|
||
background: #f0301f;
|
||
border: 2px solid var(--card-background-color, var(--hp-bg));
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
.devlayer {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
}
|
||
.dev {
|
||
position: absolute;
|
||
/* per-device multiplier on top of the card-wide icon size */
|
||
--dev-size: calc(var(--icon-size, 2.5cqw) * var(--dev-scale, 1));
|
||
/* центр квадрата (включая рамку 1px) точно на точке привязки: -(size/2 + border) */
|
||
width: var(--dev-size);
|
||
height: var(--dev-size);
|
||
margin: calc(var(--dev-size) / -2 - 1px) 0 0 calc(var(--dev-size) / -2 - 1px);
|
||
border-radius: 22%;
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-line);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--hp-txt);
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
transition: background 0.15s, border-color 0.15s, opacity 0.2s;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||
z-index: 2;
|
||
}
|
||
.dev ha-icon {
|
||
/* from --dev-size, NOT --icon-size: the per-device size multiplier must
|
||
scale the GLYPH with its badge. Pinned to the base size, "make this
|
||
icon bigger" grew an empty box around a default-size glyph (user
|
||
report via the owner, 2026-07-29). */
|
||
--mdc-icon-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.62);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
line-height: 0;
|
||
}
|
||
.stage.mode-devices .dev { cursor: grab; }
|
||
.stage.mode-devices .dev:active { cursor: grabbing; }
|
||
.dev:hover {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
z-index: 5;
|
||
}
|
||
.dev.on {
|
||
background: var(--hp-on);
|
||
border-color: var(--hp-on);
|
||
color: #503c00;
|
||
box-shadow: 0 0 8px rgba(255, 212, 92, 0.7);
|
||
}
|
||
.dev.open {
|
||
background: var(--hp-open);
|
||
border-color: var(--hp-open);
|
||
color: #4a2800;
|
||
}
|
||
.dev.unavail {
|
||
opacity: 0.35;
|
||
}
|
||
.dev.virtual {
|
||
border-style: dashed;
|
||
}
|
||
/* "hide from plan" flag, shown only in the device editor with the
|
||
"show hidden devices" toggle on (docs/FILTERING.md). BLUE, so a hidden
|
||
device cannot be mistaken for an unavailable one (translucent dark) —
|
||
and no live-state paint at all: a ghost is configuration, not status
|
||
(owner's request). */
|
||
.dev.ghost {
|
||
opacity: 0.6;
|
||
border-style: dashed;
|
||
border-color: var(--hp-accent);
|
||
background: rgba(62, 166, 255, 0.22); /* fallback for old WebViews */
|
||
background: color-mix(in srgb, var(--hp-accent) 30%, var(--card-background-color, #1c2530));
|
||
color: var(--hp-accent);
|
||
box-shadow: none;
|
||
}
|
||
.dev.sel {
|
||
border-color: #ffc14d;
|
||
box-shadow: 0 0 0 3px rgba(255, 193, 77, 0.35);
|
||
}
|
||
|
||
.dev .tval {
|
||
position: absolute;
|
||
left: 100%;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
margin-left: calc(var(--icon-size, 2.5cqw) * 0.1);
|
||
background: var(--card-background-color, var(--hp-bg));
|
||
border: 1px solid var(--hp-accent);
|
||
border-radius: calc(var(--icon-size, 2.5cqw) * 0.18);
|
||
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.14);
|
||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.45);
|
||
font-weight: 700;
|
||
line-height: calc(var(--icon-size, 2.5cqw) * 0.68);
|
||
color: var(--hp-txt);
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
.dev .hval {
|
||
position: absolute;
|
||
left: 100%;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
margin-left: calc(var(--icon-size, 2.5cqw) * 0.1);
|
||
background: var(--card-background-color, var(--hp-bg));
|
||
border: 1px solid #4fc3f7;
|
||
border-radius: calc(var(--icon-size, 2.5cqw) * 0.18);
|
||
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.14);
|
||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.45);
|
||
font-weight: 700;
|
||
line-height: calc(var(--icon-size, 2.5cqw) * 0.68);
|
||
color: var(--hp-txt);
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
.dev .lqi {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
margin-top: calc(var(--icon-size, 2.5cqw) * 0.05);
|
||
font-size: calc(var(--icon-size, 2.5cqw) * 0.38);
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
.editbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 9px 14px;
|
||
border-bottom: 1px solid var(--hp-line);
|
||
font-size: 13px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.tab .tabedit {
|
||
--mdc-icon-size: 13px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-left: 6px;
|
||
opacity: 0.4;
|
||
}
|
||
.tab:hover .tabedit {
|
||
opacity: 0.9;
|
||
}
|
||
.tab.tabadd {
|
||
padding: 6px 8px;
|
||
}
|
||
.tab.tabadd ha-icon {
|
||
--mdc-icon-size: 15px;
|
||
}
|
||
.srcrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
padding: 2px 0;
|
||
}
|
||
.dispsection {
|
||
margin-top: 12px !important;
|
||
padding-top: 8px;
|
||
border-top: 1px solid var(--hp-line);
|
||
font-weight: 600;
|
||
color: var(--hp-txt) !important;
|
||
}
|
||
.colorrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.colorrow input[type='color'] {
|
||
width: 42px;
|
||
height: 28px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
padding: 1px;
|
||
cursor: pointer;
|
||
}
|
||
.colorrow input[type='range'] { flex: 1; }
|
||
.colorrow .tempin { width: 70px; flex: none; }
|
||
.temprange {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
margin-left: auto;
|
||
color: var(--hp-muted);
|
||
font-size: 12px;
|
||
}
|
||
/* beat the generic .dialog .body .namein { width:100% } rule */
|
||
.dialog .body .temprange .tempin { width: 56px; flex: none; padding: 3px 6px; }
|
||
.dialog .body .colorrow .tempin { width: 72px; flex: none; }
|
||
.srcrow { flex-wrap: nowrap; }
|
||
.srcrow > span:first-of-type { white-space: nowrap; }
|
||
.colorrow .opl { color: var(--hp-muted); font-size: 12px; }
|
||
.colorrow .opv { font-size: 12px; min-width: 34px; text-align: right; }
|
||
.planrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
/* the "already uploaded" picker: a plan is never deleted for being
|
||
unreferenced, so it has to be findable again */
|
||
.savedplans {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
max-height: 240px;
|
||
overflow: auto;
|
||
margin: 6px 0 2px;
|
||
padding: 6px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 8px;
|
||
background: var(--hp-bg2, rgba(255, 255, 255, 0.03));
|
||
}
|
||
.savedplan {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.savedplan.cur { outline: 1px solid var(--hp-accent); border-radius: 6px; }
|
||
.savedplan img {
|
||
width: 56px;
|
||
height: 40px;
|
||
object-fit: contain;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 4px;
|
||
background: #fff;
|
||
flex: none;
|
||
}
|
||
.savedmeta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
|
||
.savedmeta b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.savedmeta .muted { font-size: 11px; }
|
||
.savedplan .btn.danger ha-icon { color: #f25a4a; }
|
||
.savedplan .btn[disabled] { opacity: 0.4; pointer-events: none; }
|
||
.planprev {
|
||
max-width: 120px;
|
||
max-height: 70px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
}
|
||
.planname {
|
||
font-size: 12.5px;
|
||
max-width: 150px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.planname.muted {
|
||
color: var(--hp-muted);
|
||
}
|
||
.filebtn {
|
||
cursor: pointer;
|
||
}
|
||
.btn.danger {
|
||
border-color: #b3402a;
|
||
color: #ff7a5c;
|
||
}
|
||
.dialog .row .spacer {
|
||
flex: 1;
|
||
}
|
||
.dialog.wide {
|
||
width: min(500px, 94vw);
|
||
}
|
||
.dialog .body {
|
||
max-height: 66vh;
|
||
overflow-y: auto;
|
||
}
|
||
.descin {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-line);
|
||
color: var(--hp-txt);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
font-size: 13px;
|
||
font-family: inherit;
|
||
resize: vertical;
|
||
/* flex column of the dialog body squeezes textareas — keep a usable height */
|
||
min-height: 92px;
|
||
flex-shrink: 0;
|
||
line-height: 1.35;
|
||
}
|
||
.bindsel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 8px;
|
||
padding: 8px;
|
||
}
|
||
.bindsel .opt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
border: 1px solid var(--hp-line);
|
||
background: transparent;
|
||
color: var(--hp-txt);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
cursor: pointer;
|
||
font-size: 12.5px;
|
||
font-family: inherit;
|
||
}
|
||
.bindsel .opt.on {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
border-color: var(--hp-accent);
|
||
}
|
||
.curbind {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12.5px;
|
||
color: var(--hp-txt);
|
||
flex-wrap: wrap;
|
||
}
|
||
.curbind .ref {
|
||
color: var(--hp-muted);
|
||
font-size: 11px;
|
||
}
|
||
.candlist {
|
||
max-height: 160px;
|
||
overflow-y: auto;
|
||
border-top: 1px solid var(--hp-line);
|
||
}
|
||
.cand {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
font-size: 12.5px;
|
||
}
|
||
.cand:hover {
|
||
background: rgba(127, 127, 127, 0.15);
|
||
}
|
||
.cand.sel {
|
||
background: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
}
|
||
.cand .cs {
|
||
color: var(--hp-muted);
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
}
|
||
.cand.sel .cs {
|
||
color: var(--text-primary-color, #fff);
|
||
opacity: 0.85;
|
||
}
|
||
.cand.muted {
|
||
color: var(--hp-muted);
|
||
cursor: default;
|
||
}
|
||
.pdfedit {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
align-items: center;
|
||
}
|
||
.pdftag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 6px;
|
||
padding: 3px 6px;
|
||
font-size: 12px;
|
||
}
|
||
.pdftag a {
|
||
color: var(--hp-txt);
|
||
text-decoration: none;
|
||
max-width: 150px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.pdftag .x {
|
||
--mdc-icon-size: 15px;
|
||
cursor: pointer;
|
||
color: var(--hp-muted);
|
||
}
|
||
.pdftag .x:hover {
|
||
color: #ff7a5c;
|
||
}
|
||
.entlist {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.entrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
border-radius: 8px;
|
||
background: var(--secondary-background-color, rgba(128, 128, 128, 0.12));
|
||
}
|
||
.entrow ha-icon { --mdc-icon-size: 20px; color: var(--hp-muted); }
|
||
.entrow.on ha-icon { color: var(--hp-accent); }
|
||
.entrow .en { flex: 1; font-size: 13px; }
|
||
.entrow .ev { font-size: 13px; color: var(--hp-muted); }
|
||
.entbtn {
|
||
min-width: 74px;
|
||
min-height: 32px;
|
||
padding: 4px 12px;
|
||
border: 1px solid var(--hp-muted);
|
||
border-radius: 999px;
|
||
background: transparent;
|
||
color: var(--hp-txt);
|
||
font: inherit;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
}
|
||
.entbtn.on {
|
||
background: var(--hp-accent);
|
||
border-color: var(--hp-accent);
|
||
color: var(--text-primary-color, #fff);
|
||
font-weight: 600;
|
||
}
|
||
.inforow {
|
||
display: flex;
|
||
gap: 10px;
|
||
font-size: 13px;
|
||
margin: 3px 0;
|
||
}
|
||
.inforow .k {
|
||
color: var(--hp-muted);
|
||
min-width: 84px;
|
||
}
|
||
.inforow a {
|
||
color: var(--hp-accent);
|
||
word-break: break-all;
|
||
}
|
||
.infodesc {
|
||
font-size: 13px;
|
||
white-space: pre-wrap;
|
||
margin-top: 6px;
|
||
}
|
||
.infodesc.muted {
|
||
color: var(--hp-muted);
|
||
}
|
||
.pdflist {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.pdf {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
color: var(--hp-accent);
|
||
text-decoration: none;
|
||
}
|
||
ha-icon-picker {
|
||
display: block;
|
||
}
|
||
.floorrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 4px;
|
||
font-size: 13.5px;
|
||
cursor: pointer;
|
||
}
|
||
.floorrow .floorlvl {
|
||
color: var(--hp-muted);
|
||
font-size: 11px;
|
||
border: 1px solid var(--hp-line);
|
||
border-radius: 5px;
|
||
padding: 0 5px;
|
||
}
|
||
.importprog {
|
||
margin-left: auto;
|
||
color: var(--hp-muted);
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
}
|
||
.rhint {
|
||
font-size: 12px;
|
||
color: var(--hp-muted);
|
||
margin-bottom: 6px;
|
||
}
|
||
.rtest {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.rtest .namein { flex: 1; }
|
||
.rtest ha-icon { color: var(--hp-accent); }
|
||
.rtesticon { font-size: 11px; color: var(--hp-muted); }
|
||
.rrow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin: 2px 0;
|
||
}
|
||
.rrow .rpat { flex: 2; }
|
||
.rrow .ricon { flex: 1.4; }
|
||
.rrow .rpat.bad { border-color: #ff7a5c; }
|
||
.rrow .rprev { --mdc-icon-size: 18px; color: var(--hp-txt); min-width: 18px; }
|
||
.rrow .ract {
|
||
--mdc-icon-size: 16px;
|
||
color: var(--hp-muted);
|
||
cursor: pointer;
|
||
}
|
||
.rrow .ract:hover { color: var(--hp-txt); }
|
||
.rrow .ract.del:hover { color: #ff7a5c; }
|
||
|
||
.gsrow .gsl {
|
||
min-width: 150px;
|
||
font-size: 12.5px;
|
||
color: var(--hp-muted);
|
||
}
|
||
.dialogwrap {
|
||
background: rgba(0, 0, 0, 0.45);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 90;
|
||
}
|
||
.dialog {
|
||
background: var(--card-background-color, var(--hp-bg));
|
||
border: 1px solid var(--hp-accent);
|
||
border-radius: 14px;
|
||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
|
||
width: min(360px, 92vw);
|
||
overflow: hidden;
|
||
}
|
||
.dialog .hd {
|
||
padding: 12px 16px;
|
||
font-weight: 600;
|
||
border-bottom: 1px solid var(--hp-line);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.dialog .hd ha-icon {
|
||
color: var(--hp-accent);
|
||
}
|
||
.dialog .body {
|
||
padding: 14px 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.dialog .body label {
|
||
font-size: 12px;
|
||
color: var(--hp-muted);
|
||
margin-top: 6px;
|
||
}
|
||
.dialog .body .namein,
|
||
.dialog .body .areasel {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
.dialog .row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
border-top: 1px solid var(--hp-line);
|
||
}
|
||
.editbar .warn {
|
||
color: #ffc14d;
|
||
}
|
||
.editbar .sname {
|
||
font-weight: 600;
|
||
}
|
||
.editbar input {
|
||
width: 74px;
|
||
background: transparent;
|
||
border: 1px solid var(--hp-line);
|
||
color: var(--hp-txt);
|
||
border-radius: 6px;
|
||
padding: 5px 7px;
|
||
font-size: 13px;
|
||
}
|
||
.editbar label,
|
||
.editbar .hint {
|
||
color: var(--hp-muted);
|
||
font-size: 12px;
|
||
}
|
||
.menuwrap {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 80;
|
||
}
|
||
.menu {
|
||
position: fixed;
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-accent);
|
||
border-radius: 10px;
|
||
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
|
||
min-width: 210px;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
transform: translate(0, 8px);
|
||
}
|
||
.menu .hd {
|
||
padding: 8px 12px;
|
||
font-weight: 600;
|
||
font-size: 12.5px;
|
||
border-bottom: 1px solid var(--hp-line);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.menu .hd ha-icon,
|
||
.menu .it.all ha-icon {
|
||
color: var(--hp-accent);
|
||
--mdc-icon-size: 16px;
|
||
}
|
||
.menu .it {
|
||
padding: 8px 12px;
|
||
font-size: 12.5px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.menu .it ha-icon {
|
||
--mdc-icon-size: 16px;
|
||
color: var(--hp-muted);
|
||
}
|
||
.menu .it:hover {
|
||
background: rgba(127, 127, 127, 0.15);
|
||
}
|
||
.menu .it.all {
|
||
color: var(--hp-accent);
|
||
font-weight: 600;
|
||
}
|
||
.tip {
|
||
position: fixed;
|
||
pointer-events: none;
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-accent);
|
||
color: var(--hp-txt);
|
||
padding: 6px 10px;
|
||
border-radius: 8px;
|
||
font-size: 12.5px;
|
||
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
|
||
z-index: 99;
|
||
max-width: 260px;
|
||
}
|
||
.tip .m {
|
||
color: var(--hp-muted);
|
||
font-size: 11px;
|
||
display: block;
|
||
}
|
||
.toast {
|
||
position: fixed;
|
||
left: 50%;
|
||
bottom: 22px;
|
||
transform: translateX(-50%);
|
||
background: var(--hp-bg);
|
||
border: 1px solid var(--hp-accent);
|
||
color: var(--hp-txt);
|
||
padding: 9px 16px;
|
||
border-radius: 10px;
|
||
font-size: 13px;
|
||
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
|
||
z-index: 120;
|
||
max-width: 90vw;
|
||
}
|
||
`,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(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 Xi(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 Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(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=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({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=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId),p=d.filter(t=>!t.hidden),u=function(t,e,i){const s={},o=i/100*Gi*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=Ji(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,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(d,i,r),_=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?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)continue;const e=zi(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}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j`<polygon class="${s}" style="${n}" points="${e.poly.map(t=>t.join(",")).join(" ")}"></polygon>`:j`<rect class="${s}" style="${n}" x="${e.x}" y="${e.y}" width="${e.w}" height="${e.h}" rx="${.03*Math.min(e.w,e.h)}"></rect>`;return j`${c}${a?j`<text class="rlabel" x="${l[0]}" y="${l[1]}">${e.name}</text>`:G}`}),m=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};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,u,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`<div class="dev ${e.virtual?"virtual":""}" style="left:${n}%;top:${r}%">
|
||
<ha-icon icon="${e.icon}"></ha-icon>
|
||
</div>`}),g=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[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`<div class="roomlabel" style="left:${r}%;top:${a}%;color:${o.color};opacity:${l}">${e.name}</div>`}):[],f=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
|
||
<div class="hp-static-stage" style="aspect-ratio:${s[2]}/${s[3]}">
|
||
<svg viewBox="${s[0]} ${s[1]} ${s[2]} ${s[3]}" preserveAspectRatio="xMidYMid meet">
|
||
${f?j`<image href="${f}" x="${i.bg.x}" y="${i.bg.y}" width="${i.bg.w}" height="${i.bg.h}" preserveAspectRatio="none" />`:G}
|
||
${_}
|
||
</svg>
|
||
<div class="devlayer" style="--icon-size:${r}cqw">${m}${g}</div>
|
||
</div>
|
||
`}let Qi=null,ts=null,es=!1;const is=new Set;function ss(){if(Qi)return Qi;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 os(t){return Qi?Promise.resolve(Qi):ts||(ts=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Qi={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!es&&t.connection?.subscribeEvents){es=!0;const e=()=>{Qi=null,is.forEach(t=>t())};try{await t.connection.subscribeEvents(e,"houseplan_config_updated"),await t.connection.subscribeEvents(e,"houseplan_layout_updated")}catch{es=!1}}return Qi}(t).finally(()=>{ts=null}),ts)}class ns 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 Hi(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:Bi(t,"editor.space"),title:Bi(t,"editor.title"),show_button:Bi(t,"editor.show_button"),button_label:Bi(t,"editor.button_label"),button_target:Bi(t,"editor.button_target"),aspect_ratio:Bi(t,"editor.aspect_ratio"),icon_size:Bi(t,"editor.icon_size")};return B`<ha-form
|
||
.hass=${this.hass}
|
||
.data=${this._config}
|
||
.schema=${this._schema}
|
||
.computeLabel=${t=>e[t.name]||t.name}
|
||
@value-changed=${this._valueChanged}
|
||
></ha-form>`}_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)}}ns.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-space-card-editor")||customElements.define("houseplan-space-card-editor",ns);const rs=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 as extends lt{constructor(){super(...arguments),this._snap=null,this._loading=!1,this._loadedOnce=!1,this._signer=new ki(()=>this.requestUpdate()),this._goToSpace=()=>{const t=(this._config?.button_target||"/plan-doma").replace(/#.*$/,"");rs(`${t}#space=${encodeURIComponent(this._config.space)}`)}}static getConfigElement(){return document.createElement("houseplan-space-card-editor")}static getStubConfig(t){const e=ss();return{type:"custom:houseplan-space-card",space:Zi(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||ss()}connectedCallback(){var t;super.connectedCallback(),this._unsub=(t=()=>{this._loading=!1,this._snap=null,this.requestUpdate()},is.add(t),()=>is.delete(t)),this._signer.start(()=>this.hass,()=>this._referenced())}disconnectedCallback(){this._unsub?.(),this._unsub=void 0,this._signer.dispose(),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 os(this.hass);this._snap=t,this._loadedOnce=!0}catch{}finally{this._loading=!1,this.requestUpdate()}}}get _lang(){return Hi(this.hass,this._config?.language)}getCardSize(){const t=Zi(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`<ha-card><div class="hp-static-error">${t}</div></ha-card>`}_referenced(){return fi(this._snap?.config)}render(){if(!this._config)return G;const t=this._snap?.config;if(!t)return B`<ha-card><div class="hp-static-error">${Bi(this._lang,"space_card.loading")}</div></ha-card>`;const e=this._config.space,i=Yi({hass:this.hass,cfg:t,layout:this._snap?.layout||{},spaceId:e,iconSize:this._config.icon_size,lang:this._lang,displayUrl:t=>this._signer.display(this.hass,t)});if(!i)return this._errorCard(Bi(this._lang,"space_card.not_found",{id:e}));const s=Zi(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||Bi(this._lang,"space_card.button");return B`
|
||
<ha-card>
|
||
${o?B`<div class="hp-static-title">${o}</div>`:G}
|
||
${i}
|
||
${n?B`<div class="hp-static-foot">
|
||
<button class="hp-static-btn" @click=${this._goToSpace}>${r}</button>
|
||
</div>`:G}
|
||
</ha-card>
|
||
`}}as.properties={hass:{attribute:!1},_config:{state:!0},_snap:{state:!0}},as.styles=[Wi,n`
|
||
.hp-static-title {
|
||
font-weight: 700;
|
||
padding: 10px 14px 6px;
|
||
font-size: 16px;
|
||
color: var(--primary-text-color);
|
||
}
|
||
.hp-static-stage {
|
||
position: relative;
|
||
width: 100%;
|
||
container-type: inline-size;
|
||
overflow: hidden;
|
||
pointer-events: none; /* kill ALL interaction on the schematic (§4) */
|
||
background: var(--ha-card-background, var(--card-background-color, #111));
|
||
}
|
||
.hp-static-stage svg {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
.hp-static-stage .devlayer {
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
.hp-static-foot {
|
||
padding: 8px 12px 12px;
|
||
pointer-events: auto; /* the button stays clickable */
|
||
}
|
||
.hp-static-btn {
|
||
width: 100%;
|
||
padding: 9px 14px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: var(--primary-color, #3ea6ff);
|
||
color: var(--text-primary-color, #fff);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.hp-static-btn:hover {
|
||
filter: brightness(1.08);
|
||
}
|
||
.hp-static-error {
|
||
padding: 24px;
|
||
text-align: center;
|
||
color: var(--secondary-text-color, #9aa4ad);
|
||
}
|
||
`],customElements.get("houseplan-space-card")||customElements.define("houseplan-space-card",as),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 ls="houseplan_card_layout_v1",cs="houseplan_card_cfg_v1",hs="houseplan_card_zoom_v1",ds="houseplan_card_nav_v1",ps="houseplan_card_kiosk_v1",us=1e3,_s=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},ms=(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},gs=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class fs 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._unsubLayout=null,this._layoutRev=0,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._slide="",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._hdrH=118,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._showHidden=!1,this._signer=new ki(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=ms(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(ls,JSON.stringify(this._layout))},600),this._writesPending=0,this._writeChain=Promise.resolve(),this._saveConfigDebounced=ms(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"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._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=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:dt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}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)}_slideTo(t,e){if(t===this._space)return;const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._space=t,this._selId=null,this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slide="",this.requestUpdate()},260),this.requestUpdate())}_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._slideTo(t[(e+1)%t.length],"left"),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._signer.start(()=>this.hass,()=>fi(this._serverCfg)),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._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),clearTimeout(this._layoutSyncTimer),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(hs)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(ps)||"null");this._kioskScale={icon:yi(t?.icon),font:yi(t?.font)}}catch{}try{const e=JSON.parse(localStorage.getItem(cs)||"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(cs,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.plan_aspect||"")+","+(i.plan_url||"").length+","+(i.rooms?.length||0)+","+(i.openings?.length||0)+","+(i.decor?.length||0)+";";for(const t of i.rooms||[]){const i=t.poly?.[0],s=t.poly?.[t.poly.length-1];e+=(t.poly?.length||0)+"."+(t.id||"")+"."+(t.open_to||[]).join("+")+"."+(t.area||"")+"."+JSON.stringify(t.settings||0)+"."+(t.x??"")+","+(t.y??"")+","+(t.w??"")+","+(t.h??"")+","+(i?i[0]+"/"+i[1]:"")+","+(s?s[0]+"/"+s[1]:"")+";"}}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(){if(!this._serverCfg)return[];const t=this._serverCfg;return Zi(t).map((e,i)=>{const s=t.spaces[i]?.plan_url;return e.bg&&s?{...e,bg:{...e.bg,href:s}}:e})}_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.filter_seeded?this._showHidden:!!this._settings.show_all}_toggleShowAll(){if(this._serverCfg){if(this._settings.filter_seeded)return this._showHidden=!this._showHidden,void this.requestUpdate();this._serverCfg={...this._serverCfg,settings:{...this._serverCfg.settings,show_all:!this._settings.show_all}},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate()}}_seedHiddenDevices(){if(!this._serverCfg||!this._norm||!this._canEdit)return;const t=this._serverCfg,e=function(t){const{hass:e,areaToSpace:i,markers:s,settings:o,excluded:n,iconRules:r}=t,a=!1!==o.group_lights,l=Ri(e,a),c=new Set(l.map(t=>t.area)),h=$i(e),d=new Set(s.map(t=>t.binding)),p=[];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(d.has("device:"+t.id))continue;const o=h[t.id]||[],l=Si(e,t,o);let u=n.has(l)||"Group"===t.model||/scene/i.test(t.model||"")||/bridge/i.test((t.model||"")+(t.name||""))||"myheat"===l&&!!t.via_device_id;!u&&a&&c.has(s)&&"mdi:lightbulb"===Ai(e,(t.name_by_user||t.name||"").trim(),t.model,o,r)&&(u=!0),u&&p.push("device:"+t.id)}return p}({hass:this.hass,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),markers:this._markers,settings:this._settings,excluded:this._excluded,firstSpaceId:this._model[0]?.id||"",iconRules:this._iconRules});if(!e.length&&t.settings?.filter_seeded)return;t.markers=t.markers||[];const i=[];for(const s of e){const e="h"+s.slice(s.indexOf(":")+1);t.markers.push({id:e,binding:s,hidden:!0}),i.push(s.slice(s.indexOf(":")+1))}const s={...t.settings||{},filter_seeded:!0};delete s.show_all,i.length&&Array.isArray(s.new_device_ids)&&(s.new_device_ids=s.new_device_ids.filter(t=>!i.includes(t))),t.settings=s,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=pt(t)),this._rulesCompiled}get _fillColors(){return si(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;t&&!this._roViewport&&(this._roViewport=new ResizeObserver(()=>this._refitView()),this._roViewport.observe(t));const e=this.renderRoot.querySelector(".hdr");if(e&&t&&!this._roHdr){const i=()=>{const e=this.renderRoot.querySelector("ha-card");if(!e)return;const i=t.getBoundingClientRect().top-e.getBoundingClientRect().top,s=Math.min(Math.max(e.getBoundingClientRect().top,0),120),o=Math.round(i+s);o>=0&&Math.abs(o-this._hdrH)>1&&(this._hdrH=o)};this._roHdr=new ResizeObserver(()=>requestAnimationFrame(i)),this._roHdr.observe(e),this._onWinResize=()=>requestAnimationFrame(i),window.addEventListener("resize",this._onWinResize),i()}if(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._layoutRev=e?.rev??0,this._unsubCfg||(this._unsubCfg=await this.hass.connection.subscribeEvents(t=>{(t?.data?.rev??-1)!==this._cfgRev&&this._reloadConfigOnly()},"houseplan_config_updated")),this._unsubLayout||(this._unsubLayout=await this.hass.connection.subscribeEvents(t=>this._onLayoutEvent(Number(t?.data?.rev??-1)),"houseplan_layout_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(ls)||"{}")||{}}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)}))}}_display(t){return this._signer.display(this.hass,t)}_resign(){this._signer.resign(this.hass,fi(this._serverCfg))}_onLayoutEvent(t){t<=this._layoutRev||(clearTimeout(this._layoutSyncTimer),this._layoutSyncTimer=window.setTimeout(()=>{t<=this._layoutRev||this._reloadLayoutOnly()},200))}_noteLayoutRev(t){const e=t?.rev;"number"==typeof e&&e>this._layoutRev&&(this._layoutRev=e)}async _reloadLayoutOnly(){if(!this._serverStorage||!this.hass?.callWS)return;const t=new Map;for(const e of this._dirtyPos)this._layout[e]&&t.set(e,this._layout[e]);this._persistLayout.pending()&&this._persistLayout.flush();for(const[e,i]of this._sentPos)t.set(e,i);try{const e=await this.hass.callWS({type:"houseplan/layout/get"}),i={...e?.layout||{}};for(const[e,s]of t)i[e]=s;this._layout=i,this._layoutRev=e?.rev??this._layoutRev,this._cacheSnapshot(),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")+":"+Hi(t,this._config?.language);e===this._regSignature&&this._devices.length||(this._regSignature=e,this._devices=Oi({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(),this._seedHiddenDevices())}_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=Ti(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*us*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),d=a/c,p=l/Math.max(h,1),u=o.map((t,e)=>({x:n.x+r+d*(e%c+.5),y:n.y+r+p*(Math.floor(e/c)+.5)}));je(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)return{x:i.x*us,y:i.y*us}}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._layout[t.id]?.k;this._layout={...this._layout,[t.id]:{s:t.space,x:o/us,y:n/us,...r?{k:r}:{}}}}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(di);if(e.length)return e.some(t=>"on"===this.hass.states[t]?.state)?"on":"";if(Ci(this.hass,t))return"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("climate"===s){const t=i.attributes?.hvac_action;return null!=t?["heating","cooling","drying","fan"].includes(t)?"on":"":["off","unknown"].includes(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:zi(this.hass,t.entities):null}_liveHum(t){return this._config?.show_temperature&&t.primary&&Pi(this.hass,t.primary)?Ei(this.hass,t.entities):null}_openMoreInfo(t){t?_s(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(di);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||Xe.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 Bi(Hi(this.hass,this._config?.language),t,e)}get _stageEl(){return this.renderRoot.querySelector(".stage")}_baseVb(){const t=this._spaceModel();if(t.bg)return t.vb;if("view"!==this._mode)return t.vb;const e=this._devices.filter(e=>e.space===t.id).map(t=>{const e=this._pos(t);return[e.x,e.y]}),i=function(t,e=.05,i){let s=1/0,o=1/0,n=-1/0,r=-1/0;const a=(t,e)=>{Number.isFinite(t)&&Number.isFinite(e)&&(t<-250||t>1250||e<-250||e>1250||(t<s&&(s=t),e<o&&(o=e),t>n&&(n=t),e>r&&(r=e)))};for(const e of t.rooms||[])if(e.poly)for(const t of e.poly)a(t[0],t[1]);else null!=e.x&&null!=e.y&&(a(e.x,e.y),a(e.x+(e.w||0),e.y+(e.h||0)));for(const t of i||[])a(t[0],t[1]);if(s>n||o>r)return null;if(n-s<30){const t=(s+n)/2;s=t-100,n=t+100}if(r-o<30){const t=(o+r)/2;o=t-100,r=t+100}const l=Math.max(n-s,r-o)*e;return{x:s-l,y:o-l,w:n-s+2*l,h:r-o+2*l}}(t,.05,e);return i?[i.x,i.y,i.w,i.h]:t.vb}_stageAspect(){const t=this._stageEl,e=this._baseVb();return t&&t.clientHeight?t.clientWidth/t.clientHeight:e[2]/e[3]}_viewOr(t){return this._view&&this._view.w?this._view:Be(t,this._stageAspect())}_screenToVb(t,e){const i=this._stageEl,s=this._viewOr(this._baseVb()),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:t.w>=e.w?e.x+(e.w-t.w)/2:Math.max(e.x,Math.min(e.x+e.w-t.w,t.x)),y:t.h>=e.h?e.y+(e.h-t.h)/2:Math.max(e.y,Math.min(e.y+e.h-t.h,t.y))}}_applyView(t,e,i){const s=this._baseVb(),o=Be(s,this._stageAspect()),n=Math.min(fs.ZOOM_MAX,Math.max(fs.ZOOM_MIN,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=Be(this._baseVb(),this._stageAspect()),n=Math.min(fs.ZOOM_MAX,Math.max(fs.ZOOM_MIN,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._baseVb();this._zoom=1,this._view=Be(t,this._stageAspect()),this._saveZoom()}_saveZoom(){this._zoomBySpace={...this._zoomBySpace,[this._space]:this._zoom};try{localStorage.setItem(hs,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._baseVb();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)return;if(this._markup&&t.target.closest?.(".roomlabel, .rlhandle, .dev, .oplock, .op-hit, button"))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._baseVb());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._markup&&1===this._pointers.size&&this._markupMove(t),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=Be(this._baseVb(),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)<n||Math.abs(t)<1.5*Math.abs(e))return null;const r=s.indexOf(o);if(r<0)return null;const a=s.length;return t<0?s[(r+1)%a]:s[(r-1+a)%a]}(i,s,this._zoom,this._model.map(t=>t.id),this._space);o&&(this._slideTo(o,i<0?"left":"right"),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),_s(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},gs(t),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._baseVb(),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)}get _noHover(){return fs._noHoverMq||fs._touchSeen}_notePointer(t){"touch"!==t.pointerType&&"pen"!==t.pointerType||(fs._touchSeen=!0,this._tip&&(this._tip=null))}_showTip(t,e,i,s,o){this._noHover||this._drag||(this._tip={x:t.clientX,y:t.clientY,title:e,meta:i,lqi:s,temp:o})}get _gridPitch(){return us/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(){return this._curSpaceCfg,us}get _segments(){const t=this._curSpaceCfg,e=this._spaceH;return xe(t?.rooms||[]).map(t=>[t[0]*us,t[1]*e,t[2]*us,t[3]*e])}_savedNav(){try{return JSON.parse(localStorage.getItem(ds)||"null")}catch{return null}}_saveNav(){try{localStorage.setItem(ds,JSON.stringify({space:this._space,mode:this._mode}))}catch{}}_setMode(t){if(this._kiosk&&"view"!==t)return;if(this._mode===t)return;if(("plan"===t||"decor"===t)&&!this._norm)return void this._showToast(this._t("toast.markup_needs_server"));const e=!this._spaceModel().bg&&"view"===t!=("view"===this._mode);this._mode=t,e&&(this._zoom=1,this._view=null),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()}_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}get _cfgWriting(){return this._writesPending>0}_writeConfig(){this._writesPending++,this._writeChain=this._writeChain.catch(()=>{}).then(async()=>{if(!this._serverCfg)return;this._dropLegacySegments();const t=await this.hass.callWS({type:"houseplan/config/set",config:this._serverCfg,expected_rev:this._cfgRev});this._cfgRev=t?.rev??this._cfgRev+1});return this._writeChain.finally(()=>{this._writesPending--})}_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<t.length;i++)for(let s=0;s<e.length;s++)if(Ee(t[i],t[(i+1)%t.length],e[s],e[(s+1)%e.length]))return!0;return!Oe(t,e,i)&&!Oe(e,t,i)&&(Ne(t,e,i)||Ne(e,t,i))}(t,i)})}_pointInRoom(t,e){return e.poly?Me(t,e.poly):null!=e.x&&t[0]>=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._suppressClick)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*us,ry:t.y*e,rlen:t.length*us}))}_cmToUnits(t){return t/this._cellCm*this._gridPitch}get _decorList(){const t=this._curSpaceCfg;return Array.isArray(t?.decor)?t.decor:[]}get _decorH(){return us}_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},gs(t),!0}if("text"===e){const e=this._snap(this._svgPoint(t));return this._decorTextDialog={x:e[0]/us,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])<e)return;const i=us,s=this._decorH,o=this._decorStyle,n="dc"+Date.now().toString(36)+Math.random().toString(36).slice(2,5);let r;if("line"===t.kind)r={id:n,kind:"line",x1:t.a[0]/i,y1:t.a[1]/s,x2:t.b[0]/i,y2:t.b[1]/s,color:o.color,width:o.width};else{const e=Math.min(t.a[0],t.b[0])/i,a=Math.min(t.a[1],t.b[1])/s,l=Math.abs(t.b[0]-t.a[0])/i,c=Math.abs(t.b[1]-t.a[1])/s;r={id:n,kind:t.kind,x:e,y:a,w:l,h:c,color:o.color,width:o.width,fill:o.fill}}this._curSpaceCfg.decor=[...this._decorList,r],this._decorSel=n,this._saveConfig(),this.requestUpdate()}_decorShapeDown(t,e){if("decor"===this._mode){if(t.stopPropagation(),t.preventDefault(),"erase"===this._decorTool){return this._curSpaceCfg.decor=this._decorList.filter(t=>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},gs(t))}}_decorMoveUpdate(t){const e=this._decorMove,i=this._svgPoint(t),s=this._gridPitch;let o=ve(i[0]-e.start[0],s)/us,n=ve(i[1]-e.start[1],s)/this._decorH;const r=e.orig,a="line"===r.kind?Math.min(r.x1,r.x2):r.x,l="line"===r.kind?Math.min(r.y1,r.y2):r.y,c="line"===r.kind?Math.abs(r.x2-r.x1):r.w||0,h="line"===r.kind?Math.abs(r.y2-r.y1):r.h||0,d=.25;o=Math.max(-a-d,Math.min(1.25-a-c,o)),n=Math.max(-l-d,Math.min(1.25-l-h,n)),(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=us,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`<line class="${n}" x1="${o.x1*t}" y1="${o.y1*e}" x2="${o.x2*t}" y2="${o.y2*e}"
|
||
stroke="${o.color}" stroke-width="${o.width}" @pointerdown=${r}></line>`:"rect"===o.kind?j`<rect class="${n}" x="${o.x*t}" y="${o.y*e}" width="${o.w*t}" height="${o.h*e}"
|
||
stroke="${o.color}" stroke-width="${o.width}"
|
||
fill="${o.fill?o.color:"none"}" fill-opacity="${o.fill?.25:0}" @pointerdown=${r}></rect>`:"ellipse"===o.kind?j`<ellipse class="${n}" cx="${(o.x+o.w/2)*t}" cy="${(o.y+o.h/2)*e}"
|
||
rx="${o.w/2*t}" ry="${o.h/2*e}" stroke="${o.color}" stroke-width="${o.width}"
|
||
fill="${o.fill?o.color:"none"}" fill-opacity="${o.fill?.25:0}" @pointerdown=${r}></ellipse>`:"text"===o.kind?j`<text class="${n} dtext" x="${o.x*t}" y="${o.y*e}" fill="${o.color}"
|
||
font-size="${i[o.size]||i.m}" @pointerdown=${r} @dblclick=${()=>this._decorShapeDbl(o)}>${o.text}</text>`:G});let n=G;const r=this._decorDraft;if(r){const t=this._decorStyle;if("line"===r.kind)n=j`<line class="ddraft" x1="${r.a[0]}" y1="${r.a[1]}" x2="${r.b[0]}" y2="${r.b[1]}"
|
||
stroke="${t.color}" stroke-width="${t.width}"></line>`;else{const e=Math.min(r.a[0],r.b[0]),i=Math.min(r.a[1],r.b[1]),s=Math.abs(r.b[0]-r.a[0]),o=Math.abs(r.b[1]-r.a[1]);n="rect"===r.kind?j`<rect class="ddraft" x="${e}" y="${i}" width="${s}" height="${o}" stroke="${t.color}"
|
||
stroke-width="${t.width}" fill="${t.fill?t.color:"none"}" fill-opacity="${t.fill?.15:0}"></rect>`:j`<ellipse class="ddraft" cx="${e+s/2}" cy="${i+o/2}" rx="${s/2}" ry="${o/2}"
|
||
stroke="${t.color}" stroke-width="${t.width}" fill="${t.fill?t.color:"none"}" fill-opacity="${t.fill?.15:0}"></ellipse>`}}return j`<g class="decorlayer">${o}${n}</g>`}_renderDecorBar(){return B`<div class="editbar decorbar">
|
||
<ha-icon icon="mdi:draw" class="warn"></ha-icon>
|
||
${[["select","mdi:cursor-default-outline","decor.select"],["line","mdi:vector-line","decor.line"],["rect","mdi:rectangle-outline","decor.rect"],["ellipse","mdi:ellipse-outline","decor.ellipse"],["text","mdi:format-text","decor.text"],["erase","mdi:eraser","decor.erase"]].map(([t,e,i])=>B`<button class="btn ${this._decorTool===t?"on":""}"
|
||
@click=${()=>{this._decorTool=t,this._decorDraft=null}}
|
||
title=${this._t(i)}>
|
||
<ha-icon icon=${e}></ha-icon><span class="ml">${this._t(i)}</span>
|
||
</button>`)}
|
||
<input type="color" class="dcolor" .value=${this._decorStyle.color}
|
||
title=${this._t("decor.color")}
|
||
@input=${t=>this._decorStyle={...this._decorStyle,color:t.target.value}} />
|
||
<select class="dwidth" title=${this._t("decor.width")}
|
||
@change=${t=>this._decorStyle={...this._decorStyle,width:Number(t.target.value)}}>
|
||
${[[1.5,"decor.w_thin"],[3,"decor.w_mid"],[6,"decor.w_thick"]].map(([t,e])=>B`<option value=${t} ?selected=${this._decorStyle.width===t}>${this._t(e)}</option>`)}
|
||
</select>
|
||
<label class="dfill"><input type="checkbox" .checked=${this._decorStyle.fill}
|
||
@change=${t=>this._decorStyle={...this._decorStyle,fill:t.target.checked}} />
|
||
${this._t("decor.fill")}</label>
|
||
<span class="spacer"></span>
|
||
<button class="btn barclose" title=${this._t("title.close_editor")}
|
||
@click=${()=>this._setMode("view")}>
|
||
<ha-icon icon="mdi:close"></ha-icon>
|
||
</button>
|
||
</div>`}_renderDecorTextDialog(){const t=this._decorTextDialog;return B`<div class="menuwrap dialogwrap" @click=${()=>this._decorTextDialog=null}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:format-text"></ha-icon>${this._t("decor.text_title")}</div>
|
||
<div class="body">
|
||
<label>${this._t("decor.text_label")}</label>
|
||
<input class="namein" .value=${t.text} autofocus
|
||
@input=${e=>this._decorTextDialog={...t,text:e.target.value}}
|
||
@keydown=${t=>{"Enter"===t.key&&this._decorSaveText()}} />
|
||
<label>${this._t("decor.text_size")}</label>
|
||
<div class="radiorow">
|
||
${["s","m","l"].map(e=>B`<label class="srcrow inline">
|
||
<input type="radio" name="dtsize" .checked=${t.size===e}
|
||
@change=${()=>this._decorTextDialog={...t,size:e}} />
|
||
<span>${this._t("decor.size_"+e)}</span>
|
||
</label>`)}
|
||
</div>
|
||
<label>${this._t("decor.color")}</label>
|
||
<input type="color" .value=${t.color}
|
||
@input=${e=>this._decorTextDialog={...t,color:e.target.value}} />
|
||
</div>
|
||
<div class="row">
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._decorTextDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn primary" ?disabled=${!t.text.trim()} @click=${()=>this._decorSaveText()}>${this._t("btn.save")}</button>
|
||
</div>
|
||
</div>
|
||
</div>`}get _openWallHover(){if(!this._markup||"openwall"!==this._tool||!this._cursorPt)return null;const t=this._openWallHit(this._cursorPt);return t?{segs:t.segs,open:t.open}:null}_renderOpenWalls(t){const e=this._openPairs(),i=this._openWallHover;if(!e.length&&!i)return j``;const s=this._markup&&"openwall"===this._tool;return j`<g class="openwalls ${s?"hot":""}" style="--ow-stroke:${t?.color||"var(--hp-muted)"}">
|
||
${e.flatMap(t=>t.segs.map(t=>j`<line class="openwall"
|
||
x1="${t[0]}" y1="${t[1]}" x2="${t[2]}" y2="${t[3]}"></line>`))}
|
||
${i?i.segs.map(t=>j`<line class="openwall-preview ${i.open?"willclose":""}"
|
||
x1="${t[0]}" y1="${t[1]}" x2="${t[2]}" y2="${t[3]}"></line>`):G}
|
||
</g>`}_openPairs(){const t=this._spaceModel();if(this._openPairsCache&&this._openPairsCache.model===t)return this._openPairsCache.pairs;const e=this._computeOpenPairs();return this._openPairsCache={model:t,pairs:e},e}_computeOpenPairs(){const t=this._spaceModel().rooms.filter(t=>t.id),e=[];for(let i=0;i<t.length;i++)for(let s=i+1;s<t.length;s++){const o=t[i],n=t[s];if(!((o.open_to||[]).includes(n.id)||(n.open_to||[]).includes(o.id)))continue;const r=we(o),a=we(n);if(!r||!a)continue;const l=pi(r,a,.02*this._gridPitch);l.length&&e.push({a:o,b:n,segs:l})}return e}_openWallHit(t){const e=this._spaceModel().rooms.filter(t=>t.id),i=6*this._gridPitch;let s=null;for(let o=0;o<e.length;o++)for(let n=o+1;n<e.length;n++){const r=we(e[o]),a=we(e[n]);if(!r||!a)continue;const l=pi(r,a,.02*this._gridPitch);for(const r of l){const a=wi(t,r);a<=i&&(!s||a<s.d)&&(s={a:e[o],b:e[n],segs:l,d:a})}}if(!s)return null;const o=(s.a.open_to||[]).includes(s.b.id)||(s.b.open_to||[]).includes(s.a.id);return{a:s.a,b:s.b,segs:s.segs,open:o}}_openWallClick(t){const e=this._openWallHit(t);if(!e)return void this._showToast(this._t("toast.openwall_pick"));const i=this._curSpaceCfg,s=i.rooms.find(t=>t.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=ke(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{gs(t)}catch{}this._opDrag={id:e.id,moved:!1,sx:t.clientX,sy:t.clientY,dirty:!1}}}_opPointerMove(t,e){if(!this._opDrag||this._opDrag.id!==e.id)return;if(Math.abs(t.clientX-this._opDrag.sx)+Math.abs(t.clientY-this._opDrag.sy)<=3)return;const i=ke(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);if(!o)return;const n=i.x/us,r=i.y/this._spaceH;o.x===n&&o.y===r&&o.angle===i.angle||(this._opDrag.dirty=!0),o.x=n,o.y=r,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._opDrag.dirty&&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/us,y:t.y/i,angle:t.angle,length:this._cmToUnits(Math.max(20,t.lengthCm))/us,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?qe(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]/us,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=Fe(t,s,i),r=Fe(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<e.length-1;i++)for(let s=0;s<t.length;s++)if(Ee(e[i],e[i+1],t[s],t[(s+1)%t.length]))return null;for(let t=0;t<e.length-1;t++)for(let i=t+2;i<e.length-1;i++)if(Ee(e[t],e[t+1],e[i],e[i+1]))return null;if(2===e.length&&!ze([(s[0]+o[0])/2,(s[1]+o[1])/2],t,i))return null;const l=(e,s,o,n)=>{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),Ue(r,i)};let c,h;if(n===r){const r=Ue([...e],i);if(r.length<3||Ie(r)<=i)return null;const a=[];for(let i=0;i<t.length;i++)if(a.push(t[i]),i===n){const i=(t[(n+1)%t.length][0]-t[n][0])*(o[0]-s[0])+(t[(n+1)%t.length][1]-t[n][1])*(o[1]-s[1])>=0?e:[...e].reverse();for(const t of i)a.push(t)}c=Ue(a,i),h=r}else c=Ue([...l(s,n,o,r),...[...a].reverse()],i),h=Ue([...l(o,r,s,n),...a],i);return c.length<3||h.length<3||Ie(c)<=i||Ie(h)<=i||Math.abs(Ie(c)+Ie(h)-Ie(t))>Math.max(i,1e-6*Ie(t))?null:[c,h]}(s,[...c,a],o);if(!h)return void this._showToast(this._t("toast.split_bad_cut"));this._resetRoomDialogFields();const[d,p]=h,u=Ie(d)>=Ie(p)?d:p,_=u===d?p:d;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=ke(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]/us,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]/us,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=us,e={...this._layout};for(const i of this._devices){if(i.area!==o||i.space!==this._space)continue;if(n++,this._layout[i.id])continue;const s=this._defPos[i.id];s&&(e[i.id]={s:this._space,x:s.x/us,y:s.y/t},this._dirtyPos.add(i.id))}this._layout=e,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:"",isLight:!0===t.marker?.is_light,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:"",hideFromPlan:!0===t.marker?.hidden,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:"",isLight:!1,glowRadius:"",model:"",link:"",description:"",pdfs:[],room:"",hideFromPlan:!1,busy:!1,uploadId:"up_"+Date.now().toString(36)+Math.random().toString(36).slice(2,6)}: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.uploadId||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,is_light:!!t.isLight||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,hidden:!!t.hideFromPlan};("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,d=!!t.room&&null!=c&&(c.space!==o||c.area!==n||h!==r);let p=!1;const u=t.uploadId||a;if(u&&u!==i&&l.pdfs?.length)try{const t=await this.hass.callWS({type:"houseplan/files/migrate",from_id:u,to_id:i}),e=t?.mapping||{};l.pdfs=function(t,e,i,s){if(!e||!i||e===i)return t;const o="/files/"+e+"/",n="/files/"+i+"/";return t.map(t=>{if(!t.url.includes(o))return t;const e=t.url.split(o)[1]||"",[i,r]=[e.split("?")[0],e.includes("?")?"?"+e.split("?")[1]:""];if(s){const e=s[decodeURIComponent(i)]??s[i];return e?{...t,url:t.url.split(o+i)[0]+n+encodeURIComponent(e)+r}:t}return{...t,url:t.url.split(o).join(n)}})}(l.pdfs,u,i,e),p=Object.keys(e).length>0}catch(t){this._showToast(this._t("toast.files_migrate_failed",{err:this._errText(t)}))}e.markers=e.markers.filter(t=>t.id!==i&&t.id!==a),e.markers.push(l);let _=null;const m=o||c?.space||this._space,g=a?this._layout[a]:null,f=g?{s:g.s||c?.space||this._space,x:g.x,y:g.y}:a&&c&&this._defPos[a]?this._normPos(c.space,this._defPos[a].x,this._defPos[a].y):null;if(f&&f.s===m)i===a&&this._layout[i]&&!d||(_={s:f.s,x:f.x,y:f.y},this._layout={...this._layout,[i]:_});else if(!this._layout[i]||d){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)),_=this._normPos(o||this._space,e,s),this._layout={...this._layout,[i]:_}}await this._saveConfigNow(),_&&this._noteLayoutRev(await this.hass.callWS({type:"houseplan/layout/update",device_id:i,pos:_})),a&&a!==i&&(delete this._layout[a],await this.hass.callWS({type:"houseplan/layout/delete",device_id:a}).then(t=>this._noteLayoutRev(t)).catch(()=>{})),p&&u&&await this.hass.callWS({type:"houseplan/files/cleanup",marker_id:u}).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}).then(t=>this._noteLayoutRev(t)).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){return{s:t,x:e/us,y:i/us}}_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=ti(i);this._spaceDialog={mode:t,spaceId:e,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",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",showBorders:!1,showNames:!1,roomColor:Ye,roomOpacity:Qe,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<o.length;t+=32768)n+=String.fromCharCode(...o.subarray(t,t+32768));const r=btoa(n),a=URL.createObjectURL(i),l=await new Promise(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}}}_useServerPlan(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let e=0;e<40;e++){const e=this._display(t);if(e){const i=await new Promise(t=>{const i=new Image;i.onload=()=>t(i.naturalWidth&&i.naturalHeight?i.naturalWidth/i.naturalHeight:0),i.onerror=()=>t(0),i.src=e}),s=this._spaceDialog;return s&&s.planUrl===t&&Number.isFinite(i)&&i>0?(this._spaceDialog={...s,savedAspect:i},i):0}if(await new Promise(t=>setTimeout(t,150)),this._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this._t("confirm.delete_plan",{name:t})))try{await this.hass.callWS({type:"houseplan/plans/delete",name:t});const e=this._spaceDialog;e?.saved&&(this._spaceDialog={...e,saved:e.saved.filter(e=>e.name!==t)})}catch(t){this._showToast(this._t("toast.plan_delete_failed",{err:this._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return B`<div class="savedplans muted">${this._t("space.loading")}</div>`;const e=t.saved||[];if(!e.length)return B`<div class="savedplans muted">${this._t("space.no_saved")}</div>`;return B`<div class="savedplans">
|
||
${e.map(e=>B`
|
||
<div class="savedplan ${e.url===t.planUrl?"cur":""}">
|
||
<img src=${this._display(e.url)} alt="" loading="lazy" decoding="async" />
|
||
<div class="savedmeta">
|
||
<b>${e.name}</b>
|
||
<span class="muted">${(t=>t>=1048576?(t/1048576).toFixed(1)+" MB":Math.round(t/1024)+" KB")(e.size)}${e.used_by.length?" · "+this._t("space.used_by",{list:e.used_by.join(", ")}):""}</span>
|
||
</div>
|
||
<button class="btn ghost" @click=${()=>this._useServerPlan(e.url)}
|
||
?disabled=${e.url===t.planUrl}>${this._t("btn.use")}</button>
|
||
<button class="btn ghost danger"
|
||
title=${e.used_by.length||e.url===t.planUrl?this._t("space.in_use"):this._t("btn.delete")}
|
||
?disabled=${e.used_by.length>0||e.url===t.planUrl}
|
||
@click=${()=>this._deleteServerPlan(e.name)}>
|
||
<ha-icon icon="mdi:trash-can-outline"></ha-icon>
|
||
</button>
|
||
</div>`)}
|
||
</div>`}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="create"===t.mode?"s"+Date.now().toString(36):t.spaceId;let s=null;if("file"===t.source&&t.planFile){s={url:(await this.hass.callWS({type:"houseplan/plan/set",space_id:i,ext:t.planFile.ext,data:t.planFile.b64})).url,aspect:t.planFile.aspect}}let o=t.savedAspect||null;!s&&"file"===t.source&&t.planUrl&&!o&&this._aspectJob&&(o=await this._aspectJob||null);const n=this._serverCfg;let r;if("create"===t.mode)r={id:i,title:t.title.trim(),plan_url:null,view_box:[0,0,1,1],rooms:[]},n.spaces.push(r);else{if(r=n.spaces.find(t=>t.id===i),!r)throw new Error("space "+i+" is gone from the config");r.title=t.title.trim()}s?(r.plan_url=s.url,r.plan_aspect=s.aspect):"file"===t.source&&t.planUrl&&t.planUrl!==r.plan_url&&(r.plan_url=t.planUrl,r.plan_aspect=o),"draw"===t.source&&(r.plan_url=null,r.plan_aspect=null);const a="draw"===t.source;r.settings={...r.settings||{},show_borders:!(!a||"create"!==t.mode)||t.showBorders,show_names:!(!a||"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},r.cell_cm=Number.isFinite(t.cellCm)&&t.cellCm>0?t.cellCm:5,await this._saveConfigNow(),this._spaceDialog=null,"create"===t.mode&&(this._space=r.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._cfgEpoch++;try{await this._writeConfig()}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",showBorders:!1,showNames:!1,roomColor:Ye,roomOpacity:Qe,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`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:home-floor-1"></ha-icon>${this._t("import.title")}</div>
|
||
<div class="body">
|
||
<div class="rhint">${this._t("import.hint")}</div>
|
||
${t.floors.map((e,i)=>B`<label class="floorrow">
|
||
<input type="checkbox" .checked=${e.checked}
|
||
@change=${s=>{const o=[...t.floors];o[i]={...e,checked:s.target.checked},this._importDialog={floors:o}}} />
|
||
<span>${e.name}</span>
|
||
${null!=e.level?B`<span class="floorlvl">L${e.level}</span>`:G}
|
||
</label>`)}
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn ghost" @click=${()=>{this._importDialog=null,this._openSpaceDialog("create")}}>
|
||
${this._t("import.manual")}
|
||
</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn on" @click=${()=>this._startImport()} ?disabled=${!e}>
|
||
<ha-icon icon="mdi:import"></ha-icon>${this._t("import.start",{n:e})}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_setFillColor(t,e){const i=this._settingsDialog;this._settingsDialog={...i,colors:{...i.colors,[t]:{...i.colors[t],...e}}}}async _saveSettingsDialog(){const t=this._settingsDialog;if(t&&!t.busy){this._settingsDialog={...t,busy:!0};try{const e=this._serverCfg,i=JSON.stringify(t.colors)===JSON.stringify(ei),s={...e.settings};i?delete s.fill_colors:s.fill_colors=t.colors;const o=this._imperial?30.48*t.glowRadius:100*t.glowRadius;Number.isFinite(o)&&o>0&&300!==Math.round(o)?s.glow_radius_cm=Math.round(o):delete s.glow_radius_cm,this._serverCfg={...e,settings:s},await this._saveConfigNow(),this._settingsDialog=null,this.requestUpdate(),this._showToast(this._t("gs.saved"))}catch(t){this._settingsDialog&&(this._settingsDialog={...this._settingsDialog,busy:!1}),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}_renderColorRow(t,e){const i=this._settingsDialog.colors[t];return B`<div class="colorrow gsrow">
|
||
<span class="gsl">${this._t(e)}</span>
|
||
<input type="color" .value=${i.c}
|
||
@input=${e=>this._setFillColor(t,{c:e.target.value})} />
|
||
<input type="range" min="0" max="100" .value=${String(Math.round(100*i.a))}
|
||
@input=${e=>this._setFillColor(t,{a:Number(e.target.value)/100})} />
|
||
<span class="opv">${Math.round(100*i.a)}%</span>
|
||
</div>`}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;if(a.hidden)continue;const l=Ci(this.hass,a);if(!l)continue;const c=li(this.hass.states[l],e.glow_light.c);if(!c)continue;const h=Number(a.marker?.glow_radius_cm),d=Number.isFinite(h)&&h>0?h/this._cellCm*this._gridPitch:i,p=this._pos(a),u=[...o].reverse().find(t=>this._pointInRoom([p.x,p.y],t.r));let _=null;if(u){const e=u.r.id?ui(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=Ce([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(!hi([t.rx,t.ry],t.angle,[p.x,p.y],l,.6*s))continue;const c=ci([p.x,p.y],[t.rx-o,t.ry-n],[t.rx+o,t.ry+n],d);c&&a.push("M "+c.map(t=>t[0]+" "+t[1]).join(" L ")+" Z")}_=a}r.push({pos:p,c:c.c,alpha:e.glow_light.a*c.bri,clip:_,r:d})}return r.length?j`<defs>
|
||
${r.map((t,e)=>j`
|
||
<radialGradient id="hp-glow-${e}">
|
||
<stop offset="0%" stop-color="${t.c}" stop-opacity="${t.alpha.toFixed(3)}"></stop>
|
||
<stop offset="70%" stop-color="${t.c}" stop-opacity="${t.alpha.toFixed(3)}"></stop>
|
||
<stop offset="100%" stop-color="${t.c}" stop-opacity="0"></stop>
|
||
</radialGradient>
|
||
${t.clip?j`<clipPath id="hp-glowclip-${e}">${t.clip.map(t=>j`<path d="${t}"></path>`)}</clipPath>`:G}`)}
|
||
</defs>
|
||
<g class="glowlayer">
|
||
${r.map((t,e)=>j`<circle cx="${t.pos.x}" cy="${t.pos.y}" r="${t.r}"
|
||
fill="url(#hp-glow-${e})" ${""}
|
||
clip-path=${t.clip?`url(#hp-glowclip-${e})`:G}></circle>`)}
|
||
</g>`:j``}_renderSettingsDialog(){return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog wide" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:cog-outline"></ha-icon>${this._t("gs.title")}</div>
|
||
<div class="body">
|
||
<div class="rhint">${this._t("gs.hint")}</div>
|
||
<label class="dispsection">${this._t("gs.light_group")}</label>
|
||
${this._renderColorRow("light_on","gs.light_on")}
|
||
${this._renderColorRow("light_off","gs.light_off")}
|
||
${this._renderColorRow("light_none","gs.light_none")}
|
||
<label class="dispsection">${this._t("gs.temp_group")}</label>
|
||
${this._renderColorRow("temp_cold","gs.temp_cold")}
|
||
${this._renderColorRow("temp_ok","gs.temp_ok")}
|
||
${this._renderColorRow("temp_hot","gs.temp_hot")}
|
||
<label class="dispsection">${this._t("gs.lqi_group")}</label>
|
||
${this._renderColorRow("lqi_low","gs.lqi_low")}
|
||
${this._renderColorRow("lqi_high","gs.lqi_high")}
|
||
<label class="dispsection">${this._t("gs.glow_group")}</label>
|
||
${this._renderColorRow("glow_base","gs.glow_base")}
|
||
${this._renderColorRow("glow_light","gs.glow_light")}
|
||
<div class="colorrow gsrow">
|
||
<span class="gsl">${this._t("gs.glow_radius")}</span>
|
||
<input type="number" class="tempin" min="0.5" step="0.5"
|
||
.value=${String(this._settingsDialog.glowRadius)}
|
||
@input=${t=>{const e=parseFloat(t.target.value);Number.isFinite(e)&&e>0&&(this._settingsDialog={...this._settingsDialog,glowRadius:e})}} />
|
||
<span class="opl">${this._imperial?this._t("gs.unit_ft"):this._t("gs.unit_m")}</span>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn ghost" @click=${()=>this._settingsDialog={...this._settingsDialog,colors:JSON.parse(JSON.stringify(ei)),glowRadius:this._imperial?9.8:3}}>
|
||
${this._t("gs.reset")}
|
||
</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._settingsDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._saveSettingsDialog} ?disabled=${this._settingsDialog.busy}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${this._settingsDialog.busy?"…":this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_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(dt),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=pt(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`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog wide" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:shape-plus-outline"></ha-icon>${this._t("rules.title")}</div>
|
||
<div class="body">
|
||
<div class="rhint">${this._t("rules.hint")}</div>
|
||
<div class="rtest">
|
||
<input class="namein" type="text" placeholder=${this._t("rules.test_ph")}
|
||
.value=${t.test}
|
||
@input=${e=>this._rulesDialog={...t,test:e.target.value}} />
|
||
${i?B`<ha-icon icon=${i}></ha-icon><span class="rtesticon">${i}</span>`:G}
|
||
</div>
|
||
${t.rules.map((e,i)=>{const o=""!==e.pattern.trim()&&!function(t){try{return new RegExp(t,"i"),!0}catch{return!1}}(e.pattern);return B`<div class="rrow">
|
||
<input class="namein rpat ${o?"bad":""}" type="text"
|
||
placeholder=${this._t("rules.pattern_ph")}
|
||
title=${o?this._t("rules.invalid"):""}
|
||
.value=${e.pattern}
|
||
@input=${s=>{const o=[...t.rules];o[i]={...e,pattern:s.target.value},this._rulesSet(o)}} />
|
||
<input class="namein ricon" type="text" placeholder=${this._t("rules.icon_ph")}
|
||
.value=${e.icon}
|
||
@input=${s=>{const o=[...t.rules];o[i]={...e,icon:s.target.value},this._rulesSet(o)}} />
|
||
<ha-icon class="rprev" icon=${e.icon||"mdi:chip"}></ha-icon>
|
||
<ha-icon class="ract" icon="mdi:arrow-up" title=${this._t("btn.up")}
|
||
@click=${()=>s(i,-1)}></ha-icon>
|
||
<ha-icon class="ract" icon="mdi:arrow-down" title=${this._t("btn.down")}
|
||
@click=${()=>s(i,1)}></ha-icon>
|
||
<ha-icon class="ract del" icon="mdi:close" title=${this._t("btn.delete")}
|
||
@click=${()=>this._rulesSet(t.rules.filter((t,e)=>e!==i))}></ha-icon>
|
||
</div>`})}
|
||
<button class="btn ghost" @click=${()=>this._rulesSet([...t.rules,{pattern:"",icon:""}])}>
|
||
<ha-icon icon="mdi:plus"></ha-icon>${this._t("rules.add")}
|
||
</button>
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn ghost" @click=${()=>this._rulesSet(dt.map(t=>({...t})))}>
|
||
${this._t("rules.reset")}
|
||
</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._rulesDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._saveRules} ?disabled=${t.busy}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${t.busy?"…":this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_saveKioskScale(t){this._kioskScale={...this._kioskScale,...t};try{localStorage.setItem(ps,JSON.stringify(this._kioskScale))}catch{}this.requestUpdate()}_renderKioskDialog(){const t=this._kioskScale,e=(e,i)=>B`<label>${i}</label>
|
||
<div class="colorrow">
|
||
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(100*t[e]))}
|
||
@input=${t=>this._saveKioskScale({[e]:Number(t.target.value)/100})} />
|
||
<span class="opv">${Math.round(100*t[e])}%</span>
|
||
</div>`;return B`<div class="menuwrap dialogwrap" @click=${()=>this._kioskDialog=!1}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:tablet"></ha-icon>${this._t("kiosk.title")}</div>
|
||
<div class="body">
|
||
<div class="rhint">${this._t("kiosk.hint")}</div>
|
||
${e("icon",this._t("kiosk.icon_scale"))}
|
||
${e("font",this._t("kiosk.font_scale"))}
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn ghost" @click=${()=>this._saveKioskScale({icon:1,font:1})}>${this._t("gs.reset")}</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn on" @click=${()=>this._kioskDialog=!1}>${this._t("btn.close")}</button>
|
||
</div>
|
||
</div>
|
||
</div>`}render(){if(!this._config||!this.hass)return G;const t=this._model;if(!t.length)return B`<ha-card>
|
||
<div class="head">
|
||
<div class="title"><ha-icon icon="mdi:home-city"></ha-icon>${this._config.title||this._t("card.title")}</div>
|
||
</div>
|
||
<div class="empty">
|
||
<ha-icon icon="mdi:floor-plan" class="big"></ha-icon>
|
||
<p>${this._t("empty.no_spaces")}</p>
|
||
${this._serverStorage?B`<p class="muted">${this._t("empty.add_first")}</p>
|
||
<button class="btn on" @click=${()=>this._openSpaceDialog("create")}>
|
||
<ha-icon icon="mdi:plus"></ha-icon>${this._t("btn.add_space")}
|
||
</button>`:B`<p class="muted">${this._t("empty.install")}</p>`}
|
||
</div>
|
||
${this._spaceDialog?this._renderSpaceDialog():G}
|
||
${this._importDialog?this._renderImportDialog():G}
|
||
${this._toast?B`<div class="toast">${this._toast}</div>`:G}
|
||
</ha-card>`;const e=this._spaceModel(),i=e.vb,s="devices"===this._mode&&this._showAll,o=this._devices.filter(t=>t.space===e.id&&(!t.hidden||s)),n=ti(this._curSpaceCfg),r=n.showLqi??this._config.show_signal??!0,a=this._config.icon_size??2.5,l=a>8?2.5:a,c=this._viewOr(i);return B`
|
||
<ha-card>
|
||
<div class="hdr ${this._kiosk?"kioskhide":""}">
|
||
<div class="head">
|
||
<div class="title">
|
||
<ha-icon icon="mdi:home-city"></ha-icon>
|
||
${this._config.title||this._t("card.title")}
|
||
</div>
|
||
<div class="tabs">
|
||
${t.map(t=>B`<button
|
||
class="tab ${this._space===t.id?"active":""}"
|
||
@click=${()=>{this._space=t.id,this._selId=null,this._navApplied=!0,this._restoreZoom(),this._saveNav()}}
|
||
>
|
||
${t.title}${this._norm&&this._canEdit?B`<ha-icon class="tabedit" icon="mdi:cog-outline"
|
||
title=${this._t("title.configure_space")}
|
||
@click=${e=>{e.stopPropagation(),this._openSpaceDialog("edit",t.id)}}></ha-icon>`:G}
|
||
</button>`)}
|
||
${this._norm&&"plan"===this._mode?B`<button class="tab tabadd" title=${this._t("title.add_space")}
|
||
@click=${()=>this._openSpaceDialog("create")}>
|
||
<ha-icon icon="mdi:plus"></ha-icon>
|
||
</button>`:G}
|
||
</div>
|
||
${this._canEdit?B`<div class="modes">
|
||
${[["plan","mdi:floor-plan"],["devices","mdi:tune-variant"],["decor","mdi:draw"]].map(([t,e])=>B`<button class="modetab ${this._mode===t?"active":""}"
|
||
title=${this._t("mode."+t+"_tip")}
|
||
@click=${()=>{this._mode!==t&&this._setMode(t)}}>
|
||
<ha-icon icon=${e}></ha-icon><span class="ml">${this._t("mode."+t)}</span>
|
||
${this._mode===t?B`<ha-icon class="closex" icon="mdi:close" title=${this._t("title.close_editor")}
|
||
@click=${t=>{t.stopPropagation(),this._setMode("view")}}></ha-icon>`:G}
|
||
</button>`)}
|
||
</div>`:G}
|
||
<span class="count">${this._t("count.devices",{n:o.filter(t=>!t.hidden).length})}</span>
|
||
<span class="spacer"></span>
|
||
<div class="zoomctl">
|
||
<button class="btn zb" @click=${()=>this._stepZoom(-1)} title=${this._t("title.zoom_out")}><ha-icon icon="mdi:minus"></ha-icon></button>
|
||
<button class="btn zb" @click=${()=>this._resetZoom()} ?disabled=${1===this._zoom}
|
||
title=${this._t("title.zoom_reset")}><ha-icon icon="mdi:fit-to-page-outline"></ha-icon></button>
|
||
<button class="btn zb" @click=${()=>this._stepZoom(1)} title=${this._t("title.zoom_in")}><ha-icon icon="mdi:plus"></ha-icon></button>
|
||
</div>
|
||
${this._norm&&this._canEdit?B`<button class="btn" @click=${this._openSettingsDialog} title=${this._t("title.general_settings")}>
|
||
<ha-icon icon="mdi:cog-outline"></ha-icon>
|
||
</button>`:G}
|
||
</div>
|
||
${this._markup?this._renderMarkupBar():"devices"===this._mode?this._renderDevicesBar():"decor"===this._mode?this._renderDecorBar():G}
|
||
</div>
|
||
|
||
<div class="stage ${this._markup?"markup tool-"+this._tool+("split"!==this._tool||this._splitSel?"":" pickstage")+("openwall"===this._tool&&this._openWallHover?" wallhot":""):""} ${"decor"===this._mode?"dtool-"+this._decorTool:""} ${e.bg?"":"noplan"} mode-${this._mode}"
|
||
style="height:${this._kiosk?"100dvh":`calc(100dvh - ${this._hdrH}px)`}"
|
||
@click=${t=>this._markupClick(t)}
|
||
@wheel=${t=>this._onWheel(t)}
|
||
@pointerdown=${t=>{this._notePointer(t),this._stagePointerDown(t)}}
|
||
@pointermove=${t=>this._stagePointerMove(t)}
|
||
@pointerup=${t=>this._stagePointerUp(t)}
|
||
@pointercancel=${t=>this._stagePointerUp(t)}>
|
||
<div class="zoomwrap ${this._slide?"slide-"+this._slide:""}">
|
||
<svg viewBox="${c.x} ${c.y} ${c.w} ${c.h}" preserveAspectRatio="xMidYMid meet">
|
||
${this._editing?this._renderMarkupDefs(i):G}
|
||
${this._editing&&!this._markup?j`<rect x="${i[0]}" y="${i[1]}" width="${i[2]}" height="${i[3]}" fill="url(#hp-grid)" pointer-events="none"></rect>`:G}
|
||
${e.bg&&this._display(e.bg.href)?j`<image href="${this._display(e.bg.href)}" x="${e.bg.x}" y="${e.bg.y}" width="${e.bg.w}" height="${e.bg.h}" preserveAspectRatio="none" />`:G}
|
||
${this._renderDecorLayer()}
|
||
${(()=>{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||n.showBorders).map(i=>{let o="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||i.id!==this._mergeSel&&i.id!==this._splitSel?.roomId||(o+=" picked");let a="";const l=vi(n.fill,i);if(!this._markup&&(n.showBorders||"none"!==l)){o+=" styled";const t=[];t.push(`--room-stroke:${n.color}`,`--room-stroke-op:${n.showBorders?n.opacity:0}`);const e="glow"===l?this._fillColors.glow_base:"temp"===l?ni("temp",null,"none",this._roomTemp(i),n.tempMin,n.tempMax,this._fillColors):i.area?ni(l,"lqi"===l?this._roomLqi(i.area):null,"light"===l?Ii(this.hass,this._devices,i.area):"none",null,n.tempMin,n.tempMax,this._fillColors):null;e?(o+=" 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,"",r?this._roomLqi(i.area):null,this._roomTemp(i)),h=!e.bg&&!n.showNames&&!this._markup,d=this._roomCenter(i),p=this._markup&&(i.id===this._mergeSel||i.id===this._splitSel?.roomId),u=i.id&&!p?t.filter(t=>t.a.id===i.id||t.b.id===i.id).flatMap(t=>t.segs):[];u.length&&(o+=" noedge");const _=s(i),m=_?function(t,e,i=1e-6){const s=e.filter(e=>Oe(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Oe(e,t,i)))}(_,(g=i,e.rooms.filter(t=>t!==g).map(s).filter(Boolean))):[];var g;const f=m.length&&_?j`<path class="${o}" style="${a}" fill-rule="evenodd"
|
||
d="${[_,...m].map(t=>"M "+t.map(t=>t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}"
|
||
@mousemove=${c}
|
||
@mouseleave=${()=>this._tip=null}></path>`:i.poly?j`<polygon class="${o}" style="${a}" points="${i.poly.map(t=>t.join(",")).join(" ")}"
|
||
@mousemove=${c}
|
||
@mouseleave=${()=>this._tip=null}></polygon>`:j`<rect class="${o}" style="${a}"
|
||
x="${i.x}" y="${i.y}" width="${i.w}" height="${i.h}" rx="${.03*Math.min(i.w,i.h)}"
|
||
@mousemove=${c}
|
||
@mouseleave=${()=>this._tip=null}></rect>`,b=u.length&&_?function(t,e,i=1e-6){const s=[];for(let e=0;e<t.length;e++){const i=t[e],o=t[(e+1)%t.length];s.push([i[0],i[1],o[0],o[1]])}return _i(s,e,i)}(_,u,.02*this._gridPitch):null,v=b?j`<path class="room-outline ${this._markup?"outlined":""}"
|
||
d="${b.map(t=>`M ${t[0]} ${t[1]} L ${t[2]} ${t[3]}`).join(" ")}"
|
||
style=${this._markup?G:`stroke:${n.color};stroke-opacity:${n.showBorders?n.opacity:0}`}></path>`:G;return j`${f}${v}${h?j`<text class="rlabel" x="${d[0]}" y="${d[1]}">${i.name}</text>`:G}`})})()}
|
||
${"glow"!==n.fill||this._markup?G:this._renderGlowLayer(e)}
|
||
${this._renderOpenWalls(n)}
|
||
${this._editing?this._renderAlignGuides():G}
|
||
${this._markup?this._renderMarkupLayer(i):G}
|
||
${this._renderOpenings(n)}
|
||
</svg>
|
||
<div class="devlayer" style="--icon-size:${(l*i[2]*(this._kiosk?this._kioskScale.icon:1)/c.w).toFixed(3)}cqw;--rl-font:${this._kiosk?this._kioskScale.font:1}">
|
||
${o.map(t=>this._renderDevice(t,c,r))}
|
||
${this._renderOpeningLocks(c)}
|
||
${n.showNames||this._markup?e.rooms.map(t=>this._renderRoomLabel(t,e,c,n)):G}
|
||
${this._markup?e.rooms.map(t=>this._renderRoomGear(t,e,c)):G}
|
||
</div>
|
||
${this._measureAnchor?B`<div class="measurelayer">${this._renderMeasureLabel(c)}</div>`:G}
|
||
</div>
|
||
${this._zoom>1?B`<div class="zoombadge">${Math.round(100*this._zoom)}%</div>`:G}
|
||
</div>
|
||
|
||
${this._roomDialog?this._renderRoomDialog():G}
|
||
${this._mergeDialog?this._renderMergeDialog():G}
|
||
${this._openingDialog?this._renderOpeningDialog():G}
|
||
${this._openingInfo?this._renderOpeningInfoCard():G}
|
||
${this._decorTextDialog?this._renderDecorTextDialog():G}
|
||
${this._spaceDialog?this._renderSpaceDialog():G}
|
||
${this._markerDialog?this._renderMarkerDialog():G}
|
||
${this._infoCard?this._renderInfoCard():G}
|
||
${this._rulesDialog?this._renderRulesDialog():G}
|
||
${this._settingsDialog?this._renderSettingsDialog():G}
|
||
${this._importDialog?this._renderImportDialog():G}
|
||
${this._tip?B`<div class="tip" style="left:${this._tip.x+12}px;top:${this._tip.y+12}px">
|
||
<b>${this._tip.title}</b>${this._tip.meta?B`<span class="m">${this._tip.meta}</span>`:G}
|
||
${null!=this._tip.temp?B`<span class="m">${this._t("tip.temp_avg")} <b>${this._tip.temp}°</b></span>`:G}
|
||
${null!=this._tip.lqi?B`<span class="m">${this._t("tip.lqi")}
|
||
<b style="color:${be(this._tip.lqi)}">${this._tip.lqi}</b></span>`:G}
|
||
</div>`:G}
|
||
${this._kiosk&&this._kioskDots&&this._model.length>1?B`<div class="kioskdots">
|
||
${this._model.map(t=>B`<span class="kdot ${t.id===this._space?"on":""}"></span>`)}
|
||
</div>`:G}
|
||
${this._kioskDialog?this._renderKioskDialog():G}
|
||
${this._toast?B`<div class="toast">${this._toast}</div>`:G}
|
||
</ha-card>
|
||
`}_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=t.hidden?"":this._stateClass(t),a=t.hidden?null:this._liveTemp(t),l=t.hidden?null:this._liveHum(t),c=!i||t.virtual||t.hidden?null:Ti(this.hass,t.entities),h=t.marker,d=h?.display||"badge",p=("ripple"===d||"icon_ripple"===d)&&!t.hidden,u=t.primary?this.hass.states[t.primary]:void 0,_="value"!==d||t.hidden?null: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,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states&&!t.hidden?ri(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(di),b=this._config?.live_states&&!t.hidden?f.length?f.map(t=>ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`<div
|
||
class="dev ${r} ${this._selId===t.id?"sel":""} ${t.virtual?"virtual":""} ${t.hidden?"ghost":""} ${"ripple"!==d||t.hidden?"":"noicon"} ${null!=_?"valonly":""} ${b?"rgb":""} ${v?"alarm":""}"
|
||
style="${$.join(";")}"
|
||
@click=${e=>this._clickDevice(e,t)}
|
||
@contextmenu=${e=>this._ctxDevice(e,t)}
|
||
@mousemove=${e=>this._showTip(e,t.name,t.model+(null!=a?" · "+a+"°":"")+(null!=l?" · "+l+"%":"")+(null!=c?" · LQI "+c:""))}
|
||
@mouseleave=${()=>this._tip=null}
|
||
@pointerdown=${e=>this._pointerDown(e,t)}
|
||
@pointermove=${e=>this._pointerMove(e,t)}
|
||
@pointerup=${e=>this._pointerUp(e,t)}
|
||
@pointercancel=${e=>this._pointerUp(e,t)}
|
||
>
|
||
${p?B`<span class="ripple ${y?"active":""}"><i></i><i></i><i></i></span>`:G}
|
||
${this._newIds.has(t.id)?B`<span class="newdot" title=${this._t("device.new")}></span>`:G}
|
||
${null!=_?B`<span class="valtext">${_}</span>`:"ripple"!==d||t.hidden?B`<ha-icon icon="${g}" style=${x?`transform:rotate(${x}deg)`:G}></ha-icon>`:G}
|
||
${null!=a&&null==_?B`<span class="tval">${a}°</span>`:G}
|
||
${null!=l&&null==_?B`<span class="hval">${l}%</span>`:G}
|
||
${null!=c?B`<span class="lqi" style="color:${be(c)}">${c}</span>`:G}
|
||
</div>`}_roomTemp(t){const e=t.settings?.temp_source;return e?Li(this.hass,e,"temp"):t.area?this._climate().get(t.area)?.temp??null:null}_roomHum(t){const e=t.settings?.hum_source;return e?Li(this.hass,e,"hum"):t.area?this._climate().get(t.area)?.hum??null:null}_climate(){const t=this._climateCache;if(t&&t.h===this.hass&&t.r===this._iconRules)return t.m;const e=function(t,e){const i=new Map;if(!t?.entities)return i;const s=new Map;for(const[e,i]of Object.entries(t.entities)){const o=i.device_id?t.devices?.[i.device_id]:null,n=i.area_id||o?.area_id||null;if(!n)continue;if(i.entity_category)continue;if(ht.has(i.platform))continue;if(qi.test(e))continue;let r=s.get(n);r||(r=new Map,s.set(n,r));const a=i.device_id||e;let l=r.get(a);if(!l){const s=t.states?.[e];l={name:(o?o.name_by_user||o.name:i.name||s?.attributes?.friendly_name||e)||e,model:o?.model,ents:[]},r.set(a,l)}l.ents.push(e)}for(const[o,n]of s){const s=[],r=[];for(const i of n.values()){const o=Ai(t,i.name,i.model,i.ents,e),n="mdi:thermometer"===o||"mdi:air-filter"===o;if(n){const e=zi(t,i.ents);null!=e&&s.push(e)}if(n||"mdi:water-percent"===o){const e=Ei(t,i.ents);null!=e&&r.push(e)}}(s.length||r.length)&&i.set(o,{temp:s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null,hum:r.length?Math.round(r.reduce((t,e)=>t+e,0)/r.length):null})}return i}(this.hass,this._iconRules);return this._climateCache={h:this.hass,r:this._iconRules,m:e},e}_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=yi(t.settings?.name_scale),this._roomLabelScale=yi(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)return{x:i.x*us,y:i.y*us};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},gs(t),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]),d=Math.max(n[0]+h,Math.min(n[0]+n[2]-h,this._drag.ox+l)),p=Math.max(n[1]+h,Math.min(n[1]+n[3]-h,this._drag.oy+c));this._savePos({id:s,space:i},d,p)}_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},gs(t)}_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);this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/us,y:o.y/us,k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomGear(t,e,i){if(!t.id)return G;let s=null;if(t.poly?(s=this._gearPtCache.get(t.poly)||null,s||(s=Re(t.poly),this._gearPtCache.set(t.poly,s))):null!=t.x&&null!=t.y&&(s=[t.x+(t.w||0)/2,t.y+(t.h||0)/2]),!s)return G;const o=(s[0]-i.x)/i.w*100,n=(s[1]-i.y)/i.h*100;return B`<button class="rlgearbtn" style="left:${o}%;top:${n}%"
|
||
title=${this._t("room.settings_title")}
|
||
@pointerdown=${t=>t.stopPropagation()}
|
||
@click=${e=>{e.stopPropagation(),this._openRoomEdit(t)}}>
|
||
<ha-icon icon="mdi:cog-outline"></ha-icon>
|
||
<span class="rlgeartext">${this._t("room.settings_short")}</span>
|
||
</button>`}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)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){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`<span class="rlm"><ha-icon icon="mdi:thermometer"></ha-icon>${e}°</span>`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`<span class="rlm"><ha-icon icon="mdi:water-percent"></ha-icon>${e}%</span>`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`<span class="rlm"><ha-icon icon="mdi:zigbee"></ha-icon>${e}</span>`)}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&&!n.hidden)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`<span class="rlm ${e.on?"lit":""}"><ha-icon icon=${e.on?"mdi:lightbulb-on":"mdi:lightbulb-outline"}></ha-icon>${t}</span>`)}}}return B`<div class="roomlabel ${c.length?"card":""}"
|
||
style="left:${n}%;top:${r}%;color:${s.color};opacity:${a};--rl-scale:${l};--rl-space:${s.cardFontScale};--rl-name:${yi(t.settings?.name_scale)};--rl-meta:${yi(t.settings?.label_scale)}"
|
||
@pointerdown=${i=>this._labelDown(i,t,e.id)}
|
||
@pointermove=${i=>this._labelMove(i,t,e.id)}
|
||
@pointerup=${()=>this._labelUp(t)}
|
||
@pointercancel=${()=>this._labelUp(t)}
|
||
><span class="rlname">${t.name||(this._markup?this._t("room.unnamed"):"")}${!this._markup&&t.area?B`<ha-icon class="rlgo" icon="mdi:open-in-new"
|
||
title=${this._t("room.open_area")}
|
||
@click=${e=>{e.stopPropagation(),this._clickRoom(t)}}
|
||
@pointerdown=${t=>t.stopPropagation()}></ha-icon>`:G}</span>
|
||
${c.length?B`<span class="rlmetrics">${c}</span>`:G}
|
||
${"plan"===this._mode?["tl","tr","bl","br"].map(i=>B`<span class="rlhandle ${i}"
|
||
@pointerdown=${i=>this._rlResizeDown(i,t,e.id)}
|
||
@pointermove=${t=>this._rlResizeMove(t)}
|
||
@pointerup=${()=>this._rlResizeUp()}
|
||
@pointercancel=${()=>this._rlResizeUp()}></span>`):G}
|
||
</div>`}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`<div class="measurelabel ${a?"on45":""}" style="left:${s}%;top:${o}%">
|
||
${this._fmtLen(e,i)} · ${r}°</div>`}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=us,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=us,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||e<s.d)&&(s={d:e,c:n})}if(Math.abs(n[1]-t[1])<=i){const e=Math.abs(n[0]-t[0]);e>1e-6&&(!o||e<o.d)&&(o={d:e,c:n})}}const n=[];return s&&n.push({axis:"x",at:s.c[0],from:s.c}),o&&n.push({axis:"y",at:o.c[1],from:o.c}),n}(t,this._alignCandidates(),e);if(!i.length)return j``;const s=this._gridPitch,o=1.5*s;return j`<g class="alignguides">
|
||
${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`<line class="alignline" x1="${i}" y1="${n}" x2="${r}" y2="${a}"></line>
|
||
<circle class="aligndot" cx="${e.from[0]}" cy="${e.from[1]}" r="${.18*s}"></circle>`})}
|
||
</g>`}_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:$e(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`
|
||
<path class="op-arc" d="M 0 0 A ${e} ${e} 0 0 0 ${-e} ${-e}" fill="none"
|
||
stroke="${o}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-s)}"></path>
|
||
<path class="op-arc" d="M 0 0 A ${e} ${e} 0 0 1 ${e} ${-e}" fill="none"
|
||
stroke="${o}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-s)}"></path>
|
||
<g transform="translate(${-e} 0)">
|
||
<g class="op-leaf" style="transform:rotate(${-90*s}deg)">
|
||
<rect x="0" y="-1.5" width="${e}" height="3" fill="${o}"></rect>
|
||
</g>
|
||
</g>
|
||
<g transform="translate(${e} 0)">
|
||
<g class="op-leaf" style="transform:rotate(${90*s}deg)">
|
||
<rect x="${-e}" y="-1.5" width="${e}" height="3" fill="${o}"></rect>
|
||
</g>
|
||
</g>`}else{const i=t.rlen,n=Math.PI/2*i;a=j`
|
||
<path class="op-arc" d="M ${e} 0 A ${i} ${i} 0 0 0 ${-e} ${-i}" fill="none"
|
||
stroke="${o}" stroke-dasharray="${n}" stroke-dashoffset="${n*(1-s)}"></path>
|
||
<g transform="translate(${-e} 0)">
|
||
<g class="op-leaf" style="transform:rotate(${-90*s}deg)">
|
||
<rect x="0" y="-1.75" width="${i}" height="3.5" fill="${o}"></rect>
|
||
</g>
|
||
</g>`}return j`<g class="opening" transform="translate(${t.rx} ${t.ry}) rotate(${t.angle})">
|
||
<g transform="scale(${n} ${r})">
|
||
<line x1="${-e}" y1="${-4}" x2="${-e}" y2="${4}" stroke="${i}" stroke-width="2.5"></line>
|
||
<line x1="${e}" y1="${-4}" x2="${e}" y2="${4}" stroke="${i}" stroke-width="2.5"></line>
|
||
${a}
|
||
</g>
|
||
<rect class="op-outline" x="${-e-10}" y="-16" width="${t.rlen+20}" height="32" rx="6"></rect>
|
||
<rect class="op-hit" x="${-e-12}" y="-20" width="${t.rlen+24}" height="40"
|
||
@click=${e=>this._opClick(e,t)}
|
||
@pointerdown=${e=>this._opPointerDown(e,t)}
|
||
@pointermove=${e=>this._opPointerMove(e,t)}
|
||
@pointerup=${e=>this._opPointerUp(e,t)}
|
||
@pointercancel=${e=>this._opPointerUp(e,t)}></rect>
|
||
</g>`})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"door"===t.type&&t.lock);return e.length?B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,s="locked"===i,o=s||["unlocked","open","opening","unlocking","locking"].includes(String(i)),n=(e.angle+90)*Math.PI/180,r=16*(e.flip_v?-1:1),a=e.rx+Math.cos(n)*r,l=e.ry+Math.sin(n)*r,c=(a-t.x)/t.w*100,h=(l-t.y)/t.h*100;return B`<div class="oplock ${s?"locked":o?"unlocked":"unknown"}"
|
||
style="left:${c}%;top:${h}%"
|
||
@click=${t=>{t.stopPropagation(),"view"===this._mode&&(this._openingInfo=e)}}>
|
||
<ha-icon icon="${s?"mdi:lock":o?"mdi:lock-open-variant":"mdi:lock-question"}"></ha-icon>
|
||
</div>`})}`:B``}_lockAction(t,e){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact?this.hass.states[t.contact]?.state:null,i=this._openingAmt(t),s=t.lock?this.hass.states[t.lock]?.state:null,o=(t,e,i,s="")=>B`<div class="oprow ${s}"><ha-icon icon=${t}></ha-icon><span>${e}</span><b>${i}</b></div>`;return B`<div class="menuwrap dialogwrap" @click=${()=>this._openingInfo=null}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon=${"door"===t.type?"mdi:door":"mdi:window-closed-variant"}></ha-icon>
|
||
${this._t("door"===t.type?"opening.door":"opening.window")}</div>
|
||
<div class="body">
|
||
${t.contact?o(i>0?"mdi:door-open":"mdi:door-closed",this._t("opening.contact_label"),"unavailable"===e||null==e?this._t("opening.state_unknown"):this._t(i>0?"opening.open":"opening.closed"),i>0?"warn":"ok"):G}
|
||
${t.lock?o("locked"===s?"mdi:lock":"mdi:lock-open-variant",this._t("opening.lock_label"),"locked"===s?this._t("opening.locked"):["unlocked","open"].includes(String(s))?this._t("opening.unlocked"):this._t("opening.state_unknown"),"locked"===s?"ok":"warn"):G}
|
||
${t.lock&&("locked"===s||["unlocked","open"].includes(String(s)))?B`<button
|
||
class="btn lockact ${"locked"===s?"warn":""}"
|
||
@click=${()=>this._lockAction(t.lock,"locked"===s?"unlock":"lock")}>
|
||
<ha-icon icon=${"locked"===s?"mdi:lock-open-variant":"mdi:lock"}></ha-icon>
|
||
${this._t("locked"===s?"opening.unlock_action":"opening.lock_action")}
|
||
</button>`:t.lock&&["locking","unlocking"].includes(String(s))?B`<button class="btn lockact" disabled>
|
||
<ha-icon icon="mdi:timer-sand"></ha-icon>${this._t("opening.lock_pending")}
|
||
</button>`:G}
|
||
${t.contact||t.lock?G:B`<p class="muted">${this._t("opening.no_entities")}</p>`}
|
||
</div>
|
||
<div class="row">
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._openingInfo=null}>${this._t("btn.close")}</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderOpeningDialog(){const t=this._openingDialog,e=(t,e,i)=>B`<select class="areasel" @change=${t=>i(t.target.value)}>
|
||
<option value="" ?selected=${!e}>${this._t("opening.none")}</option>
|
||
${t.map(t=>B`<option value=${t.value} ?selected=${t.value===e}>${t.label}</option>`)}
|
||
</select>`;return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:door"></ha-icon>
|
||
${t.id?this._t("opening.edit"):this._t("opening.new")}</div>
|
||
<div class="body">
|
||
<label>${this._t("opening.type_label")}</label>
|
||
<label class="srcrow"><input type="radio" name="optype" .checked=${"door"===t.type}
|
||
@change=${()=>this._openingDialog={...t,type:"door",lengthCm:t.id?t.lengthCm:90}} />
|
||
<span>${this._t("opening.door")}</span></label>
|
||
<label class="srcrow"><input type="radio" name="optype" .checked=${"window"===t.type}
|
||
@change=${()=>this._openingDialog={...t,type:"window",lengthCm:t.id?t.lengthCm:120}} />
|
||
<span>${this._t("opening.window")}</span></label>
|
||
|
||
<label>${this._t("opening.length_label")}</label>
|
||
<input class="namein tempin" type="number" min="20" max="600" step="5" .value=${String(t.lengthCm)}
|
||
@input=${e=>{const i=parseFloat(e.target.value);Number.isFinite(i)&&(this._openingDialog={...t,lengthCm:i})}} />
|
||
|
||
<label>${this._t("opening.contact_label")}</label>
|
||
${e(this._contactCandidates(),t.contact,e=>this._openingDialog={...t,contact:e})}
|
||
${t.contact?B`<label class="srcrow"><input type="checkbox" .checked=${t.invert}
|
||
@change=${e=>this._openingDialog={...t,invert:e.target.checked}} />
|
||
<span>${this._t("opening.invert")}</span></label>`:G}
|
||
|
||
${"door"===t.type?B`<label>${this._t("opening.lock_label")}</label>
|
||
${e(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:G}
|
||
|
||
<label class="srcrow"><input type="checkbox" .checked=${t.flipH}
|
||
@change=${e=>this._openingDialog={...t,flipH:e.target.checked}} />
|
||
<span>${this._t("opening.flip_h")}</span></label>
|
||
<label class="srcrow"><input type="checkbox" .checked=${t.flipV}
|
||
@change=${e=>this._openingDialog={...t,flipV:e.target.checked}} />
|
||
<span>${this._t("opening.flip_v")}</span></label>
|
||
</div>
|
||
<div class="row">
|
||
${t.id?B`<button class="btn danger" @click=${this._deleteOpening}>
|
||
<ha-icon icon="mdi:delete-outline"></ha-icon>${this._t("btn.delete")}
|
||
</button>`:G}
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._openingDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._saveOpening}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderMarkupDefs(t){const e=this._gridPitch,i=.14*e;return j`<defs>
|
||
<pattern id="hp-grid" x="0" y="0" width="${e}" height="${e}" patternUnits="userSpaceOnUse">
|
||
<circle cx="0" cy="0" r="${i}" class="griddot"></circle>
|
||
<circle cx="${e}" cy="0" r="${i}" class="griddot"></circle>
|
||
<circle cx="0" cy="${e}" r="${i}" class="griddot"></circle>
|
||
<circle cx="${e}" cy="${e}" r="${i}" class="griddot"></circle>
|
||
</pattern>
|
||
</defs>`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=e.length?_i(this._segments,e,.02*this._gridPitch):this._segments,s=this._path,o=this._gridPitch;return j`
|
||
<rect x="${t[0]}" y="${t[1]}" width="${t[2]}" height="${t[3]}" fill="url(#hp-grid)" pointer-events="none"></rect>
|
||
${i.map(t=>j`<line class="seg" x1="${t[0]}" y1="${t[1]}" x2="${t[2]}" y2="${t[3]}"></line>`)}
|
||
${s.length>1?j`<polyline class="pathline" points="${s.map(t=>t.join(",")).join(" ")}"></polyline>`:G}
|
||
${s.length&&this._cursorPt&&"draw"===this._tool&&!this._contourClosed?j`<line class="preview" x1="${s[s.length-1][0]}" y1="${s[s.length-1][1]}"
|
||
x2="${this._cursorPt[0]}" y2="${this._cursorPt[1]}"></line>`:G}
|
||
${s.map((t,e)=>j`<circle class="vertex ${0===e?"first":""}" cx="${t[0]}" cy="${t[1]}" r="${.22*o}"></circle>`)}
|
||
${(()=>{const t=this._openingPreview;if(!t)return G;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return j`<line class="opghost" x1="${t.x-i}" y1="${t.y-s}"
|
||
x2="${t.x+i}" y2="${t.y+s}"></line>
|
||
<circle class="opghost-dot" cx="${t.x}" cy="${t.y}" r="${.18*o}"></circle>`})()}
|
||
${"split"===this._tool&&this._splitSel?.pts?.length?j`${this._splitSel.pts.length>1?j`<polyline class="pathline" points="${this._splitSel.pts.map(t=>t.join(",")).join(" ")}"></polyline>`:G}
|
||
${this._splitSel.pts.map((t,e)=>j`<circle class="vertex ${0===e?"first":""}" cx="${t[0]}" cy="${t[1]}" r="${.22*o}"></circle>`)}
|
||
${this._cursorPt?j`<line class="preview" x1="${this._splitSel.pts[this._splitSel.pts.length-1][0]}" y1="${this._splitSel.pts[this._splitSel.pts.length-1][1]}"
|
||
x2="${this._cursorPt[0]}" y2="${this._cursorPt[1]}"></line>`:G}`:G}
|
||
`}_renderMarkupBar(){return B`<div class="editbar">
|
||
<ha-icon icon="mdi:vector-square-edit" class="warn"></ha-icon>
|
||
<button class="btn ${"draw"===this._tool?"on":""}" @click=${()=>this._tool="draw"}
|
||
title=${this._t("title.markup_add")}>
|
||
<ha-icon icon="mdi:vector-polyline-plus"></ha-icon>${this._t("markup.add")}
|
||
</button>
|
||
<button class="btn ${"merge"===this._tool?"on":""}"
|
||
@click=${()=>{this._tool="merge",this._cancelPath(),this._tool="merge"}}
|
||
title=${this._t("title.markup_merge")}>
|
||
<ha-icon icon="mdi:vector-union"></ha-icon>${this._t("markup.merge")}
|
||
</button>
|
||
<button class="btn ${"split"===this._tool?"on":""}"
|
||
@click=${()=>{this._tool="split",this._cancelPath(),this._tool="split"}}
|
||
title=${this._t("title.markup_split")}>
|
||
<ha-icon icon="mdi:vector-polyline-remove"></ha-icon>${this._t("markup.split")}
|
||
</button>
|
||
<button class="btn ${"opening"===this._tool?"on":""}"
|
||
@click=${()=>{this._cancelPath(),this._tool="opening"}}
|
||
title=${this._t("title.markup_opening")}>
|
||
<ha-icon icon="mdi:door"></ha-icon>${this._t("markup.opening")}
|
||
</button>
|
||
<button class="btn ${"openwall"===this._tool?"on":""}"
|
||
@click=${()=>{this._cancelPath(),this._tool="openwall"}}
|
||
title=${this._t("title.markup_openwall")}>
|
||
<ha-icon icon="mdi:border-none-variant"></ha-icon>${this._t("markup.openwall")}
|
||
</button>
|
||
<button class="btn ${"delroom"===this._tool?"on":""}" @click=${()=>this._tool="delroom"}
|
||
title=${this._t("title.markup_delroom")}>
|
||
<ha-icon icon="mdi:delete-outline"></ha-icon>${this._t("markup.delete")}
|
||
</button>
|
||
<span class="spacer"></span>
|
||
${"draw"===this._tool?B`<span class="hint">${this._path.length?this._t("markup.hint_points",{n:this._path.length}):this._t("markup.hint_start")}</span>
|
||
${this._path.length?B`<button class="btn ghost" @click=${this._cancelPath}>${this._t("btn.reset")}</button>`:G}`:G}
|
||
<button class="btn barclose" title=${this._t("title.close_editor")}
|
||
@click=${()=>this._setMode("view")}>
|
||
<ha-icon icon="mdi:close"></ha-icon>
|
||
</button>
|
||
</div>`}_renderDevicesBar(){return B`<div class="editbar devbar">
|
||
<ha-icon icon="mdi:tune-variant" class="warn"></ha-icon>
|
||
<button class="btn" @click=${()=>this._openMarkerDialog()} title=${this._t("title.add_device")}>
|
||
<ha-icon icon="mdi:plus-box-outline"></ha-icon>${this._t("devbar.add")}
|
||
</button>
|
||
<button class="btn ${this._showAll?"on":""}" @click=${this._toggleShowAll}
|
||
title=${this._t("title.show_all")}>
|
||
<ha-icon icon="${this._showAll?"mdi:eye":"mdi:eye-off-outline"}"></ha-icon>${this._t("devbar.show_all")}
|
||
</button>
|
||
<button class="btn" @click=${this._openRulesDialog} title=${this._t("title.icon_rules")}>
|
||
<ha-icon icon="mdi:shape-plus-outline"></ha-icon>${this._t("devbar.rules")}
|
||
</button>
|
||
<span class="spacer"></span>
|
||
<button class="btn barclose" title=${this._t("title.close_editor")}
|
||
@click=${()=>this._setMode("view")}>
|
||
<ha-icon icon="mdi:close"></ha-icon>
|
||
</button>
|
||
</div>`}_cardEntities(t){const e=this.hass,i=[],s=new Set,o=t=>{if(!t||s.has(t)||!e.states[t])return;const o=e.entities[t];if("config"===o?.entity_category||"diagnostic"===o?.entity_category)return;s.add(t);const n=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(n)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(n)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(n)&&i.push({eid:t,kind:"value"})};for(const e of t.marker?.controls||[])o(e);t.primary&&o(t.primary);for(const e of t.entities)o(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_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(di);return B`<div class="menuwrap dialogwrap" @click=${()=>this._infoCard=null}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="${t.icon}"></ha-icon>${t.name}</div>
|
||
<div class="body">
|
||
${(()=>{const e=this._cardEntities(t);return e.length?B`<div class="entlist">
|
||
${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,o=i?this.hass.formatEntityState?.(i)??i.state:"",n="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`<div class="entrow ${n?"on":""}">
|
||
<ha-icon icon=${ri(gt(s,"",this._iconRules),t.split(".")[0],i?.attributes?.device_class,i?.state,!1)}></ha-icon>
|
||
<span class="en">${s}</span>
|
||
${"toggle"===e?B`<button class="entbtn ${n?"on":""}"
|
||
@click=${()=>this._cardToggle(t)}>${o}</button>`:"open"===e?B`<button class="entbtn"
|
||
@click=${()=>{this._infoCard=null,this._openMoreInfo(t)}}>${o}</button>`:B`<span class="ev">${o}</span>`}
|
||
</div>`})}
|
||
</div>`:G})()}
|
||
${t.model?B`<div class="inforow"><span class="k">${this._t("info.model")}</span><span>${t.model}</span></div>`:G}
|
||
${i&&!this._cardEntities(t).length?B`<div class="inforow"><span class="k">${this._t("info.state")}</span><span>${i}</span></div>`:G}
|
||
${We(t.link)?B`<div class="inforow"><span class="k">${this._t("info.link")}</span>
|
||
<a href="${We(t.link)}" target="_blank" rel="noreferrer noopener">${t.link}</a></div>`:G}
|
||
${t.description?B`<div class="infodesc">${t.description}</div>`:G}
|
||
${t.pdfs&&t.pdfs.length?B`<div class="inforow"><span class="k">${this._t("info.manuals")}</span><span class="pdflist">
|
||
${t.pdfs.map(t=>B`<a class="pdf" href="${We(this._display(t.url))||"#"}" target="_blank" rel="noreferrer noopener">
|
||
<ha-icon icon="mdi:file-pdf-box"></ha-icon>${t.name}</a>`)}</span></div>`:G}
|
||
${s.length?B`<div class="inforow"><span class="k">${this._t("info.controls")}</span>
|
||
<span class="ctrlstates">
|
||
${s.map(t=>{const e=this.hass.states[t],i="on"===e?.state;return B`<span class="ctrlstate ${i?"on":""}">
|
||
<ha-icon icon=${i?"mdi:lightbulb-on":"mdi:lightbulb-outline"}></ha-icon>
|
||
${e?.attributes?.friendly_name||t}</span>`})}
|
||
</span></div>`:G}
|
||
${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?G:B`<div class="infodesc muted">${this._t("info.none")}</div>`}
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn" @click=${()=>{const e=t;this._infoCard=null,this._openMarkerDialog(e)}}>
|
||
<ha-icon icon="mdi:pencil"></ha-icon>${this._t("btn.edit")}
|
||
</button>
|
||
${t.primary?B`<button class="btn" @click=${()=>{const e=t.primary;this._infoCard=null,this._openMoreInfo(e)}}>
|
||
<ha-icon icon="mdi:open-in-new"></ha-icon>${this._t("btn.open_in_ha")}
|
||
</button>`:G}
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._infoCard=null}>${this._t("btn.close")}</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[o,n]=t.binding.split(":");return"device"===o?this.hass.devices[n]?.name_by_user||this.hass.devices[n]?.name||n:this.hass.states[n]?.attributes?.friendly_name||n})();return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog wide" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:shape-plus"></ha-icon>
|
||
${t.devId?this._t("info.device_header"):this._t("marker.new_device")}</div>
|
||
<div class="body">
|
||
<label>${this._t("marker.name_label")}</label>
|
||
<input class="namein" type="text" placeholder=${this._t("marker.name_ph")}
|
||
.value=${t.name}
|
||
@input=${e=>this._markerDialog={...t,name:e.target.value}} />
|
||
|
||
<label>${this._t("marker.binding_label")}</label>
|
||
<div class="bindsel">
|
||
<label class="srcrow">
|
||
<input type="radio" name="bmode" .checked=${"virtual"===t.bindingMode}
|
||
@change=${()=>this._markerDialog={...t,bindingMode:"virtual",binding:"virtual",bindingOpen:!1}} />
|
||
<span>${this._t("marker.virtual_option")}</span>
|
||
</label>
|
||
<div class="bindharow">
|
||
<label class="srcrow">
|
||
<input type="radio" name="bmode" .checked=${"ha"===t.bindingMode}
|
||
@change=${()=>this._markerDialog={...t,bindingMode:"ha",binding:"virtual"===t.binding?"":t.binding,bindingOpen:"virtual"===t.binding||!t.binding}} />
|
||
<span>${this._t("marker.from_ha_option")}</span>
|
||
</label>
|
||
<label class="srcrow inline entcheck" title=${this._t("marker.show_entities_tip")}>
|
||
<input type="checkbox" .checked=${t.showEntities}
|
||
?disabled=${"ha"!==t.bindingMode}
|
||
@change=${e=>this._markerDialog={...t,showEntities:e.target.checked}} />
|
||
<span>${this._t("marker.show_entities")}</span>
|
||
</label>
|
||
</div>
|
||
${"ha"===t.bindingMode?B`<button class="dropbtn ${t.bindingOpen?"open":""}"
|
||
@click=${()=>this._markerDialog={...t,bindingOpen:!t.bindingOpen}}>
|
||
${s?B`<b>${s}</b><span class="ref">${t.binding}</span>`:B`<span class="muted">${this._t("marker.pick_ph")}</span>`}
|
||
<ha-icon icon=${t.bindingOpen?"mdi:chevron-up":"mdi:chevron-down"}></ha-icon>
|
||
</button>
|
||
${t.bindingOpen?B`<div class="droppanel">
|
||
<input class="namein" type="text" placeholder=${this._t("marker.search_ph")}
|
||
.value=${t.bindingFilter}
|
||
@input=${e=>this._markerDialog={...t,bindingFilter:e.target.value}} />
|
||
<div class="candlist">
|
||
${i.map(e=>B`<div class="cand ${e.value===t.binding?"sel":""}"
|
||
@click=${()=>this._markerDialog={...t,binding:e.value,bindingOpen:!1}}>
|
||
<span class="cl">${e.label}</span><span class="cs">${e.sub}</span>
|
||
</div>`)}
|
||
${i.length?G:B`<div class="cand muted">${this._t("marker.nothing_found")}</div>`}
|
||
</div>
|
||
</div>`:G}`:G}
|
||
</div>
|
||
|
||
<label>${this._t("marker.room_label")}${e?"":this._t("marker.room_override")}</label>
|
||
<select class="areasel"
|
||
@change=${e=>this._markerDialog={...t,room:e.target.value}}>
|
||
<option value="">${e?this._t("marker.room_choose"):this._t("marker.room_auto")}</option>
|
||
${this._allRoomsFlat().map(e=>B`<option value=${e.value} ?selected=${e.value===t.room}>${e.label}</option>`)}
|
||
</select>
|
||
|
||
<label>${this._t("marker.tap_label")}</label>
|
||
<select class="areasel"
|
||
@change=${e=>this._markerDialog={...t,tapAction:e.target.value}}>
|
||
${Ve.map(t=>[t,"tap."+t.replace("-","_")]).map(([e,i])=>B`<option value=${e} ?selected=${(t.tapAction||t.defaultTap)===e}>${this._t(i)}</option>`)}
|
||
</select>
|
||
|
||
<label>${this._t("marker.controls_label")}</label>
|
||
<div class="rhint">${this._t("marker.controls_hint")}</div>
|
||
${t.controls.length?B`<div class="ctrlchips">
|
||
${t.controls.map(e=>B`<span class="ctrlchip">
|
||
${this.hass.states[e]?.attributes?.friendly_name||e}
|
||
<ha-icon icon="mdi:close" @click=${()=>this._markerDialog={...t,controls:t.controls.filter(t=>t!==e)}}></ha-icon>
|
||
</span>`)}
|
||
</div>`:G}
|
||
<input class="namein" type="text" placeholder=${this._t("marker.controls_filter")}
|
||
.value=${t.controlsFilter}
|
||
@input=${e=>this._markerDialog={...t,controlsFilter:e.target.value}} />
|
||
${t.controlsFilter.trim()?B`<div class="ctrllist">
|
||
${Object.keys(this.hass.states).filter(e=>di(e)&&!t.controls.includes(e)).filter(e=>{const i=t.controlsFilter.trim().toLowerCase(),s=String(this.hass.states[e]?.attributes?.friendly_name||"");return e.toLowerCase().includes(i)||s.toLowerCase().includes(i)}).slice(0,8).map(e=>B`<button class="ctrlopt"
|
||
@click=${()=>this._markerDialog={...t,controls:[...t.controls,e],controlsFilter:""}}>
|
||
<ha-icon icon=${e.startsWith("light.")?"mdi:lightbulb":"mdi:toggle-switch"}></ha-icon>
|
||
${this.hass.states[e]?.attributes?.friendly_name||e}
|
||
<span class="sub">${e}</span>
|
||
</button>`)}
|
||
</div>`:G}
|
||
|
||
<label class="srcrow" title=${this._t("marker.is_light_tip")}>
|
||
<input type="checkbox" .checked=${t.isLight}
|
||
@change=${e=>this._markerDialog={...t,isLight:e.target.checked}} />
|
||
<span>${this._t("marker.is_light")}</span>
|
||
</label>
|
||
<label class="srcrow" title=${this._t("marker.hide_tip")}>
|
||
<input type="checkbox" .checked=${t.hideFromPlan}
|
||
@change=${e=>this._markerDialog={...t,hideFromPlan:e.target.checked}} />
|
||
<span>${this._t("marker.hide")}</span>
|
||
</label>
|
||
<label>${this._t("marker.glow_radius_label")}</label>
|
||
<div class="colorrow">
|
||
<input class="tempin" type="number" min="0.5" step="0.5"
|
||
placeholder=${this._glowRadiusPlaceholder}
|
||
.value=${t.glowRadius}
|
||
@input=${e=>this._markerDialog={...t,glowRadius:e.target.value}} />
|
||
<span class="opl">${this._imperial?this._t("gs.unit_ft"):this._t("gs.unit_m")}</span>
|
||
<span class="opl muted">${this._t("marker.glow_radius_hint")}</span>
|
||
</div>
|
||
|
||
<label>${this._t("marker.icon_label")}</label>
|
||
${customElements.get("ha-icon-picker")?B`<ha-icon-picker .hass=${this.hass} .value=${t.icon}
|
||
.placeholder=${t.autoIcon||void 0}
|
||
.fallbackPath=${void 0}
|
||
@value-changed=${e=>this._markerDialog={...t,icon:e.detail.value||""}}></ha-icon-picker>`:B`<input class="namein" type="text"
|
||
placeholder=${t.autoIcon||this._t("marker.icon_ph")}
|
||
.value=${t.icon}
|
||
@input=${e=>this._markerDialog={...t,icon:e.target.value}} />`}
|
||
${!t.icon&&t.autoIcon?B`<p class="muted iconauto"><ha-icon icon=${t.autoIcon}></ha-icon>
|
||
${this._t("marker.icon_auto",{icon:t.autoIcon})}</p>`:G}
|
||
|
||
<label>${this._t("marker.display_label")}</label>
|
||
<select class="areasel"
|
||
@change=${e=>this._markerDialog={...t,display:e.target.value}}>
|
||
${Ge.map(t=>[t,"display."+t]).map(([e,i])=>B`<option value=${e} ?selected=${t.display===e}>${this._t(i)}</option>`)}
|
||
</select>
|
||
${"ripple"===t.display||"icon_ripple"===t.display?B`<div class="colorrow">
|
||
<input type="color" .value=${t.rippleColor||"#3ea6ff"}
|
||
@input=${e=>this._markerDialog={...t,rippleColor:e.target.value}} />
|
||
<span class="opl">${this._t("marker.ripple_size")}</span>
|
||
<input type="range" min="2" max="8" step="0.5" .value=${String(t.rippleSize)}
|
||
@input=${e=>this._markerDialog={...t,rippleSize:Number(e.target.value)}} />
|
||
<span class="opv">×${t.rippleSize}</span>
|
||
</div>`:G}
|
||
|
||
<label>${this._t("marker.size_label")}</label>
|
||
<div class="colorrow">
|
||
<input type="range" min="0.5" max="3" step="0.1" .value=${String(t.size)}
|
||
@input=${e=>this._markerDialog={...t,size:Number(e.target.value)}} />
|
||
<span class="opv">×${t.size.toFixed(1)}</span>
|
||
<span class="opl">${this._t("marker.angle_label")}</span>
|
||
<input type="range" min="0" max="350" step="10" .value=${String(t.angle)}
|
||
@input=${e=>this._markerDialog={...t,angle:Number(e.target.value)}} />
|
||
<span class="opv">${t.angle}°</span>
|
||
</div>
|
||
|
||
<label>${this._t("marker.model_label")}</label>
|
||
<input class="namein" type="text" placeholder=${this._t("marker.model_ph")}
|
||
.value=${t.model}
|
||
@input=${e=>this._markerDialog={...t,model:e.target.value}} />
|
||
|
||
<label>${this._t("marker.link_label")}</label>
|
||
<input class="namein" type="url" placeholder="https://…"
|
||
.value=${t.link}
|
||
@input=${e=>this._markerDialog={...t,link:e.target.value}} />
|
||
|
||
<label>${this._t("marker.desc_label")}</label>
|
||
<textarea class="descin" rows="4" placeholder=${this._t("marker.desc_ph")}
|
||
.value=${t.description}
|
||
@input=${e=>this._markerDialog={...t,description:e.target.value}}></textarea>
|
||
|
||
<label>${this._t("marker.manuals_label")}</label>
|
||
<div class="pdfedit">
|
||
${t.pdfs.map(t=>B`<span class="pdftag"><ha-icon icon="mdi:file-pdf-box"></ha-icon>
|
||
<a href="${We(this._display(t.url))||"#"}" target="_blank" rel="noreferrer noopener">${t.name}</a>
|
||
<ha-icon class="x" icon="mdi:close" @click=${()=>this._removeMarkerPdf(t.url)}></ha-icon></span>`)}
|
||
<label class="btn filebtn">
|
||
<ha-icon icon="mdi:paperclip"></ha-icon>${this._t("btn.attach")}
|
||
<input type="file" hidden multiple accept=".pdf,.png,.jpg,.jpeg,.webp,.txt,application/pdf"
|
||
@change=${t=>this._pickMarkerFiles(t)} />
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
${t.devId&&"virtual"===t.binding?B`<button class="btn danger" @click=${this._deleteMarker}>
|
||
<ha-icon icon="mdi:delete-outline"></ha-icon>${this._t("btn.remove")}
|
||
</button>`:G}
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._markerDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._saveMarker}
|
||
?disabled=${t.busy||"ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding)}
|
||
title=${"ha"!==t.bindingMode||t.binding&&"virtual"!==t.binding?"":this._t("marker.pick_ph")}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${t.busy?"…":this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderSpaceDialog(){const t=this._spaceDialog;return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog wide" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:floor-plan"></ha-icon>
|
||
${"create"===t.mode?this._t("space.new"):this._t("space.header")}
|
||
${this._importTotal>0&&"create"===t.mode?B`<span class="importprog">${this._t("import.progress",{i:this._importTotal-this._importQueue.length,n:this._importTotal})}</span>`:G}</div>
|
||
<div class="body">
|
||
<label>${this._t("space.title_label")}</label>
|
||
<input class="namein" type="text" placeholder=${this._t("space.title_ph")}
|
||
.value=${t.title}
|
||
@input=${e=>this._spaceDialog={...t,title:e.target.value}} />
|
||
<label>${this._t("space.plan_label")}</label>
|
||
<label class="srcrow">
|
||
<input type="radio" name="plansrc" .checked=${"file"===t.source}
|
||
@change=${()=>this._spaceDialog={...t,source:"file"}} />
|
||
<span>${this._t("space.source_file")}</span>
|
||
</label>
|
||
${"file"===t.source?B`<div class="planrow">
|
||
${t.planFile?B`<span class="planname">${t.planFile.name}</span>`:t.planUrl?B`<img class="planprev" src=${this._display(t.planUrl)} alt=${this._t("space.plan_alt")} />`:B`<span class="planname muted">${this._t("space.no_plan")}</span>`}
|
||
<label class="btn filebtn">
|
||
<ha-icon icon="mdi:upload"></ha-icon>${t.planUrl||t.planFile?this._t("btn.replace"):this._t("btn.upload")}
|
||
<input type="file" hidden accept=".svg,.png,.jpg,.jpeg,.webp,image/svg+xml,image/png,image/jpeg,image/webp"
|
||
@change=${t=>this._pickPlanFile(t)} />
|
||
</label>
|
||
<button class="btn ghost" @click=${this._toggleServerPlans}
|
||
title=${this._t("space.pick_saved_hint")}>
|
||
<ha-icon icon="mdi:folder-image"></ha-icon>${this._t("space.pick_saved")}
|
||
</button>
|
||
</div>
|
||
${t.pickSaved?this._renderServerPlans(t):G}`:G}
|
||
<label class="srcrow">
|
||
<input type="radio" name="plansrc" .checked=${"draw"===t.source}
|
||
@change=${()=>this._spaceDialog={...t,source:"draw"}} />
|
||
<span>${this._t("space.source_draw")}</span>
|
||
</label>
|
||
|
||
<label>${this._t("space.scale_label")}</label>
|
||
<div class="colorrow">
|
||
<input class="namein tempin" type="number" min="0.1" step="0.1" .value=${String(t.cellCm)}
|
||
@input=${e=>{const i=parseFloat(e.target.value);this._spaceDialog={...t,cellCm:Number.isFinite(i)&&i>0?i:t.cellCm}}} />
|
||
<span class="opl">${this._t("space.scale_unit")}</span>
|
||
</div>
|
||
|
||
<label class="dispsection">${this._t("space.display_section")}</label>
|
||
<label class="srcrow">
|
||
<input type="checkbox" .checked=${t.showBorders}
|
||
@change=${e=>this._spaceDialog={...t,showBorders:e.target.checked}} />
|
||
<span>${this._t("space.show_borders")}</span>
|
||
</label>
|
||
<label class="srcrow">
|
||
<input type="checkbox" .checked=${t.showNames}
|
||
@change=${e=>this._spaceDialog={...t,showNames:e.target.checked}} />
|
||
<span>${this._t("space.show_names")}</span>
|
||
</label>
|
||
<label class="srcrow">
|
||
<input type="checkbox" .checked=${t.showLqi}
|
||
@change=${e=>this._spaceDialog={...t,showLqi:e.target.checked}} />
|
||
<span>${this._t("space.show_lqi")}</span>
|
||
</label>
|
||
<label class="dispsection">${this._t("space.roomcard_section")}</label>
|
||
${[["labelTemp","space.label_temp"],["labelHum","space.label_hum"],["labelLqi","space.label_lqi"],["labelLight","space.label_light"]].map(([e,i])=>B`<label class="srcrow">
|
||
<input type="checkbox" .checked=${t[e]}
|
||
@change=${i=>this._spaceDialog={...t,[e]:i.target.checked}} />
|
||
<span>${this._t(i)}</span>
|
||
</label>`)}
|
||
<label>${this._t("space.card_font")}</label>
|
||
<div class="colorrow gsrow">
|
||
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(100*t.cardFontScale))}
|
||
@input=${e=>this._spaceDialog={...t,cardFontScale:Number(e.target.value)/100}} />
|
||
<span class="opv">${Math.round(100*t.cardFontScale)}%</span>
|
||
</div>
|
||
${this._renderCardPreview(t.cardFontScale,1,1)}
|
||
<label>${this._t("space.room_color")}</label>
|
||
<div class="colorrow">
|
||
<input type="color" .value=${t.roomColor}
|
||
@input=${e=>this._spaceDialog={...t,roomColor:e.target.value}} />
|
||
<span class="opl">${this._t("space.opacity")}</span>
|
||
<input type="range" min="0" max="100" .value=${String(Math.round(100*t.roomOpacity))}
|
||
@input=${e=>this._spaceDialog={...t,roomOpacity:Number(e.target.value)/100}} />
|
||
<span class="opv">${Math.round(100*t.roomOpacity)}%</span>
|
||
</div>
|
||
<label>${this._t("space.fill_label")}</label>
|
||
${Ke.map(t=>[t,"fill."+t]).map(([e,i])=>B`<label class="srcrow">
|
||
<input type="radio" name="fillmode" .checked=${t.fillMode===e}
|
||
@change=${()=>this._spaceDialog={...t,fillMode:e}} />
|
||
<span>${this._t(i)}</span>
|
||
${"temp"===e&&"temp"===t.fillMode?B`<span class="temprange">
|
||
<input class="namein tempin" type="number" step="0.5" .value=${String(t.tempMin)}
|
||
@input=${e=>{const i=parseFloat(e.target.value);Number.isFinite(i)&&(this._spaceDialog={...t,tempMin:i})}} />
|
||
–
|
||
<input class="namein tempin" type="number" step="0.5" .value=${String(t.tempMax)}
|
||
@input=${e=>{const i=parseFloat(e.target.value);Number.isFinite(i)&&(this._spaceDialog={...t,tempMax:i})}} />
|
||
°C
|
||
</span>`:G}
|
||
</label>`)}
|
||
</div>
|
||
<div class="row">
|
||
${"edit"===t.mode?B`<button class="btn danger" @click=${this._deleteSpace}>
|
||
<ha-icon icon="mdi:delete-outline"></ha-icon>${this._t("btn.delete")}
|
||
</button>`:G}
|
||
<span class="spacer"></span>
|
||
${this._importTotal>0&&"create"===t.mode?B`<button class="btn ghost" @click=${()=>this._skipImport()}>${this._t("btn.skip")}</button>`:G}
|
||
<button class="btn ghost" @click=${()=>{this._spaceDialog=null,this._importQueue=[],this._importTotal=0}}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._saveSpaceDialog}
|
||
?disabled=${!t.title.trim()||"file"===t.source&&!(t.planFile||t.planUrl)||t.busy}
|
||
title=${"file"!==t.source||t.planFile||t.planUrl?"":this._t("title.need_plan")}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${t.busy?"…":this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderMergeDialog(){const t=this._mergeDialog,e=this._spaceModel().rooms,i=(i,s)=>{const o=e.find(t=>t.id===i),n=o?.area?this.hass.areas[o.area]?.name:null;return B`<label class="srcrow">
|
||
<input type="radio" name="mergekeep" .checked=${t.pick===s}
|
||
@change=${()=>this._mergeDialog={...t,pick:s}} />
|
||
<span>${o?.name||""} <span class="muted">· ${n||this._t("merge.no_area")}</span></span>
|
||
</label>`};return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon="mdi:vector-union"></ha-icon>${this._t("merge.header")}</div>
|
||
<div class="body">
|
||
<p class="muted">${this._t("merge.hint")}</p>
|
||
<label>${this._t("merge.keep")}</label>
|
||
${i(t.aId,"a")}
|
||
${i(t.bId,"b")}
|
||
</div>
|
||
<div class="row">
|
||
<span class="spacer"></span>
|
||
<button class="btn ghost" @click=${()=>this._mergeDialog=null}>${this._t("btn.cancel")}</button>
|
||
<button class="btn on" @click=${this._commitMerge}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${this._t("btn.save")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>`}_renderCardPreview(t,e,i){const s=18*t;return B`<div class="cardpreview">
|
||
<span class="cpname" style="font-size:${(s*e).toFixed(1)}px">
|
||
${this._t("preview.room_name")}</span>
|
||
<span class="cpmeta" style="font-size:${(.62*s*i).toFixed(1)}px">
|
||
<ha-icon icon="mdi:thermometer"></ha-icon>22.4° ·
|
||
<ha-icon icon="mdi:water-percent"></ha-icon>45% ·
|
||
<ha-icon icon="mdi:lightbulb-on"></ha-icon>${this._t("roomcard.light_partial",{on:1,total:3})}
|
||
</span>
|
||
</div>`}_renderRoomSource(t){const e="temp"===t?this._roomTempSrc:this._roomHumSrc,i=e=>{"temp"===t?this._roomTempSrc=e:this._roomHumSrc=e,this.requestUpdate()},s=this._roomSrcOpen===t;return B`
|
||
<label>${this._t("temp"===t?"room.temp_src_label":"room.hum_src_label")}</label>
|
||
<label class="srcrow">
|
||
<input type="radio" name="rsrc-${t}" .checked=${!e}
|
||
@change=${()=>{i(""),this._roomSrcOpen=null}} />
|
||
<span>${this._t("room.src_average")}</span>
|
||
</label>
|
||
<label class="srcrow">
|
||
<input type="radio" name="rsrc-${t}" .checked=${!!e}
|
||
@change=${()=>{this._roomSrcOpen=t,this._roomSrcFilter="",this.requestUpdate()}} />
|
||
<span>${this._t("room.src_pick")}</span>
|
||
</label>
|
||
${e||s?B`<button class="dropbtn ${s?"open":""}"
|
||
@click=${()=>{this._roomSrcOpen=s?null:t,this._roomSrcFilter=""}}>
|
||
${e?B`<b>${this._roomSrcLabel(e)}</b><span class="ref">${e}</span>`:B`<span class="muted">${this._t("room.src_ph")}</span>`}
|
||
<ha-icon icon=${s?"mdi:chevron-up":"mdi:chevron-down"}></ha-icon>
|
||
</button>
|
||
${s?B`<div class="droppanel">
|
||
<input class="namein" type="text" placeholder=${this._t("marker.search_ph")}
|
||
.value=${this._roomSrcFilter}
|
||
@input=${t=>{this._roomSrcFilter=t.target.value,this.requestUpdate()}} />
|
||
<div class="candlist">
|
||
${this._roomSrcCandidates().map(t=>B`<div class="cand ${t.value===e?"sel":""}"
|
||
@click=${()=>{i(t.value),this._roomSrcOpen=null}}>
|
||
<span class="cl">${t.label}</span><span class="cs">${t.sub}</span>
|
||
</div>`)}
|
||
</div>
|
||
</div>`:G}`:G}`}_renderRoomDialog(){const t=!!this._roomEditId,e=[...this._freeAreas];if(t&&this._areaSel&&!e.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&e.unshift(t)}return B`<div class="menuwrap dialogwrap" @click=${t=>t.stopPropagation()}>
|
||
<div class="dialog" @click=${t=>t.stopPropagation()}>
|
||
<div class="hd"><ha-icon icon=${t?"mdi:cog-outline":"mdi:floor-plan"}></ha-icon>
|
||
${t?this._t("room.settings_title"):this._t("room.new")}</div>
|
||
<div class="body">
|
||
<label>${this._t("room.name_label")}</label>
|
||
<input class="namein" type="text" placeholder=${this._t("room.name_ph")}
|
||
.value=${this._nameSel}
|
||
@input=${t=>this._nameSel=t.target.value} />
|
||
<label>${this._t("room.area_label")}</label>
|
||
<select class="areasel"
|
||
@change=${t=>{this._areaSel=t.target.value,!this._nameSel&&this._areaSel&&(this._nameSel=this.hass.areas[this._areaSel]?.name||""),this.requestUpdate()}}>
|
||
<option value="">${this._t("room.no_area_option")}</option>
|
||
${e.map(t=>B`<option value=${t.area_id} ?selected=${t.area_id===this._areaSel}>${t.name}</option>`)}
|
||
</select>
|
||
|
||
<label class="dispsection">${this._t("room.settings_section")}</label>
|
||
<label>${this._t("room.fill_label")}</label>
|
||
${[["","fill.inherit"],...Ze.map(t=>[t,"fill."+t])].map(([t,e])=>B`<label class="srcrow inline">
|
||
<input type="radio" name="rfill" .checked=${this._roomFill===t}
|
||
@change=${()=>{this._roomFill=t,this.requestUpdate()}} />
|
||
<span>${this._t(e)}</span>
|
||
</label>`)}
|
||
${this._renderRoomSource("temp")}
|
||
${this._renderRoomSource("hum")}
|
||
|
||
<label class="dispsection">${this._t("room.sizes_section")}</label>
|
||
<label>${this._t("room.name_scale")}</label>
|
||
<div class="colorrow gsrow">
|
||
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(100*this._roomNameScale))}
|
||
@input=${t=>{this._roomNameScale=Number(t.target.value)/100,this.requestUpdate()}} />
|
||
<span class="opv">${Math.round(100*this._roomNameScale)}%</span>
|
||
</div>
|
||
<label>${this._t("room.label_scale")}</label>
|
||
<div class="colorrow gsrow">
|
||
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(100*this._roomLabelScale))}
|
||
@input=${t=>{this._roomLabelScale=Number(t.target.value)/100,this.requestUpdate()}} />
|
||
<span class="opv">${Math.round(100*this._roomLabelScale)}%</span>
|
||
</div>
|
||
${this._renderCardPreview(ti(this._curSpaceCfg).cardFontScale,this._roomNameScale,this._roomLabelScale)}
|
||
</div>
|
||
<div class="row">
|
||
<button class="btn ghost" @click=${this._roomDialogCancel}>${this._t("btn.cancel")}</button>
|
||
<span class="spacer"></span>
|
||
${t?B`<button class="btn on" @click=${()=>this._saveRoomEdit()} ?disabled=${!this._nameSel.trim()}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${this._t("btn.save")}
|
||
</button>`:B`<button class="btn ghost" @click=${this._saveRoomNoArea} ?disabled=${!this._nameSel.trim()}
|
||
title=${this._t("title.no_area_room")}>
|
||
${this._t("btn.no_area")}
|
||
</button>
|
||
<button class="btn on" @click=${this._saveRoom} ?disabled=${!this._areaSel}
|
||
title=${this._areaSel?"":this._t("title.choose_area")}>
|
||
<ha-icon icon="mdi:check"></ha-icon>${this._t("btn.save")}
|
||
</button>`}
|
||
</div>
|
||
</div>
|
||
</div>`}}fs.properties={_hdrH:{state:!0},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}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),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.51.3 ","background:#3ea6ff;color:#04121f;font-weight:700","");
|