diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7010954..63be507 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -21,6 +21,7 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unsafe-declaration-merging': 'off', 'no-console': 'error', 'prettier/prettier': [ 'error', diff --git a/src/data.ts b/src/data.ts index ce78242..2d38067 100644 --- a/src/data.ts +++ b/src/data.ts @@ -3,76 +3,86 @@ import { Utils } from './utils'; export class MeteoalarmEventInfo { constructor( - public type: MeteoalarmEventType, - public fullName: string, - public icon: string + public type: MeteoalarmEventType, + public fullName: string, + public icon: string, ) {} get translationKey(): string { - return 'events.' + this.fullName.toLocaleLowerCase() - .replace(' ', '_') - .replace('/', '_') - .replace('-', '_'); + return ( + 'events.' + + this.fullName.toLocaleLowerCase().replace(' ', '_').replace('/', '_').replace('-', '_') + ); } } export class MeteoalarmLevelInfo { constructor( - public type: MeteoalarmLevelType, - public fullName: string, - public color: string + public type: MeteoalarmLevelType, + public fullName: string, + public color: string, ) {} get translationKey(): string { - return 'messages.' + this.fullName.toLocaleLowerCase() - .replace(' ', '_') - .replace('/', '_') - .replace('-', '_'); + return ( + 'messages.' + + this.fullName.toLocaleLowerCase().replace(' ', '_').replace('/', '_').replace('-', '_') + ); } } export class MeteoalarmData { static get events(): MeteoalarmEventInfo[] { // Use some new icons - if(!Utils.minHAversion(2022, 8)) { + if (!Utils.minHAversion(2022, 8)) { /* eslint-disable-next-line no-console */ - console.warn('MeteoalarmCard: You are using old HA version! Please update to at least 2022.08 for the best experience.'); + console.warn( + 'MeteoalarmCard: You are using old HA version! Please update to at least 2022.08 for the best experience.', + ); } const tsunami = Utils.minHAversion(2022, 6) ? 'tsunami' : 'waves'; const dust = Utils.minHAversion(2022, 8) ? 'weather-dust' : 'weather-windy'; // This list should be ordered from most to least dangerous return [ - new MeteoalarmEventInfo(MeteoalarmEventType.Nuclear, 'Nuclear Event', 'radioactive'), - new MeteoalarmEventInfo(MeteoalarmEventType.Hurricane, 'Hurricane', 'weather-hurricane'), - new MeteoalarmEventInfo(MeteoalarmEventType.Tornado, 'Tornado', 'weather-tornado'), - new MeteoalarmEventInfo(MeteoalarmEventType.CoastalEvent, 'Coastal Event', tsunami), - new MeteoalarmEventInfo(MeteoalarmEventType.Tsunami, 'Tsunami', tsunami), - new MeteoalarmEventInfo(MeteoalarmEventType.ForestFire, 'Forest Fire', 'pine-tree-fire'), - new MeteoalarmEventInfo(MeteoalarmEventType.Avalanches, 'Avalanches', 'image-filter-hdr'), - new MeteoalarmEventInfo(MeteoalarmEventType.Earthquake, 'Earthquake', 'image-broken-variant'), - new MeteoalarmEventInfo(MeteoalarmEventType.Volcano, 'Volcanic Activity','volcano-outline'), - new MeteoalarmEventInfo(MeteoalarmEventType.Flooding, 'Flooding', 'home-flood'), - new MeteoalarmEventInfo(MeteoalarmEventType.SeaEvent, 'Sea Event', 'ferry'), - new MeteoalarmEventInfo(MeteoalarmEventType.Thunderstorms, 'Thunderstorms', 'weather-lightning'), - new MeteoalarmEventInfo(MeteoalarmEventType.Rain, 'Rain', 'weather-pouring'), - new MeteoalarmEventInfo(MeteoalarmEventType.SnowIce, 'Snow/Ice', 'weather-snowy-heavy'), - new MeteoalarmEventInfo(MeteoalarmEventType.HighTemperature, 'High Temperature', 'thermometer'), - new MeteoalarmEventInfo(MeteoalarmEventType.LowTemperature, 'Low Temperature', 'snowflake'), - new MeteoalarmEventInfo(MeteoalarmEventType.Dust, 'Dust', dust), - new MeteoalarmEventInfo(MeteoalarmEventType.Wind, 'Wind', 'weather-windy'), - new MeteoalarmEventInfo(MeteoalarmEventType.Fog, 'Fog', 'weather-fog'), - new MeteoalarmEventInfo(MeteoalarmEventType.AirQuality, 'Air Quality', 'air-filter'), - new MeteoalarmEventInfo(MeteoalarmEventType.Unknown, 'Unknown Event', 'alert-circle-outline') + new MeteoalarmEventInfo(MeteoalarmEventType.Nuclear, 'Nuclear Event', 'radioactive'), + new MeteoalarmEventInfo(MeteoalarmEventType.Hurricane, 'Hurricane', 'weather-hurricane'), + new MeteoalarmEventInfo(MeteoalarmEventType.Tornado, 'Tornado', 'weather-tornado'), + new MeteoalarmEventInfo(MeteoalarmEventType.CoastalEvent, 'Coastal Event', tsunami), + new MeteoalarmEventInfo(MeteoalarmEventType.Tsunami, 'Tsunami', tsunami), + new MeteoalarmEventInfo(MeteoalarmEventType.ForestFire, 'Forest Fire', 'pine-tree-fire'), + new MeteoalarmEventInfo(MeteoalarmEventType.Avalanches, 'Avalanches', 'image-filter-hdr'), + new MeteoalarmEventInfo(MeteoalarmEventType.Earthquake, 'Earthquake', 'image-broken-variant'), + new MeteoalarmEventInfo(MeteoalarmEventType.Volcano, 'Volcanic Activity', 'volcano-outline'), + new MeteoalarmEventInfo(MeteoalarmEventType.Flooding, 'Flooding', 'home-flood'), + new MeteoalarmEventInfo(MeteoalarmEventType.SeaEvent, 'Sea Event', 'ferry'), + new MeteoalarmEventInfo( + MeteoalarmEventType.Thunderstorms, + 'Thunderstorms', + 'weather-lightning', + ), + new MeteoalarmEventInfo(MeteoalarmEventType.Rain, 'Rain', 'weather-pouring'), + new MeteoalarmEventInfo(MeteoalarmEventType.SnowIce, 'Snow/Ice', 'weather-snowy-heavy'), + new MeteoalarmEventInfo( + MeteoalarmEventType.HighTemperature, + 'High Temperature', + 'thermometer', + ), + new MeteoalarmEventInfo(MeteoalarmEventType.LowTemperature, 'Low Temperature', 'snowflake'), + new MeteoalarmEventInfo(MeteoalarmEventType.Dust, 'Dust', dust), + new MeteoalarmEventInfo(MeteoalarmEventType.Wind, 'Wind', 'weather-windy'), + new MeteoalarmEventInfo(MeteoalarmEventType.Fog, 'Fog', 'weather-fog'), + new MeteoalarmEventInfo(MeteoalarmEventType.AirQuality, 'Air Quality', 'air-filter'), + new MeteoalarmEventInfo(MeteoalarmEventType.Unknown, 'Unknown Event', 'alert-circle-outline'), ]; } static get levels(): MeteoalarmLevelInfo[] { return [ - new MeteoalarmLevelInfo(MeteoalarmLevelType.Red, 'Red', '#db4437'), + new MeteoalarmLevelInfo(MeteoalarmLevelType.Red, 'Red', '#db4437'), new MeteoalarmLevelInfo(MeteoalarmLevelType.Orange, 'Orange', '#EE5A24'), new MeteoalarmLevelInfo(MeteoalarmLevelType.Yellow, 'Yellow', '#ff9800'), - new MeteoalarmLevelInfo(MeteoalarmLevelType.None, 'None', 'inherit') + new MeteoalarmLevelInfo(MeteoalarmLevelType.None, 'None', 'inherit'), ]; } diff --git a/src/editor-warnings.ts b/src/editor-warnings.ts index 02a4b8e..9af55c7 100644 --- a/src/editor-warnings.ts +++ b/src/editor-warnings.ts @@ -5,27 +5,32 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType } from './types' export function generateEditorWarnings( integration: MeteoalarmIntegration | undefined, - entities: EntityConfig[] | undefined + entities: EntityConfig[] | undefined, ): TemplateResult { // If entities are undefined, default to empty array - if(!Array.isArray(entities)) entities = []; - if(!integration) return html``; + if (!Array.isArray(entities)) entities = []; + if (!integration) return html``; return html` - ${duplicateWarning(entities)} - ${missingExpectedEntityWarning(integration, entities)} + ${duplicateWarning(entities)} ${missingExpectedEntityWarning(integration, entities)} ${tooManyEntitiesWarning(integration, entities)} - `; + `; } /** * Generate warning for CurrentExpected integrations when second entity is not provided */ -function missingExpectedEntityWarning(integration: MeteoalarmIntegration, entities: EntityConfig[]): TemplateResult { +function missingExpectedEntityWarning( + integration: MeteoalarmIntegration, + entities: EntityConfig[], +): TemplateResult { const validEntity = integration?.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected; - if(validEntity && entities.length == 1) { + if (validEntity && entities.length == 1) { return html` - + ${localize('editor.error.expected_entity')} `; @@ -37,14 +42,17 @@ function missingExpectedEntityWarning(integration: MeteoalarmIntegration, entiti * Generate warning when user provides two identical entities in editor */ function duplicateWarning(entities: EntityConfig[]): TemplateResult { - const uniqueEntities = Array.from(new Set(entities.map(x => x.entity))); - const hasDuplicateEntities = uniqueEntities.length != entities.length; - if(hasDuplicateEntities) { + const uniqueEntities = Array.from(new Set(entities.map((x) => x.entity))); + const hasDuplicateEntities = uniqueEntities.length != entities.length; + if (hasDuplicateEntities) { return html` - + ${localize('editor.error.duplicate')} - `; + `; } return html``; } @@ -52,17 +60,22 @@ function duplicateWarning(entities: EntityConfig[]): TemplateResult { /** * Generate warning when there are too much entities provided */ -function tooManyEntitiesWarning(integration: MeteoalarmIntegration, entities: EntityConfig[]): TemplateResult { +function tooManyEntitiesWarning( + integration: MeteoalarmIntegration, + entities: EntityConfig[], +): TemplateResult { const shouldConsider = integration.metadata.entitiesCount > 0; - if(shouldConsider && entities.length > integration.metadata.entitiesCount) { + if (shouldConsider && entities.length > integration.metadata.entitiesCount) { return html` - - ${localize('editor.error.too_many_entities') - .replace('{expected}', String(integration.metadata.entitiesCount)) - .replace('{got}', String(entities.length))} + + ${localize('editor.error.too_many_entities') + .replace('{expected}', String(integration.metadata.entitiesCount)) + .replace('{got}', String(entities.length))} - `; + `; } return html``; } - diff --git a/src/editor.ts b/src/editor.ts index ba2b77d..6e1fbe9 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -11,302 +11,333 @@ import { generateEditorWarnings } from './editor-warnings'; import { processEditorEntities } from './helpers/process-editor-entities'; import { localize } from './localize/localize'; import { MeteoalarmCard } from './meteoalarm-card'; -import { MeteoalarmCardConfig, MeteoalarmIntegrationEntityType, MeteoalarmScalingMode } from './types'; +import { + MeteoalarmCardConfig, + MeteoalarmIntegrationEntityType, + MeteoalarmScalingMode, +} from './types'; @customElement('meteoalarm-card-editor') -export class MeteoalarmCardCardEditor extends ScopedRegistryHost(LitElement) implements LovelaceCardEditor { - @property({ attribute: false }) public hass?: HomeAssistant; - @state() private _config?: MeteoalarmCardConfig; - @state() private _helpers?: any; - private _initialized = false; - @state() private _configEntities?: EntityConfig[]; - - static elementDefinitions = { - ...textfieldDefinition, - ...selectDefinition, - ...switchDefinition, - ...formfieldDefinition - }; - - public setConfig(config: MeteoalarmCardConfig): void { - this._config = config; - this._configEntities = processEditorEntities(config.entities!); - } - - protected firstUpdated(): void { - this.loadLovelaceElements(); - } - - protected shouldUpdate(): boolean { - if (!this._initialized) { - this._initialize(); - } - return true; - } - - get _integration(): string { - return this._config?.integration || ''; - } - - get _override_headline(): boolean { - return this._config?.override_headline || false; - } - - get _hide_when_no_warning(): boolean { - return this._config?.hide_when_no_warning || false; - } - - get _hide_caption(): boolean { - return this._config?.hide_caption || false; - } - - get _disable_swiper(): boolean { - return this._config?.disable_swiper || false; - } - - get _scaling_mode(): string { - return this._config?.scaling_mode || 'headline_and_scale'; - } - - protected render(): TemplateResult | void { - if (!this.hass) { - return html``; - } - - const integration = MeteoalarmCard.integrations.find(i => i.metadata.key === this._integration); - - return html` - - ${generateEditorWarnings(integration, this._configEntities)} - - - ev.stopPropagation()} - > - ${MeteoalarmCard.integrations.map((integration) => { - return html`${integration.metadata.name}`; - })} - - - - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.SingleEntity ? html` - 0 ? this._configEntities![0].entity : ''} - @value-changed=${this._valueChanged} - > - ` : html` -

${localize('editor.entity')} (${localize('editor.required')})

-

