- ${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 `
- ${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 `
- ${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 5ee4e0e..37dbc99 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 d07a46e..00426c2 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 0caa264..63b60f6 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 878bc12..04832eb 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 8edbf0b..15b53bd 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 6519ddd..b1b6a7e 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`