mirror of
https://github.com/DigiLive/mushroom-strategy.git
synced 2025-08-09 14:24:26 +02:00
Refactors view creation to support sections
Updates view creation to leverage sections for improved layout control, especially on the home view. The home view now uses a grid layout via sections. This change makes it easier to create complex layouts and improves responsiveness. Previously, the card creation logic was embedded directly within each view, leading to code duplication and difficulty in customizing layouts. The changes introduce the concept of sections, which are distinct areas within a view that can contain multiple cards. The home view is updated to use sections for the persons, areas, and quick access cards. This makes it easier to create responsive layouts that adapt to different screen sizes.
This commit is contained in:
1
dist/mushroom-strategy.js
vendored
1
dist/mushroom-strategy.js
vendored
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@ import {
|
|||||||
ViewInfo,
|
ViewInfo,
|
||||||
} from './types/strategy/strategy-generics';
|
} from './types/strategy/strategy-generics';
|
||||||
import { sanitizeClassName } from './utilities/auxiliaries';
|
import { sanitizeClassName } from './utilities/auxiliaries';
|
||||||
import { logMessage, lvlError, lvlInfo } from './utilities/debug';
|
import { logMessage, lvlError } from './utilities/debug';
|
||||||
import RegistryFilter from './utilities/RegistryFilter';
|
import RegistryFilter from './utilities/RegistryFilter';
|
||||||
import { stackHorizontal } from './utilities/cardStacking';
|
import { stackHorizontal } from './utilities/cardStacking';
|
||||||
import { PersistentNotification } from './utilities/PersistentNotification';
|
import { PersistentNotification } from './utilities/PersistentNotification';
|
||||||
@@ -57,13 +57,8 @@ class MushroomStrategy extends HTMLTemplateElement {
|
|||||||
const moduleName = sanitizeClassName(`${viewName}View`);
|
const moduleName = sanitizeClassName(`${viewName}View`);
|
||||||
const View = (await import(`./views/${moduleName}`)).default;
|
const View = (await import(`./views/${moduleName}`)).default;
|
||||||
const currentView = new View(Registry.strategyOptions.views[viewName]);
|
const currentView = new View(Registry.strategyOptions.views[viewName]);
|
||||||
const viewConfiguration = await currentView.getView();
|
|
||||||
|
|
||||||
if (viewConfiguration.cards.length) {
|
return await currentView.getView();
|
||||||
return viewConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
logMessage(lvlInfo, `View ${viewName} has no entities available!`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logMessage(lvlError, `Error importing ${viewName} view!`, e);
|
logMessage(lvlError, `Error importing ${viewName} view!`, e);
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ export function stackHorizontal(
|
|||||||
defaultCount: number = 2,
|
defaultCount: number = 2,
|
||||||
columnCounts?: {
|
columnCounts?: {
|
||||||
[key: string]: number | undefined;
|
[key: string]: number | undefined;
|
||||||
},
|
}
|
||||||
): LovelaceCardConfig[] {
|
): LovelaceCardConfig[] {
|
||||||
if (cardConfigurations.length <= 1) {
|
if (cardConfigurations.length <= 1) {
|
||||||
return cardConfigurations;
|
return cardConfigurations;
|
||||||
|
@@ -11,6 +11,7 @@ import { sanitizeClassName } from '../utilities/auxiliaries';
|
|||||||
import { logMessage, lvlFatal } from '../utilities/debug';
|
import { logMessage, lvlFatal } from '../utilities/debug';
|
||||||
import RegistryFilter from '../utilities/RegistryFilter';
|
import RegistryFilter from '../utilities/RegistryFilter';
|
||||||
import { stackHorizontal } from '../utilities/cardStacking';
|
import { stackHorizontal } from '../utilities/cardStacking';
|
||||||
|
import { LovelaceSectionRawConfig } from '../types/homeassistant/data/lovelace/config/section';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract View Class.
|
* Abstract View Class.
|
||||||
@@ -34,10 +35,6 @@ abstract class AbstractView {
|
|||||||
type: '',
|
type: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
protected get domain(): SupportedDomains | 'home' {
|
|
||||||
return (this.constructor as unknown as ViewConstructor).domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor.
|
* Class constructor.
|
||||||
*
|
*
|
||||||
@@ -50,10 +47,40 @@ abstract class AbstractView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get domain(): SupportedDomains | 'home' {
|
||||||
|
return (this.constructor as unknown as ViewConstructor).domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols Methodd is dynamically called.
|
||||||
|
/**
|
||||||
|
* Get a view configuration.
|
||||||
|
*
|
||||||
|
* The configuration includes the card configurations which are created by createCardConfigurations().
|
||||||
|
*/
|
||||||
|
async getView(): Promise<LovelaceViewConfig | false> {
|
||||||
|
const sectionsCards = await this.createSections();
|
||||||
|
|
||||||
|
if (!sectionsCards.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.domain === 'home') {
|
||||||
|
return {
|
||||||
|
...this.baseConfiguration,
|
||||||
|
sections: sectionsCards,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...this.baseConfiguration,
|
||||||
|
cards: sectionsCards as LovelaceCardConfig[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the configuration of the cards to include in the view.
|
* Create the configuration of the cards to include in the view.
|
||||||
*/
|
*/
|
||||||
protected async createCardConfigurations(): Promise<LovelaceCardConfig[]> {
|
protected async createSections(): Promise<LovelaceSectionRawConfig[]> {
|
||||||
const viewCards: LovelaceCardConfig[] = [];
|
const viewCards: LovelaceCardConfig[] = [];
|
||||||
const moduleName = sanitizeClassName(this.domain + 'Card');
|
const moduleName = sanitizeClassName(this.domain + 'Card');
|
||||||
const DomainCard = (await import(`../cards/${moduleName}`)).default;
|
const DomainCard = (await import(`../cards/${moduleName}`)).default;
|
||||||
@@ -82,8 +109,8 @@ abstract class AbstractView {
|
|||||||
// Create a card configuration for each entity in the current area.
|
// Create a card configuration for each entity in the current area.
|
||||||
areaCards.push(
|
areaCards.push(
|
||||||
...areaEntities.map((entity) =>
|
...areaEntities.map((entity) =>
|
||||||
new DomainCard(entity, Registry.strategyOptions.card_options?.[entity.entity_id]).getCard(),
|
new DomainCard(entity, Registry.strategyOptions.card_options?.[entity.entity_id]).getCard()
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stack the cards of the current area.
|
// Stack the cards of the current area.
|
||||||
@@ -91,7 +118,7 @@ abstract class AbstractView {
|
|||||||
areaCards = stackHorizontal(
|
areaCards = stackHorizontal(
|
||||||
areaCards,
|
areaCards,
|
||||||
Registry.strategyOptions.domains[this.domain as SupportedDomains].stack_count ??
|
Registry.strategyOptions.domains[this.domain as SupportedDomains].stack_count ??
|
||||||
Registry.strategyOptions.domains['_'].stack_count,
|
Registry.strategyOptions.domains['_'].stack_count
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create and insert a Header card.
|
// Create and insert a Header card.
|
||||||
@@ -113,29 +140,6 @@ abstract class AbstractView {
|
|||||||
return viewCards;
|
return viewCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a view configuration.
|
|
||||||
*
|
|
||||||
* The configuration includes the card configurations which are created by createCardConfigurations().
|
|
||||||
*/
|
|
||||||
async getView(): Promise<LovelaceViewConfig> {
|
|
||||||
return {
|
|
||||||
...this.baseConfiguration,
|
|
||||||
cards: await this.createCardConfigurations(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Initialize the view configuration with defaults and custom settings.
|
||||||
*
|
*
|
||||||
@@ -146,7 +150,7 @@ abstract class AbstractView {
|
|||||||
protected initializeViewConfig(
|
protected initializeViewConfig(
|
||||||
viewConfiguration: ViewConfig,
|
viewConfiguration: ViewConfig,
|
||||||
customConfiguration: ViewConfig = {},
|
customConfiguration: ViewConfig = {},
|
||||||
headerCardConfig: CustomHeaderCardConfig,
|
headerCardConfig: CustomHeaderCardConfig
|
||||||
): void {
|
): void {
|
||||||
this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration };
|
this.baseConfiguration = { ...this.baseConfiguration, ...viewConfiguration, ...customConfiguration };
|
||||||
|
|
||||||
@@ -162,6 +166,17 @@ abstract class AbstractView {
|
|||||||
...headerCardConfig,
|
...headerCardConfig,
|
||||||
}).createCard();
|
}).createCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AbstractView;
|
export default AbstractView;
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
// noinspection JSUnusedGlobalSymbols Class is dynamically imported.
|
||||||
|
|
||||||
import { Registry } from '../Registry';
|
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 { AreaCardConfig, StackCardConfig } from '../types/homeassistant/panels/lovelace/cards/types';
|
||||||
import { PersonCardConfig } from '../types/lovelace-mushroom/cards/person-card-config';
|
import { PersonCardConfig } from '../types/lovelace-mushroom/cards/person-card-config';
|
||||||
import { TemplateCardConfig } from '../types/lovelace-mushroom/cards/template-card-config';
|
import { TemplateCardConfig } from '../types/lovelace-mushroom/cards/template-card-config';
|
||||||
@@ -13,9 +11,13 @@ import { logMessage, lvlError, lvlInfo } from '../utilities/debug';
|
|||||||
import { localize } from '../utilities/localize';
|
import { localize } from '../utilities/localize';
|
||||||
import AbstractView from './AbstractView';
|
import AbstractView from './AbstractView';
|
||||||
import registryFilter from '../utilities/RegistryFilter';
|
import registryFilter from '../utilities/RegistryFilter';
|
||||||
import { stackHorizontal } from '../utilities/cardStacking';
|
|
||||||
import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view';
|
import { LovelaceViewConfig } from '../types/homeassistant/data/lovelace/config/view';
|
||||||
import { LovelaceBadgeConfig } from '../types/homeassistant/data/lovelace/config/badge';
|
import { LovelaceBadgeConfig } from '../types/homeassistant/data/lovelace/config/badge';
|
||||||
|
import { LovelaceSectionRawConfig } from '../types/homeassistant/data/lovelace/config/section';
|
||||||
|
import { ActionConfig } from '../types/homeassistant/data/lovelace/config/action';
|
||||||
|
import HeaderCard from '../cards/HeaderCard';
|
||||||
|
import { stackHorizontal } from '../utilities/cardStacking';
|
||||||
|
import { LovelaceCardConfig } from '../types/homeassistant/data/lovelace/config/card';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Home View Class.
|
* Home View Class.
|
||||||
@@ -41,8 +43,8 @@ class HomeView extends AbstractView {
|
|||||||
// TODO: Move type and max_columns to the abstract class.
|
// TODO: Move type and max_columns to the abstract class.
|
||||||
static getDefaultConfig(): ViewConfig {
|
static getDefaultConfig(): ViewConfig {
|
||||||
return {
|
return {
|
||||||
//type: 'sections',
|
type: 'sections',
|
||||||
//max_columns: 4,
|
max_columns: 3,
|
||||||
header: {
|
header: {
|
||||||
badges_position: 'top',
|
badges_position: 'top',
|
||||||
layout: 'center',
|
layout: 'center',
|
||||||
@@ -60,38 +62,8 @@ class HomeView extends AbstractView {
|
|||||||
* The configuration includes the card configurations which are created by createCardConfigurations().
|
* The configuration includes the card configurations which are created by createCardConfigurations().
|
||||||
*/
|
*/
|
||||||
async getView(): Promise<LovelaceViewConfig> {
|
async getView(): Promise<LovelaceViewConfig> {
|
||||||
return {
|
if (this.baseConfiguration.header && !Registry.strategyOptions.home_view.hidden.includes('greeting')) {
|
||||||
...this.baseConfiguration,
|
this.baseConfiguration.header.card = {
|
||||||
badges: await this.createBadgeSection(),
|
|
||||||
cards: await this.createCardConfigurations(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the configuration of the cards to include in the view.
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
async createCardConfigurations(): Promise<LovelaceCardConfig[]> {
|
|
||||||
const homeViewCards: LovelaceCardConfig[] = [];
|
|
||||||
|
|
||||||
let personsSection, areasSection;
|
|
||||||
|
|
||||||
try {
|
|
||||||
[personsSection, areasSection] = await Promise.all([this.createPersonsSection(), this.createAreasSection()]);
|
|
||||||
} catch (e) {
|
|
||||||
logMessage(lvlError, 'Error importing created sections!', e);
|
|
||||||
|
|
||||||
return homeViewCards;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (personsSection) {
|
|
||||||
homeViewCards.push(personsSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the greeting section.
|
|
||||||
if (!Registry.strategyOptions.home_view.hidden.includes('greeting')) {
|
|
||||||
homeViewCards.push({
|
|
||||||
type: 'custom:mushroom-template-card',
|
type: 'custom:mushroom-template-card',
|
||||||
primary: `{% set time = now().hour %}
|
primary: `{% set time = now().hour %}
|
||||||
{% if (time >= 18) %}
|
{% if (time >= 18) %}
|
||||||
@@ -113,27 +85,76 @@ class HomeView extends AbstractView {
|
|||||||
hold_action: {
|
hold_action: {
|
||||||
action: 'none',
|
action: 'none',
|
||||||
} as ActionConfig,
|
} as ActionConfig,
|
||||||
} as TemplateCardConfig);
|
} as TemplateCardConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Registry.strategyOptions.quick_access_cards) {
|
return {
|
||||||
homeViewCards.push(...Registry.strategyOptions.quick_access_cards);
|
...this.baseConfiguration,
|
||||||
|
badges: await this.createBadgeSection(),
|
||||||
|
sections: await this.createSections(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the configuration of the cards to include in the view.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
async createSections(): Promise<LovelaceSectionRawConfig[]> {
|
||||||
|
const MEDIA_QUERY = {
|
||||||
|
SMALL: '(max-width: 1343px)',
|
||||||
|
LARGE: '(min-width: 1344px)',
|
||||||
|
};
|
||||||
|
const sections: LovelaceSectionRawConfig[] = [];
|
||||||
|
|
||||||
|
const addSection = (title: string, cards: LovelaceCardConfig[], mediaQuery?: string) => {
|
||||||
|
const section: LovelaceSectionRawConfig = {
|
||||||
|
type: 'grid',
|
||||||
|
/*title: title,*/ // TODO: Property is deprecated.
|
||||||
|
cards: cards,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mediaQuery) {
|
||||||
|
section.visibility = [
|
||||||
|
{
|
||||||
|
condition: 'screen',
|
||||||
|
media_query: mediaQuery,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.push(section);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [personCards, areaCards] = await Promise.all([this.createPersonCards(), this.createAreaCards()]);
|
||||||
|
|
||||||
|
const sectionConfigurations = [
|
||||||
|
['Persons', [personCards], MEDIA_QUERY.SMALL, !!personCards],
|
||||||
|
['Quick Access Wide', Registry.strategyOptions.quick_access_cards, MEDIA_QUERY.LARGE, true],
|
||||||
|
[
|
||||||
|
'Persons and Areas',
|
||||||
|
[personCards, areaCards].filter(Boolean),
|
||||||
|
MEDIA_QUERY.LARGE,
|
||||||
|
!!(personCards || areaCards),
|
||||||
|
],
|
||||||
|
['Quick Access Narrow', Registry.strategyOptions.quick_access_cards, MEDIA_QUERY.SMALL, true],
|
||||||
|
['Areas', [areaCards], MEDIA_QUERY.SMALL, !!areaCards],
|
||||||
|
['Extra', Registry.strategyOptions.extra_cards, undefined, true],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
sectionConfigurations.forEach(([title, cards, mediaQuery, condition]) => {
|
||||||
|
if (condition && cards.length) {
|
||||||
|
addSection(title, cards as LovelaceCardConfig[], mediaQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage(lvlInfo, `Section ${title} has no entities available.`);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logMessage(lvlError, 'Error importing section cards!', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (areasSection) {
|
return sections;
|
||||||
homeViewCards.push(areasSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Registry.strategyOptions.extra_cards) {
|
|
||||||
homeViewCards.push(...Registry.strategyOptions.extra_cards);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: 'vertical-stack',
|
|
||||||
cards: homeViewCards,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,10 +226,9 @@ class HomeView extends AbstractView {
|
|||||||
*
|
*
|
||||||
* If the section is marked as hidden in the strategy option, then the section is not created.
|
* If the section is marked as hidden in the strategy option, then the section is not created.
|
||||||
*/
|
*/
|
||||||
private async createPersonsSection(): Promise<StackCardConfig | undefined> {
|
private async createPersonCards(): Promise<StackCardConfig | undefined> {
|
||||||
if (Registry.strategyOptions.home_view.hidden.includes('persons')) {
|
if (Registry.strategyOptions.home_view.hidden.includes('persons')) {
|
||||||
// The section is hidden.
|
logMessage(lvlInfo, 'Persons section is hidden.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,8 +241,13 @@ class HomeView extends AbstractView {
|
|||||||
.map((person) => new PersonCard(person).getCard())
|
.map((person) => new PersonCard(person).getCard())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cardConfigurations.push(...cardConfigurations);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'vertical-stack',
|
type: 'vertical-stack',
|
||||||
|
grid_options: {
|
||||||
|
columns: 'full',
|
||||||
|
},
|
||||||
cards: stackHorizontal(
|
cards: stackHorizontal(
|
||||||
cardConfigurations,
|
cardConfigurations,
|
||||||
Registry.strategyOptions.home_view.stack_count['persons'] ?? Registry.strategyOptions.home_view.stack_count['_']
|
Registry.strategyOptions.home_view.stack_count['persons'] ?? Registry.strategyOptions.home_view.stack_count['_']
|
||||||
@@ -236,9 +261,9 @@ class HomeView extends AbstractView {
|
|||||||
* Area cards are grouped into two areas per row.
|
* 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.
|
* If the section is marked as hidden in the strategy option, then the section is not created.
|
||||||
*/
|
*/
|
||||||
private async createAreasSection(): Promise<StackCardConfig | undefined> {
|
private async createAreaCards(): Promise<StackCardConfig | undefined> {
|
||||||
if (Registry.strategyOptions.home_view.hidden.includes('areas')) {
|
if (Registry.strategyOptions.home_view.hidden.includes('areas')) {
|
||||||
// Areas section is hidden.
|
logMessage(lvlInfo, 'Areas section is hidden.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,9 +294,13 @@ class HomeView extends AbstractView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Registry.strategyOptions.home_view.hidden.includes('areasTitle')) {
|
||||||
|
cardConfigurations.unshift(new HeaderCard({}, { title: localize('generic.areas') }).createCard());
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'vertical-stack',
|
type: 'vertical-stack',
|
||||||
title: Registry.strategyOptions.home_view.hidden.includes('areasTitle') ? undefined : localize('generic.areas'),
|
columns: 'full',
|
||||||
cards: stackHorizontal(cardConfigurations, Registry.strategyOptions.home_view.stack_count['_'], {
|
cards: stackHorizontal(cardConfigurations, Registry.strategyOptions.home_view.stack_count['_'], {
|
||||||
'custom:mushroom-template-card': Registry.strategyOptions.home_view.stack_count.areas?.[0],
|
'custom:mushroom-template-card': Registry.strategyOptions.home_view.stack_count.areas?.[0],
|
||||||
area: Registry.strategyOptions.home_view.stack_count.areas?.[1],
|
area: Registry.strategyOptions.home_view.stack_count.areas?.[1],
|
||||||
|
Reference in New Issue
Block a user