- ${localize('editor.description.start')} - ${' '} - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected ? html` +export class MeteoalarmCardCardEditor + extends ScopedRegistryHost(LitElement) + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + @state() private _config?: MeteoalarmCardConfig; + @state() private _helpers?: any; + private _initialized = false; + @state() private _configEntities?: EntityConfig[]; + + static elementDefinitions = { + ...textfieldDefinition, + ...selectDefinition, + ...switchDefinition, + ...formfieldDefinition, + }; + + public setConfig(config: MeteoalarmCardConfig): void { + this._config = config; + this._configEntities = processEditorEntities(config.entities!); + } + + protected firstUpdated(): void { + this.loadLovelaceElements(); + } + + protected shouldUpdate(): boolean { + if (!this._initialized) { + this._initialize(); + } + return true; + } + + get _integration(): string { + return this._config?.integration || ''; + } + + get _override_headline(): boolean { + return this._config?.override_headline || false; + } + + get _hide_when_no_warning(): boolean { + return this._config?.hide_when_no_warning || false; + } + + get _hide_caption(): boolean { + return this._config?.hide_caption || false; + } + + get _disable_swiper(): boolean { + return this._config?.disable_swiper || false; + } + + get _scaling_mode(): string { + return this._config?.scaling_mode || 'headline_and_scale'; + } + + protected render(): TemplateResult | void { + if (!this.hass) { + return html``; + } + + const integration = MeteoalarmCard.integrations.find( + (i) => i.metadata.key === this._integration, + ); + + return html` + + ${generateEditorWarnings(integration, this._configEntities)} + + + ev.stopPropagation()} + > + ${MeteoalarmCard.integrations.map((integration) => { + return html`${integration.metadata.name}`; + })} + + + + ${integration?.metadata.type == MeteoalarmIntegrationEntityType.SingleEntity + ? html` + 0 + ? this._configEntities![0].entity + : ''} + @value-changed=${this._valueChanged} + > + ` + : html` +

${localize('editor.entity')} (${localize('editor.required')})

+

+ ${localize('editor.description.start')} ${' '} + ${integration?.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected + ? html` ${localize('editor.description.current_expected')}

- ` : ''} - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.Slots ? html` + ` + : ''} + ${integration?.metadata.type == MeteoalarmIntegrationEntityType.Slots + ? html` ${localize('editor.description.slots')}

- ` : ''} - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.WarningWatchStatementAdvisory ? html` + ` + : ''} + ${integration?.metadata.type == + MeteoalarmIntegrationEntityType.WarningWatchStatementAdvisory + ? html` ${localize('editor.description.warning_watch_statement_advisory')}

- ` : ''} - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.SeparateEvents ? html` + ` + : ''} + ${integration?.metadata.type == MeteoalarmIntegrationEntityType.SeparateEvents + ? html` ${localize('editor.description.separate_events')}

- ` : ''} - ${' '} - ${localize('editor.description.end')} -

- - - `} + ` + : ''} + ${' '} ${localize('editor.description.end')} +

+ + + `}
- - ${integration?.metadata.returnMultipleAlerts? html` - - - - `: ''} - - - ${integration?.metadata.returnHeadline ? html` - - + + ${integration?.metadata.returnMultipleAlerts + ? html` + + + + ` + : ''} + + + ${integration?.metadata.returnHeadline + ? html` + + + + ` + : ''} + + + ${integration?.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected + ? html` + + + + ` + : ''} + + + + - `: ''} - - - ${integration?.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected? html` - - - - `: ''} - - - - -
- +
-
- ev.stopPropagation()} - > - ${Object.values(MeteoalarmScalingMode).map((mode) => { - return html` - +
+ ev.stopPropagation()} + > + ${Object.values(MeteoalarmScalingMode).map((mode) => { + return html` ${localize(`editor.scaling_mode_options.${mode}`)} `; - })} - - - Scaling mode documentation - + })} + + + Scaling mode documentation + +
-
- `; - } - - private _initialize(): void { - if (this.hass === undefined) return; - if (this._config === undefined) return; - if (this._helpers === undefined) return; - this._initialized = true; - } - - private async loadLovelaceElements(): Promise { - // This function load card helpers and pre-loads all pre-made - // custom elements from Lovelace like ha-entity-picker - // Read more on why and code explanation: - // Pre-loading elements: - // https://github.com/thomasloven/hass-config/wiki/PreLoading-Lovelace-Elements - // Pre-loading elements in ScopedRegistryHost: - // https://gist.github.com/thomasloven/5f965bd26e5f69876890886c09dd9ba8 - - const registry = (this.shadowRoot as any)?.customElements; - if (!registry) return; - if (registry.get('ha-entity-picker')) return; - - this._helpers = await (window as any).loadCardHelpers(); - const entitiesCard = await this._helpers.createCardElement({ type: 'entities', entities: [] }); - await entitiesCard.constructor.getConfigElement(); - const glanceCard = await this._helpers.createCardElement({ type: 'glance', entities: [] }); - await glanceCard.constructor.getConfigElement(); - - registry.define('ha-entity-picker', window.customElements.get('ha-entity-picker')); - registry.define('hui-entity-editor', window.customElements.get('hui-entity-editor')); - registry.define('ha-alert', window.customElements.get('ha-alert')); - } - - private _valueChanged(ev): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target; - if (this[`_${target.configValue}`] === target.value) { - return; - } - if (target.configValue) { - if (target.value === '') { - const tmpConfig = { ...this._config }; - delete tmpConfig[target.configValue]; - this._config = tmpConfig; - } - else { - // Set value to config - this._config = { - ...this._config, - [target.configValue]: target.checked !== undefined ? target.checked : target.value - }; - - // Convert entity format - this._config = { - ...this._config, - entities: processEditorEntities(this._config.entities) - }; - - const integration = MeteoalarmCard.integrations.find(i => i.metadata.key === this._integration); - if(integration?.metadata.type == MeteoalarmIntegrationEntityType.SingleEntity) { - const entities = this._config.entities as EntityConfig[]; - this._config.entities = [entities[0]]; - } - } - } - fireEvent(this, 'config-changed', { config: this._config }); - } - - private _entitiesChanged(ev: CustomEvent): void { - // Change in entities from multiple entities selector - let config = this._config!; - config = { ...config, entities: ev.detail.entities! }; - - this._configEntities = processEditorEntities(this._config!.entities!); - fireEvent(this, 'config-changed', { config }); - } - - static styles: CSSResultGroup = css` - mwc-select, - mwc-textfield, - ha-entity-picker, - hui-entity-editor, - ha-alert { - margin-bottom: 16px; - display: block; + `; } - mwc-formfield { - padding-bottom: 8px; + + private _initialize(): void { + if (this.hass === undefined) return; + if (this._config === undefined) return; + if (this._helpers === undefined) return; + this._initialized = true; } - mwc-switch { - --mdc-theme-secondary: var(--switch-checked-color); - --mdc-theme-surface: #999999; + + private async loadLovelaceElements(): Promise { + // This function load card helpers and pre-loads all pre-made + // custom elements from Lovelace like ha-entity-picker + // Read more on why and code explanation: + // Pre-loading elements: + // https://github.com/thomasloven/hass-config/wiki/PreLoading-Lovelace-Elements + // Pre-loading elements in ScopedRegistryHost: + // https://gist.github.com/thomasloven/5f965bd26e5f69876890886c09dd9ba8 + + const registry = (this.shadowRoot as any)?.customElements; + if (!registry) return; + if (registry.get('ha-entity-picker')) return; + + this._helpers = await (window as any).loadCardHelpers(); + const entitiesCard = await this._helpers.createCardElement({ type: 'entities', entities: [] }); + await entitiesCard.constructor.getConfigElement(); + const glanceCard = await this._helpers.createCardElement({ type: 'glance', entities: [] }); + await glanceCard.constructor.getConfigElement(); + + registry.define('ha-entity-picker', window.customElements.get('ha-entity-picker')); + registry.define('hui-entity-editor', window.customElements.get('hui-entity-editor')); + registry.define('ha-alert', window.customElements.get('ha-alert')); } - .options { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 18px; - margin: 24px 0; + + private _valueChanged(ev): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === '') { + const tmpConfig = { ...this._config }; + delete tmpConfig[target.configValue]; + this._config = tmpConfig; + } else { + // Set value to config + this._config = { + ...this._config, + [target.configValue]: target.checked !== undefined ? target.checked : target.value, + }; + + // Convert entity format + this._config = { + ...this._config, + entities: processEditorEntities(this._config.entities), + }; + + const integration = MeteoalarmCard.integrations.find( + (i) => i.metadata.key === this._integration, + ); + if (integration?.metadata.type == MeteoalarmIntegrationEntityType.SingleEntity) { + const entities = this._config.entities as EntityConfig[]; + this._config.entities = [entities[0]]; + } + } + } + fireEvent(this, 'config-changed', { config: this._config }); } - p { - max-width: 600px; + + private _entitiesChanged(ev: CustomEvent): void { + // Change in entities from multiple entities selector + let config = this._config!; + config = { ...config, entities: ev.detail.entities! }; + + this._configEntities = processEditorEntities(this._config!.entities!); + fireEvent(this, 'config-changed', { config }); } - `; + + static styles: CSSResultGroup = css` + mwc-select, + mwc-textfield, + ha-entity-picker, + hui-entity-editor, + ha-alert { + margin-bottom: 16px; + display: block; + } + mwc-formfield { + padding-bottom: 8px; + } + mwc-switch { + --mdc-theme-secondary: var(--switch-checked-color); + --mdc-theme-surface: #999999; + } + .options { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 18px; + margin: 24px 0; + } + p { + max-width: 600px; + } + `; } diff --git a/src/events-praser.ts b/src/events-praser.ts index 4862411..d53cf50 100644 --- a/src/events-praser.ts +++ b/src/events-praser.ts @@ -8,32 +8,30 @@ import { MeteoalarmAlertParsed, MeteoalarmEventType, MeteoalarmIntegration, - MeteoalarmIntegrationEntityType + MeteoalarmIntegrationEntityType, } from './types'; /** * This is the class that stands between integration and rendering code. */ class EventsParser { - constructor( - private integration: MeteoalarmIntegration - ) {} + constructor(private integration: MeteoalarmIntegration) {} /** - * This function parses all of the provided entities and provides their attributes - * to integration specified in constructors. The result is additionally processed and - * verified then, packed to array of MeteoalarmAlertParsed objects - */ + * This function parses all of the provided entities and provides their attributes + * to integration specified in constructors. The result is additionally processed and + * verified then, packed to array of MeteoalarmAlertParsed objects + */ public getEvents( entities: HassEntity[], disableSweeper = false, overrideHeadline = false, hideCaption = false, ignoredLevels: string[] = [], - ignoredEvents: string[] = [] + ignoredEvents: string[] = [], ): MeteoalarmAlertParsed[] { - if(this.isAnyEntityUnavailable(entities)) { - return [ PredefinedCards.unavailableCard() ]; + if (this.isAnyEntityUnavailable(entities)) { + return [PredefinedCards.unavailableCard()]; } this.checkIfIntegrationSupportsEntities(entities); @@ -42,21 +40,21 @@ class EventsParser { alerts = this.filterAlerts(alerts, ignoredLevels, ignoredEvents); const result: MeteoalarmAlertParsed[] = []; - for(const alert of alerts) { + for (const alert of alerts) { const event = MeteoalarmData.getEvent(alert.event); const level = MeteoalarmData.getLevel(alert.level); const headlines = this.generateHeadlines(event, level); // If there is provided headline, and user wants it, push it to headlines - if(!overrideHeadline && alert.headline) { + if (!overrideHeadline && alert.headline) { headlines.unshift(alert.headline); } let caption: string | undefined = undefined; let captionIcon: string | undefined = undefined; - if(!hideCaption) { - if(alert.kind == MeteoalarmAlertKind.Expected) { + if (!hideCaption) { + if (alert.kind == MeteoalarmAlertKind.Expected) { caption = localize('common.expected'); captionIcon = 'clock-outline'; } @@ -69,37 +67,37 @@ class EventsParser { color: level.color, headlines: headlines, caption: caption, - captionIcon: captionIcon + captionIcon: captionIcon, }); } // If there are no results that mean above loop didn't trigger // event parsing even once since every sensor was inactive. - if(result.length == 0) { - return [ PredefinedCards.noWarningsCard(entities[0]) ]; + if (result.length == 0) { + return [PredefinedCards.noWarningsCard(entities[0])]; } return disableSweeper ? result.slice(1) : result; } /** - * Call integration for each of the entities and put all alerts in array - */ + * Call integration for each of the entities and put all alerts in array + */ private graterAllAlerts(entities: HassEntity[]): MeteoalarmAlert[] { const alerts: MeteoalarmAlert[] = []; - for(const entity of entities) { + for (const entity of entities) { const active = this.integration.alertActive(entity); - if(!active) continue; + if (!active) continue; let entityAlerts = this.integration.getAlerts(entity); - if(!Array.isArray(entityAlerts)) { - entityAlerts = [ entityAlerts ]; + if (!Array.isArray(entityAlerts)) { + entityAlerts = [entityAlerts]; } - if(entityAlerts.length == 0) { + if (entityAlerts.length == 0) { throw new Error('Integration is active but did not return any events'); } - for(const alert of entityAlerts) { - alerts.push({...alert, _entity: entity}); + for (const alert of entityAlerts) { + alerts.push({ ...alert, _entity: entity }); } } return alerts; @@ -108,15 +106,18 @@ class EventsParser { private filterAlerts( alerts: MeteoalarmAlert[], ignoredLevels: string[], - ignoredEvents: string[] + ignoredEvents: string[], ): MeteoalarmAlert[] { - if(ignoredEvents.length == 0 && ignoredLevels.length == 0) return alerts; + if (ignoredEvents.length == 0 && ignoredLevels.length == 0) return alerts; const result: MeteoalarmAlert[] = []; - for(const alert of alerts) { - const eventInfo = MeteoalarmData.events.find(e => e.type == alert.event)!; - const levelInfo = MeteoalarmData.levels.find(e => e.type == alert.level)!; - if(!ignoredEvents.includes(eventInfo.fullName) && !ignoredLevels.includes(levelInfo.fullName)) { + for (const alert of alerts) { + const eventInfo = MeteoalarmData.events.find((e) => e.type == alert.event)!; + const levelInfo = MeteoalarmData.levels.find((e) => e.type == alert.level)!; + if ( + !ignoredEvents.includes(eventInfo.fullName) && + !ignoredLevels.includes(levelInfo.fullName) + ) { result.push(alert); } } @@ -129,96 +130,103 @@ class EventsParser { // Sort by how dangerous events are alerts = alerts.sort((a, b) => { const eventsData = MeteoalarmData.events; - const aLevel = eventsData.indexOf(eventsData.find(e => e.type == a.event)!); - const bLevel = eventsData.indexOf(eventsData.find(e => e.type == b.event)!); - return(bLevel - aLevel); + const aLevel = eventsData.indexOf(eventsData.find((e) => e.type == a.event)!); + const bLevel = eventsData.indexOf(eventsData.find((e) => e.type == b.event)!); + return bLevel - aLevel; }); // Sort by level - alerts = alerts.sort((a, b) => (b.level - a.level)); + alerts = alerts.sort((a, b) => b.level - a.level); // Push expected events to back of the list alerts = alerts.sort((a, b) => { - if(a.kind === undefined) return 0; - if(a.kind == MeteoalarmAlertKind.Current && b.kind == MeteoalarmAlertKind.Expected) return -1; - else if(a.kind == MeteoalarmAlertKind.Expected && b.kind == MeteoalarmAlertKind.Current) return 1; + if (a.kind === undefined) return 0; + if (a.kind == MeteoalarmAlertKind.Current && b.kind == MeteoalarmAlertKind.Expected) + return -1; + else if (a.kind == MeteoalarmAlertKind.Expected && b.kind == MeteoalarmAlertKind.Current) + return 1; return 0; }); return alerts; } /** - * Validate if specified alert is up to standards - */ + * Validate if specified alert is up to standards + */ private validateAlert(alerts: MeteoalarmAlert[]): void { - for(const alert of alerts) { - if(alert.event === undefined || alert.level === undefined) { + for (const alert of alerts) { + if (alert.event === undefined || alert.level === undefined) { throw new Error( - `[Alert QA Error] Invalid event object received: event: ${alert.event} level: ${alert.level}`); + `[Alert QA Error] Invalid event object received: event: ${alert.event} level: ${alert.level}`, + ); } - if(!this.integration.metadata.returnHeadline && alert.headline) { + if (!this.integration.metadata.returnHeadline && alert.headline) { throw new Error( - '[Alert QA Error] metadata.returnHeadline is false but headline was returned'); + '[Alert QA Error] metadata.returnHeadline is false but headline was returned', + ); } - if( - (this.integration.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected) - && alert.kind == undefined + if ( + this.integration.metadata.type == MeteoalarmIntegrationEntityType.CurrentExpected && + alert.kind == undefined ) { - throw new Error( - '[Alert QA Error] CurrentExpected type is required to provide alert.kind'); + throw new Error('[Alert QA Error] CurrentExpected type is required to provide alert.kind'); } - if( - (this.integration.metadata.type != MeteoalarmIntegrationEntityType.CurrentExpected) - && alert.kind != undefined + if ( + this.integration.metadata.type != MeteoalarmIntegrationEntityType.CurrentExpected && + alert.kind != undefined ) { - throw new Error( - '[Alert QA Error] only CurrentExpected type can return alert.kind'); + throw new Error('[Alert QA Error] only CurrentExpected type can return alert.kind'); } - if(!this.integration.metadata.returnMultipleAlerts && alerts.length > 1) { + if (!this.integration.metadata.returnMultipleAlerts && alerts.length > 1) { throw new Error( - '[Alert QA Error] returnMultipleAlerts is false but more than one alert was returned'); + '[Alert QA Error] returnMultipleAlerts is false but more than one alert was returned', + ); } } } // Artificially generate headlines from event type and level private generateHeadlines(event: MeteoalarmEventInfo, level: MeteoalarmLevelInfo): string[] { - if(event.type === MeteoalarmEventType.Unknown) { + if (event.type === MeteoalarmEventType.Unknown) { return [ localize(level.translationKey + '.generic'), - localize(level.translationKey + '.color') + localize(level.translationKey + '.color'), ]; - } - else { + } else { const e = localize(event.translationKey); return [ - localize(level.translationKey + '.event').replace('{event}', localize(event.translationKey)), - e.charAt(0).toUpperCase() + e.slice(1) + localize(level.translationKey + '.event').replace( + '{event}', + localize(event.translationKey), + ), + e.charAt(0).toUpperCase() + e.slice(1), ]; } } private isAnyEntityUnavailable(entities: HassEntity[]): boolean { - return entities.some(e => { - return e == undefined || (e.attributes.status || e.attributes.state || e.state) === 'unavailable'; + return entities.some((e) => { + return ( + e == undefined || (e.attributes.status || e.attributes.state || e.state) === 'unavailable' + ); }); } private checkIfIntegrationSupportsEntities(entities: HassEntity[]): void { - if(!entities.every(e => this.integration.supports(e))) { - if(entities.length == 1) { + if (!entities.every((e) => this.integration.supports(e))) { + if (entities.length == 1) { throw new Error(localize('error.entity_invalid.single')); - } - else { - const unsupportedEntities = entities.filter(e => !this.integration.supports(e)); + } else { + const unsupportedEntities = entities.filter((e) => !this.integration.supports(e)); throw new Error( - localize('error.entity_invalid.multiple') - .replace('{entity}', unsupportedEntities.map(x => x.entity_id).join(', ')) + localize('error.entity_invalid.multiple').replace( + '{entity}', + unsupportedEntities.map((x) => x.entity_id).join(', '), + ), ); } } } - } export default EventsParser; diff --git a/src/external/swiperStyles.ts b/src/external/swiperStyles.ts index 24b8d72..a5859fb 100644 --- a/src/external/swiperStyles.ts +++ b/src/external/swiperStyles.ts @@ -12,5 +12,616 @@ import { css } from 'lit'; export default css` -@font-face{font-family:swiper-icons;src:url('data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA');font-weight:400;font-style:normal}:root{--swiper-theme-color:#007aff}.swiper{margin-left:auto;margin-right:auto;position:relative;overflow:hidden;list-style:none;padding:0;z-index:1}.swiper-vertical>.swiper-wrapper{flex-direction:column}.swiper-wrapper{position:relative;width:100%;height:100%;z-index:1;display:flex;transition-property:transform;box-sizing:content-box}.swiper-android .swiper-slide,.swiper-wrapper{transform:translate3d(0px,0,0)}.swiper-pointer-events{touch-action:pan-y}.swiper-pointer-events.swiper-vertical{touch-action:pan-x}.swiper-slide{flex-shrink:0;width:100%;height:100%;position:relative;transition-property:transform}.swiper-slide-invisible-blank{visibility:hidden}.swiper-autoheight,.swiper-autoheight .swiper-slide{height:auto}.swiper-autoheight .swiper-wrapper{align-items:flex-start;transition-property:transform,height}.swiper-backface-hidden .swiper-slide{transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden}.swiper-3d,.swiper-3d.swiper-css-mode .swiper-wrapper{perspective:1200px}.swiper-3d .swiper-cube-shadow,.swiper-3d .swiper-slide,.swiper-3d .swiper-slide-shadow,.swiper-3d .swiper-slide-shadow-bottom,.swiper-3d .swiper-slide-shadow-left,.swiper-3d .swiper-slide-shadow-right,.swiper-3d .swiper-slide-shadow-top,.swiper-3d .swiper-wrapper{transform-style:preserve-3d}.swiper-3d .swiper-slide-shadow,.swiper-3d .swiper-slide-shadow-bottom,.swiper-3d .swiper-slide-shadow-left,.swiper-3d .swiper-slide-shadow-right,.swiper-3d .swiper-slide-shadow-top{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:10}.swiper-3d .swiper-slide-shadow{background:rgba(0,0,0,.15)}.swiper-3d .swiper-slide-shadow-left{background-image:linear-gradient(to left,rgba(0,0,0,.5),rgba(0,0,0,0))}.swiper-3d .swiper-slide-shadow-right{background-image:linear-gradient(to right,rgba(0,0,0,.5),rgba(0,0,0,0))}.swiper-3d .swiper-slide-shadow-top{background-image:linear-gradient(to top,rgba(0,0,0,.5),rgba(0,0,0,0))}.swiper-3d .swiper-slide-shadow-bottom{background-image:linear-gradient(to bottom,rgba(0,0,0,.5),rgba(0,0,0,0))}.swiper-css-mode>.swiper-wrapper{overflow:auto;scrollbar-width:none;-ms-overflow-style:none}.swiper-css-mode>.swiper-wrapper::-webkit-scrollbar{display:none}.swiper-css-mode>.swiper-wrapper>.swiper-slide{scroll-snap-align:start start}.swiper-horizontal.swiper-css-mode>.swiper-wrapper{scroll-snap-type:x mandatory}.swiper-vertical.swiper-css-mode>.swiper-wrapper{scroll-snap-type:y mandatory}.swiper-centered>.swiper-wrapper::before{content:'';flex-shrink:0;order:9999}.swiper-centered.swiper-horizontal>.swiper-wrapper>.swiper-slide:first-child{margin-inline-start:var(--swiper-centered-offset-before)}.swiper-centered.swiper-horizontal>.swiper-wrapper::before{height:100%;min-height:1px;width:var(--swiper-centered-offset-after)}.swiper-centered.swiper-vertical>.swiper-wrapper>.swiper-slide:first-child{margin-block-start:var(--swiper-centered-offset-before)}.swiper-centered.swiper-vertical>.swiper-wrapper::before{width:100%;min-width:1px;height:var(--swiper-centered-offset-after)}.swiper-centered>.swiper-wrapper>.swiper-slide{scroll-snap-align:center center}.swiper-virtual .swiper-slide{-webkit-backface-visibility:hidden;transform:translateZ(0)}.swiper-virtual.swiper-css-mode .swiper-wrapper::after{content:'';position:absolute;left:0;top:0;pointer-events:none}.swiper-virtual.swiper-css-mode.swiper-horizontal .swiper-wrapper::after{height:1px;width:var(--swiper-virtual-size)}.swiper-virtual.swiper-css-mode.swiper-vertical .swiper-wrapper::after{width:1px;height:var(--swiper-virtual-size)}:root{--swiper-navigation-size:44px}.swiper-button-next,.swiper-button-prev{position:absolute;top:50%;width:calc(var(--swiper-navigation-size)/ 44 * 27);height:var(--swiper-navigation-size);margin-top:calc(0px - (var(--swiper-navigation-size)/ 2));z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--swiper-navigation-color,var(--swiper-theme-color))}.swiper-button-next.swiper-button-disabled,.swiper-button-prev.swiper-button-disabled{opacity:.35;cursor:auto;pointer-events:none}.swiper-button-next.swiper-button-hidden,.swiper-button-prev.swiper-button-hidden{opacity:0;cursor:auto;pointer-events:none}.swiper-navigation-disabled .swiper-button-next,.swiper-navigation-disabled .swiper-button-prev{display:none!important}.swiper-button-next:after,.swiper-button-prev:after{font-family:swiper-icons;font-size:var(--swiper-navigation-size);text-transform:none!important;letter-spacing:0;font-variant:initial;line-height:1}.swiper-button-prev,.swiper-rtl .swiper-button-next{left:10px;right:auto}.swiper-button-prev:after,.swiper-rtl .swiper-button-next:after{content:'prev'}.swiper-button-next,.swiper-rtl .swiper-button-prev{right:10px;left:auto}.swiper-button-next:after,.swiper-rtl .swiper-button-prev:after{content:'next'}.swiper-button-lock{display:none}.swiper-pagination{position:absolute;text-align:center;transition:.3s opacity;transform:translate3d(0,0,0);z-index:10}.swiper-pagination.swiper-pagination-hidden{opacity:0}.swiper-pagination-disabled>.swiper-pagination,.swiper-pagination.swiper-pagination-disabled{display:none!important}.swiper-horizontal>.swiper-pagination-bullets,.swiper-pagination-bullets.swiper-pagination-horizontal,.swiper-pagination-custom,.swiper-pagination-fraction{bottom:10px;left:0;width:100%}.swiper-pagination-bullets-dynamic{overflow:hidden;font-size:0}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet{transform:scale(.33);position:relative}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active{transform:scale(1)}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main{transform:scale(1)}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev{transform:scale(.66)}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev{transform:scale(.33)}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next{transform:scale(.66)}.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next{transform:scale(.33)}.swiper-pagination-bullet{width:var(--swiper-pagination-bullet-width,var(--swiper-pagination-bullet-size,8px));height:var(--swiper-pagination-bullet-height,var(--swiper-pagination-bullet-size,8px));display:inline-block;border-radius:50%;background:var(--swiper-pagination-bullet-inactive-color,#000);opacity:var(--swiper-pagination-bullet-inactive-opacity, .2)}button.swiper-pagination-bullet{border:none;margin:0;padding:0;box-shadow:none;-webkit-appearance:none;appearance:none}.swiper-pagination-clickable .swiper-pagination-bullet{cursor:pointer}.swiper-pagination-bullet:only-child{display:none!important}.swiper-pagination-bullet-active{opacity:var(--swiper-pagination-bullet-opacity, 1);background:var(--swiper-pagination-color,var(--swiper-theme-color))}.swiper-pagination-vertical.swiper-pagination-bullets,.swiper-vertical>.swiper-pagination-bullets{right:10px;top:50%;transform:translate3d(0px,-50%,0)}.swiper-pagination-vertical.swiper-pagination-bullets .swiper-pagination-bullet,.swiper-vertical>.swiper-pagination-bullets .swiper-pagination-bullet{margin:var(--swiper-pagination-bullet-vertical-gap,6px) 0;display:block}.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic,.swiper-vertical>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic{top:50%;transform:translateY(-50%);width:8px}.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet,.swiper-vertical>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet{display:inline-block;transition:.2s transform,.2s top}.swiper-horizontal>.swiper-pagination-bullets .swiper-pagination-bullet,.swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet{margin:0 var(--swiper-pagination-bullet-horizontal-gap,4px)}.swiper-horizontal>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic,.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic{left:50%;transform:translateX(-50%);white-space:nowrap}.swiper-horizontal>.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet,.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet{transition:.2s transform,.2s left}.swiper-horizontal.swiper-rtl>.swiper-pagination-bullets-dynamic .swiper-pagination-bullet{transition:.2s transform,.2s right}.swiper-pagination-progressbar{background:rgba(0,0,0,.25);position:absolute}.swiper-pagination-progressbar .swiper-pagination-progressbar-fill{background:var(--swiper-pagination-color,var(--swiper-theme-color));position:absolute;left:0;top:0;width:100%;height:100%;transform:scale(0);transform-origin:left top}.swiper-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill{transform-origin:right top}.swiper-horizontal>.swiper-pagination-progressbar,.swiper-pagination-progressbar.swiper-pagination-horizontal,.swiper-pagination-progressbar.swiper-pagination-vertical.swiper-pagination-progressbar-opposite,.swiper-vertical>.swiper-pagination-progressbar.swiper-pagination-progressbar-opposite{width:100%;height:4px;left:0;top:0}.swiper-horizontal>.swiper-pagination-progressbar.swiper-pagination-progressbar-opposite,.swiper-pagination-progressbar.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite,.swiper-pagination-progressbar.swiper-pagination-vertical,.swiper-vertical>.swiper-pagination-progressbar{width:4px;height:100%;left:0;top:0}.swiper-pagination-lock{display:none}.swiper-scrollbar{border-radius:10px;position:relative;-ms-touch-action:none;background:rgba(0,0,0,.1)}.swiper-scrollbar-disabled>.swiper-scrollbar,.swiper-scrollbar.swiper-scrollbar-disabled{display:none!important}.swiper-horizontal>.swiper-scrollbar{position:absolute;left:1%;bottom:3px;z-index:50;height:5px;width:98%}.swiper-vertical>.swiper-scrollbar{position:absolute;right:3px;top:1%;z-index:50;width:5px;height:98%}.swiper-scrollbar-drag{height:100%;width:100%;position:relative;background:rgba(0,0,0,.5);border-radius:10px;left:0;top:0}.swiper-scrollbar-cursor-drag{cursor:move}.swiper-scrollbar-lock{display:none}.swiper-zoom-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center;text-align:center}.swiper-zoom-container>canvas,.swiper-zoom-container>img,.swiper-zoom-container>svg{max-width:100%;max-height:100%;object-fit:contain}.swiper-slide-zoomed{cursor:move}.swiper-lazy-preloader{width:42px;height:42px;position:absolute;left:50%;top:50%;margin-left:-21px;margin-top:-21px;z-index:10;transform-origin:50%;box-sizing:border-box;border:4px solid var(--swiper-preloader-color,var(--swiper-theme-color));border-radius:50%;border-top-color:transparent}.swiper-watch-progress .swiper-slide-visible .swiper-lazy-preloader,.swiper:not(.swiper-watch-progress) .swiper-lazy-preloader{animation:swiper-preloader-spin 1s infinite linear}.swiper-lazy-preloader-white{--swiper-preloader-color:#fff}.swiper-lazy-preloader-black{--swiper-preloader-color:#000}@keyframes swiper-preloader-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.swiper .swiper-notification{position:absolute;left:0;top:0;pointer-events:none;opacity:0;z-index:-1000}.swiper-free-mode>.swiper-wrapper{transition-timing-function:ease-out;margin:0 auto}.swiper-grid>.swiper-wrapper{flex-wrap:wrap}.swiper-grid-column>.swiper-wrapper{flex-wrap:wrap;flex-direction:column}.swiper-fade.swiper-free-mode .swiper-slide{transition-timing-function:ease-out}.swiper-fade .swiper-slide{pointer-events:none;transition-property:opacity}.swiper-fade .swiper-slide .swiper-slide{pointer-events:none}.swiper-fade .swiper-slide-active,.swiper-fade .swiper-slide-active .swiper-slide-active{pointer-events:auto}.swiper-cube{overflow:visible}.swiper-cube .swiper-slide{pointer-events:none;-webkit-backface-visibility:hidden;backface-visibility:hidden;z-index:1;visibility:hidden;transform-origin:0 0;width:100%;height:100%}.swiper-cube .swiper-slide .swiper-slide{pointer-events:none}.swiper-cube.swiper-rtl .swiper-slide{transform-origin:100% 0}.swiper-cube .swiper-slide-active,.swiper-cube .swiper-slide-active .swiper-slide-active{pointer-events:auto}.swiper-cube .swiper-slide-active,.swiper-cube .swiper-slide-next,.swiper-cube .swiper-slide-next+.swiper-slide,.swiper-cube .swiper-slide-prev{pointer-events:auto;visibility:visible}.swiper-cube .swiper-slide-shadow-bottom,.swiper-cube .swiper-slide-shadow-left,.swiper-cube .swiper-slide-shadow-right,.swiper-cube .swiper-slide-shadow-top{z-index:0;-webkit-backface-visibility:hidden;backface-visibility:hidden}.swiper-cube .swiper-cube-shadow{position:absolute;left:0;bottom:0px;width:100%;height:100%;opacity:.6;z-index:0}.swiper-cube .swiper-cube-shadow:before{content:'';background:#000;position:absolute;left:0;top:0;bottom:0;right:0;filter:blur(50px)}.swiper-flip{overflow:visible}.swiper-flip .swiper-slide{pointer-events:none;-webkit-backface-visibility:hidden;backface-visibility:hidden;z-index:1}.swiper-flip .swiper-slide .swiper-slide{pointer-events:none}.swiper-flip .swiper-slide-active,.swiper-flip .swiper-slide-active .swiper-slide-active{pointer-events:auto}.swiper-flip .swiper-slide-shadow-bottom,.swiper-flip .swiper-slide-shadow-left,.swiper-flip .swiper-slide-shadow-right,.swiper-flip .swiper-slide-shadow-top{z-index:0;-webkit-backface-visibility:hidden;backface-visibility:hidden}.swiper-creative .swiper-slide{-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow:hidden;transition-property:transform,opacity,height}.swiper-cards{overflow:visible}.swiper-cards .swiper-slide{transform-origin:center bottom;-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow:hidden} + @font-face { + font-family: swiper-icons; + src: url('data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA'); + font-weight: 400; + font-style: normal; + } + :root { + --swiper-theme-color: #007aff; + } + .swiper { + margin-left: auto; + margin-right: auto; + position: relative; + overflow: hidden; + list-style: none; + padding: 0; + z-index: 1; + } + .swiper-vertical > .swiper-wrapper { + flex-direction: column; + } + .swiper-wrapper { + position: relative; + width: 100%; + height: 100%; + z-index: 1; + display: flex; + transition-property: transform; + box-sizing: content-box; + } + .swiper-android .swiper-slide, + .swiper-wrapper { + transform: translate3d(0px, 0, 0); + } + .swiper-pointer-events { + touch-action: pan-y; + } + .swiper-pointer-events.swiper-vertical { + touch-action: pan-x; + } + .swiper-slide { + flex-shrink: 0; + width: 100%; + height: 100%; + position: relative; + transition-property: transform; + } + .swiper-slide-invisible-blank { + visibility: hidden; + } + .swiper-autoheight, + .swiper-autoheight .swiper-slide { + height: auto; + } + .swiper-autoheight .swiper-wrapper { + align-items: flex-start; + transition-property: transform, height; + } + .swiper-backface-hidden .swiper-slide { + transform: translateZ(0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + } + .swiper-3d, + .swiper-3d.swiper-css-mode .swiper-wrapper { + perspective: 1200px; + } + .swiper-3d .swiper-cube-shadow, + .swiper-3d .swiper-slide, + .swiper-3d .swiper-slide-shadow, + .swiper-3d .swiper-slide-shadow-bottom, + .swiper-3d .swiper-slide-shadow-left, + .swiper-3d .swiper-slide-shadow-right, + .swiper-3d .swiper-slide-shadow-top, + .swiper-3d .swiper-wrapper { + transform-style: preserve-3d; + } + .swiper-3d .swiper-slide-shadow, + .swiper-3d .swiper-slide-shadow-bottom, + .swiper-3d .swiper-slide-shadow-left, + .swiper-3d .swiper-slide-shadow-right, + .swiper-3d .swiper-slide-shadow-top { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; + } + .swiper-3d .swiper-slide-shadow { + background: rgba(0, 0, 0, 0.15); + } + .swiper-3d .swiper-slide-shadow-left { + background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); + } + .swiper-3d .swiper-slide-shadow-right { + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); + } + .swiper-3d .swiper-slide-shadow-top { + background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); + } + .swiper-3d .swiper-slide-shadow-bottom { + background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); + } + .swiper-css-mode > .swiper-wrapper { + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none; + } + .swiper-css-mode > .swiper-wrapper::-webkit-scrollbar { + display: none; + } + .swiper-css-mode > .swiper-wrapper > .swiper-slide { + scroll-snap-align: start start; + } + .swiper-horizontal.swiper-css-mode > .swiper-wrapper { + scroll-snap-type: x mandatory; + } + .swiper-vertical.swiper-css-mode > .swiper-wrapper { + scroll-snap-type: y mandatory; + } + .swiper-centered > .swiper-wrapper::before { + content: ''; + flex-shrink: 0; + order: 9999; + } + .swiper-centered.swiper-horizontal > .swiper-wrapper > .swiper-slide:first-child { + margin-inline-start: var(--swiper-centered-offset-before); + } + .swiper-centered.swiper-horizontal > .swiper-wrapper::before { + height: 100%; + min-height: 1px; + width: var(--swiper-centered-offset-after); + } + .swiper-centered.swiper-vertical > .swiper-wrapper > .swiper-slide:first-child { + margin-block-start: var(--swiper-centered-offset-before); + } + .swiper-centered.swiper-vertical > .swiper-wrapper::before { + width: 100%; + min-width: 1px; + height: var(--swiper-centered-offset-after); + } + .swiper-centered > .swiper-wrapper > .swiper-slide { + scroll-snap-align: center center; + } + .swiper-virtual .swiper-slide { + -webkit-backface-visibility: hidden; + transform: translateZ(0); + } + .swiper-virtual.swiper-css-mode .swiper-wrapper::after { + content: ''; + position: absolute; + left: 0; + top: 0; + pointer-events: none; + } + .swiper-virtual.swiper-css-mode.swiper-horizontal .swiper-wrapper::after { + height: 1px; + width: var(--swiper-virtual-size); + } + .swiper-virtual.swiper-css-mode.swiper-vertical .swiper-wrapper::after { + width: 1px; + height: var(--swiper-virtual-size); + } + :root { + --swiper-navigation-size: 44px; + } + .swiper-button-next, + .swiper-button-prev { + position: absolute; + top: 50%; + width: calc(var(--swiper-navigation-size) / 44 * 27); + height: var(--swiper-navigation-size); + margin-top: calc(0px - (var(--swiper-navigation-size) / 2)); + z-index: 10; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--swiper-navigation-color, var(--swiper-theme-color)); + } + .swiper-button-next.swiper-button-disabled, + .swiper-button-prev.swiper-button-disabled { + opacity: 0.35; + cursor: auto; + pointer-events: none; + } + .swiper-button-next.swiper-button-hidden, + .swiper-button-prev.swiper-button-hidden { + opacity: 0; + cursor: auto; + pointer-events: none; + } + .swiper-navigation-disabled .swiper-button-next, + .swiper-navigation-disabled .swiper-button-prev { + display: none !important; + } + .swiper-button-next:after, + .swiper-button-prev:after { + font-family: swiper-icons; + font-size: var(--swiper-navigation-size); + text-transform: none !important; + letter-spacing: 0; + font-variant: initial; + line-height: 1; + } + .swiper-button-prev, + .swiper-rtl .swiper-button-next { + left: 10px; + right: auto; + } + .swiper-button-prev:after, + .swiper-rtl .swiper-button-next:after { + content: 'prev'; + } + .swiper-button-next, + .swiper-rtl .swiper-button-prev { + right: 10px; + left: auto; + } + .swiper-button-next:after, + .swiper-rtl .swiper-button-prev:after { + content: 'next'; + } + .swiper-button-lock { + display: none; + } + .swiper-pagination { + position: absolute; + text-align: center; + transition: 0.3s opacity; + transform: translate3d(0, 0, 0); + z-index: 10; + } + .swiper-pagination.swiper-pagination-hidden { + opacity: 0; + } + .swiper-pagination-disabled > .swiper-pagination, + .swiper-pagination.swiper-pagination-disabled { + display: none !important; + } + .swiper-horizontal > .swiper-pagination-bullets, + .swiper-pagination-bullets.swiper-pagination-horizontal, + .swiper-pagination-custom, + .swiper-pagination-fraction { + bottom: 10px; + left: 0; + width: 100%; + } + .swiper-pagination-bullets-dynamic { + overflow: hidden; + font-size: 0; + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + transform: scale(0.33); + position: relative; + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active { + transform: scale(1); + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main { + transform: scale(1); + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev { + transform: scale(0.66); + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev { + transform: scale(0.33); + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next { + transform: scale(0.66); + } + .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next { + transform: scale(0.33); + } + .swiper-pagination-bullet { + width: var(--swiper-pagination-bullet-width, var(--swiper-pagination-bullet-size, 8px)); + height: var(--swiper-pagination-bullet-height, var(--swiper-pagination-bullet-size, 8px)); + display: inline-block; + border-radius: 50%; + background: var(--swiper-pagination-bullet-inactive-color, #000); + opacity: var(--swiper-pagination-bullet-inactive-opacity, 0.2); + } + button.swiper-pagination-bullet { + border: none; + margin: 0; + padding: 0; + box-shadow: none; + -webkit-appearance: none; + appearance: none; + } + .swiper-pagination-clickable .swiper-pagination-bullet { + cursor: pointer; + } + .swiper-pagination-bullet:only-child { + display: none !important; + } + .swiper-pagination-bullet-active { + opacity: var(--swiper-pagination-bullet-opacity, 1); + background: var(--swiper-pagination-color, var(--swiper-theme-color)); + } + .swiper-pagination-vertical.swiper-pagination-bullets, + .swiper-vertical > .swiper-pagination-bullets { + right: 10px; + top: 50%; + transform: translate3d(0px, -50%, 0); + } + .swiper-pagination-vertical.swiper-pagination-bullets .swiper-pagination-bullet, + .swiper-vertical > .swiper-pagination-bullets .swiper-pagination-bullet { + margin: var(--swiper-pagination-bullet-vertical-gap, 6px) 0; + display: block; + } + .swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic, + .swiper-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic { + top: 50%; + transform: translateY(-50%); + width: 8px; + } + .swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic + .swiper-pagination-bullet, + .swiper-vertical + > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic + .swiper-pagination-bullet { + display: inline-block; + transition: + 0.2s transform, + 0.2s top; + } + .swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet, + .swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet { + margin: 0 var(--swiper-pagination-bullet-horizontal-gap, 4px); + } + .swiper-horizontal > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic, + .swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic { + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + } + .swiper-horizontal + > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic + .swiper-pagination-bullet, + .swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic + .swiper-pagination-bullet { + transition: + 0.2s transform, + 0.2s left; + } + .swiper-horizontal.swiper-rtl > .swiper-pagination-bullets-dynamic .swiper-pagination-bullet { + transition: + 0.2s transform, + 0.2s right; + } + .swiper-pagination-progressbar { + background: rgba(0, 0, 0, 0.25); + position: absolute; + } + .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { + background: var(--swiper-pagination-color, var(--swiper-theme-color)); + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + transform: scale(0); + transform-origin: left top; + } + .swiper-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { + transform-origin: right top; + } + .swiper-horizontal > .swiper-pagination-progressbar, + .swiper-pagination-progressbar.swiper-pagination-horizontal, + .swiper-pagination-progressbar.swiper-pagination-vertical.swiper-pagination-progressbar-opposite, + .swiper-vertical > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite { + width: 100%; + height: 4px; + left: 0; + top: 0; + } + .swiper-horizontal > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite, + .swiper-pagination-progressbar.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite, + .swiper-pagination-progressbar.swiper-pagination-vertical, + .swiper-vertical > .swiper-pagination-progressbar { + width: 4px; + height: 100%; + left: 0; + top: 0; + } + .swiper-pagination-lock { + display: none; + } + .swiper-scrollbar { + border-radius: 10px; + position: relative; + -ms-touch-action: none; + background: rgba(0, 0, 0, 0.1); + } + .swiper-scrollbar-disabled > .swiper-scrollbar, + .swiper-scrollbar.swiper-scrollbar-disabled { + display: none !important; + } + .swiper-horizontal > .swiper-scrollbar { + position: absolute; + left: 1%; + bottom: 3px; + z-index: 50; + height: 5px; + width: 98%; + } + .swiper-vertical > .swiper-scrollbar { + position: absolute; + right: 3px; + top: 1%; + z-index: 50; + width: 5px; + height: 98%; + } + .swiper-scrollbar-drag { + height: 100%; + width: 100%; + position: relative; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + left: 0; + top: 0; + } + .swiper-scrollbar-cursor-drag { + cursor: move; + } + .swiper-scrollbar-lock { + display: none; + } + .swiper-zoom-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + .swiper-zoom-container > canvas, + .swiper-zoom-container > img, + .swiper-zoom-container > svg { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } + .swiper-slide-zoomed { + cursor: move; + } + .swiper-lazy-preloader { + width: 42px; + height: 42px; + position: absolute; + left: 50%; + top: 50%; + margin-left: -21px; + margin-top: -21px; + z-index: 10; + transform-origin: 50%; + box-sizing: border-box; + border: 4px solid var(--swiper-preloader-color, var(--swiper-theme-color)); + border-radius: 50%; + border-top-color: transparent; + } + .swiper-watch-progress .swiper-slide-visible .swiper-lazy-preloader, + .swiper:not(.swiper-watch-progress) .swiper-lazy-preloader { + animation: swiper-preloader-spin 1s infinite linear; + } + .swiper-lazy-preloader-white { + --swiper-preloader-color: #fff; + } + .swiper-lazy-preloader-black { + --swiper-preloader-color: #000; + } + @keyframes swiper-preloader-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + .swiper .swiper-notification { + position: absolute; + left: 0; + top: 0; + pointer-events: none; + opacity: 0; + z-index: -1000; + } + .swiper-free-mode > .swiper-wrapper { + transition-timing-function: ease-out; + margin: 0 auto; + } + .swiper-grid > .swiper-wrapper { + flex-wrap: wrap; + } + .swiper-grid-column > .swiper-wrapper { + flex-wrap: wrap; + flex-direction: column; + } + .swiper-fade.swiper-free-mode .swiper-slide { + transition-timing-function: ease-out; + } + .swiper-fade .swiper-slide { + pointer-events: none; + transition-property: opacity; + } + .swiper-fade .swiper-slide .swiper-slide { + pointer-events: none; + } + .swiper-fade .swiper-slide-active, + .swiper-fade .swiper-slide-active .swiper-slide-active { + pointer-events: auto; + } + .swiper-cube { + overflow: visible; + } + .swiper-cube .swiper-slide { + pointer-events: none; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 1; + visibility: hidden; + transform-origin: 0 0; + width: 100%; + height: 100%; + } + .swiper-cube .swiper-slide .swiper-slide { + pointer-events: none; + } + .swiper-cube.swiper-rtl .swiper-slide { + transform-origin: 100% 0; + } + .swiper-cube .swiper-slide-active, + .swiper-cube .swiper-slide-active .swiper-slide-active { + pointer-events: auto; + } + .swiper-cube .swiper-slide-active, + .swiper-cube .swiper-slide-next, + .swiper-cube .swiper-slide-next + .swiper-slide, + .swiper-cube .swiper-slide-prev { + pointer-events: auto; + visibility: visible; + } + .swiper-cube .swiper-slide-shadow-bottom, + .swiper-cube .swiper-slide-shadow-left, + .swiper-cube .swiper-slide-shadow-right, + .swiper-cube .swiper-slide-shadow-top { + z-index: 0; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + } + .swiper-cube .swiper-cube-shadow { + position: absolute; + left: 0; + bottom: 0px; + width: 100%; + height: 100%; + opacity: 0.6; + z-index: 0; + } + .swiper-cube .swiper-cube-shadow:before { + content: ''; + background: #000; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + filter: blur(50px); + } + .swiper-flip { + overflow: visible; + } + .swiper-flip .swiper-slide { + pointer-events: none; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 1; + } + .swiper-flip .swiper-slide .swiper-slide { + pointer-events: none; + } + .swiper-flip .swiper-slide-active, + .swiper-flip .swiper-slide-active .swiper-slide-active { + pointer-events: auto; + } + .swiper-flip .swiper-slide-shadow-bottom, + .swiper-flip .swiper-slide-shadow-left, + .swiper-flip .swiper-slide-shadow-right, + .swiper-flip .swiper-slide-shadow-top { + z-index: 0; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + } + .swiper-creative .swiper-slide { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + overflow: hidden; + transition-property: transform, opacity, height; + } + .swiper-cards { + overflow: visible; + } + .swiper-cards .swiper-slide { + transform-origin: center bottom; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + overflow: hidden; + } `; diff --git a/src/helpers/action-handler-directive.ts b/src/helpers/action-handler-directive.ts index 02a1460..6614bc6 100644 --- a/src/helpers/action-handler-directive.ts +++ b/src/helpers/action-handler-directive.ts @@ -3,166 +3,165 @@ import { ActionHandlerDetail, ActionHandlerOptions } from 'custom-card-helpers/d import { noChange } from 'lit'; import { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive'; -const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0; +const isTouch = + 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0; interface ActionHandler extends HTMLElement { - holdTime: number; - bind(element: Element, options): void; + holdTime: number; + bind(element: Element, options): void; } interface ActionHandlerElement extends HTMLElement { - actionHandler?: boolean; + actionHandler?: boolean; } declare global { - interface HASSDomEvents { - action: ActionHandlerDetail; - } + interface HASSDomEvents { + action: ActionHandlerDetail; + } } class ActionHandler extends HTMLElement implements ActionHandler { - public holdTime = 500; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public ripple: any; - - protected timer?: number; - - protected held = false; - - private dblClickTimeout?: number; - - constructor() { - super(); - this.ripple = document.createElement('mwc-ripple'); - } - - public connectedCallback(): void { - Object.assign(this.style, { - position: 'absolute', - width: isTouch ? '100px' : '50px', - height: isTouch ? '100px' : '50px', - transform: 'translate(-50%, -50%)', - pointerEvents: 'none', - zIndex: '999' - }); - - this.appendChild(this.ripple); - this.ripple.primary = true; - - ['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach((ev) => { - document.addEventListener( - ev, - () => { - clearTimeout(this.timer); - this.stopAnimation(); - this.timer = undefined; - }, - { passive: true } - ); - }); - } - - public bind(element: ActionHandlerElement, options): void { - if (element.actionHandler) { - return; - } - element.actionHandler = true; - - element.addEventListener('contextmenu', (ev: Event) => { - const e = ev || window.event; - if (e.preventDefault) { - e.preventDefault(); - } - if (e.stopPropagation) { - e.stopPropagation(); - } - e.cancelBubble = true; - e.returnValue = false; - return false; - }); - - const start = (ev: Event): void => { - this.held = false; - let x; - let y; - if ((ev as TouchEvent).touches) { - x = (ev as TouchEvent).touches[0].pageX; - y = (ev as TouchEvent).touches[0].pageY; - } - else { - x = (ev as MouseEvent).pageX; - y = (ev as MouseEvent).pageY; - } - - this.timer = window.setTimeout(() => { - this.startAnimation(x, y); - this.held = true; - }, this.holdTime); - }; - - const end = (ev: Event): void => { - // Prevent mouse event if touch event - ev.preventDefault(); - if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) { - return; - } - clearTimeout(this.timer); - this.stopAnimation(); - this.timer = undefined; - if (this.held) { - fireEvent(element, 'action', { action: 'hold' }); - } - else if (options.hasDoubleClick) { - if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) { - this.dblClickTimeout = window.setTimeout(() => { - this.dblClickTimeout = undefined; - fireEvent(element, 'action', { action: 'tap' }); - }, 250); - } - else { - clearTimeout(this.dblClickTimeout); - this.dblClickTimeout = undefined; - fireEvent(element, 'action', { action: 'double_tap' }); - } - } - else { - fireEvent(element, 'action', { action: 'tap' }); - } - }; - - const handleEnter = (ev: KeyboardEvent): void => { - if (ev.keyCode !== 13) { - return; - } - end(ev); - }; - - element.addEventListener('touchstart', start, { passive: true }); - element.addEventListener('touchend', end); - element.addEventListener('touchcancel', end); - - element.addEventListener('mousedown', start, { passive: true }); - element.addEventListener('click', end); - - element.addEventListener('keyup', handleEnter); - } - - private startAnimation(x: number, y: number): void { - Object.assign(this.style, { - left: `${x}px`, - top: `${y}px`, - display: null - }); - this.ripple.disabled = false; - this.ripple.active = true; - this.ripple.unbounded = true; - } - - private stopAnimation(): void { - this.ripple.active = false; - this.ripple.disabled = true; - this.style.display = 'none'; - } + public holdTime = 500; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public ripple: any; + + protected timer?: number; + + protected held = false; + + private dblClickTimeout?: number; + + constructor() { + super(); + this.ripple = document.createElement('mwc-ripple'); + } + + public connectedCallback(): void { + Object.assign(this.style, { + position: 'absolute', + width: isTouch ? '100px' : '50px', + height: isTouch ? '100px' : '50px', + transform: 'translate(-50%, -50%)', + pointerEvents: 'none', + zIndex: '999', + }); + + this.appendChild(this.ripple); + this.ripple.primary = true; + + ['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach( + (ev) => { + document.addEventListener( + ev, + () => { + clearTimeout(this.timer); + this.stopAnimation(); + this.timer = undefined; + }, + { passive: true }, + ); + }, + ); + } + + public bind(element: ActionHandlerElement, options): void { + if (element.actionHandler) { + return; + } + element.actionHandler = true; + + element.addEventListener('contextmenu', (ev: Event) => { + const e = ev || window.event; + if (e.preventDefault) { + e.preventDefault(); + } + if (e.stopPropagation) { + e.stopPropagation(); + } + e.cancelBubble = true; + e.returnValue = false; + return false; + }); + + const start = (ev: Event): void => { + this.held = false; + let x; + let y; + if ((ev as TouchEvent).touches) { + x = (ev as TouchEvent).touches[0].pageX; + y = (ev as TouchEvent).touches[0].pageY; + } else { + x = (ev as MouseEvent).pageX; + y = (ev as MouseEvent).pageY; + } + + this.timer = window.setTimeout(() => { + this.startAnimation(x, y); + this.held = true; + }, this.holdTime); + }; + + const end = (ev: Event): void => { + // Prevent mouse event if touch event + ev.preventDefault(); + if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) { + return; + } + clearTimeout(this.timer); + this.stopAnimation(); + this.timer = undefined; + if (this.held) { + fireEvent(element, 'action', { action: 'hold' }); + } else if (options.hasDoubleClick) { + if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) { + this.dblClickTimeout = window.setTimeout(() => { + this.dblClickTimeout = undefined; + fireEvent(element, 'action', { action: 'tap' }); + }, 250); + } else { + clearTimeout(this.dblClickTimeout); + this.dblClickTimeout = undefined; + fireEvent(element, 'action', { action: 'double_tap' }); + } + } else { + fireEvent(element, 'action', { action: 'tap' }); + } + }; + + const handleEnter = (ev: KeyboardEvent): void => { + if (ev.keyCode !== 13) { + return; + } + end(ev); + }; + + element.addEventListener('touchstart', start, { passive: true }); + element.addEventListener('touchend', end); + element.addEventListener('touchcancel', end); + + element.addEventListener('mousedown', start, { passive: true }); + element.addEventListener('click', end); + + element.addEventListener('keyup', handleEnter); + } + + private startAnimation(x: number, y: number): void { + Object.assign(this.style, { + left: `${x}px`, + top: `${y}px`, + display: null, + }); + this.ripple.disabled = false; + this.ripple.active = true; + this.ripple.unbounded = true; + } + + private stopAnimation(): void { + this.ripple.active = false; + this.ripple.disabled = true; + this.style.display = 'none'; + } } customElements.define('action-handler-meteoalarm', ActionHandler); @@ -179,7 +178,10 @@ const getActionHandler = (): ActionHandler => { return actionhandler as ActionHandler; }; -export const actionHandlerBind = (element: ActionHandlerElement, options?: ActionHandlerOptions): void => { +export const actionHandlerBind = ( + element: ActionHandlerElement, + options?: ActionHandlerOptions, +): void => { const actionhandler: ActionHandler = getActionHandler(); if (!actionhandler) { return; @@ -196,5 +198,5 @@ export const actionHandler = directive( // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars render(_options?: ActionHandlerOptions) {} - } + }, ); diff --git a/src/helpers/process-config-entities.ts b/src/helpers/process-config-entities.ts index 8d41a03..7806dbf 100644 --- a/src/helpers/process-config-entities.ts +++ b/src/helpers/process-config-entities.ts @@ -3,41 +3,33 @@ import { EntityConfig } from 'custom-card-helpers'; -export function processConfigEntities(entities: Array | string | EntityConfig): EntityConfig[] { +export function processConfigEntities( + entities: Array | string | EntityConfig, +): EntityConfig[] { if (!Array.isArray(entities)) { - entities = [ entities ]; + entities = [entities]; } - if(entities.length > 0 && entities.every(e => e == null)) { + if (entities.length > 0 && entities.every((e) => e == null)) { return []; } - return entities.map( - (entityConf, index): any=> { - if ( - typeof entityConf === 'object' && - !Array.isArray(entityConf) && - entityConf.type - ) { - return entityConf; - } + return entities.map((entityConf, index): any => { + if (typeof entityConf === 'object' && !Array.isArray(entityConf) && entityConf.type) { + return entityConf; + } - let config: EntityConfig; + let config: EntityConfig; - if (typeof entityConf === 'string') { - config = { entity: entityConf } as EntityConfig; - } - else if (typeof entityConf === 'object' && !Array.isArray(entityConf)) { - if (!('entity' in entityConf)) { - throw new Error( - `Entity object at position ${index} is missing entity field.` - ); - } - config = entityConf as EntityConfig; - } - else { - throw new Error(`Invalid entity specified at position ${index}.`); + if (typeof entityConf === 'string') { + config = { entity: entityConf } as EntityConfig; + } else if (typeof entityConf === 'object' && !Array.isArray(entityConf)) { + if (!('entity' in entityConf)) { + throw new Error(`Entity object at position ${index} is missing entity field.`); } - return config; + config = entityConf as EntityConfig; + } else { + throw new Error(`Invalid entity specified at position ${index}.`); } - ); + return config; + }); } diff --git a/src/helpers/process-editor-entities.ts b/src/helpers/process-editor-entities.ts index 5b95df3..b543648 100644 --- a/src/helpers/process-editor-entities.ts +++ b/src/helpers/process-editor-entities.ts @@ -5,13 +5,13 @@ import { EntityConfig } from 'custom-card-helpers'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function processEditorEntities(entities: Array | string | undefined): EntityConfig[] { - if(entities == undefined) { + if (entities == undefined) { return []; } - if(!Array.isArray(entities)) { - entities = [ entities ]; + if (!Array.isArray(entities)) { + entities = [entities]; } - if(entities.length > 0 && entities.every(e => e == null)) { + if (entities.length > 0 && entities.every((e) => e == null)) { return []; } return entities.map((entityConf: any) => { diff --git a/src/integrations/burze_dzis_net.ts b/src/integrations/burze_dzis_net.ts index ced9c8c..73904ab 100644 --- a/src/integrations/burze_dzis_net.ts +++ b/src/integrations/burze_dzis_net.ts @@ -4,16 +4,16 @@ import { MeteoalarmEventType, MeteoalarmIntegration, MeteoalarmIntegrationEntityType, - MeteoalarmIntegrationMetadata + MeteoalarmIntegrationMetadata, } from '../types'; type BurzeDzisNetEntity = HassEntity & { attributes: { - attribution: string, - description?: string, - level?: number - } -} + attribution: string; + description?: string; + level?: number; + }; +}; export default class BurzeDzisNet implements MeteoalarmIntegration { public get metadata(): MeteoalarmIntegrationMetadata { @@ -30,15 +30,16 @@ export default class BurzeDzisNet implements MeteoalarmIntegration { MeteoalarmEventType.Rain, MeteoalarmEventType.Thunderstorms, MeteoalarmEventType.Tornado, - MeteoalarmEventType.Wind - ] + MeteoalarmEventType.Wind, + ], }; } public supports(entity: BurzeDzisNetEntity): boolean { return ( entity.attributes.attribution == 'Information provided by Burze.dzis.net.' && - this.getEventType(entity) !== undefined); + this.getEventType(entity) !== undefined + ); } public alertActive(entity: BurzeDzisNetEntity): boolean { @@ -50,27 +51,40 @@ export default class BurzeDzisNet implements MeteoalarmIntegration { return { event: event, level: entity.attributes.level!, - headline: entity.attributes.description + headline: entity.attributes.description, }; } private getEventType(entity: HassEntity): MeteoalarmEventType | undefined { - if(entity.entity_id.endsWith('frost_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Mróz')) { + if ( + entity.entity_id.endsWith('frost_warning') && + entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Mróz') + ) { return MeteoalarmEventType.LowTemperature; - } - else if(entity.entity_id.endsWith('heat_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Upał')) { + } else if ( + entity.entity_id.endsWith('heat_warning') && + entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Upał') + ) { return MeteoalarmEventType.HighTemperature; - } - else if(entity.entity_id.endsWith('precipitation_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Opad')) { + } else if ( + entity.entity_id.endsWith('precipitation_warning') && + entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Opad') + ) { return MeteoalarmEventType.Rain; - } - else if(entity.entity_id.endsWith('storm_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Burza')) { + } else if ( + entity.entity_id.endsWith('storm_warning') && + entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Burza') + ) { return MeteoalarmEventType.Thunderstorms; - } - else if(entity.entity_id.endsWith('tornado_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Trąba')) { + } else if ( + entity.entity_id.endsWith('tornado_warning') && + entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Trąba') + ) { return MeteoalarmEventType.Tornado; - } - else if(entity.entity_id.endsWith('wind_warning') && entity.attributes.friendly_name?.endsWith(' Ostrzeżenie - Wiatr')) { + } else if ( + entity.entity_id.endsWith('wind_warning') && + entity.attributes.friendly_name?.endsWith(' Ostrzeżenie - Wiatr') + ) { return MeteoalarmEventType.Wind; } return undefined; diff --git a/src/integrations/dwd.ts b/src/integrations/dwd.ts index d038152..cbb1488 100644 --- a/src/integrations/dwd.ts +++ b/src/integrations/dwd.ts @@ -6,16 +6,16 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; import { Utils } from '../utils'; type DWDEntity = HassEntity & { attributes: { - attribution: string, - warning_count: number - } -} + attribution: string; + warning_count: number; + }; +}; export default class DWD implements MeteoalarmIntegration { public get metadata(): MeteoalarmIntegrationMetadata { @@ -26,12 +26,15 @@ export default class DWD implements MeteoalarmIntegration { returnHeadline: true, returnMultipleAlerts: true, entitiesCount: 2, - monitoredConditions: Utils.convertEventTypesForMetadata(this.eventTypes) + monitoredConditions: Utils.convertEventTypesForMetadata(this.eventTypes), }; } public supports(entity: DWDEntity): boolean { - return entity.attributes.attribution == 'Data provided by DWD' && this.getEntityKind(entity) !== undefined; + return ( + entity.attributes.attribution == 'Data provided by DWD' && + this.getEntityKind(entity) !== undefined + ); } public alertActive(entity: DWDEntity): boolean { @@ -102,7 +105,7 @@ export default class DWD implements MeteoalarmIntegration { 15: MeteoalarmEventType.CoastalEvent, 16: MeteoalarmEventType.CoastalEvent, 57: MeteoalarmEventType.CoastalEvent, - 58: MeteoalarmEventType.CoastalEvent + 58: MeteoalarmEventType.CoastalEvent, }; } @@ -116,19 +119,17 @@ export default class DWD implements MeteoalarmIntegration { const level = entity.attributes[`warning_${i}_level`]; const id = entity.attributes[`warning_${i}_type`]; const headline = entity.attributes[`warning_${i}_headline`]; - if(level == entity.state) { - if(id in this.eventTypes) { + if (level == entity.state) { + if (id in this.eventTypes) { result.push({ - headline: headline, + headline: headline, level: this.convertAwarenessLevel(level) as MeteoalarmLevelType, event: this.eventTypes[id], - kind: kind + kind: kind, }); - } - else if(id == 98 || id == 99) { + } else if (id == 98 || id == 99) { throw new Error('An test warning was issued! ID: ' + id); - } - else { + } else { throw new Error('Unknown event ID: ' + id); } } @@ -143,16 +144,13 @@ export default class DWD implements MeteoalarmIntegration { } private getEntityKind(entity: HassEntity): MeteoalarmAlertKind | undefined { - if(entity.entity_id.split('_').includes('current')) { + if (entity.entity_id.split('_').includes('current')) { return MeteoalarmAlertKind.Current; - } - else if(entity.entity_id.split('_').includes('advance')) { + } else if (entity.entity_id.split('_').includes('advance')) { return MeteoalarmAlertKind.Expected; - } - else if(entity.attributes.friendly_name?.split(' ').includes('Current')) { + } else if (entity.attributes.friendly_name?.split(' ').includes('Current')) { return MeteoalarmAlertKind.Current; - } - else if(entity.attributes.friendly_name?.split(' ').includes('Advance')) { + } else if (entity.attributes.friendly_name?.split(' ').includes('Advance')) { return MeteoalarmAlertKind.Expected; } return undefined; diff --git a/src/integrations/env_canada.ts b/src/integrations/env_canada.ts index 8ea97c2..2dc5a2b 100644 --- a/src/integrations/env_canada.ts +++ b/src/integrations/env_canada.ts @@ -5,20 +5,20 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; type EnvCanadaEntity = HassEntity & { attributes: { - attribution: string - } -} + attribution: string; + }; +}; export enum EnvCanadaEntityType { Warning, Watch, Statement, - Advisory + Advisory, } const ATTRIBUTION_EN = 'Data provided by Environment Canada'; @@ -33,7 +33,7 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { returnHeadline: false, returnMultipleAlerts: true, entitiesCount: 4, - monitoredConditions: [...new Set(this.eventTypes.map(e => e.type))] + monitoredConditions: [...new Set(this.eventTypes.map((e) => e.type))], }; } @@ -42,174 +42,175 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { return ( [ATTRIBUTION_EN, ATTRIBUTION_FR].includes(entity.attributes.attribution) && this.getEntityType(entity) !== undefined && - isStateNumber); + isStateNumber + ); } public alertActive(entity: EnvCanadaEntity): boolean { return Number(entity.state) > 0; } - private get eventTypes(): {en: string, fr: string, type: MeteoalarmEventType }[] { + private get eventTypes(): { en: string; fr: string; type: MeteoalarmEventType }[] { // English from: https://www.canada.ca/en/environment-climate-change/services/types-weather-forecasts-use/public/criteria-alerts.html // French from : https://www.canada.ca/fr/environnement-changement-climatique/services/types-previsions-meteorologiques-utilisation/publiques/criteres-alertes-meteo.html return [ { en: 'Arctic Outflow', fr: 'Poussée d’air Arctique', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Blizzard', fr: 'Blizzard', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Blowing Snow', fr: 'Poudrerie', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Dust Storm', fr: 'Tempête de Poussière', - type: MeteoalarmEventType.Dust + type: MeteoalarmEventType.Dust, }, { en: 'Extreme Cold', fr: 'Froid Extrême', - type: MeteoalarmEventType.LowTemperature + type: MeteoalarmEventType.LowTemperature, }, { en: 'Flash Freeze', fr: 'Refroidissement Soudain', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Fog', fr: 'Brouillard', - type: MeteoalarmEventType.Fog + type: MeteoalarmEventType.Fog, }, { en: 'Freezing Drizzle', fr: 'Bruine Verglaçante', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Freezing Rain', fr: 'Pluie Verglaçante', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Frost', fr: 'Gel', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Heat', fr: 'Chaleur', - type: MeteoalarmEventType.HighTemperature + type: MeteoalarmEventType.HighTemperature, }, { en: 'Hurricane', fr: 'Ouragan', - type: MeteoalarmEventType.Hurricane + type: MeteoalarmEventType.Hurricane, }, { en: 'Rainfall', fr: 'Pluie', - type: MeteoalarmEventType.Rain + type: MeteoalarmEventType.Rain, }, { en: 'Severe Thunderstorm', fr: 'Orage Violent', - type: MeteoalarmEventType.Thunderstorms + type: MeteoalarmEventType.Thunderstorms, }, { en: 'Smog', fr: 'Smog', - type: MeteoalarmEventType.AirQuality + type: MeteoalarmEventType.AirQuality, }, { en: 'Snowfall', fr: 'Neige', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Snow Squall', fr: 'Bourrasques de Neige', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Storm Surge', fr: 'Onde de Tempête', - type: MeteoalarmEventType.Thunderstorms + type: MeteoalarmEventType.Thunderstorms, }, { en: 'Tornado', fr: 'Tornade', - type: MeteoalarmEventType.Tornado + type: MeteoalarmEventType.Tornado, }, { en: 'Tropical Storm', fr: 'Tempête Tropicale', - type: MeteoalarmEventType.Hurricane + type: MeteoalarmEventType.Hurricane, }, { en: 'Tsunami', fr: 'Tsunami', - type: MeteoalarmEventType.Tsunami + type: MeteoalarmEventType.Tsunami, }, { en: 'Weather', fr: 'Météorologique', - type: MeteoalarmEventType.Unknown + type: MeteoalarmEventType.Unknown, }, { en: 'Wind', fr: 'Vents', - type: MeteoalarmEventType.Wind + type: MeteoalarmEventType.Wind, }, { en: 'Winter Storm', fr: 'Tempête Hivernale', - type: MeteoalarmEventType.SnowIce + type: MeteoalarmEventType.SnowIce, }, { en: 'Special Weather', fr: 'Météorologique Spécial', - type: MeteoalarmEventType.Unknown + type: MeteoalarmEventType.Unknown, }, { en: 'Special Air Quality', - fr: 'Spécial Sur La Qualité De L\'Air', - type: MeteoalarmEventType.AirQuality - } + fr: "Spécial Sur La Qualité De L'Air", + type: MeteoalarmEventType.AirQuality, + }, ]; } - private get entityTypeTranslation(): {en: string, fr: string, type: EnvCanadaEntityType }[] { + private get entityTypeTranslation(): { en: string; fr: string; type: EnvCanadaEntityType }[] { // English from: https://www.canada.ca/en/environment-climate-change/services/types-weather-forecasts-use/public/criteria-alerts.html // French from : https://www.canada.ca/fr/environnement-changement-climatique/services/types-previsions-meteorologiques-utilisation/publiques/criteres-alertes-meteo.html return [ { type: EnvCanadaEntityType.Warning, en: 'Warning', - fr: 'Avertissement De' + fr: 'Avertissement De', }, { type: EnvCanadaEntityType.Watch, en: 'Watch', - fr: 'Veille De' + fr: 'Veille De', }, { type: EnvCanadaEntityType.Statement, en: 'Statement', - fr: 'Bulletin' + fr: 'Bulletin', }, { type: EnvCanadaEntityType.Advisory, en: 'Advisory', - fr: 'Avis De' - } + fr: 'Avis De', + }, ]; } @@ -220,19 +221,18 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { * This transforms this back to event type */ private praseAlertName(alertName: string, type: EnvCanadaEntityType, isFrench: boolean) { - const prefixTranslation = this.entityTypeTranslation.find(t => t.type == type)!; + const prefixTranslation = this.entityTypeTranslation.find((t) => t.type == type)!; const prefix = isFrench ? prefixTranslation.fr : prefixTranslation.en; - if(!alertName.includes(prefix)) { - throw new Error(`Translated event prefix was not found in alert name '${prefix}' (isFrench=${isFrench})`); + if (!alertName.includes(prefix)) { + throw new Error( + `Translated event prefix was not found in alert name '${prefix}' (isFrench=${isFrench})`, + ); } alertName = alertName.replace(prefix, '').trim(); - return this.eventTypes.find(e => { - return ( - (isFrench && e.fr == alertName) || - (!isFrench && e.en == alertName) - ); + return this.eventTypes.find((e) => { + return (isFrench && e.fr == alertName) || (!isFrench && e.en == alertName); }); } @@ -247,13 +247,12 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { const isFrench = entity.attributes.attribution == 'Données fournies par Environnement Canada'; const alert = this.praseAlertName(alertName, type, isFrench); - if(alert) { + if (alert) { result.push({ level: this.getLevelFromType(type), - event: alert.type + event: alert.type, }); - } - else { + } else { throw new Error(`Unknown Env canada alert type: ${alertName} (isFrench=${isFrench})`); } } @@ -264,18 +263,18 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { private getEntityType(entity: HassEntity): EnvCanadaEntityType | undefined { // It's actually a sketchy solution to this, entity type can be detected by // entity_id or friendly_name so it loops thought both of them - for(const attribute of [ entity.entity_id, entity.attributes.friendly_name?.toLocaleLowerCase() ]) { - if(!attribute) continue; - if(attribute.includes('warnings')) { + for (const attribute of [ + entity.entity_id, + entity.attributes.friendly_name?.toLocaleLowerCase(), + ]) { + if (!attribute) continue; + if (attribute.includes('warnings')) { return EnvCanadaEntityType.Warning; - } - else if(attribute.includes('watches')) { + } else if (attribute.includes('watches')) { return EnvCanadaEntityType.Watch; - } - else if(attribute.includes('statements')) { + } else if (attribute.includes('statements')) { return EnvCanadaEntityType.Statement; - } - else if(attribute.includes('advisory')) { + } else if (attribute.includes('advisory')) { return EnvCanadaEntityType.Advisory; } } @@ -283,16 +282,13 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { } private getLevelFromType(type: EnvCanadaEntityType): MeteoalarmLevelType { - if(type == EnvCanadaEntityType.Warning) { + if (type == EnvCanadaEntityType.Warning) { return MeteoalarmLevelType.Red; - } - else if(type == EnvCanadaEntityType.Watch) { + } else if (type == EnvCanadaEntityType.Watch) { return MeteoalarmLevelType.Orange; - } - else if(type == EnvCanadaEntityType.Statement || type == EnvCanadaEntityType.Advisory) { + } else if (type == EnvCanadaEntityType.Statement || type == EnvCanadaEntityType.Advisory) { return MeteoalarmLevelType.Yellow; - } - else { + } else { // This should never happen but is required in order for TS to stop complaining return MeteoalarmLevelType.Red; } diff --git a/src/integrations/integrations.ts b/src/integrations/integrations.ts index 0e89197..71dc3c3 100644 --- a/src/integrations/integrations.ts +++ b/src/integrations/integrations.ts @@ -13,7 +13,7 @@ const INTEGRATIONS = [ NINA, EnvironmentCanada, BurzeDzisNet, - Weatheralerts + Weatheralerts, ]; export default INTEGRATIONS; diff --git a/src/integrations/meteoalarm.ts b/src/integrations/meteoalarm.ts index 8d1a0bc..bac8658 100644 --- a/src/integrations/meteoalarm.ts +++ b/src/integrations/meteoalarm.ts @@ -5,7 +5,7 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; import { Utils } from '../utils'; @@ -15,16 +15,16 @@ type MeteoalarmEntity = HassEntity & { // Only awareness_level and awareness_type: https://github.com/MrBartusek/MeteoalarmCard/issues/49 // awareness_level and awareness_type not present: https://github.com/MrBartusek/MeteoalarmCard/issues/48 // code should except that everything or nothing will be there - awareness_level?: string, - awareness_type?: string, - event?: string, - severity?: string, - headline?: string, - description?: string, - icon: string, - attribution: string - } -} + awareness_level?: string; + awareness_type?: string; + event?: string; + severity?: string; + headline?: string; + description?: string; + icon: string; + attribution: string; + }; +}; export default class Meteoalarm implements MeteoalarmIntegration { public get metadata(): MeteoalarmIntegrationMetadata { @@ -35,7 +35,7 @@ export default class Meteoalarm implements MeteoalarmIntegration { returnHeadline: true, returnMultipleAlerts: false, entitiesCount: 1, - monitoredConditions: this.eventTypes + monitoredConditions: this.eventTypes, }; } @@ -61,7 +61,7 @@ export default class Meteoalarm implements MeteoalarmIntegration { MeteoalarmEventType.Avalanches, MeteoalarmEventType.Rain, MeteoalarmEventType.Flooding, - MeteoalarmEventType.Flooding + MeteoalarmEventType.Flooding, ]; } @@ -71,36 +71,38 @@ export default class Meteoalarm implements MeteoalarmIntegration { headline, severity, awareness_type: awarenessType, - awareness_level: awarenessLevel + awareness_level: awarenessLevel, } = entity.attributes; let event: MeteoalarmEventType | undefined; let level: MeteoalarmLevelType | undefined; - if(awarenessType != undefined) { + if (awarenessType != undefined) { event = this.eventTypes[Number(awarenessType.split(';')[0]) - 1]; } - if(awarenessLevel != undefined) { + if (awarenessLevel != undefined) { let levelID = Number(awarenessLevel.split(';')[0]); - if(levelID == 1) { + if (levelID == 1) { // Fallback for https://github.com/MrBartusek/MeteoalarmCard/issues/49 levelID = 2; } - level = levelID - 1 as MeteoalarmLevelType; + level = (levelID - 1) as MeteoalarmLevelType; } - if(level === undefined && severity !== undefined) { + if (level === undefined && severity !== undefined) { level = Utils.getLevelBySeverity(severity); } - if(level === undefined) { + if (level === undefined) { throw new Error('Failed to determine alert level. awareness_level nor severity are provided'); } - return [{ - headline: eventHeadline || headline, - level: level, - event: event || MeteoalarmEventType.Unknown - }]; + return [ + { + headline: eventHeadline || headline, + level: level, + event: event || MeteoalarmEventType.Unknown, + }, + ]; } } diff --git a/src/integrations/meteofrance.ts b/src/integrations/meteofrance.ts index 4c7b468..71c8821 100644 --- a/src/integrations/meteofrance.ts +++ b/src/integrations/meteofrance.ts @@ -5,43 +5,43 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; -const STATE_GREEN = 'Vert'; +const STATE_GREEN = 'Vert'; const STATE_YELLOW = 'Jaune'; const STATE_ORANGE = 'Orange'; -const STATE_RED = 'Rouge'; +const STATE_RED = 'Rouge'; // The list of event alert is located here // https://github.com/hacf-fr/meteofrance-api/blob/master/src/meteofrance_api/const.py -const EVENT_WIND = 'Vent violent'; -const EVENT_RAIN_FLOOD = 'Pluie-inondation'; -const EVENT_THUNDERSTORMS = 'Orages'; -const EVENT_FLOOD = 'Inondation'; -const EVENT_SNOW_ICE = 'Neige-verglas'; +const EVENT_WIND = 'Vent violent'; +const EVENT_RAIN_FLOOD = 'Pluie-inondation'; +const EVENT_THUNDERSTORMS = 'Orages'; +const EVENT_FLOOD = 'Inondation'; +const EVENT_SNOW_ICE = 'Neige-verglas'; const EVENT_HIGH_TEMPERATURE = 'Canicule'; -const EVENT_LOW_TEMPERATURE = 'Grand-froid'; -const EVENT_AVALANCHES = 'Avalanches'; -const EVENT_COASTAL = 'Vagues-submersion'; +const EVENT_LOW_TEMPERATURE = 'Grand-froid'; +const EVENT_AVALANCHES = 'Avalanches'; +const EVENT_COASTAL = 'Vagues-submersion'; const EVENT_TYPES = { - [EVENT_WIND]: MeteoalarmEventType.Wind, - [EVENT_RAIN_FLOOD]: MeteoalarmEventType.Flooding, - [EVENT_THUNDERSTORMS]: MeteoalarmEventType.Thunderstorms, - [EVENT_FLOOD]: MeteoalarmEventType.Flooding, - [EVENT_SNOW_ICE]: MeteoalarmEventType.SnowIce, + [EVENT_WIND]: MeteoalarmEventType.Wind, + [EVENT_RAIN_FLOOD]: MeteoalarmEventType.Flooding, + [EVENT_THUNDERSTORMS]: MeteoalarmEventType.Thunderstorms, + [EVENT_FLOOD]: MeteoalarmEventType.Flooding, + [EVENT_SNOW_ICE]: MeteoalarmEventType.SnowIce, [EVENT_HIGH_TEMPERATURE]: MeteoalarmEventType.HighTemperature, - [EVENT_LOW_TEMPERATURE]: MeteoalarmEventType.LowTemperature, - [EVENT_AVALANCHES]: MeteoalarmEventType.Avalanches, - [EVENT_COASTAL]: MeteoalarmEventType.CoastalEvent + [EVENT_LOW_TEMPERATURE]: MeteoalarmEventType.LowTemperature, + [EVENT_AVALANCHES]: MeteoalarmEventType.Avalanches, + [EVENT_COASTAL]: MeteoalarmEventType.CoastalEvent, }; const LEVEL_TYPES = { [STATE_YELLOW]: MeteoalarmLevelType.Yellow, [STATE_ORANGE]: MeteoalarmLevelType.Orange, - [STATE_RED]: MeteoalarmLevelType.Red + [STATE_RED]: MeteoalarmLevelType.Red, }; export default class MeteoFrance implements MeteoalarmIntegration { @@ -60,15 +60,18 @@ export default class MeteoFrance implements MeteoalarmIntegration { MeteoalarmEventType.Flooding, MeteoalarmEventType.SnowIce, MeteoalarmEventType.HighTemperature, - MeteoalarmEventType.LowTemperature, + MeteoalarmEventType.LowTemperature, MeteoalarmEventType.Avalanches, - MeteoalarmEventType.CoastalEvent - ] + MeteoalarmEventType.CoastalEvent, + ], }; } public supports(entity: HassEntity): boolean { - return entity.attributes.attribution == 'Data provided by Météo-France' && entity.attributes[EVENT_WIND] != undefined; + return ( + entity.attributes.attribution == 'Data provided by Météo-France' && + entity.attributes[EVENT_WIND] != undefined + ); } public alertActive(entity: HassEntity): boolean { @@ -78,13 +81,13 @@ export default class MeteoFrance implements MeteoalarmIntegration { public getAlerts(entity: HassEntity): MeteoalarmAlert[] { const result: MeteoalarmAlert[] = []; - for(const [eventName, event] of Object.entries(EVENT_TYPES)) { + for (const [eventName, event] of Object.entries(EVENT_TYPES)) { const eventLevel = entity.attributes[eventName]; - if(!eventLevel) continue; - if(eventLevel === STATE_GREEN) continue; + if (!eventLevel) continue; + if (eventLevel === STATE_GREEN) continue; result.push({ level: LEVEL_TYPES[eventLevel], - event: event + event: event, }); } return result; diff --git a/src/integrations/nina.ts b/src/integrations/nina.ts index 37db358..4235009 100644 --- a/src/integrations/nina.ts +++ b/src/integrations/nina.ts @@ -5,16 +5,16 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; import { Utils } from '../utils'; type NinaEntity = HassEntity & { attributes: { - headline: string, - severity: string, - } -} + headline: string; + severity: string; + }; +}; export default class NINA implements MeteoalarmIntegration { public get metadata(): MeteoalarmIntegrationMetadata { @@ -25,9 +25,7 @@ export default class NINA implements MeteoalarmIntegration { returnHeadline: true, returnMultipleAlerts: true, entitiesCount: 0, - monitoredConditions: [ - MeteoalarmEventType.Unknown - ] + monitoredConditions: [MeteoalarmEventType.Unknown], }; } @@ -43,13 +41,14 @@ export default class NINA implements MeteoalarmIntegration { public getAlerts(entity: NinaEntity): MeteoalarmAlert[] { const { severity, headline } = entity.attributes; - return [{ - event: MeteoalarmEventType.Unknown, - headline: headline, - level: Utils.getLevelBySeverity( - severity, { - 'Moderate': MeteoalarmLevelType.Orange - }) - }]; + return [ + { + event: MeteoalarmEventType.Unknown, + headline: headline, + level: Utils.getLevelBySeverity(severity, { + Moderate: MeteoalarmLevelType.Orange, + }), + }, + ]; } } diff --git a/src/integrations/weatheralerts.ts b/src/integrations/weatheralerts.ts index b0fe980..d12c969 100644 --- a/src/integrations/weatheralerts.ts +++ b/src/integrations/weatheralerts.ts @@ -5,22 +5,22 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata, - MeteoalarmLevelType + MeteoalarmLevelType, } from '../types'; import { Utils } from '../utils'; type WeatheralertsAlert = { - event: string, - severity: string, - title: string -} + event: string; + severity: string; + title: string; +}; type WeatheralertsEntity = HassEntity & { attributes: { - integration: string, - alerts: WeatheralertsAlert[] - } -} + integration: string; + alerts: WeatheralertsAlert[]; + }; +}; export default class Weatheralerts implements MeteoalarmIntegration { public get metadata(): MeteoalarmIntegrationMetadata { @@ -31,7 +31,7 @@ export default class Weatheralerts implements MeteoalarmIntegration { returnHeadline: true, returnMultipleAlerts: true, entitiesCount: 1, - monitoredConditions: Utils.convertEventTypesForMetadata(this.eventTypes) + monitoredConditions: Utils.convertEventTypesForMetadata(this.eventTypes), }; } @@ -47,51 +47,51 @@ export default class Weatheralerts implements MeteoalarmIntegration { // Event types from: https://www.weather.gov/lwx/WarningsDefined return { 'Winter Storm': MeteoalarmEventType.SnowIce, - 'Blizzard': MeteoalarmEventType.SnowIce, + Blizzard: MeteoalarmEventType.SnowIce, 'Ice Storm': MeteoalarmEventType.SnowIce, 'Winter Weather': MeteoalarmEventType.SnowIce, - 'Freeze': MeteoalarmEventType.LowTemperature, - 'Frost': MeteoalarmEventType.LowTemperature, + Freeze: MeteoalarmEventType.LowTemperature, + Frost: MeteoalarmEventType.LowTemperature, 'Wind Chill': MeteoalarmEventType.LowTemperature, 'Fire Weather': MeteoalarmEventType.ForestFire, 'Red Flag': MeteoalarmEventType.ForestFire, 'Dense Fog': MeteoalarmEventType.Fog, 'High Wind': MeteoalarmEventType.Wind, - 'Wind': MeteoalarmEventType.Wind, + Wind: MeteoalarmEventType.Wind, 'Severe Thunderstorm': MeteoalarmEventType.Thunderstorms, - 'Tornado': MeteoalarmEventType.Tornado, + Tornado: MeteoalarmEventType.Tornado, 'Extreme Wind': MeteoalarmEventType.Wind, 'Small Craft': MeteoalarmEventType.Wind, - 'Gale': MeteoalarmEventType.SeaEvent, - 'Storm': MeteoalarmEventType.Thunderstorms, + Gale: MeteoalarmEventType.SeaEvent, + Storm: MeteoalarmEventType.Thunderstorms, 'Hurricane Force Wind': MeteoalarmEventType.Hurricane, 'Special Marine': MeteoalarmEventType.Unknown, 'Coastal Flood': MeteoalarmEventType.Flooding, 'Flash Flood': MeteoalarmEventType.Flooding, - 'Flood': MeteoalarmEventType.Flooding, + Flood: MeteoalarmEventType.Flooding, 'River Flood': MeteoalarmEventType.Flooding, 'Excessive Heat': MeteoalarmEventType.HighTemperature, - 'Heat': MeteoalarmEventType.HighTemperature, + Heat: MeteoalarmEventType.HighTemperature, 'Tropical Storm': MeteoalarmEventType.Hurricane, - 'Hurricane': MeteoalarmEventType.Hurricane, + Hurricane: MeteoalarmEventType.Hurricane, 'Air Quality': MeteoalarmEventType.AirQuality, 'Rip Current': MeteoalarmEventType.CoastalEvent, 'Special Weather': MeteoalarmEventType.Unknown, 'High Surf': MeteoalarmEventType.CoastalEvent, 'Hazardous Seas': MeteoalarmEventType.SeaEvent, 'Beach Hazard': MeteoalarmEventType.CoastalEvent, - 'Blowing Dust': MeteoalarmEventType.Dust + 'Blowing Dust': MeteoalarmEventType.Dust, }; } private get eventLevels(): { [key: string]: MeteoalarmLevelType } { // Event types from: https://www.weather.gov/lwx/WarningsDefined return { - 'Warning': MeteoalarmLevelType.Red, - 'Statement': MeteoalarmLevelType.Orange, - 'Watch': MeteoalarmLevelType.Orange, - 'Advisory': MeteoalarmLevelType.Yellow, - 'Alert': MeteoalarmLevelType.Yellow + Warning: MeteoalarmLevelType.Red, + Statement: MeteoalarmLevelType.Orange, + Watch: MeteoalarmLevelType.Orange, + Advisory: MeteoalarmLevelType.Yellow, + Alert: MeteoalarmLevelType.Yellow, }; } @@ -100,29 +100,29 @@ export default class Weatheralerts implements MeteoalarmIntegration { const result: MeteoalarmAlert[] = []; - for(const alert of alerts) { + for (const alert of alerts) { const fullAlertName = alert.event; let alertLevel: MeteoalarmLevelType | undefined = undefined; let alertType: MeteoalarmEventType | undefined = undefined; - for(const [levelName, level] of Object.entries(this.eventLevels)) { - if(!fullAlertName.includes(levelName)) continue; + for (const [levelName, level] of Object.entries(this.eventLevels)) { + if (!fullAlertName.includes(levelName)) continue; alertLevel = level; const alertName = fullAlertName.replace(levelName, '').trim(); alertType = this.eventTypes[alertName]; - if(alertType == undefined) { + if (alertType == undefined) { throw Error(`Unknown weatheralerts alert type: ${alertName}`); } } - if(alertLevel == undefined) { + if (alertLevel == undefined) { throw Error(`Unknown weatheralerts alert level: ${fullAlertName}`); } result.push({ headline: fullAlertName, level: alertLevel, - event: alertType! + event: alertType!, }); } return result; diff --git a/src/localize/localize.ts b/src/localize/localize.ts index efecda9..94401c6 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -27,42 +27,46 @@ const languages: any = { sv: sv, cs: cs, pt: pt, - bg: bg + bg: bg, }; export function localize(string: string): string { - if(string.toLocaleLowerCase() != string) { + if (string.toLocaleLowerCase() != string) { // eslint-disable-next-line no-console console.warn(`MeteoalarmCard: Received invalid translation key: ${string}`); } string = string.toLocaleLowerCase(); let storedLang = localStorage.getItem('selectedLanguage'); - if(storedLang === 'null') { + if (storedLang === 'null') { storedLang = null; } - const lang = (storedLang || navigator.language.split('-')[0] || 'en').replace(/['"]+/g, '').replace('-', '_'); + const lang = (storedLang || navigator.language.split('-')[0] || 'en') + .replace(/['"]+/g, '') + .replace('-', '_'); let translated: string | undefined = undefined; // Try using specified language try { translated = string.split('.').reduce((o, i) => o[i], languages[lang]); - } - catch (e) { + } catch (e) { // eslint-disable-next-line no-console - console.warn(`MeteoalarmCard: Translation for "${string}" is not specified in "${lang}" language.`); + console.warn( + `MeteoalarmCard: Translation for "${string}" is not specified in "${lang}" language.`, + ); } // Try using english if (translated == undefined) { try { translated = string.split('.').reduce((o, i) => o[i], languages['en']); - } - catch (e) { + } catch (e) { // eslint-disable-next-line no-console - console.warn(`MeteoalarmCard: Translation for "${string}" is not specified in fallback english language.`); + console.warn( + `MeteoalarmCard: Translation for "${string}" is not specified in fallback english language.`, + ); } } // Fall back to string - if(translated == undefined) translated = string; + if (translated == undefined) translated = string; return translated; } diff --git a/src/measure-text.ts b/src/measure-text.ts index b2ee48d..f279a23 100644 --- a/src/measure-text.ts +++ b/src/measure-text.ts @@ -1,11 +1,11 @@ /** - * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. - * - * @param {String} text The text to be rendered. - * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). - * - * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 - */ + * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. + * + * @param {String} text The text to be rendered. + * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). + * + * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 + */ export function getTextWidth(text: string, font: string): number { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d')!; diff --git a/src/meteoalarm-card.ts b/src/meteoalarm-card.ts index 6cd2e61..c3365c9 100644 --- a/src/meteoalarm-card.ts +++ b/src/meteoalarm-card.ts @@ -1,6 +1,13 @@ import { - ActionHandlerEvent, debounce, EntityConfig, handleAction, hasAction, hasConfigOrEntityChanged, - HomeAssistant, LovelaceCardConfig, LovelaceCardEditor + ActionHandlerEvent, + debounce, + EntityConfig, + handleAction, + hasAction, + hasConfigOrEntityChanged, + HomeAssistant, + LovelaceCardConfig, + LovelaceCardEditor, } from 'custom-card-helpers'; import { HassEntity } from 'home-assistant-js-websocket'; import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from 'lit'; @@ -17,13 +24,18 @@ import INTEGRATIONS from './integrations/integrations'; import { localize } from './localize/localize'; import { getCanvasFont, getTextWidth } from './measure-text'; import styles from './styles'; -import { MeteoalarmCardConfig, MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmScalingMode } from './types'; +import { + MeteoalarmCardConfig, + MeteoalarmIntegration, + MeteoalarmIntegrationEntityType, + MeteoalarmScalingMode, +} from './types'; // eslint-disable-next-line no-console console.info( `%c MeteoalarmCard %c ${CARD_VERSION} `, 'color: white; font-weight: bold; background: #1c1c1c', - 'color: white; font-weight: bold; background: #db4437' + 'color: white; font-weight: bold; background: #db4437', ); // Push card into UI card picker @@ -32,12 +44,11 @@ console.info( preview: true, type: 'meteoalarm-card', name: localize('common.name'), - description: localize('common.description') + description: localize('common.description'), }); @customElement('meteoalarm-card') export class MeteoalarmCard extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; @state() private config!: MeteoalarmCardConfig; @@ -51,7 +62,7 @@ export class MeteoalarmCard extends LitElement { private currentEntity?: string; static get integrations(): MeteoalarmIntegration[] { - return INTEGRATIONS.map(i => new i()); + return INTEGRATIONS.map((i) => new i()); } public static async getConfigElement(): Promise { @@ -63,53 +74,49 @@ export class MeteoalarmCard extends LitElement { // Find fist entity that is supported by any integration const ALLOWED_INTEGRATION_TYPES = [ MeteoalarmIntegrationEntityType.SingleEntity, - MeteoalarmIntegrationEntityType.CurrentExpected - ]; + MeteoalarmIntegrationEntityType.CurrentExpected, + ]; - for(const entity of entities) { + for (const entity of entities) { const integrations = MeteoalarmCard.integrations.filter((x) => - ALLOWED_INTEGRATION_TYPES.includes(x.metadata.type) + ALLOWED_INTEGRATION_TYPES.includes(x.metadata.type), ); - for(const integration of integrations) { - if(integration.supports(hass.states[entity])) { + for (const integration of integrations) { + if (integration.supports(hass.states[entity])) { return { entities: { entity }, - integration: integration.metadata.key + integration: integration.metadata.key, }; } } } return { entities: '', - integration: '' + integration: '', }; } public setConfig(config: LovelaceCardConfig): void { if (!config) { throw new Error(localize('common.invalid_configuration')); - } - else if ( + } else if ( config.entities == undefined || (Array.isArray(config.entities) && config.entities.length == 0) || - (Array.isArray(config.entities) && config.entities.every(e => e == null))) { + (Array.isArray(config.entities) && config.entities.every((e) => e == null)) + ) { throw new Error(localize('error.missing_entity')); - } - else if (config.integration == undefined) { + } else if (config.integration == undefined) { throw new Error(localize('error.invalid_integration')); } this.config = { name: 'Meteoalarm', - ...config + ...config, }; } static get styles(): CSSResultGroup { - return [ - swiperStyles, - styles - ]; + return [swiperStyles, styles]; } public getCardSize(): number { @@ -124,13 +131,13 @@ export class MeteoalarmCard extends LitElement { this.measureCard(); this.attachObserver(); const swiper = (this.renderRoot as ShadowRoot).getElementById('swiper'); - if(!swiper) return; + if (!swiper) return; this.swiper = new Swiper(swiper, { modules: [Pagination], pagination: { - el: swiper.getElementsByClassName('swiper-pagination')[0] as HTMLElement + el: swiper.getElementsByClassName('swiper-pagination')[0] as HTMLElement, }, - observer: true + observer: true, }); this.swiper.on('transitionEnd', () => { this.updateCurrentEntity(); @@ -148,9 +155,7 @@ export class MeteoalarmCard extends LitElement { private attachObserver() { if (!this.resizeObserver) { - this.resizeObserver = new ResizeObserver( - debounce(() => this.measureCard(), 250, false) - ); + this.resizeObserver = new ResizeObserver(debounce(() => this.measureCard(), 250, false)); } const card = this.shadowRoot!.querySelector('ha-card'); if (!card) return; @@ -161,27 +166,33 @@ export class MeteoalarmCard extends LitElement { const regular = container.querySelector('.headline-regular') as HTMLElement; const narrow = container.querySelector('.headline-narrow') as HTMLElement; const veryNarrow = container.querySelector('.headline-verynarrow') as HTMLElement; - return [ regular, narrow, veryNarrow ]; + return [regular, narrow, veryNarrow]; } private measureCard() { if (!this.isConnected) return; const card = this.shadowRoot!.querySelector('ha-card'); if (!card) return; - if(this.scalingMode == MeteoalarmScalingMode.Disabled) return; - - const scaleHeadline = [MeteoalarmScalingMode.Scale, MeteoalarmScalingMode.HeadlineAndScale].includes(this.scalingMode); - const swapHeadline = [MeteoalarmScalingMode.Headline, MeteoalarmScalingMode.HeadlineAndScale].includes(this.scalingMode); + if (this.scalingMode == MeteoalarmScalingMode.Disabled) return; + + const scaleHeadline = [ + MeteoalarmScalingMode.Scale, + MeteoalarmScalingMode.HeadlineAndScale, + ].includes(this.scalingMode); + const swapHeadline = [ + MeteoalarmScalingMode.Headline, + MeteoalarmScalingMode.HeadlineAndScale, + ].includes(this.scalingMode); const MAX_FONT_SIZE = 22; const MIN_FONT_SIZE = 17; // Scale headlines of each swiper card const swiper = card.querySelector('.swiper-wrapper'); const slides = swiper?.getElementsByClassName('swiper-slide') as HTMLCollectionOf; - for(const slide of slides) { - const [ regular, narrow, veryNarrow ] = this.getHeadlineElements(slide); - const sizes: [string, HTMLElement][]= [['regular', regular]]; - if(swapHeadline) { + for (const slide of slides) { + const [regular, narrow, veryNarrow] = this.getHeadlineElements(slide); + const sizes: [string, HTMLElement][] = [['regular', regular]]; + if (swapHeadline) { sizes.push(['narrow', narrow]); sizes.push(['veryNarrow', veryNarrow]); } @@ -189,12 +200,15 @@ export class MeteoalarmCard extends LitElement { this.setCardScaling(slide, 'regular', MAX_FONT_SIZE); let isSizeSet = false; - for(const [ size, element ] of sizes) { - if(isSizeSet) break; + for (const [size, element] of sizes) { + if (isSizeSet) break; const minFontSize = scaleHeadline ? MIN_FONT_SIZE : MAX_FONT_SIZE; for (let fontSize = MAX_FONT_SIZE; fontSize >= minFontSize; fontSize--) { - const elementSize = getTextWidth(element.textContent!, getCanvasFont(regular, fontSize + 'px')); - if(elementSize <= regular.clientWidth) { + const elementSize = getTextWidth( + element.textContent!, + getCanvasFont(regular, fontSize + 'px'), + ); + if (elementSize <= regular.clientWidth) { this.setCardScaling(slide, size as any, fontSize); isSizeSet = true; break; @@ -203,40 +217,39 @@ export class MeteoalarmCard extends LitElement { } // Fallback if measuring couldn't fit the text - if(!isSizeSet) { - if(swapHeadline) { + if (!isSizeSet) { + if (swapHeadline) { this.setCardScaling(slide, 'icon', MAX_FONT_SIZE); - } - else { + } else { this.setCardScaling(slide, 'regular' as any, MIN_FONT_SIZE); } - } } } - private setCardScaling(container: HTMLElement, scale: 'regular' | 'narrow' | 'veryNarrow' | 'icon', fontSize: number) { - const [ regular, narrow, veryNarrow ] = this.getHeadlineElements(container); + private setCardScaling( + container: HTMLElement, + scale: 'regular' | 'narrow' | 'veryNarrow' | 'icon', + fontSize: number, + ) { + const [regular, narrow, veryNarrow] = this.getHeadlineElements(container); - if(scale == 'regular') { + if (scale == 'regular') { regular.style.fontSize = `${fontSize}px`; regular.style.display = 'block'; narrow.style.display = 'none'; veryNarrow.style.display = 'none'; - } - else if(scale == 'narrow') { + } else if (scale == 'narrow') { narrow.style.fontSize = `${fontSize}px`; regular.style.display = 'none'; narrow.style.display = 'block'; veryNarrow.style.display = 'none'; - } - else if(scale == 'veryNarrow') { + } else if (scale == 'veryNarrow') { veryNarrow.style.fontSize = `${fontSize}px`; regular.style.display = 'none'; narrow.style.display = 'none'; veryNarrow.style.display = 'block'; - } - else if(scale == 'icon') { + } else if (scale == 'icon') { regular.style.display = 'none'; narrow.style.display = 'none'; veryNarrow.style.display = 'none'; @@ -245,12 +258,14 @@ export class MeteoalarmCard extends LitElement { private get entities(): HassEntity[] { const entities: EntityConfig[] = processConfigEntities(this.config.entities!); - return entities.map(e => this.hass.states[e.entity]); + return entities.map((e) => this.hass.states[e.entity]); } private get integration(): MeteoalarmIntegration { - const integration = MeteoalarmCard.integrations.find(i => i.metadata.key === this.config.integration); - if(integration === undefined) { + const integration = MeteoalarmCard.integrations.find( + (i) => i.metadata.key === this.config.integration, + ); + if (integration === undefined) { throw new Error(localize('error.invalid_integration')); } return integration!; @@ -258,8 +273,8 @@ export class MeteoalarmCard extends LitElement { private get scalingMode(): MeteoalarmScalingMode { const modeString = this.config.scaling_mode; - if(!modeString) return MeteoalarmScalingMode.HeadlineAndScale; - if(!(Object.values(MeteoalarmScalingMode)).includes(modeString as any)) { + if (!modeString) return MeteoalarmScalingMode.HeadlineAndScale; + if (!Object.values(MeteoalarmScalingMode).includes(modeString as any)) { throw new Error('MeteoalarmCard: ' + localize('error.invalid_scaling_mode')); } return modeString as MeteoalarmScalingMode; @@ -274,13 +289,15 @@ export class MeteoalarmCard extends LitElement { this.config.override_headline, this.config.hide_caption, this.config.ignored_levels, - this.config.ignored_events + this.config.ignored_events, ); // Handle hide_when_no_warning - if(events.every(e => !e.isActive) && this.config.hide_when_no_warning) { + if (events.every((e) => !e.isActive) && this.config.hide_when_no_warning) { // eslint-disable-next-line no-console - console.log('MeteoalarmCard: Card is hidden - hide_when_no_warning is enabled and there are no warnings'); + console.log( + 'MeteoalarmCard: Card is hidden - hide_when_no_warning is enabled and there are no warnings', + ); this.setCardMargin(false); return html``; } @@ -292,38 +309,45 @@ export class MeteoalarmCard extends LitElement { @action=${this.handleAction} .actionHandler=${actionHandler({ hasHold: hasAction(this.config.hold_action), - hasDoubleClick: hasAction(this.config.double_tap_action) + hasDoubleClick: hasAction(this.config.double_tap_action), })} tabindex="0" >
-
+
- ${events.map((event => html` -
-
- ${this.renderMainIcon(event.icon)} - ${this.renderHeadlines(event.headlines)} -
- ${event.caption && event.captionIcon ? html` -
- ${this.renderCaption(event.captionIcon, event.caption)} + ${events.map( + (event) => html` +
+
+ ${this.renderMainIcon(event.icon)} ${this.renderHeadlines(event.headlines)}
- ` : ''} -
- `))} + ${event.caption && event.captionIcon + ? html` +
+ ${this.renderCaption(event.captionIcon, event.caption)} +
+ ` + : ''} +
+ `, + )}
`; - } - catch(error) { + } catch (error) { // eslint-disable-next-line no-console console.error('[METEOALARM CARD ERROR]\nReport issue: https://bit.ly/3hK1hL4 \n\n', error); return this.showError(error as string); @@ -331,7 +355,10 @@ export class MeteoalarmCard extends LitElement { } private renderMainIcon(icon: string): TemplateResult { - return html``; + return html``; } // Transfer array of one, two or three headlines in descending length @@ -339,26 +366,24 @@ export class MeteoalarmCard extends LitElement { // card width (screen size) by resize observer private renderHeadlines(headlines: string[]): TemplateResult { // TODO: Fix this array mess - let regular = '', narrow = '', verynarrow = ''; - if(headlines.length == 0) { + let regular = '', + narrow = '', + verynarrow = ''; + if (headlines.length == 0) { throw new Error('headlines array length is 0'); - } - else if(headlines.length == 1) { + } else if (headlines.length == 1) { regular = headlines[0]; narrow = headlines[0]; verynarrow = headlines[0]; - } - else if(headlines.length == 2) { + } else if (headlines.length == 2) { regular = headlines[0]; narrow = headlines[1]; verynarrow = headlines[1]; - } - else if(headlines.length == 3) { + } else if (headlines.length == 3) { regular = headlines[0]; narrow = headlines[1]; verynarrow = headlines[2]; - } - else if(headlines.length > 3) { + } else if (headlines.length > 3) { throw new Error('headlines array length is higher than 3'); } @@ -372,13 +397,16 @@ export class MeteoalarmCard extends LitElement { private renderCaption(icon: string, caption: string): TemplateResult { return html` ${caption} - + `; } private setCardMargin(showMargin: boolean): void { const container = this.shadowRoot?.host as HTMLElement; - if(!container) return; + if (!container) return; container.style.margin = showMargin ? '' : '0px'; } @@ -387,7 +415,7 @@ export class MeteoalarmCard extends LitElement { errorCard.setConfig({ type: 'error', error, - origConfig: this.config + origConfig: this.config, }); return html` ${errorCard} `; @@ -396,7 +424,7 @@ export class MeteoalarmCard extends LitElement { private handleAction(ev: ActionHandlerEvent): void { const config = { ...this.config, - entity: this.currentEntity + entity: this.currentEntity, }; if (this.hass && this.config && ev.detail.action) { handleAction(this, this.hass, config, ev.detail.action); diff --git a/src/predefined-cards.ts b/src/predefined-cards.ts index ebbbfc4..d8ebcb9 100644 --- a/src/predefined-cards.ts +++ b/src/predefined-cards.ts @@ -10,10 +10,7 @@ export class PredefinedCards { entity: undefined, icon: 'cloud-question', color: MeteoalarmData.getLevel(MeteoalarmLevelType.None).color, - headlines: [ - localize('common.unavailable.long'), - localize('common.unavailable.short') - ] + headlines: [localize('common.unavailable.long'), localize('common.unavailable.short')], }; } @@ -23,10 +20,7 @@ export class PredefinedCards { entity: entity, icon: 'shield-outline', color: MeteoalarmData.getLevel(MeteoalarmLevelType.None).color, - headlines: [ - localize('events.no_warnings') - ] + headlines: [localize('events.no_warnings')], }; } } - diff --git a/src/styles.ts b/src/styles.ts index 4123ce1..39abb17 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -1,101 +1,94 @@ import { css } from 'lit'; export default css` - :host - { - display: flex; - flex: 1; - flex-direction: column; - } + :host { + display: flex; + flex: 1; + flex-direction: column; + } - ha-card - { - flex-direction: column; - flex: 1; - position: relative; - padding: 0px; - border-radius: var(--ha-card-border-radius, 12px); - box-shadow: var( - --ha-card-box-shadow, - 0px 2px 1px -1px rgba(0, 0, 0, 0.2), - 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 1px 3px 0px rgba(0, 0, 0, 0.12)); - overflow: hidden; - transition: all 0.3s ease-out 0s; - } + ha-card { + flex-direction: column; + flex: 1; + position: relative; + padding: 0px; + border-radius: var(--ha-card-border-radius, 12px); + box-shadow: var( + --ha-card-box-shadow, + 0px 2px 1px -1px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 1px 3px 0px rgba(0, 0, 0, 0.12) + ); + overflow: hidden; + transition: all 0.3s ease-out 0s; + } - a - { - color: var(--secondary-text-color); - } + a { + color: var(--secondary-text-color); + } - .container - { - background: var(--card-background-color); - cursor: pointer; - overflow: hidden; - position: relative; - } + .container { + background: var(--card-background-color); + cursor: pointer; + overflow: hidden; + position: relative; + } - .content - { - display: flex; - padding: 36px 28px; - justify-content: center; - } + .content { + display: flex; + padding: 36px 28px; + justify-content: center; + } - .main-icon - { - --mdc-icon-size: 50px; - height: 50px; - flex: 0; - } + .main-icon { + --mdc-icon-size: 50px; + height: 50px; + flex: 0; + } - .headline - { - flex: 1; - font-size: 22px; - line-height: normal; - margin: auto; - margin-left: 18px; - text-align: center; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } + .headline { + flex: 1; + font-size: 22px; + line-height: normal; + margin: auto; + margin-left: 18px; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } - .caption { - top: 0; - right: 0; - position: absolute; - display: flex; - align-items: center; - margin: 10px 12px; - font-size: 13px; - line-height: normal; - } + .caption { + top: 0; + right: 0; + position: absolute; + display: flex; + align-items: center; + margin: 10px 12px; + font-size: 13px; + line-height: normal; + } - .caption-icon - { - --mdc-icon-size: 19px; - height: 19px; - flex: 0; - margin-left: 5px; - } + .caption-icon { + --mdc-icon-size: 19px; + height: 19px; + flex: 0; + margin-left: 5px; + } - .headline-narrow, .headline-verynarrow - { - display: none; - } + .headline-narrow, + .headline-verynarrow { + display: none; + } - .swiper { - --swiper-pagination-bullet-size: 5px; - } + .swiper { + --swiper-pagination-bullet-size: 5px; + } - .swiper-pagination-bullet { - background-color: #dfdfdf; - } - .swiper-pagination-bullet-active { - background-color: #ffffff; - } + .swiper-pagination-bullet { + background-color: #dfdfdf; + } + .swiper-pagination-bullet-active { + background-color: #ffffff; + } `; diff --git a/src/types.ts b/src/types.ts index 7df5d93..02d2c35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,131 +1,136 @@ -import { ActionConfig, EntityConfig, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } from 'custom-card-helpers'; +import { + ActionConfig, + EntityConfig, + LovelaceCard, + LovelaceCardConfig, + LovelaceCardEditor, +} from 'custom-card-helpers'; import { HassEntity } from 'home-assistant-js-websocket'; declare global { - interface HTMLElementTagNameMap { - 'meteoalarm-card-editor': LovelaceCardEditor; - 'hui-error-card': LovelaceCard; - } + interface HTMLElementTagNameMap { + 'meteoalarm-card-editor': LovelaceCardEditor; + 'hui-error-card': LovelaceCard; + } } export interface MeteoalarmCardConfig extends LovelaceCardConfig { - type: string; - entities?: string | string[] | EntityConfig[]; - integration?: string; - override_headline?: boolean; - hide_when_no_warning?: boolean; - hide_caption?: boolean; - disable_swiper?: boolean; - scaling_mode?: string; - ignored_events?: string[]; - ignored_levels?: string[]; + type: string; + entities?: string | string[] | EntityConfig[]; + integration?: string; + override_headline?: boolean; + hide_when_no_warning?: boolean; + hide_caption?: boolean; + disable_swiper?: boolean; + scaling_mode?: string; + ignored_events?: string[]; + ignored_levels?: string[]; - tap_action?: ActionConfig; - hold_action?: ActionConfig; - double_tap_action?: ActionConfig; + tap_action?: ActionConfig; + hold_action?: ActionConfig; + double_tap_action?: ActionConfig; } export interface MeteoalarmIntegration { - metadata: MeteoalarmIntegrationMetadata, - supports(entity: HassEntity): boolean, - alertActive(entity: HassEntity): boolean, - getAlerts(entity: HassEntity): MeteoalarmAlert[] | MeteoalarmAlert, + metadata: MeteoalarmIntegrationMetadata; + supports(entity: HassEntity): boolean; + alertActive(entity: HassEntity): boolean; + getAlerts(entity: HassEntity): MeteoalarmAlert[] | MeteoalarmAlert; } export interface MeteoalarmIntegrationMetadata { - key: string, - name: string, - type: MeteoalarmIntegrationEntityType, - entitiesCount: number - returnHeadline: boolean, - returnMultipleAlerts: boolean, - monitoredConditions: MeteoalarmEventType[] + key: string; + name: string; + type: MeteoalarmIntegrationEntityType; + entitiesCount: number; + returnHeadline: boolean; + returnMultipleAlerts: boolean; + monitoredConditions: MeteoalarmEventType[]; } export enum MeteoalarmIntegrationEntityType { - // Alerts in this integrations all all in attributes of single entity - SingleEntity = 0, - // Alerts in this integration are split across two entities - // one contains current warnings and another future warnings - CurrentExpected = 1, - // Alerts in this integration are split across multiple (probably unlimited amount) of entities - // each one contains one warning - Slots = 2, - // Alerts in this integration are split across exactly 4 entities: warnings, watches, statements, advisories - WarningWatchStatementAdvisory = 3, - // Alerts in this integration are split across multiple entities, count is strictly specified - // Each warning is dedicated for one entity kind - SeparateEvents = 4 + // Alerts in this integrations all all in attributes of single entity + SingleEntity = 0, + // Alerts in this integration are split across two entities + // one contains current warnings and another future warnings + CurrentExpected = 1, + // Alerts in this integration are split across multiple (probably unlimited amount) of entities + // each one contains one warning + Slots = 2, + // Alerts in this integration are split across exactly 4 entities: warnings, watches, statements, advisories + WarningWatchStatementAdvisory = 3, + // Alerts in this integration are split across multiple entities, count is strictly specified + // Each warning is dedicated for one entity kind + SeparateEvents = 4, } /** * Is the alert currently active or will be active in the future * This is mostly used with type MeteoalarmIntegrationEntityType.CurrentExpected */ export enum MeteoalarmAlertKind { - - Current = 0, - Expected = 1 + Current = 0, + Expected = 1, } export enum MeteoalarmScalingMode { - Disabled = 'disabled', - Headline = 'headline', - Scale = 'scale', - HeadlineAndScale = 'headline_and_scale' + Disabled = 'disabled', + Headline = 'headline', + Scale = 'scale', + HeadlineAndScale = 'headline_and_scale', } // Event returned by the integration export interface MeteoalarmAlert { - event: MeteoalarmEventType, - level: MeteoalarmLevelType, - headline?: string, - kind?: MeteoalarmAlertKind, - _entity?: HassEntity + event: MeteoalarmEventType; + level: MeteoalarmLevelType; + headline?: string; + kind?: MeteoalarmAlertKind; + _entity?: HassEntity; } /** * Event transformed from MeteoalarmEvent used for rendering card */ export interface MeteoalarmAlertParsed { - /** - * Should this alert be shown when hide_when_no_warnings is enabled - */ - isActive: boolean, - icon: string, - color: string, - headlines: string[], - captionIcon?: string, - caption?: string, - entity?: HassEntity, + /** + * Should this alert be shown when hide_when_no_warnings is enabled + */ + isActive: boolean; + icon: string; + color: string; + headlines: string[]; + captionIcon?: string; + caption?: string; + entity?: HassEntity; } export enum MeteoalarmEventType { - Unknown, - Nuclear, - Hurricane, - Tornado, - CoastalEvent, - ForestFire, - Avalanches, - Earthquake, - Volcano, - Flooding, - SeaEvent, - Thunderstorms, - Rain, - SnowIce, - HighTemperature, - LowTemperature, - Wind, - Fog, - AirQuality, - Dust, - Tsunami + Unknown, + Nuclear, + Hurricane, + Tornado, + CoastalEvent, + ForestFire, + Avalanches, + Earthquake, + Volcano, + Flooding, + SeaEvent, + Thunderstorms, + Rain, + SnowIce, + HighTemperature, + LowTemperature, + Wind, + Fog, + AirQuality, + Dust, + Tsunami, } export enum MeteoalarmLevelType { - Red = 3, - Orange = 2, - Yellow = 1, - None = 0 + Red = 3, + Orange = 2, + Yellow = 1, + None = 0, } diff --git a/src/utils.ts b/src/utils.ts index d4141b6..fe17fef 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,10 +7,10 @@ export class Utils { */ public static minHAversion(minYear: number, minMonth: number): boolean { const rawVersion = (window as any).frontendVersion as string; - if(!rawVersion) return false; - const year = rawVersion.substring(0,4); - const version = rawVersion.substring(4,6); - return Number(year) >= minYear || (Number(year) >= minYear && Number(version) >= minMonth) ; + if (!rawVersion) return false; + const year = rawVersion.substring(0, 4); + const version = rawVersion.substring(4, 6); + return Number(year) >= minYear || (Number(year) >= minYear && Number(version) >= minMonth); } /** @@ -27,11 +27,14 @@ export class Utils { * For example `{ "Moderate": MeteoalarmLevelType.Orange }` * @returns */ - public static getLevelBySeverity(severity: string, overrides?: { [key: string]: MeteoalarmLevelType }): MeteoalarmLevelType { - if(overrides && overrides[severity]) { + public static getLevelBySeverity( + severity: string, + overrides?: { [key: string]: MeteoalarmLevelType }, + ): MeteoalarmLevelType { + if (overrides && overrides[severity]) { return overrides[severity]; } - switch(severity) { + switch (severity) { case 'Unknown': case 'Minor': case 'Moderate': @@ -51,7 +54,9 @@ export class Utils { * function convert this list for metadata.monitoredConditions * @param eventTypes eventTypes dict */ - public static convertEventTypesForMetadata(eventTypes: { [key: number | string]: MeteoalarmEventType }): MeteoalarmEventType[] { + public static convertEventTypesForMetadata(eventTypes: { + [key: number | string]: MeteoalarmEventType; + }): MeteoalarmEventType[] { return [...new Set(Object.values(eventTypes))]; } }