From d121c1cd18d80e58ec5de1e07cadf79b3e0b0e6e Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 14 Dec 2022 09:44:43 +0100 Subject: [PATCH 01/56] Classify binary sensor locks active state as alert (= red) (#14761) fixes undefined --- src/common/entity/color/binary_sensor_color.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/entity/color/binary_sensor_color.ts b/src/common/entity/color/binary_sensor_color.ts index f3458214fd..90f7247be4 100644 --- a/src/common/entity/color/binary_sensor_color.ts +++ b/src/common/entity/color/binary_sensor_color.ts @@ -6,6 +6,7 @@ const ALERTING_DEVICE_CLASSES = new Set([ "carbon_monoxide", "gas", "heat", + "lock", "moisture", "problem", "safety", From 614496d65ccb4637fef30844705d2ce482a03c40 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 14 Dec 2022 11:35:41 +0100 Subject: [PATCH 02/56] Add pulse animation for jammed state for lock (#14766) --- src/common/style/icon_color_css.ts | 7 ++++--- src/components/entity/state-badge.ts | 2 -- src/panels/lovelace/cards/hui-button-card.ts | 1 - src/panels/lovelace/cards/hui-entity-card.ts | 1 - 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/common/style/icon_color_css.ts b/src/common/style/icon_color_css.ts index fef56682b5..2b717c13cc 100644 --- a/src/common/style/icon_color_css.ts +++ b/src/common/style/icon_color_css.ts @@ -1,9 +1,10 @@ import { css } from "lit"; export const iconColorCSS = css` - ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="pending"], - ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="arming"], - ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="triggered"] { + ha-state-icon[data-domain="alarm_control_panel"][data-state="pending"], + ha-state-icon[data-domain="alarm_control_panel"][data-state="arming"], + ha-state-icon[data-domain="alarm_control_panel"][data-state="triggered"], + ha-state-icon[data-domain="lock"][data-state="jammed"] { animation: pulse 1s infinite; } diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts index abbcc52b73..972ea11ea7 100644 --- a/src/components/entity/state-badge.ts +++ b/src/components/entity/state-badge.ts @@ -59,11 +59,9 @@ export class StateBadge extends LitElement { } const domain = stateObj ? computeStateDomain(stateObj) : undefined; - const active = this._stateColor && stateObj ? stateActive(stateObj) : false; return html` Date: Thu, 15 Dec 2022 11:09:49 +0100 Subject: [PATCH 03/56] Check if area exists during default dashboard generation (#14767) --- .../common/generate-lovelace-config.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 12dd3fc537..07b99cb22d 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -47,6 +47,7 @@ interface SplittedByAreaDevice { } const splitByAreaDevice = ( + areaEntries: HomeAssistant["areas"], deviceEntries: HomeAssistant["devices"], entityEntries: HomeAssistant["entities"], entities: HassEntities @@ -55,25 +56,21 @@ const splitByAreaDevice = ( const areasWithEntities: SplittedByAreaDevice["areasWithEntities"] = {}; const devicesWithEntities: SplittedByAreaDevice["devicesWithEntities"] = {}; - const areaDevices = new Set( - Object.values(deviceEntries) - .filter((device) => device.area_id) - .map((device) => device.id) - ); for (const entity of Object.values(entityEntries)) { - if ( - (entity.area_id || - (entity.device_id && areaDevices.has(entity.device_id))) && - entity.entity_id in allEntities - ) { - const areaId = - entity.area_id || deviceEntries[entity.device_id!].area_id!; + const areaId = + entity.area_id || + (entity.device_id && deviceEntries[entity.device_id].area_id); + if (areaId && areaId in areaEntries && entity.entity_id in allEntities) { if (!(areaId in areasWithEntities)) { areasWithEntities[areaId] = []; } areasWithEntities[areaId].push(allEntities[entity.entity_id]); delete allEntities[entity.entity_id]; - } else if (entity.device_id && entity.entity_id in allEntities) { + } else if ( + entity.device_id && + entity.device_id in deviceEntries && + entity.entity_id in allEntities + ) { if (!(entity.device_id in devicesWithEntities)) { devicesWithEntities[entity.device_id] = []; } @@ -460,6 +457,7 @@ export const generateDefaultViewConfig = ( } const splittedByAreaDevice = splitByAreaDevice( + areaEntries, deviceEntries, entityEntries, states From 43ea175a1a7c12267bebddac20282765aa6fe2c3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 15 Dec 2022 16:13:20 +0100 Subject: [PATCH 04/56] Bumped version to 20221213.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5641dfcf15..3d836f6999 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221213.0" +version = "20221213.1" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 2575d35f2cd569ba042ba44acadff6c7037c9cdd Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 27 Dec 2022 21:36:08 +0100 Subject: [PATCH 05/56] Add aliases dialog to entity registry settings (#14860) --- demo/src/ha-demo.ts | 2 + gallery/src/pages/misc/integration-card.ts | 1 + src/data/entity_registry.ts | 2 + .../entity-aliases/dialog-entity-aliases.ts | 200 ++++++++++++++++++ .../show-dialog-entity-aliases.ts | 25 +++ .../entities/entity-registry-settings.ts | 60 +++++- .../config/entities/ha-config-entities.ts | 1 + src/translations/en.json | 13 ++ 8 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts create mode 100644 src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index 070fea8b44..37a707952a 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -71,6 +71,7 @@ class HaDemo extends HomeAssistantAppEl { entity_category: null, has_entity_name: false, unique_id: "co2_intensity", + aliases: [], }, { config_entry_id: "co2signal", @@ -86,6 +87,7 @@ class HaDemo extends HomeAssistantAppEl { entity_category: null, has_entity_name: false, unique_id: "grid_fossil_fuel_percentage", + aliases: [], }, ]); diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 56de4308e8..8bd6acf6c8 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -197,6 +197,7 @@ const createEntityRegistryEntries = ( platform: "updater", has_entity_name: false, unique_id: "updater", + aliases: [], }, ]; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index a80562cb41..44e1b3679c 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -22,6 +22,7 @@ export interface EntityRegistryEntry { original_name?: string; unique_id: string; translation_key?: string; + aliases: string[]; } export interface ExtEntityRegistryEntry extends EntityRegistryEntry { @@ -63,6 +64,7 @@ export interface EntityRegistryEntryUpdateParams { new_entity_id?: string; options_domain?: string; options?: SensorEntityOptions | NumberEntityOptions | WeatherEntityOptions; + aliases?: string[]; } export const findBatteryEntity = ( diff --git a/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts new file mode 100644 index 0000000000..29e3e012cc --- /dev/null +++ b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts @@ -0,0 +1,200 @@ +import "@material/mwc-button/mwc-button"; +import { mdiDeleteOutline, mdiPlus } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import "../../../../components/ha-alert"; +import "../../../../components/ha-area-picker"; +import "../../../../components/ha-dialog"; +import "../../../../components/ha-textfield"; +import type { HaTextField } from "../../../../components/ha-textfield"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import { HomeAssistant } from "../../../../types"; +import { EntityAliasesDialogParams } from "./show-dialog-entity-aliases"; + +@customElement("dialog-entity-aliases") +class DialogEntityAliases extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _error?: string; + + @state() private _params?: EntityAliasesDialogParams; + + @state() private _aliases!: string[]; + + @state() private _submitting = false; + + public async showDialog(params: EntityAliasesDialogParams): Promise { + this._params = params; + this._error = undefined; + this._aliases = + this._params.entity.aliases?.length > 0 + ? this._params.entity.aliases + : [""]; + await this.updateComplete; + } + + public closeDialog(): void { + this._error = ""; + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + + const entityId = this._params.entity.entity_id; + const stateObj = entityId ? this.hass.states[entityId] : undefined; + + const name = (stateObj && computeStateName(stateObj)) || entityId; + + return html` + +
+ ${this._error + ? html`${this._error} ` + : ""} +
+ ${this._aliases.map( + (alias, index) => html` +
+ + +
+ ` + )} +
+ + ${this.hass!.localize( + "ui.dialogs.entity_registry.editor.aliases.add_alias" + )} + + +
+
+
+ + ${this.hass.localize("ui.common.cancel")} + + + ${this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases.save" + )} + +
+ `; + } + + private async _addAlias() { + this._aliases = [...this._aliases, ""]; + await this.updateComplete; + const field = this.shadowRoot?.querySelector(`ha-textfield[data-last]`) as + | HaTextField + | undefined; + field?.focus(); + } + + private async _editAlias(ev: Event) { + const index = (ev.target as any).index; + this._aliases[index] = (ev.target as any).value; + } + + private async _removeAlias(ev: Event) { + const index = (ev.target as any).index; + const aliases = [...this._aliases]; + aliases.splice(index, 1); + this._aliases = aliases; + } + + private async _updateEntry(): Promise { + this._submitting = true; + const noEmptyAliases = this._aliases + .map((alias) => alias.trim()) + .filter((alias) => alias); + + try { + await this._params!.updateEntry({ + aliases: noEmptyAliases, + }); + this.closeDialog(); + } catch (err: any) { + this._error = + err.message || + this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases.unknown_error" + ); + } finally { + this._submitting = false; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + .row { + margin-bottom: 8px; + } + ha-textfield { + display: block; + } + ha-icon-button { + display: block; + } + mwc-button { + margin-left: 8px; + } + #alias_input { + margin-top: 8px; + } + .alias { + border: 1px solid var(--divider-color); + border-radius: 4px; + margin-top: 4px; + --mdc-icon-button-size: 24px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-entity-aliases": DialogEntityAliases; + } +} diff --git a/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts b/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts new file mode 100644 index 0000000000..d3fec599ec --- /dev/null +++ b/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts @@ -0,0 +1,25 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import { + EntityRegistryEntry, + EntityRegistryEntryUpdateParams, +} from "../../../../data/entity_registry"; + +export interface EntityAliasesDialogParams { + entity: EntityRegistryEntry; + updateEntry: ( + updates: Partial + ) => Promise; +} + +export const loadEntityAliasesDialog = () => import("./dialog-entity-aliases"); + +export const showEntityAliasesDialog = ( + element: HTMLElement, + entityAliasesParams: EntityAliasesDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-entity-aliases", + dialogImport: loadEntityAliasesDialog, + dialogParams: entityAliasesParams, + }); +}; diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 21f1fbd1cd..615485d399 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1,6 +1,7 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-formfield/mwc-formfield"; import "@material/mwc-list/mwc-list-item"; +import { mdiPencil } from "@mdi/js"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -26,6 +27,7 @@ import { import "../../../components/ha-alert"; import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; +import "../../../components/ha-icon"; import "../../../components/ha-icon-picker"; import "../../../components/ha-radio"; import "../../../components/ha-select"; @@ -75,6 +77,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail"; +import { showEntityAliasesDialog } from "./entity-aliases/show-dialog-entity-aliases"; const OVERRIDE_DEVICE_CLASSES = { cover: [ @@ -673,7 +676,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
${this.hass.localize( "ui.dialogs.entity_registry.editor.entity_status" - )}: + )}
${this._disabledBy && @@ -760,12 +763,43 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
` : ""} + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases_section" + )} +
+ + 0} + hasMeta + @click=${this._openAliasesSettings} + > + + ${this.entry.aliases.length > 0 + ? this.hass.localize( + "ui.dialogs.entity_registry.editor.configured_aliases", + { count: this.entry.aliases.length } + ) + : this.hass.localize( + "ui.dialogs.entity_registry.editor.no_aliases" + )} + + ${this.entry.aliases.join(", ")} + + + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases.description" + )} +
${this.entry.device_id ? html`
${this.hass.localize( "ui.dialogs.entity_registry.editor.change_area" - )}: + )}
{ + const result = await updateEntityRegistryEntry( + this.hass, + this.entry.entity_id, + updates + ); + fireEvent(this, "entity-entry-updated", result.entity_entry); + }, + }); + } + private async _enableEntry() { this._error = undefined; this._submitting = true; @@ -1212,7 +1260,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { } .secondary { margin: 8px 0; - width: 340px; } li[divider] { border-bottom-color: var(--divider-color); @@ -1220,6 +1267,13 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { ha-alert mwc-button { width: max-content; } + .aliases { + border-radius: 4px; + margin-top: 4px; + margin-bottom: 4px; + --mdc-icon-button-size: 24px; + overflow: hidden; + } `, ]; } diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 5fb9d6bf9c..cafff74d14 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -728,6 +728,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { selectable: false, entity_category: null, has_entity_name: false, + aliases: [], }); } if (changed) { diff --git a/src/translations/en.json b/src/translations/en.json index f8415ad180..c828d219e0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -961,6 +961,19 @@ "stream_orientation_6": "Rotate left", "stream_orientation_7": "Rotate right and flip", "stream_orientation_8": "Rotate right" + }, + "aliases_section": "Aliases", + "no_aliases": "No configured aliases", + "configured_aliases": "{count} configured {count, plural,\n one {alias}\n other {aliases}\n}", + "aliases": { + "heading": "{name} aliases", + "description": "Aliases are alternative names used in voice assistants to refer to this entity.", + "remove_alias": "Remove alias", + "save": "Save", + "add_alias": "Add alias", + "no_aliases": "No aliases have been added yet", + "update": "Update", + "unknown_error": "Unknown error" } } }, From e175c7ba3ca04dc613d9bf63450a40bf3aff4471 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 27 Dec 2022 12:47:42 -0800 Subject: [PATCH 06/56] Add edit/update support for calendar events (#14814) Co-authored-by: Bram Kragten --- src/data/calendar.ts | 11 ++- .../calendar/dialog-calendar-event-editor.ts | 73 ++++++++++++++++++- src/panels/calendar/ha-full-calendar.ts | 4 + src/translations/en.json | 6 ++ 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/data/calendar.ts b/src/data/calendar.ts index 0b856de9b9..c0b9d2b072 100644 --- a/src/data/calendar.ts +++ b/src/data/calendar.ts @@ -50,6 +50,7 @@ export enum RecurrenceRange { export const enum CalendarEntityFeature { CREATE_EVENT = 1, DELETE_EVENT = 2, + UPDATE_EVENT = 4, } export const fetchCalendarEvents = async ( @@ -161,12 +162,18 @@ export const createCalendarEvent = ( export const updateCalendarEvent = ( hass: HomeAssistant, entityId: string, - event: CalendarEventMutableParams + uid: string, + event: CalendarEventMutableParams, + recurrence_id?: string, + recurrence_range?: RecurrenceRange ) => hass.callWS({ type: "calendar/event/update", entity_id: entityId, - event: event, + uid, + recurrence_id, + recurrence_range, + event, }); export const deleteCalendarEvent = ( diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index 19965a748e..b7dacd23b3 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -25,6 +25,8 @@ import { CalendarEventMutableParams, createCalendarEvent, deleteCalendarEvent, + updateCalendarEvent, + RecurrenceRange, } from "../../data/calendar"; import { haStyleDialog } from "../../resources/styles"; import { HomeAssistant } from "../../types"; @@ -87,10 +89,10 @@ class DialogCalendarEventEditor extends LitElement { this._summary = entry.summary; this._rrule = entry.rrule; if (this._allDay) { - this._dtstart = new Date(entry.dtstart); + this._dtstart = new Date(entry.dtstart + "T00:00:00"); // Calendar event end dates are exclusive, but not shown that way in the UI. The // reverse happens when persisting the event. - this._dtend = addDays(new Date(entry.dtend), -1); + this._dtend = addDays(new Date(entry.dtend + "T00:00:00"), -1); } else { this._dtstart = new Date(entry.dtstart); this._dtend = new Date(entry.dtend); @@ -168,6 +170,7 @@ class DialogCalendarEventEditor extends LitElement { class="summary" name="summary" .label=${this.hass.localize("ui.components.calendar.event.summary")} + .value=${this._summary} required @change=${this._handleSummaryChanged} error-message=${this.hass.localize("ui.common.error_required")} @@ -179,6 +182,7 @@ class DialogCalendarEventEditor extends LitElement { .label=${this.hass.localize( "ui.components.calendar.event.description" )} + .value=${this._description} @change=${this._handleDescriptionChanged} autogrow > @@ -412,6 +416,13 @@ class DialogCalendarEventEditor extends LitElement { this._calendarId = ev.detail.value; } + private _isValidStartEnd(): boolean { + if (this._allDay) { + return this._dtend! >= this._dtstart!; + } + return this._dtend! > this._dtstart!; + } + private async _createEvent() { if (!this._summary || !this._calendarId) { this._error = this.hass.localize( @@ -420,7 +431,7 @@ class DialogCalendarEventEditor extends LitElement { return; } - if (this._dtend! <= this._dtstart!) { + if (!this._isValidStartEnd()) { this._error = this.hass.localize( "ui.components.calendar.event.invalid_duration" ); @@ -445,7 +456,61 @@ class DialogCalendarEventEditor extends LitElement { } private async _saveEvent() { - // to be implemented + if (!this._summary || !this._calendarId) { + this._error = this.hass.localize( + "ui.components.calendar.event.not_all_required_fields" + ); + return; + } + + if (!this._isValidStartEnd()) { + this._error = this.hass.localize( + "ui.components.calendar.event.invalid_duration" + ); + return; + } + + this._submitting = true; + const entry = this._params!.entry!; + let range: RecurrenceRange | undefined = RecurrenceRange.THISEVENT; + if (entry.recurrence_id) { + range = await showConfirmEventDialog(this, { + title: this.hass.localize( + "ui.components.calendar.event.confirm_update.update" + ), + text: this.hass.localize( + "ui.components.calendar.event.confirm_update.recurring_prompt" + ), + confirmText: this.hass.localize( + "ui.components.calendar.event.confirm_update.update_this" + ), + confirmFutureText: this.hass.localize( + "ui.components.calendar.event.confirm_update.update_future" + ), + }); + } + if (range === undefined) { + // Cancel + this._submitting = false; + return; + } + try { + await updateCalendarEvent( + this.hass!, + this._calendarId!, + entry.uid!, + this._calculateData(), + entry.recurrence_id || "", + range! + ); + } catch (err: any) { + this._error = err ? err.message : "Unknown error"; + return; + } finally { + this._submitting = false; + } + await this._params!.updated(); + this.closeDialog(); } private async _deleteEvent() { diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index e0e2a114d3..9e4f3ad5e0 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -302,6 +302,9 @@ export class HAFullCalendar extends LitElement { private _handleEventClick(info): void { const entityStateObj = this.hass.states[info.event.extendedProps.calendar]; + const canEdit = + entityStateObj && + supportsFeature(entityStateObj, CalendarEntityFeature.UPDATE_EVENT); const canDelete = entityStateObj && supportsFeature(entityStateObj, CalendarEntityFeature.DELETE_EVENT); @@ -312,6 +315,7 @@ export class HAFullCalendar extends LitElement { updated: () => { this._fireViewChanged(); }, + canEdit: canEdit, canDelete: canDelete, }); } diff --git a/src/translations/en.json b/src/translations/en.json index c828d219e0..f774926c70 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -623,6 +623,12 @@ "prompt": "Do you want to delete this event?", "recurring_prompt": "Do you want to delete only this event, or this and all future occurrences of the event?" }, + "confirm_update": { + "update": "Update Event", + "update_this": "Update Only This Event", + "update_future": "Update All Future Events", + "recurring_prompt": "Do you want to update only this event, or this and all future occurrences of the event?" + }, "repeat": { "label": "Repeat", "freq": { From 419f23879a0e6e65cb496e144dfe92ba7d707284 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:57:47 -0800 Subject: [PATCH 07/56] Fix broken numeric text entry in state-card-input_number (#14812) fixes undefined --- src/state-summary/state-card-input_number.js | 200 ------------------- src/state-summary/state-card-input_number.ts | 169 ++++++++++++++++ 2 files changed, 169 insertions(+), 200 deletions(-) delete mode 100644 src/state-summary/state-card-input_number.js create mode 100644 src/state-summary/state-card-input_number.ts diff --git a/src/state-summary/state-card-input_number.js b/src/state-summary/state-card-input_number.js deleted file mode 100644 index ced62e2984..0000000000 --- a/src/state-summary/state-card-input_number.js +++ /dev/null @@ -1,200 +0,0 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior"; -import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { computeStateDisplay } from "../common/entity/compute_state_display"; -import "../components/entity/state-info"; -import "../components/ha-slider"; -import "../components/ha-textfield"; - -class StateCardInputNumber extends mixinBehaviors( - [IronResizableBehavior], - PolymerElement -) { - static get template() { - return html` - - - -
- ${this.stateInfoTemplate} - - - -
- `; - } - - static get stateInfoTemplate() { - return html` - - `; - } - - ready() { - super.ready(); - if (typeof ResizeObserver === "function") { - const ro = new ResizeObserver((entries) => { - entries.forEach(() => { - this.hiddenState(); - }); - }); - ro.observe(this.$.input_number_card); - } else { - this.addEventListener("iron-resize", this.hiddenState); - } - } - - static get properties() { - return { - hass: Object, - hiddenbox: { - type: Boolean, - value: true, - }, - hiddenslider: { - type: Boolean, - value: true, - }, - inDialog: { - type: Boolean, - value: false, - }, - stateObj: { - type: Object, - observer: "stateObjectChanged", - }, - min: { - type: Number, - value: 0, - }, - max: { - type: Number, - value: 100, - }, - maxlength: { - type: Number, - value: 3, - }, - step: Number, - value: Number, - formattedState: String, - mode: String, - }; - } - - hiddenState() { - if (this.mode !== "slider") return; - const sliderwidth = this.$.slider.offsetWidth; - if (sliderwidth < 100) { - this.$.sliderstate.hidden = true; - } else if (sliderwidth >= 145) { - this.$.sliderstate.hidden = false; - } - } - - stateObjectChanged(newVal) { - const prevMode = this.mode; - this.setProperties({ - min: Number(newVal.attributes.min), - max: Number(newVal.attributes.max), - step: Number(newVal.attributes.step), - value: Number(newVal.state), - formattedState: computeStateDisplay( - this.hass.localize, - newVal, - this.hass.locale, - this.hass.entities, - newVal.state - ), - mode: String(newVal.attributes.mode), - maxlength: String(newVal.attributes.max).length, - hiddenbox: newVal.attributes.mode !== "box", - hiddenslider: newVal.attributes.mode !== "slider", - }); - if (this.mode === "slider" && prevMode !== "slider") { - this.hiddenState(); - } - } - - onInput(ev) { - this.value = ev.target.value; - } - - selectedValueChanged() { - if (this.value === Number(this.stateObj.state)) { - return; - } - this.hass.callService("input_number", "set_value", { - value: this.value, - entity_id: this.stateObj.entity_id, - }); - } - - stopPropagation(ev) { - ev.stopPropagation(); - } -} - -customElements.define("state-card-input_number", StateCardInputNumber); diff --git a/src/state-summary/state-card-input_number.ts b/src/state-summary/state-card-input_number.ts new file mode 100644 index 0000000000..02829dd96a --- /dev/null +++ b/src/state-summary/state-card-input_number.ts @@ -0,0 +1,169 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { HassEntity } from "home-assistant-js-websocket"; +import { customElement, property } from "lit/decorators"; +import { computeStateDisplay } from "../common/entity/compute_state_display"; +import { computeRTLDirection } from "../common/util/compute_rtl"; +import { debounce } from "../common/util/debounce"; +import "../components/ha-slider"; +import "../components/ha-textfield"; +import "../components/entity/state-info"; +import { isUnavailableState } from "../data/entity"; +import { setValue } from "../data/input_text"; +import { HomeAssistant } from "../types"; +import { installResizeObserver } from "../panels/lovelace/common/install-resize-observer"; + +@customElement("state-card-input_number") +class StateCardInputNumber extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public stateObj!: HassEntity; + + @property({ type: Boolean }) public inDialog = false; + + private _loaded?: boolean; + + private _updated?: boolean; + + private _resizeObserver?: ResizeObserver; + + public connectedCallback(): void { + super.connectedCallback(); + if (this._updated && !this._loaded) { + this._initialLoad(); + } + this._attachObserver(); + } + + public disconnectedCallback(): void { + this._resizeObserver?.disconnect(); + } + + protected firstUpdated(): void { + this._updated = true; + if (this.isConnected && !this._loaded) { + this._initialLoad(); + } + this._attachObserver(); + } + + protected render(): TemplateResult { + return html` + + ${this.stateObj.attributes.mode === "slider" + ? html` +
+ + + ${computeStateDisplay( + this.hass.localize, + this.stateObj, + this.hass.locale, + this.hass.entities, + this.stateObj.state + )} + +
+ ` + : html` +
+ + +
+ `} + `; + } + + static get styles(): CSSResultGroup { + return css` + :host { + display: flex; + } + .flex { + display: flex; + align-items: center; + justify-content: flex-end; + flex-grow: 2; + } + .state { + min-width: 45px; + text-align: end; + } + ha-textfield { + text-align: end; + } + ha-slider { + width: 100%; + max-width: 200px; + } + `; + } + + private async _initialLoad(): Promise { + this._loaded = true; + await this.updateComplete; + this._measureCard(); + } + + private _measureCard() { + if (!this.isConnected) { + return; + } + const element = this.shadowRoot!.querySelector(".state") as HTMLElement; + if (!element) { + return; + } + element.hidden = this.clientWidth <= 300; + } + + private async _attachObserver(): Promise { + if (!this._resizeObserver) { + await installResizeObserver(); + this._resizeObserver = new ResizeObserver( + debounce(() => this._measureCard(), 250, false) + ); + } + if (this.isConnected) { + this._resizeObserver.observe(this); + } + } + + private _selectedValueChanged(ev: Event): void { + if ((ev.target as HTMLInputElement).value !== this.stateObj.state) { + setValue( + this.hass!, + this.stateObj.entity_id, + (ev.target as HTMLInputElement).value + ); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-card-input_number": StateCardInputNumber; + } +} From 1c139d0bc7631900f7aac8736cf23e5ca11edb65 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 27 Dec 2022 13:00:19 -0800 Subject: [PATCH 08/56] Fix map card not loading in sidebar view (#14872) fixes undefined --- src/panels/lovelace/cards/hui-map-card.ts | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index e5b7951160..ca91c301e8 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -102,6 +102,7 @@ class HuiMapCard extends LitElement implements LovelaceCard { ratio && ratio.w > 0 && ratio.h > 0 ? `${((100 * ratio.h) / ratio.w).toFixed(2)}` : "100"; + return 1 + Math.floor(Number(ar) / 25) || 3; } @@ -185,10 +186,21 @@ class HuiMapCard extends LitElement implements LovelaceCard { return false; } - protected firstUpdated(changedProps: PropertyValues): void { - super.firstUpdated(changedProps); - const root = this.shadowRoot!.getElementById("root"); + protected updated(changedProps: PropertyValues): void { + if (this._config?.hours_to_show && this._configEntities?.length) { + if (changedProps.has("_config")) { + this._getHistory(); + } else if (Date.now() - this._date!.getTime() >= MINUTE) { + this._getHistory(); + } + } + if (changedProps.has("_config")) { + this._computePadding(); + } + } + private _computePadding(): void { + const root = this.shadowRoot!.getElementById("root"); if (!this._config || this.isPanel || !root) { return; } @@ -206,16 +218,6 @@ class HuiMapCard extends LitElement implements LovelaceCard { : (root.style.paddingBottom = "100%"); } - protected updated(changedProps: PropertyValues): void { - if (this._config?.hours_to_show && this._configEntities?.length) { - if (changedProps.has("_config")) { - this._getHistory(); - } else if (Date.now() - this._date!.getTime() >= MINUTE) { - this._getHistory(); - } - } - } - private _fitMap() { this._map?.fitMap(); } From adb61ab99b614feb91e1c56d278b6e6615817669 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Tue, 27 Dec 2022 22:00:37 +0100 Subject: [PATCH 09/56] Enforce valid entity ID in card config YAML (#14792) --- src/panels/lovelace/cards/hui-map-card.ts | 2 +- .../lovelace/common/process-config-entities.ts | 6 ++---- .../hui-alarm-panel-card-editor.ts | 11 ++++++----- .../config-elements/hui-button-card-editor.ts | 3 ++- .../config-elements/hui-entity-card-editor.ts | 7 ++++--- .../config-elements/hui-gauge-card-editor.ts | 7 ++++--- .../hui-humidifier-card-editor.ts | 5 +++-- .../config-elements/hui-light-card-editor.ts | 3 ++- .../config-elements/hui-map-card-editor.ts | 13 +++++++------ .../hui-media-control-card-editor.ts | 3 ++- .../hui-picture-entity-card-editor.ts | 5 +++-- .../hui-plant-status-card-editor.ts | 5 +++-- .../config-elements/hui-sensor-card-editor.ts | 5 +++-- .../hui-thermostat-card-editor.ts | 5 +++-- .../config-elements/hui-tile-card-editor.ts | 3 ++- .../hui-weather-forecast-card-editor.ts | 17 +++++++++-------- 16 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index ca91c301e8..412ccf8f13 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -79,7 +79,7 @@ class HuiMapCard extends LitElement implements LovelaceCard { config.geo_location_sources && !Array.isArray(config.geo_location_sources) ) { - throw new Error("Geo_location_sources needs to be an array"); + throw new Error("Parameter geo_location_sources needs to be an array"); } this._config = config; diff --git a/src/panels/lovelace/common/process-config-entities.ts b/src/panels/lovelace/common/process-config-entities.ts index 2dfa9a92a7..1d4519ea5f 100644 --- a/src/panels/lovelace/common/process-config-entities.ts +++ b/src/panels/lovelace/common/process-config-entities.ts @@ -27,13 +27,11 @@ export const processConfigEntities = < config = { entity: entityConf } as T; } else if (typeof entityConf === "object" && !Array.isArray(entityConf)) { if (!("entity" in entityConf)) { - throw new Error( - `Entity object at position ${index} is missing entity field.` - ); + throw new Error(`Object at position ${index} is missing entity field`); } config = entityConf as T; } else { - throw new Error(`Invalid entity specified at position ${index}.`); + throw new Error(`Invalid entity ID at position ${index}`); } if (checkEntityId && !isValidEntityId((config as EntityConfig).entity!)) { diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index 97f5593057..f1c6168dbf 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -1,20 +1,21 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { array, assert, assign, object, optional, string } from "superstruct"; import memoizeOne from "memoize-one"; +import { array, assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { AlarmPanelCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; -import type { LocalizeFunc } from "../../../../common/translations/localize"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), states: optional(array()), theme: optional(string()), diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts index 7ac8bb39e9..da8436cf5b 100644 --- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts @@ -6,6 +6,7 @@ import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import { entityId } from "../../../../common/structs/is-entity-id"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; @@ -18,7 +19,7 @@ import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), show_name: optional(boolean()), icon: optional(string()), diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index 334e84bc06..37e3c34874 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -1,12 +1,13 @@ -import "../../../../components/ha-form/ha-form"; +import type { HassEntity } from "home-assistant-js-websocket/dist/types"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { assert, assign, boolean, object, optional, string } from "superstruct"; -import type { HassEntity } from "home-assistant-js-websocket/dist/types"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { EntityCardConfig } from "../../cards/types"; @@ -17,7 +18,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), icon: optional(string()), attribute: optional(string()), diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index 477b949a78..55592f9132 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -1,6 +1,6 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { array, assert, @@ -11,8 +11,9 @@ import { optional, string, } from "superstruct"; -import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { GaugeCardConfig } from "../../cards/types"; @@ -29,7 +30,7 @@ const cardConfigStruct = assign( baseLovelaceCardConfig, object({ name: optional(string()), - entity: optional(string()), + entity: optional(entityId()), unit: optional(string()), min: optional(number()), max: optional(number()), diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts index 055cdf68c9..7516347005 100644 --- a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts @@ -1,8 +1,9 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { HumidifierCardConfig } from "../../cards/types"; @@ -12,7 +13,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), theme: optional(string()), }) diff --git a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts index e0e42f6fe7..e8247b9eb7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts @@ -6,6 +6,7 @@ import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import { entityId } from "../../../../common/structs/is-entity-id"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; @@ -19,7 +20,7 @@ const cardConfigStruct = assign( baseLovelaceCardConfig, object({ name: optional(string()), - entity: optional(string()), + entity: optional(entityId()), theme: optional(string()), icon: optional(string()), hold_action: optional(actionConfigStruct), diff --git a/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts index 4c23122554..13161eac14 100644 --- a/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts @@ -1,17 +1,19 @@ -import "../../../../components/ha-form/ha-form"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, assert, + assign, boolean, number, object, optional, string, - assign, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; +import { SchemaUnion } from "../../../../components/ha-form/types"; import "../../../../components/ha-formfield"; import "../../../../components/ha-switch"; import { PolymerChangedEvent } from "../../../../polymer-types"; @@ -22,11 +24,9 @@ import "../../components/hui-input-list-editor"; import { EntityConfig } from "../../entity-rows/types"; import { LovelaceCardEditor } from "../../types"; import { processEditorEntities } from "../process-editor-entities"; -import { entitiesConfigStruct } from "../structs/entities-struct"; +import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { EntitiesEditorEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -35,9 +35,10 @@ const cardConfigStruct = assign( aspect_ratio: optional(string()), default_zoom: optional(number()), dark_mode: optional(boolean()), - entities: array(entitiesConfigStruct), + entities: array(entityId()), hours_to_show: optional(number()), geo_location_sources: optional(array(string())), + auto_fit: optional(boolean()), }) ); diff --git a/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts index 9470be0a78..f7bd27249c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts @@ -2,6 +2,7 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-theme-picker"; import { HomeAssistant } from "../../../../types"; @@ -13,7 +14,7 @@ import { EditorTarget, EntitiesEditorEvent } from "../types"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), theme: optional(string()), }) ); diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index 4b24e2a523..5980b1e07a 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -2,6 +2,8 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { PictureEntityCardConfig } from "../../cards/types"; @@ -9,12 +11,11 @@ import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { configElementStyle } from "./config-elements-style"; -import "../../../../components/ha-form/ha-form"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), image: optional(string()), name: optional(string()), camera_image: optional(string()), diff --git a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts index dbc19b69a3..bf3fe6e720 100644 --- a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts @@ -1,8 +1,9 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { PlantStatusCardConfig } from "../../cards/types"; @@ -12,7 +13,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), theme: optional(string()), }) diff --git a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts index d3b06a385e..d9c6d314a7 100644 --- a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts @@ -1,4 +1,3 @@ -import "../../../../components/ha-form/ha-form"; import type { HassEntity } from "home-assistant-js-websocket"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -16,6 +15,8 @@ import { import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { SensorCardConfig } from "../../cards/types"; @@ -26,7 +27,7 @@ import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), icon: optional(string()), graph: optional(union([literal("line"), literal("none")])), diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts index cf5af922f7..ebaa0331d9 100644 --- a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -1,8 +1,9 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { ThermostatCardConfig } from "../../cards/types"; @@ -12,7 +13,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), theme: optional(string()), }) diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts index ba9b383512..65dbeac6d1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts @@ -16,6 +16,7 @@ import { import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import { entityId } from "../../../../common/structs/is-entity-id"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; @@ -35,7 +36,7 @@ import "./hui-tile-card-features-editor"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), icon: optional(string()), color: optional(string()), diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 410371bcdd..cc040c84d1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -1,23 +1,24 @@ -import "../../../../components/ha-form/ha-form"; +import { memoize } from "@fullcalendar/common"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { assert, boolean, object, optional, string, assign } from "superstruct"; -import { memoize } from "@fullcalendar/common"; +import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { entityId } from "../../../../common/structs/is-entity-id"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; +import { UNAVAILABLE } from "../../../../data/entity"; +import type { WeatherEntity } from "../../../../data/weather"; import type { HomeAssistant } from "../../../../types"; import type { WeatherForecastCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { UNAVAILABLE } from "../../../../data/entity"; -import type { WeatherEntity } from "../../../../data/weather"; -import type { LocalizeFunc } from "../../../../common/translations/localize"; -import type { SchemaUnion } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, object({ - entity: optional(string()), + entity: optional(entityId()), name: optional(string()), theme: optional(string()), show_current: optional(boolean()), From 81e36524461d4a2655b3f7f967fd6224d587767a Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 27 Dec 2022 22:03:14 +0100 Subject: [PATCH 10/56] Make JSON formatting optional when using MQTT subscribe from config entry page (#14830) --- .../mqtt/mqtt-subscribe-card.ts | 31 +++++++++++++++++-- src/translations/en.json | 1 + 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts index b953b7e4a5..ebc0e1a123 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts @@ -9,6 +9,8 @@ import { MQTTMessage, subscribeMQTTTopic } from "../../../../../data/mqtt"; import { HomeAssistant } from "../../../../../types"; import "@material/mwc-list/mwc-list-item"; import { LocalStorage } from "../../../../../common/decorators/local-storage"; +import "../../../../../components/ha-formfield"; +import "../../../../../components/ha-switch"; const qosLevel = ["0", "1", "2"]; @@ -22,6 +24,9 @@ class MqttSubscribeCard extends LitElement { @LocalStorage("panel-dev-mqtt-qos-subscribe", true, false) private _qos = "0"; + @LocalStorage("panel-dev-mqtt-json-format", true, false) + private _json_format = false; + @state() private _subscribed?: () => void; @state() private _messages: Array<{ @@ -47,6 +52,18 @@ class MqttSubscribeCard extends LitElement { header=${this.hass.localize("ui.panel.config.mqtt.description_listen")} >
+

+ + + +

{ if (this._subscribed) { this._subscribed(); @@ -132,9 +153,13 @@ class MqttSubscribeCard extends LitElement { const tail = this._messages.length > 30 ? this._messages.slice(0, 29) : this._messages; let payload: string; - try { - payload = JSON.stringify(JSON.parse(message.payload), null, 4); - } catch (err: any) { + if (this._json_format) { + try { + payload = JSON.stringify(JSON.parse(message.payload), null, 4); + } catch (err: any) { + payload = message.payload; + } + } else { payload = message.payload; } this._messages = [ diff --git a/src/translations/en.json b/src/translations/en.json index f774926c70..51c2c8047d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3217,6 +3217,7 @@ "payload": "Payload (template allowed)", "publish": "Publish", "description_listen": "Listen to a topic", + "json_formatting": "Format JSON content", "listening_to": "Listening to", "subscribe_to": "Topic to subscribe to", "start_listening": "Start listening", From d4d3a1cb651adf69c04991565833e7604b0b188b Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 28 Dec 2022 11:22:40 +0100 Subject: [PATCH 11/56] Use _ prefix for local vars on MQTT config entry page (#14898) --- .../mqtt/mqtt-config-panel.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index 5ab4a00ab2..1b09bb5cc3 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -22,16 +22,16 @@ class HaPanelDevMqtt extends LitElement { @property({ type: Boolean }) public narrow!: boolean; @LocalStorage("panel-dev-mqtt-topic-ls", true, false) - private topic = ""; + private _topic = ""; @LocalStorage("panel-dev-mqtt-payload-ls", true, false) - private payload = ""; + private _payload = ""; @LocalStorage("panel-dev-mqtt-qos-ls", true, false) - private qos = "0"; + private _qos = "0"; @LocalStorage("panel-dev-mqtt-retain-ls", true, false) - private retain = false; + private _retain = false; protected render(): TemplateResult { return html` @@ -53,12 +53,12 @@ class HaPanelDevMqtt extends LitElement {
${qosLevel.map( (qos) => @@ -70,7 +70,7 @@ class HaPanelDevMqtt extends LitElement { >
@@ -80,7 +80,7 @@ class HaPanelDevMqtt extends LitElement { autocomplete-entities autocomplete-icons .hass=${this.hass} - .value=${this.payload} + .value=${this._payload} @value-changed=${this._handlePayload} dir="ltr" > @@ -101,22 +101,22 @@ class HaPanelDevMqtt extends LitElement { } private _handleTopic(ev: CustomEvent) { - this.topic = (ev.target! as any).value; + this._topic = (ev.target! as any).value; } private _handlePayload(ev: CustomEvent) { - this.payload = ev.detail.value; + this._payload = ev.detail.value; } private _handleQos(ev: CustomEvent) { const newValue = (ev.target! as any).value; - if (newValue >= 0 && newValue !== this.qos) { - this.qos = newValue; + if (newValue >= 0 && newValue !== this._qos) { + this._qos = newValue; } } private _handleRetain(ev: CustomEvent) { - this.retain = (ev.target! as any).checked; + this._retain = (ev.target! as any).checked; } private _publish(): void { @@ -124,10 +124,10 @@ class HaPanelDevMqtt extends LitElement { return; } this.hass.callService("mqtt", "publish", { - topic: this.topic, - payload_template: this.payload, - qos: parseInt(this.qos), - retain: this.retain, + topic: this._topic, + payload_template: this._payload, + qos: parseInt(this._qos), + retain: this._retain, }); } From 1d1ff410b267069ba08e6975a8f7eb1a015190f6 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 28 Dec 2022 12:12:30 +0100 Subject: [PATCH 12/56] Make using template rendering optional when using MQTT publish from the config entry page (#14828) --- .../mqtt/mqtt-config-panel.ts | 30 +++++++++++++++++-- src/translations/en.json | 2 ++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index 1b09bb5cc3..0656ed360e 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -33,6 +33,9 @@ class HaPanelDevMqtt extends LitElement { @LocalStorage("panel-dev-mqtt-retain-ls", true, false) private _retain = false; + @LocalStorage("panel-dev-mqtt-allow-template-ls", true, false) + private _allowTemplate = false; + protected render(): TemplateResult { return html` @@ -74,7 +77,25 @@ class HaPanelDevMqtt extends LitElement { >
-

${this.hass.localize("ui.panel.config.mqtt.payload")}

+

+ + + +

+

+ ${this._allowTemplate + ? this.hass.localize("ui.panel.config.mqtt.payload") + : this.hass.localize( + "ui.panel.config.mqtt.payload_no_template" + )} +

Date: Wed, 28 Dec 2022 07:50:38 -0500 Subject: [PATCH 13/56] Conversation dialog tweaks (#14869) --- src/data/conversation.ts | 4 +- .../ha-voice-command-dialog.ts | 40 ++++++++++++------- src/translations/en.json | 1 + 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/data/conversation.ts b/src/data/conversation.ts index 4e7a504f11..b448876dfd 100644 --- a/src/data/conversation.ts +++ b/src/data/conversation.ts @@ -61,12 +61,14 @@ export const processConversationInput = ( hass: HomeAssistant, text: string, // eslint-disable-next-line: variable-name - conversation_id: string + conversation_id: string | null, + language: string ): Promise => hass.callWS({ type: "conversation/process", text, conversation_id, + language, }); export const getAgentInfo = (hass: HomeAssistant): Promise => diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts index 49c998bcb4..91577f65e8 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -13,7 +13,6 @@ import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../common/dom/fire_event"; import { SpeechRecognition } from "../../common/dom/speech-recognition"; -import { uid } from "../../common/util/uid"; import "../../components/ha-dialog"; import type { HaDialog } from "../../components/ha-dialog"; import "../../components/ha-icon-button"; @@ -60,7 +59,7 @@ export class HaVoiceCommandDialog extends LitElement { private recognition!: SpeechRecognition; - private _conversationId?: string; + private _conversationId: string | null = null; public async showDialog(): Promise { this._opened = true; @@ -175,7 +174,6 @@ export class HaVoiceCommandDialog extends LitElement { protected firstUpdated(changedProps: PropertyValues) { super.updated(changedProps); - this._conversationId = uid(); this._conversation = [ { who: "hass", @@ -211,18 +209,29 @@ export class HaVoiceCommandDialog extends LitElement { private _initRecognition() { this.recognition = new SpeechRecognition(); this.recognition.interimResults = true; - this.recognition.lang = "en-US"; + this.recognition.lang = this.hass.language; - this.recognition.onstart = () => { + this.recognition.addEventListener("start", () => { this.results = { final: false, transcript: "", }; - }; - this.recognition.onerror = (event) => { + }); + this.recognition.addEventListener("nomatch", () => { + this._addMessage({ + who: "user", + text: `<${this.hass.localize( + "ui.dialogs.voice_command.did_not_understand" + )}>`, + error: true, + }); + }); + this.recognition.addEventListener("error", (event) => { + // eslint-disable-next-line + console.error("Error recognizing text", event); this.recognition!.abort(); // @ts-ignore - if (event.error !== "aborted") { + if (event.error !== "aborted" && event.error !== "no-speech") { const text = this.results && this.results.transcript ? this.results.transcript @@ -232,8 +241,8 @@ export class HaVoiceCommandDialog extends LitElement { this._addMessage({ who: "user", text, error: true }); } this.results = null; - }; - this.recognition.onend = () => { + }); + this.recognition.addEventListener("end", () => { // Already handled by onerror if (this.results == null) { return; @@ -251,15 +260,14 @@ export class HaVoiceCommandDialog extends LitElement { error: true, }); } - }; - - this.recognition.onresult = (event) => { + }); + this.recognition.addEventListener("result", (event) => { const result = event.results[0]; this.results = { transcript: result[0].transcript, final: result.isFinal, }; - }; + }); } private async _processText(text: string) { @@ -277,8 +285,10 @@ export class HaVoiceCommandDialog extends LitElement { const response = await processConversationInput( this.hass, text, - this._conversationId! + this._conversationId, + this.hass.language ); + this._conversationId = response.conversation_id; const plain = response.response.speech?.plain; if (plain) { message.text = plain.speech; diff --git a/src/translations/en.json b/src/translations/en.json index 92c455f6bc..a6329fb139 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -777,6 +777,7 @@ }, "voice_command": { "did_not_hear": "Home Assistant did not hear anything", + "did_not_understand": "Didn't quite get that", "found": "I found the following for you:", "error": "Oops, an error has occurred", "how_can_i_help": "How can I help?", From b99a139f51b4c5e937349d5550ece25d554e90df Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Dec 2022 14:02:46 +0100 Subject: [PATCH 14/56] Fix target selector (#14895) --- .../ha-selector/ha-selector-target.ts | 52 ++++++++++--------- src/components/ha-target-picker.ts | 1 - 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index 55f078f6bf..e4cf47278d 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -1,8 +1,4 @@ -import { - HassEntity, - HassServiceTarget, - UnsubscribeFunc, -} from "home-assistant-js-websocket"; +import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket"; import { css, CSSResultGroup, @@ -17,8 +13,7 @@ import { DeviceRegistryEntry, getDeviceIntegrationLookup, } from "../../data/device_registry"; -import type { EntityRegistryEntry } from "../../data/entity_registry"; -import { subscribeEntityRegistry } from "../../data/entity_registry"; +import { EntityRegistryEntry } from "../../data/entity_registry"; import { EntitySources, fetchEntitySourcesWithCache, @@ -28,12 +23,11 @@ import { filterSelectorEntities, TargetSelector, } from "../../data/selector"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../types"; import "../ha-target-picker"; @customElement("ha-selector-target") -export class HaTargetSelector extends SubscribeMixin(LitElement) { +export class HaTargetSelector extends LitElement { @property() public hass!: HomeAssistant; @property() public selector!: TargetSelector; @@ -48,18 +42,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { @state() private _entitySources?: EntitySources; - @state() private _entities?: EntityRegistryEntry[]; - private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup); - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entities = entities.filter((entity) => entity.device_id !== null); - }), - ]; - } - protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); if ( @@ -88,12 +72,19 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { .value=${this.value} .helper=${this.helper} .deviceFilter=${this._filterDevices} - .entityFilter=${this._filterEntities} + .entityFilter=${this._filterStates} + .entityRegFilter=${this._filterRegEntities} + .includeDeviceClasses=${this.selector.target?.entity?.device_class + ? [this.selector.target?.entity.device_class] + : undefined} + .includeDomains=${this.selector.target?.entity?.domain + ? [this.selector.target?.entity.domain] + : undefined} .disabled=${this.disabled} >`; } - private _filterEntities = (entity: HassEntity): boolean => { + private _filterStates = (entity: HassEntity): boolean => { if (!this.selector.target?.entity) { return true; } @@ -105,15 +96,26 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) { ); }; + private _filterRegEntities = (entity: EntityRegistryEntry): boolean => { + if (this.selector.target?.entity?.integration) { + if (entity.platform !== this.selector.target.entity.integration) { + return false; + } + } + return true; + }; + private _filterDevices = (device: DeviceRegistryEntry): boolean => { if (!this.selector.target?.device) { return true; } - const deviceIntegrations = - this._entitySources && this._entities - ? this._deviceIntegrationLookup(this._entitySources, this._entities) - : undefined; + const deviceIntegrations = this._entitySources + ? this._deviceIntegrationLookup( + this._entitySources, + Object.values(this.hass.entities) + ) + : undefined; return filterSelectorDevices( this.selector.target.device, diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index d5dd2c8630..09336afc65 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -358,7 +358,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { "ui.components.target-picker.add_device_id" )} .deviceFilter=${this.deviceFilter} - .entityFilter=${this.entityRegFilter} .includeDeviceClasses=${this.includeDeviceClasses} .includeDomains=${this.includeDomains} @value-changed=${this._targetPicked} From 6a15216104f73bbcc0b3f020fe3880e7d80b4e71 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 28 Dec 2022 05:07:17 -0800 Subject: [PATCH 15/56] Add monthly variations for recurrence rules (#14849) * Add variations on monthly recurrence rules * Recurrence rule code simplificiation * Invalidate when the interval changes * update * Update ha-recurrence-rule-editor.ts Co-authored-by: Bram Kragten --- .../calendar/dialog-calendar-event-detail.ts | 50 +----- .../calendar/dialog-calendar-event-editor.ts | 2 + .../calendar/ha-recurrence-rule-editor.ts | 128 ++++++++++++-- src/panels/calendar/recurrence.ts | 166 +++++++++++++++++- 4 files changed, 282 insertions(+), 64 deletions(-) diff --git a/src/panels/calendar/dialog-calendar-event-detail.ts b/src/panels/calendar/dialog-calendar-event-detail.ts index 3f16f2c82c..8bc8f81512 100644 --- a/src/panels/calendar/dialog-calendar-event-detail.ts +++ b/src/panels/calendar/dialog-calendar-event-detail.ts @@ -4,15 +4,11 @@ import { addDays, isSameDay } from "date-fns/esm"; import { toDate } from "date-fns-tz"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; -import { RRule, Weekday } from "rrule"; import { formatDate } from "../../common/datetime/format_date"; import { formatDateTime } from "../../common/datetime/format_date_time"; import { formatTime } from "../../common/datetime/format_time"; import { fireEvent } from "../../common/dom/fire_event"; -import { capitalizeFirstLetter } from "../../common/string/capitalize-first-letter"; import { isDate } from "../../common/string/is_date"; -import { dayNames } from "../../common/translations/day_names"; -import { monthNames } from "../../common/translations/month_names"; import "../../components/entity/state-info"; import "../../components/ha-date-input"; import "../../components/ha-time-input"; @@ -23,10 +19,10 @@ import { import { haStyleDialog } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import "../lovelace/components/hui-generic-entity-row"; -import "./ha-recurrence-rule-editor"; import { showConfirmEventDialog } from "./show-confirm-event-dialog-box"; import { CalendarEventDetailDialogParams } from "./show-dialog-calendar-event-detail"; import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor"; +import { renderRRuleAsText } from "./recurrence"; class DialogCalendarEventDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -137,54 +133,16 @@ class DialogCalendarEventDetail extends LitElement { return ""; } try { - const rule = RRule.fromString(`RRULE:${value}`); - if (rule.isFullyConvertibleToText()) { - return html`
- ${capitalizeFirstLetter( - rule.toText( - this._translateRRuleElement, - { - dayNames: dayNames(this.hass.locale), - monthNames: monthNames(this.hass.locale), - tokens: {}, - }, - this._formatDate - ) - )} -
`; + const ruleText = renderRRuleAsText(this.hass, value); + if (ruleText !== undefined) { + return html`
${ruleText}
`; } - return html`
Cannot convert recurrence rule
`; } catch (e) { return "Error while processing the rule"; } } - private _translateRRuleElement = (id: string | number | Weekday): string => { - if (typeof id === "string") { - return this.hass.localize(`ui.components.calendar.event.rrule.${id}`); - } - - return ""; - }; - - private _formatDate = (year: number, month: string, day: number): string => { - if (!year || !month || !day) { - return ""; - } - - // Build date so we can then format it - const date = new Date(); - date.setFullYear(year); - // As input we already get the localized month name, so we now unfortunately - // need to convert it back to something Date can work with. The already localized - // months names are a must in the RRule.Language structure (an empty string[] would - // mean we get undefined months input in this method here). - date.setMonth(monthNames(this.hass.locale).indexOf(month)); - date.setDate(day); - return formatDate(date, this.hass.locale); - }; - private _formatDateRange() { // Parse a dates in the browser timezone const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index b7dacd23b3..613a048f7e 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -248,6 +248,8 @@ class DialogCalendarEventEditor extends LitElement { = new Set(); + @state() private _monthlyRepeat?: string; + + @state() private _monthlyRepeatWeekday?: Weekday; + + @state() private _monthday?: number; + @state() private _end: RepeatEnd = "never"; @state() private _count?: number; @state() private _until?: Date; + @query("#monthly") private _monthlyRepeatSelect!: HaSelect; + private _allWeekdays?: WeekdayStr[]; + private _monthlyRepeatItems: MonthlyRepeatItem[] = []; + protected willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); @@ -61,12 +80,45 @@ export class RecurrenceRuleEditor extends LitElement { ); } - if (!changedProps.has("value") || this._computedRRule === this.value) { + if (changedProps.has("dtstart") || changedProps.has("_interval")) { + this._monthlyRepeatItems = this.dtstart + ? getMonthlyRepeatItems(this.hass, this._interval, this.dtstart) + : []; + this._computeWeekday(); + const selectElement = this._monthlyRepeatSelect; + if (selectElement) { + const oldSelected = selectElement.index; + selectElement.select(-1); + this.updateComplete.then(() => { + selectElement.select(changedProps.has("dtstart") ? 0 : oldSelected); + }); + } + } + + if ( + changedProps.has("timezone") || + changedProps.has("_freq") || + changedProps.has("_interval") || + changedProps.has("_weekday") || + changedProps.has("_monthlyRepeatWeekday") || + changedProps.has("_monthday") || + changedProps.has("_end") || + changedProps.has("_count") || + changedProps.has("_until") + ) { + this._updateRule(); + return; + } + + if (this._computedRRule === this.value) { return; } this._interval = 1; this._weekday.clear(); + this._monthlyRepeat = undefined; + this._monthday = undefined; + this._monthlyRepeatWeekday = undefined; this._end = "never"; this._count = undefined; this._until = undefined; @@ -88,6 +140,14 @@ export class RecurrenceRuleEditor extends LitElement { if (rrule.interval) { this._interval = rrule.interval; } + this._monthlyRepeatWeekday = getMonthlyRepeatWeekdayFromRule(rrule); + if (this._monthlyRepeatWeekday) { + this._monthlyRepeat = `BYDAY=${this._monthlyRepeatWeekday.toString()}`; + } + this._monthday = getMonthdayRepeatFromRule(rrule); + if (this._monthday) { + this._monthlyRepeat = `BYMONTHDAY=${this._monthday}`; + } if ( this._freq === "weekly" && rrule.byweekday && @@ -129,7 +189,28 @@ export class RecurrenceRuleEditor extends LitElement { } renderMonthly() { - return this.renderInterval(); + return html` + ${this.renderInterval()} + ${this._monthlyRepeatItems.length > 0 + ? html` + ${this._monthlyRepeatItems!.map( + (item) => html` + + ${item.label} + + ` + )} + ` + : html``} + `; } renderWeekly() { @@ -222,7 +303,6 @@ export class RecurrenceRuleEditor extends LitElement { private _onIntervalChange(e: Event) { this._interval = (e.target! as any).value; - this._updateRule(); } private _onRepeatSelected(e: CustomEvent>) { @@ -233,9 +313,20 @@ export class RecurrenceRuleEditor extends LitElement { } if (this._freq !== "weekly") { this._weekday.clear(); + this._computeWeekday(); } e.stopPropagation(); - this._updateRule(); + } + + private _onMonthlyDetailSelected(e: CustomEvent>) { + e.stopPropagation(); + const selectedItem = this._monthlyRepeatItems[e.detail.index]; + if (!selectedItem) { + return; + } + this._monthlyRepeat = selectedItem.value; + this._monthlyRepeatWeekday = selectedItem.byday; + this._monthday = selectedItem.bymonthday; } private _onWeekdayToggle(e: MouseEvent) { @@ -246,7 +337,6 @@ export class RecurrenceRuleEditor extends LitElement { } else { this._weekday.delete(value); } - this._updateRule(); } private _onEndSelected(e: CustomEvent>) { @@ -270,31 +360,47 @@ export class RecurrenceRuleEditor extends LitElement { this._until = undefined; } e.stopPropagation(); - this._updateRule(); } private _onCountChange(e: Event) { this._count = (e.target! as any).value; - this._updateRule(); } private _onUntilChange(e: CustomEvent) { e.stopPropagation(); this._until = new Date(e.detail.value); - this._updateRule(); + } + + // Reset the weekday selected when there is only a single value + private _computeWeekday() { + if (this.dtstart && this._weekday.size <= 1) { + const weekdayNum = getWeekday(this.dtstart); + this._weekday.clear(); + this._weekday.add(new Weekday(weekdayNum).toString() as WeekdayStr); + } } private _computeRRule() { if (this._freq === undefined || this._freq === "none") { return ""; } - const options = { + let byweekday: Weekday[] | undefined; + let bymonthday: number | undefined; + if (this._freq === "monthly" && this._monthlyRepeatWeekday !== undefined) { + byweekday = [this._monthlyRepeatWeekday]; + } else if (this._freq === "monthly" && this._monthday !== undefined) { + bymonthday = this._monthday; + } else if (this._freq === "weekly") { + byweekday = ruleByWeekDay(this._weekday); + } + const options: Partial = { freq: convertRepeatFrequency(this._freq!)!, interval: this._interval > 1 ? this._interval : undefined, - byweekday: ruleByWeekDay(this._weekday), count: this._count, until: this._until, tzid: this.timezone, + byweekday: byweekday, + bymonthday: bymonthday, }; const contentline = RRule.optionsToString(options); return contentline.slice(6); // Strip "RRULE:" prefix diff --git a/src/panels/calendar/recurrence.ts b/src/panels/calendar/recurrence.ts index dce91b7d6b..331a57cf5b 100644 --- a/src/panels/calendar/recurrence.ts +++ b/src/panels/calendar/recurrence.ts @@ -1,8 +1,22 @@ // Library for converting back and forth from values use by this webcomponent // and the values defined by rrule.js. -import { RRule, Frequency, Weekday } from "rrule"; -import type { WeekdayStr } from "rrule"; -import { addDays, addMonths, addWeeks, addYears } from "date-fns"; +import { + addDays, + addMonths, + addWeeks, + addYears, + getDate, + getDay, + isLastDayOfMonth, + isSameMonth, +} from "date-fns"; +import type { Options, WeekdayStr } from "rrule"; +import { Frequency, RRule, Weekday } from "rrule"; +import { formatDate } from "../../common/datetime/format_date"; +import { capitalizeFirstLetter } from "../../common/string/capitalize-first-letter"; +import { dayNames } from "../../common/translations/day_names"; +import { monthNames } from "../../common/translations/month_names"; +import { HomeAssistant } from "../../types"; export type RepeatFrequency = | "none" @@ -21,6 +35,13 @@ export const DEFAULT_COUNT = { daily: 30, }; +export interface MonthlyRepeatItem { + value: string; + byday?: Weekday; + bymonthday?: number; + label: string; +} + export function intervalSuffix(freq: RepeatFrequency) { if (freq === "monthly") { return "months"; @@ -101,7 +122,16 @@ export const WEEKDAYS = [ RRule.SA, ]; -export function getWeekdays(firstDay?: number) { +/** Return a weekday number compatible with rrule.js weekdays */ +export function getWeekday(dtstart: Date): number { + let weekDay = getDay(dtstart) - 1; + if (weekDay < 0) { + weekDay += 7; + } + return weekDay; +} + +export function getWeekdays(firstDay?: number): Weekday[] { if (firstDay === undefined || firstDay === 0) { return WEEKDAYS; } @@ -114,9 +144,7 @@ export function getWeekdays(firstDay?: number) { return weekDays; } -export function ruleByWeekDay( - weekdays: Set -): Weekday[] | undefined { +export function ruleByWeekDay(weekdays: Set): Weekday[] { return Array.from(weekdays).map((value: string) => { switch (value) { case "MO": @@ -138,3 +166,127 @@ export function ruleByWeekDay( } }); } + +/** + * Determine the recurrence options based on the day of the month. The + * return values are a Weekday object that represent a BYDAY for a + * particular week of the month like "first Saturday" or "last Friday". + */ +function getWeekydaysForMonth(dtstart: Date): Weekday[] { + const weekDay = getWeekday(dtstart); + const dayOfMonth = getDate(dtstart); + const nthWeekdayOfMonth = Math.floor((dayOfMonth - 1) / 7) + 1; + const isLastWeekday = !isSameMonth(dtstart, addDays(dtstart, 7)); + const byweekdays: Weekday[] = []; + if (!isLastWeekday || dayOfMonth <= 28) { + byweekdays.push(new Weekday(weekDay, nthWeekdayOfMonth)); + } + if (isLastWeekday) { + byweekdays.push(new Weekday(weekDay, -1)); + } + return byweekdays; +} + +/** + * Returns the list of repeat values available for the specified date. + */ +export function getMonthlyRepeatItems( + hass: HomeAssistant, + interval: number, + dtstart: Date +): MonthlyRepeatItem[] { + const getLabel = (repeatValue: string) => + renderRRuleAsText(hass, `FREQ=MONTHLY;INTERVAL=${interval};${repeatValue}`); + + const result: MonthlyRepeatItem[] = [ + // The default repeat rule is on day of month e.g. 3rd day of month + { + value: `BYMONTHDAY=${getDate(dtstart)}`, + label: getLabel(`BYMONTHDAY=${getDate(dtstart)}`)!, + }, + // Additional optional rules based on the week of month e.g. 2nd sunday of month + ...getWeekydaysForMonth(dtstart).map((item) => ({ + value: `BYDAY=${item.toString()}`, + byday: item, + label: getLabel(`BYDAY=${item.toString()}`)!, + })), + ]; + if (isLastDayOfMonth(dtstart)) { + result.push({ + value: "BYMONTHDAY=-1", + bymonthday: -1, + label: getLabel(`BYMONTHDAY=-1`)!, + }); + } + return result; +} + +export function getMonthlyRepeatWeekdayFromRule( + rrule: Partial +): Weekday | undefined { + if (rrule.freq !== Frequency.MONTHLY) { + return undefined; + } + if ( + rrule.byweekday && + Array.isArray(rrule.byweekday) && + rrule.byweekday.length === 1 && + rrule.byweekday[0] instanceof Weekday + ) { + return rrule.byweekday[0]; + } + return undefined; +} + +export function getMonthdayRepeatFromRule( + rrule: Partial +): number | undefined { + if (rrule.freq !== Frequency.MONTHLY || !rrule.bymonthday) { + return undefined; + } + if (Array.isArray(rrule.bymonthday)) { + return rrule.bymonthday[0]; + } + return rrule.bymonthday; +} + +/** + * A wrapper around RRule.toText that assists with translation. + */ +export function renderRRuleAsText(hass: HomeAssistant, value: string) { + const rule = RRule.fromString(`RRULE:${value}`); + if (!rule.isFullyConvertibleToText()) { + return undefined; + } + return capitalizeFirstLetter( + rule.toText( + (id: string | number | Weekday): string => { + if (typeof id === "string") { + return hass.localize(`ui.components.calendar.event.rrule.${id}`); + } + return ""; + }, + { + dayNames: dayNames(hass.locale), + monthNames: monthNames(hass.locale), + tokens: {}, + }, + // Format the date + (year: number, month: string, day: number): string => { + if (!year || !month || !day) { + return ""; + } + // Build date so we can then format it + const date = new Date(); + date.setFullYear(year); + // As input we already get the localized month name, so we now unfortunately + // need to convert it back to something Date can work with. The already localized + // months names are a must in the RRule.Language structure (an empty string[] would + // mean we get undefined months input in this method here). + date.setMonth(monthNames(hass.locale).indexOf(month)); + date.setDate(day); + return formatDate(date, hass.locale); + } + ) + ); +} From 1198f983aa1e8a487e691c41d4022d28f28368c1 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 28 Dec 2022 05:24:32 -0800 Subject: [PATCH 16/56] Prevent duplicate entities from being chosen in the target picker (#14882) Co-authored-by: Bram Kragten fixes undefined --- src/components/device/ha-device-picker.ts | 20 +++++++++++++++-- src/components/ha-area-picker.ts | 27 +++++++++++++++++++---- src/components/ha-target-picker.ts | 10 +++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index d93e717b27..c51032eee9 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -84,6 +84,14 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; + /** + * List of devices to be excluded. + * @type {Array} + * @attr exclude-devices + */ + @property({ type: Array, attribute: "exclude-devices" }) + public excludeDevices?: string[]; + @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; @property({ type: Boolean }) public disabled?: boolean; @@ -104,7 +112,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { includeDomains: this["includeDomains"], excludeDomains: this["excludeDomains"], includeDeviceClasses: this["includeDeviceClasses"], - deviceFilter: this["deviceFilter"] + deviceFilter: this["deviceFilter"], + excludeDevices: this["excludeDevices"] ): Device[] => { if (!devices.length) { return [ @@ -164,6 +173,12 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { }); } + if (excludeDevices) { + inputDevices = inputDevices.filter( + (device) => !excludeDevices!.includes(device.id) + ); + } + if (includeDeviceClasses) { inputDevices = inputDevices.filter((device) => { const devEntities = deviceEntityLookup[device.id]; @@ -258,7 +273,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { this.includeDomains, this.excludeDomains, this.includeDeviceClasses, - this.deviceFilter + this.deviceFilter, + this.excludeDevices ); } } diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index ec242ba8cc..d45f4fdb89 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -73,6 +73,14 @@ export class HaAreaPicker extends LitElement { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; + /** + * List of areas to be excluded. + * @type {Array} + * @attr exclude-areas + */ + @property({ type: Array, attribute: "exclude-areas" }) + public excludeAreas?: string[]; + @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; @property() public entityFilter?: (entity: EntityRegistryEntry) => boolean; @@ -109,7 +117,8 @@ export class HaAreaPicker extends LitElement { includeDeviceClasses: this["includeDeviceClasses"], deviceFilter: this["deviceFilter"], entityFilter: this["entityFilter"], - noAdd: this["noAdd"] + noAdd: this["noAdd"], + excludeAreas: this["excludeAreas"] ): AreaRegistryEntry[] => { if (!areas.length) { return [ @@ -235,6 +244,12 @@ export class HaAreaPicker extends LitElement { outputAreas = areas.filter((area) => areaIds!.includes(area.area_id)); } + if (excludeAreas) { + outputAreas = outputAreas.filter( + (area) => !excludeAreas!.includes(area.area_id) + ); + } + if (!outputAreas.length) { outputAreas = [ { @@ -264,7 +279,7 @@ export class HaAreaPicker extends LitElement { (this._init && changedProps.has("_opened") && this._opened) ) { this._init = true; - (this.comboBox as any).items = this._getAreas( + const areas = this._getAreas( Object.values(this.hass.areas), Object.values(this.hass.devices), Object.values(this.hass.entities), @@ -273,8 +288,11 @@ export class HaAreaPicker extends LitElement { this.includeDeviceClasses, this.deviceFilter, this.entityFilter, - this.noAdd + this.noAdd, + this.excludeAreas ); + (this.comboBox as any).items = areas; + (this.comboBox as any).filteredItems = areas; } } @@ -384,7 +402,8 @@ export class HaAreaPicker extends LitElement { this.includeDeviceClasses, this.deviceFilter, this.entityFilter, - this.noAdd + this.noAdd, + this.excludeAreas ); await this.updateComplete; await this.comboBox.updateComplete; diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 09336afc65..3aa581b441 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -345,6 +345,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { .entityFilter=${this.entityRegFilter} .includeDeviceClasses=${this.includeDeviceClasses} .includeDomains=${this.includeDomains} + .excludeAreas=${ensureArray(this.value?.area_id)} @value-changed=${this._targetPicked} > `; @@ -360,6 +361,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { .deviceFilter=${this.deviceFilter} .includeDeviceClasses=${this.includeDeviceClasses} .includeDomains=${this.includeDomains} + .excludeDevices=${ensureArray(this.value?.device_id)} @value-changed=${this._targetPicked} > `; @@ -375,6 +377,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { .entityFilter=${this.entityFilter} .includeDeviceClasses=${this.includeDeviceClasses} .includeDomains=${this.includeDomains} + .excludeEntities=${ensureArray(this.value?.entity_id)} @value-changed=${this._targetPicked} allow-custom-entity > @@ -392,6 +395,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { const target = ev.currentTarget; target.value = ""; this._addMode = undefined; + if ( + this.value && + this.value[target.type] && + ensureArray(this.value[target.type]).includes(value) + ) { + return; + } fireEvent(this, "value-changed", { value: this.value ? { From e926091e5476c05d36dde6c95eb1657d1290d58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Wed, 28 Dec 2022 14:25:45 +0100 Subject: [PATCH 17/56] Sort strings by locale language (#14533) --- .../addon-store/hassio-addon-repository.ts | 4 +- hassio/src/dashboard/hassio-addons.ts | 8 +++- .../hardware/dialog-hassio-hardware.ts | 12 ++++-- .../dialog-hassio-repositories.ts | 4 +- src/common/string/compare.ts | 40 +++++++++++++++++-- src/components/country-datalist.ts | 8 ++-- src/components/currency-datalist.ts | 8 ++-- .../device/ha-area-devices-picker.ts | 3 +- src/components/device/ha-device-picker.ts | 2 +- src/components/entity/ha-entity-picker.ts | 6 ++- src/components/entity/ha-statistic-picker.ts | 4 +- src/components/ha-addon-picker.ts | 4 +- src/components/ha-blueprint-picker.ts | 4 +- src/components/ha-config-entry-picker.ts | 3 +- src/components/ha-sidebar.ts | 26 +++++++----- src/components/user/ha-user-picker.ts | 4 +- src/data/device_registry.ts | 7 +++- src/data/entity_registry.ts | 7 +++- src/data/update.ts | 10 +++-- src/dialogs/quick-bar/ha-quick-bar.ts | 12 +++++- src/onboarding/onboarding-core-config.ts | 8 +++- src/onboarding/onboarding-integrations.ts | 2 +- .../config/areas/ha-config-area-page.ts | 16 ++++++-- .../automation/action/ha-automation-action.ts | 2 +- .../types/ha-automation-action-condition.ts | 2 +- .../condition/ha-automation-condition.ts | 2 +- .../trigger/ha-automation-trigger.ts | 2 +- .../types/ha-automation-trigger-tag.ts | 6 ++- src/panels/config/cloud/alexa/cloud-alexa.ts | 3 +- .../cloud-google-assistant.ts | 3 +- .../config/core/ha-config-section-general.ts | 6 ++- .../ha-device-via-devices-card.ts | 3 +- .../config/devices/ha-config-device-page.ts | 3 +- .../entities/entity-registry-settings.ts | 8 +++- .../hardware/dialog-hardware-available.ts | 12 ++++-- .../integrations/dialog-add-integration.ts | 14 ++++++- .../integrations/ha-config-integrations.ts | 3 +- .../integrations/ha-domain-integrations.ts | 3 +- .../integration-panels/zha/zha-device-card.ts | 3 +- .../ha-config-lovelace-dashboards.ts | 11 ++++- .../resources/ha-config-lovelace-resources.ts | 2 +- src/panels/config/person/ha-config-person.ts | 7 ++-- src/panels/config/zone/ha-config-zone.ts | 5 ++- .../developer-tools/event/events-list.js | 4 +- .../view-editor/hui-view-visibility-editor.ts | 4 +- 45 files changed, 231 insertions(+), 79 deletions(-) diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts index a2eb5157e3..0d22c49ad2 100644 --- a/hassio/src/addon-store/hassio-addon-repository.ts +++ b/hassio/src/addon-store/hassio-addon-repository.ts @@ -29,7 +29,9 @@ class HassioAddonRepositoryEl extends LitElement { if (filter) { return filterAndSort(addons, filter); } - return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); + return addons.sort((a, b) => + caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) + ); }); protected render(): TemplateResult { diff --git a/hassio/src/dashboard/hassio-addons.ts b/hassio/src/dashboard/hassio-addons.ts index cc855ccc69..87aaa545a3 100644 --- a/hassio/src/dashboard/hassio-addons.ts +++ b/hassio/src/dashboard/hassio-addons.ts @@ -35,7 +35,13 @@ class HassioAddons extends LitElement { ` : this.supervisor.addon.addons - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) + .sort((a, b) => + caseInsensitiveStringCompare( + a.name, + b.name, + this.hass.locale.language + ) + ) .map( (addon) => html` + ( + showAdvanced: boolean, + hardware: HassioHardwareInfo, + filter: string, + language: string + ) => hardware.devices .filter( (device) => @@ -28,7 +33,7 @@ const _filterDevices = memoizeOne( .toLocaleLowerCase() .includes(filter)) ) - .sort((a, b) => stringCompare(a.name, b.name)) + .sort((a, b) => stringCompare(a.name, b.name, language)) ); @customElement("dialog-hassio-hardware") @@ -56,7 +61,8 @@ class HassioHardwareDialog extends LitElement { const devices = _filterDevices( this.hass.userData?.showAdvanced || false, this._dialogParams.hardware, - (this._filter || "").toLowerCase() + (this._filter || "").toLowerCase(), + this.hass.locale.language ); return html` diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts index 82325db788..0a11027766 100644 --- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts +++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts @@ -68,7 +68,9 @@ class HassioRepositoriesDialog extends LitElement { repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons repo.slug !== "5c53de3b" // The ESPHome repository ) - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) + .sort((a, b) => + caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) + ) ); private _filteredUsedRepositories = memoizeOne( diff --git a/src/common/string/compare.ts b/src/common/string/compare.ts index 67ef4c87bd..a14e087ccb 100644 --- a/src/common/string/compare.ts +++ b/src/common/string/compare.ts @@ -1,4 +1,15 @@ -export const stringCompare = (a: string, b: string) => { +import memoizeOne from "memoize-one"; + +const collator = memoizeOne( + (language: string | undefined) => new Intl.Collator(language) +); + +const caseInsensitiveCollator = memoizeOne( + (language: string | undefined) => + new Intl.Collator(language, { sensitivity: "accent" }) +); + +const fallbackStringCompare = (a: string, b: string) => { if (a < b) { return -1; } @@ -9,5 +20,28 @@ export const stringCompare = (a: string, b: string) => { return 0; }; -export const caseInsensitiveStringCompare = (a: string, b: string) => - stringCompare(a.toLowerCase(), b.toLowerCase()); +export const stringCompare = ( + a: string, + b: string, + language: string | undefined = undefined +) => { + // @ts-ignore + if (Intl?.Collator) { + return collator(language).compare(a, b); + } + + return fallbackStringCompare(a, b); +}; + +export const caseInsensitiveStringCompare = ( + a: string, + b: string, + language: string | undefined = undefined +) => { + // @ts-ignore + if (Intl?.Collator) { + return caseInsensitiveCollator(language).compare(a, b); + } + + return fallbackStringCompare(a.toLowerCase(), b.toLowerCase()); +}; diff --git a/src/components/country-datalist.ts b/src/components/country-datalist.ts index b4295f99ad..7692e58c0c 100644 --- a/src/components/country-datalist.ts +++ b/src/components/country-datalist.ts @@ -266,14 +266,16 @@ export const getCountryOptions = memoizeOne((language?: string) => { value: country, label: countryDisplayNames ? countryDisplayNames.of(country)! : country, })); - options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label)); + options.sort((a, b) => + caseInsensitiveStringCompare(a.label, b.label, language) + ); return options; }); -export const createCountryListEl = () => { +export const createCountryListEl = (language?: string) => { const list = document.createElement("datalist"); list.id = "countries"; - const options = getCountryOptions(); + const options = getCountryOptions(language); for (const country of options) { const option = document.createElement("option"); option.value = country.value; diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts index 1339c47520..439d7b9211 100644 --- a/src/components/currency-datalist.ts +++ b/src/components/currency-datalist.ts @@ -173,14 +173,16 @@ export const getCurrencyOptions = memoizeOne((language?: string) => { value: currency, label: currencyDisplayNames ? currencyDisplayNames.of(currency)! : currency, })); - options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label)); + options.sort((a, b) => + caseInsensitiveStringCompare(a.label, b.label, language) + ); return options; }); -export const createCurrencyListEl = () => { +export const createCurrencyListEl = (language: string) => { const list = document.createElement("datalist"); list.id = "currencies"; - for (const currency of getCurrencyOptions()) { + for (const currency of getCurrencyOptions(language)) { const option = document.createElement("option"); option.value = currency.value; option.innerText = currency.label; diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index 169a49f518..3edee20f5a 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -189,7 +189,8 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { .sort((a, b) => stringCompare( devicesByArea[a].name || "", - devicesByArea[b].name || "" + devicesByArea[b].name || "", + this.hass.locale.language ) ) .map((key) => devicesByArea[key]); diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index c51032eee9..95a3332fe0 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -231,7 +231,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { return outputDevices; } return outputDevices.sort((a, b) => - stringCompare(a.name || "", b.name || "") + stringCompare(a.name || "", b.name || "", this.hass.locale.language) ); } ); diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 056918602a..e2f2339c1c 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -174,7 +174,8 @@ export class HaEntityPicker extends LitElement { .sort((entityA, entityB) => caseInsensitiveStringCompare( entityA.friendly_name, - entityB.friendly_name + entityB.friendly_name, + this.hass.locale.language ) ); } @@ -205,7 +206,8 @@ export class HaEntityPicker extends LitElement { .sort((entityA, entityB) => caseInsensitiveStringCompare( entityA.friendly_name, - entityB.friendly_name + entityB.friendly_name, + this.hass.locale.language ) ); diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index a19ee9e011..59c942d95e 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -177,7 +177,9 @@ export class HaStatisticPicker extends LitElement { } if (output.length > 1) { - output.sort((a, b) => stringCompare(a.name || "", b.name || "")); + output.sort((a, b) => + stringCompare(a.name || "", b.name || "", this.hass.locale.language) + ); } output.push({ diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index cd8b7c4bf5..955bb6fefb 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -80,7 +80,9 @@ class HaAddonPicker extends LitElement { const addonsInfo = await fetchHassioAddonsInfo(this.hass); this._addons = addonsInfo.addons .filter((addon) => addon.version) - .sort((a, b) => stringCompare(a.name, b.name)); + .sort((a, b) => + stringCompare(a.name, b.name, this.hass.locale.language) + ); } else { showAlertDialog(this, { title: this.hass.localize( diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index 58a0f03f4a..0dfc64c3a9 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -46,7 +46,9 @@ class HaBluePrintPicker extends LitElement { ...(blueprint as Blueprint).metadata, path, })); - return result.sort((a, b) => stringCompare(a.name, b.name)); + return result.sort((a, b) => + stringCompare(a.name, b.name, this.hass!.locale.language) + ); }); protected render(): TemplateResult { diff --git a/src/components/ha-config-entry-picker.ts b/src/components/ha-config-entry-picker.ts index 733598b003..7bd4301bfe 100644 --- a/src/components/ha-config-entry-picker.ts +++ b/src/components/ha-config-entry-picker.ts @@ -121,7 +121,8 @@ class HaConfigEntryPicker extends LitElement { .sort((conf1, conf2) => caseInsensitiveStringCompare( conf1.localized_domain_name + conf1.title, - conf2.localized_domain_name + conf2.title + conf2.localized_domain_name + conf2.title, + this.hass.locale.language ) ); }); diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 30c4959335..1173b0c7a7 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -87,7 +87,8 @@ const panelSorter = ( reverseSort: string[], defaultPanel: string, a: PanelInfo, - b: PanelInfo + b: PanelInfo, + language: string ) => { const indexA = reverseSort.indexOf(a.url_path); const indexB = reverseSort.indexOf(b.url_path); @@ -97,13 +98,14 @@ const panelSorter = ( } return -1; } - return defaultPanelSorter(defaultPanel, a, b); + return defaultPanelSorter(defaultPanel, a, b, language); }; const defaultPanelSorter = ( defaultPanel: string, a: PanelInfo, - b: PanelInfo + b: PanelInfo, + language: string ) => { // Put all the Lovelace at the top. const aLovelace = a.component_name === "lovelace"; @@ -117,7 +119,7 @@ const defaultPanelSorter = ( } if (aLovelace && bLovelace) { - return stringCompare(a.title!, b.title!); + return stringCompare(a.title!, b.title!, language); } if (aLovelace && !bLovelace) { return -1; @@ -139,7 +141,7 @@ const defaultPanelSorter = ( return 1; } // both not built in, sort by title - return stringCompare(a.title!, b.title!); + return stringCompare(a.title!, b.title!, language); }; const computePanels = memoizeOne( @@ -147,7 +149,8 @@ const computePanels = memoizeOne( panels: HomeAssistant["panels"], defaultPanel: HomeAssistant["defaultPanel"], panelsOrder: string[], - hiddenPanels: string[] + hiddenPanels: string[], + locale: HomeAssistant["locale"] ): [PanelInfo[], PanelInfo[]] => { if (!panels) { return [[], []]; @@ -171,8 +174,12 @@ const computePanels = memoizeOne( const reverseSort = [...panelsOrder].reverse(); - beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b)); - afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b)); + beforeSpacer.sort((a, b) => + panelSorter(reverseSort, defaultPanel, a, b, locale.language) + ); + afterSpacer.sort((a, b) => + panelSorter(reverseSort, defaultPanel, a, b, locale.language) + ); return [beforeSpacer, afterSpacer]; } @@ -374,7 +381,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { this.hass.panels, this.hass.defaultPanel, this._panelOrder, - this._hiddenPanels + this._hiddenPanels, + this.hass.locale ); // Show the supervisor as beeing part of configuration diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index 6bbc3c1af3..50ec6b4611 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -30,7 +30,9 @@ class HaUserPicker extends LitElement { return users .filter((user) => !user.system_generated) - .sort((a, b) => stringCompare(a.name, b.name)); + .sort((a, b) => + stringCompare(a.name, b.name, this.hass!.locale.language) + ); }); protected render(): TemplateResult { diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index a026221819..bc1437adfa 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -123,9 +123,12 @@ export const subscribeDeviceRegistry = ( onChange ); -export const sortDeviceRegistryByName = (entries: DeviceRegistryEntry[]) => +export const sortDeviceRegistryByName = ( + entries: DeviceRegistryEntry[], + language: string +) => entries.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name || "", entry2.name || "") + caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); export const getDeviceEntityLookup = ( diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 44e1b3679c..c402f2afd7 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -164,9 +164,12 @@ export const subscribeEntityRegistry = ( onChange ); -export const sortEntityRegistryByName = (entries: EntityRegistryEntry[]) => +export const sortEntityRegistryByName = ( + entries: EntityRegistryEntry[], + language: string +) => entries.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name || "", entry2.name || "") + caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); export const entityRegistryById = memoizeOne( diff --git a/src/data/update.ts b/src/data/update.ts index 802c1d0554..cc352c5116 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -68,7 +68,10 @@ export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) => entity_id: entityId, }); -export const filterUpdateEntities = (entities: HassEntities) => +export const filterUpdateEntities = ( + entities: HassEntities, + language?: string +) => ( Object.values(entities).filter( (entity) => computeStateDomain(entity) === "update" @@ -94,7 +97,8 @@ export const filterUpdateEntities = (entities: HassEntities) => } return caseInsensitiveStringCompare( a.attributes.title || a.attributes.friendly_name || "", - b.attributes.title || b.attributes.friendly_name || "" + b.attributes.title || b.attributes.friendly_name || "", + language ); }); @@ -110,7 +114,7 @@ export const checkForEntityUpdates = async ( element: HTMLElement, hass: HomeAssistant ) => { - const entities = filterUpdateEntities(hass.states).map( + const entities = filterUpdateEntities(hass.states, hass.locale.language).map( (entity) => entity.entity_id ); diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 8b62007c9a..730ef8ad68 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -484,7 +484,11 @@ export class QuickBar extends LitElement { }; }) .sort((a, b) => - caseInsensitiveStringCompare(a.primaryText, b.primaryText) + caseInsensitiveStringCompare( + a.primaryText, + b.primaryText, + this.hass.locale.language + ) ); } @@ -494,7 +498,11 @@ export class QuickBar extends LitElement { ...this._generateServerControlCommands(), ...(await this._generateNavigationCommands()), ].sort((a, b) => - caseInsensitiveStringCompare(a.strings.join(" "), b.strings.join(" ")) + caseInsensitiveStringCompare( + a.strings.join(" "), + b.strings.join(" "), + this.hass.locale.language + ) ); } diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts index d1d732d51c..3aa5b91549 100644 --- a/src/onboarding/onboarding-core-config.ts +++ b/src/onboarding/onboarding-core-config.ts @@ -271,7 +271,9 @@ class OnboardingCoreConfig extends LitElement { "[name=currency]" ) as HaTextField; curInput.updateComplete.then(() => { - curInput.shadowRoot!.appendChild(createCurrencyListEl()); + curInput.shadowRoot!.appendChild( + createCurrencyListEl(this.hass.locale.language) + ); curInput.formElement.setAttribute("list", "currencies"); }); @@ -279,7 +281,9 @@ class OnboardingCoreConfig extends LitElement { "[name=country]" ) as HaTextField; countryInput.updateComplete.then(() => { - countryInput.shadowRoot!.appendChild(createCountryListEl()); + countryInput.shadowRoot!.appendChild( + createCountryListEl(this.hass.locale.language) + ); countryInput.formElement.setAttribute("list", "countries"); }); diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 263ce23108..e35cdf15e2 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -117,7 +117,7 @@ class OnboardingIntegrations extends LitElement { } ); const content = [...entries, ...discovered] - .sort((a, b) => stringCompare(a[0], b[0])) + .sort((a, b) => stringCompare(a[0], b[0], this.hass.locale.language)) .map((item) => item[1]); return html` diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index e4e3911d2d..2b5627e180 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -192,13 +192,13 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { devices.forEach((entry) => { entry.name = computeDeviceName(entry, this.hass); }); - sortDeviceRegistryByName(devices); + sortDeviceRegistryByName(devices, this.hass.locale.language); } if (entities) { entities.forEach((entry) => { entry.name = computeEntityRegistryName(this.hass, entry); }); - sortEntityRegistryByName(entities); + sortEntityRegistryByName(entities, this.hass.locale.language); } // Group entities by domain @@ -507,7 +507,11 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } }); groupedEntities.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name!, entry2.name!) + caseInsensitiveStringCompare( + entry1.name!, + entry2.name!, + this.hass.locale.language + ) ); } if (relatedEntityIds?.length) { @@ -521,7 +525,11 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } }); relatedEntities.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name!, entry2.name!) + caseInsensitiveStringCompare( + entry1.name!, + entry2.name!, + this.hass.locale.language + ) ); } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 6c4827a633..a35fef3b91 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -299,7 +299,7 @@ export default class HaAutomationAction extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index a21b4bb9d5..6123678139 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -66,7 +66,7 @@ export class HaConditionAction extends LitElement implements ActionElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); private _conditionChanged(ev: CustomEvent) { diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 2b0bd7e1c2..4d318d09cd 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -328,7 +328,7 @@ export default class HaAutomationCondition extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 74534e457a..c53738ef86 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -302,7 +302,7 @@ export default class HaAutomationTrigger extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index 4d09932c75..e9c13c24fb 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -54,7 +54,11 @@ export class HaTagTrigger extends LitElement implements TriggerElement { private async _fetchTags() { this._tags = (await fetchTags(this.hass)).sort((a, b) => - caseInsensitiveStringCompare(a.name || a.id, b.name || b.id) + caseInsensitiveStringCompare( + a.name || a.id, + b.name || b.id, + this.hass.locale.language + ) ); } diff --git a/src/panels/config/cloud/alexa/cloud-alexa.ts b/src/panels/config/cloud/alexa/cloud-alexa.ts index 0ac078e360..ad9edd2bdf 100644 --- a/src/panels/config/cloud/alexa/cloud-alexa.ts +++ b/src/panels/config/cloud/alexa/cloud-alexa.ts @@ -349,7 +349,8 @@ class CloudAlexa extends SubscribeMixin(LitElement) { const stateB = this.hass.states[b.entity_id]; return stringCompare( stateA ? computeStateName(stateA) : a.entity_id, - stateB ? computeStateName(stateB) : b.entity_id + stateB ? computeStateName(stateB) : b.entity_id, + this.hass.locale.language ); }); this._entities = entities; diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index da22c0fc0b..75d14227cc 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -402,7 +402,8 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { const stateB = this.hass.states[b.entity_id]; return stringCompare( stateA ? computeStateName(stateA) : a.entity_id, - stateB ? computeStateName(stateB) : b.entity_id + stateB ? computeStateName(stateB) : b.entity_id, + this.hass.locale.language ); }); this._entities = entities; diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index 462f23fd9a..900b5ccdac 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -304,7 +304,11 @@ class HaConfigSectionGeneral extends LitElement { } this._languages = Object.entries(this.hass.translationMetadata.translations) .sort((a, b) => - caseInsensitiveStringCompare(a[1].nativeName, b[1].nativeName) + caseInsensitiveStringCompare( + a[1].nativeName, + b[1].nativeName, + this.hass.locale.language + ) ) .map(([value, metaData]) => ({ value, diff --git a/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts b/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts index 3a0c276d6f..b437c42938 100644 --- a/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts @@ -31,7 +31,8 @@ export class HaDeviceViaDevicesCard extends LitElement { .sort((d1, d2) => caseInsensitiveStringCompare( computeDeviceName(d1, this.hass), - computeDeviceName(d2, this.hass) + computeDeviceName(d2, this.hass), + this.hass.locale.language ) ) ); diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 143f2a8f51..db7d20c342 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -157,7 +157,8 @@ export class HaConfigDevicePage extends LitElement { .sort((ent1, ent2) => stringCompare( ent1.stateName || `zzz${ent1.entity_id}`, - ent2.stateName || `zzz${ent2.entity_id}` + ent2.stateName || `zzz${ent2.entity_id}`, + this.hass.locale.language ) ) ); diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 615485d399..2bd09c640b 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1191,7 +1191,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { domain: entry, label: domainToName(localize, entry), })) - .sort((a, b) => stringCompare(a.label, b.label)) + .sort((a, b) => + stringCompare(a.label, b.label, this.hass.locale.language) + ) ); private _deviceClassesSorted = memoizeOne( @@ -1203,7 +1205,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { `ui.dialogs.entity_registry.editor.device_classes.${domain}.${entry}` ), })) - .sort((a, b) => stringCompare(a.label, b.label)) + .sort((a, b) => + stringCompare(a.label, b.label, this.hass.locale.language) + ) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/hardware/dialog-hardware-available.ts b/src/panels/config/hardware/dialog-hardware-available.ts index aee0f828e7..d99a15c723 100644 --- a/src/panels/config/hardware/dialog-hardware-available.ts +++ b/src/panels/config/hardware/dialog-hardware-available.ts @@ -20,7 +20,12 @@ import { haStyle, haStyleDialog } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; const _filterDevices = memoizeOne( - (showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) => + ( + showAdvanced: boolean, + hardware: HassioHardwareInfo, + filter: string, + language: string + ) => hardware.devices .filter( (device) => @@ -33,7 +38,7 @@ const _filterDevices = memoizeOne( .toLocaleLowerCase() .includes(filter)) ) - .sort((a, b) => stringCompare(a.name, b.name)) + .sort((a, b) => stringCompare(a.name, b.name, language)) ); @customElement("ha-dialog-hardware-available") @@ -70,7 +75,8 @@ class DialogHardwareAvailable extends LitElement implements HassDialog { const devices = _filterDevices( this.hass.userData?.showAdvanced || false, this._hardware, - (this._filter || "").toLowerCase() + (this._filter || "").toLowerCase(), + this.hass.locale.language ); return html` diff --git a/src/panels/config/integrations/dialog-add-integration.ts b/src/panels/config/integrations/dialog-add-integration.ts index fb8367ee15..15b52126d4 100644 --- a/src/panels/config/integrations/dialog-add-integration.ts +++ b/src/panels/config/integrations/dialog-add-integration.ts @@ -147,7 +147,13 @@ class AddIntegrationDialog extends LitElement { is_built_in: true, is_add: true, })) - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); + .sort((a, b) => + caseInsensitiveStringCompare( + a.name, + b.name, + this.hass.locale.language + ) + ); const integrations: IntegrationListItem[] = []; const yamlIntegrations: IntegrationListItem[] = []; @@ -242,7 +248,11 @@ class AddIntegrationDialog extends LitElement { return [ ...addDeviceRows, ...integrations.sort((a, b) => - caseInsensitiveStringCompare(a.name || "", b.name || "") + caseInsensitiveStringCompare( + a.name || "", + b.name || "", + this.hass.locale.language + ) ), ]; } diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 3ba289ae99..aaea690b57 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -230,7 +230,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { (conf1, conf2) => caseInsensitiveStringCompare( conf1.localized_domain_name + conf1.title, - conf2.localized_domain_name + conf2.title + conf2.localized_domain_name + conf2.title, + this.hass.locale.language ) ); }, diff --git a/src/panels/config/integrations/ha-domain-integrations.ts b/src/panels/config/integrations/ha-domain-integrations.ts index 21f75f28d3..32a281247f 100644 --- a/src/panels/config/integrations/ha-domain-integrations.ts +++ b/src/panels/config/integrations/ha-domain-integrations.ts @@ -121,7 +121,8 @@ class HaDomainIntegrations extends LitElement { } return caseInsensitiveStringCompare( a[1].name || domainToName(this.hass.localize, a[0]), - b[1].name || domainToName(this.hass.localize, b[0]) + b[1].name || domainToName(this.hass.localize, b[0]), + this.hass.locale.language ); }) .map( diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts index f357a9bdf9..ab15c017d6 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts @@ -50,7 +50,8 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) { .sort((ent1, ent2) => stringCompare( ent1.stateName || `zzz${ent1.entity_id}`, - ent2.stateName || `zzz${ent2.entity_id}` + ent2.stateName || `zzz${ent2.entity_id}`, + this.hass.locale.language ) ) ); diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index afa5d71126..e055214469 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -230,7 +230,9 @@ export class HaConfigLovelaceDashboards extends LitElement { result.push( ...dashboards - .sort((a, b) => stringCompare(a.title, b.title)) + .sort((a, b) => + stringCompare(a.title, b.title, this.hass.locale.language) + ) .map((dashboard) => ({ filename: "", ...dashboard, @@ -342,7 +344,12 @@ export class HaConfigLovelaceDashboards extends LitElement { createDashboard: async (values: LovelaceDashboardCreateParams) => { const created = await createDashboard(this.hass!, values); this._dashboards = this._dashboards!.concat(created).sort( - (res1, res2) => stringCompare(res1.url_path, res2.url_path) + (res1, res2) => + stringCompare( + res1.url_path, + res2.url_path, + this.hass.locale.language + ) ); }, updateDashboard: async (values) => { diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index b93afcc17d..411d288fbe 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -143,7 +143,7 @@ export class HaConfigLovelaceRescources extends LitElement { createResource: async (values) => { const created = await createResource(this.hass!, values); this._resources = this._resources!.concat(created).sort((res1, res2) => - stringCompare(res1.url, res2.url) + stringCompare(res1.url, res2.url, this.hass!.locale.language) ); loadLovelaceResources([created], this.hass!.auth.data.hassUrl); }, diff --git a/src/panels/config/person/ha-config-person.ts b/src/panels/config/person/ha-config-person.ts index 87d16caf94..ffd68efeec 100644 --- a/src/panels/config/person/ha-config-person.ts +++ b/src/panels/config/person/ha-config-person.ts @@ -156,10 +156,10 @@ class HaConfigPerson extends LitElement { const personData = await fetchPersons(this.hass!); this._storageItems = personData.storage.sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._configItems = personData.config.sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._openDialogIfPersonSpecifiedInRoute(); } @@ -221,7 +221,8 @@ class HaConfigPerson extends LitElement { createEntry: async (values) => { const created = await createPerson(this.hass!, values); this._storageItems = this._storageItems!.concat(created).sort( - (ent1, ent2) => stringCompare(ent1.name, ent2.name) + (ent1, ent2) => + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); }, updateEntry: async (values) => { diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 7089e7735a..d46e313ff4 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -296,7 +296,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { private async _fetchData() { this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._getStates(); } @@ -411,7 +411,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { private async _createEntry(values: ZoneMutableParams) { const created = await createZone(this.hass!, values); this._storageItems = this._storageItems!.concat(created).sort( - (ent1, ent2) => stringCompare(ent1.name, ent2.name) + (ent1, ent2) => + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); if (this.narrow) { return; diff --git a/src/panels/developer-tools/event/events-list.js b/src/panels/developer-tools/event/events-list.js index 89b07d6933..a6893d269e 100644 --- a/src/panels/developer-tools/event/events-list.js +++ b/src/panels/developer-tools/event/events-list.js @@ -58,7 +58,9 @@ class EventsList extends EventsMixin(LocalizeMixin(PolymerElement)) { connectedCallback() { super.connectedCallback(); this.hass.callApi("GET", "events").then((events) => { - this.events = events.sort((e1, e2) => stringCompare(e1.event, e2.event)); + this.events = events.sort((e1, e2) => + stringCompare(e1.event, e2.event, this.hass.locale.language) + ); }); } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts index 727325d07c..42765e0d2c 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts @@ -42,7 +42,9 @@ export class HuiViewVisibilityEditor extends LitElement { @state() private _visible!: boolean | ShowViewConfig[]; private _sortedUsers = memoizeOne((users: User[]) => - users.sort((a, b) => stringCompare(a.name, b.name)) + users.sort((a, b) => + stringCompare(a.name, b.name, this.hass.locale.language) + ) ); protected firstUpdated(changedProps: PropertyValues) { From 2fbe6809c11c235246a6ae12180fa2c8fe7c8413 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 28 Dec 2022 14:40:38 +0100 Subject: [PATCH 18/56] Bumped version to 20221228.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5641dfcf15..18a9d7c5f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221213.0" +version = "20221228.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 5c2fcd7f9b6cedaee3b51ec74cb7febc5ae719e6 Mon Sep 17 00:00:00 2001 From: Gia Ferrari Date: Wed, 28 Dec 2022 17:16:05 -0800 Subject: [PATCH 19/56] Add alt attribute to various images (#14405) * ha-config-area-page: Add alt tag for area-picture * dialog-tag-detail: Add alt tag for generated QR code image. * ha-config-hardware: Blank alt tag for hardware pic, info already elsewhere * dialog-energy-solar-settings: Blank alt tag for brand icon. * ha-energy-grid-settings: Blank alt tag for co2signal brand icon. * Add a few more appropriately-blank alt texts. * ha-config-device-page: Logo alt text set to name of device domain. * ha-config-repairs: Logo alt text set to name of issue domain. * hui-picture-card(-editor): Alternate Text via config (blank default) * hui-picture-entity-card(-editor): Alternate Text via config (blank default) * ha-long-lived-access-token-dialog: Alt text for QR code. * hui-picture-header-footer: Support alt text via optional property. * A few more blank alt attributes. * ha-tile-image: Support alt tag (but it is blank in current usage). * prod cla-bot * Lint. Fix whitespace. * Add missing alt text properties to TS types. * Fix my silly typo in picture-entity-card-editor's SCHEMA (+ minor reformat) * Add alt_text to Picture(Entity)CardConfig TypeScript types. * Format with prettier. * Revise alt text for tag QR * Revise alt text for token QR * Revise alternate to alternative * Add alt to logo in gallery * Add alt text to crop image * Use ifDefined for tile image alt * Change area picture alt to area name * Remove entry from entities config struct * Revert altText changes for Picture Entity Card (to revisit in future PR) See: https://github.com/home-assistant/frontend/pull/14405#discussion_r1032735871 * Revert changes to hui-image and picture entity editor Co-authored-by: Steve Repsher --- gallery/src/pages/components/ha-alert.ts | 4 +++- hassio/src/addon-view/info/hassio-addon-info.ts | 1 + src/components/ha-addon-picker.ts | 6 +++++- src/components/ha-config-entry-picker.ts | 1 + src/components/tile/ha-tile-image.ts | 7 ++++++- .../image-cropper-dialog/image-cropper-dialog.ts | 2 +- src/onboarding/integration-badge.ts | 1 + src/panels/config/areas/ha-config-area-page.ts | 3 ++- .../config/devices/ha-config-device-page.ts | 4 ++++ .../energy/components/ha-energy-grid-settings.ts | 2 ++ .../dialogs/dialog-energy-solar-settings.ts | 1 + src/panels/config/hardware/ha-config-hardware.ts | 2 +- .../config/integrations/ha-domain-integrations.ts | 1 + .../config/integrations/ha-integration-header.ts | 1 + .../integrations/ha-integration-list-item.ts | 1 + src/panels/config/repairs/ha-config-repairs.ts | 1 + .../config/repairs/integrations-startup-time.ts | 1 + src/panels/config/tags/dialog-tag-detail.ts | 9 ++++++++- src/panels/lovelace/cards/hui-picture-card.ts | 5 ++++- src/panels/lovelace/cards/types.ts | 1 + .../config-elements/hui-picture-card-editor.ts | 15 +++++++++++++++ .../header-footer/hui-picture-header-footer.ts | 1 + src/panels/lovelace/header-footer/structs.ts | 1 + src/panels/lovelace/header-footer/types.ts | 1 + .../profile/ha-long-lived-access-token-dialog.ts | 9 ++++++++- src/translations/en.json | 8 ++++++-- 26 files changed, 78 insertions(+), 11 deletions(-) diff --git a/gallery/src/pages/components/ha-alert.ts b/gallery/src/pages/components/ha-alert.ts index ced4c5a44b..a0d3f309fb 100644 --- a/gallery/src/pages/components/ha-alert.ts +++ b/gallery/src/pages/components/ha-alert.ts @@ -98,7 +98,9 @@ const alerts: { description: "Alert with slotted image", type: "warning", iconSlot: html`Home Assistant logo`, }, { diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 3cc3d23010..a6e8c99217 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -404,6 +404,7 @@ class HassioAddonInfo extends LitElement { ? html` ` diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index 955bb6fefb..8c765d9755 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -16,7 +16,11 @@ const rowRenderer: ComboBoxLitRenderer = ( ${item.name} ${item.slug} ${item.icon - ? html`` + ? html`` : ""} `; diff --git a/src/components/ha-config-entry-picker.ts b/src/components/ha-config-entry-picker.ts index 7bd4301bfe..09bfc45b4e 100644 --- a/src/components/ha-config-entry-picker.ts +++ b/src/components/ha-config-entry-picker.ts @@ -59,6 +59,7 @@ class HaConfigEntryPicker extends LitElement { > ${item.localized_domain_name} - ${this.imageUrl ? html`` : null} + ${this.imageUrl + ? html`${ifDefined(this.imageAlt)}` + : null} `; } diff --git a/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts b/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts index 74e3711235..94b6d22065 100644 --- a/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts +++ b/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts @@ -74,7 +74,7 @@ export class HaImagecropperDialog extends LitElement { round: Boolean(this._params?.options.round), })}" > - + ${this.hass.localize("ui.dialogs.image_cropper.crop_image")} ${this.hass.localize("ui.common.cancel")} diff --git a/src/onboarding/integration-badge.ts b/src/onboarding/integration-badge.ts index 62e48d1b00..fc440b1589 100644 --- a/src/onboarding/integration-badge.ts +++ b/src/onboarding/integration-badge.ts @@ -19,6 +19,7 @@ class IntegrationBadge extends LitElement { return html`
${area.picture ? html`
- + ${imageURL - ? html`` + ? html`` : ""} ${boardName || diff --git a/src/panels/config/integrations/ha-domain-integrations.ts b/src/panels/config/integrations/ha-domain-integrations.ts index 32a281247f..f62461b553 100644 --- a/src/panels/config/integrations/ha-domain-integrations.ts +++ b/src/panels/config/integrations/ha-domain-integrations.ts @@ -48,6 +48,7 @@ class HaDomainIntegrations extends LitElement { hasMeta >
${domainToName(this.hass.localize, `; + this._qrCode = html`${this.hass.localize(`; } static get styles(): CSSResultGroup { diff --git a/src/panels/lovelace/cards/hui-picture-card.ts b/src/panels/lovelace/cards/hui-picture-card.ts index 20a2a2f315..ffcf4f5009 100644 --- a/src/panels/lovelace/cards/hui-picture-card.ts +++ b/src/panels/lovelace/cards/hui-picture-card.ts @@ -101,7 +101,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard { ), })} > - + ${this._config.alt_text} `; } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 5ab627b466..2ad69eeb94 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -332,6 +332,7 @@ export interface PictureCardConfig extends LovelaceCardConfig { hold_action?: ActionConfig; double_tap_action?: ActionConfig; theme?: string; + alt_text?: string; } export interface PictureElementsCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts index 8174a049e2..ee75869fc6 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts @@ -20,6 +20,7 @@ const cardConfigStruct = assign( tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), theme: optional(string()), + alt_text: optional(string()), }) ); @@ -53,6 +54,10 @@ export class HuiPictureCardEditor return this._config!.theme || ""; } + get _alt_text(): string { + return this._config!.alt_text || ""; + } + protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; @@ -72,6 +77,16 @@ export class HuiPictureCardEditor .configValue=${"image"} @input=${this._valueChanged} > + `; + this._qrCode = html`${this.hass.localize(`; } static get styles(): CSSResultGroup { diff --git a/src/translations/en.json b/src/translations/en.json index a6329fb139..f0b5e00104 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -791,7 +791,8 @@ "close": "Close" }, "image_cropper": { - "crop": "Crop" + "crop": "Crop", + "crop_image": "Picture to crop" }, "date-picker": { "today": "Today" @@ -1435,6 +1436,7 @@ "confirm_remove_title": "Remove tag?", "confirm_remove": "Are you sure you want to remove tag {tag}?", "automation_title": "Tag {name} is scanned", + "qr_code_image": "QR code for tag {name}", "headers": { "icon": "Icon", "name": "Name", @@ -4195,6 +4197,7 @@ "description": "The Light card allows you to change the brightness of the light." }, "generic": { + "alt_text": "Alternative Text", "aspect_ratio": "Aspect Ratio", "attribute": "Attribute", "camera_image": "Camera Entity", @@ -4598,7 +4601,8 @@ "name": "Name", "prompt_name": "Give the token a name", "prompt_copy_token": "Copy your access token. It will not be shown again.", - "empty_state": "You have no long-lived access tokens yet." + "empty_state": "You have no long-lived access tokens yet.", + "qr_code_image": "QR code for token {name}" } }, "shopping_list": { From a9378abe31600240511948f5ad22a64913896947 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 28 Dec 2022 21:06:48 -0800 Subject: [PATCH 20/56] Fix days missing from ha-base-time-input _valueChanged (#14910) * Fix days missing from ha-base-time-input _valueChanged * style change --- src/components/ha-base-time-input.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 9ada34f871..8db3e132e1 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -266,6 +266,9 @@ export class HaBaseTimeInput extends LitElement { seconds: this.seconds, milliseconds: this.milliseconds, }; + if (this.enableDay) { + value.days = this.days; + } if (this.format === 12) { value.amPm = this.amPm; } From f1d644ac518c9595de12a8a4a43302ec5953cfe1 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 30 Dec 2022 13:05:44 +0100 Subject: [PATCH 21/56] Add mV as unit for sensor device_class voltage (#14921) --- src/panels/config/entities/entity-registry-settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 2bd09c640b..bd9f7db055 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -125,6 +125,7 @@ const OVERRIDE_SENSOR_UNITS = { pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"], speed: ["ft/s", "in/d", "in/h", "km/h", "kn", "m/s", "mm/d", "mm/h", "mph"], temperature: ["°C", "°F", "K"], + voltage: ["V", "mV"], volume: ["CCF", "fl. oz.", "ft³", "gal", "L", "mL", "m³"], water: ["CCF", "ft³", "gal", "L", "m³"], weight: ["g", "kg", "lb", "mg", "oz", "st", "µg"], From a16e41a7ac48c5da5f77d929d442f00746789fff Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 30 Dec 2022 13:06:11 +0100 Subject: [PATCH 22/56] Add support for unit conversion of electric current (#14916) --- src/panels/config/entities/entity-registry-settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index bd9f7db055..72f80712fe 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -118,6 +118,7 @@ const OVERRIDE_NUMBER_UNITS = { }; const OVERRIDE_SENSOR_UNITS = { + current: ["A", "mA"], distance: ["cm", "ft", "in", "km", "m", "mi", "mm", "yd"], gas: ["CCF", "ft³", "m³"], precipitation: ["cm", "in", "mm"], From 4901d509183aeb1b7771b5e068334c34b3dada4a Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 30 Dec 2022 04:15:07 -0800 Subject: [PATCH 23/56] Fix All Day recurring events that end on a specific date (#14905) --- src/panels/calendar/dialog-calendar-event-editor.ts | 1 + src/panels/calendar/ha-recurrence-rule-editor.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index 613a048f7e..043064a300 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -250,6 +250,7 @@ class DialogCalendarEventEditor extends LitElement { Date: Fri, 30 Dec 2022 13:21:47 +0100 Subject: [PATCH 24/56] Bumped version to 20221230.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 18a9d7c5f0..5095848ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221228.0" +version = "20221230.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 0374330676121755e47c2735fa3f1667a0e1266d Mon Sep 17 00:00:00 2001 From: 930913 <3722064+930913@users.noreply.github.com> Date: Sat, 31 Dec 2022 20:38:21 +0000 Subject: [PATCH 25/56] Add helper text to select slider (#14884) * Add helper text to select slider * Make helper text conditional Only show if set. Co-authored-by: Bram Kragten * Lint changes Co-authored-by: Bram Kragten --- src/components/ha-form/ha-form-integer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ha-form/ha-form-integer.ts b/src/components/ha-form/ha-form-integer.ts index f09821e3d7..86de2d310c 100644 --- a/src/components/ha-form/ha-form-integer.ts +++ b/src/components/ha-form/ha-form-integer.ts @@ -67,6 +67,9 @@ export class HaFormInteger extends LitElement implements HaFormElement { @change=${this._valueChanged} >
+ ${this.helper + ? html`${this.helper}` + : ""}
`; } From 86ea3082f7fab01979a814ccb87135dccfcad2f5 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 2 Jan 2023 12:10:52 +0100 Subject: [PATCH 26/56] Add aliases editor for helpers (#14951) --- .../settings/entity-settings-helper-tab.ts | 8 ++- .../entities/entity-registry-basic-editor.ts | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts index 0ad83b15df..59b6471cc4 100644 --- a/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts +++ b/src/panels/config/entities/editor-tabs/settings/entity-settings-helper-tab.ts @@ -57,7 +57,13 @@ export class EntityRegistrySettingsHelper extends LitElement { super.updated(changedProperties); if (changedProperties.has("entry")) { this._error = undefined; - this._item = undefined; + if ( + this.entry.unique_id !== + (changedProperties.get("entry") as ExtEntityRegistryEntry)?.unique_id + ) { + this._item = undefined; + } + this._getItem(); } } diff --git a/src/panels/config/entities/entity-registry-basic-editor.ts b/src/panels/config/entities/entity-registry-basic-editor.ts index 3d915e2db4..f984f7a771 100644 --- a/src/panels/config/entities/entity-registry-basic-editor.ts +++ b/src/panels/config/entities/entity-registry-basic-editor.ts @@ -1,7 +1,11 @@ import "@material/mwc-formfield/mwc-formfield"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import { mdiPencil } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; @@ -21,6 +25,7 @@ import { import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../types"; +import { showEntityAliasesDialog } from "./entity-aliases/show-dialog-entity-aliases"; @customElement("ha-registry-basic-editor") export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { @@ -44,6 +49,20 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { @state() private _submitting = false; + private _openAliasesSettings() { + showEntityAliasesDialog(this, { + entity: this.entry!, + updateEntry: async (updates) => { + const result = await updateEntityRegistryEntry( + this.hass, + this.entry.entity_id, + updates + ); + fireEvent(this, "entity-entry-updated", result.entity_entry); + }, + }); + } + public async updateEntry(): Promise { this._submitting = true; const params: Partial = { @@ -247,6 +266,37 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
` : ""} + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases_section" + )} +
+ + 0} + hasMeta + @click=${this._openAliasesSettings} + > + + ${this.entry.aliases.length > 0 + ? this.hass.localize( + "ui.dialogs.entity_registry.editor.configured_aliases", + { count: this.entry.aliases.length } + ) + : this.hass.localize( + "ui.dialogs.entity_registry.editor.no_aliases" + )} + + ${this.entry.aliases.join(", ")} + + + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.aliases.description" + )} +
`; } @@ -300,6 +350,13 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { .label { margin-top: 16px; } + .aliases { + border-radius: 4px; + margin-top: 4px; + margin-bottom: 4px; + --mdc-icon-button-size: 24px; + overflow: hidden; + } `; } } From bdef9244269853485856434ac2b9700ff27670d9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 2 Jan 2023 18:50:12 +0100 Subject: [PATCH 27/56] Enable unit conversion for DATA_RATE (#14902) Co-authored-by: Bram Kragten --- .../config/entities/entity-registry-settings.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 72f80712fe..177f9bfc15 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -119,6 +119,19 @@ const OVERRIDE_NUMBER_UNITS = { const OVERRIDE_SENSOR_UNITS = { current: ["A", "mA"], + data_rate: [ + "bit/s", + "kbit/s", + "Mbit/s", + "Gbit/s", + "B/s", + "kB/s", + "MB/s", + "GB/s", + "KiB/s", + "MiB/s", + "GiB/s", + ], distance: ["cm", "ft", "in", "km", "m", "mi", "mm", "yd"], gas: ["CCF", "ft³", "m³"], precipitation: ["cm", "in", "mm"], From fe874663515405df13681508aee9c44b067ad6f0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 Jan 2023 20:42:31 +0100 Subject: [PATCH 28/56] Add link to aliases in cloud config entity settings (#14959) --- demo/src/ha-demo.ts | 2 - gallery/src/pages/misc/integration-card.ts | 1 - src/components/entity/state-info.ts | 2 +- src/data/entity_registry.ts | 2 +- .../config/automation/ha-automation-trace.ts | 5 -- src/panels/config/cloud/alexa/cloud-alexa.ts | 64 ++++++++++------- .../cloud-google-assistant.ts | 71 ++++++++++++------- .../show-dialog-entity-aliases.ts | 4 +- .../config/entities/ha-config-entities.ts | 1 - src/panels/config/script/ha-script-trace.ts | 5 -- src/translations/en.json | 4 +- 11 files changed, 92 insertions(+), 69 deletions(-) diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index 37a707952a..070fea8b44 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -71,7 +71,6 @@ class HaDemo extends HomeAssistantAppEl { entity_category: null, has_entity_name: false, unique_id: "co2_intensity", - aliases: [], }, { config_entry_id: "co2signal", @@ -87,7 +86,6 @@ class HaDemo extends HomeAssistantAppEl { entity_category: null, has_entity_name: false, unique_id: "grid_fossil_fuel_percentage", - aliases: [], }, ]); diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 8bd6acf6c8..56de4308e8 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -197,7 +197,6 @@ const createEntityRegistryEntries = ( platform: "updater", has_entity_name: false, unique_id: "updater", - aliases: [], }, ]; diff --git a/src/components/entity/state-info.ts b/src/components/entity/state-info.ts index 7ee6c6ba39..9db1bae9f7 100644 --- a/src/components/entity/state-info.ts +++ b/src/components/entity/state-info.ts @@ -28,7 +28,7 @@ class StateInfo extends LitElement { const name = computeStateName(this.stateObj); - return html` - ${entity.interfaces - .filter((ifc) => !IGNORE_INTERFACES.includes(ifc)) - .map((ifc) => ifc.replace(/(Alexa.|Controller)/g, "")) - .join(", ")} + ${entity.entity_id in this.hass.entities + ? html`` + : ""} ${!emptyFilter ? html`${iconButton}` @@ -323,23 +329,33 @@ class CloudAlexa extends SubscribeMixin(LitElement) { if (changedProps.has("cloudStatus")) { this._entityConfigs = this.cloudStatus.prefs.alexa_entity_configs; } + if ( + changedProps.has("hass") && + changedProps.get("hass")?.entities !== this.hass.entities + ) { + const categories = {}; + + for (const entry of Object.values(this.hass.entities)) { + categories[entry.entity_id] = entry.entity_category; + } + + this._entityCategories = categories; + } } - protected override hassSubscribe(): ( - | UnsubscribeFunc - | Promise - )[] { - return [ - subscribeEntityRegistry(this.hass.connection, (entries) => { - const categories = {}; - - for (const entry of entries) { - categories[entry.entity_id] = entry.entity_category; - } - - this._entityCategories = categories; - }), - ]; + private async _openAliasesSettings(ev) { + ev.stopPropagation(); + const entityId = ev.target.entityId; + const entry = await getExtendedEntityRegistryEntry(this.hass, entityId); + if (!entry) { + return; + } + showEntityAliasesDialog(this, { + entity: entry, + updateEntry: async (updates) => { + await updateEntityRegistryEntry(this.hass, entry.entity_id, updates); + }, + }); } private async _fetchData() { diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index 75d14227cc..92bf480175 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -9,7 +9,6 @@ import { mdiFormatListChecks, mdiSync, } from "@mdi/js"; -import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -41,7 +40,8 @@ import { } from "../../../../data/cloud"; import { EntityRegistryEntry, - subscribeEntityRegistry, + getExtendedEntityRegistryEntry, + updateEntityRegistryEntry, } from "../../../../data/entity_registry"; import { fetchCloudGoogleEntities, @@ -51,15 +51,15 @@ import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; import "../../../../layouts/hass-loading-screen"; import "../../../../layouts/hass-subpage"; -import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; -import { haStyle } from "../../../../resources/styles"; +import { buttonLinkStyle, haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import { showToast } from "../../../../util/toast"; +import { showEntityAliasesDialog } from "../../entities/entity-aliases/show-dialog-entity-aliases"; const DEFAULT_CONFIG_EXPOSE = true; @customElement("cloud-google-assistant") -class CloudGoogleAssistant extends SubscribeMixin(LitElement) { +class CloudGoogleAssistant extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public cloudStatus!: CloudStatusLoggedIn; @@ -174,15 +174,23 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { secondary-line @click=${this._showMoreInfo} > - ${entity.traits - .map((trait) => trait.substr(trait.lastIndexOf(".") + 1)) - .join(", ")} + ${entity.entity_id in this.hass.entities + ? html`` + : ""} ${!emptyFilter ? html`${iconButton}` : html` ${iconButton} @@ -308,7 +316,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { ${!this.narrow ? this.hass!.localize( - "ui.panel.config.cloud.alexa.exposed", + "ui.panel.config.cloud.google.exposed", "selected", selected ) @@ -329,7 +337,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { ${!this.narrow ? this.hass!.localize( - "ui.panel.config.cloud.alexa.not_exposed", + "ui.panel.config.cloud.google.not_exposed", "selected", this._entities.length - selected ) @@ -354,23 +362,33 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { if (changedProps.has("cloudStatus")) { this._entityConfigs = this.cloudStatus.prefs.google_entity_configs; } + if ( + changedProps.has("hass") && + changedProps.get("hass")?.entities !== this.hass.entities + ) { + const categories = {}; + + for (const entry of Object.values(this.hass.entities)) { + categories[entry.entity_id] = entry.entity_category; + } + + this._entityCategories = categories; + } } - protected override hassSubscribe(): ( - | UnsubscribeFunc - | Promise - )[] { - return [ - subscribeEntityRegistry(this.hass.connection, (entries) => { - const categories = {}; - - for (const entry of entries) { - categories[entry.entity_id] = entry.entity_category; - } - - this._entityCategories = categories; - }), - ]; + private async _openAliasesSettings(ev) { + ev.stopPropagation(); + const entityId = ev.target.entityId; + const entry = await getExtendedEntityRegistryEntry(this.hass, entityId); + if (!entry) { + return; + } + showEntityAliasesDialog(this, { + entity: entry, + updateEntry: async (updates) => { + await updateEntityRegistryEntry(this.hass, entry.entity_id, updates); + }, + }); } private _configIsDomainExposed( @@ -583,6 +601,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { static get styles(): CSSResultGroup { return [ haStyle, + buttonLinkStyle, css` mwc-list-item > [slot="meta"] { margin-left: 4px; diff --git a/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts b/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts index d3fec599ec..0e9cbd9357 100644 --- a/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts +++ b/src/panels/config/entities/entity-aliases/show-dialog-entity-aliases.ts @@ -1,11 +1,11 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { - EntityRegistryEntry, EntityRegistryEntryUpdateParams, + ExtEntityRegistryEntry, } from "../../../../data/entity_registry"; export interface EntityAliasesDialogParams { - entity: EntityRegistryEntry; + entity: ExtEntityRegistryEntry; updateEntry: ( updates: Partial ) => Promise; diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index cafff74d14..5fb9d6bf9c 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -728,7 +728,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { selectable: false, entity_category: null, has_entity_name: false, - aliases: [], }); } if (changed) { diff --git a/src/panels/config/script/ha-script-trace.ts b/src/panels/config/script/ha-script-trace.ts index ae001665ca..a8addf7bd8 100644 --- a/src/panels/config/script/ha-script-trace.ts +++ b/src/panels/config/script/ha-script-trace.ts @@ -527,15 +527,10 @@ export class HaScriptTrace extends LitElement { :host([narrow]) .graph { max-width: 100%; } - .info { flex: 1; background-color: var(--card-background-color); } - - .linkButton { - color: var(--primary-text-color); - } .trace-link { text-decoration: none; } diff --git a/src/translations/en.json b/src/translations/en.json index f0b5e00104..91de8f9abe 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2634,7 +2634,7 @@ "enable_state_reporting": "Enable State Reporting", "info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Amazon. This allows you to always see the latest states in the Alexa app and use the state changes to create routines.", "state_reporting_error": "Unable to {enable_disable} report state.", - "manage_entities": "Manage Entities", + "manage_entities": "[%key:ui::panel::config::cloud::account::google::manage_entities%]", "enable": "enable", "disable": "disable", "not_configured_title": "Alexa is not activated", @@ -2685,6 +2685,7 @@ "follow_domain": "[%key:ui::panel::config::cloud::google::follow_domain%]", "exposed": "[%key:ui::panel::config::cloud::google::exposed%]", "not_exposed": "[%key:ui::panel::config::cloud::google::not_exposed%]", + "manage_aliases": "[%key:ui::panel::config::cloud::google::manage_aliases%]", "expose": "Expose to Alexa", "sync_entities": "Synchronize entities", "sync_entities_error": "Failed to sync entities:" @@ -2710,6 +2711,7 @@ "follow_domain": "Follow domain", "exposed": "{selected} exposed", "not_exposed": "{selected} not exposed", + "manage_aliases": "Manage aliases", "sync_to_google": "Synchronizing changes to Google.", "sync_entities": "Synchronize entities", "sync_entities_error": "Failed to sync entities:", From afcd45a780338ac64f40d1a07fb12edd2d49f3e3 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 2 Jan 2023 21:17:14 +0100 Subject: [PATCH 29/56] Enable unit conversion for DATA_SIZE (#14903) Co-authored-by: Bram Kragten --- .../entities/entity-registry-settings.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 177f9bfc15..9fc640993f 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -132,6 +132,29 @@ const OVERRIDE_SENSOR_UNITS = { "MiB/s", "GiB/s", ], + data_size: [ + "bit", + "kbit", + "Mbit", + "Gbit", + "B", + "kB", + "MB", + "GB", + "TB", + "PB", + "EB", + "ZB", + "YB", + "KiB", + "MiB", + "GiB", + "TiB", + "PiB", + "EiB", + "ZiB", + "YiB", + ], distance: ["cm", "ft", "in", "km", "m", "mi", "mm", "yd"], gas: ["CCF", "ft³", "m³"], precipitation: ["cm", "in", "mm"], From e3ac2c149d4b3bc2fb16078c2532543f7150ecd6 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 2 Jan 2023 12:19:36 -0800 Subject: [PATCH 30/56] Use translations for all fields in recurrence rule editor (#14940) Co-authored-by: Bram Kragten --- .../calendar/ha-recurrence-rule-editor.ts | 80 ++++++++++++++----- src/panels/calendar/recurrence.ts | 20 ----- src/translations/en.json | 27 +++++++ 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/panels/calendar/ha-recurrence-rule-editor.ts b/src/panels/calendar/ha-recurrence-rule-editor.ts index d2965e0284..5d8af6c2a9 100644 --- a/src/panels/calendar/ha-recurrence-rule-editor.ts +++ b/src/panels/calendar/ha-recurrence-rule-editor.ts @@ -6,6 +6,7 @@ import type { Options, WeekdayStr } from "rrule"; import { ByWeekday, RRule, Weekday } from "rrule"; import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { stopPropagation } from "../../common/dom/stop_propagation"; +import { LocalizeKeys } from "../../common/translations/localize"; import "../../components/ha-chip"; import "../../components/ha-list-item"; import "../../components/ha-select"; @@ -19,12 +20,10 @@ import { getWeekday, getWeekdays, getMonthlyRepeatItems, - intervalSuffix, RepeatEnd, RepeatFrequency, ruleByWeekDay, untilValue, - WEEKDAY_NAME, MonthlyRepeatItem, getMonthlyRepeatWeekdayFromRule, getMonthdayRepeatFromRule, @@ -174,18 +173,36 @@ export class RecurrenceRuleEditor extends LitElement { return html` - None - Yearly - Monthly - Weekly - Daily + + ${this.hass.localize("ui.components.calendar.event.repeat.freq.none")} + + + ${this.hass.localize( + "ui.components.calendar.event.repeat.freq.yearly" + )} + + + ${this.hass.localize( + "ui.components.calendar.event.repeat.freq.monthly" + )} + + + ${this.hass.localize( + "ui.components.calendar.event.repeat.freq.weekly" + )} + + + ${this.hass.localize( + "ui.components.calendar.event.repeat.freq.daily" + )} + `; } @@ -196,7 +213,9 @@ export class RecurrenceRuleEditor extends LitElement { ${this._monthlyRepeatItems.length > 0 ? html`${WEEKDAY_NAME[item]}${this.hass.localize( + `ui.components.calendar.event.repeat.weekly.weekday.${ + item.toLowerCase() as Lowercase + }` + )} ` )} @@ -241,11 +264,16 @@ export class RecurrenceRuleEditor extends LitElement { return html` `; @@ -255,26 +283,38 @@ export class RecurrenceRuleEditor extends LitElement { return html` - Never - After - On + + ${this.hass.localize("ui.components.calendar.event.repeat.end.never")} + + + ${this.hass.localize("ui.components.calendar.event.repeat.end.after")} + + + ${this.hass.localize("ui.components.calendar.event.repeat.end.on")} + ${this._end === "after" ? html` ` @@ -283,7 +323,9 @@ export class RecurrenceRuleEditor extends LitElement { ? html` Date: Mon, 2 Jan 2023 21:27:30 +0100 Subject: [PATCH 31/56] Do not close aliases dialog on enter (#14952) Co-authored-by: Bram Kragten --- src/components/ha-dialog.ts | 12 +++++++++++- .../entity-aliases/dialog-entity-aliases.ts | 18 +++++++++++++++--- .../entities/entity-registry-basic-editor.ts | 11 ++++------- .../entities/entity-registry-settings.ts | 11 ++++------- src/translations/en.json | 3 ++- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index f3995a7737..1e69ad2ac4 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -3,10 +3,12 @@ import { styles } from "@material/mwc-dialog/mwc-dialog.css"; import { mdiClose } from "@mdi/js"; import { css, html, TemplateResult } from "lit"; import { customElement } from "lit/decorators"; -import type { HomeAssistant } from "../types"; import { FOCUS_TARGET } from "../dialogs/make-dialog-manager"; +import type { HomeAssistant } from "../types"; import "./ha-icon-button"; +const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button"]; + export const createCloseHeading = ( hass: HomeAssistant, title: string | TemplateResult @@ -32,6 +34,14 @@ export class HaDialog extends DialogBase { return html` ${super.renderHeading()} `; } + protected firstUpdated(): void { + super.firstUpdated(); + this.suppressDefaultPressSelector = [ + this.suppressDefaultPressSelector, + SUPPRESS_DEFAULT_PRESS_SELECTOR, + ].join(", "); + } + static override styles = [ styles, css` diff --git a/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts index 29e3e012cc..9859bcf602 100644 --- a/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts +++ b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts @@ -72,16 +72,21 @@ class DialogEntityAliases extends LitElement { dialogInitialFocus=${index} .index=${index} class="flex-auto" - label="Alias" + .label=${this.hass!.localize( + "ui.dialogs.entity_registry.editor.aliases.input_label", + { number: index + 1 } + )} .value=${alias} ?data-last=${index === this._aliases.length - 1} - @change=${this._editAlias} + @input=${this._editAlias} + @keydown=${this._keyDownAlias} > { @@ -272,12 +273,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { "ui.dialogs.entity_registry.editor.aliases_section" )} - - 0} - hasMeta - @click=${this._openAliasesSettings} - > + + 0} hasMeta> ${this.entry.aliases.length > 0 ? this.hass.localize( diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 9fc640993f..5a318ff9ed 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -807,12 +807,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { "ui.dialogs.entity_registry.editor.aliases_section" )} - - 0} - hasMeta - @click=${this._openAliasesSettings} - > + + 0} hasMeta> ${this.entry.aliases.length > 0 ? this.hass.localize( @@ -1015,7 +1011,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { }); } - private _openAliasesSettings() { + private _handleAliasesClicked(ev: CustomEvent) { + if (ev.detail.index !== 0) return; showEntityAliasesDialog(this, { entity: this.entry!, updateEntry: async (updates) => { diff --git a/src/translations/en.json b/src/translations/en.json index ff490edd2c..df3e8b1c90 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1003,7 +1003,8 @@ "aliases": { "heading": "{name} aliases", "description": "Aliases are alternative names used in voice assistants to refer to this entity.", - "remove_alias": "Remove alias", + "remove_alias": "Remove alias {number}", + "input_label": "Alias {number}", "save": "Save", "add_alias": "Add alias", "no_aliases": "No aliases have been added yet", From 3cc1cb7893829ed541561f99c61b371eb05076f2 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 2 Jan 2023 12:27:50 -0800 Subject: [PATCH 32/56] Rollback calendar trigger day offset support (#14933) --- .../automation/trigger/types/ha-automation-trigger-calendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts index c9cccc4795..846d22a5d5 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -46,7 +46,7 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { ], ], }, - { name: "offset", selector: { duration: { enable_day: true } } }, + { name: "offset", selector: { duration: {} } }, { name: "offset_type", type: "select", From 44d91eaa4f2225e4e76931f5868618abd22c6835 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 Jan 2023 21:28:30 +0100 Subject: [PATCH 33/56] Bumped version to 20230102.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5095848ad9..776a80fa6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221230.0" +version = "20230102.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From f31a7c3af0a39f9aabadcbfe66d16b1593741274 Mon Sep 17 00:00:00 2001 From: Felipe Santos Date: Tue, 3 Jan 2023 07:09:18 -0300 Subject: [PATCH 34/56] Fix issue with reload not working sometimes (#14939) fixes undefined --- src/panels/lovelace/common/handle-action.ts | 2 +- src/panels/lovelace/hui-root.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts index 6f8865f7d2..ddbbc838f3 100644 --- a/src/panels/lovelace/common/handle-action.ts +++ b/src/panels/lovelace/common/handle-action.ts @@ -46,7 +46,7 @@ export const handleAction = async ( actionConfig.confirmation && (!actionConfig.confirmation.exemptions || !actionConfig.confirmation.exemptions.some( - (e) => e.user === hass!.user!.id + (e) => e.user === hass!.user?.id )) ) { forwardHaptic("warning"); diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 37208ba078..78cf52d702 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -266,7 +266,7 @@ class HUIRoot extends LitElement { ((Array.isArray(view.visible) && !view.visible.some( (e) => - e.user === this.hass!.user!.id + e.user === this.hass!.user?.id )) || view.visible === false)) ), @@ -470,7 +470,7 @@ class HUIRoot extends LitElement { view.visible !== undefined && ((Array.isArray(view.visible) && !view.visible.some( - (e) => e.user === this.hass!.user!.id + (e) => e.user === this.hass!.user?.id )) || view.visible === false) ), From 9836912efa5746483605b4f24a962777da07fde8 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 3 Jan 2023 14:58:30 +0100 Subject: [PATCH 35/56] Display zone name in state badge (#14974) --- src/components/entity/ha-state-label-badge.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 35fa54748e..c51309ccfb 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -223,6 +223,10 @@ export class HaStateLabelBadge extends LitElement { if (domainStateKey) { return this.hass!.localize(`state_badge.${domainStateKey}`); } + // Person and device tracker state can be zone name + if (domain === "person" || domain === "device_tracker") { + return entityState.state; + } if (domain === "timer") { return secondsToDuration(_timerTimeRemaining); } From bf6ad3d0a5049ddcf47ad140902f804a5ed27b2d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 3 Jan 2023 15:02:10 +0100 Subject: [PATCH 36/56] Replace ZMK by ZMW currency (#14975) --- src/components/currency-datalist.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts index 439d7b9211..ef29faf56c 100644 --- a/src/components/currency-datalist.ts +++ b/src/components/currency-datalist.ts @@ -157,7 +157,7 @@ export const CURRENCIES = [ "XPF", "YER", "ZAR", - "ZMK", + "ZMW", "ZWL", ]; From 18a69d633f9f29643e70a40ece02c7a20d4a1719 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 3 Jan 2023 15:02:16 +0100 Subject: [PATCH 37/56] Add padding to device action form (#14976) --- .../automation/action/types/ha-automation-action-device_id.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts index f4cf9cf800..37d641adba 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts @@ -158,6 +158,7 @@ export class HaDeviceAction extends LitElement { } ha-form { + display: block; margin-top: 24px; } `; From b36eba0916e6d676f7875f6183492404f05fd1b4 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 4 Jan 2023 10:44:39 +0100 Subject: [PATCH 38/56] Ensure calender event description can be edited (#14979) --- src/panels/calendar/dialog-calendar-event-editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index 043064a300..64a7aa3321 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -51,7 +51,7 @@ class DialogCalendarEventEditor extends LitElement { @state() private _summary = ""; - @state() private _description = ""; + @state() private _description? = ""; @state() private _rrule?: string; @@ -87,6 +87,7 @@ class DialogCalendarEventEditor extends LitElement { const entry = params.entry!; this._allDay = isDate(entry.dtstart); this._summary = entry.summary; + this._description = entry.description; this._rrule = entry.rrule; if (this._allDay) { this._dtstart = new Date(entry.dtstart + "T00:00:00"); From 9750e0e0b5b2ebaf4ef3a24e3c473146d4a908c2 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 4 Jan 2023 05:29:47 -0500 Subject: [PATCH 39/56] Ensure Lovelace `deviceEntries` lookup handles missing keys (#14980) fixes undefined --- src/panels/lovelace/common/generate-lovelace-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 07b99cb22d..661334392d 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -59,7 +59,7 @@ const splitByAreaDevice = ( for (const entity of Object.values(entityEntries)) { const areaId = entity.area_id || - (entity.device_id && deviceEntries[entity.device_id].area_id); + (entity.device_id && deviceEntries[entity.device_id]?.area_id); if (areaId && areaId in areaEntries && entity.entity_id in allEntities) { if (!(areaId in areasWithEntities)) { areasWithEntities[areaId] = []; From dd109b0054c7cf51b19b0ea4a55b0ea8b0206c75 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 4 Jan 2023 11:30:15 +0100 Subject: [PATCH 40/56] Fix lokalization of MQTT config entry panel re-configure button and title (#14915) --- .../integration-panels/mqtt/mqtt-config-panel.ts | 8 ++++++-- src/translations/en.json | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts index 0656ed360e..297ad0c9d9 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-config-panel.ts @@ -40,10 +40,14 @@ class HaPanelDevMqtt extends LitElement { return html`
- +
Re-configure MQTT${this.hass.localize( + "ui.panel.config.mqtt.reconfigure" + )}
diff --git a/src/translations/en.json b/src/translations/en.json index df3e8b1c90..7c66bcf349 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3245,6 +3245,8 @@ }, "mqtt": { "title": "MQTT", + "settings_title": "MQTT settings", + "reconfigure": "Re-configure MQTT", "description_publish": "Publish a packet", "topic": "Topic", "payload": "Payload (template allowed)", From f2fa4333269a0f596c3997fec5c211cdb8d2b53f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 4 Jan 2023 11:35:18 +0100 Subject: [PATCH 41/56] Bumped version to 20230104.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 776a80fa6b..9ecc1b760d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20230102.0" +version = "20230104.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 1585c6bf52e32b27486d0c26e2b7461656f9169a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jan 2023 14:55:09 -0500 Subject: [PATCH 42/56] Bump json5 from 1.0.1 to 1.0.2 (#14986) Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c81d28f903..0cb6839234 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10740,13 +10740,13 @@ fsevents@^1.2.7: linkType: hard "json5@npm:^1.0.1": - version: 1.0.1 - resolution: "json5@npm:1.0.1" + version: 1.0.2 + resolution: "json5@npm:1.0.2" dependencies: minimist: ^1.2.0 bin: json5: lib/cli.js - checksum: e76ea23dbb8fc1348c143da628134a98adf4c5a4e8ea2adaa74a80c455fc2cdf0e2e13e6398ef819bfe92306b610ebb2002668ed9fc1af386d593691ef346fc3 + checksum: 866458a8c58a95a49bef3adba929c625e82532bcff1fe93f01d29cb02cac7c3fe1f4b79951b7792c2da9de0b32871a8401a6e3c5b36778ad852bf5b8a61165d7 languageName: node linkType: hard From 616bced37c2024f625fd8e0aaab3f9ae11f5d5f7 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 4 Jan 2023 17:19:00 -0500 Subject: [PATCH 43/56] Bump @codemirror packages to version 6.x (#14969) --- package.json | 20 +-- src/components/ha-code-editor.ts | 17 +- src/panels/lovelace/hui-editor.ts | 2 +- src/resources/codemirror.ts | 28 ++-- yarn.lock | 266 +++++++++--------------------- 5 files changed, 113 insertions(+), 220 deletions(-) diff --git a/package.json b/package.json index 531d6e4246..c6a8f9c686 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,13 @@ "license": "Apache-2.0", "dependencies": { "@braintree/sanitize-url": "^6.0.0", - "@codemirror/autocomplete": "^0.19.12", - "@codemirror/commands": "^0.19.8", - "@codemirror/gutter": "^0.19.9", - "@codemirror/highlight": "^0.19.7", - "@codemirror/history": "^0.19.2", - "@codemirror/legacy-modes": "^0.19.0", - "@codemirror/rectangular-selection": "^0.19.1", - "@codemirror/search": "^0.19.6", - "@codemirror/state": "^0.19.6", - "@codemirror/stream-parser": "^0.19.5", - "@codemirror/text": "^0.19.6", - "@codemirror/view": "^0.19.40", + "@codemirror/autocomplete": "^6.4.0", + "@codemirror/commands": "^6.1.3", + "@codemirror/language": "^6.3.2", + "@codemirror/legacy-modes": "^6.3.1", + "@codemirror/search": "^6.2.3", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.1", "@formatjs/intl-datetimeformat": "^4.2.5", "@formatjs/intl-getcanonicallocales": "^1.8.0", "@formatjs/intl-locale": "^2.4.40", @@ -49,6 +44,7 @@ "@fullcalendar/interaction": "5.9.0", "@fullcalendar/list": "5.9.0", "@fullcalendar/timegrid": "5.9.0", + "@lezer/highlight": "^1.1.3", "@lit-labs/motion": "^1.0.2", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", "@material/chips": "14.0.0-canary.261f2db59.0", diff --git a/src/components/ha-code-editor.ts b/src/components/ha-code-editor.ts index a8f9938332..794c202740 100644 --- a/src/components/ha-code-editor.ts +++ b/src/components/ha-code-editor.ts @@ -4,6 +4,7 @@ import type { CompletionResult, CompletionSource, } from "@codemirror/autocomplete"; +import type { Extension } from "@codemirror/state"; import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view"; import { HassEntities } from "home-assistant-js-websocket"; import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit"; @@ -72,9 +73,9 @@ export class HaCodeEditor extends ReactiveElement { if (!this.codemirror || !this._loadedCodeMirror) { return false; } - const className = this._loadedCodeMirror.HighlightStyle.get( + const className = this._loadedCodeMirror.highlightingFor( this.codemirror.state, - this._loadedCodeMirror.tags.comment + [this._loadedCodeMirror.tags.comment] ); return !!this.shadowRoot!.querySelector(`span.${className}`); } @@ -136,7 +137,7 @@ export class HaCodeEditor extends ReactiveElement { private async _load(): Promise { this._loadedCodeMirror = await loadCodeMirror(); - const extensions = [ + const extensions: Extension[] = [ this._loadedCodeMirror.lineNumbers(), this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true), this._loadedCodeMirror.history(), @@ -152,10 +153,8 @@ export class HaCodeEditor extends ReactiveElement { saveKeyBinding, ] as KeyBinding[]), this._loadedCodeMirror.langCompartment.of(this._mode), - this._loadedCodeMirror.theme, - this._loadedCodeMirror.Prec.fallback( - this._loadedCodeMirror.highlightStyle - ), + this._loadedCodeMirror.haTheme, + this._loadedCodeMirror.haSyntaxHighlighting, this._loadedCodeMirror.readonlyCompartment.of( this._loadedCodeMirror.EditorView.editable.of(!this.readOnly) ), @@ -227,7 +226,7 @@ export class HaCodeEditor extends ReactiveElement { return { from: Number(entityWord.from), options: states, - span: /^[a-z_]{3,}\.\w*$/, + validFor: /^[a-z_]{3,}\.\w*$/, }; } @@ -268,7 +267,7 @@ export class HaCodeEditor extends ReactiveElement { return { from: Number(match.from), options: iconItems, - span: /^mdi:\S*$/, + validFor: /^mdi:\S*$/, }; } diff --git a/src/panels/lovelace/hui-editor.ts b/src/panels/lovelace/hui-editor.ts index e59971d11f..4d200be7a5 100644 --- a/src/panels/lovelace/hui-editor.ts +++ b/src/panels/lovelace/hui-editor.ts @@ -1,4 +1,4 @@ -import { undoDepth } from "@codemirror/history"; +import { undoDepth } from "@codemirror/commands"; import "@material/mwc-button"; import { mdiClose } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; diff --git a/src/resources/codemirror.ts b/src/resources/codemirror.ts index 3b69e471b5..f16e1f61f0 100644 --- a/src/resources/codemirror.ts +++ b/src/resources/codemirror.ts @@ -1,25 +1,29 @@ import { indentLess, indentMore } from "@codemirror/commands"; -import { HighlightStyle, tags } from "@codemirror/highlight"; +import { + HighlightStyle, + StreamLanguage, + syntaxHighlighting, +} from "@codemirror/language"; import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2"; import { yaml } from "@codemirror/legacy-modes/mode/yaml"; import { Compartment } from "@codemirror/state"; -import { StreamLanguage } from "@codemirror/stream-parser"; import { EditorView, KeyBinding } from "@codemirror/view"; +import { tags } from "@lezer/highlight"; -export { defaultKeymap } from "@codemirror/commands"; -export { lineNumbers } from "@codemirror/gutter"; -export { HighlightStyle, tags } from "@codemirror/highlight"; -export { history, historyKeymap } from "@codemirror/history"; -export { rectangularSelection } from "@codemirror/rectangular-selection"; -export { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; -export { EditorState, Prec } from "@codemirror/state"; export { autocompletion } from "@codemirror/autocomplete"; +export { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; +export { highlightingFor } from "@codemirror/language"; +export { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; +export { EditorState } from "@codemirror/state"; export { drawSelection, EditorView, highlightActiveLine, keymap, + lineNumbers, + rectangularSelection, } from "@codemirror/view"; +export { tags } from "@lezer/highlight"; export const langs = { jinja2: StreamLanguage.define(jinja2), @@ -37,7 +41,7 @@ export const tabKeyBindings: KeyBinding[] = [ }, ]; -export const theme = EditorView.theme({ +export const haTheme = EditorView.theme({ "&": { color: "var(--primary-text-color)", backgroundColor: @@ -186,7 +190,7 @@ export const theme = EditorView.theme({ ".cm-gutterElement.lineNumber": { color: "inherit" }, }); -export const highlightStyle = HighlightStyle.define([ +const haHighlightStyle = HighlightStyle.define([ { tag: tags.keyword, color: "var(--codemirror-keyword, #6262FF)" }, { tag: [ @@ -259,3 +263,5 @@ export const highlightStyle = HighlightStyle.define([ { tag: tags.inserted, color: "var(--codemirror-string2, #07a)" }, { tag: tags.invalid, color: "var(--error-color)" }, ]); + +export const haSyntaxHighlighting = syntaxHighlighting(haHighlightStyle); diff --git a/yarn.lock b/yarn.lock index 0cb6839234..fbeb2587ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1365,197 +1365,84 @@ __metadata: languageName: node linkType: hard -"@codemirror/autocomplete@npm:^0.19.12": - version: 0.19.12 - resolution: "@codemirror/autocomplete@npm:0.19.12" +"@codemirror/autocomplete@npm:^6.4.0": + version: 6.4.0 + resolution: "@codemirror/autocomplete@npm:6.4.0" dependencies: - "@codemirror/language": ^0.19.0 - "@codemirror/state": ^0.19.4 - "@codemirror/text": ^0.19.2 - "@codemirror/tooltip": ^0.19.12 - "@codemirror/view": ^0.19.0 - "@lezer/common": ^0.15.0 - checksum: f57dfe7b911e9dd928a589d72d487c84f74281e3a899120f14e857a48f4c9af109ae1df9f7e0e5959c77aedcfeafa74a428c832d1cd8cb0adde2d3e2daf6fec8 + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.6.0 + "@lezer/common": ^1.0.0 + peerDependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/common": ^1.0.0 + checksum: 3470fee01da60d3d71b8b4f8728629c0f0441e704b8b828592f98c000d75fdb2c9077727e82685626cf45b95cadbc0c1a03968261df2f0cfb4162418b5f4dd1f languageName: node linkType: hard -"@codemirror/commands@npm:^0.19.8": - version: 0.19.8 - resolution: "@codemirror/commands@npm:0.19.8" +"@codemirror/commands@npm:^6.1.3": + version: 6.1.3 + resolution: "@codemirror/commands@npm:6.1.3" dependencies: - "@codemirror/language": ^0.19.0 - "@codemirror/matchbrackets": ^0.19.0 - "@codemirror/state": ^0.19.2 - "@codemirror/text": ^0.19.6 - "@codemirror/view": ^0.19.22 - "@lezer/common": ^0.15.0 - checksum: 296f7564e71c07680da0ade9b73db67d68dd845ede22d2ba9a2a83f8b3a5429953ccd22a11d96cf2543425d416a15d1f026fb5c74a2e80522cf1779cc7cd13ff + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.2.0 + "@codemirror/view": ^6.0.0 + "@lezer/common": ^1.0.0 + checksum: beca0248fa2528005e4088a46840bc2a057d34e5b51c60cb20703ca734761dc55f5e205f5dcf94280ffbb5c3366753f46f2d03a5abfd69bd95eac8a67c2e96bb languageName: node linkType: hard -"@codemirror/gutter@npm:^0.19.9": - version: 0.19.9 - resolution: "@codemirror/gutter@npm:0.19.9" +"@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.2": + version: 6.3.2 + resolution: "@codemirror/language@npm:6.3.2" dependencies: - "@codemirror/rangeset": ^0.19.0 - "@codemirror/state": ^0.19.0 - "@codemirror/view": ^0.19.23 - checksum: 948e4bdeddfdd2f824412aa8a2cc43915444e948c310ee113faca4a988e98b6b02bea72f8849481adf82a5021b00d6a8ee2bdf0b105864de0e8aa417b41a9ed1 - languageName: node - linkType: hard - -"@codemirror/highlight@npm:^0.19.0, @codemirror/highlight@npm:^0.19.7": - version: 0.19.7 - resolution: "@codemirror/highlight@npm:0.19.7" - dependencies: - "@codemirror/language": ^0.19.0 - "@codemirror/rangeset": ^0.19.0 - "@codemirror/state": ^0.19.3 - "@codemirror/view": ^0.19.0 - "@lezer/common": ^0.15.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/common": ^1.0.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 style-mod: ^4.0.0 - checksum: 8be9d2d900501b483aa108fbd58e4cc628d01b6b5150e4f0242c1e779fd20b930f69c2da8d2eb5468712e01135808f900e44500c76fb0a838538c69c9aa31a96 + checksum: b70ed9b85d0bea79181c86e88a1f5c0bada30680ee1fe6a68efc01bc037c3d14f94a83602fc46cc4b4393589605ef7e986ed5174443502f3365dd61f883894fa languageName: node linkType: hard -"@codemirror/history@npm:^0.19.2": - version: 0.19.2 - resolution: "@codemirror/history@npm:0.19.2" +"@codemirror/legacy-modes@npm:^6.3.1": + version: 6.3.1 + resolution: "@codemirror/legacy-modes@npm:6.3.1" dependencies: - "@codemirror/state": ^0.19.2 - "@codemirror/view": ^0.19.0 - checksum: c9d794289ea0b493b11a24df487a8de14afb7f8aef502bfaa9a8dda48e01c172c769ae76209743e4cb2d5937df0e64bea1295f07722b571a858d7417b21cc4f8 + "@codemirror/language": ^6.0.0 + checksum: 9065e521bf14e33856e9d3ea114d7b352adf341a8b8d4fb94b4c866189336a32b5ed42ffc20f5d2fa3c839f1bdf29a868bbf9b74c105ed83fa9fd6080e0429e9 languageName: node linkType: hard -"@codemirror/language@npm:^0.19.0": - version: 0.19.2 - resolution: "@codemirror/language@npm:0.19.2" +"@codemirror/search@npm:^6.2.3": + version: 6.2.3 + resolution: "@codemirror/search@npm:6.2.3" dependencies: - "@codemirror/state": ^0.19.0 - "@codemirror/text": ^0.19.0 - "@codemirror/view": ^0.19.0 - "@lezer/common": ^0.15.0 - "@lezer/lr": ^0.15.0 - checksum: f0b0e555869b17b08017a128fe07b0c3280391310b8e9bc443cecfbc054f670cf58fef8e33ed4c6b9d421acdfdc541a8149404cdbdbc66e1f5d688f90153ebc3 - languageName: node - linkType: hard - -"@codemirror/legacy-modes@npm:^0.19.0": - version: 0.19.0 - resolution: "@codemirror/legacy-modes@npm:0.19.0" - dependencies: - "@codemirror/stream-parser": ^0.19.0 - checksum: 8ad6235f443ef7218651ab21b7b407712ddceaa158c74f7698f5fe507fb1edcc60382318fe2413294716e8b395f568bbdd985436f2d3b3699abbb9e17456614a - languageName: node - linkType: hard - -"@codemirror/matchbrackets@npm:^0.19.0": - version: 0.19.1 - resolution: "@codemirror/matchbrackets@npm:0.19.1" - dependencies: - "@codemirror/language": ^0.19.0 - "@codemirror/state": ^0.19.0 - "@codemirror/view": ^0.19.0 - "@lezer/common": ^0.15.0 - checksum: 6a5a6a4fc166b3032ea0757f959263623105d1014202911016a5899b0b956493c6726f18b5689cb056f5e85108d59f93fdc7862d28d77822b1d944ac4b93efa5 - languageName: node - linkType: hard - -"@codemirror/panel@npm:^0.19.0": - version: 0.19.0 - resolution: "@codemirror/panel@npm:0.19.0" - dependencies: - "@codemirror/state": ^0.19.0 - "@codemirror/view": ^0.19.0 - checksum: 6ebc6f02fdc248812ac0ae8e510bb5ed93d636d2a60acbb78813ce4e65cde68336239973fd5456584376fb0c27f3603b6ebc37c46c28002e61d10caf6814c8bb - languageName: node - linkType: hard - -"@codemirror/rangeset@npm:^0.19.0, @codemirror/rangeset@npm:^0.19.5": - version: 0.19.6 - resolution: "@codemirror/rangeset@npm:0.19.6" - dependencies: - "@codemirror/state": ^0.19.0 - checksum: f7b9ff54ac514a5c67dea1689c7f227906b46643007da76e93045ea163bd863c823a35ded4d33ba8ab1d085cb562c67134b2bf9165ffc14a9f44fbf3d85afa43 - languageName: node - linkType: hard - -"@codemirror/rectangular-selection@npm:^0.19.1": - version: 0.19.1 - resolution: "@codemirror/rectangular-selection@npm:0.19.1" - dependencies: - "@codemirror/state": ^0.19.0 - "@codemirror/text": ^0.19.4 - "@codemirror/view": ^0.19.0 - checksum: 63b7d8d1efaa551bbb12bf8baf3218d6e00fcd62a1b9648b0c02420c1c008fc7451a3e68e881ab9ce3e1d7e76126a224a47fe67cf8461e2da6b165a93a1d7213 - languageName: node - linkType: hard - -"@codemirror/search@npm:^0.19.6": - version: 0.19.6 - resolution: "@codemirror/search@npm:0.19.6" - dependencies: - "@codemirror/panel": ^0.19.0 - "@codemirror/rangeset": ^0.19.0 - "@codemirror/state": ^0.19.3 - "@codemirror/text": ^0.19.0 - "@codemirror/view": ^0.19.34 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 crelt: ^1.0.5 - checksum: 1313b389b1f7b0282ab988d338fcadbd9025765d2e85d7de90dec43477241b1f31b4ab118506c2ff1821086256f3c50a570baa4a1abdfd1909c79d0f34f3776b + checksum: 7ab0ffab7992f5c6260313e06ec8935f55807b95ca86f0327154ea1ae0ab984cd22c2fc1a812bd6cace1db131785353689fbfd080d2e12c660e3db0295dec355 languageName: node linkType: hard -"@codemirror/state@npm:^0.19.0, @codemirror/state@npm:^0.19.2, @codemirror/state@npm:^0.19.3, @codemirror/state@npm:^0.19.4, @codemirror/state@npm:^0.19.6": - version: 0.19.6 - resolution: "@codemirror/state@npm:0.19.6" +"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.4, @codemirror/state@npm:^6.2.0": + version: 6.2.0 + resolution: "@codemirror/state@npm:6.2.0" + checksum: fdc99c773dc09c700dd02bf918f06132aa8d3069c262cc4eb6ca5c810ce24ae2d7e90719ae7630a8158fd263018de6d40bd78f312e6bfba754e737b64e6c6b3d + languageName: node + linkType: hard + +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.6.0, @codemirror/view@npm:^6.7.1": + version: 6.7.1 + resolution: "@codemirror/view@npm:6.7.1" dependencies: - "@codemirror/text": ^0.19.0 - checksum: 65bee46d76c0b55b10ed4818cbb77267a6c75dff3c8cc04e83056a79a1d36e79d7b8bf750d4695238ac28fe792d6329939fd725839f8314eee34146941cae344 - languageName: node - linkType: hard - -"@codemirror/stream-parser@npm:^0.19.0, @codemirror/stream-parser@npm:^0.19.5": - version: 0.19.5 - resolution: "@codemirror/stream-parser@npm:0.19.5" - dependencies: - "@codemirror/highlight": ^0.19.0 - "@codemirror/language": ^0.19.0 - "@codemirror/state": ^0.19.0 - "@codemirror/text": ^0.19.0 - "@lezer/common": ^0.15.0 - "@lezer/lr": ^0.15.0 - checksum: 3a1edef98def985e31f9d1be3669bebc7bb14c41d0e69bd23e57868b67c1f473b7713cba1c45638e4453faf99e84888df2d4a3ebb183ea1db9795a367fde93bc - languageName: node - linkType: hard - -"@codemirror/text@npm:^0.19.0, @codemirror/text@npm:^0.19.2, @codemirror/text@npm:^0.19.4, @codemirror/text@npm:^0.19.6": - version: 0.19.6 - resolution: "@codemirror/text@npm:0.19.6" - checksum: 685e46c1f0114a216081b7a070460e1b0db9c51b0a2b361e9ed90e5ea2ed89d86a7a834b76f7c63b27fd192809d9414e7a15e0d186bd15cdb5d4f85639d434f0 - languageName: node - linkType: hard - -"@codemirror/tooltip@npm:^0.19.12": - version: 0.19.13 - resolution: "@codemirror/tooltip@npm:0.19.13" - dependencies: - "@codemirror/state": ^0.19.0 - "@codemirror/view": ^0.19.0 - checksum: 00e0554510aa6545efb201ce9a7925d13122c78455429ec26c220ff6c9de480728e16ad3bb7e451ceee1c1e1968e2c7168c38f7ffb64b5e096b4a504fe135494 - languageName: node - linkType: hard - -"@codemirror/view@npm:^0.19.0, @codemirror/view@npm:^0.19.22, @codemirror/view@npm:^0.19.23, @codemirror/view@npm:^0.19.34, @codemirror/view@npm:^0.19.40": - version: 0.19.40 - resolution: "@codemirror/view@npm:0.19.40" - dependencies: - "@codemirror/rangeset": ^0.19.5 - "@codemirror/state": ^0.19.3 - "@codemirror/text": ^0.19.0 + "@codemirror/state": ^6.1.4 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: bf3356a15a2bd24bdea7097483f055b5bef9ae20508639bb37d0bc33439824f093d348736d0e0da5b3e076f2fc6662437d9b5795e0668325bd6329f3f0bbd50f + checksum: 75a5846d61e63027e9bf1dfd0b507932934cb7650b7959c1191e68b161eb1756e9773f964c4331970b51864aef8f7954bc5cc8fdb51b0f6533de6c20568833ed languageName: node linkType: hard @@ -1998,19 +1885,28 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:^0.15.0": - version: 0.15.4 - resolution: "@lezer/common@npm:0.15.4" - checksum: 567a8f947848f224231f2123f3800529396ab78336d6eba5933eeaa9d4a9ca70fae48ffee9f54c5c39485f3b1115b953ed8f7afb1f8328d2be4f0af75bc3d4a3 +"@lezer/common@npm:^1.0.0": + version: 1.0.2 + resolution: "@lezer/common@npm:1.0.2" + checksum: bbcc58e07be02652bf0700d2856042ec089d5be0b95893d628b3e18192ade864fac83b61b19653e10b9f1472261a178b12318d934e9004edd5483a577c0db56b languageName: node linkType: hard -"@lezer/lr@npm:^0.15.0": - version: 0.15.2 - resolution: "@lezer/lr@npm:0.15.2" +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": + version: 1.1.3 + resolution: "@lezer/highlight@npm:1.1.3" dependencies: - "@lezer/common": ^0.15.0 - checksum: 62009e7587c2eea3992438076f1dd025e9ff498165b41ce3d305b872dd6410ab3f020635469a372c82bcc2304398b827fcbc6bad83d78acc81cdc7c11b698c61 + "@lezer/common": ^1.0.0 + checksum: 90ec143ce46b32f6779c3b245f1b5a540d66686939816d3daed8318821acc4bc719466dc222336cfd483bf04a8de4fdc6f279e904cf114d4d9f786f9feccbbd8 + languageName: node + linkType: hard + +"@lezer/lr@npm:^1.0.0": + version: 1.2.5 + resolution: "@lezer/lr@npm:1.2.5" + dependencies: + "@lezer/common": ^1.0.0 + checksum: 9a2fb2663dba5608c0f8a7d51b4c1beeb37d391da972fb3569fe51b637167ac4889b055ceb0c5267b8612a0aa5dfd517cbbd1349975cd662d1ca7fea374916b1 languageName: node linkType: hard @@ -9327,18 +9223,13 @@ fsevents@^1.2.7: "@babel/preset-env": ^7.20.2 "@babel/preset-typescript": ^7.18.6 "@braintree/sanitize-url": ^6.0.0 - "@codemirror/autocomplete": ^0.19.12 - "@codemirror/commands": ^0.19.8 - "@codemirror/gutter": ^0.19.9 - "@codemirror/highlight": ^0.19.7 - "@codemirror/history": ^0.19.2 - "@codemirror/legacy-modes": ^0.19.0 - "@codemirror/rectangular-selection": ^0.19.1 - "@codemirror/search": ^0.19.6 - "@codemirror/state": ^0.19.6 - "@codemirror/stream-parser": ^0.19.5 - "@codemirror/text": ^0.19.6 - "@codemirror/view": ^0.19.40 + "@codemirror/autocomplete": ^6.4.0 + "@codemirror/commands": ^6.1.3 + "@codemirror/language": ^6.3.2 + "@codemirror/legacy-modes": ^6.3.1 + "@codemirror/search": ^6.2.3 + "@codemirror/state": ^6.2.0 + "@codemirror/view": ^6.7.1 "@formatjs/intl-datetimeformat": ^4.2.5 "@formatjs/intl-getcanonicallocales": ^1.8.0 "@formatjs/intl-locale": ^2.4.40 @@ -9352,6 +9243,7 @@ fsevents@^1.2.7: "@fullcalendar/list": 5.9.0 "@fullcalendar/timegrid": 5.9.0 "@koa/cors": ^3.1.0 + "@lezer/highlight": ^1.1.3 "@lit-labs/motion": ^1.0.2 "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch" "@material/chips": 14.0.0-canary.261f2db59.0 From e1a94c679f9dd90734253cfdd5eb39e7dd15ed52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:22:41 +0000 Subject: [PATCH 44/56] bump workbox from 6.4.2 to 6.5.4 (#14775) * prod(deps): bump workbox-cacheable-response from 6.4.2 to 6.5.4 Bumps [workbox-cacheable-response](https://github.com/googlechrome/workbox) from 6.4.2 to 6.5.4. - [Release notes](https://github.com/googlechrome/workbox/releases) - [Commits](https://github.com/googlechrome/workbox/compare/v6.4.2...v6.5.4) --- updated-dependencies: - dependency-name: workbox-cacheable-response dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump the rest of the workbox packages Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steve Repsher --- package.json | 14 ++-- yarn.lock | 233 +++++++++++++++++++++++++-------------------------- 2 files changed, 123 insertions(+), 124 deletions(-) diff --git a/package.json b/package.json index c6a8f9c686..d45ffe7031 100644 --- a/package.json +++ b/package.json @@ -137,12 +137,12 @@ "vue": "^2.6.12", "vue2-daterange-picker": "^0.5.1", "weekstart": "^1.1.0", - "workbox-cacheable-response": "^6.4.2", - "workbox-core": "^6.4.2", - "workbox-expiration": "^6.4.2", - "workbox-precaching": "^6.4.2", - "workbox-routing": "^6.4.2", - "workbox-strategies": "^6.4.2", + "workbox-cacheable-response": "^6.5.4", + "workbox-core": "^6.5.4", + "workbox-expiration": "^6.5.4", + "workbox-precaching": "^6.5.4", + "workbox-routing": "^6.5.4", + "workbox-strategies": "^6.5.4", "xss": "^1.0.9" }, "devDependencies": { @@ -241,7 +241,7 @@ "webpack-dev-server": "^4.3.0", "webpack-manifest-plugin": "^4.0.2", "webpackbar": "^5.0.0-3", - "workbox-build": "^6.4.2" + "workbox-build": "^6.5.4" }, "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", "resolutions": { diff --git a/yarn.lock b/yarn.lock index fbeb2587ab..db4325103d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9418,13 +9418,13 @@ fsevents@^1.2.7: webpack-manifest-plugin: ^4.0.2 webpackbar: ^5.0.0-3 weekstart: ^1.1.0 - workbox-build: ^6.4.2 - workbox-cacheable-response: ^6.4.2 - workbox-core: ^6.4.2 - workbox-expiration: ^6.4.2 - workbox-precaching: ^6.4.2 - workbox-routing: ^6.4.2 - workbox-strategies: ^6.4.2 + workbox-build: ^6.5.4 + workbox-cacheable-response: ^6.5.4 + workbox-core: ^6.5.4 + workbox-expiration: ^6.5.4 + workbox-precaching: ^6.5.4 + workbox-routing: ^6.5.4 + workbox-strategies: ^6.5.4 xss: ^1.0.9 languageName: unknown linkType: soft @@ -9674,10 +9674,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"idb@npm:^6.1.4": - version: 6.1.5 - resolution: "idb@npm:6.1.5" - checksum: 45d81be3bf5d5ae6d009d62b4a7eeb873fe2a9972d235aaa5c33cd3e27947b33a01fd3fb7bbdbe795cd608d2279c55ccd2db3f8b3f486bc74bdb5eab1c1be957 +"idb@npm:^7.0.1": + version: 7.1.1 + resolution: "idb@npm:7.1.1" + checksum: 1973c28d53c784b177bdef9f527ec89ec239ec7cf5fcbd987dae75a16c03f5b7dfcc8c6d3285716fd0309dd57739805390bd9f98ce23b1b7d8849a3b52de8d56 languageName: node linkType: hard @@ -16271,28 +16271,28 @@ typescript@^3.8.3: languageName: node linkType: hard -"workbox-background-sync@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-background-sync@npm:6.4.2" +"workbox-background-sync@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-background-sync@npm:6.5.4" dependencies: - idb: ^6.1.4 - workbox-core: 6.4.2 - checksum: db8c267cef752176ab34b9d863334a700f27b70daa8109ca65fade7e2ff07f7969ccc2f64c075f043e2d8e3f89787c7f46e1bcde4c8a1a682f107c36f7e75d5e + idb: ^7.0.1 + workbox-core: 6.5.4 + checksum: 60ac80275cc9083b82eb53b6034e3d555d15146927a21c6017329e2b5de12d802619cc2cc6cf023f534a1f1a51671d89cdb59b26a80587d5391e8dc4b7f7dd1d languageName: node linkType: hard -"workbox-broadcast-update@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-broadcast-update@npm:6.4.2" +"workbox-broadcast-update@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-broadcast-update@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: cbf948c84530edce754797e205ed36a2b9db3b4a2d9a97d23cab56d84bcb880f5a9f0b22549e456199c52d2feee926a138a6b4a3982e820b4a31ed64dcdd5b7d + workbox-core: 6.5.4 + checksum: 63cbab2012456871ffeae401e10b16668a0654fa3fa311743cf14e05b8719b797ac3afb47dc8955d87e24f0f1199a547b090bcfdbddd67191b07697d24ac5746 languageName: node linkType: hard -"workbox-build@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-build@npm:6.4.2" +"workbox-build@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-build@npm:6.5.4" dependencies: "@apideck/better-ajv-errors": ^0.3.1 "@babel/core": ^7.11.1 @@ -16312,153 +16312,152 @@ typescript@^3.8.3: rollup: ^2.43.1 rollup-plugin-terser: ^7.0.0 source-map: ^0.8.0-beta.0 - source-map-url: ^0.4.0 stringify-object: ^3.3.0 strip-comments: ^2.0.1 tempy: ^0.6.0 upath: ^1.2.0 - workbox-background-sync: 6.4.2 - workbox-broadcast-update: 6.4.2 - workbox-cacheable-response: 6.4.2 - workbox-core: 6.4.2 - workbox-expiration: 6.4.2 - workbox-google-analytics: 6.4.2 - workbox-navigation-preload: 6.4.2 - workbox-precaching: 6.4.2 - workbox-range-requests: 6.4.2 - workbox-recipes: 6.4.2 - workbox-routing: 6.4.2 - workbox-strategies: 6.4.2 - workbox-streams: 6.4.2 - workbox-sw: 6.4.2 - workbox-window: 6.4.2 - checksum: 3c8d45899b11420ae2584ce39487bd4a754e7a95bd79131ef7f3b7cbdbd6482048ef178fbb741182f45bcb4e0e9d43bcf3d2600347ea5a167ca396a0ffdce2b8 + workbox-background-sync: 6.5.4 + workbox-broadcast-update: 6.5.4 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-google-analytics: 6.5.4 + workbox-navigation-preload: 6.5.4 + workbox-precaching: 6.5.4 + workbox-range-requests: 6.5.4 + workbox-recipes: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + workbox-streams: 6.5.4 + workbox-sw: 6.5.4 + workbox-window: 6.5.4 + checksum: 7336bbab4ce8e6e43a17873beedf7360ec32e72310306c670cd4d9ebd7e5a6a729257b2806e63830136a9bf01955632c96b27edf7a00d52c7744dbe875cca6c1 languageName: node linkType: hard -"workbox-cacheable-response@npm:6.4.2, workbox-cacheable-response@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-cacheable-response@npm:6.4.2" +"workbox-cacheable-response@npm:6.5.4, workbox-cacheable-response@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-cacheable-response@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: ca8e1d64ec55b9be8a79cd6b5d905a963693a13d9fd4641ac529e2bd88c03b3a7429b16252cd15e7f30351a90737a4095d6c896ef4e0aafdf652426a741cebbb + workbox-core: 6.5.4 + checksum: f7545b71c1505d6f56f4ba1191989ea7af7119e67fa4eb414d80603221acd0fa31362014106c1df9b9ea0e28bdcf1e2b440859acab06a75e38e978a0d1c2e489 languageName: node linkType: hard -"workbox-core@npm:6.4.2, workbox-core@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-core@npm:6.4.2" - checksum: bbdf4346e85d775d7162a49710957083bfa2b8cfc50b475bce02fcb62879ef1619ff381b00c969553a48b0c64c8b5ef7d9fce23fd5a64df1df8ed8f78667f23a +"workbox-core@npm:6.5.4, workbox-core@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-core@npm:6.5.4" + checksum: d973cc6c1c5fdbde7f6642632384c2e0de48f08228eb234db2c97a18a7e5422b483005767e7b447ea774abc0772dfc1edef2ef2b5df174df4d40ae61d4c49719 languageName: node linkType: hard -"workbox-expiration@npm:6.4.2, workbox-expiration@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-expiration@npm:6.4.2" +"workbox-expiration@npm:6.5.4, workbox-expiration@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-expiration@npm:6.5.4" dependencies: - idb: ^6.1.4 - workbox-core: 6.4.2 - checksum: 15234417ec60af7fc6222bbf812619a35e2c4b62187f7c3777b2ebab28cf0c4de1d5e728bb380400eaa5a4f6263436b4889ba3b3fbc80bba05844094fb691316 + idb: ^7.0.1 + workbox-core: 6.5.4 + checksum: 4b012b69ceafeb5afb3dd6c5c9abe6d55f2eb70666ab603bd78ff839f602336e7493990f729d507ded1fa505b852a5f9135f63afb75b9554c8f948e571143fce languageName: node linkType: hard -"workbox-google-analytics@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-google-analytics@npm:6.4.2" +"workbox-google-analytics@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-google-analytics@npm:6.5.4" dependencies: - workbox-background-sync: 6.4.2 - workbox-core: 6.4.2 - workbox-routing: 6.4.2 - workbox-strategies: 6.4.2 - checksum: 69e43a18c69881b293054af3550b38b182599ae93f261d5313f4a82a20b2c0f79667cf721ee9bf32cc76b1e2e77bd8409e5c8af02c7272f4553c7a1bc727b9f4 + workbox-background-sync: 6.5.4 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: fcce5e313780cb4f74ac962c4809fe04f9a93d3d3905d282552a2cbe6d5c6c1b8744641fe7c57d1e4b62754b90c56155e97e589712f99f6a4cab750731d60b93 languageName: node linkType: hard -"workbox-navigation-preload@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-navigation-preload@npm:6.4.2" +"workbox-navigation-preload@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-navigation-preload@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: ab8433b12d7273057389b9ef36a8cae605ce713625a523925c14d3345be04abfa432d01206fd5f10295250e935c51a65e0284e13d99c128f0cbd22b040252358 + workbox-core: 6.5.4 + checksum: c8c341b799f328bb294de8eb9e331a55501d495153237e4ddbaa08bf8630efa700621df5d81f08fb9bffc0f40ecd191a60581f72a3cd5cc72ed2e5baa318c63a languageName: node linkType: hard -"workbox-precaching@npm:6.4.2, workbox-precaching@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-precaching@npm:6.4.2" +"workbox-precaching@npm:6.5.4, workbox-precaching@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-precaching@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - workbox-routing: 6.4.2 - workbox-strategies: 6.4.2 - checksum: b1d6c6a62418b4234b5a13aa1ed643908449ed1bc4acdbc2ffcc235341c36cd6e7b4d5fcee041c833b0c4bba07413a4da3a3a505b6f04745d2c19407e84e2f82 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: 15ef24ffb04edd13bcdfa6c4e7f64002551badce2d507031c343019b3bcdc569591fdff8f8e30cf1262d641d3eff611115bdda7b2ad0deb9d4ccef8f4be8bd20 languageName: node linkType: hard -"workbox-range-requests@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-range-requests@npm:6.4.2" +"workbox-range-requests@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-range-requests@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: 940297ed423ac414b7edf59cf4e499230f8340713a4818de4a103296f2a1b29a52371f5f2e7bc3c41f3ea9317f974b80385e4cc58d2adeed6efc4ada251e14c0 + workbox-core: 6.5.4 + checksum: 50f144ced7af7db77b3c64c06c0f9924db5b8573ff2c50b3899fc22c4a360baaf6b332e65f47cf812adfc9dec882a94556fed1cf90ae4ef20b645caa03d1149e languageName: node linkType: hard -"workbox-recipes@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-recipes@npm:6.4.2" +"workbox-recipes@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-recipes@npm:6.5.4" dependencies: - workbox-cacheable-response: 6.4.2 - workbox-core: 6.4.2 - workbox-expiration: 6.4.2 - workbox-precaching: 6.4.2 - workbox-routing: 6.4.2 - workbox-strategies: 6.4.2 - checksum: 75a07ba6317f5e2fbf51b4a432914065fa8e62d232515664fc40eddc96a2c355ed03efb72411d1e73e947d40a845a2bad85c22c80e43e23fcb60b739f7869e31 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-precaching: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: 397befeb7c4c63adb0eb1913934ecaf496846844124044f0b39348288ad5950ffb45eb488cfef2504adeafe28a51cdbcc21af2a234813d81ab3da0949942c265 languageName: node linkType: hard -"workbox-routing@npm:6.4.2, workbox-routing@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-routing@npm:6.4.2" +"workbox-routing@npm:6.5.4, workbox-routing@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-routing@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: 7cb503caa2c87572235b0b891a07bd9bcebd644bd8eec715982b4b5285867bf885e772feef0c2b6797c868e4d65b6d1014654afde0ba779177d683f7b44e23ac + workbox-core: 6.5.4 + checksum: 7198c50b9016d3cea0e5b51512d66f5813d6e6ad5e99c201435d6c0ab3baee1c90aa2bbdd72dd954f439267b6e6196fb04ec96e62347e6c89385db6c1a4dec79 languageName: node linkType: hard -"workbox-strategies@npm:6.4.2, workbox-strategies@npm:^6.4.2": - version: 6.4.2 - resolution: "workbox-strategies@npm:6.4.2" +"workbox-strategies@npm:6.5.4, workbox-strategies@npm:^6.5.4": + version: 6.5.4 + resolution: "workbox-strategies@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - checksum: f981ab0bb103695f765cb4305d0bf35ebe347bcb19c441a58b2b48e99dc238495b6cbb1d1d3f55c89f2dee3202d9d2f8cb31f10b98120a33ed52f7e838366a98 + workbox-core: 6.5.4 + checksum: 52134ecd6c05f4edd31e7b022b33a91b7b59c215bfdfb987bc0f10be02fea4d4e6385a9638a2303ba336190c5d28f9721182cd78a6779b9c817a66ec12cb1c6b languageName: node linkType: hard -"workbox-streams@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-streams@npm:6.4.2" +"workbox-streams@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-streams@npm:6.5.4" dependencies: - workbox-core: 6.4.2 - workbox-routing: 6.4.2 - checksum: b17223f0a6604a869b6564ce29932146c4d8d8ec0e9f8d36cede5776ccde78fe0400598373c119209429ee01281d3e371c16e2722ae5d95dd67d8d526048ca14 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + checksum: efd6917ead915011be2b25dc3ebbb9d051dbd10ba2d91cdaec36ca742360e2c33627564653fc40f336dee874d501e94bcc4a25d1b65eaf5a6ee5f1a8b894af44 languageName: node linkType: hard -"workbox-sw@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-sw@npm:6.4.2" - checksum: c9b05dc9af1f3da1cdb1ca8a2e57d273e63c76eaf29a216669234dea6934ee547f47836dd930143f3c04b5c756b38d1aa221efdc90f82bb69287bf3664849853 +"workbox-sw@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-sw@npm:6.5.4" + checksum: b95c76a74b84ff268ef7691447125697f4de85b076ebc33c9545fb7532b020b6f66b37f7a4bedbc21ab45473d1109337a5f037c45b3d99126ae8f5eeb898a687 languageName: node linkType: hard -"workbox-window@npm:6.4.2": - version: 6.4.2 - resolution: "workbox-window@npm:6.4.2" +"workbox-window@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-window@npm:6.5.4" dependencies: "@types/trusted-types": ^2.0.2 - workbox-core: 6.4.2 - checksum: 811dd5cae8f493e66e39729440b36a96ca3cb91b99595fd62f151c6f92f5e658109b0444aa3b91fafd1232220798c61c2164f1f2c76e21079abed9ceebe93f22 + workbox-core: 6.5.4 + checksum: bc43c8d31908ab564d740eb1041180c0b0ca4d1f0a3ccde59c5764a8f96d7b08edb7df975360fd37c2bec9f3f57ca9de6c7e34fd252aa1a4a075b5b002f74f60 languageName: node linkType: hard From e9aded77da88aeb22beafaf6781d6ef17e7f125c Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 5 Jan 2023 23:25:55 +0100 Subject: [PATCH 45/56] Bump mdi/js and mdi/svg to 7.1.96 (#15006) --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index d45ffe7031..b2437e4582 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "@material/mwc-textfield": "0.25.3", "@material/mwc-top-app-bar-fixed": "^0.25.3", "@material/top-app-bar": "14.0.0-canary.261f2db59.0", - "@mdi/js": "7.0.96", - "@mdi/svg": "7.0.96", + "@mdi/js": "7.1.96", + "@mdi/svg": "7.1.96", "@polymer/app-layout": "^3.1.0", "@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-icon": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index db4325103d..4b9d295b76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2962,17 +2962,17 @@ __metadata: languageName: node linkType: hard -"@mdi/js@npm:7.0.96": - version: 7.0.96 - resolution: "@mdi/js@npm:7.0.96" - checksum: 4bcc4542a2d8ba9e706a754333e90ce2677616709d3776016b83be59c7219e7514308dbca6e62c7dd38436b3c5d251b8769679c90e354f971b090fa2ebcbc4fe +"@mdi/js@npm:7.1.96": + version: 7.1.96 + resolution: "@mdi/js@npm:7.1.96" + checksum: cd3102a0120c06b4af7a153e30ee964807746ce55f84a35479d382841d0ab1a10f986059e0c6af0c240444393679b7330d306d0688e1193a23fc03bb8cee0b82 languageName: node linkType: hard -"@mdi/svg@npm:7.0.96": - version: 7.0.96 - resolution: "@mdi/svg@npm:7.0.96" - checksum: 434994401283e4f790dd9d7061d5781fc8eec24f85b7be33d07a32a9fecb9a268437b35fa3c30ee4fe433d49f7f69674571494510aaa9efade05fb4e8e71755f +"@mdi/svg@npm:7.1.96": + version: 7.1.96 + resolution: "@mdi/svg@npm:7.1.96" + checksum: afbb0947cbf342ffb08d9b0069095db5e4d9a5dab741457eac2a0a8433217834bb96589be26b8abbf4ac315473870e07a14b0b759b5148efeedad193067ab67c languageName: node linkType: hard @@ -9270,8 +9270,8 @@ fsevents@^1.2.7: "@material/mwc-textfield": 0.25.3 "@material/mwc-top-app-bar-fixed": ^0.25.3 "@material/top-app-bar": 14.0.0-canary.261f2db59.0 - "@mdi/js": 7.0.96 - "@mdi/svg": 7.0.96 + "@mdi/js": 7.1.96 + "@mdi/svg": 7.1.96 "@octokit/auth-oauth-device": ^4.0.2 "@octokit/rest": ^19.0.4 "@open-wc/dev-server-hmr": ^0.0.2 From b2d530448820bd77eabdb8393495f909eedb3fd9 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 6 Jan 2023 08:04:45 +0100 Subject: [PATCH 46/56] Fixes weekday calendar chips toggle (#14990) --- src/panels/calendar/ha-recurrence-rule-editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/calendar/ha-recurrence-rule-editor.ts b/src/panels/calendar/ha-recurrence-rule-editor.ts index 5d8af6c2a9..59be35cf5f 100644 --- a/src/panels/calendar/ha-recurrence-rule-editor.ts +++ b/src/panels/calendar/ha-recurrence-rule-editor.ts @@ -381,6 +381,7 @@ export class RecurrenceRuleEditor extends LitElement { } else { this._weekday.delete(value); } + this.requestUpdate("_weekday"); } private _onEndSelected(e: CustomEvent>) { From f2a67a5fa9c41e8c3816543041d8a08b0d407252 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 6 Jan 2023 09:43:57 +0100 Subject: [PATCH 47/56] Add calendar domain to sensors (#15002) --- src/common/const.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/const.ts b/src/common/const.ts index a279cfd348..07ada9c7e3 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -201,6 +201,7 @@ export const DOMAINS_WITH_CARD = [ export const SENSOR_ENTITIES = [ "sensor", "binary_sensor", + "calendar", "camera", "device_tracker", "weather", From d1caeed05e950eea71cf5ceb6452515269382e95 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 6 Jan 2023 12:15:45 +0100 Subject: [PATCH 48/56] Remove aliases configuration from alexa cloud page (#15003) --- src/panels/config/cloud/alexa/cloud-alexa.ts | 35 ++------------------ 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/panels/config/cloud/alexa/cloud-alexa.ts b/src/panels/config/cloud/alexa/cloud-alexa.ts index e498f49fa4..8c666890dc 100644 --- a/src/panels/config/cloud/alexa/cloud-alexa.ts +++ b/src/panels/config/cloud/alexa/cloud-alexa.ts @@ -40,17 +40,12 @@ import { updateCloudAlexaEntityConfig, updateCloudPref, } from "../../../../data/cloud"; -import { - EntityRegistryEntry, - getExtendedEntityRegistryEntry, - updateEntityRegistryEntry, -} from "../../../../data/entity_registry"; +import { EntityRegistryEntry } from "../../../../data/entity_registry"; import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler"; import "../../../../layouts/hass-loading-screen"; import "../../../../layouts/hass-subpage"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; -import { showEntityAliasesDialog } from "../../entities/entity-aliases/show-dialog-entity-aliases"; const DEFAULT_CONFIG_EXPOSE = true; @@ -167,20 +162,8 @@ class CloudAlexa extends LitElement { - ${entity.entity_id in this.hass.entities - ? html`` - : ""} ${!emptyFilter ? html`${iconButton}` @@ -343,21 +326,6 @@ class CloudAlexa extends LitElement { } } - private async _openAliasesSettings(ev) { - ev.stopPropagation(); - const entityId = ev.target.entityId; - const entry = await getExtendedEntityRegistryEntry(this.hass, entityId); - if (!entry) { - return; - } - showEntityAliasesDialog(this, { - entity: entry, - updateEntry: async (updates) => { - await updateEntityRegistryEntry(this.hass, entry.entity_id, updates); - }, - }); - } - private async _fetchData() { const entities = await fetchCloudAlexaEntities(this.hass); entities.sort((a, b) => { @@ -558,6 +526,7 @@ class CloudAlexa extends LitElement { } state-info { cursor: pointer; + height: 40px; } ha-switch { padding: 8px 0; From dcee89caeb9f96310ca2fdbc3878741415abb33d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 6 Jan 2023 12:27:36 +0100 Subject: [PATCH 49/56] Display aliases in cloud page (#14989) * Display aliases in cloud page * Fixes ellipsis * Improve sort * Separate alias list and button * Remove alexa changes * Apply suggestions --- src/data/entity_registry.ts | 9 ++ .../cloud-google-assistant.ts | 88 +++++++++++++++---- .../entity-aliases/dialog-entity-aliases.ts | 2 +- .../entities/entity-registry-basic-editor.ts | 7 +- .../entities/entity-registry-settings.ts | 8 +- src/translations/en.json | 4 + 6 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index e75bba06ff..cbc5459b3b 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -111,6 +111,15 @@ export const getExtendedEntityRegistryEntry = ( entity_id: entityId, }); +export const getExtendedEntityRegistryEntries = ( + hass: HomeAssistant, + entityIds: string[] +): Promise> => + hass.callWS({ + type: "config/entity_registry/get_entries", + entity_ids: entityIds, + }); + export const updateEntityRegistryEntry = ( hass: HomeAssistant, entityId: string, diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index 92bf480175..c11ad9c82f 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -40,7 +40,8 @@ import { } from "../../../../data/cloud"; import { EntityRegistryEntry, - getExtendedEntityRegistryEntry, + ExtEntityRegistryEntry, + getExtendedEntityRegistryEntries, updateEntityRegistryEntry, } from "../../../../data/entity_registry"; import { @@ -68,6 +69,8 @@ class CloudGoogleAssistant extends LitElement { @state() private _entities?: GoogleEntity[]; + @state() private _entries?: { [id: string]: ExtEntityRegistryEntry }; + @state() private _syncing = false; @state() @@ -164,6 +167,8 @@ class CloudGoogleAssistant extends LitElement { : mdiCloseBoxMultiple} >`; + const aliases = this._entries?.[entity.entity_id]?.aliases; + target.push(html`
@@ -174,17 +179,51 @@ class CloudGoogleAssistant extends LitElement { secondary-line @click=${this._showMoreInfo} > - ${entity.entity_id in this.hass.entities - ? html`` - : ""} + ${aliases + ? html` + + ${aliases.length > 0 + ? [...aliases] + .sort((a, b) => + stringCompare(a, b, this.hass.locale.language) + ) + .join(", ") + : this.hass.localize( + "ui.panel.config.cloud.google.no_aliases" + )} + +
+ + ` + : html` + + ${this.hass.localize( + "ui.panel.config.cloud.google.aliases_not_available" + )} + +
+ + `} ${!emptyFilter ? html`${iconButton}` @@ -379,14 +418,19 @@ class CloudGoogleAssistant extends LitElement { private async _openAliasesSettings(ev) { ev.stopPropagation(); const entityId = ev.target.entityId; - const entry = await getExtendedEntityRegistryEntry(this.hass, entityId); + const entry = this._entries![entityId]; if (!entry) { return; } showEntityAliasesDialog(this, { entity: entry, updateEntry: async (updates) => { - await updateEntityRegistryEntry(this.hass, entry.entity_id, updates); + const { entity_entry } = await updateEntityRegistryEntry( + this.hass, + entry.entity_id, + updates + ); + this._entries![entity_entry.entity_id] = entity_entry; }, }); } @@ -415,6 +459,13 @@ class CloudGoogleAssistant extends LitElement { private async _fetchData() { const entities = await fetchCloudGoogleEntities(this.hass); + this._entries = await getExtendedEntityRegistryEntries( + this.hass, + entities + .filter((ent) => this.hass.entities[ent.entity_id]) + .map((e) => e.entity_id) + ); + entities.sort((a, b) => { const stateA = this.hass.states[a.entity_id]; const stateB = this.hass.states[b.entity_id]; @@ -429,7 +480,14 @@ class CloudGoogleAssistant extends LitElement { private _showMoreInfo(ev) { const entityId = ev.currentTarget.stateObj.entity_id; - fireEvent(this, "hass-more-info", { entityId }); + const moreInfoTab = ev.currentTarget.moreInfoTab; + fireEvent(this, "hass-more-info", { entityId, tab: moreInfoTab }); + } + + private _showMoreInfoSettings(ev) { + ev.stopPropagation(); + const entityId = ev.currentTarget.stateObj.entity_id; + fireEvent(this, "hass-more-info", { entityId, tab: "settings" }); } private async _exposeChanged(ev: CustomEvent) { diff --git a/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts index 9859bcf602..87f3e7cd85 100644 --- a/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts +++ b/src/panels/config/entities/entity-aliases/dialog-entity-aliases.ts @@ -30,7 +30,7 @@ class DialogEntityAliases extends LitElement { this._error = undefined; this._aliases = this._params.entity.aliases?.length > 0 - ? this._params.entity.aliases + ? [...this._params.entity.aliases].sort() : [""]; await this.updateComplete; } diff --git a/src/panels/config/entities/entity-registry-basic-editor.ts b/src/panels/config/entities/entity-registry-basic-editor.ts index 946e3bf90a..680a66bf7b 100644 --- a/src/panels/config/entities/entity-registry-basic-editor.ts +++ b/src/panels/config/entities/entity-registry-basic-editor.ts @@ -7,6 +7,7 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; +import { stringCompare } from "../../../common/string/compare"; import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; import "../../../components/ha-radio"; @@ -285,7 +286,11 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { "ui.dialogs.entity_registry.editor.no_aliases" )} - ${this.entry.aliases.join(", ")} + + ${[...this.entry.aliases] + .sort((a, b) => stringCompare(a, b, this.hass.locale.language)) + .join(", ")} + diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 5a318ff9ed..b1061a9f43 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -819,7 +819,13 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { "ui.dialogs.entity_registry.editor.no_aliases" )} - ${this.entry.aliases.join(", ")} + + ${[...this.entry.aliases] + .sort((a, b) => + stringCompare(a, b, this.hass.locale.language) + ) + .join(", ")} + diff --git a/src/translations/en.json b/src/translations/en.json index 7c66bcf349..b9e0779322 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2740,6 +2740,10 @@ "exposed": "{selected} exposed", "not_exposed": "{selected} not exposed", "manage_aliases": "Manage aliases", + "add_aliases": "Add aliases", + "no_aliases": "No aliases", + "aliases_not_available": "Aliases not available", + "aliases_not_available_learn_more": "Learn more", "sync_to_google": "Synchronizing changes to Google.", "sync_entities": "Synchronize entities", "sync_entities_error": "Failed to sync entities:", From b44b22c72337511d6be2775495a0311d8fc64784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 12:29:40 +0100 Subject: [PATCH 50/56] Bump fs-extra from 7.0.1 to 11.1.0 (#15012) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 35 ++++++++--------------------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index b2437e4582..e2f48fb6af 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "eslint-plugin-unused-imports": "^1.1.5", "eslint-plugin-wc": "^1.3.2", "fancy-log": "^2.0.0", - "fs-extra": "^7.0.1", + "fs-extra": "^11.1.0", "glob": "^7.2.0", "gulp": "^4.0.2", "gulp-flatmap": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 4b9d295b76..bb0d8e4f02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8535,14 +8535,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^7.0.1": - version: 7.0.1 - resolution: "fs-extra@npm:7.0.1" +"fs-extra@npm:^11.1.0": + version: 11.1.0 + resolution: "fs-extra@npm:11.1.0" dependencies: - graceful-fs: ^4.1.2 - jsonfile: ^4.0.0 - universalify: ^0.1.0 - checksum: 141b9dccb23b66a66cefdd81f4cda959ff89282b1d721b98cea19ba08db3dcbe6f862f28841f3cf24bb299e0b7e6c42303908f65093cb7e201708e86ea5a8dcf + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: 5ca476103fa1f5ff4a9b3c4f331548f8a3c1881edaae323a4415d3153b5dc11dc6a981c8d1dd93eec8367ceee27b53f8bd27eecbbf66ffcdd04927510c171e7f languageName: node linkType: hard @@ -9343,7 +9343,7 @@ fsevents@^1.2.7: eslint-plugin-unused-imports: ^1.1.5 eslint-plugin-wc: ^1.3.2 fancy-log: ^2.0.0 - fs-extra: ^7.0.1 + fs-extra: ^11.1.0 fuse.js: ^6.0.0 glob: ^7.2.0 google-timezones-json: ^1.0.2 @@ -10651,18 +10651,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jsonfile@npm:^4.0.0": - version: 4.0.0 - resolution: "jsonfile@npm:4.0.0" - dependencies: - graceful-fs: ^4.1.6 - dependenciesMeta: - graceful-fs: - optional: true - checksum: 6447d6224f0d31623eef9b51185af03ac328a7553efcee30fa423d98a9e276ca08db87d71e17f2310b0263fd3ffa6c2a90a6308367f661dc21580f9469897c9e - languageName: node - linkType: hard - "jsonfile@npm:^6.0.1": version: 6.1.0 resolution: "jsonfile@npm:6.1.0" @@ -15530,13 +15518,6 @@ typescript@^3.8.3: languageName: node linkType: hard -"universalify@npm:^0.1.0": - version: 0.1.2 - resolution: "universalify@npm:0.1.2" - checksum: 40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" From 7286aa7dc472236449ce9eb1a608ccd65e80f4e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 06:05:16 +0000 Subject: [PATCH 51/56] bump open from 7.3.0 to 8.4.0 (#14776) * dev(deps-dev): bump open from 7.3.0 to 8.4.0 Bumps [open](https://github.com/sindresorhus/open) from 7.3.0 to 8.4.0. - [Release notes](https://github.com/sindresorhus/open/releases) - [Commits](https://github.com/sindresorhus/open/compare/v7.3.0...v8.4.0) --- updated-dependencies: - dependency-name: open dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Deduplicate dependencies Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steve Repsher --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e2f48fb6af..d796be483a 100644 --- a/package.json +++ b/package.json @@ -218,7 +218,7 @@ "merge-stream": "^1.0.1", "mocha": "^8.4.0", "object-hash": "^2.0.3", - "open": "^7.0.4", + "open": "^8.4.0", "pinst": "^3.0.0", "prettier": "^2.8.1", "require-dir": "^1.2.0", diff --git a/yarn.lock b/yarn.lock index bb0d8e4f02..100cae2313 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9377,7 +9377,7 @@ fsevents@^1.2.7: mocha: ^8.4.0 node-vibrant: 3.2.1-alpha.1 object-hash: ^2.0.3 - open: ^7.0.4 + open: ^8.4.0 pinst: ^3.0.0 prettier: ^2.8.1 proxy-polyfill: ^0.3.2 @@ -12289,7 +12289,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"open@npm:^7.0.3, open@npm:^7.0.4, open@npm:^7.3.0": +"open@npm:^7.0.3, open@npm:^7.3.0": version: 7.3.0 resolution: "open@npm:7.3.0" dependencies: @@ -12299,14 +12299,14 @@ fsevents@^1.2.7: languageName: node linkType: hard -"open@npm:^8.0.9": - version: 8.2.1 - resolution: "open@npm:8.2.1" +"open@npm:^8.0.9, open@npm:^8.4.0": + version: 8.4.0 + resolution: "open@npm:8.4.0" dependencies: define-lazy-prop: ^2.0.0 is-docker: ^2.1.1 is-wsl: ^2.2.0 - checksum: fcde0059188dd497e080436f81c5240dad0bebd331d1c856a532d4b870808bdc5770ef7c5c4b83143fd0c0577fe2b580e54c03357d695771259aa59f64cf0f40 + checksum: e9545bec64cdbf30a0c35c1bdc310344adf8428a117f7d8df3c0af0a0a24c513b304916a6d9b11db0190ff7225c2d578885080b761ed46a3d5f6f1eebb98b63c languageName: node linkType: hard From d55307098af5d24ec3054aa6c032d4d3ad591279 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 06:45:49 +0000 Subject: [PATCH 52/56] Bump @vue/web-component-wrapper from 1.2.0 to 1.3.0 (#14782) * Bump @vue/web-component-wrapper from 1.2.0 to 1.3.0 Bumps [@vue/web-component-wrapper](https://github.com/vuejs/web-component-wrapper) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/vuejs/web-component-wrapper/releases) - [Commits](https://github.com/vuejs/web-component-wrapper/commits) --- updated-dependencies: - dependency-name: "@vue/web-component-wrapper" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Correct wrap type and remove unnecessary TS ignores Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steve Repsher --- package.json | 2 +- src/components/date-range-picker.ts | 18 ++++++------------ yarn.lock | 10 +++++----- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index d796be483a..159e9737be 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "@vibrant/color": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", - "@vue/web-component-wrapper": "^1.2.0", + "@vue/web-component-wrapper": "^1.3.0", "@webcomponents/scoped-custom-element-registry": "^0.0.5", "@webcomponents/webcomponentsjs": "^2.2.10", "app-datepicker": "^5.1.0", diff --git a/src/components/date-range-picker.ts b/src/components/date-range-picker.ts index b6f93a9584..20a952fb6c 100644 --- a/src/components/date-range-picker.ts +++ b/src/components/date-range-picker.ts @@ -5,7 +5,6 @@ import DateRangePicker from "vue2-daterange-picker"; // @ts-ignore import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css"; import { fireEvent } from "../common/dom/fire_event"; -import { Constructor } from "../types"; const Component = Vue.extend({ props: { @@ -47,35 +46,26 @@ const Component = Vue.extend({ }, }, render(createElement) { - // @ts-ignore + // @ts-expect-error return createElement(DateRangePicker, { props: { - // @ts-ignore "time-picker": this.timePicker, - // @ts-ignore "auto-apply": this.autoApply, opens: "right", "show-dropdowns": false, - // @ts-ignore "time-picker24-hour": this.twentyfourHours, - // @ts-ignore disabled: this.disabled, - // @ts-ignore ranges: this.ranges ? {} : false, "locale-data": { - // @ts-ignore firstDay: this.firstDay, }, }, model: { value: { - // @ts-ignore startDate: this.startDate, - // @ts-ignore endDate: this.endDate, }, callback: (value) => { - // @ts-ignore fireEvent(this.$el as HTMLElement, "change", value); }, expression: "dateRange", @@ -106,7 +96,11 @@ const Component = Vue.extend({ }, }); -const WrappedElement: Constructor = wrap(Vue, Component); +// Assertion corrects HTMLElement type from package +const WrappedElement = wrap( + Vue, + Component +) as unknown as CustomElementConstructor; @customElement("date-range-picker") class DateRangePickerElement extends WrappedElement { diff --git a/yarn.lock b/yarn.lock index 100cae2313..98593a9591 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4693,10 +4693,10 @@ __metadata: languageName: node linkType: hard -"@vue/web-component-wrapper@npm:^1.2.0": - version: 1.2.0 - resolution: "@vue/web-component-wrapper@npm:1.2.0" - checksum: 342d56d3fcebcbd94e0be984adc1163587f2d1326dcda7727d3624800be221cf0329c1c6c3f949fdc5a00c9f487946750781cf6441d1585a4fa3800d11ef939e +"@vue/web-component-wrapper@npm:^1.3.0": + version: 1.3.0 + resolution: "@vue/web-component-wrapper@npm:1.3.0" + checksum: 8cc4d1135990e61ab9d38a7b6460b018703b38b4dd3477390083018bffb93b283fabb7d57d83b3cfb78dd44da4f863167b964fe88dfa9886a54996f308036a94 languageName: node linkType: hard @@ -9315,7 +9315,7 @@ fsevents@^1.2.7: "@vibrant/color": ^3.2.1-alpha.1 "@vibrant/core": ^3.2.1-alpha.1 "@vibrant/quantizer-mmcq": ^3.2.1-alpha.1 - "@vue/web-component-wrapper": ^1.2.0 + "@vue/web-component-wrapper": ^1.3.0 "@web/dev-server": ^0.0.24 "@web/dev-server-rollup": ^0.2.11 "@webcomponents/scoped-custom-element-registry": ^0.0.5 From 78768b1e2f7286ee7890e00e74848eef9904198a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:39:28 +0100 Subject: [PATCH 53/56] Bump actions/checkout from 3.2.0 to 3.3.0 (#15050) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cast_deployment.yaml | 4 ++-- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/demo_deployment.yaml | 4 ++-- .github/workflows/design_deployment.yaml | 2 +- .github/workflows/design_preview.yaml | 2 +- .github/workflows/nightly.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/translations.yaml | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cast_deployment.yaml b/.github/workflows/cast_deployment.yaml index 8b8b0b98f7..9f339f6c01 100644 --- a/.github/workflows/cast_deployment.yaml +++ b/.github/workflows/cast_deployment.yaml @@ -22,7 +22,7 @@ jobs: url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 with: ref: dev @@ -60,7 +60,7 @@ jobs: url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 with: ref: master diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 75c023ef90..9f0094eb82 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 with: @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 with: @@ -63,7 +63,7 @@ jobs: needs: [lint, test] steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 with: @@ -82,7 +82,7 @@ jobs: needs: [lint, test] steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7717444454..7e417b355b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/demo_deployment.yaml b/.github/workflows/demo_deployment.yaml index cc394b646f..d55b29fd34 100644 --- a/.github/workflows/demo_deployment.yaml +++ b/.github/workflows/demo_deployment.yaml @@ -23,7 +23,7 @@ jobs: url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 with: ref: dev @@ -61,7 +61,7 @@ jobs: url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 with: ref: master diff --git a/.github/workflows/design_deployment.yaml b/.github/workflows/design_deployment.yaml index 0574dd8676..d54b8684ab 100644 --- a/.github/workflows/design_deployment.yaml +++ b/.github/workflows/design_deployment.yaml @@ -17,7 +17,7 @@ jobs: url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 diff --git a/.github/workflows/design_preview.yaml b/.github/workflows/design_preview.yaml index 084f69ad93..c49e544f6f 100644 --- a/.github/workflows/design_preview.yaml +++ b/.github/workflows/design_preview.yaml @@ -22,7 +22,7 @@ jobs: if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') steps: - name: Check out files from GitHub - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} uses: actions/setup-node@v3.5.1 diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index bd54ca3026..e27fd5a4bc 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -21,7 +21,7 @@ jobs: contents: write steps: - name: Checkout the repository - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 662c7adcc6..32358597d4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,7 +24,7 @@ jobs: contents: write # Required to upload release assets steps: - name: Checkout the repository - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Verify version uses: home-assistant/actions/helpers/verify-version@master diff --git a/.github/workflows/translations.yaml b/.github/workflows/translations.yaml index 4d593874d7..594117661c 100644 --- a/.github/workflows/translations.yaml +++ b/.github/workflows/translations.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3.3.0 - name: Upload Translations run: | From 74fb8b0427c0b180556823b6650077e0d48d51ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 11:33:34 +0100 Subject: [PATCH 54/56] Bump actions/setup-node from 3.5.1 to 3.6.0 (#15049) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cast_deployment.yaml | 4 ++-- .github/workflows/ci.yaml | 8 ++++---- .github/workflows/demo_deployment.yaml | 4 ++-- .github/workflows/design_deployment.yaml | 2 +- .github/workflows/design_preview.yaml | 2 +- .github/workflows/nightly.yaml | 2 +- .github/workflows/release.yaml | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cast_deployment.yaml b/.github/workflows/cast_deployment.yaml index 9f339f6c01..a3ecc20db0 100644 --- a/.github/workflows/cast_deployment.yaml +++ b/.github/workflows/cast_deployment.yaml @@ -27,7 +27,7 @@ jobs: ref: dev - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -65,7 +65,7 @@ jobs: ref: master - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f0094eb82..4df43edc3f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -46,7 +46,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -65,7 +65,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -84,7 +84,7 @@ jobs: - name: Check out files from GitHub uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/demo_deployment.yaml b/.github/workflows/demo_deployment.yaml index d55b29fd34..d28a5ffe11 100644 --- a/.github/workflows/demo_deployment.yaml +++ b/.github/workflows/demo_deployment.yaml @@ -28,7 +28,7 @@ jobs: ref: dev - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -66,7 +66,7 @@ jobs: ref: master - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/design_deployment.yaml b/.github/workflows/design_deployment.yaml index d54b8684ab..c9b37d605a 100644 --- a/.github/workflows/design_deployment.yaml +++ b/.github/workflows/design_deployment.yaml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/design_preview.yaml b/.github/workflows/design_preview.yaml index c49e544f6f..607838b940 100644 --- a/.github/workflows/design_preview.yaml +++ b/.github/workflows/design_preview.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v3.3.0 - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index e27fd5a4bc..ffadf0a483 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -29,7 +29,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 32358597d4..01058a9413 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -35,7 +35,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }} - uses: actions/setup-node@v3.5.1 + uses: actions/setup-node@v3.6.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn From 5094e8f428ff4807febe93eb7c7153fe1ec93075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:18:45 +0100 Subject: [PATCH 55/56] Bump typescript from 4.9.3 to 4.9.4 (#15052) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.3 to 4.9.4. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.3...v4.9.4) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 159e9737be..9fbdeabd07 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "tar": "^6.1.11", "terser-webpack-plugin": "^5.2.4", "ts-lit-plugin": "^1.2.1", - "typescript": "^4.9.3", + "typescript": "^4.9.4", "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", "webpack": "^5.55.1", diff --git a/yarn.lock b/yarn.lock index 98593a9591..ebb08e619f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9404,7 +9404,7 @@ fsevents@^1.2.7: tinykeys: ^1.1.3 ts-lit-plugin: ^1.2.1 tsparticles: ^1.34.0 - typescript: ^4.9.3 + typescript: ^4.9.4 unfetch: ^4.1.0 vinyl-buffer: ^1.0.1 vinyl-source-stream: ^2.0.0 @@ -15325,13 +15325,13 @@ typescript@^3.8.3: languageName: node linkType: hard -"typescript@npm:^4.9.3": - version: 4.9.3 - resolution: "typescript@npm:4.9.3" +"typescript@npm:^4.9.4": + version: 4.9.4 + resolution: "typescript@npm:4.9.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 17b8f816050b412403e38d48eef0e893deb6be522d6dc7caf105e54a72e34daf6835c447735fd2b28b66784e72bfbf87f627abb4818a8e43d1fa8106396128dc + checksum: e782fb9e0031cb258a80000f6c13530288c6d63f1177ed43f770533fdc15740d271554cdae86701c1dd2c83b082cea808b07e97fd68b38a172a83dbf9e0d0ef9 languageName: node linkType: hard @@ -15345,13 +15345,13 @@ typescript@^3.8.3: languageName: node linkType: hard -"typescript@patch:typescript@^4.9.3#~builtin": - version: 4.9.3 - resolution: "typescript@patch:typescript@npm%3A4.9.3#~builtin::version=4.9.3&hash=a1c5e5" +"typescript@patch:typescript@^4.9.4#~builtin": + version: 4.9.4 + resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=a1c5e5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: ef65c22622d864497d0a0c5db693523329b3284c15fe632e93ad9aa059e8dc38ef3bd767d6f26b1e5ecf9446f49bd0f6c4e5714a2eeaf352805dc002479843d1 + checksum: 37f6e2c3c5e2aa5934b85b0fddbf32eeac8b1bacf3a5b51d01946936d03f5377fe86255d4e5a4ae628fd0cd553386355ad362c57f13b4635064400f3e8e05b9d languageName: node linkType: hard From 8fac5f6d75ae3f9ad0b8a9505f1a6818a5908fc3 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 9 Jan 2023 03:28:31 -0800 Subject: [PATCH 56/56] Fix UNTIL values to be inclusive of last event and bug in populating recurrence rules when editing calendar events (#15024) * Fix bug in populating recurrence rules when editing calendar events * Set UNTIL value to be inclusive of the last instance * Fix lint errors --- .../calendar/ha-recurrence-rule-editor.ts | 74 ++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/panels/calendar/ha-recurrence-rule-editor.ts b/src/panels/calendar/ha-recurrence-rule-editor.ts index 59be35cf5f..b7b3c03f5e 100644 --- a/src/panels/calendar/ha-recurrence-rule-editor.ts +++ b/src/panels/calendar/ha-recurrence-rule-editor.ts @@ -1,4 +1,5 @@ import type { SelectedDetail } from "@material/mwc-list"; +import { formatInTimeZone, toDate } from "date-fns-tz"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -64,7 +65,7 @@ export class RecurrenceRuleEditor extends LitElement { @state() private _count?: number; - @state() private _until?: Date; + @state() private _untilDay?: Date; @query("#monthly") private _monthlyRepeatSelect!: HaSelect; @@ -97,15 +98,17 @@ export class RecurrenceRuleEditor extends LitElement { } if ( - changedProps.has("timezone") || - changedProps.has("_freq") || - changedProps.has("_interval") || - changedProps.has("_weekday") || - changedProps.has("_monthlyRepeatWeekday") || - changedProps.has("_monthday") || - changedProps.has("_end") || - changedProps.has("_count") || - changedProps.has("_until") + !changedProps.has("value") && + (changedProps.has("dtstart") || + changedProps.has("timezone") || + changedProps.has("_freq") || + changedProps.has("_interval") || + changedProps.has("_weekday") || + changedProps.has("_monthlyRepeatWeekday") || + changedProps.has("_monthday") || + changedProps.has("_end") || + changedProps.has("_count") || + changedProps.has("_untilDay")) ) { this._updateRule(); return; @@ -122,7 +125,7 @@ export class RecurrenceRuleEditor extends LitElement { this._monthlyRepeatWeekday = undefined; this._end = "never"; this._count = undefined; - this._until = undefined; + this._untilDay = undefined; this._computedRRule = this.value; if (this.value === "") { @@ -162,7 +165,7 @@ export class RecurrenceRuleEditor extends LitElement { } if (rrule.until) { this._end = "on"; - this._until = rrule.until; + this._untilDay = toDate(rrule.until, { timeZone: this.timezone }); } else if (rrule.count) { this._end = "after"; this._count = rrule.count; @@ -327,7 +330,7 @@ export class RecurrenceRuleEditor extends LitElement { "ui.components.calendar.event.repeat.end_on.label" )} .locale=${this.locale} - .value=${this._until!.toISOString()} + .value=${this._formatDate(this._untilDay!)} @value-changed=${this._onUntilChange} > ` @@ -394,15 +397,15 @@ export class RecurrenceRuleEditor extends LitElement { switch (this._end) { case "after": this._count = DEFAULT_COUNT[this._freq!]; - this._until = undefined; + this._untilDay = undefined; break; case "on": this._count = undefined; - this._until = untilValue(this._freq!); + this._untilDay = untilValue(this._freq!); break; default: this._count = undefined; - this._until = undefined; + this._untilDay = undefined; } e.stopPropagation(); } @@ -413,7 +416,9 @@ export class RecurrenceRuleEditor extends LitElement { private _onUntilChange(e: CustomEvent) { e.stopPropagation(); - this._until = new Date(e.detail.value); + this._untilDay = toDate(e.detail.value + "T00:00:00", { + timeZone: this.timezone, + }); } // Reset the weekday selected when there is only a single value @@ -442,18 +447,27 @@ export class RecurrenceRuleEditor extends LitElement { freq: convertRepeatFrequency(this._freq!)!, interval: this._interval > 1 ? this._interval : undefined, count: this._count, - until: this._until, - tzid: this.timezone, byweekday: byweekday, bymonthday: bymonthday, }; let contentline = RRule.optionsToString(options); - if (this._until && this.allDay) { - // rrule.js only computes UNTIL values as DATE-TIME however rfc5545 says - // The value of the UNTIL rule part MUST have the same value type as the - // "DTSTART" property. If needed, strip off any time values as a workaround - // This converts "UNTIL=20220512T060000" to "UNTIL=20220512" - contentline = contentline.replace(/(UNTIL=\d{8})T\d{6}Z?/, "$1"); + if (this._untilDay) { + // The UNTIL value should be inclusive of the last event instance + const until = toDate( + this._formatDate(this._untilDay!) + + "T" + + this._formatTime(this.dtstart!), + { timeZone: this.timezone } + ); + // rrule.js can't compute some UNTIL variations so we compute that ourself. Must be + // in the same format as dtstart. + const format = this.allDay ? "yyyyMMdd" : "yyyyMMdd'T'HHmmss"; + const newUntilValue = formatInTimeZone( + until, + this.hass.config.time_zone, + format + ); + contentline += `;UNTIL=${newUntilValue}`; } return contentline.slice(6); // Strip "RRULE:" prefix } @@ -473,6 +487,16 @@ export class RecurrenceRuleEditor extends LitElement { ); } + // Formats a date in browser display timezone + private _formatDate(date: Date): string { + return formatInTimeZone(date, this.timezone!, "yyyy-MM-dd"); + } + + // Formats a time in browser display timezone + private _formatTime(date: Date): string { + return formatInTimeZone(date, this.timezone!, "HH:mm:ss"); + } + static styles = css` ha-textfield, ha-select {