From 47975d95c39b9acb1bc9c292edb707d31fa0b831 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 23 Aug 2026 13:32:07 +0200 Subject: [PATCH] Add a custom icon --- .../assets/switch_manager_panel.js | 1969 +++++++++++++++-- custom_components/switch_manager/models.py | 4 +- custom_components/switch_manager/schema.py | 1 + custom_components/switch_manager/store.py | 1 + frontend/src/helpers.ts | 1 + frontend/src/switch-manager-index.ts | 89 +- frontend/src/switch-manager-switch-editor.ts | 20 +- frontend/src/types.ts | 1 + 8 files changed, 1932 insertions(+), 154 deletions(-) diff --git a/custom_components/switch_manager/assets/switch_manager_panel.js b/custom_components/switch_manager/assets/switch_manager_panel.js index f7a3cfdd..6a09cf62 100644 --- a/custom_components/switch_manager/assets/switch_manager_panel.js +++ b/custom_components/switch_manager/assets/switch_manager_panel.js @@ -1,10 +1,295 @@ -function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPropertyDescriptor(e,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(t,e,i,s);else for(var n=t.length-1;n>=0;n--)(o=t[n])&&(a=(r<3?o(a):r>3?o(e,i,a):o(e,i))||a);return r>3&&a&&Object.defineProperty(e,i,a),a}"function"==typeof SuppressedError&&SuppressedError;const e=globalThis,i=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),o=new WeakMap;let r=class{constructor(t,e,i){if(this._$cssResult$=!0,i!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(i&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=o.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&o.set(e,t))}return t}toString(){return this.cssText}};const a=(t,...e)=>{const i=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 r(i,t,s)},n=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new r("string"==typeof t?t:t+"",void 0,s))(e)})(t):t,{is:c,defineProperty:l,getOwnPropertyDescriptor:h,getOwnPropertyNames:d,getOwnPropertySymbols:p,getPrototypeOf:u}=Object,g=globalThis,m=g.trustedTypes,v=m?m.emptyScript:"",_=g.reactiveElementPolyfillSupport,b=(t,e)=>t,f={toAttribute(t,e){switch(e){case Boolean:t=t?v: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}},y=(t,e)=>!c(t,e),w={attribute:!0,type:String,converter:f,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),g.litPropertyMetadata??=new WeakMap;let $=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=w){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}=h(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const r=s?.call(this);o?.call(this,e),this.requestUpdate(t,r,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??w}static _$Ei(){if(this.hasOwnProperty(b("elementProperties")))return;const t=u(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(b("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(b("properties"))){const t=this.properties,e=[...d(t),...p(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(n(t))}else void 0!==t&&e.push(n(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 t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,s)=>{if(i)t.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const i of s){const s=document.createElement("style"),o=e.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=i.cssText,t.appendChild(s)}})(t,this.constructor.elementStyles),t}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:f).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:f;this._$Em=s;const r=o.fromAttribute(e,t.type);this[s]=r??this._$Ej?.get(s)??r,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const r=this.constructor;if(!1===s&&(o=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??y)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(r._$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},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??e??this[t]),!0!==o||void 0!==r)||(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){}};$.elementStyles=[],$.shadowRootOptions={mode:"open"},$[b("elementProperties")]=new Map,$[b("finalized")]=new Map,_?.({ReactiveElement:$}),(g.reactiveElementVersions??=[]).push("2.1.2");const x=globalThis,A=t=>t,C=x.trustedTypes,S=C?C.createPolicy("lit-html",{createHTML:t=>t}):void 0,k="$lit$",H=`lit$${Math.random().toFixed(9).slice(2)}$`,V="?"+H,E=`<${V}>`,L=document,M=()=>L.createComment(""),D=t=>null===t||"object"!=typeof t&&"function"!=typeof t,P=Array.isArray,T="[ \t\n\f\r]",O=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,U=/>/g,q=RegExp(`>|${T}(?:([^\\s"'>=/]+)(${T}*=${T}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),z=/'/g,R=/"/g,j=/^(?:script|style|textarea|title)$/i,I=(t=>(e,...i)=>({_$litType$:t,strings:e,values:i}))(1),B=Symbol.for("lit-noChange"),Z=Symbol.for("lit-nothing"),W=new WeakMap,F=L.createTreeWalker(L,129);function J(t,e){if(!P(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const K=(t,e)=>{const i=t.length-1,s=[];let o,r=2===e?"":3===e?"":"",a=O;for(let e=0;e"===c[0]?(a=o??O,l=-1):void 0===c[1]?l=-2:(l=a.lastIndex-c[2].length,n=c[1],a=void 0===c[3]?q:'"'===c[3]?R:z):a===R||a===z?a=q:a===N||a===U?a=O:(a=q,o=void 0);const d=a===q&&t[e+1].startsWith("/>")?" ":"";r+=a===O?i+E:l>=0?(s.push(n),i.slice(0,l)+k+i.slice(l)+H+d):i+H+(-2===l?e:d)}return[J(t,r+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class G{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,r=0;const a=t.length-1,n=this.parts,[c,l]=K(t,e);if(this.el=G.createElement(c,i),F.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=F.nextNode())&&n.length0){s.textContent=C?C.emptyScript:"";for(let i=0;iP(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!==Z&&D(this._$AH)?this._$AA.nextSibling.data=t:this.T(L.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=G.createElement(J(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=W.get(t.strings);return void 0===e&&W.set(t.strings,e=new G(t)),e}k(t){P(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 X(this.O(M()),this.O(M()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=Z}_$AI(t,e=this,i,s){const o=this.strings;let r=!1;if(void 0===o)t=Y(this,t,e,0),r=!D(t)||t!==this._$AH&&t!==B,r&&(this._$AH=t);else{const s=t;let a,n;for(t=o[0],a=0;a{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new X(e.insertBefore(M(),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 B}};nt._$litElement$=!0,nt.finalized=!0,at.litElementHydrateSupport?.({LitElement:nt});const ct=at.litElementPolyfillSupport;ct?.({LitElement:nt}),(at.litElementVersions??=[]).push("4.2.2");const lt=t=>(e,i)=>{void 0!==i?i.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)},ht={attribute:!0,type:String,converter:f,reflect:!1,hasChanged:y},dt=(t=ht,e,i)=>{const{kind:s,metadata:o}=i;let r=globalThis.litPropertyMetadata.get(o);if(void 0===r&&globalThis.litPropertyMetadata.set(o,r=new Map),"setter"===s&&((t=Object.create(t)).wrapped=!0),r.set(i.name,t),"accessor"===s){const{name:s}=i;return{set(i){const o=e.get.call(this);e.set.call(this,i),this.requestUpdate(s,o,t,!0,i)},init(e){return void 0!==e&&this.C(s,void 0,t,e),e}}}if("setter"===s){const{name:s}=i;return function(i){const o=this[s];e.call(this,i),this.requestUpdate(s,o,t,!0,i)}}throw Error("Unsupported decorator location: "+s)};function pt(t){return(e,i)=>"object"==typeof i?dt(t,e,i):((t,e,i)=>{const s=e.hasOwnProperty(i);return e.constructor.createProperty(i,t),s?Object.getOwnPropertyDescriptor(e,i):void 0})(t,e,i)}function ut(t){return pt({...t,state:!0,attribute:!1})}const gt=(t,e,i)=>(i.configurable=!0,i.enumerable=!0,Reflect.decorate&&"object"!=typeof e&&Object.defineProperty(t,e,i),i);function mt(t,e){return(i,s,o)=>{const r=e=>e.renderRoot?.querySelector(t)??null;if(e){const{get:t,set:e}="object"==typeof s?i:o??(()=>{const t=Symbol();return{get(){return this[t]},set(e){this[t]=e}}})();return gt(i,s,{get(){let i=t.call(this);return void 0===i&&(i=r(this),(null!==i||this.hasUpdated)&&e.call(this,i)),i}})}return gt(i,s,{get(){return r(this)}})}}const vt=["ha-form","ha-icon","ha-icon-button","ha-selector","ha-textfield","ha-icon-picker","ha-icon-button","ha-entity-picker","ha-select","ha-dialog","ha-sortable","ha-svg-icon","ha-alert","ha-button","ha-color-picker","ha-badge","ha-sankey-chart","mwc-button"],_t="switch_manager",bt=["single","restart","queued","parallel"];function ft(t){return t?`/${_t}/${t}`:`/${_t}`}function yt(t){return`/assets/${_t}/${t}`}function wt(t){return`${_t}/${t}`}function $t(t){history.pushState(null,"",t);const e=new Event("location-changed");window.dispatchEvent(e)}function xt(t,e,i){const s=new CustomEvent(e,{bubbles:!0,composed:!0,detail:i});t.dispatchEvent(s)}function At(t,e){xt(t,"hass-notification",{message:e})}function Ct(t,e,i,s){xt(t,"show-dialog",{dialogTag:e,dialogImport:i,dialogParams:s})}let St=class extends nt{constructor(){super(...arguments),this.heading="",this._onKeydown=t=>{"Escape"===t.key&&this._close()}}connectedCallback(){super.connectedCallback(),document.addEventListener("keydown",this._onKeydown)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("keydown",this._onKeydown)}render(){return I` +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise, SuppressedError, Symbol, Iterator */ + + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}; + +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t$3=globalThis,e$6=t$3.ShadowRoot&&(void 0===t$3.ShadyCSS||t$3.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$4=new WeakMap;let n$3 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$6&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$4.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$4.set(s,t));}return t}toString(){return this.cssText}};const r$4=t=>new n$3("string"==typeof t?t:t+"",void 0,s$2),i$4=(t,...e)=>{const o=1===t.length?t[0]:e.reduce((e,s,o)=>e+(t=>{if(true===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.")})(s)+t[o+1],t[0]);return new n$3(o,t,s$2)},S$1=(s,o)=>{if(e$6)s.adoptedStyleSheets=o.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of o){const o=document.createElement("style"),n=t$3.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$6?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$4(e)})(t):t; + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const{is:i$3,defineProperty:e$5,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$3,getOwnPropertySymbols:o$3,getPrototypeOf:n$2}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){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}},f$1=(t,s)=>!i$3(t,s),b$1={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y 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,s=b$1){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$5(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$2(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$3(t),...o$3(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,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,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach(t=>t.hostConnected?.());}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.());}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i,e=false,h){if(void 0!==t){const r=this.constructor;if(false===e&&(h=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;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,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.2"); + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t$2=globalThis,i$2=t=>t,s$1=t$2.trustedTypes,e$4=s$1?s$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,h="$lit$",o$2=`lit$${Math.random().toFixed(9).slice(2)}$`,n$1="?"+o$2,r$2=`<${n$1}>`,l=document,c=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e$4?e$4.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"":3===i?"":"",c=v;for(let i=0;i"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r$2:d>=0?(e.push(a),s.slice(0,d)+h+s.slice(d)+o$2+x):s+o$2+(-2===d?i:x);}return [V(t,l+(t[s]||"")+(2===i?"":3===i?"":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length0){r.textContent=s$1?s$1.emptyScript:"";for(let s=0;s2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new k(i.insertBefore(c(),t),t,void 0,s??{});}return h._$AI(t),h}; + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const s=globalThis;let i$1 = class i extends y$1{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 r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=D(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return E}};i$1._$litElement$=true,i$1["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i$1});const o$1=s.litElementPolyfillSupport;o$1?.({LitElement:i$1});(s.litElementVersions??=[]).push("4.2.2"); + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t$1=t=>(e,o)=>{ void 0!==o?o.addInitializer(()=>{customElements.define(t,e);}):customElements.define(t,e);}; + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const o={attribute:true,type:String,converter:u$1,reflect:false,hasChanged:f$1},r$1=(t=o,e,r)=>{const{kind:n,metadata:i}=r;let s=globalThis.litPropertyMetadata.get(i);if(void 0===s&&globalThis.litPropertyMetadata.set(i,s=new Map),"setter"===n&&((t=Object.create(t)).wrapped=true),s.set(r.name,t),"accessor"===n){const{name:o}=r;return {set(r){const n=e.get.call(this);e.set.call(this,r),this.requestUpdate(o,n,t,true,r);},init(e){return void 0!==e&&this.C(o,void 0,t,e),e}}}if("setter"===n){const{name:o}=r;return function(r){const n=this[o];e.call(this,r),this.requestUpdate(o,n,t,true,r);}}throw Error("Unsupported decorator location: "+n)};function n(t){return (e,o)=>"object"==typeof o?r$1(t,e,o):((t,e,o)=>{const r=e.hasOwnProperty(o);return e.constructor.createProperty(o,t),r?Object.getOwnPropertyDescriptor(e,o):void 0})(t,e,o)} + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */function r(r){return n({...r,state:true,attribute:false})} + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const e$3=(e,t,c)=>(c.configurable=true,c.enumerable=true,Reflect.decorate&&"object"!=typeof t&&Object.defineProperty(e,t,c),c); + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */function e$2(e,r){return (n,s,i)=>{const o=t=>t.renderRoot?.querySelector(e)??null;if(r){const{get:e,set:r}="object"==typeof s?n:i??(()=>{const t=Symbol();return {get(){return this[t]},set(e){this[t]=e;}}})();return e$3(n,s,{get(){let t=e.call(this);return void 0===t&&(t=o(this),(null!==t||this.hasUpdated)&&r.call(this,t)),t}})}return e$3(n,s,{get(){return o(this)}})}} + +/** + * Utility function to asynchronously load Home Assistant form components + * if they are not already registered in the custom elements registry. + * + * @param components - Optional array of component names to load. If not provided, defaults to a predefined list. + * @returns Promise that resolves when all required components are loaded + */ +// Define the default list of required components +const DEFAULT_HA_COMPONENTS = [ + 'ha-form', + 'ha-icon', + 'ha-icon-button', + 'ha-selector', + 'ha-textfield', + 'ha-icon-picker', + 'ha-icon-button', + 'ha-entity-picker', + 'ha-select', + 'ha-dialog', + 'ha-sortable', + 'ha-svg-icon', + 'ha-alert', + 'ha-button', + 'ha-color-picker', + 'ha-badge', + 'ha-sankey-chart', + 'mwc-button' +]; +const loadHaComponents = async (components) => { + // Use provided components or default to the predefined list + const componentsToLoad = components || DEFAULT_HA_COMPONENTS; + try { + // Check if all required custom elements are already defined + if (componentsToLoad.every(component => customElements.get(component))) { + return; + } + // Wait for the partial-panel-resolver to be defined with timeout + await Promise.race([ + customElements.whenDefined('partial-panel-resolver'), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for partial-panel-resolver')), 10000)) + ]); + // Create and configure the panel resolver with proper typing + const ppr = document.createElement('partial-panel-resolver'); + // Check if the element was created successfully + if (!ppr) { + throw new Error('Failed to create partial-panel-resolver element'); + } + ppr.hass = { + panels: [ + { + url_path: 'tmp', + component_name: 'config', + }, + ], + }; + // Check if _updateRoutes method exists + if (typeof ppr._updateRoutes !== 'function') { + throw new Error('partial-panel-resolver does not have _updateRoutes method'); + } + ppr._updateRoutes(); + // Check if routes were created + if (!ppr.routerOptions?.routes?.tmp?.load) { + throw new Error('Failed to create tmp route in partial-panel-resolver'); + } + // Load the temporary route with timeout + await Promise.race([ + ppr.routerOptions.routes.tmp.load(), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout loading tmp route')), 10000)) + ]); + // Wait for the config panel to be defined with timeout + await Promise.race([ + customElements.whenDefined('ha-panel-config'), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for ha-panel-config')), 10000)) + ]); + // Create the config panel and load automation components with proper typing + const cpr = document.createElement('ha-panel-config'); + // Check if the element was created successfully + if (!cpr) { + throw new Error('Failed to create ha-panel-config element'); + } + // Check if automation route exists + if (!cpr.routerOptions?.routes?.automation?.load) { + throw new Error('ha-panel-config does not have automation route'); + } + // Load automation components with timeout + await Promise.race([ + cpr.routerOptions.routes.automation.load(), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout loading automation components')), 10000)) + ]); + // Final verification that components were loaded + const missingComponents = componentsToLoad.filter(component => !customElements.get(component)); + if (missingComponents.length > 0) { + throw new Error(`Failed to load components: ${missingComponents.join(', ')}`); + } + } + catch (error) { + // Log the error but don't throw to prevent breaking the card + console.error('Error loading Home Assistant form components:', error); + // Attempt to use a fallback approach if available + try { + // Try to load components directly from Home Assistant frontend if available + if (window.customElements && window.customElements.get('home-assistant')) { + console.log('Attempting fallback loading method for HA components'); + // This is a fallback approach that might work in some environments + const event = new CustomEvent('ha-request-load-components', { + detail: { + components: componentsToLoad + }, + bubbles: true, + composed: true + }); + document.dispatchEvent(event); + } + } + catch (fallbackError) { + console.error('Fallback loading method failed:', fallbackError); + } + } +}; + +const DOMAIN = "switch_manager"; +const SCRIPT_MODES = ["single", "restart", "queued", "parallel"]; +function navigateTo(path) { + return path ? `/${DOMAIN}/${path}` : `/${DOMAIN}`; +} +function assetUrl(file) { + return `/assets/${DOMAIN}/${file}`; +} +function wsType(type) { + return `${DOMAIN}/${type}`; +} +function navigate(path) { + history.pushState(null, "", path); + const event = new Event("location-changed"); + window.dispatchEvent(event); +} +function createEmptyConfig(blueprint) { + const config = { + id: null, + name: "New Switch", + enabled: true, + identifier: "", + blueprint: blueprint, + valid_blueprint: true, + custom_image: "", + buttons: [], + is_mismatch: false, + rotate: 0, + }; + blueprint.buttons.forEach((btn, btnIdx) => { + config.buttons[btnIdx] = { actions: [] }; + btn.actions.forEach((_action, actIdx) => { + config.buttons[btnIdx].actions[actIdx] = { + mode: SCRIPT_MODES[0], + sequence: [], + }; + }); + }); + return config; +} +function fireEvent(node, type, detail) { + const event = new CustomEvent(type, { + bubbles: true, + composed: true, + detail, + }); + node.dispatchEvent(event); +} +function showToast(node, message) { + fireEvent(node, "hass-notification", { message }); +} +function showDialog(node, dialogTag, dialogImport, dialogParams) { + fireEvent(node, "show-dialog", { + dialogTag, + dialogImport, + dialogParams, + }); +} + +const mdiClose = "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"; +let SwitchManagerDialog = class SwitchManagerDialog extends i$1 { + constructor() { + super(...arguments); + this.heading = ""; + this._onKeydown = (e) => { + if (e.key === "Escape") + this._close(); + }; + } + connectedCallback() { + super.connectedCallback(); + document.addEventListener("keydown", this._onKeydown); + } + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener("keydown", this._onKeydown); + } + render() { + return b `
${this.heading} @@ -13,7 +298,15 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro
- `}_stop(t){t.stopPropagation()}_close(){this.dispatchEvent(new CustomEvent("closed"))}static{this.styles=a` + `; + } + _stop(e) { + e.stopPropagation(); + } + _close() { + this.dispatchEvent(new CustomEvent("closed")); + } + static { this.styles = i$4 ` .backdrop { position: fixed; inset: 0; @@ -72,39 +365,99 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro ::slotted(button.destructive) { color: var(--error-color, #db4437); } - `}};t([pt()],St.prototype,"heading",void 0),St=t([lt("switch-manager-dialog")],St);let kt=class extends nt{constructor(){super(...arguments),this._blueprints=[],this._filter="",this._protocol=""}showDialog(t){this._params=t,this.hass=this.parentElement?.hass||document.querySelector("home-assistant")?.hass,this._loadBlueprints()}closeDialog(){this._params=void 0,this._blueprints=[],this._filter="",this._protocol=""}_protoKey(t){return"event_entity"===t.event_type?(t.service||"").toLowerCase():(t.event_type||"").split(/[._]/)[0]}async _loadBlueprints(){const t=await this.hass.callWS({type:wt("blueprints")});this._blueprints=Object.values(t.blueprints)}render(){if(!this._params)return I``;const t=new Map;for(const e of this._blueprints){const i=this._protoKey(e);i&&t.set(i,(t.get(i)||0)+1)}const e=[...t.entries()].sort((t,e)=>e[1]-t[1]),i=this._blueprints.filter(t=>{const e=!this._filter||t.name.toLowerCase().includes(this._filter.toLowerCase())||t.service.toLowerCase().includes(this._filter.toLowerCase()),i=!this._protocol||this._protoKey(t)===this._protocol;return e&&i});return I` + `; } +}; +__decorate([ + n() +], SwitchManagerDialog.prototype, "heading", void 0); +SwitchManagerDialog = __decorate([ + t$1("switch-manager-dialog") +], SwitchManagerDialog); + +let SwitchManagerDialogBlueprintSelector = class SwitchManagerDialogBlueprintSelector extends i$1 { + constructor() { + super(...arguments); + this._blueprints = []; + this._filter = ""; + this._protocol = ""; + } + showDialog(params) { + this._params = params; + this.hass = this.parentElement?.hass || document.querySelector("home-assistant")?.hass; + this._loadBlueprints(); + } + closeDialog() { + this._params = undefined; + this._blueprints = []; + this._filter = ""; + this._protocol = ""; + } + _protoKey(bp) { + // Event-entity blueprints share one generic event_type, so the integration + // they belong to (Matter, Hue, ...) comes from the service field instead. + if (bp.event_type === "event_entity") { + return (bp.service || "").toLowerCase(); + } + return (bp.event_type || "").split(/[._]/)[0]; + } + async _loadBlueprints() { + const res = await this.hass.callWS({ + type: wsType("blueprints"), + }); + this._blueprints = Object.values(res.blueprints); + } + render() { + if (!this._params) + return b ``; + const counts = new Map(); + for (const bp of this._blueprints) { + const k = this._protoKey(bp); + if (k) + counts.set(k, (counts.get(k) || 0) + 1); + } + const options = [...counts.entries()].sort((a, b) => b[1] - a[1]); + const filtered = this._blueprints.filter((bp) => { + const matchText = !this._filter || + bp.name.toLowerCase().includes(this._filter.toLowerCase()) || + bp.service.toLowerCase().includes(this._filter.toLowerCase()); + const matchProto = !this._protocol || this._protoKey(bp) === this._protocol; + return matchText && matchProto; + }); + return b ` this._filter=t.target.value} + @input=${(e) => (this._filter = e.target.value)} />
- ${i.map(t=>I` + ${filtered.map((bp) => b ` this._selectBlueprint(t)} + @click=${() => this._selectBlueprint(bp)} >
- ${t.has_image?I``:I`` + : b ``}
-
${t.name}
-
${t.service}
+
${bp.name}
+
${bp.service}
@@ -112,7 +465,13 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro
- `}_selectBlueprint(t){this.closeDialog(),$t(ft(`new/${t.id}`))}static{this.styles=a` + `; + } + _selectBlueprint(bp) { + this.closeDialog(); + navigate(navigateTo(`new/${bp.id}`)); + } + static { this.styles = i$4 ` .blueprints { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); @@ -167,7 +526,53 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro color: var(--primary-text-color); font: inherit; } - `}};t([ut()],kt.prototype,"_params",void 0),t([ut()],kt.prototype,"_blueprints",void 0),t([ut()],kt.prototype,"_filter",void 0),t([ut()],kt.prototype,"_protocol",void 0),kt=t([lt("switch-manager-dialog-blueprint-selector")],kt);var Ht=Object.freeze({__proto__:null,get SwitchManagerDialogBlueprintSelector(){return kt}});let Vt=class extends nt{constructor(){super(...arguments),this.path="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z",this.label="Menu",this.align="left",this._open=!1,this._onDocClick=t=>{t.composedPath().includes(this)||(this._open=!1)}}connectedCallback(){super.connectedCallback(),document.addEventListener("click",this._onDocClick)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("click",this._onDocClick)}render(){return I` + `; } +}; +__decorate([ + r() +], SwitchManagerDialogBlueprintSelector.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerDialogBlueprintSelector.prototype, "_blueprints", void 0); +__decorate([ + r() +], SwitchManagerDialogBlueprintSelector.prototype, "_filter", void 0); +__decorate([ + r() +], SwitchManagerDialogBlueprintSelector.prototype, "_protocol", void 0); +SwitchManagerDialogBlueprintSelector = __decorate([ + t$1("switch-manager-dialog-blueprint-selector") +], SwitchManagerDialogBlueprintSelector); + +var blueprintSelector = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogBlueprintSelector () { return SwitchManagerDialogBlueprintSelector; } +}); + +const mdiDotsVertical = "M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"; +let SwitchManagerMenu = class SwitchManagerMenu extends i$1 { + constructor() { + super(...arguments); + this.path = mdiDotsVertical; + this.label = "Menu"; + /** "left" aligns the surface to the trigger's right edge (default), "right" to the left edge */ + this.align = "left"; + this._open = false; + this._onDocClick = (e) => { + if (!e.composedPath().includes(this)) + this._open = false; + }; + } + connectedCallback() { + super.connectedCallback(); + document.addEventListener("click", this._onDocClick); + } + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener("click", this._onDocClick); + } + render() { + return b `
- `}_toggle(t){t.stopPropagation(),this._open=!this._open}_onSurfaceClick(t){const e=t.target.closest(".menu-item");e&&e.hasAttribute("disabled")?t.stopPropagation():this._open=!1}static{this.styles=a` + `; + } + _toggle(e) { + e.stopPropagation(); + this._open = !this._open; + } + _onSurfaceClick(e) { + // Close after an item is chosen, unless the clicked item is disabled. + const item = e.target.closest(".menu-item"); + if (item && item.hasAttribute("disabled")) { + e.stopPropagation(); + return; + } + this._open = false; + } + static { this.styles = i$4 ` :host { position: relative; display: inline-flex; @@ -231,9 +651,129 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro ::slotted(.menu-item.warning) { color: var(--error-color, #db4437); } - `}};t([pt()],Vt.prototype,"path",void 0),t([pt()],Vt.prototype,"label",void 0),t([pt()],Vt.prototype,"align",void 0),t([ut()],Vt.prototype,"_open",void 0),Vt=t([lt("switch-manager-menu")],Vt);let Et=class extends nt{constructor(){super(...arguments),this.narrow=!1,this._data=[],this._filter="",this._sortColumn="name",this._sortDirection="asc"}connectedCallback(){super.connectedCallback();try{const t=JSON.parse(localStorage.getItem("switchManagerSort")||"{}");t.column&&(this._sortColumn=t.column),t.direction&&(this._sortDirection=t.direction)}catch{}this._populateSwitches()}get _filteredSortedData(){let t=this._data;if(this._filter){const e=this._filter.toLowerCase();t=t.filter(t=>t.name.toLowerCase().includes(e)||t.service.toLowerCase().includes(e)||t.type.toLowerCase().includes(e))}const e=this._sortColumn,i="asc"===this._sortDirection?1:-1;return[...t].sort((t,s)=>{if("enabled"===e)return((t.enabled?1:0)-(s.enabled?1:0))*i;const o=String(t[e]||"").toLowerCase(),r=String(s[e]||"").toLowerCase();return o.localeCompare(r)*i})}_toggleSort(t){this._sortColumn===t?this._sortDirection="asc"===this._sortDirection?"desc":"asc":(this._sortColumn=t,this._sortDirection="asc"),localStorage.setItem("switchManagerSort",JSON.stringify({column:this._sortColumn,direction:this._sortDirection}))}_sortIcon(t){return this._sortColumn!==t?Z:I``}_getOverflowItems(t){return[{path:t.enabled?"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M9,9V15H15V9":"M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z",label:t.enabled?"Disable":"Enable",action:()=>this._toggleEnabled(t.switch_id,t.enabled)},{path:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z",label:"Duplicate",action:()=>this._duplicate(t.switch_id)},{path:"M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z",label:"Delete",action:()=>this._deleteConfirm(t),warning:!0}]}render(){const t=this._filteredSortedData;return I` + `; } +}; +__decorate([ + n() +], SwitchManagerMenu.prototype, "path", void 0); +__decorate([ + n() +], SwitchManagerMenu.prototype, "label", void 0); +__decorate([ + n() +], SwitchManagerMenu.prototype, "align", void 0); +__decorate([ + r() +], SwitchManagerMenu.prototype, "_open", void 0); +SwitchManagerMenu = __decorate([ + t$1("switch-manager-menu") +], SwitchManagerMenu); + +// MDI icon paths +const mdiPlus = "M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"; +const mdiStop = "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M9,9V15H15V9"; +const mdiPlay = "M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z"; +const mdiDelete$1 = "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"; +const mdiGestureTapButton = "M13 5C15.21 5 17 6.79 17 9C17 10.5 16.2 11.77 15 12.46V11.24C15.61 10.69 16 9.89 16 9C16 7.34 14.66 6 13 6S10 7.34 10 9C10 9.89 10.39 10.69 11 11.24V12.46C9.8 11.77 9 10.5 9 9C9 6.79 10.79 5 13 5M20 20.5C19.97 21.32 19.32 21.97 18.5 22H13C12.62 22 12.26 21.85 12 21.57L8 17.37L8.74 16.6C8.93 16.39 9.2 16.28 9.5 16.28H9.7L12 18V9C12 8.45 12.45 8 13 8S14 8.45 14 9V13.47L15.21 13.6L19.15 15.79C19.68 16.03 20 16.56 20 17.14V20.5M20 2H4C2.9 2 2 2.9 2 4V12C2 13.11 2.9 14 4 14H8V12L4 12L4 4H20L20 12H18V14H20V13.96L20.04 14C21.13 14 22 13.09 22 12V4C22 2.9 21.11 2 20 2Z"; +const mdiArrowUp = "M7,15L12,10L17,15H7Z"; +const mdiArrowDown = "M7,10L12,15L17,10H7Z"; +const mdiContentCopy = "M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"; +const mdiMagnify = "M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"; +const mdiCamera = "M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z"; +const mdiImageFrage = "M10,14.29L6.5,19H17.46L14.75,15.46L12.78,17.8L10,14.29M5,21V7H18.96V21H5M12,2.4L14.61,5.03H9.37L12,2.4M5,5.03C4.5,5.03 4,5.22 3.61,5.61C3.2,6 3,6.46 3,7V21C3,21.5 3.2,22 3.61,22.39C4,22.8 4.5,23 5,23H18.96C19.5,23 19.96,22.8 20.37,22.39C20.77,22 21,21.5 21,21V7C21,6.46 20.77,6 20.37,5.61C19.96,5.22 19.5,5.03 18.96,5.03H16L12,1L7.96,5.03H5Z"; +let SwitchManagerIndex = class SwitchManagerIndex extends i$1 { + constructor() { + super(...arguments); + this.narrow = false; + this._data = []; + this._filter = ""; + this._sortColumn = "name"; + this._sortDirection = "asc"; + } + connectedCallback() { + super.connectedCallback(); + try { + const saved = JSON.parse(localStorage.getItem("switchManagerSort") || "{}"); + if (saved.column) + this._sortColumn = saved.column; + if (saved.direction) + this._sortDirection = saved.direction; + } + catch { } + this._populateSwitches(); + } + get _filteredSortedData() { + let items = this._data; + if (this._filter) { + const f = this._filter.toLowerCase(); + items = items.filter((item) => item.name.toLowerCase().includes(f) || + item.service.toLowerCase().includes(f) || + item.type.toLowerCase().includes(f)); + } + const col = this._sortColumn; + const dir = this._sortDirection === "asc" ? 1 : -1; + return [...items].sort((a, b) => { + if (col === "enabled") { + return ((a.enabled ? 1 : 0) - (b.enabled ? 1 : 0)) * dir; + } + const aVal = String(a[col] || "").toLowerCase(); + const bVal = String(b[col] || "").toLowerCase(); + return aVal.localeCompare(bVal) * dir; + }); + } + _toggleSort(column) { + if (this._sortColumn === column) { + this._sortDirection = this._sortDirection === "asc" ? "desc" : "asc"; + } + else { + this._sortColumn = column; + this._sortDirection = "asc"; + } + localStorage.setItem("switchManagerSort", JSON.stringify({ + column: this._sortColumn, + direction: this._sortDirection, + })); + } + _sortIcon(column) { + if (this._sortColumn !== column) + return A; + return b ``; + } + _getOverflowItems(item) { + return [ + { + path: mdiCamera, + label: "Set custom Image", + action: () => this._uploadEncodedImage(item), + }, + { + path: mdiImageFrage, + label: "Use Default Image", + action: () => this._removeCustomImage(item), + }, + { + path: item.enabled ? mdiStop : mdiPlay, + label: item.enabled ? "Disable" : "Enable", + action: () => this._toggleEnabled(item.switch_id, item.enabled), + }, + { + path: mdiContentCopy, + label: "Duplicate", + action: () => this._duplicate(item.switch_id), + }, + { + path: mdiDelete$1, + label: "Delete", + action: () => this._deleteConfirm(item), + warning: true, + }, + ]; + } + render() { + const data = this._filteredSortedData; + return b `
@@ -259,67 +801,77 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro
this._toggleSort("name")} + @click=${() => this._toggleSort("name")} > Name ${this._sortIcon("name")}
- ${this.narrow?Z:I` + ${!this.narrow + ? b `
this._toggleSort("service")} + @click=${() => this._toggleSort("service")} > Service ${this._sortIcon("service")}
this._toggleSort("type")} + @click=${() => this._toggleSort("type")} > Type ${this._sortIcon("type")}
- `} + ` + : A}
- ${0===t.length?I`
- ${0===this._data.length?"No Switches configured":"No matches found"} -
`:t.map(t=>I` + ${data.length === 0 + ? b `
+ ${this._data.length === 0 + ? "No Switches configured" + : "No matches found"} +
` + : data.map((item) => b `
this._editSwitch(t.switch_id)} + @click=${() => this._editSwitch(item.switch_id)} >
- ${t.switch.valid_blueprint&&t.switch.blueprint.has_image?I``:I``} + ${this._getImageSrcData(item) + ? b `` + : b ``}
- ${t.error?I`${t.name} (${t.error})`:t.name} + ${item.error + ? b `${item.name} (${item.error})` + : item.name} - ${t.enabled?Z:I`Disabled`} + >` + : A}
- ${this.narrow?Z:I` + ${!this.narrow + ? b `
- ${t.service} + ${item.service}
-
${t.type}
- `} -
t.stopPropagation()}> +
${item.type}
+ ` + : A} +
e.stopPropagation()}> - ${this._getOverflowItems(t).map(t=>I` + ${this._getOverflowItems(item).map((mi) => b ` `)} @@ -335,12 +887,149 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro extended @click=${this._showBlueprintDialog} > - +
- `}_populateSwitches(){this.hass.callWS({type:wt("configs")}).then(t=>{const e=[];Object.values(t.configs).forEach(t=>{const i=t.valid_blueprint?t.blueprint:{id:t.blueprint,service:"",name:""};e.push({switch:t,blueprint_id:i.id,switch_id:t.id,error:t._error,enabled:t.enabled,name:t.name,service:i.service||"",type:i.name||"",actions:t.id})}),this._data=e})}_editSwitch(t){$t(ft(`edit/${t}`))}async _toggleEnabled(t,e){try{const i=await this.hass.callWS({type:wt("config/enabled"),enabled:!e,config_id:t});this._populateSwitches(),At(this,"Switch "+(i.enabled?"Enabled":"Disabled"))}catch(t){At(this,t.message)}}async _duplicate(t){try{const e=await this.hass.callWS({type:wt("config/duplicate"),config_id:t});At(this,"Switch Duplicated"),$t(ft(`edit/${e.config_id}`))}catch(t){At(this,t.message)}}async _deleteConfirm(t){Ct(this,"switch-manager-dialog-confirm",()=>Promise.resolve().then(function(){return qt}),{title:"Delete switch?",text:`${t.name} will be permanently deleted.`,confirmText:"Delete",dismissText:"Cancel",confirm:()=>this._delete(t.switch_id),confirmation:!0,destructive:!0})}async _delete(t){try{await this.hass.callWS({type:wt("config/delete"),config_id:t.toString()}),this._populateSwitches(),At(this,"Switch Deleted")}catch(t){At(this,t.message)}}_showBlueprintDialog(){Ct(this,"switch-manager-dialog-blueprint-selector",()=>Promise.resolve().then(function(){return Ht}),{})}static{this.styles=a` + `; + } + _populateSwitches() { + this.hass + .callWS({ type: wsType("configs") }) + .then((res) => { + const items = []; + Object.values(res.configs).forEach((sw) => { + const bp = sw.valid_blueprint + ? sw.blueprint + : { id: sw.blueprint, service: "", name: "" }; + items.push({ + switch: sw, + blueprint_id: bp.id, + switch_id: sw.id, + error: sw._error, + enabled: sw.enabled, + name: sw.name, + service: bp.service || "", + type: bp.name || "", + actions: sw.id, + }); + }); + this._data = items; + }); + } + _editSwitch(id) { + navigate(navigateTo(`edit/${id}`)); + } + _getImageSrcData(item) { + if (item.switch.custom_image !== "") { + return item.switch.custom_image; + } + if (item.switch.valid_blueprint && item.switch.blueprint.has_image) { + return assetUrl(item.blueprint_id + ".png"); + } + return null; + } + async _toggleEnabled(switchId, currentEnabled) { + try { + const res = await this.hass.callWS({ + type: wsType("config/enabled"), + enabled: !currentEnabled, + config_id: switchId, + }); + this._populateSwitches(); + showToast(this, `Switch ${res.enabled ? "Enabled" : "Disabled"}`); + } + catch (e) { + showToast(this, e.message); + } + } + async _uploadEncodedImage(item) { + try { + const imageInput = document.createElement("input"); + imageInput.type = "file"; + imageInput?.addEventListener("change", () => { + const file = imageInput.files?.[0]; + if (!file) + return; + showToast(this, `Selected file: ${file.name}`); + const reader = new FileReader(); + reader.onload = async () => { + item.switch.custom_image = reader.result; + try { + await this.hass.callWS({ + type: wsType("config/save"), + config: { ...item.switch, blueprint: item.switch.blueprint.id }, + fix_mismatch: true, + }); + this._populateSwitches(); + } + catch (e) { + showToast(this, e.message); + } + }; + reader.readAsDataURL(file); + }); + imageInput.click(); + } + catch (e) { + showToast(this, e.message); + } + } + async _removeCustomImage(item) { + try { + item.switch.custom_image = ""; + await this.hass.callWS({ + type: wsType("config/save"), + config: { ...item.switch, blueprint: item.switch.blueprint.id }, + fix_mismatch: true, + }); + } + catch (e) { + showToast(this, e.message); + } + } + async _duplicate(switchId) { + try { + const res = await this.hass.callWS({ + type: wsType("config/duplicate"), + config_id: switchId, + }); + showToast(this, "Switch Duplicated"); + navigate(navigateTo(`edit/${res.config_id}`)); + } + catch (e) { + showToast(this, e.message); + } + } + async _deleteConfirm(item) { + showDialog(this, "switch-manager-dialog-confirm", () => Promise.resolve().then(function () { return confirm; }), { + title: "Delete switch?", + text: `${item.name} will be permanently deleted.`, + confirmText: "Delete", + dismissText: "Cancel", + confirm: () => this._delete(item.switch_id), + confirmation: true, + destructive: true, + }); + } + async _delete(switchId) { + try { + await this.hass.callWS({ + type: wsType("config/delete"), + config_id: switchId.toString(), + }); + this._populateSwitches(); + showToast(this, "Switch Deleted"); + } + catch (e) { + showToast(this, e.message); + } + } + _showBlueprintDialog() { + showDialog(this, "switch-manager-dialog-blueprint-selector", () => Promise.resolve().then(function () { return blueprintSelector; }), {}); + } + static { this.styles = i$4 ` :host { display: block; } @@ -532,25 +1221,97 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro padding: 1.2em; z-index: 1; } - `}};t([pt({attribute:!1})],Et.prototype,"hass",void 0),t([pt({type:Boolean})],Et.prototype,"narrow",void 0),t([pt({attribute:!1})],Et.prototype,"panel",void 0),t([pt({attribute:!1})],Et.prototype,"route",void 0),t([ut()],Et.prototype,"_data",void 0),t([ut()],Et.prototype,"_filter",void 0),t([ut()],Et.prototype,"_sortColumn",void 0),t([ut()],Et.prototype,"_sortDirection",void 0),Et=t([lt("switch-manager-index")],Et);const Lt=1;class Mt{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}const Dt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends Mt{constructor(t){if(super(t),t.type!==Lt||"class"!==t.name||t.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return" "+Object.keys(t).filter(e=>t[e]).join(" ")+" "}update(t,[e]){if(void 0===this.st){this.st=new Set,void 0!==t.strings&&(this.nt=new Set(t.strings.join(" ").split(/\s/).filter(t=>""!==t)));for(const t in e)e[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(e)}const i=t.element.classList;for(const t of this.st)t in e||(i.remove(t),this.st.delete(t));for(const t in e){const s=!!e[t];s===this.st.has(t)||this.nt?.has(t)||(s?(i.add(t),this.st.add(t)):(i.remove(t),this.st.delete(t)))}return B}});let Pt=class extends nt{constructor(){super(...arguments),this.index=0}render(){return!this.blueprint_actions||this.blueprint_actions.length<=1?I``:I` + `; } +}; +__decorate([ + n({ attribute: false }) +], SwitchManagerIndex.prototype, "hass", void 0); +__decorate([ + n({ type: Boolean }) +], SwitchManagerIndex.prototype, "narrow", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerIndex.prototype, "panel", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerIndex.prototype, "route", void 0); +__decorate([ + r() +], SwitchManagerIndex.prototype, "_data", void 0); +__decorate([ + r() +], SwitchManagerIndex.prototype, "_filter", void 0); +__decorate([ + r() +], SwitchManagerIndex.prototype, "_sortColumn", void 0); +__decorate([ + r() +], SwitchManagerIndex.prototype, "_sortDirection", void 0); +SwitchManagerIndex = __decorate([ + t$1("switch-manager-index") +], SwitchManagerIndex); + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t={ATTRIBUTE:1},e$1=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}} + +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const e=e$1(class extends i{constructor(t$1){if(super(t$1),t$1.type!==t.ATTRIBUTE||"class"!==t$1.name||t$1.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return " "+Object.keys(t).filter(s=>t[s]).join(" ")+" "}update(s,[i]){if(void 0===this.st){this.st=new Set,void 0!==s.strings&&(this.nt=new Set(s.strings.join(" ").split(/\s/).filter(t=>""!==t)));for(const t in i)i[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.st)t in i||(r.remove(t),this.st.delete(t));for(const t in i){const s=!!i[t];s===this.st.has(t)||this.nt?.has(t)||(s?(r.add(t),this.st.add(t)):(r.remove(t),this.st.delete(t)));}return E}}); + +// Custom tab strip — replaces HA's legacy paper-tabs/paper-tab, which current HA +// no longer auto-loads. Plain buttons + CSS, so it never drifts with HA. +let SwitchManagerButtonActions = class SwitchManagerButtonActions extends i$1 { + constructor() { + super(...arguments); + this.index = 0; + } + render() { + if (!this.blueprint_actions || this.blueprint_actions.length <= 1) { + return b ``; + } + return b `
- ${this.blueprint_actions.map((t,e)=>{const i=this.config_actions?.[e]?.sequence?.length||0;return I` + ${this.blueprint_actions.map((action, idx) => { + const seqLen = this.config_actions?.[idx]?.sequence?.length || 0; + return b ` - `})} + `; + })}
- `}flash(t){const e=this.tabs?.querySelector(`[index="${t}"]`);e&&(e.removeAttribute("feedback"),e.setAttribute("feedback",""),setTimeout(()=>e.removeAttribute("feedback"),1e3))}_select(t){this.dispatchEvent(new CustomEvent("changed",{detail:{index:t}}))}static{this.styles=a` + `; + } + flash(index) { + const tab = this.tabs?.querySelector(`[index="${index}"]`); + if (tab) { + tab.removeAttribute("feedback"); + tab.setAttribute("feedback", ""); + setTimeout(() => tab.removeAttribute("feedback"), 1000); + } + } + _select(idx) { + this.dispatchEvent(new CustomEvent("changed", { detail: { index: idx } })); + } + static { this.styles = i$4 ` @keyframes feedback { to { border-color: #00e903; @@ -609,62 +1370,111 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro .init-icon { --mdc-icon-size: 18px; } - `}};t([pt({attribute:!1})],Pt.prototype,"hass",void 0),t([pt({attribute:!1})],Pt.prototype,"blueprint_actions",void 0),t([pt({attribute:!1})],Pt.prototype,"config_actions",void 0),t([pt({type:Number,reflect:!0})],Pt.prototype,"index",void 0),t([mt(".tabs",!0)],Pt.prototype,"tabs",void 0),Pt=t([lt("switch-manager-button-actions")],Pt);let Tt=class extends nt{constructor(){super(...arguments),this.narrow=!1,this.disabled=!1,this.sequence=[],this.button_index=0,this.action_index=0,this.is_new=!0,this._is_yaml=!1,this._dirty=!1,this._debug=!1,this._block_save=!1}render(){if(!this.config)return I``;const t=!!this.config._error;return I` + `; } +}; +__decorate([ + n({ attribute: false }) +], SwitchManagerButtonActions.prototype, "hass", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerButtonActions.prototype, "blueprint_actions", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerButtonActions.prototype, "config_actions", void 0); +__decorate([ + n({ type: Number, reflect: true }) +], SwitchManagerButtonActions.prototype, "index", void 0); +__decorate([ + e$2(".tabs", true) +], SwitchManagerButtonActions.prototype, "tabs", void 0); +SwitchManagerButtonActions = __decorate([ + t$1("switch-manager-button-actions") +], SwitchManagerButtonActions); + +// MDI icon paths +const mdiArrowLeft = "M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"; +const mdiIdentifier = "M10 7V9H9V15H10V17H6V15H7V9H6V7H10M16 7C17.11 7 18 7.9 18 9V15C18 16.11 17.11 17 16 17H12V7M16 9H14V15H16V9Z"; +const mdiRename = "M18,17H10.5L12.5,15H18M6,17V14.5L13.88,6.65C14.07,6.45 14.39,6.45 14.59,6.65L16.35,8.41C16.55,8.61 16.55,8.92 16.35,9.12L8.47,17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"; +const mdiRotate = "M16.89,15.5L18.31,16.89C19.21,15.73 19.76,14.39 19.93,13H17.91C17.77,13.87 17.43,14.72 16.89,15.5M13,17.9V19.92C14.39,19.75 15.74,19.21 16.9,18.31L15.46,16.87C14.71,17.41 13.87,17.76 13,17.9M19.93,11C19.76,9.61 19.21,8.27 18.31,7.11L16.89,8.53C17.43,9.28 17.77,10.13 17.91,11M15.55,5.55L11,1V4.07C7.06,4.56 4,7.92 4,12C4,16.08 7.05,19.44 11,19.93V17.91C8.16,17.43 6,14.97 6,12C6,9.03 8.16,6.57 11,6.09V10L15.55,5.55Z"; +const mdiVariables = "M8,3A2,2 0 0,0 6,5V9A2,2 0 0,1 4,11H3V13H4A2,2 0 0,1 6,15V19A2,2 0 0,0 8,21H10V19H8V14A2,2 0 0,0 6,12A2,2 0 0,0 8,10V5H10V3M16,3A2,2 0 0,1 18,5V9A2,2 0 0,0 20,11H21V13H20A2,2 0 0,0 18,15V19A2,2 0 0,1 16,21H14V19H16V14A2,2 0 0,1 18,12A2,2 0 0,1 16,10V5H14V3H16Z"; +const mdiCopy = "M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"; +const mdiDelete = "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"; +const mdiSave = "M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z"; +const mdiSwitchIcon = "M13 5C15.21 5 17 6.79 17 9C17 10.5 16.2 11.77 15 12.46V11.24C15.61 10.69 16 9.89 16 9C16 7.34 14.66 6 13 6S10 7.34 10 9C10 9.89 10.39 10.69 11 11.24V12.46C9.8 11.77 9 10.5 9 9C9 6.79 10.79 5 13 5M20 20.5C19.97 21.32 19.32 21.97 18.5 22H13C12.62 22 12.26 21.85 12 21.57L8 17.37L8.74 16.6C8.93 16.39 9.2 16.28 9.5 16.28H9.7L12 18V9C12 8.45 12.45 8 13 8S14 8.45 14 9V13.47L15.21 13.6L19.15 15.79C19.68 16.03 20 16.56 20 17.14V20.5M20 2H4C2.9 2 2 2.9 2 4V12C2 13.11 2.9 14 4 14H8V12L4 12L4 4H20L20 12H18V14H20V13.96L20.04 14C21.13 14 22 13.09 22 12V4C22 2.9 21.11 2 20 2Z"; +let SwitchManagerSwitchEditor = class SwitchManagerSwitchEditor extends i$1 { + constructor() { + super(...arguments); + this.narrow = false; + this.disabled = false; + this.sequence = []; + this.button_index = 0; + this.action_index = 0; + this.is_new = true; + this._is_yaml = false; + this._dirty = false; + this._debug = false; + this._block_save = false; + } + render() { + if (!this.config) + return b ``; + const hasError = !!this.config._error; + return b `
Switch Manager - ${this.config?.name}
- ${t?Z:I`

${this.blueprint?.service} / ${this.blueprint?.name}

`} +
+ ${this.config.custom_image === "" ? A : b ``} + ${hasError ? A : b `

${this.blueprint?.service} / ${this.blueprint?.name}

`} +
-
- ${!this.blueprint||this.blueprint?.has_image?I``:I``} -
+
+ ${!this.blueprint || this.blueprint?.has_image + ? b `` + : b ``} +
- ${t?Z:I` + ${hasError ? A : b `
- ${this._errors?I` + ${this._errors + ? b ` ${this._errors} - ${this.config.is_mismatch?I``:""} + ${this.config.is_mismatch + ? b `` + : ""} - `:""} - ${this.config&&!this.config.enabled?I` + ` + : ""} + ${this.config && !this.config.enabled + ? b ` Switch is disabled - `:""} - ${t?Z:I` + ` + : ""} + ${hasError ? A : b `

@@ -723,22 +1544,34 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro .hass=${this.hass} .value=${this.config?.buttons[this.button_index]?.actions[this.action_index]?.mode} label="Mode" - .selector=${{select:{mode:"dropdown",options:bt.map(t=>({label:t.charAt(0).toUpperCase()+t.slice(1),value:t}))}}} + .selector=${{ + select: { + mode: "dropdown", + options: SCRIPT_MODES.map((m) => ({ + label: m.charAt(0).toUpperCase() + m.slice(1), + value: m, + })), + }, + }} @value-changed=${this._modeValueChanged} >

- ${this._is_yaml?I``:I`` + : b ` - ${t?Z:I` + ${hasError ? A : b `
- +
`}
- `}connectedCallback(){super.connectedCallback(),this._loadConfig(),this._startListeners()}disconnectedCallback(){this._killListener("_reloadListener"),this._killListener("_subscribedMonitor"),super.disconnectedCallback()}_killListener(t){return!!this[t]&&(this[t](),this[t]=void 0,!0)}async _startListeners(){this._reloadListener=await this.hass.connection.subscribeEvents(t=>{"switch_manager"===t.data.domain&&"reload"===t.data.service&&this._loadConfig()},"call_service")}_loadConfig(){"id"in this.params?(this.is_new=!1,this.hass.callWS({type:wt("configs"),config_id:this.params.id}).then(t=>this._setConfig(t.config))):(this.is_new=!0,this._dirty=!0,"blueprint"in this.params&&this._loadBlueprint(this.params.blueprint).then(t=>{this._setConfig(function(t){const e={id:null,name:"New Switch",enabled:!0,identifier:"",blueprint:t,valid_blueprint:!0,buttons:[],is_mismatch:!1,rotate:0};return t.buttons.forEach((t,i)=>{e.buttons[i]={actions:[]},t.actions.forEach((t,s)=>{e.buttons[i].actions[s]={mode:bt[0],sequence:[]}})}),e}(t.blueprint)),this._showRenameDialog()}))}_loadBlueprint(t){return this.hass.callWS({type:wt("blueprints"),blueprint_id:t})}_setConfig(t){if(this.config=t,t._error)return this._errors=t._error,void(this._block_save=!0);this._setBlueprint(t.blueprint),this._updateSequence(),this._monitor()}async _monitor(){this.is_new||(this._killListener("_subscribedMonitor"),this._subscribedMonitor=await this.hass.connection.subscribeMessage(t=>{if("action_triggered"===t.event){if(!this.config?.identifier)return;if(t.button===this.button_index&&(this.blueprint?.buttons[this.button_index]?.actions.length??0)>1&&this.button_actions.flash(t.action),1===this.blueprint?.buttons?.length)return void At(this,"Button Pressed");const e=this.svg?.querySelector(`[index="${t.button}"]`);e&&(e.removeAttribute("pressed"),e.setAttribute("pressed",""),setTimeout(()=>e.removeAttribute("pressed"),1e3))}"incoming"!==t.event&&"action_triggered"!==t.event||!this._debug||console.log(t)},{type:wt("config/monitor"),config_id:this.config.id}))}_setBlueprint(t){this.blueprint=t,this.requestUpdate(),this._drawSVG()}async _drawSVG(){if(!this.blueprint?.has_image)return;await this.updateComplete;const t=this.svg;if(t){const e=t.cloneNode(!1);t.parentNode.replaceChild(e,t)}const e=new Image;e.src=yt(`${this.blueprint.id}.png`),e.onload=()=>{const t=this.svg;if(!t)return;t.setAttributeNS(null,"viewBox",`0 0 ${e.width} ${e.height}`);const i=document.createElementNS("http://www.w3.org/2000/svg","image");i.setAttributeNS(null,"x","0"),i.setAttributeNS(null,"y","0"),i.setAttributeNS(null,"width",e.width.toString()),i.setAttributeNS(null,"height",e.height.toString()),i.setAttributeNS("http://www.w3.org/1999/xlink","href",e.src),i.setAttributeNS(null,"visibility","visible"),t.prepend(i)},this.blueprint.buttons.length>1&&this.blueprint.buttons.forEach((t,e)=>{let i;if(t.x>-1&&t.y>-1&&t.width>0&&t.height>0)i=document.createElementNS("http://www.w3.org/2000/svg","rect"),i.setAttributeNS(null,"x",t.x.toString()),i.setAttributeNS(null,"y",t.y.toString()),i.setAttributeNS(null,"width",t.width.toString()),i.setAttributeNS(null,"height",t.height.toString());else if(t.x>-1&&t.y>-1&&t.width>0)i=document.createElementNS("http://www.w3.org/2000/svg","circle"),i.setAttributeNS(null,"cx",t.x.toString()),i.setAttributeNS(null,"cy",t.y.toString()),i.setAttributeNS(null,"r",t.width.toString());else{if(!t.d)return;i=document.createElementNS("http://www.w3.org/2000/svg","path"),i.setAttributeNS(null,"d",t.d.toString())}i.setAttribute("class","button"),i.setAttribute("index",e.toString()),this.button_index===e&&i.setAttribute("selected",""),this._buttonTotalSequence(this.config.buttons[e])||i.setAttribute("empty",""),i.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation(),this._setButtonIndex(parseInt(t.target.getAttribute("index")))}),this.svg?.append(i)})}_buttonTotalSequence(t){let e=0;return t.actions.forEach(t=>e+=t.sequence.length),e}_updateSequence(t){t&&(this.config.buttons[this.button_index].actions[this.action_index].sequence=t),this.sequence=[...this.config?.buttons[this.button_index]?.actions[this.action_index]?.sequence||[]]}_validate(){return this._errors=void 0,!!this.config?.identifier||(this._showIdentifierAutoDiscoveryDialog(!0),!1)}_save(){!this._block_save&&this._validate()&&this.config&&!this.config._error&&(this._block_save=!0,this._dirty=!1,this.hass.callWS({type:wt("config/save"),config:{...this.config,blueprint:this.config.blueprint.id}}).then(t=>{this.is_new&&(this.is_new=!1,this.config.id=t.config_id,$t(ft(`edit/${t.config_id}`)),this._monitor()),At(this,"Switch Saved")}).catch(t=>{At(this,t.message),this._errors=t.message,this._dirty=!0}).finally(()=>this._block_save=!1))}_backTapped(){$t(ft())}_actionChanged(t){this._setActionIndex(t.detail.index)}_setButtonIndex(t){t!==this.button_index&&(this.button_index=t,this.svg?.querySelector("[selected]")?.removeAttribute("selected"),this.svg?.querySelector(`[index="${t}"]`)?.setAttribute("selected",""),this._setActionIndex(0))}_setActionIndex(t){this.action_index=t,this._updateSequence(),this._is_yaml&&this._yamlEditor?.setValue(this.sequence)}_configSequenceChanged(t){let e=t.detail.value;!this._is_yaml||e&&Array.isArray(e)||(e=[]),this.requestUpdate("config"),this._updateSequence(e),this._errors=void 0,this._dirty=!0}_rotate(){this.config.rotate=this.config.rotate>=3?0:this.config.rotate+1,this.requestUpdate("config"),this._dirty=!0}_toggleDebug(){this._debug=!this._debug,At(this,"Debug "+(this._debug?"Enabled. View dev console":"Disabled"))}_toggleYaml(){this._is_yaml=!this._is_yaml,this.updateComplete.then(()=>{this._is_yaml&&this._yamlEditor?.setValue(this.sequence)})}_modeValueChanged(t){const e=this.config?.buttons[this.button_index]?.actions[this.action_index]?.mode;e!==t.detail.value&&(this.config.buttons[this.button_index].actions[this.action_index].mode=t.detail.value,this.requestUpdate("config"),this._dirty=!0)}_toggleEnabled(){this.config&&!this.is_new&&(this.config.enabled=!this.config.enabled,this.hass.callWS({type:wt("config/enabled"),enabled:this.config.enabled,config_id:this.config.id}),this.requestUpdate("config"))}_fixMismatch(){this.config&&this.hass.callWS({type:wt("config/save"),config:{...this.config,blueprint:this.config.blueprint.id},fix_mismatch:!0}).then(t=>{this._errors=void 0,this._block_save=!1,this.button_index=0,this.action_index=0,this._setConfig(t.config),At(this,"Mismatch Fixed")}).catch(t=>{this._errors=t.message,At(this,t.message)})}_deleteConfirm(){this.is_new||Ct(this,"switch-manager-dialog-confirm",()=>Promise.resolve().then(function(){return qt}),{title:"Delete switch?",text:`${this.config?.name} will be permanently deleted.`,confirmText:"Delete",dismissText:"Cancel",confirm:()=>{this.hass.callWS({type:wt("config/delete"),config_id:this.config.id.toString()}).then(()=>$t(ft()))},confirmation:!0,destructive:!0})}_showIdentifierAutoDiscoveryDialog(t=!1){Ct(this,"switch-manager-dialog-identifier-auto-discovery",()=>Promise.resolve().then(function(){return Rt}),{switch_id:this.config?.id,identifier:this.config?.identifier,blueprint:this.blueprint,update:e=>{this.config.identifier=e.identifier,this._dirty=!0,this.requestUpdate(),t&&e.identifier&&this._save()},onClose:()=>{}})}_showRenameDialog(){Ct(this,"switch-manager-dialog-rename-switch",()=>Promise.resolve().then(function(){return It}),{config:this.config,update:t=>{this.config.name=t.name,this._dirty=!0,this.requestUpdate()},onClose:()=>{this.is_new&&this._showIdentifierAutoDiscoveryDialog()}})}_showCopyFromDialog(){Ct(this,"switch-manager-dialog-copy-from",()=>Promise.resolve().then(function(){return Zt}),{blueprint_id:this.config?.blueprint?.id,current_switch_id:this.config?.id,update:t=>{this.config.buttons=t.buttons,!1!==t.variables&&(this.config.variables=t.variables),this._dirty=!0,this._updateSequence(),this._drawSVG()},onClose:()=>{}})}_showVariablesEditorDialog(){Ct(this,"switch-manager-dialog-variables-editor",()=>Promise.resolve().then(function(){return Ft}),{config:this.config,update:t=>{this.config.variables=t.variables,this._dirty=!0,this.requestUpdate()},onClose:()=>{}})}static{this.styles=a` + `; + } + connectedCallback() { + super.connectedCallback(); + this._loadConfig(); + this._startListeners(); + } + disconnectedCallback() { + this._killListener("_reloadListener"); + this._killListener("_subscribedMonitor"); + super.disconnectedCallback(); + } + _killListener(name) { + if (this[name]) { + this[name](); + this[name] = undefined; + return true; + } + return false; + } + async _startListeners() { + this._reloadListener = await this.hass.connection.subscribeEvents((event) => { + if (event.data.domain === "switch_manager" && + event.data.service === "reload") { + this._loadConfig(); + } + }, "call_service"); + } + _loadConfig() { + if ("id" in this.params) { + this.is_new = false; + this.hass + .callWS({ + type: wsType("configs"), + config_id: this.params.id, + }) + .then((res) => this._setConfig(res.config)); + } + else { + this.is_new = true; + this._dirty = true; + if ("blueprint" in this.params) { + this._loadBlueprint(this.params.blueprint).then((res) => { + this._setConfig(createEmptyConfig(res.blueprint)); + this._showRenameDialog(); + }); + } + } + } + _loadBlueprint(id) { + return this.hass.callWS({ + type: wsType("blueprints"), + blueprint_id: id, + }); + } + _setConfig(config) { + this.config = config; + if (config._error) { + this._errors = config._error; + this._block_save = true; + return; + } + this._setBlueprint(config.blueprint); + this._updateSequence(); + this._monitor(); + } + async _monitor() { + if (this.is_new) + return; + this._killListener("_subscribedMonitor"); + this._subscribedMonitor = await this.hass.connection.subscribeMessage((msg) => { + if (msg.event === "action_triggered") { + if (!this.config?.identifier) + return; + if (msg.button === this.button_index && + (this.blueprint?.buttons[this.button_index]?.actions.length ?? 0) > + 1) { + this.button_actions.flash(msg.action); + } + if (this.blueprint?.buttons?.length === 1) { + showToast(this, "Button Pressed"); + return; + } + const rect = this.svg?.querySelector(`[index="${msg.button}"]`); + if (rect) { + rect.removeAttribute("pressed"); + rect.setAttribute("pressed", ""); + setTimeout(() => rect.removeAttribute("pressed"), 1000); + } + } + if ((msg.event === "incoming" || msg.event === "action_triggered") && + this._debug) { + console.log(msg); + } + }, { type: wsType("config/monitor"), config_id: this.config.id }); + } + _setBlueprint(blueprint) { + this.blueprint = blueprint; + this.requestUpdate(); + this._drawSVG(); + } + async _drawSVG() { + if (!this.blueprint?.has_image) + return; + await this.updateComplete; + // Reset SVG + const oldSvg = this.svg; + if (oldSvg) { + const newSvg = oldSvg.cloneNode(false); + oldSvg.parentNode.replaceChild(newSvg, oldSvg); + } + const img = new Image(); + img.src = assetUrl(`${this.blueprint.id}.png`); + img.onload = () => { + const svg = this.svg; + if (!svg) + return; + svg.setAttributeNS(null, "viewBox", `0 0 ${img.width} ${img.height}`); + const svgImg = document.createElementNS("http://www.w3.org/2000/svg", "image"); + svgImg.setAttributeNS(null, "x", "0"); + svgImg.setAttributeNS(null, "y", "0"); + svgImg.setAttributeNS(null, "width", img.width.toString()); + svgImg.setAttributeNS(null, "height", img.height.toString()); + svgImg.setAttributeNS("http://www.w3.org/1999/xlink", "href", img.src); + svgImg.setAttributeNS(null, "visibility", "visible"); + svg.prepend(svgImg); + }; + if (this.blueprint.buttons.length > 1) { + this.blueprint.buttons.forEach((btn, idx) => { + let el; + if (btn.x > -1 && btn.y > -1 && btn.width > 0 && btn.height > 0) { + el = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + el.setAttributeNS(null, "x", btn.x.toString()); + el.setAttributeNS(null, "y", btn.y.toString()); + el.setAttributeNS(null, "width", btn.width.toString()); + el.setAttributeNS(null, "height", btn.height.toString()); + } + else if (btn.x > -1 && btn.y > -1 && btn.width > 0) { + el = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + el.setAttributeNS(null, "cx", btn.x.toString()); + el.setAttributeNS(null, "cy", btn.y.toString()); + el.setAttributeNS(null, "r", btn.width.toString()); + } + else if (btn.d) { + el = document.createElementNS("http://www.w3.org/2000/svg", "path"); + el.setAttributeNS(null, "d", btn.d.toString()); + } + else { + return; + } + el.setAttribute("class", "button"); + el.setAttribute("index", idx.toString()); + if (this.button_index === idx) + el.setAttribute("selected", ""); + if (!this._buttonTotalSequence(this.config.buttons[idx])) { + el.setAttribute("empty", ""); + } + el.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + this._setButtonIndex(parseInt(e.target.getAttribute("index"))); + }); + this.svg?.append(el); + }); + } + } + _buttonTotalSequence(button) { + let total = 0; + button.actions.forEach((a) => (total += a.sequence.length)); + return total; + } + _updateSequence(newSequence) { + if (newSequence) { + this.config.buttons[this.button_index].actions[this.action_index].sequence = newSequence; + } + this.sequence = [ + ...(this.config?.buttons[this.button_index]?.actions[this.action_index] + ?.sequence || []), + ]; + } + _validate() { + this._errors = undefined; + if (!this.config?.identifier) { + // Opened from the save flow: once an identifier is set, continue the + // interrupted save so it is actually persisted (fixes the popup + // re-appearing on every save when the stored identifier is empty). + this._showIdentifierAutoDiscoveryDialog(true); + return false; + } + return true; + } + _save() { + if (this._block_save || !this._validate() || !this.config || this.config._error) + return; + this._block_save = true; + this._dirty = false; + this.hass + .callWS({ + type: wsType("config/save"), + config: { + ...this.config, + blueprint: this.config.blueprint.id, + }, + }) + .then((res) => { + if (this.is_new) { + this.is_new = false; + this.config.id = res.config_id; + navigate(navigateTo(`edit/${res.config_id}`)); + this._monitor(); + } + showToast(this, "Switch Saved"); + }) + .catch((err) => { + showToast(this, err.message); + this._errors = err.message; + this._dirty = true; + }) + .finally(() => (this._block_save = false)); + } + _backTapped() { + navigate(navigateTo()); + } + _actionChanged(e) { + this._setActionIndex(e.detail.index); + } + _setButtonIndex(idx) { + if (idx !== this.button_index) { + this.button_index = idx; + this.svg?.querySelector("[selected]")?.removeAttribute("selected"); + this.svg?.querySelector(`[index="${idx}"]`)?.setAttribute("selected", ""); + this._setActionIndex(0); + } + } + _setActionIndex(idx) { + this.action_index = idx; + this._updateSequence(); + if (this._is_yaml) + this._yamlEditor?.setValue(this.sequence); + } + _configSequenceChanged(e) { + let value = e.detail.value; + if (this._is_yaml && (!value || !Array.isArray(value))) { + value = []; + } + this.requestUpdate("config"); + this._updateSequence(value); + this._errors = undefined; + this._dirty = true; + } + _rotate() { + this.config.rotate = this.config.rotate >= 3 ? 0 : this.config.rotate + 1; + this.requestUpdate("config"); + this._dirty = true; + } + _toggleDebug() { + this._debug = !this._debug; + showToast(this, `Debug ${this._debug ? "Enabled. View dev console" : "Disabled"}`); + } + _toggleYaml() { + this._is_yaml = !this._is_yaml; + this.updateComplete.then(() => { + if (this._is_yaml) + this._yamlEditor?.setValue(this.sequence); + }); + } + _modeValueChanged(e) { + const current = this.config?.buttons[this.button_index]?.actions[this.action_index]?.mode; + if (current !== e.detail.value) { + this.config.buttons[this.button_index].actions[this.action_index].mode = e.detail.value; + this.requestUpdate("config"); + this._dirty = true; + } + } + _toggleEnabled() { + if (!this.config || this.is_new) + return; + this.config.enabled = !this.config.enabled; + this.hass.callWS({ + type: wsType("config/enabled"), + enabled: this.config.enabled, + config_id: this.config.id, + }); + this.requestUpdate("config"); + } + _fixMismatch() { + if (!this.config) + return; + this.hass + .callWS({ + type: wsType("config/save"), + config: { ...this.config, blueprint: this.config.blueprint.id }, + fix_mismatch: true, + }) + .then((res) => { + // The backend reshapes the buttons to the blueprint, so adopt the config it + // returns instead of keeping the mismatched one around - otherwise the editor + // stays blank and the error comes back on the next load. + this._errors = undefined; + this._block_save = false; + this.button_index = 0; + this.action_index = 0; + this._setConfig(res.config); + showToast(this, "Mismatch Fixed"); + }) + .catch((err) => { + this._errors = err.message; + showToast(this, err.message); + }); + } + _deleteConfirm() { + if (this.is_new) + return; + showDialog(this, "switch-manager-dialog-confirm", () => Promise.resolve().then(function () { return confirm; }), { + title: "Delete switch?", + text: `${this.config?.name} will be permanently deleted.`, + confirmText: "Delete", + dismissText: "Cancel", + confirm: () => { + this.hass + .callWS({ type: wsType("config/delete"), config_id: this.config.id.toString() }) + .then(() => navigate(navigateTo())); + }, + confirmation: true, + destructive: true, + }); + } + _showIdentifierAutoDiscoveryDialog(continueSave = false) { + showDialog(this, "switch-manager-dialog-identifier-auto-discovery", () => Promise.resolve().then(function () { return identifierAutoDiscovery; }), { + switch_id: this.config?.id, + identifier: this.config?.identifier, + blueprint: this.blueprint, + update: (data) => { + this.config.identifier = data.identifier; + this._dirty = true; + this.requestUpdate(); + // When the dialog interrupted a save (missing identifier), persist + // immediately instead of forcing the user to press Save again. + if (continueSave && data.identifier) + this._save(); + }, + onClose: () => { }, + }); + } + _showRenameDialog() { + showDialog(this, "switch-manager-dialog-rename-switch", () => Promise.resolve().then(function () { return renameSwitch; }), { + config: this.config, + update: (data) => { + this.config.name = data.name; + this._dirty = true; + this.requestUpdate(); + }, + onClose: () => { + if (this.is_new) + this._showIdentifierAutoDiscoveryDialog(); + }, + }); + } + _showCopyFromDialog() { + showDialog(this, "switch-manager-dialog-copy-from", () => Promise.resolve().then(function () { return copyFrom; }), { + blueprint_id: this.config?.blueprint?.id, + current_switch_id: this.config?.id, + update: (data) => { + this.config.buttons = data.buttons; + if (data.variables !== false) + this.config.variables = data.variables; + this._dirty = true; + this._updateSequence(); + this._drawSVG(); + }, + onClose: () => { }, + }); + } + _showVariablesEditorDialog() { + showDialog(this, "switch-manager-dialog-variables-editor", () => Promise.resolve().then(function () { return variablesEditor; }), { + config: this.config, + update: (data) => { + this.config.variables = data.variables; + this._dirty = true; + this.requestUpdate(); + }, + onClose: () => { }, + }); + } + static { this.styles = i$4 ` @keyframes pressed { to { fill: #3ff17975; @@ -928,7 +2145,134 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro ha-fab.blocked { bottom: calc(-80px - env(safe-area-inset-bottom)); } - `}};t([pt({attribute:!1})],Tt.prototype,"hass",void 0),t([pt({type:Boolean})],Tt.prototype,"narrow",void 0),t([pt({attribute:!1})],Tt.prototype,"panel",void 0),t([pt({attribute:!1})],Tt.prototype,"route",void 0),t([pt({attribute:!1})],Tt.prototype,"params",void 0),t([pt({attribute:!1})],Tt.prototype,"blueprint",void 0),t([pt({attribute:!1})],Tt.prototype,"config",void 0),t([pt({type:Boolean})],Tt.prototype,"disabled",void 0),t([ut()],Tt.prototype,"_subscribedMonitor",void 0),t([ut()],Tt.prototype,"_reloadListener",void 0),t([ut()],Tt.prototype,"sequence",void 0),t([ut()],Tt.prototype,"button_index",void 0),t([ut()],Tt.prototype,"action_index",void 0),t([ut()],Tt.prototype,"is_new",void 0),t([ut()],Tt.prototype,"_is_yaml",void 0),t([ut()],Tt.prototype,"_dirty",void 0),t([ut()],Tt.prototype,"_debug",void 0),t([ut()],Tt.prototype,"_block_save",void 0),t([ut()],Tt.prototype,"_errors",void 0),t([mt("#switch-svg")],Tt.prototype,"svg",void 0),t([mt("switch-manager-button-actions")],Tt.prototype,"button_actions",void 0),t([mt("ha-yaml-editor")],Tt.prototype,"_yamlEditor",void 0),Tt=t([lt("switch-manager-switch-editor")],Tt);const Ot=["ha-automation-action","ha-service-control","ha-selector","ha-yaml-editor","ha-card","ha-fab","ha-alert","ha-svg-icon","ha-icon-button","ha-menu-button"];let Nt=class extends nt{constructor(){super(...arguments),this.narrow=!1,this._params={},this._componentsLoaded=!1}set route(t){this._route=t;const e=t.path.split("/");"new"===e[1]?this._params={action:"new",blueprint:e[2]}:"edit"===e[1]?this._params={action:"edit",id:e[2]}:this._params={}}get route(){return this._route}render(){return this._componentsLoaded?"action"in this._params?I` + + #custom-image { + object-fit: cover; + float: left; + padding: 12px; + vertical-align: middle; + width: 80px; + height: 80px; + border-radius: 50%; + margin-right: 16px; + } + `; } +}; +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "hass", void 0); +__decorate([ + n({ type: Boolean }) +], SwitchManagerSwitchEditor.prototype, "narrow", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "panel", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "route", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "params", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "blueprint", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerSwitchEditor.prototype, "config", void 0); +__decorate([ + n({ type: Boolean }) +], SwitchManagerSwitchEditor.prototype, "disabled", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_subscribedMonitor", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_reloadListener", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "sequence", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "button_index", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "action_index", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "is_new", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_is_yaml", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_dirty", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_debug", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_block_save", void 0); +__decorate([ + r() +], SwitchManagerSwitchEditor.prototype, "_errors", void 0); +__decorate([ + e$2("#switch-svg") +], SwitchManagerSwitchEditor.prototype, "svg", void 0); +__decorate([ + e$2("switch-manager-button-actions") +], SwitchManagerSwitchEditor.prototype, "button_actions", void 0); +__decorate([ + e$2("ha-yaml-editor") +], SwitchManagerSwitchEditor.prototype, "_yamlEditor", void 0); +SwitchManagerSwitchEditor = __decorate([ + t$1("switch-manager-switch-editor") +], SwitchManagerSwitchEditor); + +// HA runtime components the panel + action editor depend on. loadHaComponents() +// drives HA's config/automation route loader, which transitively registers the +// whole selector/service-control tree the editor needs (so they match the live +// hass instead of being frozen in our bundle). +const HA_COMPONENTS = [ + "ha-automation-action", + "ha-service-control", + "ha-selector", + "ha-yaml-editor", + "ha-card", + "ha-fab", + "ha-alert", + "ha-svg-icon", + "ha-icon-button", + "ha-menu-button", +]; +let SwitchManagerPanel = class SwitchManagerPanel extends i$1 { + constructor() { + super(...arguments); + this.narrow = false; + this._params = {}; + this._componentsLoaded = false; + } + set route(route) { + this._route = route; + const parts = route.path.split("/"); + if (parts[1] === "new") { + this._params = { action: "new", blueprint: parts[2] }; + } + else if (parts[1] === "edit") { + this._params = { action: "edit", id: parts[2] }; + } + else { + this._params = {}; + } + } + get route() { + return this._route; + } + render() { + if (!this._componentsLoaded) { + return b `
Loading…
`; + } + if ("action" in this._params) { + return b ` - `:I` + `; + } + return b ` - `:I`
Loading…
`}async firstUpdated(){this.hass.loadFragmentTranslation("config"),this.hass.loadBackendTranslation("title"),this.hass.loadBackendTranslation("device_automation"),this.hass.loadBackendTranslation("config"),this.hass.loadBackendTranslation("services"),this.hass.loadBackendTranslation("selector"),this.hass.loadBackendTranslation("entity_component"),this._applyTheme();try{await(async t=>{const e=t||vt;try{if(e.every(t=>customElements.get(t)))return;await Promise.race([customElements.whenDefined("partial-panel-resolver"),new Promise((t,e)=>setTimeout(()=>e(new Error("Timeout waiting for partial-panel-resolver")),1e4))]);const t=document.createElement("partial-panel-resolver");if(!t)throw new Error("Failed to create partial-panel-resolver element");if(t.hass={panels:[{url_path:"tmp",component_name:"config"}]},"function"!=typeof t._updateRoutes)throw new Error("partial-panel-resolver does not have _updateRoutes method");if(t._updateRoutes(),!t.routerOptions?.routes?.tmp?.load)throw new Error("Failed to create tmp route in partial-panel-resolver");await Promise.race([t.routerOptions.routes.tmp.load(),new Promise((t,e)=>setTimeout(()=>e(new Error("Timeout loading tmp route")),1e4))]),await Promise.race([customElements.whenDefined("ha-panel-config"),new Promise((t,e)=>setTimeout(()=>e(new Error("Timeout waiting for ha-panel-config")),1e4))]);const i=document.createElement("ha-panel-config");if(!i)throw new Error("Failed to create ha-panel-config element");if(!i.routerOptions?.routes?.automation?.load)throw new Error("ha-panel-config does not have automation route");await Promise.race([i.routerOptions.routes.automation.load(),new Promise((t,e)=>setTimeout(()=>e(new Error("Timeout loading automation components")),1e4))]);const s=e.filter(t=>!customElements.get(t));if(s.length>0)throw new Error(`Failed to load components: ${s.join(", ")}`)}catch(t){console.error("Error loading Home Assistant form components:",t);try{if(window.customElements&&window.customElements.get("home-assistant")){console.log("Attempting fallback loading method for HA components");const t=new CustomEvent("ha-request-load-components",{detail:{components:e},bubbles:!0,composed:!0});document.dispatchEvent(t)}}catch(t){console.error("Fallback loading method failed:",t)}}})(Ot)}catch(t){console.error("switch_manager: loadHaComponents failed",t)}this._componentsLoaded=!0}updated(t){super.updated(t);const e=t.get("hass");e&&e.themes!==this.hass.themes&&this._applyTheme()}provideHass(t){t.hass=this.hass}_applyTheme(){this.style.backgroundColor="var(--primary-background-color)",this.style.color="var(--primary-text-color)",this.style.fontFamily="var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif))"}static{this.styles=a` + `; + } + async firstUpdated() { + this.hass.loadFragmentTranslation("config"); + this.hass.loadBackendTranslation("title"); + this.hass.loadBackendTranslation("device_automation"); + this.hass.loadBackendTranslation("config"); + // Backend translations the selector/service-control tree needs to resolve + // entity states, services and selector labels (without these the Option + // dropdown and Targets stay empty). + this.hass.loadBackendTranslation("services"); + this.hass.loadBackendTranslation("selector"); + this.hass.loadBackendTranslation("entity_component"); + this._applyTheme(); + try { + await loadHaComponents(HA_COMPONENTS); + } + catch (err) { + // Non-fatal: log and continue so the panel still renders. + console.error("switch_manager: loadHaComponents failed", err); + } + this._componentsLoaded = true; + } + updated(changedProps) { + super.updated(changedProps); + const oldHass = changedProps.get("hass"); + if (oldHass && oldHass.themes !== this.hass.themes) { + this._applyTheme(); + } + } + provideHass(el) { + el.hass = this.hass; + } + _applyTheme() { + this.style.backgroundColor = "var(--primary-background-color)"; + this.style.color = "var(--primary-text-color)"; + this.style.fontFamily = + "var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif))"; + } + static { this.styles = i$4 ` :host { display: block; } @@ -951,30 +2336,76 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro padding: 24px; color: var(--secondary-text-color); } - `}};t([pt({attribute:!1})],Nt.prototype,"hass",void 0),t([pt({type:Boolean})],Nt.prototype,"narrow",void 0),t([pt({attribute:!1})],Nt.prototype,"panel",void 0),t([ut()],Nt.prototype,"_params",void 0),t([ut()],Nt.prototype,"_componentsLoaded",void 0),t([pt({attribute:!1})],Nt.prototype,"route",null),Nt=t([lt("switch-manager-panel")],Nt);let Ut=class extends nt{showDialog(t){this._params=t}closeDialog(){this._params=void 0}render(){return this._params?I` + `; } +}; +__decorate([ + n({ attribute: false }) +], SwitchManagerPanel.prototype, "hass", void 0); +__decorate([ + n({ type: Boolean }) +], SwitchManagerPanel.prototype, "narrow", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerPanel.prototype, "panel", void 0); +__decorate([ + r() +], SwitchManagerPanel.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerPanel.prototype, "_componentsLoaded", void 0); +__decorate([ + n({ attribute: false }) +], SwitchManagerPanel.prototype, "route", null); +SwitchManagerPanel = __decorate([ + t$1("switch-manager-panel") +], SwitchManagerPanel); + +let SwitchManagerDialogConfirm = class SwitchManagerDialogConfirm extends i$1 { + showDialog(params) { + this._params = params; + } + closeDialog() { + this._params = undefined; + } + render() { + if (!this._params) + return b ``; + return b ` -
${this._params.text||""}
- ${this._params.prompt?I`${this._params.text || ""}
+ ${this._params.prompt + ? b ``:""} + .value=${this._params.promptValue || ""} + />` + : ""} - `:I``}_dismiss(){this._params?.cancel?.(),this.closeDialog()}_confirm(){this._params?.confirm?.(),this.closeDialog()}static{this.styles=a` + `; + } + _dismiss() { + this._params?.cancel?.(); + this.closeDialog(); + } + _confirm() { + this._params?.confirm?.(); + this.closeDialog(); + } + static { this.styles = i$4 ` .text-input { width: 100%; box-sizing: border-box; @@ -986,7 +2417,73 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro color: var(--primary-text-color); font: inherit; } - `}};t([ut()],Ut.prototype,"_params",void 0),Ut=t([lt("switch-manager-dialog-confirm")],Ut);var qt=Object.freeze({__proto__:null,get SwitchManagerDialogConfirm(){return Ut}});let zt=class extends nt{constructor(){super(...arguments),this._identifier="",this._discovered=[],this._listening=!1}showDialog(t){this._params=t,this._identifier=t.identifier||"",this._discovered=[],this.hass=this.parentElement?.hass||document.querySelector("home-assistant")?.hass,this._startDiscovery()}closeDialog(){this._stopDiscovery(),this._params?.onClose?.(),this._params=void 0}async _startDiscovery(){const t=this._params.blueprint;if(t){this._listening=!0;try{this._unsubscribe=await this.hass.connection.subscribeMessage(t=>{t.identifier&&!this._discovered.some(e=>e.identifier===t.identifier)&&(this._discovered=[...this._discovered,{identifier:t.identifier,name:t.name}])},{type:wt("blueprints/auto_discovery"),blueprint_id:t.id})}catch{this._listening=!1}}}_stopDiscovery(){this._unsubscribe?.(),this._unsubscribe=void 0,this._listening=!1}render(){return this._params?I` + `; } +}; +__decorate([ + r() +], SwitchManagerDialogConfirm.prototype, "_params", void 0); +SwitchManagerDialogConfirm = __decorate([ + t$1("switch-manager-dialog-confirm") +], SwitchManagerDialogConfirm); + +var confirm = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogConfirm () { return SwitchManagerDialogConfirm; } +}); + +let SwitchManagerDialogIdentifierAutoDiscovery = class SwitchManagerDialogIdentifierAutoDiscovery extends i$1 { + constructor() { + super(...arguments); + this._identifier = ""; + this._discovered = []; + this._listening = false; + } + showDialog(params) { + this._params = params; + this._identifier = params.identifier || ""; + this._discovered = []; + this.hass = + this.parentElement?.hass || + document.querySelector("home-assistant")?.hass; + this._startDiscovery(); + } + closeDialog() { + this._stopDiscovery(); + this._params?.onClose?.(); + this._params = undefined; + } + async _startDiscovery() { + const blueprint = this._params.blueprint; + if (!blueprint) + return; + this._listening = true; + try { + this._unsubscribe = await this.hass.connection.subscribeMessage((msg) => { + if (msg.identifier && + !this._discovered.some((d) => d.identifier === msg.identifier)) { + this._discovered = [ + ...this._discovered, + { identifier: msg.identifier, name: msg.name }, + ]; + } + }, { + type: wsType("blueprints/auto_discovery"), + blueprint_id: blueprint.id, + }); + } + catch { + this._listening = false; + } + } + _stopDiscovery() { + this._unsubscribe?.(); + this._unsubscribe = undefined; + this._listening = false; + } + render() { + if (!this._params) + return b ``; + return b ` this._identifier=t.target.value} + @input=${(e) => (this._identifier = e.target.value)} /> - ${"event_entity"===this._params.blueprint?.event_type?I`
+ ${this._params.blueprint?.event_type === "event_entity" + ? b `
Identifier is the Home Assistant device id of the remote; its event.* entities are used. | Devices -
`:this._params.blueprint?.mqtt_topic_format?I`
+
` + : this._params.blueprint?.mqtt_topic_format + ? b `
MQTT Discovery Topic: ${this._params.blueprint.mqtt_topic_format} | MQTT Tool -
`:this._params.blueprint?.event_type?I`
+
` + : this._params.blueprint?.event_type + ? b `
Event Type: ${this._params.blueprint.event_type} | Event Tool -
`:""} +
` + : ""} - ${this._listening?I` + ${this._listening + ? b `

Press a button on your switch to auto-discover its identifier...

- ${this._discovered.length?I` + ${this._discovered.length + ? b `
- ${this._discovered.map(t=>I` + ${this._discovered.map((d) => b `
this._selectIdentifier(t.identifier)} + @click=${() => this._selectIdentifier(d.identifier)} > - ${t.name?I`${t.name} - ${t.identifier}`:t.identifier} + ${d.name + ? b `${d.name} + ${d.identifier}` + : d.identifier}
`)}
- `:""} + ` + : ""}
- `:""} + ` + : ""}
- `:I``}_selectIdentifier(t){this._identifier=t}_save(){this._params?.update?.({identifier:this._identifier}),this.closeDialog()}static{this.styles=a` + `; + } + _selectIdentifier(id) { + this._identifier = id; + } + _save() { + this._params?.update?.({ identifier: this._identifier }); + this.closeDialog(); + } + static { this.styles = i$4 ` .content { min-width: 300px; } @@ -1110,7 +2628,46 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro transform: rotate(360deg); } } - `}};t([ut()],zt.prototype,"_params",void 0),t([ut()],zt.prototype,"_identifier",void 0),t([ut()],zt.prototype,"_discovered",void 0),t([ut()],zt.prototype,"_listening",void 0),zt=t([lt("switch-manager-dialog-identifier-auto-discovery")],zt);var Rt=Object.freeze({__proto__:null,get SwitchManagerDialogIdentifierAutoDiscovery(){return zt}});let jt=class extends nt{constructor(){super(...arguments),this._name=""}showDialog(t){this._params=t,this._name=t.config?.name||""}closeDialog(){this._params?.onClose?.(),this._params=void 0}render(){return this._params?I` + `; } +}; +__decorate([ + r() +], SwitchManagerDialogIdentifierAutoDiscovery.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerDialogIdentifierAutoDiscovery.prototype, "_identifier", void 0); +__decorate([ + r() +], SwitchManagerDialogIdentifierAutoDiscovery.prototype, "_discovered", void 0); +__decorate([ + r() +], SwitchManagerDialogIdentifierAutoDiscovery.prototype, "_listening", void 0); +SwitchManagerDialogIdentifierAutoDiscovery = __decorate([ + t$1("switch-manager-dialog-identifier-auto-discovery") +], SwitchManagerDialogIdentifierAutoDiscovery); + +var identifierAutoDiscovery = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogIdentifierAutoDiscovery () { return SwitchManagerDialogIdentifierAutoDiscovery; } +}); + +let SwitchManagerDialogRenameSwitch = class SwitchManagerDialogRenameSwitch extends i$1 { + constructor() { + super(...arguments); + this._name = ""; + } + showDialog(params) { + this._params = params; + this._name = params.config?.name || ""; + } + closeDialog() { + this._params?.onClose?.(); + this._params = undefined; + } + render() { + if (!this._params) + return b ``; + return b ` this._name=t.target.value} + @input=${(e) => (this._name = e.target.value)} /> - `:I``}_save(){this._name.trim()&&this._params?.update?.({name:this._name.trim()}),this.closeDialog()}static{this.styles=a` + `; + } + _save() { + if (this._name.trim()) { + this._params?.update?.({ name: this._name.trim() }); + } + this.closeDialog(); + } + static { this.styles = i$4 ` .text-input { width: 100%; box-sizing: border-box; @@ -1138,25 +2703,73 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro outline: none; border-color: var(--primary-color); } - `}};t([ut()],jt.prototype,"_params",void 0),t([ut()],jt.prototype,"_name",void 0),jt=t([lt("switch-manager-dialog-rename-switch")],jt);var It=Object.freeze({__proto__:null,get SwitchManagerDialogRenameSwitch(){return jt}});let Bt=class extends nt{constructor(){super(...arguments),this._switches=[],this._copyVariables=!0}showDialog(t){this._params=t,this.hass=this.parentElement?.hass||document.querySelector("home-assistant")?.hass,this._loadSwitches()}closeDialog(){this._params?.onClose?.(),this._params=void 0,this._switches=[]}async _loadSwitches(){const t=await this.hass.callWS({type:wt("copy_from_list"),blueprint_id:this._params.blueprint_id,skip_config_id:this._params.current_switch_id||""});this._switches=t.switches}render(){return this._params?I` + `; } +}; +__decorate([ + r() +], SwitchManagerDialogRenameSwitch.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerDialogRenameSwitch.prototype, "_name", void 0); +SwitchManagerDialogRenameSwitch = __decorate([ + t$1("switch-manager-dialog-rename-switch") +], SwitchManagerDialogRenameSwitch); + +var renameSwitch = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogRenameSwitch () { return SwitchManagerDialogRenameSwitch; } +}); + +let SwitchManagerDialogCopyFrom = class SwitchManagerDialogCopyFrom extends i$1 { + constructor() { + super(...arguments); + this._switches = []; + this._copyVariables = true; + } + showDialog(params) { + this._params = params; + this.hass = + this.parentElement?.hass || + document.querySelector("home-assistant")?.hass; + this._loadSwitches(); + } + closeDialog() { + this._params?.onClose?.(); + this._params = undefined; + this._switches = []; + } + async _loadSwitches() { + const res = await this.hass.callWS({ + type: wsType("copy_from_list"), + blueprint_id: this._params.blueprint_id, + skip_config_id: this._params.current_switch_id || "", + }); + this._switches = res.switches; + } + render() { + if (!this._params) + return b ``; + return b `
- ${0===this._switches.length?I`

No other switches with this blueprint found.

`:I` + ${this._switches.length === 0 + ? b `

No other switches with this blueprint found.

` + : b `
- ${this._switches.map(t=>I` + ${this._switches.map((sw) => b `
this._selectSwitch(t)} + @click=${() => this._selectSwitch(sw)} > - ${t.name} + ${sw.name}
`)}
@@ -1164,7 +2777,18 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro
- `:I``}_selectSwitch(t){this._params?.update?.({buttons:JSON.parse(JSON.stringify(t.buttons)),variables:!!this._copyVariables&&JSON.parse(JSON.stringify(t.variables||{}))}),this.closeDialog()}static{this.styles=a` + `; + } + _selectSwitch(sw) { + this._params?.update?.({ + buttons: JSON.parse(JSON.stringify(sw.buttons)), + variables: this._copyVariables + ? JSON.parse(JSON.stringify(sw.variables || {})) + : false, + }); + this.closeDialog(); + } + static { this.styles = i$4 ` .content { min-width: 300px; } @@ -1186,19 +2810,86 @@ function t(t,e,i,s){var o,r=arguments.length,a=r<3?e:null===s?s=Object.getOwnPro margin-bottom: 8px; cursor: pointer; } - `}};t([ut()],Bt.prototype,"_params",void 0),t([ut()],Bt.prototype,"_switches",void 0),t([ut()],Bt.prototype,"_copyVariables",void 0),Bt=t([lt("switch-manager-dialog-copy-from")],Bt);var Zt=Object.freeze({__proto__:null,get SwitchManagerDialogCopyFrom(){return Bt}});let Wt=class extends nt{constructor(){super(...arguments),this._variables={}}showDialog(t){this._params=t,this._variables=JSON.parse(JSON.stringify(t.config?.variables||{})),this.updateComplete.then(()=>this._yamlEditor?.setValue(this._variables))}closeDialog(){this._params?.onClose?.(),this._params=void 0}render(){return this._params?I` + `; } +}; +__decorate([ + r() +], SwitchManagerDialogCopyFrom.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerDialogCopyFrom.prototype, "_switches", void 0); +__decorate([ + r() +], SwitchManagerDialogCopyFrom.prototype, "_copyVariables", void 0); +SwitchManagerDialogCopyFrom = __decorate([ + t$1("switch-manager-dialog-copy-from") +], SwitchManagerDialogCopyFrom); + +var copyFrom = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogCopyFrom () { return SwitchManagerDialogCopyFrom; } +}); + +let SwitchManagerDialogVariablesEditor = class SwitchManagerDialogVariablesEditor extends i$1 { + constructor() { + super(...arguments); + this._variables = {}; + } + showDialog(params) { + this._params = params; + this._variables = JSON.parse(JSON.stringify(params.config?.variables || {})); + // ha-yaml-editor only picks up `value` on its own when auto-update is set, and the + // dialog element is reused for every switch, so push the variables in by hand each + // time it opens - otherwise the box stays empty (#57). + this.updateComplete.then(() => this._yamlEditor?.setValue(this._variables)); + } + closeDialog() { + this._params?.onClose?.(); + this._params = undefined; + } + render() { + if (!this._params) + return b ``; + return b `
this._variables=t.detail.value} + @value-changed=${(e) => (this._variables = e.detail.value)} >
- `:I``}_save(){this._params?.update?.({variables:this._variables}),this.closeDialog()}static{this.styles=a` + `; + } + _save() { + this._params?.update?.({ variables: this._variables }); + this.closeDialog(); + } + static { this.styles = i$4 ` .content { min-width: 400px; } - `}};t([ut()],Wt.prototype,"_params",void 0),t([ut()],Wt.prototype,"_variables",void 0),t([mt("ha-yaml-editor")],Wt.prototype,"_yamlEditor",void 0),Wt=t([lt("switch-manager-dialog-variables-editor")],Wt);var Ft=Object.freeze({__proto__:null,get SwitchManagerDialogVariablesEditor(){return Wt}});export{Nt as SwitchManagerPanel}; + `; } +}; +__decorate([ + r() +], SwitchManagerDialogVariablesEditor.prototype, "_params", void 0); +__decorate([ + r() +], SwitchManagerDialogVariablesEditor.prototype, "_variables", void 0); +__decorate([ + e$2("ha-yaml-editor") +], SwitchManagerDialogVariablesEditor.prototype, "_yamlEditor", void 0); +SwitchManagerDialogVariablesEditor = __decorate([ + t$1("switch-manager-dialog-variables-editor") +], SwitchManagerDialogVariablesEditor); + +var variablesEditor = /*#__PURE__*/Object.freeze({ + __proto__: null, + get SwitchManagerDialogVariablesEditor () { return SwitchManagerDialogVariablesEditor; } +}); + +export { SwitchManagerPanel }; +//# sourceMappingURL=switch_manager_panel.js.map diff --git a/custom_components/switch_manager/models.py b/custom_components/switch_manager/models.py index 5ee4e0e9..37dbc99c 100644 --- a/custom_components/switch_manager/models.py +++ b/custom_components/switch_manager/models.py @@ -529,7 +529,8 @@ def __init__( self, hass: HomeAssistant, blueprint: Blueprint, _id, config ): self._event_listeners = [] self._error = None self.id = str( _id ) # Ensute id is a string for future proofing - self.name = config.get('name') + self.name = config.get('name') + self.custom_image = config.get('custom_image', '') self.identifier = config.get('identifier') self.blueprint: Blueprint self.valid_blueprint: bool @@ -545,6 +546,7 @@ def __init__( self, hass: HomeAssistant, blueprint: Blueprint, _id, config ): def update( self, config ): self.name = config.get('name') + self.custom_image = config.get('custom_image', '') self.identifier = config.get('identifier') self.variables: dict = config.get('variables') self.rotate: int = config.get('rotate', 0) diff --git a/custom_components/switch_manager/schema.py b/custom_components/switch_manager/schema.py index d07a46ed..00426c2c 100644 --- a/custom_components/switch_manager/schema.py +++ b/custom_components/switch_manager/schema.py @@ -73,6 +73,7 @@ def _normalize_config_action(value): SWITCH_MANAGER_CONFIG_SCHEMA = vol.Schema({ vol.Required('id', default=None): vol.Any(str, int, None), vol.Required('name'): cv.string, + vol.Required('custom_image', default=''): cv.string, vol.Required('enabled', default=True): bool, vol.Required('blueprint'): cv.string, vol.Required('identifier'): cv.string, diff --git a/custom_components/switch_manager/store.py b/custom_components/switch_manager/store.py index 0caa2643..63b60f6c 100644 --- a/custom_components/switch_manager/store.py +++ b/custom_components/switch_manager/store.py @@ -13,6 +13,7 @@ @attr.s class SwitchManagerManagedSwitchData: name = attr.ib(type=str, default='') + custom_image = attr.ib(type=str, default='') enabled = attr.ib(type=bool, default=True) blueprint = attr.ib(type=str, default=None) identifier = attr.ib(type=str, default=None) diff --git a/frontend/src/helpers.ts b/frontend/src/helpers.ts index 878bc12e..04832ebd 100644 --- a/frontend/src/helpers.ts +++ b/frontend/src/helpers.ts @@ -29,6 +29,7 @@ export function createEmptyConfig(blueprint: Blueprint): SwitchConfig { identifier: "", blueprint: blueprint, valid_blueprint: true, + custom_image: "", buttons: [], is_mismatch: false, rotate: 0, diff --git a/frontend/src/switch-manager-index.ts b/frontend/src/switch-manager-index.ts index 8edbf0b1..15b53bd5 100644 --- a/frontend/src/switch-manager-index.ts +++ b/frontend/src/switch-manager-index.ts @@ -6,7 +6,7 @@ import type { Route, SwitchConfig, SwitchListItem, - ConfigsResponse, + ConfigsResponse, SaveConfigResponse, } from "./types"; import { wsType, @@ -36,6 +36,10 @@ const mdiContentCopy = "M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"; const mdiMagnify = "M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"; +const mdiCamera = + "M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" +const mdiImageFrage = + "M10,14.29L6.5,19H17.46L14.75,15.46L12.78,17.8L10,14.29M5,21V7H18.96V21H5M12,2.4L14.61,5.03H9.37L12,2.4M5,5.03C4.5,5.03 4,5.22 3.61,5.61C3.2,6 3,6.46 3,7V21C3,21.5 3.2,22 3.61,22.39C4,22.8 4.5,23 5,23H18.96C19.5,23 19.96,22.8 20.37,22.39C20.77,22 21,21.5 21,21V7C21,6.46 20.77,6 20.37,5.61C19.96,5.22 19.5,5.03 18.96,5.03H16L12,1L7.96,5.03H5Z" @customElement("switch-manager-index") export class SwitchManagerIndex extends LitElement { @@ -113,6 +117,16 @@ export class SwitchManagerIndex extends LitElement { item: SwitchListItem ): { path: string; label: string; action: () => void; warning?: boolean }[] { return [ + { + path: mdiCamera, + label: "Set custom Image", + action: () => this._uploadEncodedImage(item), + }, + { + path: mdiImageFrage, + label: "Use Default Image", + action: () => this._removeCustomImage(item), + }, { path: item.enabled ? mdiStop : mdiPlay, label: item.enabled ? "Disable" : "Enable", @@ -199,16 +213,9 @@ export class SwitchManagerIndex extends LitElement { @click=${() => this._editSwitch(item.switch_id)} >
- ${item.switch.valid_blueprint && - item.switch.blueprint.has_image - ? html`` - : html``} + ${this._getImageSrcData(item) + ? html`` + : html``}
@@ -296,6 +303,18 @@ export class SwitchManagerIndex extends LitElement { navigate(navigateTo(`edit/${id}`)); } + private _getImageSrcData(item: SwitchListItem): string | null { + if(item.switch.custom_image !== "") { + return item.switch.custom_image + } + + if(item.switch.valid_blueprint && item.switch.blueprint.has_image) { + return assetUrl(item.blueprint_id + ".png") + } + + return null + } + private async _toggleEnabled(switchId: string, currentEnabled: boolean) { try { const res = await this.hass.callWS<{ enabled: boolean }>({ @@ -310,6 +329,54 @@ export class SwitchManagerIndex extends LitElement { } } + private async _uploadEncodedImage(item: SwitchListItem) { + try { + const imageInput = document.createElement("input"); + imageInput.type = "file"; + + imageInput?.addEventListener("change", () => { + const file = imageInput.files?.[0]; + if (!file) return; + showToast(this, `Selected file: ${file.name}`); + + const reader = new FileReader(); + reader.onload = async () => { + item.switch.custom_image = reader.result as string; + + try { + await this.hass.callWS({ + type: wsType("config/save"), + config: { ...item.switch, blueprint: (item.switch.blueprint as any).id }, + fix_mismatch: true, + }); + this._populateSwitches(); + } catch (e: any) { + showToast(this, e.message); + } + }; + + reader.readAsDataURL(file); + }); + imageInput.click(); + + } catch (e: any) { + showToast(this, e.message); + } + } + + private async _removeCustomImage(item: SwitchListItem) { + try { + item.switch.custom_image = "" + await this.hass.callWS({ + type: wsType("config/save"), + config: { ...item.switch, blueprint: (item.switch.blueprint as any).id }, + fix_mismatch: true, + }); + } catch (e: any) { + showToast(this, e.message); + } + } + private async _duplicate(switchId: string) { try { const res = await this.hass.callWS<{ config_id: string }>({ diff --git a/frontend/src/switch-manager-switch-editor.ts b/frontend/src/switch-manager-switch-editor.ts index 6519ddd9..b1b6a7e7 100644 --- a/frontend/src/switch-manager-switch-editor.ts +++ b/frontend/src/switch-manager-switch-editor.ts @@ -157,13 +157,16 @@ export class SwitchManagerSwitchEditor extends LitElement {
+
+ ${this.config.custom_image === "" ? nothing : html``} ${hasError ? nothing : html`

${this.blueprint?.service} / ${this.blueprint?.name}

`} +
-
- ${!this.blueprint || this.blueprint?.has_image +
+ ${!this.blueprint || this.blueprint?.has_image ? html`` : html``} -
+
${hasError ? nothing : html`