Refactor code for compatibility

The code needed to be refactored to be compatible with the changes of
the `main` branch.
Also, the configuration for ESLint is refactored to migrate it to v9.
This commit is contained in:
DigiLive
2025-05-18 18:17:21 +02:00
parent 181e297330
commit 28d3e9d4bc
30 changed files with 419 additions and 316 deletions

View File

@@ -1,72 +0,0 @@
{
"root": true,
"env": {
"es2020": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"project": "./tsconfig.json",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"ignorePatterns": [
"dist/",
"node_modules/",
"*.js",
"src/types/homeassistant/",
"src/types/lovelace-mushroom/"
],
"overrides": [
{
"files": [
"webpack.config.ts",
"webpack.dev.config.ts"
],
"parserOptions": {
"project": null
}
}
],
"rules": {
"@typescript-eslint/no-empty-function": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"comma-dangle": [
"error",
"always-multiline"
],
"max-len": [
"warn",
{
"code": 120
}
],
"no-console": "off",
"no-empty-function": "off",
"no-unused-vars": "off",
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"semi": [
"error",
"always"
]
}
}

View File

@@ -58,7 +58,7 @@ body:
- type: input
id: affected-area-other
attributes:
label: Other Affected Area (if selected above)
label: The Other Affected Area (if selected above)
validations:
required: false

97
eslint.config.mjs Normal file
View File

@@ -0,0 +1,97 @@
import { defineConfig, globalIgnores } from 'eslint/config';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import globals from 'globals';
import tsParser from '@typescript-eslint/parser';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([
globalIgnores([
'**/dist/',
'**/node_modules/',
'**/*.js',
'src/types/homeassistant/',
'src/types/lovelace-mushroom/',
]),
{
extends: compat.extends(
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
),
plugins: {
'@typescript-eslint': typescriptEslint,
},
languageOptions: {
globals: {
...globals.node,
},
parser: tsParser,
ecmaVersion: 2020,
sourceType: 'module',
parserOptions: {
project: ['./tsconfig.json', './tsconfig.eslint.json'],
},
},
rules: {
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
},
],
'comma-dangle': ['error', 'always-multiline'],
'max-len': [
'warn',
{
code: 120,
},
],
'no-console': 'off',
'no-empty-function': 'off',
'no-unused-vars': 'off',
quotes: [
'error',
'single',
{
avoidEscape: true,
},
],
semi: ['error', 'always'],
},
},
{
files: ['**/webpack.config.ts', '**/webpack.dev.config.ts'],
languageOptions: {
ecmaVersion: 5,
sourceType: 'script',
parserOptions: {
project: null,
},
},
},
]);

24
package-lock.json generated
View File

