12 lines
29 KiB
JavaScript
12 lines
29 KiB
JavaScript
/*!
|
|
* WashData - Home Assistant integration for appliance cycle monitoring via smart plugs.
|
|
* Copyright (C) 2026 Lukas Bandura
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*
|
|
* This file is a MINIFIED BUILD. The corresponding readable source ships in the
|
|
* same directory and is the licensed, preferred form for modification.
|
|
* This program comes with ABSOLUTELY NO WARRANTY. See the GNU Affero General
|
|
* Public License for details: <https://www.gnu.org/licenses/>.
|
|
*/
|
|
const I="ha-washdata-card",B="ha-washdata-card-editor",S="ha_washdata",V=500,Y=250,K=10,D={state:"washer_state",program:"washer_program",time:"time_remaining",progress:"cycle_progress",power:"current_power",energy:"energy_total",phase:"current_phase"},j={pause:"pause_cycle",resume:"resume_cycle",terminate:"force_end_cycle",record_start:"record_start",record_stop:"record_stop"},N={pause:"mdi:pause",resume:"mdi:play",terminate:"mdi:stop",record_start:"mdi:record-circle-outline",record_stop:"mdi:stop-circle-outline",open_panel:"mdi:open-in-new"},O=["pause","resume","terminate","record_start","record_stop","program","open_panel"],H=["running","paused","user_paused","ending","starting","anti_wrinkle","rinse"],x=["off","unknown","unavailable","idle"],z=60,C=new WeakMap;function W(u){const t=u&&u.connection;if(!t)return Promise.resolve({stateColors:{}});if(C.has(t))return C.get(t);const e=u.callWS({type:S+"/get_constants"}).then(i=>({stateColors:i&&i.state_colors||{}})).catch(()=>({stateColors:{}}));return C.set(t,e),e}const y={},k={};function U(u){return"/"+S+"/panel-translations/"+encodeURIComponent(u)+".json"}async function G(u){if(!u)return null;const t=[u],e=u.indexOf("-");e>0&&t.push(u.slice(0,e));for(const i of t)try{const s=await fetch(U(i));if(s.ok){const n=await s.json();if(n&&typeof n=="object")return n}}catch{}return null}async function v(u){if(!u||y[u])return;if(k[u]){await k[u];return}const t=G(u).then(e=>{e&&(y[u]=e)});k[u]=t,await t}class F extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this._cfg=null,this._hass=null,this._builtSig=null,this._constantsLoaded=!1,this._langEnsured="",this._constants=null,this._spark=[],this._sparkState=null,this._holdTimer=null,this._holdTriggered=!1,this._tapTimer=null,this._lastTapTime=0,this._pointerStart=null,this._pointerCanceled=!1,this._onPointerDown=this._onPointerDown.bind(this),this._onPointerMove=this._onPointerMove.bind(this),this._onPointerUp=this._onPointerUp.bind(this),this._onPointerCancel=this._onPointerCancel.bind(this)}static getStubConfig(){return{entity:"sensor.washing_machine_state",layout:"tile",tap_action:{action:"more-info"},hold_action:{action:"none"},double_tap_action:{action:"none"}}}static getConfigElement(){return document.createElement(B)}setConfig(t){if(!t)throw new Error("Invalid configuration");const e=t.layout||"tile";if(e!=="glance"&&!t.entity)throw new Error("Please define an entity");if(e==="glance"&&!(Array.isArray(t.entities)&&t.entities.length)&&!t.entity)throw new Error("Please define entities for the glance layout");this._cfg={...t,layout:e},this._builtSig!==this._structureSig()&&(this._builtSig=null,this.shadowRoot&&(this.shadowRoot.innerHTML="")),this._render()}_structureSig(){const t=this._cfg||{},e=this._flags(),i=t.layout||"tile";return[i,e.showBar?"bar":"",e.showSparkline?"spark":"",e.buttons.join("+"),i==="glance"?String(this._glanceEntities().length):""].join("|")}set hass(t){this._hass=t,this._ensureResources(),this._render()}_detailRows(){const t=this._flags();let e=2;return t.buttons.length&&(e+=1),t.showSparkline&&(e+=1),e}getCardSize(){const t=this._cfg&&this._cfg.layout;return t==="detail"?this._detailRows():t==="glance"&&this._glanceEntities().length||1}getGridOptions(){const t=this._cfg&&this._cfg.layout;if(t==="detail"){const e=this._detailRows();return{rows:e,min_rows:e,columns:12,min_columns:6}}return t==="glance"?{rows:Math.max(1,this._glanceEntities().length),min_rows:1,columns:12,min_columns:6}:{rows:1,min_rows:1,columns:6,min_columns:3}}disconnectedCallback(){this._clearHoldTimer(),this._tapTimer&&(window.clearTimeout(this._tapTimer),this._tapTimer=null)}_lang(){const t=this._hass&&this._hass.locale&&this._hass.locale.language||this._hass&&this._hass.language||"en";return typeof t=="string"&&t?t:"en"}_tLookup(t,e){const i=y[e];if(!i)return null;const s=t.split(".").reduce((n,o)=>n&&n[o]!==void 0?n[o]:null,i);return s&&typeof s=="string"?s:null}_t(t,e,i){const s=this._lang();let n=s&&this._tLookup(t,s)||this._tLookup(t,"en")||i||t;if(e)for(const[o,a]of Object.entries(e))n=n.replace(new RegExp("\\{"+o+"\\}","g"),String(a));return n}_ensureResources(){if(!this._hass)return;const t=this._lang(),e=!this._constantsLoaded,i=t!==this._langEnsured;if(!e&&!i)return;const s=[];e&&(this._constantsLoaded=!0,s.push(W(this._hass).then(n=>{this._constants=n}),v("en"))),i&&(this._langEnsured=t,t&&t!=="en"&&s.push(v(t))),Promise.all(s).then(()=>this._update()).catch(()=>{})}_resolveRoles(t){const e=this._hass,i=this._cfg||{},s=e&&e.entities||{},n=t||i.entity,o=s[n],a=i.device_id||o&&o.device_id||null,r={state:n},c={};let d=null;if(a)for(const[l,h]of Object.entries(s)){if(!h||h.device_id!==a||h.platform&&h.platform!==S)continue;const p=h.translation_key;if(l.startsWith("sensor."))for(const[f,g]of Object.entries(D))p===g&&!r[f]&&(r[f]=l);for(const[f,g]of Object.entries(j))p===g&&!c[f]&&(c[f]=l);!d&&l.startsWith("select.")&&(d=l)}if(i.program_entity&&(r.program=i.program_entity),i.time_entity&&(r.time=i.time_entity),i.pct_entity&&(r.progress=i.pct_entity),i.power_entity&&(r.power=i.power_entity),i.energy_entity&&(r.energy=i.energy_entity),i.phase_entity&&(r.phase=i.phase_entity),!r.time||!r.progress||!r.program){const l=/^sensor\.(.+)_state$/.exec(n);if(l){const h="sensor."+l[1]+"_",p=e&&e.states,f=g=>p&&p[h+g]?h+g:null;r.time=r.time||f("time_remaining"),r.progress=r.progress||f("cycle_progress"),r.program=r.program||f("program")}}return r._buttons=c,r._select=d,r._deviceId=a,r}_fmtState(t){if(!t)return"";const e=this._hass;if(e&&typeof e.formatEntityState=="function")try{return e.formatEntityState(t)}catch{}const i=t.state||"";return i&&i.charAt(0).toUpperCase()+i.slice(1)}_vm(t){const e=this._hass,i=this._cfg,s=e.states,n=s[t.state];if(!n)return{missing:!0,missingEntity:t.state};const o=String(n.state||"").toLowerCase(),a=x.includes(o),r=H.includes(o),c=n.attributes||{};let d="";const l=t.program?s[t.program]:null;if(l){const _=String(l.state||"").toLowerCase();["unknown","none","off","unavailable",""].includes(_)||(d=this._fmtState(l))}if(!d&&c.current_program_guess){const _=String(c.current_program_guess).toLowerCase();["unknown","none","off","unavailable"].includes(_)||(d=c.current_program_guess)}let h="";const p=t.phase?s[t.phase]:null;if(p){const _=String(p.state||"").toLowerCase();["unknown","none","off","unavailable",""].includes(_)||(h=this._fmtState(p))}!h&&l&&l.attributes&&l.attributes.active_phase&&(h=l.attributes.active_phase);let f="";if(o==="running"&&c.sub_state){const _=String(c.sub_state).match(/Running \((.*)\)/);f=_&&_[1]?_[1]:c.sub_state}let g=null;const m=t.progress?s[t.progress]:null;m&&!isNaN(parseFloat(m.state))&&(g=Math.max(0,Math.min(100,Math.round(parseFloat(m.state)))));let b="";const w=t.time?s[t.time]:null;w&&!x.includes(String(w.state).toLowerCase())&&(e&&typeof e.formatEntityState=="function"?b=this._fmtState(w):isNaN(parseFloat(w.state))?b=w.state:b=w.state+" "+this._t("card.min",null,"min"));let R="",A="";if(m&&m.attributes){const _=m.attributes.projected_energy_kwh;_!=null&&(R=_+" kWh");const T=m.attributes.projected_cost;T!=null&&(A=this._fmtCost(T))}let L=null;const E=t.power?s[t.power]:null;E&&!isNaN(parseFloat(E.state))&&(L=parseFloat(E.state));let P=null;c.cycle_anomaly&&c.cycle_anomaly!=="none"&&(P={kind:c.cycle_anomaly,ratio:c.overrun_ratio});const M=this._stateColor(o,r,i.active_color);return{missing:!1,sk:o,stateObj:n,isInactive:a,isActive:r,isRunning:o==="running",stateLabel:this._fmtState(n),color:M,program:d,phase:h,subState:f,pct:g,timeText:b,energyText:R,costText:A,powerW:L,anomaly:P,buttons:t._buttons||{},select:t._select||null}}_fmtCost(t){const e=this._hass,i=e&&e.config&&e.config.currency;if(i)try{return new Intl.NumberFormat(this._lang(),{style:"currency",currency:i}).format(Number(t))}catch{}return String(t)}_stateColor(t,e,i){if(e&&i){if(Array.isArray(i)){const[o,a,r]=i;return{fg:"rgb("+o+","+a+","+r+")",bg:"rgba("+o+","+a+","+r+",0.2)"}}return{fg:i,bg:"rgba(128,128,128,0.15)"}}const n=(this._constants&&this._constants.stateColors||{})[t];return n?{fg:n,bg:this._alpha(n,t)}:x.includes(t)?{fg:"var(--disabled-text-color, grey)",bg:"rgba(128,128,128,0.1)"}:{fg:"var(--primary-color)",bg:"rgba(var(--rgb-primary-color, 33,150,243),0.2)"}}_alpha(t,e){const i=/#([0-9a-fA-F]{6})/.exec(t);if(i){const s=parseInt(i[1],16),n=s>>16&255,o=s>>8&255,a=s&255;return"rgba("+n+","+o+","+a+",0.18)"}return x.includes(e)?"rgba(128,128,128,0.1)":"rgba(128,128,128,0.15)"}_deviceTypeIcon(){const t=this._hass,e=this._cfg;if(e.icon)return e.icon;const i=t&&t.states[e.entity];return i&&i.attributes&&i.attributes.icon?i.attributes.icon:"mdi:washing-machine"}_titleText(){const t=this._cfg;if(t.title)return t.title;const e=this._hass,i=e&&e.entities||{},s=e&&e.devices||{},n=i[t.entity];if(n&&n.device_id&&s[n.device_id]){const a=s[n.device_id],r=a.name_by_user||a.name;if(r)return r}const o=e&&e.states[t.entity];return o&&o.attributes&&o.attributes.friendly_name?String(o.attributes.friendly_name).replace(/ (State|Status)$/i,""):"WashData"}_flags(){const t=this._cfg||{};return{showState:t.show_state!==!1,showProgram:t.show_program!==!1,showDetails:t.show_details!==!1,showBar:t.show_progress_bar!==!1,showPhase:t.show_phase!==!1,showEnergy:t.show_energy!==!1,showAnomaly:t.show_anomaly!==!1,showSparkline:!!t.show_sparkline,displayMode:t.display_mode||"time",buttons:Array.isArray(t.buttons)?t.buttons.filter(e=>O.includes(e)):[]}}_render(){if(!this.shadowRoot||!this._cfg)return;const t=this._structureSig();this._builtSig!==t&&(this._build(this._cfg.layout||"tile"),this._builtSig=t),this._update()}_baseStyle(){return":host{display:block;height:100%}ha-card{padding:0;background:var(--ha-card-background,var(--card-background-color,white));border-radius:var(--ha-card-border-radius,12px);box-shadow:var(--ha-card-box-shadow,none);overflow:hidden;cursor:pointer;height:100%;box-sizing:border-box;border:var(--ha-card-border-width,1px) solid var(--ha-card-border-color,var(--divider-color))}.icon-container{border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background-color .3s,color .3s;position:relative}ha-icon{--mdc-icon-size:24px}.primary{font-weight:500;color:var(--primary-text-color);white-space:nowrap;text-overflow:ellipsis;overflow:hidden;line-height:1.2}.secondary{color:var(--secondary-text-color);white-space:nowrap;text-overflow:ellipsis;overflow:hidden;line-height:1.2}.bar{height:4px;border-radius:2px;background:var(--divider-color,rgba(128,128,128,.25));overflow:hidden}.bar>i{display:block;height:100%;width:0;border-radius:2px;transition:width .5s ease,background-color .3s}.chip{display:inline-flex;align-items:center;gap:4px;font-size:11px;padding:1px 8px;border-radius:10px;background:rgba(128,128,128,.14);color:var(--secondary-text-color);white-space:nowrap}.chip.warn{background:rgba(255,152,0,.16);color:var(--warning-color,#ff9800)}.acts{display:flex;gap:4px;align-items:center}.wd-act{border:none;background:rgba(128,128,128,.12);color:var(--primary-text-color);border-radius:10px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0}.wd-act:hover{background:rgba(128,128,128,.22)}.wd-act[disabled]{opacity:.35;pointer-events:none}.wd-act ha-icon{--mdc-icon-size:20px}select.wd-prog{max-width:150px;font:inherit;color:var(--primary-text-color);background:var(--secondary-background-color);border:1px solid var(--divider-color);border-radius:8px;padding:4px 6px}"}_build(t){return t==="detail"?this._buildDetail():t==="glance"?this._buildGlance():this._buildTile()}_attachGestures(t){t.addEventListener("pointerdown",this._onPointerDown),t.addEventListener("pointermove",this._onPointerMove),t.addEventListener("pointerup",this._onPointerUp),t.addEventListener("pointercancel",this._onPointerCancel),t.addEventListener("pointerleave",this._onPointerCancel)}_buildTile(){const t=this._flags(),e=this._baseStyle()+".tile{display:flex;flex-direction:row;align-items:center;padding:8px 12px;gap:12px;width:100%;box-sizing:border-box}.tile .icon-container{width:40px;height:40px}.tile .info{display:flex;flex-direction:column;justify-content:center;overflow:hidden;flex:1;min-width:0}.tile .primary{font-size:14px}.tile .secondary{font-size:12px;margin-top:2px}.tile .bar{margin-top:6px}";this.shadowRoot.innerHTML="<style>"+e+'</style><ha-card id="card"><div class="tile"><div class="icon-container" id="iconc"><ha-icon id="icon"></ha-icon></div><div class="info"><div class="primary" id="title"></div><div class="secondary" id="state"></div>'+(t.showBar?'<div class="bar" id="bar"><i id="barfill"></i></div>':"")+"</div></div></ha-card>",this._attachGestures(this.shadowRoot.getElementById("card"))}_buildDetail(){const t=this._flags(),e=this._baseStyle()+"ha-card.wd-detail{height:auto}.detail{display:flex;flex-direction:column;gap:8px;padding:12px 14px;width:100%;box-sizing:border-box}.detail .top{display:flex;align-items:center;gap:12px}.detail .icon-container{width:48px;height:48px}.detail .icon-container ha-icon{--mdc-icon-size:30px}.detail .info{flex:1;min-width:0}.detail .primary{font-size:16px}.detail .secondary{font-size:13px;margin-top:2px}.detail .eta{text-align:right;flex-shrink:0}.detail .eta .big{font-size:22px;font-weight:600;color:var(--primary-text-color);line-height:1.1}.detail .eta .lbl{font-size:11px;color:var(--secondary-text-color);text-transform:uppercase;letter-spacing:.04em}.detail .bar{height:6px}.detail .meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.detail .meta:empty{display:none}.detail canvas{width:100%;height:34px;display:block}";this.shadowRoot.innerHTML="<style>"+e+'</style><ha-card id="card" class="wd-detail"><div class="detail"><div class="top"><div class="icon-container" id="iconc"><ha-icon id="icon"></ha-icon></div><div class="info"><div class="primary" id="title"></div><div class="secondary" id="state"></div></div><div class="eta" id="etawrap"><div class="big" id="eta"></div><div class="lbl" id="etalbl"></div></div></div>'+(t.showBar?'<div class="bar" id="bar"><i id="barfill"></i></div>':"")+'<div class="meta" id="meta"></div>'+(t.showSparkline?'<canvas id="spark"></canvas>':"")+(t.buttons.length?'<div class="acts" id="acts"></div>':"")+"</div></ha-card>",this._attachGestures(this.shadowRoot.getElementById("card")),t.buttons.length&&this._buildButtons(this.shadowRoot.getElementById("acts"),t.buttons)}_buildButtons(t,e){if(t){t.innerHTML="";for(const i of e){if(i==="program"){const o=document.createElement("select");o.className="wd-prog",o.id="prog-select",o.addEventListener("click",a=>a.stopPropagation()),o.addEventListener("pointerdown",a=>a.stopPropagation()),o.addEventListener("change",a=>{a.stopPropagation(),this._onProgramChange(a.target.value)}),t.appendChild(o);continue}const s=document.createElement("button");s.className="wd-act",s.dataset.btn=i,s.title=this._t("card.btn."+i,null,this._defaultBtnLabel(i)),s.addEventListener("pointerdown",o=>o.stopPropagation()),s.addEventListener("pointerup",o=>o.stopPropagation()),s.addEventListener("click",o=>{o.stopPropagation(),this._onActionButton(i)});const n=document.createElement("ha-icon");n.setAttribute("icon",N[i]||"mdi:gesture-tap-button"),s.appendChild(n),t.appendChild(s)}}}_defaultBtnLabel(t){return{pause:"Pause",resume:"Resume",terminate:"End cycle",record_start:"Start recording",record_stop:"Stop recording",open_panel:"Open WashData"}[t]||t}_buildGlance(){const t=this._baseStyle()+".glance{display:flex;flex-direction:column;padding:6px 0}.row{display:flex;align-items:center;gap:12px;padding:8px 14px;cursor:pointer}.row:hover{background:rgba(128,128,128,.06)}.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.row .info{flex:1;min-width:0}.row .primary{font-size:14px}.row .secondary{font-size:12px;margin-top:1px}.row .rt{font-size:13px;color:var(--secondary-text-color);flex-shrink:0;text-align:right}",e=this._glanceEntities().map((i,s)=>'<div class="row" data-idx="'+s+'"><span class="dot" data-dot></span><div class="info"><div class="primary" data-title></div><div class="secondary" data-sub></div></div><div class="rt" data-rt></div></div>').join("");this.shadowRoot.innerHTML="<style>"+t+'</style><ha-card><div class="glance">'+e+"</div></ha-card>",this.shadowRoot.querySelectorAll(".row").forEach(i=>{i.addEventListener("click",()=>{const s=parseInt(i.dataset.idx,10),n=this._glanceEntities()[s];n&&this._moreInfo(n)})})}_glanceEntities(){const t=this._cfg||{};return Array.isArray(t.entities)&&t.entities.length?t.entities.map(e=>typeof e=="string"?e:e&&e.entity).filter(Boolean):t.entity?[t.entity]:[]}_update(){if(!this.shadowRoot||!this._hass||!this._cfg)return;const t=this._cfg.layout||"tile";if(t==="glance")return this._updateGlance();const e=this._resolveRoles(),i=this._vm(e);return t==="detail"?this._updateDetail(i):this._updateTile(i)}_applyIcon(t){const e=this.shadowRoot.getElementById("icon"),i=this.shadowRoot.getElementById("iconc");!e||!i||(e.setAttribute("icon",this._deviceTypeIcon()),i.style.color=t.color.fg,i.style.background=t.color.bg)}_applyBar(t){const e=this.shadowRoot.getElementById("barfill"),i=this.shadowRoot.getElementById("bar");if(!(!e||!i)){if(!t.isActive||t.pct===null){i.style.visibility="hidden",e.style.width="0%";return}i.style.visibility="visible",e.style.width=t.pct+"%",e.style.background=t.color.fg}}_updateTile(t){const e=this.shadowRoot.getElementById("title"),i=this.shadowRoot.getElementById("state");if(t.missing){e&&(e.textContent=this._t("card.entity_not_found",null,"Entity not found")),i&&(i.textContent=t.missingEntity||"");return}e.textContent=this._titleText(),this._applyIcon(t),this._applyBar(t);const s=this._flags(),n=[];s.showState&&(t.isRunning?t.subState&&n.push(t.subState):n.push(t.stateLabel)),s.showProgram&&t.program&&n.push(t.program),!t.isInactive&&s.showDetails&&(s.displayMode==="percentage"&&t.pct!==null?n.push(t.pct+"%"):t.timeText?n.push(t.timeText):t.pct!==null&&n.push(t.pct+"%")),s.showAnomaly&&t.anomaly&&n.push(this._t("card.running_long",null,"running long")),i.textContent=n.join(" \u2022 ")}_updateDetail(t){const e=this.shadowRoot.getElementById("title"),i=this.shadowRoot.getElementById("state"),s=this.shadowRoot.getElementById("eta"),n=this.shadowRoot.getElementById("etalbl"),o=this.shadowRoot.getElementById("etawrap"),a=this.shadowRoot.getElementById("meta");if(t.missing){e&&(e.textContent=this._t("card.entity_not_found",null,"Entity not found")),i&&(i.textContent=t.missingEntity||"");return}e.textContent=this._titleText(),this._applyIcon(t),this._applyBar(t);const r=this._flags(),c=[];if(r.showState&&c.push(t.isRunning&&t.subState?t.subState:t.stateLabel),r.showProgram&&t.program&&c.push(t.program),i.textContent=c.join(" \u2022 "),o&&s&&n&&(t.isActive&&t.timeText?(s.textContent=t.timeText,n.textContent=this._t("card.remaining",null,"remaining"),o.style.visibility="visible"):t.isActive&&t.pct!==null?(s.textContent=t.pct+"%",n.textContent=this._t("card.progress",null,"progress"),o.style.visibility="visible"):o.style.visibility="hidden"),a){a.innerHTML="";const d=(l,h)=>{if(!l)return;const p=document.createElement("span");p.className="chip"+(h?" warn":""),p.textContent=l,a.appendChild(p)};if(r.showPhase&&t.phase&&t.isActive&&d(t.phase),r.showEnergy&&t.energyText&&d(t.energyText),r.showEnergy&&t.costText&&d(t.costText),t.isRunning&&t.powerW!==null&&d(Math.round(t.powerW)+" W"),r.showAnomaly&&t.anomaly){const l=t.anomaly.ratio?" ("+Math.round((t.anomaly.ratio-1)*100)+"%)":"";d(this._t("card.running_long",null,"Running long")+l,!0)}}this._updateButtons(t),r.showSparkline&&this._updateSparkline(t)}_updateButtons(t){const e=this.shadowRoot.getElementById("acts");if(!e)return;const i=t.sk,s={pause:i==="running",resume:i==="paused"||i==="user_paused",terminate:t.isActive,record_start:!t.isActive,record_stop:!0,open_panel:!0};e.querySelectorAll(".wd-act").forEach(o=>{const a=o.dataset.btn,r=a==="open_panel"||a==="program"?!0:t.buttons[a];s[a]!==!1&&(a==="open_panel"||r)?o.removeAttribute("disabled"):o.setAttribute("disabled","")});const n=this.shadowRoot.getElementById("prog-select");if(n&&t.select){const o=this._hass.states[t.select],a=o&&o.attributes&&o.attributes.options||[],r=o&&o.state,c=a.join("|");if(n._sig!==c){n._sig=c,n.innerHTML="";for(const d of a){const l=document.createElement("option");l.value=d,l.textContent=d,n.appendChild(l)}}r!==void 0&&(n.value=r)}else n&&n.setAttribute("disabled","")}_updateSparkline(t){const e=this.shadowRoot.getElementById("spark");if(!e)return;if(t.sk!==this._sparkState&&(t.isActive||(this._spark=[]),this._sparkState=t.sk),t.isActive&&t.powerW!==null){const d=this._spark[this._spark.length-1],l=Date.now();(!d||l-d.t>2e3)&&(this._spark.push({t:l,w:t.powerW}),this._spark.length>z&&this._spark.shift())}const i=this._spark;if(i.length<2){e.style.display="none";return}e.style.display="block";const s=e.clientWidth||300,n=34;e.width!==s&&(e.width=s),e.height!==n&&(e.height=n);const o=e.getContext("2d");o.clearRect(0,0,s,n);const a=Math.max.apply(null,i.map(d=>d.w))||1,r=3,c=(s-r*2)/(i.length-1);o.beginPath(),i.forEach((d,l)=>{const h=r+l*c,p=n-r-d.w/a*(n-r*2);l===0?o.moveTo(h,p):o.lineTo(h,p)}),o.strokeStyle=t.color.fg,o.lineWidth=2,o.lineJoin="round",o.stroke()}_updateGlance(){const t=this._glanceEntities(),e=this.shadowRoot.querySelectorAll(".row");t.forEach((i,s)=>{const n=e[s];if(!n)return;const o=this._resolveRoles(i),a=this._vm(o),r=n.querySelector("[data-dot]"),c=n.querySelector("[data-title]"),d=n.querySelector("[data-sub]"),l=n.querySelector("[data-rt]");if(a.missing){c&&(c.textContent=i),d&&(d.textContent=this._t("card.entity_not_found",null,"Entity not found")),r&&(r.style.background="var(--disabled-text-color, grey)"),l&&(l.textContent="");return}r&&(r.style.background=a.color.fg),c&&(c.textContent=this._deviceNameFor(i));const h=[];a.isRunning&&a.subState?h.push(a.subState):h.push(a.stateLabel),a.program&&h.push(a.program),d&&(d.textContent=h.join(" \u2022 ")),l&&(a.isActive&&a.timeText?l.textContent=a.timeText:a.isActive&&a.pct!==null?l.textContent=a.pct+"%":l.textContent="")})}_deviceNameFor(t){const e=this._hass,i=e&&e.entities||{},s=e&&e.devices||{},n=i[t];if(n&&n.device_id&&s[n.device_id]){const a=s[n.device_id],r=a.name_by_user||a.name;if(r)return r}const o=e&&e.states[t];return o&&o.attributes&&o.attributes.friendly_name?String(o.attributes.friendly_name).replace(/ (State|Status)$/i,""):t}_onActionButton(t){const e=this._hass;if(!e)return;if(t==="open_panel"){this._navigate("/ha-washdata");return}const i=this._resolveRoles(),s=i._buttons&&i._buttons[t];s&&(this._fireHaptic("light"),e.callService("button","press",{entity_id:s}))}_onProgramChange(t){const e=this._hass;if(!e||!t)return;const s=this._resolveRoles()._select;s&&e.callService("select","select_option",{entity_id:s,option:t})}_moreInfo(t){t&&this.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:t},bubbles:!0,composed:!0}))}_navigate(t){window.history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed",{detail:{replace:!1}}))}_clearHoldTimer(){this._holdTimer&&(window.clearTimeout(this._holdTimer),this._holdTimer=null)}_onPointerDown(t){if(t.button!==void 0&&t.button!==0)return;this._holdTriggered=!1,this._pointerCanceled=!1,this._pointerStart={x:t.clientX,y:t.clientY};const e=this._cfg&&this._cfg.hold_action;e&&e.action&&e.action!=="none"&&(this._clearHoldTimer(),this._holdTimer=window.setTimeout(()=>{this._holdTimer=null,this._holdTriggered=!0,this._fireHaptic("success"),this._executeAction(e)},500))}_onPointerMove(t){if(!this._pointerStart)return;const e=t.clientX-this._pointerStart.x,i=t.clientY-this._pointerStart.y;e*e+i*i>100&&(this._clearHoldTimer(),this._pointerStart=null,this._pointerCanceled=!0)}_onPointerCancel(){this._clearHoldTimer()}_onPointerUp(){if(this._clearHoldTimer(),this._pointerCanceled){this._pointerCanceled=!1;return}if(this._holdTriggered){this._holdTriggered=!1;return}const t=this._cfg&&this._cfg.tap_action||{action:"more-info"},e=this._cfg&&this._cfg.double_tap_action;if(!(e&&e.action&&e.action!=="none")){this._executeAction(t);return}const s=Date.now();if(this._tapTimer&&s-this._lastTapTime<250){window.clearTimeout(this._tapTimer),this._tapTimer=null,this._lastTapTime=0,this._executeAction(e);return}this._lastTapTime=s,this._tapTimer=window.setTimeout(()=>{this._tapTimer=null,this._executeAction(t)},250)}_fireHaptic(t){this.dispatchEvent(new CustomEvent("haptic",{detail:t,bubbles:!0,composed:!0}))}_executeAction(t){if(!t)return;const e=t.action||"more-info",i=t.entity||this._cfg&&this._cfg.entity;switch(e){case"none":return;case"more-info":this._moreInfo(i);return;case"toggle":if(!this._hass||!i)return;this._hass.callService("homeassistant","toggle",{entity_id:i});return;case"call-service":case"perform-action":{const s=t.perform_action||t.service;if(!s||!this._hass)return;const[n,o]=s.split(".");if(!n||!o)return;const a={...t.data||t.service_data||{}};this._hass.callService(n,o,a,t.target);return}case"navigate":{const s=t.navigation_path;if(!s)return;t.navigation_replace?(window.history.replaceState(window.history.state,"",s),window.dispatchEvent(new CustomEvent("location-changed",{detail:{replace:!0}}))):this._navigate(s);return}case"url":{const s=t.url_path;if(!s)return;window.open(s,"_blank","noopener,noreferrer");return}default:return}}}class q extends HTMLElement{_lang(){const t=this._hass&&this._hass.locale&&this._hass.locale.language||this._hass&&this._hass.language||"en";return typeof t=="string"&&t?t:"en"}_t(t,e){const i=this._lang(),s=y[i]||y.en;if(s){const n=t.split(".").reduce((o,a)=>o&&o[a]!==void 0?o[a]:null,s);if(n&&typeof n=="string")return n}return e||t}setConfig(t){this._cfg={layout:"tile",...t},this._render()}set hass(t){this._hass=t;const e=this._lang(),i=[];y.en||i.push(v("en")),e&&e!=="en"&&!y[e]&&i.push(v(e)),i.length&&Promise.all(i).then(()=>this._render()),this._form&&(this._form.hass=t)}_render(){this.shadowRoot||this.attachShadow({mode:"open"}),this._form||(this.shadowRoot.innerHTML='<style>.editor-container{padding:8px 4px}ha-form{display:block}</style><div class="editor-container" id="editor-container"></div>',this._form=document.createElement("ha-form"),this.shadowRoot.getElementById("editor-container").appendChild(this._form),this._form.addEventListener("value-changed",t=>this._valueChanged(t)),this._form.computeLabel=t=>this._t("card.editor."+t.name,this._humanize(t.name))),this._form.schema=this._schema(),this._form.data=this._cfg,this._hass&&(this._form.hass=this._hass)}_humanize(t){return t.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}_schema(){const t=this._cfg&&this._cfg.layout||"tile",e={name:"layout",selector:{select:{mode:"dropdown",options:[{value:"tile",label:this._t("card.layout.tile","Tile (compact)")},{value:"detail",label:this._t("card.layout.detail","Detail (rich)")},{value:"glance",label:this._t("card.layout.glance","Glance (multiple devices)")}]}}};if(t==="glance")return[e,{name:"entities",selector:{entity:{domain:"sensor",multiple:!0}}},{name:"title",selector:{text:{}}}];const i=[e,{name:"entity",selector:{entity:{domain:"sensor"}}},{name:"title",selector:{text:{}}},{name:"icon",selector:{icon:{}}},{name:"show_state",selector:{boolean:{}}},{name:"show_program",selector:{boolean:{}}},{name:"show_progress_bar",selector:{boolean:{}}},{name:"display_mode",selector:{select:{mode:"dropdown",options:[{value:"time",label:this._t("card.show_time_remaining","Show Time Remaining")},{value:"percentage",label:this._t("card.show_percentage","Show Percentage")}]}}},{name:"active_color",selector:{color_rgb:{}}}];return t==="detail"&&i.push({name:"show_phase",selector:{boolean:{}}},{name:"show_energy",selector:{boolean:{}}},{name:"show_anomaly",selector:{boolean:{}}},{name:"show_sparkline",selector:{boolean:{}}},{name:"buttons",selector:{select:{multiple:!0,mode:"list",options:O.map(s=>({value:s,label:this._t("card.btn."+s,this._humanize(s))}))}}}),i.push({name:"program_entity",selector:{entity:{domain:["sensor","select","input_select","input_text"]}}},{name:"time_entity",selector:{entity:{domain:"sensor"}}},{name:"pct_entity",selector:{entity:{domain:"sensor"}}},{name:"power_entity",selector:{entity:{domain:"sensor"}}},{name:"tap_action",selector:{ui_action:{}}},{name:"hold_action",selector:{ui_action:{}}},{name:"double_tap_action",selector:{ui_action:{}}}),i}_valueChanged(t){if(!this._cfg)return;const e=t.detail.value,i=e.layout&&e.layout!==this._cfg.layout;this._cfg={...this._cfg,...e},this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._cfg},bubbles:!0,composed:!0})),i&&this._render()}}customElements.define(I,F),customElements.define(B,q),window.customCards=window.customCards||[],window.customCards.push({type:I,name:"WashData Card",preview:!0,description:"Adaptive card for WashData appliances: compact tile, rich detail, or multi-device glance.",documentationURL:"https://github.com/3dg1luk43/ha_washdata"});
|