From b3ca5f94f956a2f092c5f9a79ce8c0ba3feef5ec Mon Sep 17 00:00:00 2001 From: DigiLive Date: Mon, 31 Mar 2025 16:35:50 +0200 Subject: [PATCH] Optimize types The type definitions and interfaces of Home Assistant and Mushroom where outdated. Also, the type definitions and interfaces of the strategy could use some refinement. --- dist/mushroom-strategy.js | 1 - package-lock.json | 1578 ++--------------- package.json | 39 +- src/Helper.ts | 522 ++++++ src/cards/CameraCard.ts | 49 +- src/cards/ControllerCard.ts | 102 ++ src/cards/HaAreaCard.ts | 56 +- src/configurationDefaults.ts | 347 ++-- src/mushroom-strategy.ts | 384 ++-- .../common/translations/localize.ts | 15 +- src/types/homeassistant/data/area_registry.ts | 25 +- .../homeassistant/data/device_registry.ts | 60 +- .../homeassistant/data/entity_registry.ts | 129 +- .../homeassistant/data/floor_registry.ts | 11 +- src/types/homeassistant/data/frontend.ts | 5 - src/types/homeassistant/data/lovelace.ts | 81 + .../data/lovelace/config/badge.ts | 12 +- .../data/lovelace/config/card.ts | 19 +- .../data/lovelace/config/section.ts | 39 +- .../data/lovelace/config/strategy.ts | 7 - .../data/lovelace/config/types.ts | 11 +- .../data/lovelace/config/view.ts | 101 +- src/types/homeassistant/data/registry.ts | 6 - src/types/homeassistant/data/translations.ts | 117 +- src/types/homeassistant/data/ws-themes.ts | 38 +- .../panels/common/validate-condition.ts | 58 +- .../panels/lovelace/cards/types.ts | 51 +- .../homeassistant/panels/lovelace/types.ts | 28 +- src/types/homeassistant/types.ts | 300 +--- .../lovelace-mushroom/cards/chips-card.ts | 8 +- .../cards/fan-card-config.ts | 30 +- src/types/strategy/cards.ts | 72 + src/types/strategy/generic.ts | 367 ++++ src/types/strategy/views.ts | 17 + src/utillties/filters.ts | 64 + src/views/AbstractView.ts | 208 ++- src/views/CameraView.ts | 97 +- src/views/ClimateView.ts | 101 +- src/views/CoverView.ts | 104 +- src/views/FanView.ts | 102 +- src/views/HomeView.ts | 379 ++-- src/views/LightView.ts | 100 +- src/views/SceneView.ts | 69 +- src/views/SwitchView.ts | 102 +- src/views/VacuumView.ts | 102 +- 45 files changed, 2890 insertions(+), 3223 deletions(-) delete mode 100644 dist/mushroom-strategy.js create mode 100644 src/Helper.ts create mode 100644 src/cards/ControllerCard.ts create mode 100644 src/types/homeassistant/data/lovelace.ts create mode 100644 src/types/strategy/cards.ts create mode 100644 src/types/strategy/generic.ts create mode 100644 src/types/strategy/views.ts create mode 100644 src/utillties/filters.ts diff --git a/dist/mushroom-strategy.js b/dist/mushroom-strategy.js deleted file mode 100644 index 618c1c3..0000000 --- a/dist/mushroom-strategy.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{var e,t,i={245:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("fan.fans"),path:"fans",icon:"mdi:fan",subview:!1,headerCardConfiguration:{iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("fan.all_fans"),subtitle:`${n.O.getCountTemplate(s.domain,"eq","on")} ${(0,r.k)("fan.fans")} `+(0,r.k)("generic.on")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="fan";const a=s},845:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-person-card",layout:"vertical",primary_info:"none",secondary_info:"none",icon_type:"entity-picture"}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},1122:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>c});var n=i(4137),r=i(1241),o=i(3007),s=i(6964),a=i(2682);const c=class{get domain(){return this.constructor.domain}constructor(){this.baseConfiguration={icon:"mdi:view-dashboard",subview:!1},this.viewHeaderCardConfiguration={cards:[],type:""},r.O.initialized||(0,s.OG)(s.ZY,"Registry not initialized!")}async createCardConfigurations(){const e=[],t=(0,o.p)(this.domain+"Card"),s=(await i(2560)(`./${t}`)).default,c=new a.A(r.O.entities).whereDomain(this.domain).where((e=>!e.entity_id.endsWith("_stateful_scene"))).toList();for(const t of r.O.areas){const i=[];let o={area_id:[t.area_id]};const l=new a.A(c).whereAreaId(t.area_id).toList();if("undisclosed"===t.area_id&&(o={entity_id:l.map((e=>e.entity_id))}),i.push(...l.map((e=>new s(e,r.O.strategyOptions.card_options?.[e.entity_id]).getCard()))),i.length){const r="headerCardConfiguration"in this.baseConfiguration?this.baseConfiguration.headerCardConfiguration:{};i.unshift(new n.default(o,{title:t.name,...r}).createCard()),e.push({type:"vertical-stack",cards:i})}}return this.viewHeaderCardConfiguration.cards.length&&e.length&&e.unshift(this.viewHeaderCardConfiguration),e}async getView(){return{...this.baseConfiguration,cards:await this.createCardConfigurations()}}getDomainTargets(){return{entity_id:r.O.entities.filter((e=>e.entity_id.startsWith(this.domain+"."))).map((e=>e.entity_id))}}initializeViewConfig(e,t={},i){this.baseConfiguration={...this.baseConfiguration,...e,...t},this.viewHeaderCardConfiguration=new n.default(this.getDomainTargets(),{..."headerCardConfiguration"in this.baseConfiguration?this.baseConfiguration.headerCardConfiguration:{},...i}).createCard()}}},1151:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(5982);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-entity-card",icon:"mdi:power-cycle",icon_color:"green"}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},1216:(e,t,i)=>{var n={"./AbstractChip":[8222,792],"./AbstractChip.ts":[8222,792],"./ClimateChip":[7757,792],"./ClimateChip.ts":[7757,792],"./CoverChip":[9589,792],"./CoverChip.ts":[9589,792],"./FanChip":[1867,792],"./FanChip.ts":[1867,792],"./LightChip":[3122,792],"./LightChip.ts":[3122,792],"./SwitchChip":[9908,792],"./SwitchChip.ts":[9908,792],"./WeatherChip":[1778,792],"./WeatherChip.ts":[1778,792]};function r(e){if(!i.o(n,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=n[e],r=t[0];return i.e(t[1]).then((()=>i(r)))}r.keys=()=>Object.keys(n),r.id=1216,e.exports=r},1241:(e,t,i)=>{"use strict";i.d(t,{O:()=>l});var n=i(4744),r=i.n(n),o=i(2700),s=i(6964),a=i(8976),c=i(2682);class l{constructor(){}static get strategyOptions(){return l._strategyOptions}static get areas(){return l._areas}static get devices(){return l._devices}static get entities(){return l._entities}static get hassStates(){return l._hassStates}static get initialized(){return l._initialized}static async initialize(e){(0,a.A)(e.hass),l._hassStates=e.hass.states;const{ConfigurationDefaults:t}=await Promise.resolve().then(i.bind(i,7688));try{l._strategyOptions=r()(t,e.config?.strategy?.options??{})}catch(e){(0,s.OG)(s.ZY,"Error importing strategy options!",e)}(0,s.Ux)(l.strategyOptions.debug?s.ZY:s.QC);try{[l._entities,l._devices,l._areas]=await Promise.all([e.hass.callWS({type:"config/entity_registry/list"}),e.hass.callWS({type:"config/device_registry/list"}),e.hass.callWS({type:"config/area_registry/list"})])}catch(e){(0,s.OG)(s.ZY,"Error importing Home Assistant registries!",e)}l._strategyOptions.extra_views.map((e=>({...e,subview:!1}))),l._entities=new c.A(l.entities).not().whereEntityCategory("config").not().whereEntityCategory("diagnostic").isNotHidden().whereDisabledBy(null).orderBy(["name","original_name"],"asc").toList(),l._entities=l.entities.map((e=>({...e,area_id:e.area_id??"undisclosed"}))),l._devices=new c.A(l.devices).isNotHidden().whereDisabledBy(null).orderBy(["name_by_user","name"],"asc").toList(),l._devices=l.devices.map((e=>({...e,area_id:e.area_id??"undisclosed"}))),l.strategyOptions.areas._?.hidden?l._areas=[]:(l.strategyOptions.areas.undisclosed?.hidden||l.areas.push(t.areas.undisclosed),l._areas=l.areas.map((e=>({...e,...l.strategyOptions.areas._,...l.strategyOptions.areas?.[e.area_id]}))),l.strategyOptions.areas.undisclosed.area_id="undisclosed",l._areas=new c.A(l.areas).isNotHidden().orderBy(["order","name"],"asc").toList()),(()=>{const e=Object.entries(l.strategyOptions.views);l.strategyOptions.views=Object.fromEntries(e.sort((([e,t],[i,n])=>(t.order??1/0)-(n.order??1/0)||(t.title??"").localeCompare(n.title??""))))})(),(()=>{const e=Object.entries(l.strategyOptions.domains);l.strategyOptions.domains=Object.fromEntries(e.sort((([,e],[,t])=>(0,o.vv)(e)&&(0,o.vv)(t)?(e.order??1/0)-(t.order??1/0)||(e.title??"").localeCompare(t.title??""):0)))})(),l.strategyOptions.extra_views.sort(((e,t)=>(e.order??1/0)-(t.order??1/0)||(e.title??"").localeCompare(t.title??""))),l._initialized=!0}static getCountTemplate(e,t,i){const n=[];return l.initialized?(n.push(...new c.A(l.entities).whereDomain(e).where((e=>!e.entity_id.endsWith("_stateful_scene"))).toList().map((e=>`states['${e.entity_id}']`))),`{% set entities = [${n}] %}\n {{ entities\n | selectattr('state','${t}','${i}')\n | selectattr('state','ne','unavailable')\n | selectattr('state','ne','unknown')\n | list\n | count\n }}`):((0,s.OG)(s.br,"Registry not initialized!"),"?")}static getExposedNames(e){if("chip"===e)return Object.entries(l.strategyOptions.chips).filter((([e,t])=>!0===t)).map((([e])=>e.split("_")[0]));const t=l.strategyOptions[`${e}s`];return Object.keys(t).filter((e=>"_"!==e&&"default"!==e&&!t[e].hidden))}}l._areas=[],l._initialized=!1},1255:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("camera.cameras"),path:"cameras",icon:"mdi:cctv",subview:!1,headerCardConfiguration:{showControls:!1}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("camera.all_cameras"),subtitle:`${n.O.getCountTemplate(s.domain,"ne","off")} ${(0,r.k)("camera.cameras")} `+(0,r.k)("generic.busy")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="camera";const a=s},1778:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(8222);class r extends n.default{static getDefaultConfig(e){return{type:"weather",entity:e,show_temperature:!0,show_conditions:!0}}constructor(e,t){super(),this.configuration={...this.configuration,...r.getDefaultConfig(e),...t}}}const o=r},1810:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(7212);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-select-card",icon:void 0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},1867:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8222),o=i(2682);class s extends r.default{static getDefaultConfig(){return{type:"template",icon:"mdi:fan",icon_color:"green",content:n.O.getCountTemplate("fan","eq","on"),tap_action:{action:"perform-action",perform_action:"fan.turn_off",target:{entity_id:new o.A(n.O.entities).whereDomain("fan").getValuesByProperty("entity_id")}},hold_action:{action:"navigate",navigation_path:"fans"}}}constructor(e){super(),this.configuration={...this.configuration,...s.getDefaultConfig(),...e}}}const a=s},2137:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-number-card",icon:void 0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},2464:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("switch.switches"),path:"switches",icon:"mdi:dip-switch",subview:!1,headerCardConfiguration:{iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("switch.all_switches"),subtitle:`${n.O.getCountTemplate(s.domain,"eq","on")} ${(0,r.k)("switch.switches")} `+(0,r.k)("generic.on")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="switch";const a=s},2560:(e,t,i)=>{var n={"./AbstractCard":[4818],"./AbstractCard.ts":[4818],"./AreaCard":[4301,792],"./AreaCard.ts":[4301,792],"./BinarySensorCard":[1151,792],"./BinarySensorCard.ts":[1151,792],"./CameraCard":[2723,792],"./CameraCard.ts":[2723,792],"./ClimateCard":[8721,792],"./ClimateCard.ts":[8721,792],"./CoverCard":[8033,792],"./CoverCard.ts":[8033,792],"./FanCard":[2667,792],"./FanCard.ts":[2667,792],"./HaAreaCard":[9042,792],"./HaAreaCard.ts":[9042,792],"./HeaderCard":[4137],"./HeaderCard.ts":[4137],"./InputSelectCard":[1810,792],"./InputSelectCard.ts":[1810,792],"./LightCard":[8254,792],"./LightCard.ts":[8254,792],"./LockCard":[6555,792],"./LockCard.ts":[6555,792],"./MediaPlayerCard":[9879,792],"./MediaPlayerCard.ts":[9879,792],"./MiscellaneousCard":[4486,792],"./MiscellaneousCard.ts":[4486,792],"./NumberCard":[2137,792],"./NumberCard.ts":[2137,792],"./PersonCard":[845,792],"./PersonCard.ts":[845,792],"./SceneCard":[2858,792],"./SceneCard.ts":[2858,792],"./SelectCard":[7212,792],"./SelectCard.ts":[7212,792],"./SensorCard":[5982],"./SensorCard.ts":[5982],"./SwitchCard":[3708,792],"./SwitchCard.ts":[3708,792],"./VacuumCard":[7430,792],"./VacuumCard.ts":[7430,792],"./ValveCard":[8868,792],"./ValveCard.ts":[8868,792]};function r(e){if(!i.o(n,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=n[e],r=t[0];return Promise.all(t.slice(1).map(i.e)).then((()=>i(r)))}r.keys=()=>Object.keys(n),r.id=2560,e.exports=r},2667:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-fan-card",icon:void 0,show_percentage_control:!0,show_oscillate_control:!0,icon_animation:!0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},2682:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(1241),r=i(6964);class o{constructor(e){this.filters=[],this.invertNext=!1,this.entries=e,this.entryIdentifier=0===e.length||"entity_id"in e[0]?"entity_id":"floor_id"in e[0]?"floor_id":"id"}not(){return this.invertNext=!this.invertNext,this}resetFilters(){return this.filters=[],this.invertNext=!1,this}where(e){return this.filters.push(this.checkInversion(e)),this}whereAreaId(e,t=!0){return this.filters.push(this.checkInversion((i=>{let r;const o=i;return t&&o.device_id&&(r=n.O.devices.find((e=>e.id===o.device_id))?.area_id),"undisclosed"===e?i.area_id===e&&(r===e||void 0===r):void 0===e?void 0===i.area_id&&(!t||void 0===r):i.area_id===e||t&&r===e}))),this}whereNameContains(e){const t=e.toLowerCase();return this.filters.push(this.checkInversion((e=>{const i=e;return[i.name,i.original_name,i.name_by_user].filter((e=>"string"==typeof e)).some((e=>e.toLowerCase().includes(t)))}))),this}whereDomain(e){const t=e+".";return this.filters.push(this.checkInversion((e=>"entity_id"in e&&e.entity_id.startsWith(t)))),this}whereFloorId(e){return this.filters.push(this.checkInversion((t=>{const i="floor_id"in t;return void 0===e?!i:i&&t.floor_id===e}))),this}whereDeviceId(e){return this.filters.push(this.checkInversion((t=>{const i="id"in t,n="device_id"in t;return void 0===e?!i&&!n:i&&t.id===e||n&&t.device_id===e}))),this}whereEntityId(e){return this.filters.push(this.checkInversion((t=>void 0===e?!("entity_id"in t):"entity_id"in t&&t.entity_id===e))),this}whereDisabledBy(e){return this.filters.push(this.checkInversion((t=>{const i="disabled_by"in t;return void 0===e?!i:i&&t.disabled_by===e}))),this}whereHiddenBy(e){return this.filters.push(this.checkInversion((t=>{const i="hidden_by"in t;return void 0===e?!i:i&&t.hidden_by===e}))),this}isNotHidden(e=!0){return this.filters.push(this.checkInversion((t=>{const i="hidden_by"in t&&t.hidden_by;if(!e)return!i;const r=t[this.entryIdentifier],o=!0===n.O.strategyOptions?.card_options?.[r]?.hidden;return!i&&!o}))),this}whereEntityCategory(e){const t=this.invertNext;return this.invertNext=!1,this.filters.push((i=>{const r="entity_category"in i?i.entity_category:void 0,o="string"==typeof r?n.O.strategyOptions?.domains?._?.[`hide_${r}_entities`]:void 0;return!0!==o&&(!1===o&&r===e||(t?r!==e:r===e))})),this}orderBy(e,t="asc"){const i=(e,t)=>{for(const i of t){const t=e[i];if(null!=t)return t}},n=[...this.entries].sort(((n,r)=>{const o=i(n,e),s=i(r,e);if(o===s)return 0;const a="asc"===t?1:-1;return null==o?a:null==s?-a:"string"==typeof o&&"string"==typeof s?o.localeCompare(s)*a:(oii>=t)),this}toList(){const e=new o(this.entries);return e.filters=[...this.filters],e.entries.filter(((t,i)=>e.filters.every((e=>e(t,i)))))}getValuesByProperty(e){return this.toList().map((t=>t[e])).filter((e=>void 0!==e))}first(){const e=new o(this.entries);return e.filters=[...this.filters],e.entries.find(((t,i)=>e.filters.every((e=>e(t,i)))))}single(){const e=new o(this.entries);e.filters=[...this.filters];const t=e.entries.filter(((t,i)=>e.filters.every((e=>e(t,i)))));if(1===t.length)return t[0];(0,r.OG)(r.br,`Expected a single element, but found ${t.length}.`)}count(){const e=new o(this.entries);return e.filters=[...this.filters],e.entries.filter(((t,i)=>e.filters.every((e=>e(t,i))))).length}checkInversion(e){return this.invertNext?(this.invertNext=!1,t=>!e(t)):e}}const s=o},2700:(e,t,i)=>{"use strict";i.d(t,{dQ:()=>u,eq:()=>a,ln:()=>d,uS:()=>l,vv:()=>s});const n=["_","binary_sensor","camera","climate","cover","default","fan","input_select","light","lock","media_player","number","scene","select","sensor","switch","vacuum"],r=["camera","climate","cover","fan","home","light","lock","scene","switch","vacuum"],o=["light","fan","cover","switch","climate","weather"];function s(e){return e&&("order"in e||"title"in e||"name"in e)}function a(e){return!!e&&("perform-action"===e.action||"call-service"===e.action)&&"perform_action"in e}function c(e,t){return t.includes(e)}function l(e){return c(e,r)}function u(e){return c(e,n)}function d(e){return c(e,o)}},2723:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{entity:"",type:"picture-entity",show_name:!1,show_state:!1,camera_view:"live"}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},2737:(e,t,i)=>{"use strict";function n(e,t){const i=(e,t)=>{const i=[];for(let n=0;nn})},2858:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>c});var n=i(1241),r=i(4818),o=i(3708),s=i(2700);class a extends r.default{static getDefaultConfig(){return{type:"custom:mushroom-entity-card",tap_action:{action:"perform-action",perform_action:"scene.turn_on",target:{}}}}constructor(e,t){const i=e.entity_id.split(".").pop(),r=n.O.entities.find((e=>e.entity_id===`switch.${i}_stateful_scene`));if(super(r??e),r)return void(this.configuration=new o.default(r).getCard());const c=a.getDefaultConfig();(0,s.eq)(c.tap_action)&&(c.tap_action.target={entity_id:e.entity_id}),c.icon=n.O.hassStates[e.entity_id]?.attributes.icon??c.icon,this.configuration={...this.configuration,...c,...t}}}const c=a},3007:(e,t,i)=>{"use strict";function n(e){return e.replace(/^([a-z])|([-_][a-z])/g,(e=>e.toUpperCase().replace(/[-_]/g,"")))}function r(e){if("function"==typeof structuredClone)try{return structuredClone(e)}catch{}try{return JSON.parse(JSON.stringify(e))}catch{return e}}i.d(t,{G:()=>r,p:()=>n})},3122:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8222),o=i(2682);class s extends r.default{static getDefaultConfig(){return{type:"template",icon:"mdi:lightbulb-group",icon_color:"amber",content:n.O.getCountTemplate("light","eq","on"),tap_action:{action:"perform-action",perform_action:"light.turn_off",target:{entity_id:new o.A(n.O.entities).whereDomain("light").getValuesByProperty("entity_id")}},hold_action:{action:"navigate",navigation_path:"lights"}}}constructor(e){super(),this.configuration={...this.configuration,...s.getDefaultConfig(),...e}}}const a=s},3708:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-entity-card",icon:void 0,tap_action:{action:"toggle"}}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},4137:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>r});class n{static getDefaultConfig(){return{type:"custom:mushroom-title-card",showControls:!0,iconOn:"mdi:power-on",iconOff:"mdi:power-off",onService:"none",offService:"none"}}constructor(e,t){this.target=e,this.configuration={...n.getDefaultConfig(),...t}}createCard(){const e=[{type:"custom:mushroom-title-card",title:this.configuration.title,subtitle:this.configuration.subtitle}];return this.configuration.showControls&&e.push({type:"horizontal-stack",cards:[{type:"custom:mushroom-template-card",icon:this.configuration.iconOff,layout:"vertical",icon_color:"red",tap_action:{action:"call-service",service:this.configuration.offService,target:this.target,data:{}}},{type:"custom:mushroom-template-card",icon:this.configuration.iconOn,layout:"vertical",icon_color:"amber",tap_action:{action:"call-service",service:this.configuration.onService,target:this.target,data:{}}}]}),{type:"horizontal-stack",cards:e}}}const r=n},4301:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-template-card",primary:void 0,icon:"mdi:floor-plan",icon_color:"blue",tap_action:{action:"navigate",navigation_path:""},hold_action:{action:"none"}}}constructor(e,t){super(e);const i=r.getDefaultConfig();let n=t;i.primary=e.name,i.icon=e.icon||i.icon,i.tap_action&&"navigation_path"in i.tap_action&&(i.tap_action.navigation_path=e.area_id),n&&"default"===n.type&&(n={...n,type:i.type}),this.configuration={...this.configuration,...i,...n}}}const o=r},4486:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-entity-card",icon_color:"blue-grey"}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},4744:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===i}(e)}(e)},i="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function n(e,t){return!1!==t.clone&&t.isMergeableObject(e)?a((i=e,Array.isArray(i)?[]:{}),e,t):e;var i}function r(e,t,i){return e.concat(t).map((function(e){return n(e,i)}))}function o(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function s(e,t){try{return t in e}catch(e){return!1}}function a(e,i,c){(c=c||{}).arrayMerge=c.arrayMerge||r,c.isMergeableObject=c.isMergeableObject||t,c.cloneUnlessOtherwiseSpecified=n;var l=Array.isArray(i);return l===Array.isArray(e)?l?c.arrayMerge(e,i,c):function(e,t,i){var r={};return i.isMergeableObject(e)&&o(e).forEach((function(t){r[t]=n(e[t],i)})),o(t).forEach((function(o){(function(e,t){return s(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,o)||(s(e,o)&&i.isMergeableObject(t[o])?r[o]=function(e,t){if(!t.customMerge)return a;var i=t.customMerge(e);return"function"==typeof i?i:a}(o,i)(e[o],t[o],i):r[o]=n(t[o],i))})),r}(e,i,c):n(i,c)}a.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,i){return a(e,i,t)}),{})};var c=a;e.exports=c},4818:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(1241),r=i(6964);const o=class{constructor(e){this.configuration={type:"custom:mushroom-entity-card",icon:"mdi:help-circle"},n.O.initialized||(0,r.OG)(r.ZY,"Registry not initialized!"),this.entity=e}getCard(){return{...this.configuration,entity:"entity_id"in this.entity?this.entity.entity_id:void 0}}}},5982:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-entity-card",icon:"mdi:information",animate:!0,line_color:"green"}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},6555:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-lock-card",icon:void 0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},6559:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("climate.climates"),path:"climates",icon:"mdi:thermostat",subview:!1,headerCardConfiguration:{showControls:!1}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("climate.all_climates"),subtitle:`${n.O.getCountTemplate(s.domain,"ne","off")} ${(0,r.k)("climate.climates")} `+(0,r.k)("generic.busy")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="climate";const a=s},6964:(e,t,i)=>{"use strict";i.d(t,{OG:()=>g,QC:()=>o,Ux:()=>f,ZY:()=>u,br:()=>c,nR:()=>l,tm:()=>a});var n,r=i(3007);!function(e){e[e.Off=0]="Off",e[e.Debug=1]="Debug",e[e.Info=2]="Info",e[e.Warn=3]="Warn",e[e.Error=4]="Error",e[e.Fatal=5]="Fatal"}(n||(n={}));const{Off:o,Debug:s,Info:a,Warn:c,Error:l,Fatal:u}=n;let d=n.Fatal;function f(e){d=e}function g(e,t,...i){if(d===n.Off||e>d)return;const o="Mushroom Strategy - An error occurred. Check the console (F12) for details.",s=`[${n[e].toUpperCase()}]`,a=i.map(r.G),c=`[at ${function(e){if(!e)return"unknown";const t=e.split("\n").filter(Boolean);for(let e=1;e{"use strict";i.r(t),i.d(t,{default:()=>s});var n=i(8976),r=i(1122);class o extends r.default{static getDefaultConfig(){return{title:(0,n.k)("scene.scenes"),path:"scenes",icon:"mdi:palette",subview:!1,headerCardConfiguration:{showControls:!1}}}static getViewHeaderCardConfig(){return{}}constructor(e){super(),this.initializeViewConfig(o.getDefaultConfig(),e,o.getViewHeaderCardConfig())}}o.domain="scene";const s=o},7212:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-select-card",icon:void 0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},7430:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>s});const n=["on_off","start_pause","stop","locate","clean_spot","return_home"];var r=i(4818);class o extends r.default{static getDefaultConfig(){return{type:"custom:mushroom-vacuum-card",icon:void 0,icon_animation:!0,commands:[...n],tap_action:{action:"more-info"}}}constructor(e,t){super(e),this.configuration={...this.configuration,...o.getDefaultConfig(),...t}}}const s=o},7547:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("cover.covers"),path:"covers",icon:"mdi:window-open",subview:!1,headerCardConfiguration:{iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("cover.all_covers"),subtitle:`${n.O.getCountTemplate(s.domain,"search","(open|opening|closing)")} ${(0,r.k)("cover.covers")} ${(0,r.k)("generic.unclosed")}`}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="cover";const a=s},7688:(e,t,i)=>{"use strict";i.d(t,{ConfigurationDefaults:()=>r});var n=i(8976);const r={areas:{undisclosed:{aliases:[],area_id:"undisclosed",created_at:0,floor_id:null,hidden:!1,humidity_entity_id:null,icon:"mdi:floor-plan",labels:[],modified_at:0,name:(0,n.k)("generic.undisclosed"),picture:null,temperature_entity_id:null}},card_options:{},chips:{weather_entity:"auto",light_count:!0,fan_count:!0,cover_count:!0,switch_count:!0,climate_count:!0,extra_chips:[]},debug:!1,domains:{_:{hide_config_entities:void 0,hide_diagnostic_entities:void 0},binary_sensor:{title:`${(0,n.k)("sensor.binary")} `+(0,n.k)("sensor.sensors"),showControls:!1,hidden:!1},camera:{title:(0,n.k)("camera.cameras"),showControls:!1,hidden:!1},climate:{title:(0,n.k)("climate.climates"),showControls:!1,hidden:!1},cover:{title:(0,n.k)("cover.covers"),showControls:!0,iconOn:"mdi:arrow-up",iconOff:"mdi:arrow-down",onService:"cover.open_cover",offService:"cover.close_cover",hidden:!1},default:{title:(0,n.k)("generic.miscellaneous"),showControls:!1,hidden:!1},fan:{title:(0,n.k)("fan.fans"),showControls:!0,iconOn:"mdi:fan",iconOff:"mdi:fan-off",onService:"fan.turn_on",offService:"fan.turn_off",hidden:!1},input_select:{title:(0,n.k)("input_select.input_selects"),showControls:!1,hidden:!1},light:{title:(0,n.k)("light.lights"),showControls:!0,iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off",hidden:!1},lock:{title:(0,n.k)("lock.locks"),showControls:!1,hidden:!1},media_player:{title:(0,n.k)("media_player.media_players"),showControls:!1,hidden:!1},number:{title:(0,n.k)("generic.numbers"),showControls:!1,hidden:!1},scene:{title:(0,n.k)("scene.scenes"),showControls:!1,onService:"scene.turn_on",hidden:!1},select:{title:(0,n.k)("select.selects"),showControls:!1,hidden:!1},sensor:{title:(0,n.k)("sensor.sensors"),showControls:!1,hidden:!1},switch:{title:(0,n.k)("switch.switches"),showControls:!0,iconOn:"mdi:power-plug",iconOff:"mdi:power-plug-off",onService:"switch.turn_on",offService:"switch.turn_off",hidden:!1},vacuum:{title:(0,n.k)("vacuum.vacuums"),showControls:!0,hidden:!1}},extra_cards:[],extra_views:[],home_view:{hidden:[]},views:{camera:{order:7,hidden:!1},climate:{order:6,hidden:!1},cover:{order:4,hidden:!1},fan:{order:3,hidden:!1},home:{order:1,hidden:!1},light:{order:2,hidden:!1},lock:{order:10,hidden:!1},scene:{order:9,hidden:!1},switch:{order:5,hidden:!1},vacuum:{order:8,hidden:!1}},quick_access_cards:[]}},7757:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>s});var n=i(1241),r=i(8222);class o extends r.default{static getDefaultConfig(){return{type:"template",icon:"mdi:thermostat",icon_color:"orange",content:n.O.getCountTemplate("climate","ne","off"),tap_action:{action:"none"},hold_action:{action:"navigate",navigation_path:"climates"}}}constructor(e){super(),this.configuration={...this.configuration,...o.getDefaultConfig(),...e}}}const s=o},7935:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("locks.locks"),path:"locks",icon:"mdi:lock-open",subview:!1,headerCardConfiguration:{iconOn:"mdi:lock-open",iconOff:"mdi:lock",onService:"lock.lock",offService:"lock.unlock"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("lock.all_locks"),subtitle:`${n.O.getCountTemplate(s.domain,"ne","locked")} ${(0,r.k)("lock.locks")} `+(0,r.k)("lock.unlocked")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="lock";const a=s},8033:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-cover-card",icon:void 0,show_buttons_control:!0,show_position_control:!0,show_tilt_position_control:!0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},8222:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(1241),r=i(6964);const o=class{constructor(){this.configuration={type:"template"},n.O.initialized||(0,r.OG)(r.ZY,"Registry not initialized!")}getChipConfiguration(){return this.configuration}}},8254:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>s});var n=i(2700),r=i(4818);class o extends r.default{static getDefaultConfig(){return{type:"custom:mushroom-light-card",icon:void 0,show_brightness_control:!0,show_color_control:!0,show_color_temp_control:!0,use_light_color:!0,double_tap_action:{action:"call-service",perform_action:"light.turn_on",target:{entity_id:void 0},data:{rgb_color:[255,255,255]}}}}constructor(e,t){super(e);const i=o.getDefaultConfig();(0,n.eq)(i.double_tap_action)&&(i.double_tap_action.target={entity_id:e.entity_id}),this.configuration={...this.configuration,...i,...t}}}const s=o},8499:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("vacuum.vacuums"),path:"vacuums",icon:"mdi:robot-vacuum",subview:!1,headerCardConfiguration:{iconOn:"mdi:robot-vacuum",iconOff:"mdi:robot-vacuum-off",onService:"vacuum.start",offService:"vacuum.stop"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("vacuum.all_vacuums"),subtitle:`${n.O.getCountTemplate(s.domain,"in","[cleaning, returning]")} ${(0,r.k)("vacuum.vacuums")} `+(0,r.k)("generic.busy")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="vacuum";const a=s},8721:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-climate-card",icon:void 0,hvac_modes:["off","cool","heat","fan_only"],show_temperature_control:!0}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},8868:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>s});var n=i(8976),r=i(4818);class o extends r.default{static getDefaultConfig(){return{type:"custom:mushroom-template-card",icon:"mdi:valve",icon_color:"blue",double_tap_action:{action:"toggle"}}}constructor(e,t){super(e);const i=o.getDefaultConfig();i.entity=e.entity_id,i.icon=e.icon??i.icon,i.primary=e.name??e.original_name??"?",i.secondary=`{% \n set mapping = {\n 'open': '${(0,n.k)("valve.open")}',\n 'opening': '${(0,n.k)("valve.opening")}',\n 'closed': '${(0,n.k)("valve.closed")}',\n 'closing': '${(0,n.k)("valve.closing")}',\n 'stopped': '${(0,n.k)("valve.stopped")}',\n 'unavailable': '${(0,n.k)("generic.unavailable")}'\n }\n %}\n {{ mapping.get(states('${e.entity_id}'), '${(0,n.k)("generic.unknown")}') }}`,this.configuration={...this.configuration,...i,...t}}}const s=o},8976:(e,t,i)=>{"use strict";i.d(t,{A:()=>m,k:()=>_});const n=JSON.parse('{"camera":{"all_cameras":"Alle Kameras","cameras":"Kameras"},"climate":{"all_climates":"Alle Klimaanlagen","climates":"Klimaanlagen"},"cover":{"all_covers":"Alle Abdeckungen","covers":"Abdeckungen"},"fan":{"all_fans":"Alle Ventilatoren","fans":"Ventilatoren"},"generic":{"all":"Alle","areas":"Bereiche","busy":"Beschäftigt","good_afternoon":"Guten Nachmittag","good_evening":"Guten Abend","good_morning":"Guten Morgen","hello":"Hallo","home":"Start","miscellaneous":"Sonstiges","numbers":"Zahlen","off":"Aus","on":"Ein","open":"Offen","unavailable":"Nicht verfügbar","unclosed":"Nicht Geschlossen","undisclosed":"Sonstiges","unknown":"Unbekannt"},"input_select":{"input_selects":"Auswahl-Eingaben"},"light":{"all_lights":"Alle Leuchten","lights":"Leuchten"},"lock":{"locked":"Gesperrt","all_locks":"Alle Schlösser","locks":"Schlösser","unlocked":"Entsperrt"},"media_player":{"media_players":"Wiedergabegeräte"},"scene":{"scenes":"Szenen"},"select":{"selects":"Auswahlen"},"sensor":{"binary":"Binäre","sensors":"Sensoren"},"switch":{"all_switches":"Alle Schalter","switches":"Schalter"},"vacuum":{"all_vacuums":"Alle Staubsauger","vacuums":"Staubsauger"},"valve":{"all_valves":"Alle Ventile","valves":"Ventile","open":"Offen","opening":"Öffnet","closed":"Geschlossen","closing":"Schließt","stopped":"Gestoppt"}}');var r=i.t(n,2);const o=JSON.parse('{"camera":{"all_cameras":"All Cameras","cameras":"Cameras"},"climate":{"all_climates":"All Climates","climates":"Climates"},"cover":{"all_covers":"All Covers","covers":"Covers"},"fan":{"all_fans":"All Fans","fans":"Fans"},"generic":{"all":"All","areas":"Areas","busy":"Busy","good_afternoon":"Good afternoon","good_evening":"Good evening","good_morning":"Good morning","hello":"Hello","home":"Home","miscellaneous":"Miscellaneous","numbers":"Numbers","off":"Off","on":"On","open":"Open","unavailable":"Unavailable","unclosed":"Unclosed","undisclosed":"Other","unknown":"Unknown"},"input_select":{"input_selects":"Input Selects"},"light":{"all_lights":"All Lights","lights":"Lights"},"lock":{"all_locks":"All Locks","locked":"Locked","locks":"Locks","unlocked":"Unlocked"},"media_player":{"media_players":"Media Players"},"scene":{"scenes":"Scenes"},"select":{"selects":"Selects"},"sensor":{"binary":"Binary","sensors":"Sensors"},"switch":{"all_switches":"All Switches","switches":"Switches"},"vacuum":{"all_vacuums":"All Vacuums","vacuums":"Vacuums"},"valve":{"all_valves":"All Valves","valves":"Valves","open":"Open","opening":"Opening","closed":"Closed","closing":"Closing","stopped":"Stopped"}}');var s=i.t(o,2);const a=JSON.parse('{"camera":{"all_cameras":"Todas las Cámaras","cameras":"Cámaras"},"climate":{"all_climates":"Todos los Termostatos","climates":"Termostatos"},"cover":{"all_covers":"Todas las Cubiertas","covers":"Cubiertas"},"fan":{"all_fans":"Todos los Ventiladores","fans":"Ventiladores"},"generic":{"all":"Todo","areas":"Áreas","busy":"Ocupado","good_afternoon":"Buenas tardes","good_evening":"Buenas noches","good_morning":"Buenos días","hello":"Hola","home":"Inicio","miscellaneous":"Varios","numbers":"Números","off":"Apagado","on":"Encendido","open":"Abierto","unavailable":"No Disponible","unclosed":"Sin Cerrar","undisclosed":"Varios","unknown":"Desconocido"},"input_select":{"input_selects":"Selecciones de Entrada"},"light":{"all_lights":"Todas las Luces","lights":"Luces"},"lock":{"all_locks":"Todas las Candados","locked":"Locked","locks":"Candados","unlocked":"Desbloqueado"},"media_player":{"media_players":"Reproductores Multimedia"},"scene":{"scenes":"Scenas"},"select":{"selects":"Seleccionar"},"sensor":{"binary":"Binario","sensors":"Sensores"},"switch":{"all_switches":"Todos los Apagadores","switches":"Apagadores"},"vacuum":{"all_vacuums":"Todas las Aspiradoras","vacuums":"Aspiradoras"},"valve":{"all_valves":"Todas las válvulas","valves":"Válvulas","open":"Abierta","opening":"Abriendo","closed":"Cerrada","closing":"Cerrando","stopped":"Detenida"}}');var c=i.t(a,2);const l=JSON.parse('{"camera":{"all_cameras":"Alle Cameras","cameras":"Cameras"},"climate":{"all_climates":"Alle Klimaatregelingen","climates":"Klimaatregelingen"},"cover":{"all_covers":"Alle Bedekkingen","covers":"Bedekkingen"},"fan":{"all_fans":"Alle Ventilatoren","fans":"Ventilatoren"},"generic":{"all":"Alle","areas":"Ruimtes","busy":"Bezig","good_afternoon":"Goedemiddag","good_evening":"Goedeavond","good_morning":"Goedemorgen","hello":"Hallo","home":"Start","miscellaneous":"Overige","numbers":"Nummers","off":"Uit","on":"Aan","open":"Open","unavailable":"Onbeschikbaar","unclosed":"Niet Gesloten","undisclosed":"Overige","unknown":"Onbekend"},"input_select":{"input_selects":"Lijsten"},"light":{"all_lights":"Alle Lampen","lights":"Lampen"},"lock":{"all_locks":"Alle Sloten","locked":"Vergrendeld","locks":"Sloten","unlocked":"Ontgrendeld"},"media_player":{"media_players":"Mediaspelers"},"scene":{"scenes":"Scenes"},"select":{"selects":"Statuslijsten"},"sensor":{"binary":"Binaire","sensors":"Sensoren"},"switch":{"all_switches":"Alle Schakelaars","switches":"Schakelaars"},"vacuum":{"all_vacuums":"Alle Afzuiging","vacuums":"Afzuiging"},"valve":{"all_valves":"Alle kleppen","valves":"Kleppen","open":"Open","opening":"Openen","closed":"Gesloten","closing":"Sluiten","stopped":"Gestopt"}}');var u=i.t(l,2),d=i(6964);const f={de:r,en:s,es:c,nl:u},g="en";function h(e,t){try{return e.split(".").reduce(((e,t)=>e[t]),f[t])}catch{return}}let p;function m(e){const t=e?.locale.language??g;p=e=>h(e,t)??h(e,g)??e}function _(e){return p?p(e):((0,d.OG)(d.br,"localize is not initialized! Call setupCustomLocalize first."),e)}},9031:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>f});var n=i(1241),r=i(2700),o=i(3007),s=i(6964),a=i(8976),c=i(1122),l=i(2682),u=i(2737);class d extends c.default{static getDefaultConfig(){return{title:(0,a.k)("generic.home"),icon:"mdi:home-assistant",path:"home",subview:!1}}constructor(e){super(),this.baseConfiguration={...this.baseConfiguration,...d.getDefaultConfig(),...e}}async createCardConfigurations(){const e=[];let t,i,r;try{[t,i,r]=await Promise.all([this.createChipsSection(),this.createPersonsSection(),this.createAreasSection()])}catch(t){return(0,s.OG)(s.nR,"Error importing created sections!",t),e}return t&&e.push(t),i&&e.push(i),"greeting"in n.O.strategyOptions.home_view.hidden||e.push({type:"custom:mushroom-template-card",primary:`{% set time = now().hour %}\n {% if (time >= 18) %}\n ${(0,a.k)("generic.good_evening")},{{user}}!\n {% elif (time >= 12) %}\n ${(0,a.k)("generic.good_afternoon")}, {{user}}!\n {% elif (time >= 6) %}\n ${(0,a.k)("generic.good_morning")}, {{user}}!\n {% else %}\n ${(0,a.k)("generic.hello")}, {{user}}! {% endif %}`,icon:"mdi:hand-wave",icon_color:"orange",tap_action:{action:"none"},double_tap_action:{action:"none"},hold_action:{action:"none"}}),n.O.strategyOptions.quick_access_cards&&e.push(...n.O.strategyOptions.quick_access_cards),r&&e.push(r),n.O.strategyOptions.extra_cards&&e.push(...n.O.strategyOptions.extra_cards),e}async createChipsSection(){if(n.O.strategyOptions.home_view.hidden.includes("chips"))return;const e=[],t=n.O.getExposedNames("chip");let a;const c="auto"===n.O.strategyOptions.chips.weather_entity?n.O.entities.find((e=>e.entity_id.startsWith("weather.")))?.entity_id:n.O.strategyOptions.chips.weather_entity;if(c)try{a=(await Promise.resolve().then(i.bind(i,1778))).default;const t=new a(c);e.push(t.getChipConfiguration())}catch(e){(0,s.OG)(s.nR,"Error importing chip weather!",e)}else(0,s.OG)(s.tm,"Weather chip has no entities available.");for(const c of t){if(!(0,r.ln)(c)||!new l.A(n.O.entities).whereDomain(c).count()){(0,s.OG)(s.tm,`Chip for domain ${c} is unsupported or has no entities available.`);continue}const t=(0,o.p)(c+"Chip");try{a=(await i(1216)(`./${t}`)).default;const n=new a;e.push(n.getChipConfiguration())}catch(e){(0,s.OG)(s.nR,`Error importing chip ${c}!`,e)}}return n.O.strategyOptions.chips?.extra_chips&&e.push(...n.O.strategyOptions.chips.extra_chips),{type:"custom:mushroom-chips-card",alignment:"center",chips:e}}async createPersonsSection(){if(n.O.strategyOptions.home_view.hidden.includes("persons"))return;const e=[],t=(await Promise.resolve().then(i.bind(i,845))).default;return e.push(...n.O.entities.filter((e=>e.entity_id.startsWith("person."))).map((e=>new t(e).getCard()))),{type:"vertical-stack",cards:(0,u.P)(e)}}async createAreasSection(){if(n.O.strategyOptions.home_view.hidden.includes("areas"))return;const e=[];let t=!0;for(const r of n.O.areas){const o=n.O.strategyOptions.areas[r.area_id]?.type??n.O.strategyOptions.areas._?.type??"default";let a;t=t&&"default"===o;try{a=(await i(2560)(`./${o}`)).default}catch(e){a=(await Promise.resolve().then(i.bind(i,4301))).default,n.O.strategyOptions.debug&&"default"!==o&&(0,s.OG)(s.nR,`Error importing ${o}: card!`,e)}e.push(new a(r).getCard())}return{type:"vertical-stack",title:n.O.strategyOptions.home_view.hidden.includes("areasTitle")?void 0:(0,a.k)("generic.areas"),cards:(0,u.P)(e,{area:1,"custom:mushroom-template-card":2})}}}d.domain="home";const f=d},9042:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"area",area:""}}constructor(e,t){super(e);const i=r.getDefaultConfig();i.area=e.area_id,i.navigation_path=i.area,this.configuration={...this.configuration,...i,...t,type:i.type}}}const o=r},9304:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8976),o=i(1122);class s extends o.default{static getDefaultConfig(){return{title:(0,r.k)("light.lights"),path:"lights",icon:"mdi:lightbulb-group",subview:!1,headerCardConfiguration:{iconOn:"mdi:lightbulb",iconOff:"mdi:lightbulb-off",onService:"light.turn_on",offService:"light.turn_off"}}}static getViewHeaderCardConfig(){return{title:(0,r.k)("light.all_lights"),subtitle:`${n.O.getCountTemplate(s.domain,"eq","on")} ${(0,r.k)("light.lights")} `+(0,r.k)("generic.on")}}constructor(e){super(),this.initializeViewConfig(s.getDefaultConfig(),e,s.getViewHeaderCardConfig())}}s.domain="light";const a=s},9555:(e,t,i)=>{var n={"./AbstractView":[1122,792],"./AbstractView.ts":[1122,792],"./CameraView":[1255,792],"./CameraView.ts":[1255,792],"./ClimateView":[6559,792],"./ClimateView.ts":[6559,792],"./CoverView":[7547,792],"./CoverView.ts":[7547,792],"./FanView":[245,792],"./FanView.ts":[245,792],"./HomeView":[9031,792],"./HomeView.ts":[9031,792],"./LightView":[9304,792],"./LightView.ts":[9304,792],"./LockView":[7935,792],"./LockView.ts":[7935,792],"./SceneView":[7204,792],"./SceneView.ts":[7204,792],"./SwitchView":[2464,792],"./SwitchView.ts":[2464,792],"./VacuumView":[8499,792],"./VacuumView.ts":[8499,792]};function r(e){if(!i.o(n,e))return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=n[e],r=t[0];return i.e(t[1]).then((()=>i(r)))}r.keys=()=>Object.keys(n),r.id=9555,e.exports=r},9589:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>s});var n=i(1241),r=i(8222);class o extends r.default{static getDefaultConfig(){return{type:"template",icon:"mdi:window-open",icon_color:"cyan",content:n.O.getCountTemplate("cover","search","(open|opening|closing)"),tap_action:{action:"none"},hold_action:{action:"navigate",navigation_path:"covers"}}}constructor(e){super(),this.configuration={...this.configuration,...o.getDefaultConfig(),...e}}}const s=o},9879:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>o});var n=i(4818);class r extends n.default{static getDefaultConfig(){return{type:"custom:mushroom-media-player-card",use_media_info:!0,media_controls:["on_off","play_pause_stop"],show_volume_level:!0,volume_controls:["volume_mute","volume_set","volume_buttons"]}}constructor(e,t){super(e),this.configuration={...this.configuration,...r.getDefaultConfig(),...t}}}const o=r},9908:(e,t,i)=>{"use strict";i.r(t),i.d(t,{default:()=>a});var n=i(1241),r=i(8222),o=i(2682);class s extends r.default{static getDefaultConfig(){return{type:"template",icon:"mdi:dip-switch",icon_color:"blue",content:n.O.getCountTemplate("switch","eq","on"),tap_action:{action:"perform-action",perform_action:"switch.turn_off",target:{entity_id:new o.A(n.O.entities).whereDomain("switch").getValuesByProperty("entity_id")}},hold_action:{action:"navigate",navigation_path:"switches"}}}constructor(e){super(),this.configuration={...this.configuration,...s.getDefaultConfig(),...e}}}const a=s}},n={};function r(e){var t=n[e];if(void 0!==t)return t.exports;var o=n[e]={exports:{}};return i[e](o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(i,n){if(1&n&&(i=this(i)),8&n)return i;if("object"==typeof i&&i){if(4&n&&i.__esModule)return i;if(16&n&&"function"==typeof i.then)return i}var o=Object.create(null);r.r(o);var s={};e=e||[null,t({}),t([]),t(t)];for(var a=2&n&&i;"object"==typeof a&&!~e.indexOf(a);a=t(a))Object.getOwnPropertyNames(a).forEach((e=>s[e]=()=>i[e]));return s.default=()=>i,r.d(o,s),o},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.e=()=>Promise.resolve(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e=r(4137),t=r(5982),i=r(1241),n=r(2700),o=r(3007),s=r(6964),a=r(2682),c=r(2737);class l extends HTMLTemplateElement{static async generateDashboard(e){await i.O.initialize(e);const t=[],a=i.O.getExposedNames("view").filter(n.uS).map((async e=>{try{const t=(0,o.p)(`${e}View`),n=new(0,(await r(9555)(`./${t}`)).default)(i.O.strategyOptions.views[e]),s=await n.getView();if(s.cards.length)return s}catch(t){(0,s.OG)(s.nR,`Error importing ${e} view!`,t)}return null})),c=(await Promise.all(a)).filter(Boolean);return t.push(...c),t.push(...i.O.areas.map((e=>({title:e.name,path:e.area_id,subview:!0,strategy:{type:"custom:mushroom-strategy",options:{area:e}}})))),i.O.strategyOptions.extra_views&&t.push(...i.O.strategyOptions.extra_views),{views:t}}static async generateView(l){const u=i.O.getExposedNames("domain"),d=l.view.strategy?.options?.area??{},f=new a.A(i.O.entities).whereAreaId(d.area_id).toList(),g=[...d.extra_cards??[]],h={area_id:[d.area_id]},p=u.filter(n.dQ).map((async n=>{const l=(0,o.p)(n+"Card"),u=new a.A(f).whereDomain(n).where((e=>!("switch"===n&&e.entity_id.endsWith("_stateful_scene")))).toList();if(!u.length)return null;const d=new e.default({entity_id:u.map((e=>e.entity_id))},i.O.strategyOptions.domains[n]).createCard();try{const e=(await r(2560)(`./${l}`)).default;if("sensor"===n){const e=u.filter((e=>i.O.hassStates[e.entity_id]?.attributes.unit_of_measurement)).map((e=>{const n={...e.device_id&&i.O.strategyOptions.card_options?.[e.device_id],...i.O.strategyOptions.card_options?.[e.entity_id],type:"custom:mini-graph-card",entities:[e.entity_id]};return new t.default(e,n).getCard()}));return e.length?{type:"vertical-stack",cards:[d,...e]}:null}let o=u.map((t=>{const n={...t.device_id&&i.O.strategyOptions.card_options?.[t.device_id],...i.O.strategyOptions.card_options?.[t.entity_id]};return new e(t,n).getCard()}));return"binary_sensor"===n&&(o=(0,c.P)(o)),o.length?{type:"vertical-stack",cards:[d,...o]}:null}catch(e){return(0,s.OG)(s.nR,`Error creating card configurations for domain ${n}`,e),null}})),m=(await Promise.all(p)).filter(Boolean);if(g.push(...m),!i.O.strategyOptions.domains.default.hidden){const t=new a.A(f).not().where((e=>(0,n.dQ)(e.entity_id.split(".",1)[0]))).toList();if(t.length)try{const n=(await Promise.resolve().then(r.bind(r,4486))).default,o=[new e.default(h,i.O.strategyOptions.domains.default).createCard(),...t.map((e=>new n(e,i.O.strategyOptions.card_options?.[e.entity_id]).getCard()))];g.push({type:"vertical-stack",cards:o})}catch(e){(0,s.OG)(s.nR,"Error creating card configurations for domain `miscellaneous`",e)}}return{cards:g}}}customElements.define("ll-strategy-mushroom-strategy",l),console.info("%c Mushroom Strategy %c ".concat("v2.3.0"," "),"color: white; background: coral; font-weight: 700;","color: coral; background: white; font-weight: 700;")})()})(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3a0d13d..3abdb49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,25 @@ { "name": "mushroom-strategy", - "version": "2.3.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mushroom-strategy", - "version": "2.3.0-alpha.1", + "version": "2.2.0", "license": "MIT", "dependencies": { "deepmerge": "^4" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.31.0", - "@typescript-eslint/parser": "^8.31.0", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-prettier": "^5.2.6", - "home-assistant-js-websocket": "^9.5.0", - "prettier": "^3.5.3", - "superstruct": "^2.0.2", - "ts-loader": "^9.5.2", - "ts-node": "^10.9.2", - "typescript": "^5.8.3", + "home-assistant-js-websocket": "^9", + "superstruct": "^1", + "ts-loader": "^9.5.0", + "ts-node": "^10", + "typescript": "^5", "version-bump-prompt": "^6", - "webpack": "^5.99.7", - "webpack-cli": "^6.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/DigiLive" + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4" } }, "node_modules/@cspotcode/source-map-support": { @@ -55,258 +45,18 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.17.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", - "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.13.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=10.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "license": "MIT", "dependencies": { @@ -437,19 +187,6 @@ "node": ">= 8" } }, - "node_modules/@pkgr/core": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", - "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -519,199 +256,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -874,45 +418,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", - "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=18.12.0" + "node": ">=14.15.0" }, "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", - "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "license": "MIT", "engines": { - "node": ">=18.12.0" + "node": ">=14.15.0" }, "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", - "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=18.12.0" + "node": ">=14.15.0" }, "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -947,16 +488,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/acorn-walk": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", @@ -983,48 +514,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "ajv": "^6.9.1" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1070,13 +569,6 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", @@ -1095,23 +587,6 @@ "node": ">=8" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1171,16 +646,6 @@ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "dev": true }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001680", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", @@ -1259,7 +724,6 @@ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, - "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -1315,13 +779,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1343,31 +800,6 @@ "node": ">= 8" } }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1443,11 +875,10 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", "dev": true, - "license": "MIT", "bin": { "envinfo": "dist/cli.js" }, @@ -1480,111 +911,6 @@ "node": ">=0.8.0" } }, - "node_modules/eslint": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", - "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.25.1", - "@eslint/plugin-kit": "^0.2.8", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", - "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -1598,228 +924,6 @@ "node": ">=8.0.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -1850,16 +954,6 @@ "node": ">=4.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -1890,13 +984,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -1920,30 +1007,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -1977,19 +1040,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2033,32 +1083,10 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2087,19 +1115,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -2126,13 +1141,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2155,11 +1163,10 @@ } }, "node_modules/home-assistant-js-websocket": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-9.5.0.tgz", - "integrity": "sha512-gCBX0ulIPW3o5EhYxyNKgztBwNIs7u4hypZ+rcWir65wjLKL3b5uDx1jzU7RceyuANxvVKwBVLNujBeFohjnUw==", - "dev": true, - "license": "Apache-2.0" + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-9.1.0.tgz", + "integrity": "sha512-R2LEMX0h5r6lfDydrobgHaA/HkZv45B8UHC96j9oLPJ1qETSfSmWLy8AF/RthjT+6kWWnZDlt7VU1EfNVT0wuQ==", + "dev": true }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -2174,42 +1181,14 @@ } }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -2229,16 +1208,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -2329,7 +1298,6 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -2360,7 +1328,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2396,26 +1363,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -2429,47 +1376,15 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -2503,13 +1418,6 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2526,6 +1434,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2592,42 +1512,12 @@ "node": ">=6" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2656,24 +1546,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -2719,19 +1591,6 @@ "node": ">=6" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2796,45 +1655,6 @@ "node": ">=8" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2887,16 +1707,6 @@ "node": ">= 10.13.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3030,16 +1840,15 @@ "dev": true }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { "node": ">= 10.13.0" @@ -3049,49 +1858,14 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -3114,7 +1888,6 @@ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, - "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -3223,25 +1996,11 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/superstruct": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", - "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz", + "integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -3270,30 +2029,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/synckit": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", - "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.3", - "tslib": "^2.8.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/synckit/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -3304,9 +2039,9 @@ } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3323,17 +2058,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -3388,25 +2123,11 @@ "node": ">=8.0" } }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/ts-loader": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", - "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", @@ -3423,11 +2144,10 @@ } }, "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -3472,19 +2192,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -3507,11 +2214,10 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3612,18 +2318,17 @@ } }, "node_modules/webpack": { - "version": "5.99.7", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", - "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", @@ -3637,9 +2342,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -3660,40 +2365,43 @@ } }, "node_modules/webpack-cli": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", - "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.6.1", - "@webpack-cli/configtest": "^3.0.1", - "@webpack-cli/info": "^3.0.1", - "@webpack-cli/serve": "^3.0.1", + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^12.1.0", + "commander": "^10.0.1", "cross-spawn": "^7.0.3", - "envinfo": "^7.14.0", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^6.0.1" + "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=18.12.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.82.0" + "webpack": "5.x.x" }, "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, "webpack-bundle-analyzer": { "optional": true }, @@ -3703,28 +2411,26 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, - "license": "MIT", "engines": { - "node": ">=18" + "node": ">=14" } }, "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, - "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.1" + "wildcard": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=10.0.0" } }, "node_modules/webpack-sources": { @@ -3755,18 +2461,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yn": { "version": "3.1.1", @@ -3776,19 +2477,6 @@ "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index 96c9489..f766e7b 100644 --- a/package.json +++ b/package.json @@ -1,49 +1,38 @@ { "name": "mushroom-strategy", - "version": "2.3.0", - "description": "Automatically generate a dashboard of Mushroom cards.", + "version": "2.2.1", + "description": "Automatically create a dashboard using Mushroom cards", "keywords": [ - "dashboard", "strategy", "mushroom" ], - "homepage": "https://github.com/DigiLive/mushroom-strategy", - "bugs": "https://github.com/DigiLive/mushroom-strategy/issues", + "homepage": "https://github.com/AalianKhan/mushroom-strategy", + "bugs": "https://github.com/AalianKhan/mushroom-strategy/issues", "license": "MIT", "author": { - "name": "Ferry Cools" + "name": "Aalian Khan" }, "contributors": [ { - "name": "Aalian Khan" + "name": "Ferry Cools" } ], - "funding": { - "type": "github", - "url": "https://github.com/sponsors/DigiLive" - }, "repository": { "type": "git", - "url": "https://github.com/DigiLive/mushroom-strategy" + "url": "https://github.com/AalianKhan/mushroom-strategy" }, "dependencies": { "deepmerge": "^4" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.31.0", - "@typescript-eslint/parser": "^8.31.0", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-prettier": "^5.2.6", - "home-assistant-js-websocket": "^9.5.0", - "prettier": "^3.5.3", - "superstruct": "^2.0.2", - "ts-loader": "^9.5.2", - "ts-node": "^10.9.2", - "typescript": "^5.8.3", + "home-assistant-js-websocket": "^9", + "superstruct": "^1", + "ts-loader": "^9.5.0", + "ts-node": "^10", + "typescript": "^5", "version-bump-prompt": "^6", - "webpack": "^5.99.7", - "webpack-cli": "^6.0.1" + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4" }, "scripts": { "build-dev": "webpack --config webpack.dev.config.ts", diff --git a/src/Helper.ts b/src/Helper.ts new file mode 100644 index 0000000..1b109e8 --- /dev/null +++ b/src/Helper.ts @@ -0,0 +1,522 @@ +import {getConfigurationDefaults} from "./configurationDefaults"; +import {HassEntities, HassEntity} from "home-assistant-js-websocket"; +import deepmerge from "deepmerge"; +import {EntityRegistryEntry} from "./types/homeassistant/data/entity_registry"; +import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry"; +import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry"; +import {generic} from "./types/strategy/generic"; +import setupCustomLocalize from "./localize"; +import {applyEntityCategoryFilters} from "./utillties/filters"; +import StrategyArea = generic.StrategyArea; +import ViewConfig = generic.StrategyViewConfig; +import SupportedDomains = generic.SupportedDomains; +import supportedViews = generic.SupportedViews; +import isSortable = generic.isSortable; +import AllDomainsConfig = generic.AllDomainsConfig; +import SingleDomainConfig = generic.SingleDomainConfig; +import isSupportedView = generic.isSupportedView; +import isSupportedDomain = generic.isSupportedDomain; + +/** + * Helper Class + * + * Contains the objects of Home Assistant's registries and helper methods. + */ +class Helper { + /** + * An array of entities from Home Assistant's entity registry. + * + * @type {EntityRegistryEntry[]} + * @private + */ + static #entities: EntityRegistryEntry[]; + + /** + * An array of entities from Home Assistant's device registry. + * + * @type {DeviceRegistryEntry[]} + * @private + */ + static #devices: DeviceRegistryEntry[]; + + /** + * An array of entities from Home Assistant's area registry. + * + * @type {StrategyArea[]} + * @private + */ + static #areas: StrategyArea[] = []; + + /** + * An array of state entities from Home Assistant's Hass-object. + * + * @type {HassEntities} + * @private + */ + static #hassStates: HassEntities; + + /** + * Indicates whether this module is initialized. + * + * @type {boolean} True if initialized. + * @private + */ + static #initialized: boolean = false; + + /** + * The Custom strategy configuration. + * + * @type {generic.StrategyConfig} + * @private + */ + static #strategyOptions: generic.StrategyConfig; + + /** + * Set to true for more verbose information in the console. + * + * @type {boolean} + * @private + */ + static #debug: boolean; + static customLocalize: Function; + + /** + * Class constructor. + * + * This class shouldn't be instantiated directly. + * Instead, it should be initialized with method initialize(). + * + * @throws {Error} If trying to instantiate this class. + */ + constructor() { + throw new Error("This class should be invoked with method initialize() instead of using the keyword new!"); + } + + /** + * Custom strategy configuration. + * + * @returns {generic.StrategyConfig} + * @static + */ + static get strategyOptions(): generic.StrategyConfig { + return this.#strategyOptions; + } + + /** + * Get the entities from Home Assistant's area registry. + * + * @returns {StrategyArea[]} + * @static + */ + static get areas(): StrategyArea[] { + return this.#areas; + } + + /** + * Get the devices from Home Assistant's device registry. + * + * @returns {DeviceRegistryEntry[]} + * @static + */ + static get devices(): DeviceRegistryEntry[] { + return this.#devices; + } + + /** + * Get the entities from Home Assistant's entity registry. + * + * @returns {EntityRegistryEntry[]} + * @static + */ + static get entities(): EntityRegistryEntry[] { + return this.#entities; + } + + /** + * Get the current debug mode of the mushroom strategy. + * + * @returns {boolean} + * @static + */ + static get debug(): boolean { + return this.#debug; + } + + /** + * Initialize this module. + * + * @param {generic.DashboardInfo} info Strategy information object. + * @returns {Promise} + * @static + */ + static async initialize(info: generic.DashboardInfo): Promise { + // Initialize properties. + this.customLocalize = setupCustomLocalize(info.hass); + + const configurationDefaults = getConfigurationDefaults(this.customLocalize) + this.#strategyOptions = deepmerge(configurationDefaults, info.config?.strategy?.options ?? {}); + + this.#hassStates = info.hass.states; + this.#debug = this.#strategyOptions.debug; + + try { + // Query the registries of Home Assistant. + + // noinspection ES6MissingAwait False positive? https://youtrack.jetbrains.com/issue/WEB-63746 + [Helper.#entities, Helper.#devices, Helper.#areas] = await Promise.all([ + info.hass.callWS({type: "config/entity_registry/list"}) as Promise, + info.hass.callWS({type: "config/device_registry/list"}) as Promise, + info.hass.callWS({type: "config/area_registry/list"}) as Promise, + ]); + } catch (e) { + Helper.logError("An error occurred while querying Home assistant's registries!", e); + throw 'Check the console for details'; + } + + // Create and add the undisclosed area if not hidden in the strategy options. + if (!this.#strategyOptions.areas.undisclosed?.hidden) { + this.#strategyOptions.areas.undisclosed = { + ...configurationDefaults.areas.undisclosed, + ...this.#strategyOptions.areas.undisclosed, + }; + + // Make sure the custom configuration of the undisclosed area doesn't overwrite the area_id. + this.#strategyOptions.areas.undisclosed.area_id = "undisclosed"; + + this.#areas.push(this.#strategyOptions.areas.undisclosed); + } + + // Merge custom areas of the strategy options into strategy areas. + this.#areas = Helper.areas.map(area => { + return {...area, ...this.#strategyOptions.areas?.[area.area_id]}; + }); + + // Sort strategy areas by order first and then by name. + this.#areas.sort((a, b) => { + return (a.order ?? Infinity) - (b.order ?? Infinity) || a.name.localeCompare(b.name); + }); + + // Sort custom and default views of the strategy options by order first and then by title. + this.#strategyOptions.views = Object.fromEntries( + Object.entries(this.#strategyOptions.views).sort(([, a], [, b]) => { + const viewA = a as ViewConfig; + const viewB = b as ViewConfig; + + return (viewA.order ?? Infinity) - (viewB.order ?? Infinity) + || (viewA.title ?? "undefined").localeCompare(viewB.title ?? "undefined"); + }), + ) as Record; + + // Sort custom and default domains of the strategy options by order first and then by title. + this.#strategyOptions.domains = Object.fromEntries( + Object.entries(this.#strategyOptions.domains).sort(([, a], [, b]) => { + if (isSortable(a) && isSortable(b)) { + const orderA = ('order' in a) ? a.order ?? Infinity : Infinity; + const orderB = ('order' in b) ? b.order ?? Infinity : Infinity; + + return orderA - orderB || (a.title ?? "undefined").localeCompare(b.title ?? "undefined"); + } + + return 0; + }), + ) as { [K in SupportedDomains]: K extends "_" ? AllDomainsConfig : SingleDomainConfig; }; + + this.#initialized = true; + } + + /** + * Get the initialization status of the Helper class. + * + * @returns {boolean} True if this module is initialized. + * @static + */ + static isInitialized(): boolean { + return this.#initialized; + } + + /** + * Get a template string to define the number of a given domain's entities with a certain state. + * + * States are compared against a given value by a given operator. + * States `unavailable` and `unknown` are always excluded. + * + * @param {string} domain The domain of the entities. + * @param {string} operator The comparison operator between state and value. + * @param {string} value The value to which the state is compared against. + * + * @return {string} The template string. + * @static + */ + static getCountTemplate(domain: SupportedDomains, operator: string, value: string): string { + // noinspection JSMismatchedCollectionQueryUpdate + /** + * Array of entity state-entries, filtered by domain. + * + * Each element contains a template-string which is used to access home assistant's state machine (state object) in + * a template. + * E.g. "states['light.kitchen']" + * + * The array excludes hidden and disabled entities. + * + * @type {string[]} + */ + const states: string[] = []; + + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + // Get the state of entities which are linked to the given area. + for (const area of this.#areas) { + let entities = this.getDeviceEntities(area, domain); + + // Exclude hidden Config and Diagnostic entities. + entities = applyEntityCategoryFilters(entities, domain); + + const newStates = entities.map((entity) => `states['${entity.entity_id}']`); + + states.push(...newStates); + } + + return ( + `{% set entities = [${states}] %} + {{ entities + | selectattr('state','${operator}','${value}') + | selectattr('state','ne','unavailable') + | selectattr('state','ne','unknown') + | list + | count + }}` + ); + } + + /** + * Get device entities from the entity registry, filtered by area and domain. + * + * The entity registry is a registry where Home-Assistant keeps track of all entities. + * A device is represented in Home Assistant via one or more entities. + * + * The result excludes hidden and disabled entities. + * + * @param {AreaRegistryEntry} area Area entity. + * @param {string} [domain] The domain of the entity-id. + * + * @return {EntityRegistryEntry[]} Array of device entities. + * @static + */ + static getDeviceEntities(area: AreaRegistryEntry, domain?: string): EntityRegistryEntry[] { + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + // Get the ID of the devices which are linked to the given area. + const areaDeviceIds = this.#devices.filter((device) => { + return (device.area_id ?? "undisclosed") === area.area_id; + }).map((device: DeviceRegistryEntry) => { + + return device.id; + }); + + // Return the entities of which all conditions of the callback function are met. @see areaFilterCallback. + return this.#entities.filter( + this.#areaFilterCallback, { + area: area, + domain: domain, + areaDeviceIds: areaDeviceIds, + }) + .sort((a, b) => { + return (a.original_name ?? "undefined").localeCompare(b.original_name ?? "undefined"); + }); + } + + /** + * Get state entities, filtered by area and domain. + * + * The result excludes hidden and disabled entities. + * + * @param {AreaRegistryEntry} area Area entity. + * @param {string} domain Domain of the entity-id. + * + * @return {HassEntity[]} Array of state entities. + */ + static getStateEntities(area: AreaRegistryEntry, domain: string): HassEntity[] { + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + const states: HassEntity[] = []; + + // Create a map for the hassEntities and devices {id: object} to improve lookup speed. + const entityMap: { + [s: string]: EntityRegistryEntry; + } = Object.fromEntries(this.#entities.map((entity) => [entity.entity_id, entity])); + const deviceMap: { + [s: string]: DeviceRegistryEntry; + } = Object.fromEntries(this.#devices.map((device) => [device.id, device])); + + // Get states whose entity-id starts with the given string. + const stateEntities = Object.values(this.#hassStates).filter( + (state) => state.entity_id.startsWith(`${domain}.`), + ); + + for (const state of stateEntities) { + const hassEntity = entityMap[state.entity_id]; + const device = deviceMap[hassEntity?.device_id ?? ""]; + + // Collect states of which any (whichever comes first) of the conditions below are met: + // 1. The linked entity is linked to the given area. + // 2. The entity is linked to a device, and the linked device is linked to the given area. + if ( + (hassEntity?.area_id === area.area_id) + || (device && device.area_id === area.area_id) + ) { + states.push(state); + } + } + + return states; + } + + /** + * Get the state object of a HASS entity. + * + * @param {EntityRegistryEntry} entity The entity for which to get the state. + * @returns {HassEntity | undefined} The state object of the entity, or undefined if not found. + * @static + */ + static getEntityState(entity: EntityRegistryEntry): HassEntity | undefined { + return this.#hassStates[entity.entity_id]; + } + + /** + * Sanitize a classname. + * + * The name is sanitized by capitalizing the first character of the name or after an underscore. + * Underscores are removed. + * + * @param {string} className Name of the class to sanitize. + * @returns {string} The sanitized classname. + */ + static sanitizeClassName(className: string): string { + className = className.charAt(0).toUpperCase() + className.slice(1); + + return className.replace(/([-_][a-z])/g, (group) => group + .toUpperCase() + .replace("-", "") + .replace("_", ""), + ); + } + + /** + * Get the ids of the views which aren't set to hidden in the strategy options. + * + * @return {SupportedViews[]} An array of view ids. + */ + static getExposedViewIds(): supportedViews[] { + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + const ids = this.#getObjectKeysByPropertyValue(this.#strategyOptions.views, "hidden", false); + + return ids.filter(isSupportedView); + } + + /** + * Get the ids of the domain ids which aren't set to hidden in the strategy options. + * + * @return {SupportedDomains[]} An array of domain ids. + */ + static getExposedDomainIds(): SupportedDomains[] { + if (!this.isInitialized()) { + console.warn("Helper class should be initialized before calling this method!"); + } + + const ids = this.#getObjectKeysByPropertyValue(this.#strategyOptions.domains, "hidden", false); + + return ids.filter(isSupportedDomain); + } + + /** + * Callback function for filtering entities. + * + * Entities of which all the conditions below are met are kept: + * 1. The entity is not hidden and the entity's device is not hidden by the strategy options. + * 2. The entity is not hidden and is not disabled by Hass. + * 3. The entity's domain matches the given domain. + * 4. The entity itself or else the entity's device is linked to the given area. + * + * @param {EntityRegistryEntry} entity The current Hass entity to evaluate. + * @this {AreaFilterContext} + * + * @return {boolean} True to keep the entity. + * @static + */ + static #areaFilterCallback( + this: { + area: AreaRegistryEntry, + areaDeviceIds: string[], + domain: string, + }, + entity: EntityRegistryEntry): boolean { + const cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + const deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"]; + + const entityUnhidden = + !cardOptions?.hidden && !deviceOptions?.hidden // Condition 1. + && entity.hidden_by === null && entity.disabled_by === null; // Condition 2. + const domainMatches = this.domain === undefined || entity.entity_id.startsWith(`${this.domain}.`); // Condition 3. + // Condition 4. + const entityLinked = this.area.area_id === "undisclosed" + // Undisclosed area. + ? !entity.area_id && (this.areaDeviceIds.includes(entity.device_id ?? "") || !entity.device_id) + // Area is a hass entity. Note: entity.area_id is set to null when using device's area. + : entity.area_id === this.area.area_id || (!entity.area_id && this.areaDeviceIds.includes(entity.device_id ?? "")); + + return (entityUnhidden && domainMatches && entityLinked); + } + + /** + * Get the keys of nested objects by its property value. + * + * @param {Object} object An object of objects. + * @param {string|number} property The name of the property to evaluate. + * @param {*} value The value which the property should match. + * + * @return {string[]} An array with keys. + */ + static #getObjectKeysByPropertyValue( + object: { [k: string]: any }, + property: string, value: any + ): string[] { + const keys: string[] = []; + + for (const key of Object.keys(object)) { + if (object[key][property] === value) { + keys.push(key); + } + } + + return keys; + } + + /** + * Logs an error message to the console. + * + * @param {string} userMessage - The error message to display. + * @param {unknown} [e] - (Optional) The error object or additional information. + * + * @return {void} + */ + static logError(userMessage: string, e?: unknown): void { + if (Helper.debug) { + console.error(userMessage, e); + + return; + } + + console.error(userMessage); + } +} + +export {Helper}; diff --git a/src/cards/CameraCard.ts b/src/cards/CameraCard.ts index 161ca45..812ec81 100644 --- a/src/cards/CameraCard.ts +++ b/src/cards/CameraCard.ts @@ -1,37 +1,44 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {PictureEntityCardConfig} from "../types/homeassistant/panels/lovelace/cards/types"; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry'; -import { PictureEntityCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; -import AbstractCard from './AbstractCard'; - /** * Camera Card Class * - * Used to create a card configuration to control an entity of the camera domain. + * Used to create a card for controlling an entity of the camera domain. + * + * @class + * @extends AbstractCard */ class CameraCard extends AbstractCard { - /** Returns the default configuration object for the card. */ - static getDefaultConfig(): PictureEntityCardConfig { - return { - entity: '', - type: 'picture-entity', - show_name: false, - show_state: false, - camera_view: 'live', - }; - } + /** + * Default configuration of the card. + * + * @type {PictureEntityCardConfig} + * @private + */ + #defaultConfig: PictureEntityCardConfig = { + entity: "", + type: "picture-entity", + show_name: false, + show_state: false, + camera_view: "live", + }; /** * Class constructor. * - * @param {EntityRegistryEntry} entity The HASS entity to create a card configuration for. - * @param {PictureEntityCardConfig} [customConfiguration] Custom card configuration. + * @param {EntityRegistryEntry} entity The hass entity to create a card for. + * @param {cards.PictureEntityCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. */ - constructor(entity: EntityRegistryEntry, customConfiguration?: PictureEntityCardConfig) { + constructor(entity: EntityRegistryEntry, options: cards.PictureEntityCardOptions = {}) { super(entity); - this.configuration = { ...this.configuration, ...CameraCard.getDefaultConfig(), ...customConfiguration }; + this.config = Object.assign(this.config, this.#defaultConfig, options); } } -export default CameraCard; +export {CameraCard}; diff --git a/src/cards/ControllerCard.ts b/src/cards/ControllerCard.ts new file mode 100644 index 0000000..b33878c --- /dev/null +++ b/src/cards/ControllerCard.ts @@ -0,0 +1,102 @@ +import {cards} from "../types/strategy/cards"; +import {LovelaceCardConfig} from "../types/homeassistant/data/lovelace"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {StackCardConfig} from "../types/homeassistant/panels/lovelace/cards/types"; + +/** + * Controller Card class. + * + * Used for creating a Title Card with controls. + * + * @class + */ +class ControllerCard { + /** + * @type {HassServiceTarget} The target to control the entities of. + * @private + */ + readonly #target: HassServiceTarget; + + /** + * Default configuration of the card. + * + * @type {cards.ControllerCardConfig} + * @private + */ + readonly #defaultConfig: cards.ControllerCardConfig = { + type: "mushroom-title-card", + showControls: true, + iconOn: "mdi:power-on", + iconOff: "mdi:power-off", + onService: "none", + offService: "none", + }; + + /** + * Class constructor. + * + * @param {HassServiceTarget} target The target to control the entities of. + * @param {cards.ControllerCardOptions} options Controller Card options. + */ + constructor(target: HassServiceTarget, options: cards.ControllerCardOptions = {}) { + this.#target = target; + this.#defaultConfig = { + ...this.#defaultConfig, + ...options, + }; + } + + /** + * Create a Controller card. + * + * @return {StackCardConfig} A Controller card. + */ + createCard(): StackCardConfig { + const cards: LovelaceCardConfig[] = [ + { + type: "custom:mushroom-title-card", + title: this.#defaultConfig.title, + subtitle: this.#defaultConfig.subtitle, + }, + ]; + + if (this.#defaultConfig.showControls) { + cards.push({ + type: "horizontal-stack", + cards: [ + { + type: "custom:mushroom-template-card", + icon: this.#defaultConfig.iconOff, + layout: "vertical", + icon_color: "red", + tap_action: { + action: "call-service", + service: this.#defaultConfig.offService, + target: this.#target, + data: {}, + }, + }, + { + type: "custom:mushroom-template-card", + icon: this.#defaultConfig.iconOn, + layout: "vertical", + icon_color: "amber", + tap_action: { + action: "call-service", + service: this.#defaultConfig.onService, + target: this.#target, + data: {}, + }, + }, + ], + }); + } + + return { + type: "horizontal-stack", + cards: cards, + }; + } +} + +export {ControllerCard}; diff --git a/src/cards/HaAreaCard.ts b/src/cards/HaAreaCard.ts index 4bdca38..ce31b34 100644 --- a/src/cards/HaAreaCard.ts +++ b/src/cards/HaAreaCard.ts @@ -1,45 +1,49 @@ +import {AbstractCard} from "./AbstractCard"; +import {cards} from "../types/strategy/cards"; +import {AreaRegistryEntry} from "../types/homeassistant/data/area_registry"; +import {AreaCardConfig} from "../types/homeassistant/panels/lovelace/cards/types"; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { AreaRegistryEntry } from '../types/homeassistant/data/area_registry'; -import { AreaCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; -import AbstractCard from './AbstractCard'; - /** * HA Area Card Class * - * Used to create card configuration for an entry of the HASS area registry. + * Used to create a card for an entity of the area domain using the built-in type 'area'. + * + * @class + * @extends AbstractCard */ class AreaCard extends AbstractCard { - /** Returns the default configuration object for the card. */ - static getDefaultConfig(): AreaCardConfig { - return { - type: 'area', - area: '', - }; - } + /** + * Default configuration of the card. + * + * @type {AreaCardConfig} + * @private + */ + #defaultConfig: AreaCardConfig = { + type: "area", + area: "", + }; /** * Class constructor. * - * @param {AreaRegistryEntry} area The HASS entity to create a card configuration for. - * @param {AreaCardConfig} [customConfiguration] Custom card configuration. + * @param {AreaRegistryEntry} area The area entity to create a card for. + * @param {cards.AreaCardOptions} [options={}] Options for the card. + * @throws {Error} If the Helper module isn't initialized. */ - constructor(area: AreaRegistryEntry, customConfiguration?: AreaCardConfig) { + + constructor(area: AreaRegistryEntry, options: cards.AreaCardOptions = {}) { super(area); // Initialize the default configuration. - const configuration = AreaCard.getDefaultConfig(); + this.#defaultConfig.area = area.area_id; + this.#defaultConfig.navigation_path = this.#defaultConfig.area; - configuration.area = area.area_id; - configuration.navigation_path = configuration.area; + // Enforce the card type. + delete options.type; - this.configuration = { - ...this.configuration, - ...configuration, - ...customConfiguration, - type: configuration.type, // Enforce the card type. - }; + this.config = Object.assign(this.config, this.#defaultConfig, options); } } -export default AreaCard; +export {AreaCard}; diff --git a/src/configurationDefaults.ts b/src/configurationDefaults.ts index 6282fb6..2d8ab4b 100644 --- a/src/configurationDefaults.ts +++ b/src/configurationDefaults.ts @@ -1,188 +1,177 @@ -import { StrategyDefaults } from './types/strategy/strategy-generics'; -import { localize } from './utilities/localize'; +import {generic} from "./types/strategy/generic"; +import StrategyDefaults = generic.StrategyDefaults; /** * Default configuration for the mushroom strategy. */ -export const ConfigurationDefaults: StrategyDefaults = { - areas: { - undisclosed: { - // TODO: Refactor undisclosed to other. - aliases: [], - area_id: 'undisclosed', - created_at: 0, - floor_id: null, - hidden: false, - humidity_entity_id: null, - icon: 'mdi:floor-plan', - labels: [], - modified_at: 0, - name: localize('generic.undisclosed'), - picture: null, - temperature_entity_id: null, +export const getConfigurationDefaults = (localize: Function): StrategyDefaults => { + return { + areas: { + undisclosed: { + aliases: [], + area_id: "undisclosed", + created_at: 0, + floor_id: null, + hidden: false, + humidity_entity_id: null, + icon: "mdi:floor-plan", + labels: [], + modified_at: 0, + name: "Undisclosed", + picture: null, + temperature_entity_id: null, + } }, - }, - card_options: {}, - chips: { - // TODO: Make chips sortable. - weather_entity: 'auto', - light_count: true, - fan_count: true, - cover_count: true, - switch_count: true, - climate_count: true, - extra_chips: [], - }, - debug: false, - domains: { - _: { - hide_config_entities: undefined, - hide_diagnostic_entities: undefined, + card_options: {}, + chips: {}, + debug: false, + domains: { + _: { + hide_config_entities: true, + hide_diagnostic_entities: true, + }, + binary_sensor: { + title: `${localize("sensor.binary")} ` + localize("sensor.sensors"), + showControls: false, + hidden: false, + }, + camera: { + title: localize("camera.cameras"), + showControls: false, + hidden: false, + }, + climate: { + title: localize("climate.climates"), + showControls: false, + hidden: false, + }, + cover: { + title: localize("cover.covers"), + showControls: true, + iconOn: "mdi:arrow-up", + iconOff: "mdi:arrow-down", + onService: "cover.open_cover", + offService: "cover.close_cover", + hidden: false, + }, + default: { + title: localize("generic.miscellaneous"), + showControls: false, + hidden: false, + }, + fan: { + title: localize("fan.fans"), + showControls: true, + iconOn: "mdi:fan", + iconOff: "mdi:fan-off", + onService: "fan.turn_on", + offService: "fan.turn_off", + hidden: false, + }, + + input_select: { + title: localize("input_select.input_selects"), + showControls: false, + hidden: false, + }, + light: { + title: localize("light.lights"), + showControls: true, + iconOn: "mdi:lightbulb", + iconOff: "mdi:lightbulb-off", + onService: "light.turn_on", + offService: "light.turn_off", + hidden: false, + }, + lock: { + title: localize("lock.locks"), + showControls: false, + hidden: false, + }, + media_player: { + title: localize("media_player.media_players"), + showControls: false, + hidden: false, + }, + number: { + title: localize("generic.numbers"), + showControls: false, + hidden: false, + }, + scene: { + title: localize("scene.scenes"), + showControls: false, + onService: "scene.turn_on", + hidden: false, + }, + select: { + title: localize("select.selects"), + showControls: false, + hidden: false, + }, + sensor: { + title: localize("sensor.sensors"), + showControls: false, + hidden: false, + }, + switch: { + title: localize("switch.switches"), + showControls: true, + iconOn: "mdi:power-plug", + iconOff: "mdi:power-plug-off", + onService: "switch.turn_on", + offService: "switch.turn_off", + hidden: false, + }, + vacuum: { + title: localize("vacuum.vacuums"), + showControls: true, + hidden: false, + }, }, - binary_sensor: { - title: `${localize('sensor.binary')} ` + localize('sensor.sensors'), - showControls: false, - hidden: false, + extra_cards: [], + extra_views: [], + home_view: { + hidden: [], }, - camera: { - title: localize('camera.cameras'), - showControls: false, - hidden: false, + views: { + camera: { + order: 7, + hidden: false, + }, + climate: { + order: 6, + hidden: false, + }, + cover: { + order: 4, + hidden: false, + }, + fan: { + order: 3, + hidden: false, + }, + home: { + order: 1, + hidden: false, + }, + light: { + order: 2, + hidden: false, + }, + scene: { + order: 9, + hidden: false, + }, + switch: { + order: 5, + hidden: false, + }, + vacuum: { + order: 8, + hidden: false, + }, }, - climate: { - title: localize('climate.climates'), - showControls: false, - hidden: false, - }, - cover: { - title: localize('cover.covers'), - showControls: true, - iconOn: 'mdi:arrow-up', - iconOff: 'mdi:arrow-down', - onService: 'cover.open_cover', - offService: 'cover.close_cover', - hidden: false, - }, - default: { - title: localize('generic.miscellaneous'), - showControls: false, - hidden: false, - }, - fan: { - title: localize('fan.fans'), - showControls: true, - iconOn: 'mdi:fan', - iconOff: 'mdi:fan-off', - onService: 'fan.turn_on', - offService: 'fan.turn_off', - hidden: false, - }, - input_select: { - title: localize('input_select.input_selects'), - showControls: false, - hidden: false, - }, - light: { - title: localize('light.lights'), - showControls: true, - iconOn: 'mdi:lightbulb', - iconOff: 'mdi:lightbulb-off', - onService: 'light.turn_on', - offService: 'light.turn_off', - hidden: false, - }, - lock: { - title: localize('lock.locks'), - showControls: false, - hidden: false, - }, - media_player: { - title: localize('media_player.media_players'), - showControls: false, - hidden: false, - }, - number: { - title: localize('generic.numbers'), - showControls: false, - hidden: false, - }, - scene: { - title: localize('scene.scenes'), - showControls: false, - onService: 'scene.turn_on', - hidden: false, - }, - select: { - title: localize('select.selects'), - showControls: false, - hidden: false, - }, - sensor: { - title: localize('sensor.sensors'), - showControls: false, - hidden: false, - }, - switch: { - title: localize('switch.switches'), - showControls: true, - iconOn: 'mdi:power-plug', - iconOff: 'mdi:power-plug-off', - onService: 'switch.turn_on', - offService: 'switch.turn_off', - hidden: false, - }, - vacuum: { - title: localize('vacuum.vacuums'), - showControls: true, - hidden: false, - }, - }, - extra_cards: [], - extra_views: [], - home_view: { - hidden: [], - }, - views: { - camera: { - order: 7, - hidden: false, - }, - climate: { - order: 6, - hidden: false, - }, - cover: { - order: 4, - hidden: false, - }, - fan: { - order: 3, - hidden: false, - }, - home: { - order: 1, - hidden: false, - }, - light: { - order: 2, - hidden: false, - }, - lock: { - order: 10, - hidden: false, - }, - scene: { - order: 9, - hidden: false, - }, - switch: { - order: 5, - hidden: false, - }, - vacuum: { - order: 8, - hidden: false, - }, - }, - quick_access_cards: [], + quick_access_cards: [] + }; }; diff --git a/src/mushroom-strategy.ts b/src/mushroom-strategy.ts index da7b82f..49f49c5 100644 --- a/src/mushroom-strategy.ts +++ b/src/mushroom-strategy.ts @@ -1,208 +1,272 @@ -import { HassServiceTarget } from 'home-assistant-js-websocket'; -import HeaderCard from './cards/HeaderCard'; -import SensorCard from './cards/SensorCard'; -import { Registry } from './Registry'; -import { LovelaceCardConfig } from './types/homeassistant/data/lovelace/config/card'; -import { LovelaceConfig } from './types/homeassistant/data/lovelace/config/types'; -import { LovelaceViewConfig, LovelaceViewRawConfig } from './types/homeassistant/data/lovelace/config/view'; -import { - DashboardInfo, - isSupportedDomain, - isSupportedView, - StrategyArea, - ViewInfo, -} from './types/strategy/strategy-generics'; -import { sanitizeClassName } from './utilities/auxiliaries'; -import { logMessage, lvlError } from './utilities/debug'; -import RegistryFilter from './utilities/RegistryFilter'; -import { stackHorizontal } from './utilities/cardStacking'; +import {Helper} from "./Helper"; +import {SensorCard} from "./cards/SensorCard"; +import {ControllerCard} from "./cards/ControllerCard"; +import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {applyEntityCategoryFilters} from "./utillties/filters"; +import {LovelaceConfig} from "./types/homeassistant/data/lovelace/config/types"; +import {LovelaceViewConfig, LovelaceViewRawConfig} from "./types/homeassistant/data/lovelace/config/view"; +import {LovelaceCardConfig} from "./types/homeassistant/data/lovelace"; +import {StackCardConfig} from "./types/homeassistant/panels/lovelace/cards/types"; +import {generic} from "./types/strategy/generic"; +import {views} from "./types/strategy/views"; +import ViewConfig = views.ViewConfig; +import StrategyArea = generic.StrategyArea; +import SupportedDomains = generic.SupportedDomains; /** * Mushroom Dashboard Strategy.
*
* Mushroom dashboard strategy provides a strategy for Home-Assistant to create a dashboard automatically.
- * The strategy makes use Mushroom and Mini Graph cards to represent your entities. - * - * @see https://github.com/DigiLive/mushroom-strategy + * The strategy makes use Mushroom and Mini Graph cards to represent your entities.
+ *
+ * Features:
+ * 🛠 Automatically create dashboard with three lines of yaml.
+ * 😍 Built-in Views for several standard domains.
+ * 🎨 Many options to customize to your needs.
+ *
+ * Check the [Repository]{@link https://github.com/AalianKhan/mushroom-strategy} for more information. */ class MushroomStrategy extends HTMLTemplateElement { /** * Generate a dashboard. * - * This method creates views for each exposed domain and area. - * It also adds custom views if specified in the strategy options. - * - * @param {DashboardInfo} info Dashboard strategy information object. - * - * @remarks * Called when opening a dashboard. + * + * @param {generic.DashboardInfo} info Dashboard strategy information object. + * @return {Promise} */ - static async generateDashboard(info: DashboardInfo): Promise { - await Registry.initialize(info); + static async generateDashboard(info: generic.DashboardInfo): Promise { + await Helper.initialize(info); + // Create views. const views: LovelaceViewRawConfig[] = []; - // Parallelize view imports and creation. - const viewPromises = Registry.getExposedNames('view') - .filter(isSupportedView) - .map(async (viewName) => { - try { - const moduleName = sanitizeClassName(`${viewName}View`); - const View = (await import(`./views/${moduleName}`)).default; - const currentView = new View(Registry.strategyOptions.views[viewName]); - const viewConfiguration = await currentView.getView(); + let viewModule; - if (viewConfiguration.cards.length) { - return viewConfiguration; - } - } catch (e) { - logMessage(lvlError, `Error importing ${viewName} view!`, e); + // Create a view for each exposed domain. + for (let viewId of Helper.getExposedViewIds()) { + try { + const viewType = Helper.sanitizeClassName(viewId + "View"); + viewModule = await import(`./views/${viewType}`); + const view: ViewConfig = await new viewModule[viewType](Helper.strategyOptions.views[viewId]).getView(); + + if (view.cards?.length) { + views.push(view); } - - return null; - }); - - const resolvedViews = (await Promise.all(viewPromises)).filter(Boolean) as LovelaceViewRawConfig[]; - - views.push(...resolvedViews); - - // Subviews for areas - views.push( - ...Registry.areas.map((area) => ({ - title: area.name, - path: area.area_id, - subview: true, - strategy: { - type: 'custom:mushroom-strategy', - options: { area }, - }, - })), - ); - - // Extra views - if (Registry.strategyOptions.extra_views) { - views.push(...Registry.strategyOptions.extra_views); + } catch (e) { + Helper.logError(`View '${viewId}' couldn't be loaded!`, e); + } } - return { views }; + // Create subviews for each area. + for (let area of Helper.areas) { + if (!area.hidden) { + views.push({ + title: area.name, + path: area.area_id ?? area.name, + subview: true, + strategy: { + type: "custom:mushroom-strategy", + options: { + area, + }, + }, + }); + } + } + + // Add custom views. + if (Helper.strategyOptions.extra_views) { + views.push(...Helper.strategyOptions.extra_views); + } + + // Return the created views. + return { + views: views, + }; } /** * Generate a view. * - * The method creates cards for each domain (e.g., sensors, switches, etc.) in the current area, using a combination - * of Header cards and entity-specific cards. - * It also handles miscellaneous entities that don't fit into any supported domain. + * Called when opening a subview. * - * @param {ViewInfo} info The view's strategy information object. - * - * @remarks - * Called upon opening a subview. + * @param {generic.ViewInfo} info The view's strategy information object. + * @return {Promise} */ - static async generateView(info: ViewInfo): Promise { - const exposedDomainNames = Registry.getExposedNames('domain'); - const area = info.view.strategy?.options?.area ?? ({} as StrategyArea); - const areaEntities = new RegistryFilter(Registry.entities).whereAreaId(area.area_id).toList(); + static async generateView(info: generic.ViewInfo): Promise { + const exposedDomainIds = Helper.getExposedDomainIds(); + const area = info.view.strategy?.options?.area ?? {} as StrategyArea; const viewCards: LovelaceCardConfig[] = [...(area.extra_cards ?? [])]; - // Set the target for any Header card to the current area. - const target: HassServiceTarget = { area_id: [area.area_id] }; + // Set the target for controller cards to the current area. + let target: HassServiceTarget = { + area_id: [area.area_id], + }; - // Prepare promises for all supported domains - const domainCardPromises = exposedDomainNames.filter(isSupportedDomain).map(async (domain) => { - const moduleName = sanitizeClassName(domain + 'Card'); - - const entities = new RegistryFilter(areaEntities) - .whereDomain(domain) - .where((entity) => !(domain === 'switch' && entity.entity_id.endsWith('_stateful_scene'))) - .toList(); - - if (!entities.length) { - return null; + // Create cards for each domain. + for (const domain of exposedDomainIds) { + if (domain === "default") { + continue; } - const titleCard = new HeaderCard( - { entity_id: entities.map((entity) => entity.entity_id) }, - Registry.strategyOptions.domains[domain], - ).createCard(); + const className = Helper.sanitizeClassName(domain + "Card"); + + let domainCards: EntityCardConfig[] = []; try { - const DomainCard = (await import(`./cards/${moduleName}`)).default; + domainCards = await import(`./cards/${className}`).then(cardModule => { + let domainCards: EntityCardConfig[] = []; + let entities = Helper.getDeviceEntities(area, domain); - if (domain === 'sensor') { - const domainCards = entities - .filter((entity) => Registry.hassStates[entity.entity_id]?.attributes.unit_of_measurement) - .map((entity) => { - const options = { - ...(entity.device_id && Registry.strategyOptions.card_options?.[entity.device_id]), - ...Registry.strategyOptions.card_options?.[entity.entity_id], - type: 'custom:mini-graph-card', - entities: [entity.entity_id], - }; - return new SensorCard(entity, options).getCard(); - }); - return domainCards.length ? { type: 'vertical-stack', cards: [titleCard, ...domainCards] } : null; - } + // Exclude hidden Config and Diagnostic entities. + entities = applyEntityCategoryFilters(entities, domain); - let domainCards = entities.map((entity) => { - const cardOptions = { - ...(entity.device_id && Registry.strategyOptions.card_options?.[entity.device_id]), - ...Registry.strategyOptions.card_options?.[entity.entity_id], - }; - return new DomainCard(entity, cardOptions).getCard(); + // Set the target for controller cards to entities without an area. + if (area.area_id === "undisclosed") { + target = { + entity_id: entities.map(entity => entity.entity_id), + } + } + + if (entities.length) { + // Create a Controller card for the current domain. + const titleCard = new ControllerCard( + target, + Helper.strategyOptions.domains[domain], + ).createCard(); + + if (domain === "sensor") { + // Create a card for each sensor-entity of the current area. + const sensorStates = Helper.getStateEntities(area, "sensor"); + const sensorCards: EntityCardConfig[] = []; + + for (const sensor of entities) { + // Find the state of the current sensor. + const sensorState = sensorStates.find(state => state.entity_id === sensor.entity_id); + let cardOptions = Helper.strategyOptions.card_options?.[sensor.entity_id]; + + if (sensorState?.attributes.unit_of_measurement) { + cardOptions = { + ...{ + type: "custom:mini-graph-card", + entities: [sensor.entity_id], + }, + ...cardOptions, + }; + + sensorCards.push(new SensorCard(sensor, cardOptions).getCard()); + } + } + + if (sensorCards.length) { + domainCards.push({ + type: "vertical-stack", + cards: sensorCards, + }); + + domainCards.unshift(titleCard); + } + + return domainCards; + } + + // Create a card for each other domain-entity of the current area. + for (const entity of entities) { + let deviceOptions; + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + + if (entity.device_id) { + deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id]; + } + + domainCards.push(new cardModule[className](entity, cardOptions).getCard()); + } + + if (domain === "binary_sensor") { + // Horizontally group every two binary sensor cards. + const horizontalCards: EntityCardConfig[] = []; + + for (let i = 0; i < domainCards.length; i += 2) { + horizontalCards.push({ + type: "horizontal-stack", + cards: domainCards.slice(i, i + 2), + }); + } + + domainCards = horizontalCards; + } + + if (domainCards.length) { + domainCards.unshift(titleCard); + } + } + + return domainCards; }); - - if (domain === 'binary_sensor') { - domainCards = stackHorizontal(domainCards); - } - - return domainCards.length ? { type: 'vertical-stack', cards: [titleCard, ...domainCards] } : null; } catch (e) { - logMessage(lvlError, `Error creating card configurations for domain ${domain}`, e); - return null; + Helper.logError("An error occurred while creating the domain cards!", e); } - }); - // Await all domain card stacks - const domainCardStacks = (await Promise.all(domainCardPromises)).filter(Boolean) as LovelaceCardConfig[]; - viewCards.push(...domainCardStacks); - - // Miscellaneous domain - if (!Registry.strategyOptions.domains.default.hidden) { - const miscellaneousEntities = new RegistryFilter(areaEntities) - .not() - .where((entity) => isSupportedDomain(entity.entity_id.split('.', 1)[0])) - .toList(); - - if (miscellaneousEntities.length) { - try { - const MiscellaneousCard = (await import('./cards/MiscellaneousCard')).default; - const miscellaneousCards = [ - new HeaderCard(target, Registry.strategyOptions.domains.default).createCard(), - ...miscellaneousEntities.map((entity) => - new MiscellaneousCard(entity, Registry.strategyOptions.card_options?.[entity.entity_id]).getCard(), - ), - ]; - - viewCards.push({ - type: 'vertical-stack', - cards: miscellaneousCards, - }); - } catch (e) { - logMessage(lvlError, 'Error creating card configurations for domain `miscellaneous`', e); - } + if (domainCards.length) { + viewCards.push({ + type: "vertical-stack", + cards: domainCards, + }); } } - return { cards: viewCards }; + if (!Helper.strategyOptions.domains.default.hidden) { + // Create cards for any other domain. + // Collect entities of the current area and unexposed domains. + let miscellaneousEntities = Helper.getDeviceEntities(area).filter( + entity => !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0] as SupportedDomains) + ); + + // Exclude hidden Config and Diagnostic entities. + miscellaneousEntities = applyEntityCategoryFilters(miscellaneousEntities, "default"); + + // Create a column of miscellaneous entity cards. + if (miscellaneousEntities.length) { + let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = []; + + try { + miscellaneousCards = await import("./cards/MiscellaneousCard").then(cardModule => { + const miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [ + new ControllerCard(target, Helper.strategyOptions.domains.default).createCard(), + ]; + + for (const entity of miscellaneousEntities) { + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + + miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard()); + } + + return miscellaneousCards; + }); + } catch (e) { + Helper.logError("An error occurred while creating the domain cards!", e); + } + + viewCards.push({ + type: "vertical-stack", + cards: miscellaneousCards, + }); + } + } + + // Return cards. + return { + cards: viewCards, + }; } } -customElements.define('ll-strategy-mushroom-strategy', MushroomStrategy); +customElements.define("ll-strategy-mushroom-strategy", MushroomStrategy); -const version = 'v2.3.0'; +const version = "v2.2.1"; console.info( - '%c Mushroom Strategy %c '.concat(version, ' '), - 'color: white; background: coral; font-weight: 700;', - 'color: coral; background: white; font-weight: 700;', + "%c Mushroom Strategy %c ".concat(version, " "), + "color: white; background: coral; font-weight: 700;", "color: coral; background: white; font-weight: 700;" ); diff --git a/src/types/homeassistant/common/translations/localize.ts b/src/types/homeassistant/common/translations/localize.ts index 8ad50fb..96a2a1d 100644 --- a/src/types/homeassistant/common/translations/localize.ts +++ b/src/types/homeassistant/common/translations/localize.ts @@ -1,10 +1,10 @@ -import type { TranslationDict } from '../../types'; +import type {TranslationDict} from "../../types"; // Exclude some patterns from key type checking for now // These are intended to be removed as errors are fixed // Fixing component category will require tighter definition of types from backend and/or web socket export type LocalizeKeys = - | FlattenObjectKeys> + | FlattenObjectKeys> | `panel.${string}` | `ui.card.alarm_control_panel.${string}` | `ui.card.weather.attributes.${string}` @@ -20,7 +20,7 @@ export type LocalizeKeys = | `ui.dialogs.quick-bar.commands.${string}` | `ui.dialogs.unhealthy.reason.${string}` | `ui.dialogs.unsupported.reason.${string}` - | `ui.panel.config.${string}.${'caption' | 'description'}` + | `ui.panel.config.${string}.${"caption" | "description"}` | `ui.panel.config.dashboard.${string}` | `ui.panel.config.zha.${string}` | `ui.panel.config.zwave_js.${string}` @@ -30,7 +30,10 @@ export type LocalizeKeys = | `component.${string}`; // Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types -export type FlattenObjectKeys, Key extends keyof T = keyof T> = Key extends string +export type FlattenObjectKeys< + T extends Record, + Key extends keyof T = keyof T, +> = Key extends string ? T[Key] extends Record ? `${Key}.${FlattenObjectKeys}` : `${Key}` @@ -41,6 +44,6 @@ export type LocalizeFunc = ( key: Keys, values?: Record< string, - string | number | { _$litType$: 1; strings: TemplateStringsArray; values: Array } | null | undefined - >, + string | number | {_$litType$: 1, strings: TemplateStringsArray, values: Array} | null | undefined + > ) => string; diff --git a/src/types/homeassistant/data/area_registry.ts b/src/types/homeassistant/data/area_registry.ts index a51b4b2..211dc3b 100644 --- a/src/types/homeassistant/data/area_registry.ts +++ b/src/types/homeassistant/data/area_registry.ts @@ -1,20 +1,17 @@ -import { RegistryEntry } from './registry'; +import {RegistryEntry} from "./registry"; /** - * Represents an entry in the Area Registry in Home Assistant. + * Entry in the Area Registry. * - * @property {string[]} aliases - An array of aliases for the area. - * @property {string} area_id - The unique identifier for the area. - * @property {string|null} floor_id - The identifier for the area's floor, or null if not applicable. - * @property {string|null} humidity_entity_id - The identifier for the area's humidity sensor, or null if not - * applicable. - * @property {string|null} icon - The icon to display for the area, or null if not specified. - * @property {string[]} labels - Labels for grouping elements irrespective of their physical location or type. - * @property {string} name - The name of the area. - * @property {string|null} picture - The URL to a picture that should be used instead of the domain icon, or null if - * not specified. - * @property {string|null} temperature_entity_id - The identifier for the area's temperature sensor, or null if not - * applicable. + * @property {string[]} aliases Array of aliases of the area. + * @property {string} area_id The id of the area. + * @property {string|null} floor_id The id of the area's floor. + * @property {string|null} humidity_entity_id The id of the area's humidity sensor. + * @property {string|null} icon Icon to show. + * @property {string[]} labels Labels allow grouping elements irrespective of their physical location or type. + * @property {string} name Name of the area. + * @property {string|null} picture URL to a picture that should be used instead of showing the domain icon. + * @property {string|null} temperature_entity_id The id of the area's temperature sensor. */ export interface AreaRegistryEntry extends RegistryEntry { aliases: string[]; diff --git a/src/types/homeassistant/data/device_registry.ts b/src/types/homeassistant/data/device_registry.ts index 99e86d7..61c510a 100644 --- a/src/types/homeassistant/data/device_registry.ts +++ b/src/types/homeassistant/data/device_registry.ts @@ -1,26 +1,40 @@ /** - * Represents a device entity in the of Home Assistant's device registry . + * Device Entity. * - * @property {string} id - Unique identifier of the device (generated by Home Assistant). - * @property {string[]} config_entries - Config entries linked to this device. - * @property {Record} config_entries_subentries - Subentries for the config entries. - * @property {[string, string][]} connections - Tuples of (connection_type, connection identifier). - * @property {[string, string][]} identifiers - Set of (DOMAIN, identifier) tuples identifying the device. - * @property {string | null} manufacturer - The manufacturer of the device. - * @property {string | null} model - The model name of the device. - * @property {string | null} model_id - The model identifier of the device. - * @property {string | null} name - The name of the device. - * @property {string[]} labels - Labels for the device. - * @property {string | null} sw_version - The firmware version of the device. - * @property {string | null} hw_version - The hardware version of the device. - * @property {string | null} serial_number - The serial number of the device. - * @property {string | null} via_device_id - Identifier of a device that routes messages to this device. - * @property {string | null} area_id - The area which the device is placed in. - * @property {string | null} name_by_user - User configured name of the device. - * @property {string[] | null} entry_type - The type of entry (e.g., service). - * @property {string | null} disabled_by - Indicates what disabled this entity. - * @property {string | null} configuration_url - URL for configuring the device. - * @property {string | null} primary_config_entry - Identifier of the primary config entry for the device. + * @property {string} id Unique ID of a device (generated by Home Assistant). + * @property {string[]} config_entries Config entries that are linked to this device. + * @property {Record} config_entries_subentries + * @property {[string, string][]} connections A set of tuples of (connection_type, connection identifier). + * Connection types are defined in the device registry module. + * Each item in the set uniquely defines a device entry, meaning another + * device can't have the same connection. + * @property {[string, string][]} identifiers Set of (DOMAIN, identifier) tuples. + * Identifiers identify the device in the outside world. + * An example is a serial number. + * Each item in the set uniquely defines a device entry, meaning another + * device can't have the same identifier. + * @property {string | null} manufacturer The manufacturer of the device. + * @property {string | null} model The model name of the device. + * @property {string | null} model_id The model identifier of the device. + * @property {string | null} name Name of this device + * @property {string[]} labels + * @property {string | null} sw_version The firmware version of the device. + * @property {string | null} hw_version The hardware version of the device. + * @property {string | null} serial_number The serial number of the device. + * Unlike a serial number in the identifiers set, this does not need to be + * unique. + * @property {string | null} via_device_id Identifier of a device that routes messages between this device and Home Assistant. + * Examples of such devices are hubs, or parent devices of a sub-device. + * This is used to show device topology in Home Assistant. + * @property {string} area_id The Area which the device is placed in. + * @property {string | null} name_by_user The user configured name of the device. + * @property {string[] | null} entry_type The type of entry. Possible values are None and DeviceEntryType enum members + * (only service). + * @property {string | null} disabled_by Indicates by what this entity is disabled. + * @property {string | null} configuration_url A URL on which the device or service can be configured, + * linking to paths inside the Home Assistant UI can be done by using + * homeassistant://. + * @property {string | null} primary_config_entry */ export interface DeviceRegistryEntry { id: string; @@ -39,8 +53,8 @@ export interface DeviceRegistryEntry { via_device_id: string | null; area_id: string | null; name_by_user: string | null; - entry_type: 'service' | null; - disabled_by: 'user' | 'integration' | 'config_entry' | null; + entry_type: "service" | null; + disabled_by: "user" | "integration" | "config_entry" | null; configuration_url: string | null; primary_config_entry: string | null; } diff --git a/src/types/homeassistant/data/entity_registry.ts b/src/types/homeassistant/data/entity_registry.ts index 7690255..f856933 100644 --- a/src/types/homeassistant/data/entity_registry.ts +++ b/src/types/homeassistant/data/entity_registry.ts @@ -1,23 +1,7 @@ -import { LightColor } from './light'; +import {LightColor} from "./light"; -export type EntityCategory = 'config' | 'diagnostic'; +type EntityCategory = "config" | "diagnostic"; -/** - * Represents the display entry for an entity in the entity registry. - * - * @property {string} entity_id - The unique identifier for the entity. - * @property {string} [name] - The name of the entity. - * @property {string} [icon] - The icon associated with the entity. - * @property {string} [device_id] - The ID of the device linked to this entity. - * @property {string} [area_id] - The ID of the area linked to this entity. - * @property {string[]} labels - Labels associated with the entity. - * @property {boolean} [hidden] - Indicates if the entity is hidden. - * @property {EntityCategory} [entity_category] - The category of the entity. - * @property {string} [translation_key] - The translation key for the entity. - * @property {string} [platform] - The platform of the entity. - * @property {number} [display_precision] - The display precision for the entity. - * @property {boolean} [has_entity_name] - Indicates if the entity has a name. - */ export interface EntityRegistryDisplayEntry { entity_id: string; name?: string; @@ -34,43 +18,41 @@ export interface EntityRegistryDisplayEntry { } /** - * Represents an entity in the entity registry of Home Assistant. + * Home assistant entity. * - * @property {string} id - The unique identifier for the entity. - * @property {string} entity_id - The ID of the entity. - * @property {string | null} name - The name of the entity. - * @property {string | null} icon - The icon associated with the entity. - * @property {string | null} platform - The platform of the entity. - * @property {string | null} config_entry_id - The ID of the config entry associated with the entity. - * @property {string | null} config_subentry_id - The ID of the config subentry associated with the entity. - * @property {string | null} device_id - The ID of the device linked to this entity. - * @property {string | null} area_id - The ID of the area linked to this entity. - * @property {string[]} labels - Labels associated with the entity. - * @property {"user" | "device" | "integration" | "config_entry" | null} disabled_by - Indicates what disabled this - * entity. - * @property {Exclude} hidden_by - Indicates what hidden this - * entity. - * @property {EntityCategory | null} entity_category - The category of the entity. - * @property {boolean} has_entity_name - Indicates if the entity has a name. - * @property {string} [original_name] - The original name of the entity. - * @property {string} unique_id - The unique identifier for the entity. - * @property {string} [translation_key] - The translation key for the entity. - * @property {EntityRegistryOptions | null} options - Additional options for the entity. - * @property {Record} categories - Categories associated with the entity. + * @property {string} id + * @property {string} entity_id The id of this entity. + * @property {string} name The name of this entity. + * @property {string | null} icon + * @property {string | null} platform + * @property {string | null} config_entry_id + * @property {string | null} config_subentry_id + * @property {string | null} device_id The id of the device to which this entity is linked. + * @property {string | null} area_id The id of the area to which this entity is linked. + * @property {string[]} labels + * @property {string | null} disabled_by Indicates by what this entity is disabled. + * @property {Object} hidden_by Indicates by what this entity is hidden. + * @property {EntityCategory | null} entity_category + * @property {boolean} has_entity_name + * @property {string} [original_name] + * @property {string} unique_id + * @property {string} [translation_key] + * @property {EntityRegistryOptions | null} options + * @property {Record} categories */ export interface EntityRegistryEntry { id: string; entity_id: string; name: string | null; icon: string | null; - platform: string | null; + platform: string; config_entry_id: string | null; config_subentry_id: string | null; device_id: string | null; area_id: string | null; labels: string[]; - disabled_by: 'user' | 'device' | 'integration' | 'config_entry' | null; - hidden_by: Exclude; + disabled_by: "user" | "device" | "integration" | "config_entry" | null; + hidden_by: Exclude; entity_category: EntityCategory | null; has_entity_name: boolean; original_name?: string; @@ -80,64 +62,28 @@ export interface EntityRegistryEntry { categories: Record; } -/** - * Represents options for a sensor entity in Home Assistant. - * - * @property {number | null} [display_precision] - The display precision for the sensor. - * @property {number | null} [suggested_display_precision] - Suggested display precision for the sensor. - * @property {string | null} [unit_of_measurement] - The unit of measurement for the sensor. - */ export interface SensorEntityOptions { display_precision?: number | null; suggested_display_precision?: number | null; unit_of_measurement?: string | null; } -/** - * Represents options for a light entity in Home Assistant. - * - * @property {LightColor[]} [favorite_colors] - An array of favorite colors for the light. - */ export interface LightEntityOptions { favorite_colors?: LightColor[]; } -/** - * Represents options for a number entity in Home Assistant. - * - * @property {string | null} [unit_of_measurement] - The unit of measurement for the number. - */ export interface NumberEntityOptions { unit_of_measurement?: string | null; } -/** - * Represents options for a lock entity in Home Assistant. - * - * @property {string | null} [default_code] - The default code for the lock. - */ export interface LockEntityOptions { default_code?: string | null; } -/** - * Represents options for an alarm control panel entity in Home Assistant. - * - * @property {string | null} [default_code] - The default code for the alarm control panel. - */ export interface AlarmControlPanelEntityOptions { default_code?: string | null; } -/** - * Represents options for a weather entity in Home Assistant. - * - * @property {string | null} [precipitation_unit] - The unit of measurement for precipitation. - * @property {string | null} [pressure_unit] - The unit of measurement for pressure. - * @property {string | null} [temperature_unit] - The unit of measurement for temperature. - * @property {string | null} [visibility_unit] - The unit of measurement for visibility. - * @property {string | null} [wind_speed_unit] - The unit of measurement for wind speed. - */ export interface WeatherEntityOptions { precipitation_unit?: string | null; pressure_unit?: string | null; @@ -146,31 +92,11 @@ export interface WeatherEntityOptions { wind_speed_unit?: string | null; } -/** - * Represents options for a switch entity in Home Assistant. - * - * @property {string} entity_id - The ID of the entity. - * @property {boolean} invert - Indicates if the switch should be inverted. - */ export interface SwitchAsXEntityOptions { entity_id: string; invert: boolean; } -/** - * Represents options for an entity in the entity registry of Home Assistant. - * - * @property {NumberEntityOptions} [number] - Options for number entities. - * @property {SensorEntityOptions} [sensor] - Options for sensor entities. - * @property {AlarmControlPanelEntityOptions} [alarm_control_panel] - Options for alarm control panel entities. - * @property {LockEntityOptions} [lock] - Options for lock entities. - * @property {WeatherEntityOptions} [weather] - Options for weather entities. - * @property {LightEntityOptions} [light] - Options for light entities. - * @property {SwitchAsXEntityOptions} [switch_as_x] - Options for switch entities. - * @property {Record} [conversation] - Options for conversation entities. - * @property {Record} ["cloud.alexa"] - Options for Alexa cloud integration. - * @property {Record} ["cloud.google_assistant"] - Options for Google Assistant cloud integration. - */ export interface EntityRegistryOptions { number?: NumberEntityOptions; sensor?: SensorEntityOptions; @@ -180,6 +106,7 @@ export interface EntityRegistryOptions { light?: LightEntityOptions; switch_as_x?: SwitchAsXEntityOptions; conversation?: Record; - 'cloud.alexa'?: Record; - 'cloud.google_assistant'?: Record; + "cloud.alexa"?: Record; + "cloud.google_assistant"?: Record; } + diff --git a/src/types/homeassistant/data/floor_registry.ts b/src/types/homeassistant/data/floor_registry.ts index 1387cc3..eccbaa5 100644 --- a/src/types/homeassistant/data/floor_registry.ts +++ b/src/types/homeassistant/data/floor_registry.ts @@ -1,14 +1,5 @@ -import { RegistryEntry } from './registry'; +import {RegistryEntry} from "./registry"; -/** - * Represents a floor entry in the Floor Registry of Home Assistant. - * - * @property {string} floor_id - The unique identifier for the floor. - * @property {string} name - The name of the floor. - * @property {number | null} level - The level of the floor (optional). - * @property {string | null} icon - The icon associated with the floor (optional). - * @property {string[]} aliases - An array of aliases for the floor. - */ export interface FloorRegistryEntry extends RegistryEntry { floor_id: string; name: string; diff --git a/src/types/homeassistant/data/frontend.ts b/src/types/homeassistant/data/frontend.ts index 8c7c77c..3155312 100644 --- a/src/types/homeassistant/data/frontend.ts +++ b/src/types/homeassistant/data/frontend.ts @@ -1,8 +1,3 @@ -/** - * Represents user data for the frontend in Home Assistant. - * - * @property {boolean} [showAdvanced] - Indicates whether advanced options should be shown to the user. - */ export interface CoreFrontendUserData { showAdvanced?: boolean; } diff --git a/src/types/homeassistant/data/lovelace.ts b/src/types/homeassistant/data/lovelace.ts new file mode 100644 index 0000000..6eb3c69 --- /dev/null +++ b/src/types/homeassistant/data/lovelace.ts @@ -0,0 +1,81 @@ +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {LovelaceGridOptions, LovelaceLayoutOptions} from "../panels/lovelace/types"; +import {Condition} from "../panels/common/validate-condition"; + +export interface LovelaceCardConfig { + index?: number; + view_index?: number; + view_layout?: any; + /** @deprecated Use `grid_options` instead */ + layout_options?: LovelaceLayoutOptions; + grid_options?: LovelaceGridOptions; + type: string; + [key: string]: any; + visibility?: Condition[]; +} + +export interface ToggleActionConfig extends BaseActionConfig { + action: "toggle"; +} + +export interface CallServiceActionConfig extends BaseActionConfig { + action: "call-service"; + service: string; + target?: HassServiceTarget; + // Property "service_data" is kept for backwards compatibility. Replaced by "data". + service_data?: Record; + data?: Record; +} + +export interface NavigateActionConfig extends BaseActionConfig { + action: "navigate"; + navigation_path: string; + navigation_replace?: boolean; +} + +export interface UrlActionConfig extends BaseActionConfig { + action: "url"; + url_path: string; +} + +export interface MoreInfoActionConfig extends BaseActionConfig { + action: "more-info"; +} + +export interface AssistActionConfig extends BaseActionConfig { + action: "assist"; + pipeline_id?: string; + start_listening?: boolean; +} + +export interface NoActionConfig extends BaseActionConfig { + action: "none"; +} + +export interface CustomActionConfig extends BaseActionConfig { + action: "fire-dom-event"; +} + +export interface BaseActionConfig { + action: string; + confirmation?: ConfirmationRestrictionConfig; +} + +export interface ConfirmationRestrictionConfig { + text?: string; + exemptions?: RestrictionConfig[]; +} + +export interface RestrictionConfig { + user: string; +} + +export type ActionConfig = + | ToggleActionConfig + | CallServiceActionConfig + | NavigateActionConfig + | UrlActionConfig + | MoreInfoActionConfig + | AssistActionConfig + | NoActionConfig + | CustomActionConfig; diff --git a/src/types/homeassistant/data/lovelace/config/badge.ts b/src/types/homeassistant/data/lovelace/config/badge.ts index cdc929d..d9e2450 100644 --- a/src/types/homeassistant/data/lovelace/config/badge.ts +++ b/src/types/homeassistant/data/lovelace/config/badge.ts @@ -1,15 +1,7 @@ -import { Condition } from '../../../panels/common/validate-condition'; +import {Condition} from "../../../panels/common/validate-condition"; -/** - * Represents the configuration for a Lovelace badge in Home Assistant. - * - * @property {string} type - The type of the badge. - * @property {Condition[]} [visibility] - An optional array of visibility conditions for the badge. - * @property {any} [key] - Additional properties can be included in the configuration. - */ export interface LovelaceBadgeConfig { type: string; - visibility?: Condition[]; - [key: string]: any; + visibility?: Condition[]; } diff --git a/src/types/homeassistant/data/lovelace/config/card.ts b/src/types/homeassistant/data/lovelace/config/card.ts index 4deef03..b25ce02 100644 --- a/src/types/homeassistant/data/lovelace/config/card.ts +++ b/src/types/homeassistant/data/lovelace/config/card.ts @@ -1,18 +1,6 @@ -import { Condition } from '../../../panels/common/validate-condition'; -import { LovelaceGridOptions, LovelaceLayoutOptions } from '../../../panels/lovelace/types'; +import {Condition} from "../../../panels/common/validate-condition"; +import {LovelaceGridOptions, LovelaceLayoutOptions} from "../../../panels/lovelace/types"; -/** - * Represents the configuration for a Lovelace card in Home Assistant. - * - * @property {number} [index] - The index of the card in the view. - * @property {number} [view_index] - The index of the view the card belongs to. - * @property {any} [view_layout] - The layout options for the card view. - * @property {LovelaceLayoutOptions} [layout_options] - Deprecated layout options; use `grid_options` instead. - * @property {LovelaceGridOptions} [grid_options] - The grid options for the card layout. - * @property {string} type - The type of the card. - * @property {Condition[]} [visibility] - An optional array of visibility conditions for the card. - * @property {any} [key] - Additional properties can be included in the configuration. - */ export interface LovelaceCardConfig { index?: number; view_index?: number; @@ -21,7 +9,6 @@ export interface LovelaceCardConfig { layout_options?: LovelaceLayoutOptions; grid_options?: LovelaceGridOptions; type: string; - visibility?: Condition[]; - [key: string]: any; + visibility?: Condition[]; } diff --git a/src/types/homeassistant/data/lovelace/config/section.ts b/src/types/homeassistant/data/lovelace/config/section.ts index 72ae099..3b5b07e 100644 --- a/src/types/homeassistant/data/lovelace/config/section.ts +++ b/src/types/homeassistant/data/lovelace/config/section.ts @@ -1,42 +1,27 @@ -import { Condition } from '../../../panels/common/validate-condition'; -import { LovelaceCardConfig } from './card'; -import { LovelaceStrategyConfig } from './strategy'; +import {LovelaceStrategyConfig} from "./strategy"; +import {LovelaceCardConfig} from "../../lovelace"; +import {Condition} from "../../../panels/common/validate-condition"; -/** - * Represents the base configuration for a Lovelace section in Home Assistant. - * - * @property {Condition[]} [visibility] - An optional array of visibility conditions for the section. - * @property {number} [column_span] - The number of columns the section spans. - * @property {number} [row_span] - The number of rows the section spans. - * @property {string} [title] - The title of the section (deprecated; use heading card instead). - */ export interface LovelaceBaseSectionConfig { visibility?: Condition[]; column_span?: number; row_span?: number; - /** @deprecated Use heading card instead. */ + /** + * @deprecated Use heading card instead. + */ title?: string; } -export interface LovelaceSectionConfig /** - * Represents the configuration for a Lovelace section in Home Assistant. - * - * @property {string} [type] - The type of the section. - * @property {LovelaceCardConfig[]} [cards] - An optional array of cards contained within the section. - */ - extends LovelaceBaseSectionConfig { +export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig { type?: string; cards?: LovelaceCardConfig[]; } -/** - * Represents the configuration for a Lovelace strategy section in Home Assistant. - * - * @property {LovelaceStrategyConfig} strategy - The strategy configuration for the section. - */ -export interface LovelaceStrategySectionConfig extends LovelaceBaseSectionConfig { +export interface LovelaceStrategySectionConfig + extends LovelaceBaseSectionConfig { strategy: LovelaceStrategyConfig; } -/** Represents the raw configuration for a Lovelace section in Home Assistant. */ -export type LovelaceSectionRawConfig = LovelaceSectionConfig | LovelaceStrategySectionConfig; +export type LovelaceSectionRawConfig = + | LovelaceSectionConfig + | LovelaceStrategySectionConfig; diff --git a/src/types/homeassistant/data/lovelace/config/strategy.ts b/src/types/homeassistant/data/lovelace/config/strategy.ts index 46107d8..5c35de4 100644 --- a/src/types/homeassistant/data/lovelace/config/strategy.ts +++ b/src/types/homeassistant/data/lovelace/config/strategy.ts @@ -1,11 +1,4 @@ -/** - * Represents the configuration for a Lovelace strategy in Home Assistant. - * - * @property {string} type - The type of the strategy. - * @property {any} [key] - Additional properties can be included in the configuration. - */ export interface LovelaceStrategyConfig { type: string; - [key: string]: any; } diff --git a/src/types/homeassistant/data/lovelace/config/types.ts b/src/types/homeassistant/data/lovelace/config/types.ts index 0a678c0..d1b8b53 100644 --- a/src/types/homeassistant/data/lovelace/config/types.ts +++ b/src/types/homeassistant/data/lovelace/config/types.ts @@ -1,15 +1,10 @@ -import { LovelaceViewRawConfig } from './view'; +import {LovelaceViewRawConfig} from "./view"; -/** Represents the base configuration for a Lovelace dashboard in Home Assistant. */ export interface LovelaceDashboardBaseConfig {} -/** - * Represents the configuration for a Lovelace dashboard in Home Assistant. - * - * @property {string} [background] - An optional background image or color for the dashboard. - * @property {LovelaceViewRawConfig[]} views - An array of views contained within the dashboard. - */ export interface LovelaceConfig extends LovelaceDashboardBaseConfig { background?: string; views: LovelaceViewRawConfig[]; } + + diff --git a/src/types/homeassistant/data/lovelace/config/view.ts b/src/types/homeassistant/data/lovelace/config/view.ts index 508b8b9..3173211 100644 --- a/src/types/homeassistant/data/lovelace/config/view.ts +++ b/src/types/homeassistant/data/lovelace/config/view.ts @@ -1,76 +1,36 @@ -import { LovelaceBadgeConfig } from './badge'; -import { LovelaceCardConfig } from './card'; -import { LovelaceSectionRawConfig } from './section'; -import { LovelaceStrategyConfig } from './strategy'; +import {LovelaceStrategyConfig} from "./strategy"; +import {LovelaceSectionRawConfig} from "./section"; +import {LovelaceCardConfig} from "./card"; +import {LovelaceBadgeConfig} from "./badge"; -/** - * Represents the configuration for showing a view in Home Assistant. - * - * @property {string} [user] - The user associated with the view. - */ export interface ShowViewConfig { user?: string; } -/** - * Represents the background configuration for a Lovelace view in Home Assistant. - * - * @property {string} [image] - The background image URL. - * @property {number} [opacity] - The opacity of the background. - * @property {'auto' | 'cover' | 'contain'} [size] - The size of the background image. - * @property {'top left' | 'top center' | 'top right' | 'center left' | 'center' | 'center right' | 'bottom left' | - * 'bottom center' | 'bottom right'} [alignment] - The alignment of the background image. - * @property {'repeat' | 'no-repeat'} [repeat] - The repeat behavior of the background image. - * @property {'scroll' | 'fixed'} [attachment] - The attachment behavior of the background image. - */ export interface LovelaceViewBackgroundConfig { image?: string; opacity?: number; - size?: 'auto' | 'cover' | 'contain'; + size?: "auto" | "cover" | "contain"; alignment?: - | 'top left' - | 'top center' - | 'top right' - | 'center left' - | 'center' - | 'center right' - | 'bottom left' - | 'bottom center' - | 'bottom right'; - repeat?: 'repeat' | 'no-repeat'; - attachment?: 'scroll' | 'fixed'; + | "top left" + | "top center" + | "top right" + | "center left" + | "center" + | "center right" + | "bottom left" + | "bottom center" + | "bottom right"; + repeat?: "repeat" | "no-repeat"; + attachment?: "scroll" | "fixed"; } -/** - * Represents the header configuration for a Lovelace view in Home Assistant. - * - * @property {LovelaceCardConfig} [card] - The card to be displayed in the header. - * @property {'start' | 'center' | 'responsive'} [layout] - The layout of the header. - * @property {'bottom' | 'top'} [badges_position] - The position of badges in the header. - */ export interface LovelaceViewHeaderConfig { card?: LovelaceCardConfig; - layout?: 'start' | 'center' | 'responsive'; - badges_position?: 'bottom' | 'top'; + layout?: "start" | "center" | "responsive"; + badges_position?: "bottom" | "top"; } -/** - * Represents the base configuration for a Lovelace view in Home Assistant. - * - * @property {number} [index] - The index of the view. - * @property {string} [title] - The title of the view. - * @property {string} [path] - The path to the view. - * @property {string} [icon] - The icon for the view. - * @property {string} [theme] - The theme for the view. - * @property {boolean} [panel] - Whether the view is a panel view. - * @property {string | LovelaceViewBackgroundConfig} [background] - The background configuration for the view. - * @property {boolean | ShowViewConfig[]} [visible] - Visibility settings for the view. - * @property {boolean} [subview] - Whether the view is a subview. - * @property {string} [back_path] - The path to go back to the previous view. - * @property {number} [max_columns] - The maximum number of columns in the view. - * @property {boolean} [dense_section_placement] - Whether to place sections densely. - * @property {boolean} [top_margin] - Whether to add top margin to the view. - */ export interface LovelaceBaseViewConfig { index?: number; title?: string; @@ -82,36 +42,33 @@ export interface LovelaceBaseViewConfig { visible?: boolean | ShowViewConfig[]; subview?: boolean; back_path?: string; + // Only used for section view, it should move to a section view config type when the views will have a dedicated editor. max_columns?: number; dense_section_placement?: boolean; top_margin?: boolean; } /** - * Represents the configuration for a Lovelace view in Home Assistant. + * View Config. * - * @property {string} [type] - The type of the view. - * @property {(string | Partial)[]} [badges] - An array of badges for the view. - * @property {LovelaceCardConfig[]} [cards] - An array of cards in the view. - * @property {LovelaceSectionRawConfig[]} [sections] - An array of sections in the view. - * @property {LovelaceViewHeaderConfig} [header] - The header configuration for the view. + * @see https://www.home-assistant.io/dashboards/views/ */ export interface LovelaceViewConfig extends LovelaceBaseViewConfig { type?: string; - badges?: (string | Partial)[]; + badges?: (string | Partial)[]; // Badge can be just an entity_id or without type cards?: LovelaceCardConfig[]; sections?: LovelaceSectionRawConfig[]; header?: LovelaceViewHeaderConfig; } -/** - * Represents the configuration for a Lovelace strategy view in Home Assistant. - * - * @property {LovelaceStrategyConfig} strategy - The strategy configuration for the view. - */ export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig { strategy: LovelaceStrategyConfig; } -/**Represents the raw configuration for a Lovelace view in Home Assistant. */ -export type LovelaceViewRawConfig = LovelaceViewConfig | LovelaceStrategyViewConfig; +export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig { + strategy: LovelaceStrategyConfig; +} + +export type LovelaceViewRawConfig = + | LovelaceViewConfig + | LovelaceStrategyViewConfig; diff --git a/src/types/homeassistant/data/registry.ts b/src/types/homeassistant/data/registry.ts index 7b58173..985d66a 100644 --- a/src/types/homeassistant/data/registry.ts +++ b/src/types/homeassistant/data/registry.ts @@ -1,9 +1,3 @@ -/** - * Represents a registry entry in Home Assistant. - * - * @property {number} created_at - The timestamp when the entry was created. - * @property {number} modified_at - The timestamp when the entry was last modified. - */ export interface RegistryEntry { created_at: number; modified_at: number; diff --git a/src/types/homeassistant/data/translations.ts b/src/types/homeassistant/data/translations.ts index a417d8f..45408b0 100644 --- a/src/types/homeassistant/data/translations.ts +++ b/src/types/homeassistant/data/translations.ts @@ -1,62 +1,47 @@ // noinspection JSUnusedGlobalSymbols -import { HomeAssistant } from '../types'; +import {HomeAssistant} from "../types"; -/** Represents the different formats for numbers in Home Assistant. */ export enum NumberFormat { - language = 'language', - system = 'system', - comma_decimal = 'comma_decimal', - decimal_comma = 'decimal_comma', - space_comma = 'space_comma', - none = 'none', + language = "language", + system = "system", + comma_decimal = "comma_decimal", + decimal_comma = "decimal_comma", + space_comma = "space_comma", + none = "none", } -/**Represents the different formats for time in Home Assistant. */ export enum TimeFormat { - language = 'language', - system = 'system', - am_pm = '12', - twenty_four = '24', + language = "language", + system = "system", + am_pm = "12", + twenty_four = "24", } -/** Represents the different time zones in Home Assistant. */ export enum TimeZone { - local = 'local', - server = 'server', + local = "local", + server = "server", } -/** Represents the different formats for dates in Home Assistant. */ export enum DateFormat { - language = 'language', - system = 'system', - DMY = 'DMY', - MDY = 'MDY', - YMD = 'YMD', + language = "language", + system = "system", + DMY = "DMY", + MDY = "MDY", + YMD = "YMD", } -/**Represents the first weekday in Home Assistant. */ export enum FirstWeekday { - language = 'language', - monday = 'monday', - tuesday = 'tuesday', - wednesday = 'wednesday', - thursday = 'thursday', - friday = 'friday', - saturday = 'saturday', - sunday = 'sunday', + language = "language", + monday = "monday", + tuesday = "tuesday", + wednesday = "wednesday", + thursday = "thursday", + friday = "friday", + saturday = "saturday", + sunday = "sunday", } -/** - * Represents the locale data for the frontend in Home Assistant. - * - * @property {string} language - The language of the frontend. - * @property {NumberFormat} number_format - The format for numbers. - * @property {TimeFormat} time_format - The format for time. - * @property {DateFormat} date_format - The format for dates. - * @property {FirstWeekday} first_weekday - The first weekday. - * @property {TimeZone} time_zone - The time zone. - */ export interface FrontendLocaleData { language: string; number_format: NumberFormat; @@ -66,47 +51,33 @@ export interface FrontendLocaleData { time_zone: TimeZone; } -/** Represents a category for translations in Home Assistant. */ export type TranslationCategory = - | 'title' - | 'state' - | 'entity' - | 'entity_component' - | 'exceptions' - | 'config' - | 'config_subentries' - | 'config_panel' - | 'options' - | 'device_automation' - | 'mfa_setup' - | 'system_health' - | 'application_credentials' - | 'issues' - | 'selector' - | 'services'; + | "title" + | "state" + | "entity" + | "entity_component" + | "exceptions" + | "config" + | "config_subentries" + | "config_panel" + | "options" + | "device_automation" + | "mfa_setup" + | "system_health" + | "application_credentials" + | "issues" + | "selector" + | "services"; -/** - * Retrieves the translations for Home Assistant. - * - * @async - * - * @param {HomeAssistant} hass - The Home Assistant instance. - * @param {string} language - The language for translations. - * @param {TranslationCategory} category - The category of translations. - * @param {string | string[]} [integration] - Optional integration name(s). - * @param {boolean} [config_flow] - Optional flag for config flow. - * - * @returns {Promise>} A promise resolving to an object containing translation key-value pairs. - */ export const getHassTranslations = async ( hass: HomeAssistant, language: string, category: TranslationCategory, integration?: string | string[], - config_flow?: boolean, + config_flow?: boolean ): Promise> => { const result = await hass.callWS<{ resources: Record }>({ - type: 'frontend/get_translations', + type: "frontend/get_translations", language, category, integration, diff --git a/src/types/homeassistant/data/ws-themes.ts b/src/types/homeassistant/data/ws-themes.ts index f2d353e..79ff1d2 100644 --- a/src/types/homeassistant/data/ws-themes.ts +++ b/src/types/homeassistant/data/ws-themes.ts @@ -1,26 +1,11 @@ -/** - * Represents the variables for a theme in Home Assistant. - * - * @property {string} primary-color - The primary color of the theme. - * @property {string} text-primary-color - The primary text color of the theme. - * @property {string} accent-color - The accent color of the theme. - * @property {string} [key] - Additional theme variables as key-value pairs. - */ export interface ThemeVars { // Incomplete - 'primary-color': string; - 'text-primary-color': string; - 'accent-color': string; - + "primary-color": string; + "text-primary-color": string; + "accent-color": string; [key: string]: string; } -/** - * Represents a theme configuration in Home Assistant. - * - * @property {ThemeVars} modes.light - The light mode variables. - * @property {ThemeVars} modes.dark - The dark mode variables. - */ export type Theme = ThemeVars & { modes?: { light?: ThemeVars; @@ -28,23 +13,14 @@ export type Theme = ThemeVars & { }; }; -/** - * Represents the overall themes configuration in Home Assistant. - * - * @property {string} default_theme - The default theme name. - * @property {string | null} default_dark_theme - The default dark theme name or null. - * @property {Record} themes - A record of available themes. - * @property {boolean} darkMode - Currently effective dark mode. - * It Will never be undefined. - * If the user selected "auto" in the theme picker, this property will still contain - * either true or false based on what has been determined via system preferences and - * support for the selected theme. - * @property {string} theme - Currently globally active theme name - */ export interface Themes { default_theme: string; default_dark_theme: string | null; themes: Record; + // Currently effective dark mode. Will never be undefined. If user selected "auto" + // in theme picker, this property will still contain either true or false based on + // what has been determined via system preferences and support for the selected theme. darkMode: boolean; + // Currently globally active theme name theme: string; } diff --git a/src/types/homeassistant/panels/common/validate-condition.ts b/src/types/homeassistant/panels/common/validate-condition.ts index 10449ff..138abc8 100644 --- a/src/types/homeassistant/panels/common/validate-condition.ts +++ b/src/types/homeassistant/panels/common/validate-condition.ts @@ -1,4 +1,3 @@ -/** Represents a condition in Home Assistant. */ export type Condition = | NumericStateCondition | StateCondition @@ -7,85 +6,40 @@ export type Condition = | OrCondition | AndCondition; -/** - * Base interface for all conditions in Home Assistant. - * - * @property {string} condition - The type of condition. - */ interface BaseCondition { condition: string; } -/** - * Represents a numeric state condition in Home Assistant. - * - * @property {'numeric_state'} condition - The condition type. - * @property {string} [entity] - The entity to evaluate. - * @property {string | number} [below] - The threshold value below which the condition is true. - * @property {string | number} [above] - The threshold value above which the condition is true. - */ export interface NumericStateCondition extends BaseCondition { - condition: 'numeric_state'; + condition: "numeric_state"; entity?: string; below?: string | number; above?: string | number; } -/** - * Represents a state condition in Home Assistant. - * - * @property {'state'} condition - The condition type. - * @property {string} [entity] - The entity to evaluate. - * @property {string | string[]} [state] - The expected state of the entity. - * @property {string | string[]} [state_not] - The state that the entity should not be in. - */ export interface StateCondition extends BaseCondition { - condition: 'state'; + condition: "state"; entity?: string; state?: string | string[]; state_not?: string | string[]; } -/** - * Represents a screen condition in Home Assistant. - * - * @property {'screen'} condition - The condition type. - * @property {string} [media_query] - The media query for screen conditions. - */ export interface ScreenCondition extends BaseCondition { - condition: 'screen'; + condition: "screen"; media_query?: string; } -/** - * Represents a user condition in Home Assistant. - * - * @property {'user'} condition - The condition type. - * @property {string[]} [users] - The list of users for the condition. - */ export interface UserCondition extends BaseCondition { - condition: 'user'; + condition: "user"; users?: string[]; } -/** - * Represents an OR condition in Home Assistant. - * - * @property {'or'} condition - The condition type. - * @property {Condition[]} [conditions] - The list of conditions to evaluate. - */ export interface OrCondition extends BaseCondition { - condition: 'or'; + condition: "or"; conditions?: Condition[]; } -/** - * Represents an AND condition in Home Assistant. - * - * @property {'and'} condition - The condition type. - * @property {Condition[]} [conditions] - The list of conditions to evaluate. - */ export interface AndCondition extends BaseCondition { - condition: 'and'; + condition: "and"; conditions?: Condition[]; } diff --git a/src/types/homeassistant/panels/lovelace/cards/types.ts b/src/types/homeassistant/panels/lovelace/cards/types.ts index b0a3462..d7cee82 100644 --- a/src/types/homeassistant/panels/lovelace/cards/types.ts +++ b/src/types/homeassistant/panels/lovelace/cards/types.ts @@ -1,41 +1,40 @@ -import { ActionConfig } from '../../../data/lovelace/config/action'; -import { LovelaceCardConfig } from '../../../data/lovelace/config/card'; +import {ActionConfig, LovelaceCardConfig} from "../../../data/lovelace"; /** * Home Assistant Area Card Config. * - * @property {string} area - The area associated with the card. - * @property {string} [navigation_path] - Optional navigation path for the card. - * @property {boolean} [show_camera] - Whether to show the camera view. - * @property {"live" | "auto"} [camera_view] - The camera view mode. - * @property {string} [aspect_ratio] - The aspect ratio of the card. * @see https://www.home-assistant.io/dashboards/area/ */ export interface AreaCardConfig extends LovelaceCardConfig { area: string; navigation_path?: string; show_camera?: boolean; - camera_view?: 'live' | 'auto'; + camera_view?: "live" | "auto"; aspect_ratio?: string; } /** * Home Assistant Picture Entity Config. * - * @property {string} entity - An entity_id used for the picture. - * @property {string} [name] - Overwrite entity name. - * @property {string} [image] - URL of an image. - * @property {string} [camera_image] - Camera entity_id to use. - * @property {"live" | "auto"} [camera_view] - The camera view mode. - * @property {Record} [state_image] - Map entity states to images. - * @property {string[]} [state_filter] - State-based CSS filters. - * @property {string} [aspect_ratio] - Forces the height of the image to be a ratio of the width. - * @property {ActionConfig} [tap_action] - Action taken on card tap. - * @property {ActionConfig} [hold_action] - Action taken on card tap and hold. - * @property {ActionConfig} [double_tap_action] - Action taken on card double tap. - * @property {boolean} [show_name=true] - Shows name in footer. - * @property {string} [theme=true] - Override the used theme for this card. - * @property {boolean} [show_state] - Shows state in footer. + * @property {string} entity An entity_id used for the picture. + * @property {string} [name] Overwrite entity name. + * @property {string} [image] URL of an image. + * @property {string} [camera_image] Camera entity_id to use. + * (not required if the entity is already a camera-entity). + * @property {string} [camera_view=auto] “live” will show the live view if stream is enabled. + * @property {Record} [state_image] Map entity states to images (state: image URL). + * @property {string[]} [state_filter] State-based CSS filters. + * @property {string} [aspect_ratio] Forces the height of the image to be a ratio of the width. + * Valid formats: Height percentage value (23%) or ratio expressed with colon or “x” + * separator (16:9 or 16x9). + * For a ratio, the second element can be omitted and will default to “1” + * (1.78 equals 1.78:1). + * @property {ActionConfig} [tap_action] Action taken on card tap. + * @property {ActionConfig} [hold_action] Action taken on card tap and hold. + * @property {ActionConfig} [double_tap_action] Action taken on card double tap. + * @property {boolean} [show_name=true] Shows name in footer. + * @property {string} [theme=true] Override the used theme for this card with any loaded theme. + * * @see https://www.home-assistant.io/dashboards/picture-entity/ */ export interface PictureEntityCardConfig extends LovelaceCardConfig { @@ -43,7 +42,7 @@ export interface PictureEntityCardConfig extends LovelaceCardConfig { name?: string; image?: string; camera_image?: string; - camera_view?: 'live' | 'auto'; + camera_view?: "live" | "auto"; state_image?: Record; state_filter?: string[]; aspect_ratio?: string; @@ -58,13 +57,13 @@ export interface PictureEntityCardConfig extends LovelaceCardConfig { /** * Home Assistant Stack Card Config. * - * @property {string} type - The stack type. - * @property {Object[]} cards - The content of the stack. + * @property {string} type The stack type. + * @property {Object[]} cards The content of the stack. + * * @see https://www.home-assistant.io/dashboards/horizontal-stack/ * @see https://www.home-assistant.io/dashboards/vertical-stack/ */ export interface StackCardConfig extends LovelaceCardConfig { - type: string; cards: LovelaceCardConfig[]; title?: string; } diff --git a/src/types/homeassistant/panels/lovelace/types.ts b/src/types/homeassistant/panels/lovelace/types.ts index 1bfba97..6496f32 100644 --- a/src/types/homeassistant/panels/lovelace/types.ts +++ b/src/types/homeassistant/panels/lovelace/types.ts @@ -1,35 +1,15 @@ -/** - * Represents the layout options for Lovelace in Home Assistant. - * - * @property {number | "full"} [grid_columns] - The number of grid columns or "full". - * @property {number | "auto"} [grid_rows] - The number of grid rows or "auto". - * @property {number} [grid_max_columns] - The maximum number of grid columns. - * @property {number} [grid_min_columns] - The minimum number of grid columns. - * @property {number} [grid_min_rows] - The minimum number of grid rows. - * @property {number} [grid_max_rows] - The maximum number of grid rows. - */ export interface LovelaceLayoutOptions { - grid_columns?: number | 'full'; - grid_rows?: number | 'auto'; + grid_columns?: number | "full"; + grid_rows?: number | "auto"; grid_max_columns?: number; grid_min_columns?: number; grid_min_rows?: number; grid_max_rows?: number; } -/** - * Represents the grid options for Lovelace in Home Assistant. - * - * @property {number | "full"} [columns] - The number of columns or "full". - * @property {number | "auto"} [rows] - The number of rows or "auto". - * @property {number} [max_columns] - The maximum number of columns. - * @property {number} [min_columns] - The minimum number of columns. - * @property {number} [min_rows] - The minimum number of rows. - * @property {number} [max_rows] - The maximum number of rows. - */ export interface LovelaceGridOptions { - columns?: number | 'full'; - rows?: number | 'auto'; + columns?: number | "full"; + rows?: number | "auto"; max_columns?: number; min_columns?: number; min_rows?: number; diff --git a/src/types/homeassistant/types.ts b/src/types/homeassistant/types.ts index ffb05d4..e61eb32 100644 --- a/src/types/homeassistant/types.ts +++ b/src/types/homeassistant/types.ts @@ -7,50 +7,27 @@ import { HassServices, HassServiceTarget, MessageBase, -} from 'home-assistant-js-websocket'; -import { LocalizeFunc } from './common/translations/localize'; -import { AreaRegistryEntry } from './data/area_registry'; -import { DeviceRegistryEntry } from './data/device_registry'; -import { EntityRegistryDisplayEntry } from './data/entity_registry'; -import { FloorRegistryEntry } from './data/floor_registry'; -import { CoreFrontendUserData } from './data/frontend'; -import { FrontendLocaleData, getHassTranslations } from './data/translations'; -import { Themes } from './data/ws-themes'; +} from "home-assistant-js-websocket"; +import {AreaRegistryEntry} from "./data/area_registry"; +import {DeviceRegistryEntry} from "./data/device_registry"; +import {EntityRegistryDisplayEntry} from "./data/entity_registry"; +import {FloorRegistryEntry} from "./data/floor_registry"; +import {Themes} from "./data/ws-themes"; +import {FrontendLocaleData, getHassTranslations} from "./data/translations"; +import {LocalizeFunc} from "./common/translations/localize"; +import {CoreFrontendUserData} from "./data/frontend"; -/** - * Represents the credentials for a user in Home Assistant. - * - * @property {string} auth_provider_type - The type of authentication provider. - * @property {string} auth_provider_id - The ID of the authentication provider. - */ export interface Credential { auth_provider_type: string; auth_provider_id: string; } -/** - * Represents a multifactor authentication module in Home Assistant. - * - * @property {string} id - The unique identifier for the MFA module. - * @property {string} name - The name of the MFA module. - * @property {boolean} enabled - Whether the MFA module is enabled. - */ export interface MFAModule { id: string; name: string; enabled: boolean; } -/** - * Represents the current user in Home Assistant. - * - * @property {string} id - The unique identifier for the user. - * @property {boolean} is_owner - Indicates if the user is an owner. - * @property {boolean} is_admin - Indicates if the user is an admin. - * @property {string} name - The name of the user. - * @property {Credential[]} credentials - The credentials associated with the user. - * @property {MFAModule[]} mfa_modules - The MFA modules associated with the user. - */ export interface CurrentUser { id: string; is_owner: boolean; @@ -60,18 +37,6 @@ export interface CurrentUser { mfa_modules: MFAModule[]; } -/** - * Represents information about a panel in Home Assistant. - * - * @template T The type of the configuration object for the panel. - * - * @property {string} component_name - The name of the component for the panel. - * @property {T} config - The configuration for the panel. - * @property {string | null} icon - The icon for the panel. - * @property {string | null} title - The title of the panel. - * @property {string} url_path - The URL path for the panel. - * @property {string} [config_panel_domain] - The domain for the configuration panel. - */ export interface PanelInfo | null> { component_name: string; config: T; @@ -81,108 +46,39 @@ export interface PanelInfo | null> { config_panel_domain?: string; } -/** - * Represents the panels in Home Assistant. - * - * @property {Record} panels - The panel configurations. - */ -export interface Panels { - panels: Record; -} +export type Panels = Record; -/** - * Represents a translation in Home Assistant. - * - * @property {string} nativeName - The native name of the language. - * @property {boolean} isRTL - Indicates if the language is written right-to-left. - * @property {string} hash - The hash for the translation. - */ export interface Translation { nativeName: string; isRTL: boolean; hash: string; } -/** - * Represents metadata for translations in Home Assistant. - * - * @property {string[]} fragments - The fragments of the translation. - * @property {Record} translations - The translations mapped by language. - */ export interface TranslationMetadata { fragments: string[]; translations: Record; } -/** - * Represents a dictionary of translations in Home Assistant. - * - * @property {Record} translations - The translations mapped by a key. - */ -export interface TranslationDict { - translations: Record; -} +export type TranslationDict = {[key: string]: string}; -/** - * Represents resources in Home Assistant. - * - * @property {Record>} resources - The resources mapped by a key. - */ -export interface Resources { - resources: Record>; -} +export type Resources = Record>; -/** - * Represents the settings for themes in Home Assistant. - * - * @property {string} theme - The name of the selected theme. - * @property {boolean} [dark] - Indicates if the theme is dark. - * @property {string} [primaryColor] - The primary color of the theme. - * @property {string} [accentColor] - The accent color of the theme. - */ +// Currently selected theme and its settings. These are the values stored in local storage. +// Note: These values are not meant to be used at runtime to check whether dark mode is active +// or which theme name to use, as this interface represents the config data for the theme picker. +// The actually active dark mode and theme name can be read from hass.themes. export interface ThemeSettings { theme: string; + // Radio box selection for theme picker. Do not use in Lovelace rendering as + // it can be undefined == auto. + // Property hass.themes.darkMode carries effective current mode. dark?: boolean; primaryColor?: string; accentColor?: string; } -/** - * Represents the main Home Assistant object. - * - * @interface HomeAssistant - * @property {Auth} auth - The authentication object. - * @property {Connection} connection - The connection object. - * @property {boolean} connected - Indicates if the connection is active. - * @property {HassEntities} states - The current states of entities. - * @property {Record} entities - The entities in the registry. - * @property {Record} devices - The devices in the registry. - * @property {Record} areas - The areas in the registry. - * @property {Record} floors - The floors in the registry. - * @property {HassServices} services - The services available in Home Assistant. - * @property {HassConfig} config - The configuration for Home Assistant. - * @property {Themes} themes - The available themes. - * @property {ThemeSettings | null} selectedTheme - The currently selected theme. - * @property {Panels} panels - The panel configurations. - * @property {string} panelUrl - The URL for the panel. - * @property {string} language - The current language. - * @property {string | null} selectedLanguage - The selected language. - * @property {FrontendLocaleData} locale - The locale data. - * @property {Resources} resources - The resources available. - * @property {LocalizeFunc} localize - The localization function. - * @property {TranslationMetadata} translationMetadata - The translation metadata. - * @property {boolean} suspendWhenHidden - Indicates if the frontend should suspend when hidden. - * @property {boolean} enableShortcuts - Indicates if shortcuts are enabled. - * @property {boolean} vibrate - Indicates if vibration feedback is enabled. - * @property {boolean} debugConnection - Indicates if debug mode is enabled for the connection. - * @property {'docked' | 'always_hidden' | 'auto'} dockedSidebar - The sidebar visibility setting. - * @property {string} defaultPanel - The default panel to show. - * @property {string | null} moreInfoEntityId - The entity ID for more info. - * @property {CurrentUser} [user] - The current user object. - * @property {CoreFrontendUserData | null} [userData] - The frontend user data. - */ export interface HomeAssistant { - auth: Auth & { external?: { [key: string]: any } }; + auth: Auth & { external?: {[key: string]: any;} }; connection: Connection; connected: boolean; states: HassEntities; @@ -196,7 +92,14 @@ export interface HomeAssistant { selectedTheme: ThemeSettings | null; panels: Panels; panelUrl: string; + // i18n + // current effective language in that order: + // - backend saved user selected language + // - language in local app storage + // - browser language + // - english (en) language: string; + // local stored language, keep that name for backward compatibility selectedLanguage: string | null; locale: FrontendLocaleData; resources: Resources; @@ -206,166 +109,57 @@ export interface HomeAssistant { enableShortcuts: boolean; vibrate: boolean; debugConnection: boolean; - dockedSidebar: 'docked' | 'always_hidden' | 'auto'; + dockedSidebar: "docked" | "always_hidden" | "auto"; defaultPanel: string; moreInfoEntityId: string | null; user?: CurrentUser; userData?: CoreFrontendUserData | null; - - /** - * Returns the URL for the Home Assistant instance. - * - * @param {any} path - Optional path to append to the base URL. - */ hassUrl(path?: any): string; - - /** - * Calls a service in Home Assistant. - * - * @param {ServiceCallRequest['domain']} domain - The domain of the service. - * @param {ServiceCallRequest['service']} service - The name of the service to call. - * @param {ServiceCallRequest['serviceData']} [serviceData] - Optional data to send with the service call. - * @param {ServiceCallRequest['target']} [target] - Optional target for the service call. - * @param {boolean} [notifyOnError] - Whether to notify on error. - * @param {boolean} [returnResponse] - Whether to return the response. - */ callService( - domain: ServiceCallRequest['domain'], - service: ServiceCallRequest['service'], - serviceData?: ServiceCallRequest['serviceData'], - target?: ServiceCallRequest['target'], + domain: ServiceCallRequest["domain"], + service: ServiceCallRequest["service"], + serviceData?: ServiceCallRequest["serviceData"], + target?: ServiceCallRequest["target"], notifyOnError?: boolean, - returnResponse?: boolean, + returnResponse?: boolean ): Promise; - - /** - * Calls the Home Assistant API. - * - * @template T The expected response type. - * - * @param {'GET' | 'POST' | 'PUT' | 'DELETE'} method - The HTTP method to use. - * @param {string} path - The API endpoint path. - * @param {Record} [parameters] - Optional parameters to send with the request. - * @param {Record} [headers] - Optional headers to include in the request. - */ callApi( - method: 'GET' | 'POST' | 'PUT' | 'DELETE', + method: "GET" | "POST" | "PUT" | "DELETE", path: string, parameters?: Record, - headers?: Record, + headers?: Record ): Promise; - - /** - * Calls the Home Assistant API with raw response. - * - * @param {'GET' | 'POST' | 'PUT' | 'DELETE'} method - The HTTP method to use. - * @param {string} path - The API endpoint path. - * @param {Record} [parameters] - Optional parameters to send with the request. - * @param {Record} [headers] - Optional headers to include in the request. - * @param {AbortSignal} [signal] - Optional signal to abort the request. - */ - callApiRaw( - method: 'GET' | 'POST' | 'PUT' | 'DELETE', + callApiRaw( // introduced in 2024.11 + method: "GET" | "POST" | "PUT" | "DELETE", path: string, parameters?: Record, headers?: Record, - signal?: AbortSignal, + signal?: AbortSignal ): Promise; - - /** - * Fetches a resource with authentication. - * - * @param {string} path - The resource path to fetch. - * @param {Record} [init] - Optional fetch options. - */ fetchWithAuth(path: string, init?: Record): Promise; - - /** - * Sends a WebSocket message. - * - * @param {MessageBase} msg - The message to send. - */ sendWS(msg: MessageBase): void; - - /** - * Calls a WebSocket service. - * - * @template T The expected response type. - * - * @param {MessageBase} msg - The message to send. - */ callWS(msg: MessageBase): Promise; - - /** - * Load backend translation. - * - * @param {Parameters[2]} category - The category of translations. - * @param {Parameters[3]} [integrations] - Optional integrations to include. - * @param {Parameters[4]} [configFlow] - Optional config flow. - * - * @returns {Promise} The localization function. - */ loadBackendTranslation( category: Parameters[2], integrations?: Parameters[3], - configFlow?: Parameters[4], + configFlow?: Parameters[4] ): Promise; - - /** - * Load fragment translation. - * - * @param {string} fragment - The fragment to load. - * @returns {Promise} The localization function or undefined. - */ loadFragmentTranslation(fragment: string): Promise; - - /** - * Formats the state of an entity. - * - * @param {HassEntity} stateObj - The state object of the entity. - * @param {string} [state] - Optional state to format. - */ formatEntityState(stateObj: HassEntity, state?: string): string; - - /** - * Formats the value of an entity attribute. - * - * @param {HassEntity} stateObj - The state object of the entity. - * @param {string} attribute - The attribute to format. - * @param {any} [value] - Optional value to format. - */ - formatEntityAttributeValue(stateObj: HassEntity, attribute: string, value?: any): string; - - /** - * Formats the name of an entity attribute. - * - * @param {HassEntity} stateObj - The state object of the entity. - * @param {string} attribute - The attribute to format. - */ + formatEntityAttributeValue( + stateObj: HassEntity, + attribute: string, + value?: any + ): string; formatEntityAttributeName(stateObj: HassEntity, attribute: string): string; } -/** - * Represents the context of a service call. - * - * @property {string} id - The unique identifier for the context. - * @property {string} [parent_id] - The optional parent ID of the context. - * @property {string | null} [user_id] - The optional user ID associated with the context. - */ export interface Context { id: string; parent_id?: string; user_id?: string | null; } -/** - * Represents a service call request in Home Assistant. - * - * @property {string} domain - The domain of the service to call. - * @property {string} service - The name of the service to call. - * @property {Record} [serviceData] - Optional data to send with the service call. - * @property {HassServiceTarget} [target] - Optional target for the service call. - */ export interface ServiceCallRequest { domain: string; service: string; @@ -373,12 +167,6 @@ export interface ServiceCallRequest { target?: HassServiceTarget; } -/** - * Represents the response from a service call in Home Assistant. - * - * @property {Context} context - The context of the service call. - * @property {any} [response] - The optional response data from the service call. - */ export interface ServiceCallResponse { context: Context; response?: any; diff --git a/src/types/lovelace-mushroom/cards/chips-card.ts b/src/types/lovelace-mushroom/cards/chips-card.ts index 5e42c67..55bf341 100644 --- a/src/types/lovelace-mushroom/cards/chips-card.ts +++ b/src/types/lovelace-mushroom/cards/chips-card.ts @@ -1,11 +1,11 @@ -import { LovelaceCardConfig } from '../../homeassistant/data/lovelace/config/card'; -import { LovelaceChipConfig } from '../utils/lovelace/chip/types'; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {LovelaceChipConfig} from "../utils/lovelace/chip/types"; /** * Chips Card Configuration * - * @property {LovelaceChipConfig[]} chips - Array of chips to display. - * @property {string} [alignment] - Chips alignment (start, end, center, justify). Defaults to 'start'. + * @param {LovelaceChipConfig[]} chips Chips Array + * @param {string} [alignment=start] Chips alignment (start,end, center, justify), when empty default behavior is start. * * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/chips.md */ diff --git a/src/types/lovelace-mushroom/cards/fan-card-config.ts b/src/types/lovelace-mushroom/cards/fan-card-config.ts index 761dbce..cf792a7 100644 --- a/src/types/lovelace-mushroom/cards/fan-card-config.ts +++ b/src/types/lovelace-mushroom/cards/fan-card-config.ts @@ -1,15 +1,16 @@ -import { LovelaceCardConfig } from '../../homeassistant/data/lovelace/config/card'; -import { ActionsSharedConfig } from '../shared/config/actions-config'; -import { AppearanceSharedConfig } from '../shared/config/appearance-config'; -import { EntitySharedConfig } from '../shared/config/entity-config'; +import {ActionsSharedConfig} from "../shared/config/actions-config"; +import {LovelaceCardConfig} from "../../homeassistant/data/lovelace"; +import {EntitySharedConfig} from "../shared/config/entity-config"; +import {AppearanceSharedConfig} from "../shared/config/appearance-config"; /** - * Fan Card Configuration + * Fan Card Config. * - * @property {boolean} [icon_animation] - Animate the icon when the fan is on. Defaults to false. - * @property {boolean} [show_percentage_control] - Show a slider to control speed. Defaults to false. - * @property {boolean} [show_oscillate_control] - Show a button to control oscillation. Defaults to false. - * @property {boolean} [show_direction_control] - Show a button to control the direction. Defaults to false. + * @property {boolean} [icon_animation=false] Animate the icon when fan is on. + * @property {boolean} [show_percentage_control=false] Show a slider to control speed. + * @property {boolean} [show_oscillate_control=false] Show a button to control oscillation. + * @property {boolean} [show_direction_control=false] Show a button to control the direction. + * @property {boolean} [icon_animation=false] Animate the icon when fan is on. * * @see https://github.com/piitaya/lovelace-mushroom/blob/main/docs/cards/fan.md */ @@ -17,8 +18,9 @@ export type FanCardConfig = LovelaceCardConfig & EntitySharedConfig & AppearanceSharedConfig & ActionsSharedConfig & { - icon_animation?: boolean; - show_percentage_control?: boolean; - show_oscillate_control?: boolean; - show_direction_control?: boolean; - }; + icon_animation?: boolean; + show_percentage_control?: boolean; + show_oscillate_control?: boolean; + show_direction_control?: boolean; + collapsible_controls?: boolean; +}; diff --git a/src/types/strategy/cards.ts b/src/types/strategy/cards.ts new file mode 100644 index 0000000..20eacfc --- /dev/null +++ b/src/types/strategy/cards.ts @@ -0,0 +1,72 @@ +import {LovelaceCardConfig} from "../homeassistant/data/lovelace"; +import {TitleCardConfig} from "../lovelace-mushroom/cards/title-card-config"; +import {EntitySharedConfig} from "../lovelace-mushroom/shared/config/entity-config"; +import {AppearanceSharedConfig} from "../lovelace-mushroom/shared/config/appearance-config"; +import {ActionsSharedConfig} from "../lovelace-mushroom/shared/config/actions-config"; +import {TemplateCardConfig} from "../lovelace-mushroom/cards/template-card-config"; +import {EntityCardConfig} from "../lovelace-mushroom/cards/entity-card-config"; +import {AreaCardConfig, PictureEntityCardConfig} from "../homeassistant/panels/lovelace/cards/types"; +import {ClimateCardConfig} from "../lovelace-mushroom/cards/climate-card-config"; +import {CoverCardConfig} from "../lovelace-mushroom/cards/cover-card-config"; +import {FanCardConfig} from "../lovelace-mushroom/cards/fan-card-config"; +import {LightCardConfig} from "../lovelace-mushroom/cards/light-card-config"; +import {LockCardConfig} from "../lovelace-mushroom/cards/lock-card-config"; +import {MediaPlayerCardConfig} from "../lovelace-mushroom/cards/media-player-card-config"; +import {NumberCardConfig} from "../lovelace-mushroom/cards/number-card-config"; +import {PersonCardConfig} from "../lovelace-mushroom/cards/person-card-config"; +import {VacuumCardConfig} from "../lovelace-mushroom/cards/vacuum-card-config"; +import {SelectCardConfig} from '../lovelace-mushroom/cards/select-card-config'; + +export namespace cards { + /** + * Abstract Card Config. + */ + export type AbstractCardConfig = LovelaceCardConfig & + EntitySharedConfig & + AppearanceSharedConfig & + ActionsSharedConfig; + + /** + * Controller Card Config. + * + * @property {boolean} [showControls=true] False to hide controls. + * @property {string} [iconOn] Icon to show for switching entities from off state. + * @property {string} [iconOff] Icon to show for switching entities to off state. + * @property {string} [onService=none] Service to call for switching entities from off state. + * @property {string} [offService=none] Service to call for switching entities to off state. + */ + export interface ControllerCardConfig extends TitleCardConfig { + type: "mushroom-title-card", + showControls?: boolean; + iconOn?: string; + iconOff?: string; + onService?: string; + offService?: string; + } + + export type AreaCardOptions = Omit; + export type ClimateCardOptions = Omit; + export type ControllerCardOptions = Omit; + export type CoverCardOptions = Omit; + export type EntityCardOptions = Omit; + export type FanCardOptions = Omit; + export type LightCardOptions = Omit; + export type LockCardOptions = Omit; + export type MediaPlayerCardOptions = Omit; + export type NumberCardOptions = Omit; + export type PersonCardOptions = Omit; + export type PictureEntityCardOptions = Omit; + export type TemplateCardOptions = Omit; + export type VacuumCardOptions = Omit; + export type SelectCardOptions = Omit; + export type InputSelectCardOptions = Omit; +} + + + + + + + + + diff --git a/src/types/strategy/generic.ts b/src/types/strategy/generic.ts new file mode 100644 index 0000000..7ced83d --- /dev/null +++ b/src/types/strategy/generic.ts @@ -0,0 +1,367 @@ +import {CallServiceActionConfig, LovelaceCardConfig,} from "../homeassistant/data/lovelace"; +import {HomeAssistant} from "../homeassistant/types"; +import {AreaRegistryEntry} from "../homeassistant/data/area_registry"; +import {cards} from "./cards"; +import {EntityRegistryEntry} from "../homeassistant/data/entity_registry"; +import {LovelaceChipConfig} from "../lovelace-mushroom/utils/lovelace/chip/types"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {LovelaceViewConfig, LovelaceViewRawConfig} from "../homeassistant/data/lovelace/config/view"; +import {LovelaceConfig} from "../homeassistant/data/lovelace/config/types"; + +/** + * List of supported domains. + * + * This constant array defines the domains that are supported by the strategy. + * Each domain represents a specific type of entity within the Home Assistant ecosystem. + * + * _ refers to all domains. + * default refers to the miscellanea domain. + * + * @readonly + * @constant + */ +const SUPPORTED_DOMAINS = [ + "_", + "binary_sensor", + "camera", + "climate", + "cover", + "default", + "fan", + "input_select", + "light", + "lock", + "media_player", + "number", + "scene", + "select", + "sensor", + "switch", + "vacuum", +] as const; + +/** + * List of supported views. + * + * This constant array defines the views that are supported by the strategy. + * + * @readonly + * @constant + */ +const SUPPORTED_VIEWS = [ + "camera", + "climate", + "cover", + "fan", + "home", + "light", + "scene", + "switch", + "vacuum", +] as const; + +const SUPPORTED_CHIPS = [ + "light", + "fan", + "cover", + "switch", + "climate", + "weather", +] as const; + +/** + * List of home view sections. + * + * This constant array defines the sections that are present in the home view. + * + * @readonly + * @constant + */ +const HOME_VIEW_SECTIONS = [ + "areas", + "areasTitle", + "chips", + "greeting", + "persons", +] as const; + +export namespace generic { + export type SupportedDomains = typeof SUPPORTED_DOMAINS[number]; + export type SupportedViews = typeof SUPPORTED_VIEWS[number]; + export type SupportedChips = typeof SUPPORTED_CHIPS[number]; + export type HomeViewSections = typeof HOME_VIEW_SECTIONS[number]; + + /** + * An entry of a Home Assistant Register. + */ + export type RegistryEntry = + | AreaRegistryEntry + | DataTransfer + | EntityRegistryEntry + + /** + * View Configuration of the strategy. + * + * @interface StrategyViewConfig + * @extends LovelaceViewConfig + * + * @property {boolean} [hidden] If True, the view is hidden from the dashboard. + * @property {number} [order] Ordering position of the views at the top of the dashboard. + */ + export interface StrategyViewConfig extends LovelaceViewConfig { + hidden: boolean; + order: number; + } + + /** + * All Domains Configuration. + * + * @interface AllDomainsConfig + * + * @property {boolean} [hide_config_entities] If True, all configuration entities are hidden from the dashboard. + * @property {boolean} [hide_diagnostic_entities] If True, all diagnostic entities are hidden from the dashboard. + */ + export interface AllDomainsConfig { + hide_config_entities: boolean; + hide_diagnostic_entities: boolean; + } + + /** + * Single Domain Configuration. + * + * @interface SingleDomainConfig + * @extends Partial + * + * @property {boolean} [hidden] If True, all entities of the domain are hidden from the dashboard. + * @property {number} [order] Ordering position of the domains in a views. + */ + export interface SingleDomainConfig extends Partial { + hidden: boolean; + order?: number; + } + + /** + * Dashboard Info Object. + * + * Home Assistant passes this object to the Dashboard Generator method. + * + * @interface DashboardInfo + * + * @property {LovelaceConfig} config Dashboard configuration. + * @property {HomeAssistant} hass The Home Assistant object. + * + * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#dashboard-strategies + */ + export interface DashboardInfo { + config: LovelaceViewRawConfig & { + strategy: { + options?: StrategyConfig & { area: StrategyArea } + } + }; + hass: HomeAssistant; + } + + /** + * View Info Object. + * + * Home Assistant passes this object to the View Generator method. + * + * @interface ViewInfo + * + * @property {LovelaceConfig} config Dashboard configuration. + * @property {HomeAssistant} hass The Home Assistant object. + * @property {LovelaceViewConfig} view View configuration. + * + * @see https://developers.home-assistant.io/docs/frontend/custom-ui/custom-strategy/#view-strategies + */ + export interface ViewInfo { + config: LovelaceConfig; + hass: HomeAssistant; + view: LovelaceViewRawConfig & { + strategy: { + options?: StrategyConfig & { area: StrategyArea } + } + }; + } + + /** + * Strategy Configuration. + * + * @interface StrategyConfig + * + * @property {Object.} areas List of areas. + * @property {Object.} card_options Card options for entities. + * @property {Partial} chips The configuration of chips in the Home view. + * @property {boolean} debug If True, the strategy outputs more verbose debug information in the console. + * @property {Object.} domains List of domains. + * @property {LovelaceCardConfig[]} extra_cards List of cards to show below room cards. + * @property {StrategyViewConfig[]} extra_views List of custom-defined views to add to the dashboard. + * @property {{ hidden: HomeViewSections[] | [] }} home_view List of views to add to the dashboard. + * @property {Object.} views The configurations of views. + * @property {LovelaceCardConfig[]} quick_access_cards List of custom-defined cards to show between the welcome card + * and rooms cards. + */ + export interface StrategyConfig { + areas: { [S: string]: StrategyArea }; + card_options: { [S: string]: CustomCardConfig }; + chips: Partial; + debug: boolean; + domains: { [K in SupportedDomains]: K extends "_" ? AllDomainsConfig : SingleDomainConfig; }; + extra_cards: LovelaceCardConfig[]; + extra_views: StrategyViewConfig[]; + home_view: { + hidden: HomeViewSections[] | []; + } + views: Record; + quick_access_cards: LovelaceCardConfig[]; + } + + /** + * Represents the default configuration for a strategy. + * + * @interface StrategyDefaults + */ + export interface StrategyDefaults extends StrategyConfig { + areas: { "undisclosed": StrategyArea } & { [S: string]: StrategyArea }; + } + + /** + * Strategy Area. + * + * @interface StrategyArea + * + * @property {boolean} [hidden] True if the entity should be hidden from the dashboard. + * @property {object[]} [extra_cards] An array of card configurations. + * The configured cards are added to the dashboard. + * @property {number} [order] Ordering position of the area in the list of available areas. + * @property {string} [type] The type of area card. + */ + export interface StrategyArea extends AreaRegistryEntry { + extra_cards?: LovelaceCardConfig[]; + hidden?: boolean; + order?: number; + type?: string; + } + + /** + * A list of chips to show in the Home view. + * + * @interface ChipConfiguration + * + * @property {boolean} climate_count Chip to display the number of climates which are not off. + * @property {boolean} cover_count Chip to display the number of unclosed covers. + * @property {boolean} fan_count Chip to display the number of fans on. + * @property {boolean} light_count Chip to display the number of lights on. + * @property {boolean} switch_count Chip to display the number of switches on. + * @property {string} weather_entity Entity ID for the weather chip to use, accepts `weather.` only. + * @property {object[]} extra_chips List of extra chips. + */ + export interface ChipConfiguration { + climate_count: boolean; + cover_count: boolean; + extra_chips: LovelaceChipConfig[]; + fan_count: boolean; + light_count: boolean; + switch_count: boolean; + weather_entity: string; + } + + /** + * Custom Card Configuration for an entity. + * + * @interface CustomCardConfig + * @extends LovelaceCardConfig + * + * @property {boolean} hidden If True, the card is hidden from the dashboard. + */ + export interface CustomCardConfig extends LovelaceCardConfig { + hidden?: boolean; + } + + /** + * Area Filter Context. + * + * @interface AreaFilterContext + * + * @property {AreaRegistryEntry} area Area Entry. + * @property {string[]} areaDeviceIds The id of devices which are linked to the area. + * @property {string} [domain] Domain of an entity. + * Example: `light`. + */ + export interface AreaFilterContext { + area: AreaRegistryEntry; + areaDeviceIds: string[]; + domain?: string; + } + + /** + * Checks if the given object is an instance of CallServiceActionConfig. + * + * @param {any} obj - The object to be checked. + * @returns {boolean} - Returns true if the object is an instance of CallServiceActionConfig, otherwise false. + */ + export function isCallServiceActionConfig(obj: any): obj is CallServiceActionConfig { + return obj && obj.action === "call-service" && ["action", "service"].every(key => key in obj); + } + + /** + * Checks if the given object is an instance of HassServiceTarget. + * + * @param {any} obj - The object to check. + * @returns {boolean} - True if the object is an instance of HassServiceTarget, false otherwise. + */ + export function isCallServiceActionTarget(obj: any): obj is HassServiceTarget { + return obj && ["entity_id", "device_id", "area_id"].some(key => key in obj); + } + + interface SortableBase { + order: number; + } + + type SortableWithTitle = SortableBase & { title: string; name?: never }; + type SortableWithName = SortableBase & { name: string; title?: never }; + + + /** + * The union type of SortableWithTitle and SortableWithName. + * + * @remarks + * This type is used to sort objects by title or by name. + * The `order` property is used to sort the objects. + * The `title` and `name` properties are used to display the object in the UI. + */ + export type Sortable = SortableWithTitle | SortableWithName; + + + /** + * Checks if the given object is of a sortable type. + * + * Sortable types are objects that have a `title` or a `name` property and an `order` property. + * + * @param {any} obj - The object to check. + * @returns {boolean} - True if the object is an instance of Sortable, false otherwise. + */ + export function isSortable(obj: any): obj is Sortable { + return obj && 'order' in obj && ('title' in obj || 'name' in obj); + } + + /** + * Checks if the given view id is a supported view. + * + * @param {string} id - The view id to check. + * @returns {boolean} - Returns true if the view id is a supported view, otherwise false. + */ + export function isSupportedView(id: string): id is SupportedViews { + return SUPPORTED_VIEWS.includes(id as SupportedViews); + } + + /** + * Checks if the given domain id is a supported domain. + * + * @param {string} id - The domain id to check. + * @returns {boolean} - Returns true if the domain id is a supported domain, otherwise false. + */ + export function isSupportedDomain(id: string): id is SupportedDomains { + return SUPPORTED_DOMAINS.includes(id as SupportedDomains); + } +} diff --git a/src/types/strategy/views.ts b/src/types/strategy/views.ts new file mode 100644 index 0000000..17442e5 --- /dev/null +++ b/src/types/strategy/views.ts @@ -0,0 +1,17 @@ +import {cards} from "./cards"; +import {LovelaceViewConfig} from "../homeassistant/data/lovelace/config/view"; + +export namespace views { + /** + * Options for the extended View class. + * + * @property {cards.ControllerCardConfig} [controllerCardOptions] Options for the Controller card. + */ + export interface ViewConfig extends LovelaceViewConfig { + controllerCardOptions?: cards.ControllerCardOptions; + } +} + + + + diff --git a/src/utillties/filters.ts b/src/utillties/filters.ts new file mode 100644 index 0000000..8786d58 --- /dev/null +++ b/src/utillties/filters.ts @@ -0,0 +1,64 @@ +import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry"; +import {Helper} from "../Helper"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + +/** + * Filter an array of entities by property/value pair + * + * @param entities The array of entities to filter. + * @param property The property to filter on. + * @param value The value to match. + * @param exclude Whether to exclude entities with the given property/value pair (default: true). + * + * @returns A new list of entities filtered by the given property/value pair. + */ +export function filterEntitiesByPropertyValue( + entities: EntityRegistryEntry[], + property: keyof EntityRegistryEntry, + value: any, + exclude: boolean = true +) { + return entities.filter(entity => exclude ? entity[property] !== value : entity[property] === value); +} + +export function applyEntityCategoryFilters(entities: EntityRegistryEntry[], domain: SupportedDomains) { + if (!Helper.isInitialized()) { + throw new Error("The Helper module must be initialized before using this one."); + } + + const domainOptions = { + ...Helper.strategyOptions.domains["_"], + ...Helper.strategyOptions.domains[domain], + }; + + let filteredEntityCategory = []; + + if (domainOptions.hide_config_entities) { + entities = filterEntitiesByPropertyValue(entities, "entity_category", "config"); + filteredEntityCategory.push("Config"); + } + + if (domainOptions.hide_diagnostic_entities) { + entities = filterEntitiesByPropertyValue(entities, "entity_category", "diagnostic"); + filteredEntityCategory.push("Diagnostic"); + } + + if (Helper.debug && filteredEntityCategory.length > 0) { + console.warn(filteredEntityCategory.join(" & ") + " entities are filtered out."); + } + + return entities; +} + +/*export function filterHiddenEntities(entities: EntityRegistryEntry[]) { + entities = entities.filter( + function (entity) { + return entity.hidden_by === null // entity is not hidden by HASS settings. + && entity.disabled_by === null // entity is not disabled by HASS settings. + && Helper.strategyOptions.card_options.[entity.entity_id] // entity is not hidden by strategy options. + } + ); +}*/ + + diff --git a/src/views/AbstractView.ts b/src/views/AbstractView.ts index e6a3aa3..a252f81 100644 --- a/src/views/AbstractView.ts +++ b/src/views/AbstractView.ts @@ -1,155 +1,171 @@ -import { HassServiceTarget } from 'home-assistant-js-websocket'; -import HeaderCard from '../cards/HeaderCard'; -import { Registry } from '../Registry'; -import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card'; -import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view'; -import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; -import { AbstractCardConfig, CustomHeaderCardConfig, StrategyHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { SupportedDomains } from '../types/strategy/strategy-generics'; -import { ViewConfig, ViewConstructor } from '../types/strategy/strategy-views'; -import { sanitizeClassName } from '../utilities/auxiliaries'; -import { logMessage, lvlFatal } from '../utilities/debug'; -import RegistryFilter from '../utilities/RegistryFilter'; +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {LovelaceCardConfig} from "../types/homeassistant/data/lovelace"; +import {cards} from "../types/strategy/cards"; +import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config"; +import {HassServiceTarget} from "home-assistant-js-websocket"; +import {applyEntityCategoryFilters} from "../utillties/filters"; +import {LovelaceViewConfig} from "../types/homeassistant/data/lovelace/config/view"; +import {StackCardConfig} from "../types/homeassistant/panels/lovelace/cards/types"; +import {generic} from "../types/strategy/generic"; +import abstractCardConfig = cards.AbstractCardConfig; +import SupportedDomains = generic.SupportedDomains; /** * Abstract View Class. * - * To create a view configuration, this class should be extended by a child class. - * Child classes should override the default configuration so the view correctly reflects the entities of a domain. + * To create a new view, extend the new class with this one. * - * @remarks - * Before this class can be used, the Registry module must be initialized by calling {@link Registry.initialize}. + * @class + * @abstract */ abstract class AbstractView { - /** The base configuration of a view. */ - protected baseConfiguration: LovelaceViewConfig = { - icon: 'mdi:view-dashboard', + /** + * Configuration of the view. + * + * @type {LovelaceViewConfig} + */ + config: LovelaceViewConfig = { + icon: "mdi:view-dashboard", subview: false, }; - /** A card configuration to control all entities in the view. */ - private viewHeaderCardConfiguration: StackCardConfig = { + /** + * A card to switch all entities in the view. + * + * @type {StackCardConfig} + */ + viewControllerCard: StackCardConfig = { cards: [], - type: '', + type: "", }; - protected get domain(): SupportedDomains | 'home' { - return (this.constructor as unknown as ViewConstructor).domain; - } + /** + * The domain of which we operate the devices. + * + * @type {SupportedDomains | "home"} + * @private + * @readonly + */ + readonly #domain: SupportedDomains | "home"; /** * Class constructor. * - * @remarks - * Before this class can be used, the Registry module must be initialized by calling {@link Registry.initialize}. + * @param {SupportedDomains} domain The domain which the view is representing. + * + * @throws {Error} If trying to instantiate this class. + * @throws {Error} If the Helper module isn't initialized. */ - protected constructor() { - if (!Registry.initialized) { - logMessage(lvlFatal, 'Registry not initialized!'); + protected constructor(domain: SupportedDomains | "home") { + if (!Helper.isInitialized()) { + throw new Error("The Helper module must be initialized before using this one."); } + + this.#domain = domain; } /** - * Create the configuration of the cards to include in the view. + * Create the cards to include in the view. + * + * @return {Promise<(StackCardConfig | TitleCardConfig)[]>} An array of card objects. */ - protected async createCardConfigurations(): Promise { + async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> { + if (this.#domain === "home") { + // The home domain should override this method because it hasn't entities on its own. + // The method override creates its own cards to show at the home view. + + return []; + } + const viewCards: LovelaceCardConfig[] = []; - const moduleName = sanitizeClassName(this.domain + 'Card'); - const DomainCard = (await import(`../cards/${moduleName}`)).default; - const domainEntities = new RegistryFilter(Registry.entities) - .whereDomain(this.domain) - .where((entity) => !entity.entity_id.endsWith('_stateful_scene')) - .toList(); - // Create card configurations for each area. - for (const area of Registry.areas) { - const areaCards: AbstractCardConfig[] = []; + // Create cards for each area. + for (const area of Helper.areas) { + const areaCards: abstractCardConfig[] = []; + const className = Helper.sanitizeClassName(this.#domain + "Card"); + const cardModule = await import(`../cards/${className}`); - // Set the target of the Header card to the current area. + // Set the target for controller cards to the current area. let target: HassServiceTarget = { area_id: [area.area_id], }; - const areaEntities = new RegistryFilter(domainEntities).whereAreaId(area.area_id).toList(); - // Set the target of the Header card to entities without an area. - if (area.area_id === 'undisclosed') { + let entities = Helper.getDeviceEntities(area, this.#domain); + // Exclude hidden Config and Diagnostic entities. + entities = applyEntityCategoryFilters(entities, this.#domain); + + // Set the target for controller cards to entities without an area. + if (area.area_id === "undisclosed") { target = { - entity_id: areaEntities.map((entity) => entity.entity_id), - }; + entity_id: entities.map(entity => entity.entity_id), + } } - // Create a card configuration for each entity in the current area. - areaCards.push( - ...areaEntities.map((entity) => - new DomainCard(entity, Registry.strategyOptions.card_options?.[entity.entity_id]).getCard(), - ), - ); + // Create a card for each domain-entity of the current area. + for (const entity of entities) { + let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; + let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"]; - // Vertically stack the cards of the current area. + if (cardOptions?.hidden || deviceOptions?.hidden) { + continue; + } + + areaCards.push(new cardModule[className](entity, cardOptions).getCard()); + } + + // Vertical stack the area cards if it has entities. if (areaCards.length) { - // Create and insert a Header card. - const areaHeaderCardOptions = ( - 'headerCardConfiguration' in this.baseConfiguration ? this.baseConfiguration.headerCardConfiguration : {} - ) as CustomHeaderCardConfig; + const titleCardOptions = ("controllerCardOptions" in this.config) ? this.config.controllerCardOptions : {}; - areaCards.unshift(new HeaderCard(target, { title: area.name, ...areaHeaderCardOptions }).createCard()); + // Create and insert a Controller card. + areaCards.unshift(new ControllerCard(target, Object.assign({title: area.name}, titleCardOptions)).createCard()); - viewCards.push({ type: 'vertical-stack', cards: areaCards }); + viewCards.push({ + type: "vertical-stack", + cards: areaCards, + } as StackCardConfig); } } - // Add a Header Card to control all the entities in the view. - if (this.viewHeaderCardConfiguration.cards.length && viewCards.length) { - viewCards.unshift(this.viewHeaderCardConfiguration); + // Add a Controller Card for all the entities in the view. + if (this.viewControllerCard.cards.length && viewCards.length) { + viewCards.unshift(this.viewControllerCard); } return viewCards; } /** - * Get a view configuration. + * Get a view object. * - * The configuration includes the card configurations which are created by createCardConfigurations(). + * The view includes the cards which are created by method createViewCards(). + * + * @returns {Promise} The view object. */ async getView(): Promise { return { - ...this.baseConfiguration, - cards: await this.createCardConfigurations(), + ...this.config, + cards: await this.createViewCards(), }; } /** - * Get the domain's entity ids to target for a HASS service call. - */ - private getDomainTargets(): HassServiceTarget { - return { - entity_id: Registry.entities - .filter((entity) => entity.entity_id.startsWith(this.domain + '.')) - .map((entity) => entity.entity_id), - }; - } - - /** - * Initialize the view configuration with defaults and custom settings. + * Get a target of entity IDs for the given domain. * - * @param viewConfiguration The view's default configuration for the view. - * @param customConfiguration The view's custom configuration to apply. - * @param headerCardConfig The view's Header card configuration. + * @param {string} domain - The target domain to retrieve entity IDs from. + * @return {HassServiceTarget} - A target for a service call. */ - protected initializeViewConfig( - viewConfiguration: ViewConfig, - customConfiguration: ViewConfig = {}, - headerCardConfig: CustomHeaderCardConfig, - ): void { - this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration }; - - this.viewHeaderCardConfiguration = new HeaderCard(this.getDomainTargets(), { - ...(('headerCardConfiguration' in this.baseConfiguration - ? this.baseConfiguration.headerCardConfiguration - : {}) as StrategyHeaderCardConfig), - ...headerCardConfig, - }).createCard(); + targetDomain(domain: string): HassServiceTarget { + return { + entity_id: Helper.entities.filter( + entity => + entity.entity_id.startsWith(domain + ".") + && !entity.hidden_by + && !Helper.strategyOptions.card_options?.[entity.entity_id]?.hidden + ).map(entity => entity.entity_id), + }; } } -export default AbstractView; +export {AbstractView}; diff --git a/src/views/CameraView.ts b/src/views/CameraView.ts index 0674cac..1fd2005 100644 --- a/src/views/CameraView.ts +++ b/src/views/CameraView.ts @@ -1,54 +1,77 @@ +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {Helper} from "../Helper"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { SupportedDomains } from '../types/strategy/strategy-generics'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Camera View Class. * - * Used to create a view configuration for entities of the camera domain. + * Used to create a view for entities of the camera domain. + * + * @class CameraView + * @extends AbstractView */ class CameraView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain: SupportedDomains = 'camera' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "camera"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('camera.cameras'), - path: 'cameras', - icon: 'mdi:cctv', - subview: false, - headerCardConfiguration: { - showControls: false, - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("camera.cameras"), + path: "cameras", + icon: "mdi:cctv", + subview: false, + controllerCardOptions: { + showControls: false, + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('camera.all_cameras'), - subtitle: - `${Registry.getCountTemplate(CameraView.domain, 'ne', 'off')} ${localize('camera.cameras')} ` + - localize('generic.busy'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("camera.all_cameras"), + subtitle: + `${Helper.getCountTemplate(CameraView.#domain, "ne", "off")} ${Helper.customLocalize("camera.cameras")} ` + + Helper.customLocalize("generic.busy"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(CameraView.#domain); - this.initializeViewConfig(CameraView.getDefaultConfig(), customConfiguration, CameraView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + {}, + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default CameraView; +export {CameraView}; diff --git a/src/views/ClimateView.ts b/src/views/ClimateView.ts index 7853567..dfd458a 100644 --- a/src/views/ClimateView.ts +++ b/src/views/ClimateView.ts @@ -1,58 +1,77 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { SupportedDomains } from '../types/strategy/strategy-generics'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Climate View Class. * - * Used to create a view configuration for entities of the climate domain. + * Used to create a view for entities of the climate domain. + * + * @class ClimateView + * @extends AbstractView */ class ClimateView extends AbstractView { - /**The domain of the entities that the view is representing. */ - static readonly domain: SupportedDomains = 'climate' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "climate"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('climate.climates'), - path: 'climates', - icon: 'mdi:thermostat', - subview: false, - headerCardConfiguration: { - showControls: false, - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("climate.climates"), + path: "climates", + icon: "mdi:thermostat", + subview: false, + controllerCardOptions: { + showControls: false, + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('climate.all_climates'), - subtitle: - `${Registry.getCountTemplate(ClimateView.domain, 'ne', 'off')} ${localize('climate.climates')} ` + - localize('generic.busy'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("climate.all_climates"), + subtitle: + `${Helper.getCountTemplate(ClimateView.#domain, "ne", "off")} ${Helper.customLocalize("climate.climates")} ` + + Helper.customLocalize("generic.busy"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(ClimateView.#domain); - this.initializeViewConfig( - ClimateView.getDefaultConfig(), - customConfiguration, - ClimateView.getViewHeaderCardConfig(), - ); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(ClimateView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default ClimateView; +export {ClimateView}; diff --git a/src/views/CoverView.ts b/src/views/CoverView.ts index 78ec2f6..de576e4 100644 --- a/src/views/CoverView.ts +++ b/src/views/CoverView.ts @@ -1,58 +1,80 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { SupportedDomains } from '../types/strategy/strategy-generics'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Cover View Class. * - * Used to create a view configuration for entities of the cover domain. + * Used to create a view for entities of the cover domain. + * + * @class CoverView + * @extends AbstractView */ class CoverView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain: SupportedDomains = 'cover' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "cover"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('cover.covers'), - path: 'covers', - icon: 'mdi:window-open', - subview: false, - headerCardConfiguration: { - iconOn: 'mdi:arrow-up', - iconOff: 'mdi:arrow-down', - onService: 'cover.open_cover', - offService: 'cover.close_cover', - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("cover.covers"), + path: "covers", + icon: "mdi:window-open", + subview: false, + controllerCardOptions: { + iconOn: "mdi:arrow-up", + iconOff: "mdi:arrow-down", + onService: "cover.open_cover", + offService: "cover.close_cover", + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('cover.all_covers'), - subtitle: - `${Registry.getCountTemplate(CoverView.domain, 'search', '(open|opening|closing)')} ` + - `${localize('cover.covers')} ` + - `${localize('generic.unclosed')}`, - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("cover.all_covers"), + subtitle: + `${Helper.getCountTemplate(CoverView.#domain, "search", "(open|opening|closing)")} ${Helper.customLocalize("cover.covers")} ` + + Helper.customLocalize("generic.unclosed"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(CoverView.#domain); - this.initializeViewConfig(CoverView.getDefaultConfig(), customConfiguration, CoverView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(CoverView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default CoverView; +export {CoverView}; diff --git a/src/views/FanView.ts b/src/views/FanView.ts index 17997bb..3cdd259 100644 --- a/src/views/FanView.ts +++ b/src/views/FanView.ts @@ -1,56 +1,80 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { SupportedDomains } from '../types/strategy/strategy-generics'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Fan View Class. * - * Used to create a view configuration for entities of the fan domain. + * Used to create a view for entities of the fan domain. + * + * @class FanView + * @extends AbstractView */ class FanView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain: SupportedDomains = 'fan' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "fan"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('fan.fans'), - path: 'fans', - icon: 'mdi:fan', - subview: false, - headerCardConfiguration: { - iconOn: 'mdi:fan', - iconOff: 'mdi:fan-off', - onService: 'fan.turn_on', - offService: 'fan.turn_off', - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("fan.fans"), + path: "fans", + icon: "mdi:fan", + subview: false, + controllerCardOptions: { + iconOn: "mdi:fan", + iconOff: "mdi:fan-off", + onService: "fan.turn_on", + offService: "fan.turn_off", + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('fan.all_fans'), - subtitle: - `${Registry.getCountTemplate(FanView.domain, 'eq', 'on')} ${localize('fan.fans')} ` + localize('generic.on'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("fan.all_fans"), + subtitle: + `${Helper.getCountTemplate(FanView.#domain, "eq", "on")} ${Helper.customLocalize("fan.fans")} ` + + Helper.customLocalize("generic.on"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(FanView.#domain); - this.initializeViewConfig(FanView.getDefaultConfig(), customConfiguration, FanView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(FanView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default FanView; +export {FanView}; diff --git a/src/views/HomeView.ts b/src/views/HomeView.ts index ce39b76..2106f71 100644 --- a/src/views/HomeView.ts +++ b/src/views/HomeView.ts @@ -1,267 +1,284 @@ +import {Helper} from "../Helper"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {LovelaceChipConfig} from "../types/lovelace-mushroom/utils/lovelace/chip/types"; +import {ChipsCardConfig} from "../types/lovelace-mushroom/cards/chips-card"; +import {TemplateCardConfig} from "../types/lovelace-mushroom/cards/template-card-config"; +import {ActionConfig} from "../types/homeassistant/data/lovelace"; +import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config"; +import {PersonCardConfig} from "../types/lovelace-mushroom/cards/person-card-config"; +import {AreaCardConfig, StackCardConfig} from "../types/homeassistant/panels/lovelace/cards/types"; +import {generic} from "../types/strategy/generic"; +import supportedChips = generic.SupportedChips; + + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { ActionConfig } from '../types/homeassistant/data/lovelace/config/action'; -import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card'; -import { AreaCardConfig, StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types'; -import { ChipsCardConfig } from '../types/lovelace-mushroom/cards/chips-card'; -import { PersonCardConfig } from '../types/lovelace-mushroom/cards/person-card-config'; -import { TemplateCardConfig } from '../types/lovelace-mushroom/cards/template-card-config'; -import { LovelaceChipConfig } from '../types/lovelace-mushroom/utils/lovelace/chip/types'; -import { HomeViewSections, isSupportedChip } from '../types/strategy/strategy-generics'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { sanitizeClassName } from '../utilities/auxiliaries'; -import { logMessage, lvlError, lvlInfo } from '../utilities/debug'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; -import registryFilter from '../utilities/RegistryFilter'; -import { stackHorizontal } from '../utilities/cardStacking'; - /** * Home View Class. * * Used to create a Home view. + * + * @class HomeView + * @extends AbstractView */ class HomeView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain = 'home' as const; - - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('generic.home'), - icon: 'mdi:home-assistant', - path: 'home', - subview: false, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("generic.home"), + icon: "mdi:home-assistant", + path: "home", + subview: false, + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super("home"); - this.baseConfiguration = { ...this.baseConfiguration, ...HomeView.getDefaultConfig(), ...customConfiguration }; + this.config = Object.assign(this.config, this.#defaultConfig, options); } /** - * Create the configuration of the cards to include in the view. + * Create the cards to include in the view. * + * @return {Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]>} Promise a View Card array. * @override */ - async createCardConfigurations(): Promise { - const homeViewCards: LovelaceCardConfig[] = []; + async createViewCards(): Promise<(StackCardConfig | TemplateCardConfig | ChipsCardConfig)[]> { + return await Promise.all([ + this.#createChips(), + this.#createPersonCards(), + this.#createAreaSection(), + ]).then(([chips, personCards, areaCards]) => { + const options = Helper.strategyOptions; + const homeViewCards = []; - let chipsSection, personsSection, areasSection; + if (chips.length) { + // TODO: Create the Chip card at this.#createChips() + homeViewCards.push({ + type: "custom:mushroom-chips-card", + alignment: "center", + chips: chips, + } as ChipsCardConfig) + } - try { - [chipsSection, personsSection, areasSection] = await Promise.all([ - this.createChipsSection(), - this.createPersonsSection(), - this.createAreasSection(), - ]); - } catch (e) { - logMessage(lvlError, 'Error importing created sections!', e); + if (personCards.length) { + // TODO: Create the stack at this.#createPersonCards() + homeViewCards.push({ + type: "horizontal-stack", + cards: personCards, + } as StackCardConfig); + } + + if (!(Helper.strategyOptions.home_view.hidden as string[]).includes("greeting")) { + homeViewCards.push({ + type: "custom:mushroom-template-card", + primary: + `{% set time = now().hour %} {% if (time >= 18) %} ${Helper.customLocalize("generic.good_evening")},{{user}}! + {% elif (time >= 12) %} ${Helper.customLocalize("generic.good_afternoon")}, {{user}}! + {% elif (time >= 5) %} ${Helper.customLocalize("generic.good_morning")}, {{user}}! + {% else %} ${Helper.customLocalize("generic.hello")}, {{user}}! {% endif %}`, + icon: "mdi:hand-wave", + icon_color: "orange", + tap_action: { + action: "none", + } as ActionConfig, + double_tap_action: { + action: "none", + } as ActionConfig, + hold_action: { + action: "none", + } as ActionConfig, + } as TemplateCardConfig); + } + + // Add quick access cards. + if (options.quick_access_cards) { + homeViewCards.push(...options.quick_access_cards); + } + + // Add area cards. + homeViewCards.push({ + type: "vertical-stack", + cards: areaCards, + } as StackCardConfig); + + // Add custom cards. + if (options.extra_cards) { + homeViewCards.push(...options.extra_cards); + } return homeViewCards; - } - - if (chipsSection) { - homeViewCards.push(chipsSection); - } - - if (personsSection) { - homeViewCards.push(personsSection); - } - - // Create the greeting section. - if (!('greeting' in Registry.strategyOptions.home_view.hidden)) { - homeViewCards.push({ - type: 'custom:mushroom-template-card', - primary: `{% set time = now().hour %} - {% if (time >= 18) %} - ${localize('generic.good_evening')},{{user}}! - {% elif (time >= 12) %} - ${localize('generic.good_afternoon')}, {{user}}! - {% elif (time >= 6) %} - ${localize('generic.good_morning')}, {{user}}! - {% else %} - ${localize('generic.hello')}, {{user}}! {% endif %}`, - icon: 'mdi:hand-wave', - icon_color: 'orange', - tap_action: { - action: 'none', - } as ActionConfig, - double_tap_action: { - action: 'none', - } as ActionConfig, - hold_action: { - action: 'none', - } as ActionConfig, - } as TemplateCardConfig); - } - - if (Registry.strategyOptions.quick_access_cards) { - homeViewCards.push(...Registry.strategyOptions.quick_access_cards); - } - - if (areasSection) { - homeViewCards.push(areasSection); - } - - if (Registry.strategyOptions.extra_cards) { - homeViewCards.push(...Registry.strategyOptions.extra_cards); - } - - return homeViewCards; + }); } /** - * Create a chip section to include in the view + * Create the chips to include in the view. * - * If the section is marked as hidden in the strategy option, then the section is not created. + * @return {Promise} Promise a chip array. */ - private async createChipsSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('chips')) { - // The section is hidden. - return; + async #createChips(): Promise { + if ((Helper.strategyOptions.home_view.hidden as string[]).includes("chips")) { + // The Chip section is hidden. + + return []; } - const chipConfigurations: LovelaceChipConfig[] = []; - const exposedChips = Registry.getExposedNames('chip'); + const chips: LovelaceChipConfig[] = []; + const chipOptions = Helper.strategyOptions.chips; - let Chip; + // TODO: Get domains from config. + const exposedChips: supportedChips[] = ["light", "fan", "cover", "switch", "climate"]; + // Create a list of area-ids, used for switching all devices via chips + const areaIds = Helper.areas.map(area => area.area_id ?? ""); + + let chipModule; // Weather chip. - // FIXME: It's not possible to hide the weather chip in the configuration. - const weatherEntityId = - Registry.strategyOptions.chips.weather_entity === 'auto' - ? Registry.entities.find((entity) => entity.entity_id.startsWith('weather.'))?.entity_id - : Registry.strategyOptions.chips.weather_entity; + const weatherEntityId = chipOptions.weather_entity ?? Helper.entities.find( + (entity) => entity.entity_id.startsWith("weather.") && entity.disabled_by === null && entity.hidden_by === null, + )?.entity_id; if (weatherEntityId) { try { - Chip = (await import('../chips/WeatherChip')).default; - const weatherChip = new Chip(weatherEntityId); + chipModule = await import("../chips/WeatherChip"); + const weatherChip = new chipModule.WeatherChip(weatherEntityId); - chipConfigurations.push(weatherChip.getChipConfiguration()); + chips.push(weatherChip.getChip()); } catch (e) { - logMessage(lvlError, 'Error importing chip weather!', e); + Helper.logError("An error occurred while creating the weather chip!", e); } - } else { - logMessage(lvlInfo, 'Weather chip has no entities available.'); } // Numeric chips. - for (const chipName of exposedChips) { - if (!isSupportedChip(chipName) || !new registryFilter(Registry.entities).whereDomain(chipName).count()) { - logMessage(lvlInfo, `Chip for domain ${chipName} is unsupported or has no entities available.`); + for (let chipType of exposedChips) { + if (chipType !== "weather" && (chipOptions?.[(`${chipType}_count`)] ?? true)) { + const className = Helper.sanitizeClassName(chipType + "Chip"); + try { + chipModule = await import((`../chips/${className}`)); + const chip = new chipModule[className](); - continue; - } - - const moduleName = sanitizeClassName(chipName + 'Chip'); - - try { - Chip = (await import(`../chips/${moduleName}`)).default; - const currentChip = new Chip(); - - chipConfigurations.push(currentChip.getChipConfiguration()); - } catch (e) { - logMessage(lvlError, `Error importing chip ${chipName}!`, e); + chip.setTapActionTarget({area_id: areaIds}); + chips.push(chip.getChip()); + } catch (e) { + Helper.logError(`An error occurred while creating the ${chipType} chip!`, e); + } } } - // Add extra chips. - if (Registry.strategyOptions.chips?.extra_chips) { - chipConfigurations.push(...Registry.strategyOptions.chips.extra_chips); + // Extra chips. + if (chipOptions?.extra_chips) { + chips.push(...chipOptions.extra_chips); } - return { - type: 'custom:mushroom-chips-card', - alignment: 'center', - chips: chipConfigurations, - }; + return chips; } /** - * Create a persons section to include in the view. + * Create the person cards to include in the view. * - * If the section is marked as hidden in the strategy option, then the section is not created. + * @return {PersonCardConfig[]} A Person Card array. */ - private async createPersonsSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('persons')) { - // The section is hidden. + #createPersonCards(): PersonCardConfig[] { + if ((Helper.strategyOptions.home_view.hidden as string[]).includes("persons")) { + // The Person section is hidden. - return; + return []; } - const cardConfigurations: PersonCardConfig[] = []; - const PersonCard = (await import('../cards/PersonCard')).default; + const cards: PersonCardConfig[] = []; - cardConfigurations.push( - ...Registry.entities - .filter((entity) => entity.entity_id.startsWith('person.')) - .map((person) => new PersonCard(person).getCard()), - ); + import("../cards/PersonCard").then(personModule => { + for (const person of Helper.entities.filter((entity) => { + return entity.entity_id.startsWith("person.") + && entity.hidden_by == null + && entity.disabled_by == null; + })) { + cards.push(new personModule.PersonCard(person).getCard()); + } + }); - return { - type: 'vertical-stack', - cards: stackHorizontal(cardConfigurations), - }; + return cards; } /** * Create the area cards to include in the view. * * Area cards are grouped into two areas per row. - * If the section is marked as hidden in the strategy option, then the section is not created. + * + * @return {Promise<(TitleCardConfig | StackCardConfig)[]>} Promise an Area Card Section. */ - private async createAreasSection(): Promise { - if ((Registry.strategyOptions.home_view.hidden as string[]).includes('areas')) { + async #createAreaSection(): Promise<(TitleCardConfig | StackCardConfig)[]> { + if ((Helper.strategyOptions.home_view.hidden as string[]).includes("areas")) { // Areas section is hidden. - return; + return []; } - const cardConfigurations: (TemplateCardConfig | AreaCardConfig)[] = []; + const groupedCards: (TitleCardConfig | StackCardConfig)[] = []; - let onlyDefaultCards = true; + let areaCards: (TemplateCardConfig | AreaCardConfig)[] = []; - for (const area of Registry.areas) { - const moduleName = - Registry.strategyOptions.areas[area.area_id]?.type ?? Registry.strategyOptions.areas['_']?.type ?? 'default'; + if (!(Helper.strategyOptions.home_view.hidden as string[]).includes("areasTitle")) { + groupedCards.push({ + type: "custom:mushroom-title-card", + title: Helper.customLocalize("generic.areas"), + }, + ); + } - let AreaCard; + for (const [i, area] of Helper.areas.entries()) { + type ModuleType = typeof import("../cards/AreaCard"); - onlyDefaultCards = onlyDefaultCards && moduleName === 'default'; + let module: ModuleType; + let moduleName = + Helper.strategyOptions.areas[area.area_id]?.type ?? + Helper.strategyOptions.areas["_"]?.type ?? + "default"; + // Load module by type in strategy options. try { - AreaCard = (await import(`../cards/${moduleName}`)).default; + module = await import((`../cards/${moduleName}`)); } catch (e) { // Fallback to the default strategy card. - AreaCard = (await import('../cards/AreaCard')).default; + module = await import("../cards/AreaCard"); - if (Registry.strategyOptions.debug && moduleName !== 'default') { - logMessage(lvlError, `Error importing ${moduleName}: card!`, e); + if (Helper.strategyOptions.debug && moduleName !== "default") { + console.error(e); } } - cardConfigurations.push(new AreaCard(area).getCard()); + // Get a card for the area. + if (!Helper.strategyOptions.areas[area.area_id as string]?.hidden) { + let options = { + ...Helper.strategyOptions.areas["_"], + ...Helper.strategyOptions.areas[area.area_id], + }; + + areaCards.push(new module.AreaCard(area, options).getCard()); + } + + // Horizontally group every two area cards if all cards are created. + if (i === Helper.areas.length - 1) { + for (let i = 0; i < areaCards.length; i += 2) { + groupedCards.push({ + type: "horizontal-stack", + cards: areaCards.slice(i, i + 2), + } as StackCardConfig); + } + } } - // FIXME: The columns are too narrow when having HASS area cards. - return { - type: 'vertical-stack', - title: (Registry.strategyOptions.home_view.hidden as HomeViewSections[]).includes('areasTitle') - ? undefined - : localize('generic.areas'), - cards: stackHorizontal(cardConfigurations, { area: 1, 'custom:mushroom-template-card': 2 }), - }; + return groupedCards; } } -export default HomeView; +export {HomeView}; diff --git a/src/views/LightView.ts b/src/views/LightView.ts index 7aa0f24..90f2cde 100644 --- a/src/views/LightView.ts +++ b/src/views/LightView.ts @@ -1,11 +1,12 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Light View Class. * @@ -15,40 +16,65 @@ import AbstractView from './AbstractView'; * @extends AbstractView */ class LightView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain = 'light' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "light"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('light.lights'), - path: 'lights', - icon: 'mdi:lightbulb-group', - subview: false, - headerCardConfiguration: { - iconOn: 'mdi:lightbulb', - iconOff: 'mdi:lightbulb-off', - onService: 'light.turn_on', - offService: 'light.turn_off', - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("light.lights"), + path: "lights", + icon: "mdi:lightbulb-group", + subview: false, + controllerCardOptions: { + iconOn: "mdi:lightbulb", + iconOff: "mdi:lightbulb-off", + onService: "light.turn_on", + offService: "light.turn_off", + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('light.all_lights'), - subtitle: - `${Registry.getCountTemplate(LightView.domain, 'eq', 'on')} ${localize('light.lights')} ` + - localize('generic.on'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("light.all_lights"), + subtitle: + `${Helper.getCountTemplate(LightView.#domain, "eq", "on")} ${Helper.customLocalize("light.lights")} ` + + Helper.customLocalize("generic.on"), + }; - constructor(customConfiguration?: ViewConfig) { - super(); + /** + * Class constructor. + * + * @param {views.ViewConfig} [options={}] Options for the view. + */ + constructor(options: views.ViewConfig = {}) { + super(LightView.#domain); - this.initializeViewConfig(LightView.getDefaultConfig(), customConfiguration, LightView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(LightView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default LightView; +export {LightView}; diff --git a/src/views/SceneView.ts b/src/views/SceneView.ts index f741415..613352e 100644 --- a/src/views/SceneView.ts +++ b/src/views/SceneView.ts @@ -1,47 +1,54 @@ +import {Helper} from "../Helper"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Scene View Class. * - * sed to create a view configuration for entities of the scene domain. + * Used to create a view for entities of the scene domain. + * + * @class SceneView + * @extends AbstractView */ class SceneView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain = 'scene' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "scene"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('scene.scenes'), - path: 'scenes', - icon: 'mdi:palette', - subview: false, - headerCardConfiguration: { - showControls: false, - }, - }; - } - - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return {}; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("scene.scenes"), + path: "scenes", + icon: "mdi:palette", + subview: false, + controllerCardOptions: { + showControls: false, + }, + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(SceneView.#domain); - this.initializeViewConfig(SceneView.getDefaultConfig(), customConfiguration, SceneView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); } } -export default SceneView; +export {SceneView}; diff --git a/src/views/SwitchView.ts b/src/views/SwitchView.ts index bb10dc1..9df4c07 100644 --- a/src/views/SwitchView.ts +++ b/src/views/SwitchView.ts @@ -1,56 +1,80 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Switch View Class. * - * Used to create a view configuration for entities of the switch domain. + * Used to create a view for entities of the switch domain. + * + * @class SwitchView + * @extends AbstractView */ class SwitchView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain = 'switch' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "switch"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('switch.switches'), - path: 'switches', - icon: 'mdi:dip-switch', - subview: false, - headerCardConfiguration: { - iconOn: 'mdi:power-plug', - iconOff: 'mdi:power-plug-off', - onService: 'switch.turn_on', - offService: 'switch.turn_off', - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("switch.switches"), + path: "switches", + icon: "mdi:dip-switch", + subview: false, + controllerCardOptions: { + iconOn: "mdi:power-plug", + iconOff: "mdi:power-plug-off", + onService: "switch.turn_on", + offService: "switch.turn_off", + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('switch.all_switches'), - subtitle: - `${Registry.getCountTemplate(SwitchView.domain, 'eq', 'on')} ${localize('switch.switches')} ` + - localize('generic.on'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("switch.all_switches"), + subtitle: + `${Helper.getCountTemplate(SwitchView.#domain, "eq", "on")} ${Helper.customLocalize("switch.switches")} ` + + Helper.customLocalize("generic.on"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(SwitchView.#domain); - this.initializeViewConfig(SwitchView.getDefaultConfig(), customConfiguration, SwitchView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(SwitchView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default SwitchView; +export {SwitchView}; diff --git a/src/views/VacuumView.ts b/src/views/VacuumView.ts index 050a58e..819e932 100644 --- a/src/views/VacuumView.ts +++ b/src/views/VacuumView.ts @@ -1,56 +1,80 @@ +import {Helper} from "../Helper"; +import {ControllerCard} from "../cards/ControllerCard"; +import {AbstractView} from "./AbstractView"; +import {views} from "../types/strategy/views"; +import {cards} from "../types/strategy/cards"; +import {generic} from "../types/strategy/generic"; +import SupportedDomains = generic.SupportedDomains; + // noinspection JSUnusedGlobalSymbols Class is dynamically imported. - -import { Registry } from '../Registry'; -import { CustomHeaderCardConfig } from '../types/strategy/strategy-cards'; -import { ViewConfig } from '../types/strategy/strategy-views'; -import { localize } from '../utilities/localize'; -import AbstractView from './AbstractView'; - /** * Vacuum View Class. * - * Used to create a view configuration for entities of the vacuum domain. + * Used to create a view for entities of the vacuum domain. + * + * @class VacuumView + * @extends AbstractView */ class VacuumView extends AbstractView { - /** The domain of the entities that the view is representing. */ - static readonly domain = 'vacuum' as const; + /** + * Domain of the view's entities. + * + * @type {SupportedDomains} + * @static + * @private + */ + static #domain: SupportedDomains = "vacuum"; - /** Returns the default configuration object for the view. */ - static getDefaultConfig(): ViewConfig { - return { - title: localize('vacuum.vacuums'), - path: 'vacuums', - icon: 'mdi:robot-vacuum', - subview: false, - headerCardConfiguration: { - iconOn: 'mdi:robot-vacuum', - iconOff: 'mdi:robot-vacuum-off', - onService: 'vacuum.start', - offService: 'vacuum.stop', - }, - }; - } + /** + * Default configuration of the view. + * + * @type {views.ViewConfig} + * @private + */ + #defaultConfig: views.ViewConfig = { + title: Helper.customLocalize("vacuum.vacuums"), + path: "vacuums", + icon: "mdi:robot-vacuum", + subview: false, + controllerCardOptions: { + iconOn: "mdi:robot-vacuum", + iconOff: "mdi:robot-vacuum-off", + onService: "vacuum.start", + offService: "vacuum.stop", + }, + }; - /** Returns the default configuration of the view's Header card. */ - static getViewHeaderCardConfig(): CustomHeaderCardConfig { - return { - title: localize('vacuum.all_vacuums'), - subtitle: - `${Registry.getCountTemplate(VacuumView.domain, 'in', '[cleaning, returning]')} ${localize('vacuum.vacuums')} ` + - localize('generic.busy'), - }; - } + /** + * Default configuration of the view's Controller card. + * + * @type {cards.ControllerCardOptions} + * @private + */ + #viewControllerCardConfig: cards.ControllerCardOptions = { + title: Helper.customLocalize("vacuum.all_vacuums"), + subtitle: + `${Helper.getCountTemplate(VacuumView.#domain, "ne", "off")} ${Helper.customLocalize("vacuum.vacuums")} ` + + Helper.customLocalize("generic.busy"), + }; /** * Class constructor. * - * @param {ViewConfig} [customConfiguration] Custom view configuration. + * @param {views.ViewConfig} [options={}] Options for the view. */ - constructor(customConfiguration?: ViewConfig) { - super(); + constructor(options: views.ViewConfig = {}) { + super(VacuumView.#domain); - this.initializeViewConfig(VacuumView.getDefaultConfig(), customConfiguration, VacuumView.getViewHeaderCardConfig()); + this.config = Object.assign(this.config, this.#defaultConfig, options); + + // Create a Controller card to switch all entities of the domain. + this.viewControllerCard = new ControllerCard( + this.targetDomain(VacuumView.#domain), + { + ...this.#viewControllerCardConfig, + ...("controllerCardOptions" in this.config ? this.config.controllerCardOptions : {}) as cards.ControllerCardConfig, + }).createCard(); } } -export default VacuumView; +export {VacuumView};