@@ -6,17 +6,20 @@
"packages": {
"": {
"name": "mushroom-strategy",
"version": "2.3.2",
"version": "2.3.3-alpha.1",
"license": "MIT",
"dependencies": {
"deepmerge": "^4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.0",
"globals": "^16.1.0",
"home-assistant-js-websocket": "^9.5.0",
"prettier": "^3.5.3",
"superstruct": "^2.0.2",
@@ -190,6 +193,19 @@
"concat-map": "0.0.1"
}
},
"node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2104,9 +2120,9 @@
"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==",
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz",
"integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -30,11 +30,14 @@
"deepmerge": "^4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.0",
"globals": "^16.1.0",
"home-assistant-js-websocket": "^9.5.0",
"prettier": "^3.5.3",
"superstruct": "^2.0.2",

View File

@@ -13,7 +13,7 @@ import {
SupportedDomains,
SupportedViews,
} from './types/strategy/strategy-generics';
import { logMessage, lvlFatal, lvlOff, lvlWarn, setDebugLevel } from './utilities/debug';
import { logMessage, lvlFatal, lvlOff, setDebugLevel } from './utilities/debug';
import setupCustomLocalize from './utilities/localize';
import RegistryFilter from './utilities/RegistryFilter';
import { getObjectKeysByPropertyValue } from './utilities/auxiliaries';
@@ -26,30 +26,9 @@ import { isSortable } from './types/strategy/type-guards';
* Contains the entries of Home Assistant's registries and Strategy configuration.
*/
class Registry {
/** Entries of Home Assistant's entity registry. */
private static _entities: EntityRegistryEntry[];
/** Entries of Home Assistant's device registry. */
private static _devices: DeviceRegistryEntry[];
/** Entries of Home Assistant's area registry. */
private static _areas: StrategyArea[] = [];
/** Entries of Home Assistant's state registry */
private static _hassStates: HassEntities;
/** Entries of Home Assistant's config registry */
private static _configEntries: ConfigEntry[] = [];
/** The Custom strategy configuration. */
private static _strategyOptions: StrategyConfig;
/** Indicates whether this module is initialized. */
private static _initialized: boolean = false;
/** Indicates whether dark mode is enabled */
static darkMode: boolean;
/**
* Home Assistant's Config Entries.
*/
static get configEntries() {
return Registry._configEntries;
}
/**
* Class constructor.
*
@@ -61,16 +40,42 @@ class Registry {
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
private static _groupingDeviceIds: Set<string>;
/** Entries of Home Assistant's device registry. */
private static _devices: DeviceRegistryEntry[];
/** Entries of Home Assistant's state registry */
private static _hassStates: HassEntities;
/** Indicates whether this module is initialized. */
private static _initialized: boolean = false;
/** Get the initialization status of the Registry class. */
static get groupingDeviceIds() {
return Registry._groupingDeviceIds;
/**
* Home Assistant's Device registry.
*
* @remarks
* This module makes changes to the registry at {@link Registry.initialize}.
*/
static get devices() {
return Registry._devices;
}
/** The configuration of the strategy. */
static get strategyOptions() {
return Registry._strategyOptions;
/** Entries of Home Assistant's entity registry. */
private static _entities: EntityRegistryEntry[];
/**
* Home Assistant's Entity registry.
*
* @remarks
* This module makes changes to the registry at {@link Registry.initialize}.
*/
static get entities() {
return Registry._entities;
}
/** Entries of Home Assistant's area registry. */
private static _areas: StrategyArea[] = [];
/** Home Assistant's State registry. */
static get hassStates() {
return Registry._hassStates;
}
/**
@@ -83,29 +88,22 @@ class Registry {
return Registry._areas;
}
/**
* Home Assistant's Device registry.
*
* @remarks
* This module makes changes to the registry at {@link Registry.initialize}.
*/
static get devices() {
return Registry._devices;
}
/** Entries of Home Assistant's config registry */
private static _configEntries: ConfigEntry[] = [];
/**
* Home Assistant's Entity registry.
*
* @remarks
* This module makes changes to the registry at {@link Registry.initialize}.
* Home Assistant's Config Entries.
*/
static get entities() {
return Registry._entities;
static get configEntries() {
return Registry._configEntries;
}
/** Home Assistant's State registry. */
static get hassStates() {
return Registry._hassStates;
/** The Custom strategy configuration. */
private static _strategyOptions: StrategyConfig;
/** The configuration of the strategy. */
static get strategyOptions() {
return Registry._strategyOptions;
}
/** Get the initialization status of the Registry class. */
@@ -113,6 +111,13 @@ class Registry {
return Registry._initialized;
}
private static _groupingDeviceIds: Set<string>;
/** Get the initialization status of the Registry class. */
static get groupingDeviceIds() {
return Registry._groupingDeviceIds;
}
/**
* Initialize this module.
*

View File

@@ -36,7 +36,7 @@ abstract class AbstractCard {
*/
protected constructor(entity: RegistryEntry) {
if (!Registry.initialized) {
logMessage(lvlFatal, 'Registry not initialized!');
logMessage(lvlFatal, 'Registry is not initialized!');
}
this.entity = entity;

View File

@@ -10,6 +10,18 @@ import AbstractCard from './AbstractCard';
* Used to create a card configuration to control an entity of the fan domain.
*/
class FanCard extends AbstractCard {
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The HASS entity to create a card configuration for.
* @param {FanCardConfig} [customConfiguration] Custom card configuration.
*/
constructor(entity: EntityRegistryEntry, customConfiguration?: FanCardConfig) {
super(entity);
this.configuration = { ...this.configuration, ...FanCard.getDefaultConfig(), ...customConfiguration };
}
/** Returns the default configuration object for the card. */
static getDefaultConfig(): FanCardConfig {
return {
@@ -22,18 +34,6 @@ class FanCard extends AbstractCard {
show_percentage_control: true,
};
}
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The HASS entity to create a card configuration for.
* @param {FanCardConfig} [customConfiguration] Custom card configuration.
*/
constructor(entity: EntityRegistryEntry, customConfiguration?: FanCardConfig) {
super(entity);
this.configuration = { ...this.configuration, ...FanCard.getDefaultConfig(), ...customConfiguration };
}
}
export default FanCard;

View File

@@ -11,6 +11,24 @@ import { isCallServiceActionConfig } from '../types/strategy/type-guards';
* Used to create a card configuration to control an entity of the light domain.
*/
class LightCard extends AbstractCard {
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The HASS entity to create a card configuration for.
* @param {LightCardConfig} [customConfiguration] Custom card configuration.
*/
constructor(entity: EntityRegistryEntry, customConfiguration?: LightCardConfig) {
super(entity);
const configuration = LightCard.getDefaultConfig();
if (isCallServiceActionConfig(configuration.double_tap_action)) {
configuration.double_tap_action.target = { entity_id: entity.entity_id };
}
this.configuration = { ...this.configuration, ...configuration, ...customConfiguration };
}
/** Returns the default configuration object for the card. */
static getDefaultConfig(): LightCardConfig {
return {
@@ -32,24 +50,6 @@ class LightCard extends AbstractCard {
},
};
}
/**
* Class constructor.
*
* @param {EntityRegistryEntry} entity The HASS entity to create a card configuration for.
* @param {LightCardConfig} [customConfiguration] Custom card configuration.
*/
constructor(entity: EntityRegistryEntry, customConfiguration?: LightCardConfig) {
super(entity);
const configuration = LightCard.getDefaultConfig();
if (isCallServiceActionConfig(configuration.double_tap_action)) {
configuration.double_tap_action.target = { entity_id: entity.entity_id };
}
this.configuration = { ...this.configuration, ...configuration, ...customConfiguration };
}
}
export default LightCard;

View File

@@ -1,3 +1,5 @@
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
import { EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
import { EntityCardConfig } from '../types/lovelace-mushroom/cards/entity-card-config';
import AbstractCard from './AbstractCard';

View File

@@ -16,18 +16,6 @@ import { isCallServiceActionConfig } from '../types/strategy/type-guards';
* If the stateful scene entity is available, it will be used instead of the original scene entity.
*/
class SceneCard extends AbstractCard {
/** Returns the default configuration object for the card. */
static getDefaultConfig(): EntityCardConfig {
return {
type: 'custom:mushroom-entity-card',
tap_action: {
action: 'perform-action',
perform_action: 'scene.turn_on',
target: {},
},
};
}
/**
* Class constructor.
*
@@ -58,6 +46,18 @@ class SceneCard extends AbstractCard {
this.configuration = { ...this.configuration, ...configuration, ...customConfiguration };
}
/** Returns the default configuration object for the card. */
static getDefaultConfig(): EntityCardConfig {
return {
type: 'custom:mushroom-entity-card',
tap_action: {
action: 'perform-action',
perform_action: 'scene.turn_on',
target: {},
},
};
}
}
export default SceneCard;

View File

@@ -31,7 +31,7 @@ abstract class AbstractChip {
*/
protected constructor() {
if (!Registry.initialized) {
logMessage(lvlFatal, 'Registry not initialized!');
logMessage(lvlFatal, 'Registry is not initialized!');
}
}

View File

@@ -43,14 +43,19 @@ class AreaCardsGenerator extends DomainCardsGenerator {
const domainCardsPromises = [...this.domains]
.filter((domain) => isSupportedDomain(domain) && domain !== 'sensor')
.map((domain) => this.createSupportedDomainCards(domain));
const supportedDomainCards = filterNonNullValues(await Promise.all(domainCardsPromises));
const supportedDomainCards = await Promise.all(domainCardsPromises);
filterNonNullValues(supportedDomainCards);
if (sensorCards) {
const insertIndex = supportedDomainCards.findIndex((card) => card.strategy.domain > 'sensor');
supportedDomainCards.splice(insertIndex, 0, sensorCards);
}
return filterNonNullValues([deviceCards, ...supportedDomainCards, miscellaneousCards]);
const viewCards = [deviceCards, ...supportedDomainCards, miscellaneousCards];
filterNonNullValues(viewCards);
return viewCards;
} catch (e) {
logMessage(lvlError, 'Error creating area cards', e);
return [];

View File

@@ -34,18 +34,23 @@ class DeviceCardsGenerator extends DomainCardsGenerator {
.filter((domain) => isSupportedDomain(domain) && domain !== 'sensor')
.map((domain) => this.createSupportedDomainCards(domain));
const supportedDomainCards = filterNonNullValues(await Promise.all(domainCardsPromises));
const supportedDomainCards = await Promise.all(domainCardsPromises);
const [sensorCards, miscellaneousCards] = await Promise.all([
this.createSensorCards(),
this.createMiscellaneousCards(),
]);
filterNonNullValues(supportedDomainCards);
if (sensorCards) {
const insertIndex = supportedDomainCards.findIndex((card) => card.strategy.domain > 'sensor');
supportedDomainCards.splice(insertIndex, 0, sensorCards);
}
return filterNonNullValues([...supportedDomainCards, miscellaneousCards]);
const viewCards = [...supportedDomainCards, miscellaneousCards];
filterNonNullValues(viewCards);
return viewCards;
} catch (e) {
logMessage(lvlError, 'Error creating device cards', e);
return [];

View File

@@ -74,7 +74,7 @@ abstract class DomainCardsGenerator {
return deviceCard;
} catch (e) {
logMessage(lvlError, `Error creating card for device with id ${device.id}`, e);
logMessage(lvlError, `Error creating card for the device with id ${device.id}`, e);
return null;
}
@@ -88,7 +88,7 @@ abstract class DomainCardsGenerator {
showControls: false,
},
).createCard();
// TODO: add horizontal stacking
return {
type: 'vertical-stack',
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
@@ -113,7 +113,7 @@ abstract class DomainCardsGenerator {
return null;
}
const cards = await Promise.all(
let cards = await Promise.all(
entities.map(async (entity) => {
return this.createEntityCard(entity, 'SensorCard', {
...Registry.strategyOptions.card_options[entity.entity_id],
@@ -123,15 +123,26 @@ abstract class DomainCardsGenerator {
}),
);
filterNonNullValues(cards);
if (cards.length) {
const headerCard = new HeaderCard({}, Registry.strategyOptions.domains['sensor']).createCard();
cards = stackHorizontal(
cards,
Registry.strategyOptions.domains['sensor'].stack_count ?? Registry.strategyOptions.domains['_'].stack_count,
);
return {
type: 'vertical-stack',
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
cards: [headerCard, ...cards],
strategy: { domain: 'sensor' },
};
}
return null;
}
/**
* Creates Lovelace card configurations for miscellaneous entities.
*
@@ -147,11 +158,11 @@ abstract class DomainCardsGenerator {
.toList();
if (!entities.length) {
logMessage(lvlInfo, `No sensors available for view of ${this.parent.type} ${this.parent.id}.`);
logMessage(lvlInfo, `No entities available for view of ${this.parent.type} ${this.parent.id}.`);
return null;
}
const cards = await Promise.all(
let cards = await Promise.all(
entities.map(async (entity) => {
return this.createEntityCard(
entity,
@@ -161,15 +172,26 @@ abstract class DomainCardsGenerator {
}),
);
filterNonNullValues(cards);
if (cards.length) {
const headerCard = new HeaderCard({}, { title: Registry.strategyOptions.domains['default'].title }).createCard();
cards = stackHorizontal(
cards,
Registry.strategyOptions.domains['default'].stack_count ?? Registry.strategyOptions.domains['_'].stack_count,
);
return {
type: 'vertical-stack',
cards: [headerCard, ...cards.filter((card): card is LovelaceCardConfig => card !== null)],
cards: [headerCard, ...cards],
strategy: { domain: 'default' },
};
}
return null;
}
/**
* Creates Lovelace card configurations for entities within a supported domain.
*
@@ -191,7 +213,7 @@ abstract class DomainCardsGenerator {
return null;
}
let cards: (LovelaceCardConfig | null)[] = await Promise.all(
let cards = await Promise.all(
entities.map(async (entity) => {
targets.push(entity.entity_id);
@@ -203,15 +225,19 @@ abstract class DomainCardsGenerator {
}),
);
if (domainName === 'binary_sensor') {
cards = stackHorizontal(filterNonNullValues(cards));
}
filterNonNullValues(cards);
if (cards.length) {
const headerCard = new HeaderCard(
{ entity_id: targets },
Registry.strategyOptions.domains[domainName],
).createCard();
cards = stackHorizontal(
cards,
Registry.strategyOptions.domains[domainName].stack_count ?? Registry.strategyOptions.domains['_'].stack_count,
);
return {
type: 'vertical-stack',
cards: [headerCard, ...cards],
@@ -219,6 +245,9 @@ abstract class DomainCardsGenerator {
};
}
return null;
}
/**
* Creates a Lovelace card configuration for a specified entity.
*

View File

@@ -1,6 +1,5 @@
import { Registry } from './Registry';
import { LovelaceConfig } from './types/homeassistant/data/lovelace/config/types';
import { LovelaceViewConfig } from './types/homeassistant/data/lovelace/config/view';
import { DashboardInfo, isSupportedView } from './types/strategy/strategy-generics';
import { filterNonNullValues, sanitizeClassName } from './utilities/auxiliaries';
import { logMessage, lvlError, lvlFatal } from './utilities/debug';
@@ -54,7 +53,8 @@ class MushroomStrategy extends HTMLTemplateElement {
return null;
});
const views = filterNonNullValues(await Promise.all(viewPromises)) as LovelaceViewConfig[];
const views = await Promise.all(viewPromises);
filterNonNullValues(views);
// Device views.
const devices = new RegistryFilter(Registry.devices)

View File

@@ -285,19 +285,13 @@ export interface StrategyConfig {
extra_cards: LovelaceCardConfig[];
extra_views: StrategyViewConfig[];
home_view: {
hidden: HomeViewSections[] | [];
hidden: HomeViewSections[];
stack_count: { _: number } & { [K in HomeViewSections]?: K extends 'areas' ? [number, number] : number };
};
views: Record<SupportedViews, StrategyViewConfig>;
quick_access_cards: LovelaceCardConfig[];
}
/**
* Represents the default configuration for a strategy.
*/
export interface StrategyDefaults extends StrategyConfig {
areas: { undisclosed: StrategyArea } & { [S: string]: StrategyArea };
}
/**
* Base interface for sortable items.
*

View File

@@ -86,14 +86,14 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
}
if (areaId === undefined) {
return entry.area_id === undefined && deviceAreaId === undefined;
return entryObject.area_id === undefined && deviceAreaId === undefined;
}
if (entry.area_id === 'undisclosed' || !entry.area_id) {
if (entryObject.area_id === 'undisclosed' || !entryObject.area_id) {
return deviceAreaId === areaId;
}
return entry.area_id === areaId;
return entryObject.area_id === areaId;
};
this.filters.push(this.checkInversion(predicate));

View File

@@ -7,6 +7,7 @@
* @param {string} className Name of the class to sanitize.
*/
export function sanitizeClassName(className: string): string {
//TODO: In place sanitization.
return className.replace(/^([a-z])|([-_][a-z])/g, (match) => match.toUpperCase().replace(/[-_]/g, ''));
}
@@ -22,6 +23,7 @@ export function sanitizeClassName(className: string): string {
* @returns {T} A deep clone of the input value, or the original value if cloning fails.
*/
export function deepClone<T>(obj: T): T {
// TODO: In place clone.
if (typeof structuredClone === 'function') {
try {
return structuredClone(obj);
@@ -63,12 +65,15 @@ export function getObjectKeysByPropertyValue(
}
/**
* Filters out null values from an array.
* Filters out null values from the given array and asserts that the remaining values are non-null.
* The original array is modified in place to only contain non-null values.
*
* @template T The type of the array elements.
* @param {Array<T | null>} arr The array to filter.
* @returns {Array<T>} An array containing the non-null elements.
* @template T The type of non-null elements in the array
* @param {Array<(T | null)>} arr The array to be filtered.
* @return {asserts arr is Array<T>} The array containing non-null values of type T.
*/
export function filterNonNullValues<T>(arr: (T | null)[]): T[] {
return arr.filter((item): item is T => item !== null);
export function filterNonNullValues<T>(arr: (T | null)[]): asserts arr is T[] {
const filtered = arr.filter((item): item is T => item !== null);
arr.length = 0;
arr.push(...filtered);
}

View File

@@ -22,6 +22,7 @@ import { StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/ty
* ```
*/
export function stackHorizontal(
// TODO: In place stacking.
cardConfigurations: LovelaceCardConfig[],
defaultCount: number = 2,
columnCounts?: {

View File

@@ -107,7 +107,7 @@ export function setDebugLevel(level: DebugLevel) {
* @throws {Error} After logging, if the level is `lvlError` or `lvlFatal`.
*
* @remarks
* It might be required to throw an additional Error after logging with `lvlError ` or `lvlFatal` to satify the
* It might be required to throw an additional Error after logging with `lvlError ` or `lvlFatal` to satisfy the
* TypeScript compiler.
*/
export function logMessage(level: DebugLevel, message: string, ...details: unknown[]): void {

View File

@@ -16,6 +16,17 @@ class CameraView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain: SupportedDomains = 'camera' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(CameraView.getDefaultConfig(), customConfiguration, CameraView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[CameraView.domain] as SingleDomainConfig;
@@ -26,7 +37,8 @@ class CameraView extends AbstractView {
icon: 'mdi:cctv',
subview: false,
headerCardConfiguration: {
showControls: domainConfig.showControls, // FIXME: This should be named "show_controls". Also in other files and Wiki.
// FIXME: This should be named "show_controls". Also in other files and Wiki.
showControls: domainConfig.showControls,
on: domainConfig.on,
off: domainConfig.off,
},
@@ -42,17 +54,6 @@ class CameraView extends AbstractView {
localize('generic.busy'),
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(CameraView.getDefaultConfig(), customConfiguration, CameraView.getViewHeaderCardConfig());
}
}
export default CameraView;

View File

@@ -16,6 +16,21 @@ class ClimateView extends AbstractView {
/**The domain of the entities that the view is representing. */
static readonly domain: SupportedDomains = 'climate' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(
ClimateView.getDefaultConfig(),
customConfiguration,
ClimateView.getViewHeaderCardConfig(),
);
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[ClimateView.domain] as SingleDomainConfig;
@@ -42,21 +57,6 @@ class ClimateView extends AbstractView {
localize('generic.busy'),
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(
ClimateView.getDefaultConfig(),
customConfiguration,
ClimateView.getViewHeaderCardConfig(),
);
}
}
export default ClimateView;

View File

@@ -16,6 +16,17 @@ class CoverView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain: SupportedDomains = 'cover' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(CoverView.getDefaultConfig(), customConfiguration, CoverView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[CoverView.domain] as SingleDomainConfig;
@@ -43,17 +54,6 @@ class CoverView extends AbstractView {
`${localize('generic.unclosed')}`,
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(CoverView.getDefaultConfig(), customConfiguration, CoverView.getViewHeaderCardConfig());
}
}
export default CoverView;

View File

@@ -16,6 +16,17 @@ class FanView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain: SupportedDomains = 'fan' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(FanView.getDefaultConfig(), customConfiguration, FanView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[FanView.domain] as SingleDomainConfig;
@@ -41,17 +52,6 @@ class FanView extends AbstractView {
`${Registry.getCountTemplate(FanView.domain, 'eq', 'on')} ${localize('fan.fans')} ` + localize('generic.on'),
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(FanView.getDefaultConfig(), customConfiguration, FanView.getViewHeaderCardConfig());
}
}
export default FanView;

View File

@@ -8,7 +8,7 @@ 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 { 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';
@@ -26,16 +26,6 @@ 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,
};
}
/**
* Class constructor.
*
@@ -47,6 +37,16 @@ class HomeView extends AbstractView {
this.baseConfiguration = { ...this.baseConfiguration, ...HomeView.getDefaultConfig(), ...customConfiguration };
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
return {
title: localize('generic.home'),
icon: 'mdi:home-assistant',
path: 'home',
subview: false,
};
}
/**
* Create the configuration of the cards to include in the view.
*

View File

@@ -16,6 +16,17 @@ class LockView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain = 'lock' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(LockView.getDefaultConfig(), customConfiguration, LockView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[LockView.domain] as SingleDomainConfig;
@@ -42,17 +53,6 @@ class LockView extends AbstractView {
localize('lock.unlocked'),
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(LockView.getDefaultConfig(), customConfiguration, LockView.getViewHeaderCardConfig());
}
}
export default LockView;

View File

@@ -15,6 +15,17 @@ class SceneView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain = 'scene' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(SceneView.getDefaultConfig(), customConfiguration, SceneView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[SceneView.domain] as SingleDomainConfig;
@@ -36,17 +47,6 @@ class SceneView extends AbstractView {
static getViewHeaderCardConfig(): HeaderCardConfig {
return {};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(SceneView.getDefaultConfig(), customConfiguration, SceneView.getViewHeaderCardConfig());
}
}
export default SceneView;

View File

@@ -16,6 +16,17 @@ class SwitchView extends AbstractView {
/** The domain of the entities that the view is representing. */
static readonly domain = 'switch' as const;
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(SwitchView.getDefaultConfig(), customConfiguration, SwitchView.getViewHeaderCardConfig());
}
/** Returns the default configuration object for the view. */
static getDefaultConfig(): ViewConfig {
const domainConfig = Registry.strategyOptions.domains[SwitchView.domain] as SingleDomainConfig;
@@ -42,17 +53,6 @@ class SwitchView extends AbstractView {
localize('generic.on'),
};
}
/**
* Class constructor.
*
* @param {ViewConfig} [customConfiguration] Custom view configuration.
*/
constructor(customConfiguration?: ViewConfig) {
super();
this.initializeViewConfig(SwitchView.getDefaultConfig(), customConfiguration, SwitchView.getViewHeaderCardConfig());
}
}
export default SwitchView;

12
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,12 @@
{
"include": [
"eslint.config.mjs"
],
"compilerOptions": {
"allowJs": true,
"module": "ESNext",
"moduleResolution": "Node",
"target": "ESNext",
"noEmit": true
}
}