diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index e49906d686..ad51a2699b 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,7 +9,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3.0.0 + - uses: dessant/lock-threads@v4.0.0 with: github-token: ${{ github.token }} issue-lock-inactive-days: "30" diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index 89cb7c500d..b0316ed0ee 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -28,6 +28,7 @@ class HassioDashboard extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .route=${this.route} + back-path="/config" .header=${this.supervisor.localize("panel.addons")} > (http://paulusschoutsen.nl)", "license": "Apache-2.0", "dependencies": { - "@braintree/sanitize-url": "^5.0.2", + "@braintree/sanitize-url": "^6.0.0", "@codemirror/autocomplete": "^0.19.12", "@codemirror/commands": "^0.19.8", "@codemirror/gutter": "^0.19.9", diff --git a/pyproject.toml b/pyproject.toml index c6e0d8889d..4a4e82f951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221201.1" +version = "20221205.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/common/ensure-array.ts b/src/common/array/ensure-array.ts similarity index 100% rename from src/common/ensure-array.ts rename to src/common/array/ensure-array.ts diff --git a/src/common/array/literal-includes.ts b/src/common/array/literal-includes.ts new file mode 100644 index 0000000000..c73648586a --- /dev/null +++ b/src/common/array/literal-includes.ts @@ -0,0 +1,5 @@ +// Creates a type predicate function for determining if an array literal includes a given value +export const arrayLiteralIncludes = + (array: T) => + (searchElement: unknown, fromIndex?: number): searchElement is T[number] => + array.includes(searchElement as T[number], fromIndex); diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index fb6e75feb0..35bc5eaa7a 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -7,10 +7,12 @@ if (__BUILD__ === "latest" && polyfillsLoaded) { } // Tuesday, August 10 -export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) => - formatDateWeekdayMem(locale).format(dateObj); +export const formatDateWeekdayDay = ( + dateObj: Date, + locale: FrontendLocaleData +) => formatDateWeekdayDayMem(locale).format(dateObj); -const formatDateWeekdayMem = memoizeOne( +const formatDateWeekdayDayMem = memoizeOne( (locale: FrontendLocaleData) => new Intl.DateTimeFormat(locale.language, { weekday: "long", @@ -92,3 +94,14 @@ const formatDateYearMem = memoizeOne( year: "numeric", }) ); + +// Monday +export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) => + formatDateWeekdayMem(locale).format(dateObj); + +const formatDateWeekdayMem = memoizeOne( + (locale: FrontendLocaleData) => + new Intl.DateTimeFormat(locale.language, { + weekday: "long", + }) +); diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts index 7c59eb2faa..92c1365b14 100644 --- a/src/common/entity/get_states.ts +++ b/src/common/entity/get_states.ts @@ -261,6 +261,11 @@ export const getStates = ( result.push(...state.attributes.activity_list); } break; + case "sensor": + if (!attribute && state.attributes.device_class === "enum") { + result.push(...state.attributes.options); + } + break; case "vacuum": if (attribute === "fan_speed") { result.push(...state.attributes.fan_speed_list); diff --git a/src/common/translations/day_names.ts b/src/common/translations/day_names.ts new file mode 100644 index 0000000000..34b18fdb4a --- /dev/null +++ b/src/common/translations/day_names.ts @@ -0,0 +1,10 @@ +import { addDays, startOfWeek } from "date-fns"; +import memoizeOne from "memoize-one"; +import { FrontendLocaleData } from "../../data/translation"; +import { formatDateWeekday } from "../datetime/format_date"; + +export const dayNames = memoizeOne((locale: FrontendLocaleData): string[] => + Array.from({ length: 7 }, (_, d) => + formatDateWeekday(addDays(startOfWeek(new Date()), d), locale) + ) +); diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index de307f90f7..0b5fc77030 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -18,6 +18,7 @@ export type LocalizeKeys = | `ui.card.alarm_control_panel.${string}` | `ui.card.weather.attributes.${string}` | `ui.card.weather.cardinal_direction.${string}` + | `ui.components.calendar.event.rrule.${string}` | `ui.components.logbook.${string}` | `ui.components.selectors.file.${string}` | `ui.dialogs.entity_registry.editor.${string}` @@ -30,7 +31,6 @@ export type LocalizeKeys = | `ui.panel.config.dashboard.${string}` | `ui.panel.config.devices.${string}` | `ui.panel.config.energy.${string}` - | `ui.panel.config.helpers.${string}` | `ui.panel.config.info.${string}` | `ui.panel.config.logs.${string}` | `ui.panel.config.lovelace.${string}` diff --git a/src/common/translations/month_names.ts b/src/common/translations/month_names.ts new file mode 100644 index 0000000000..2e456b1855 --- /dev/null +++ b/src/common/translations/month_names.ts @@ -0,0 +1,10 @@ +import { addMonths, startOfYear } from "date-fns"; +import memoizeOne from "memoize-one"; +import { FrontendLocaleData } from "../../data/translation"; +import { formatDateMonth } from "../datetime/format_date"; + +export const monthNames = memoizeOne((locale: FrontendLocaleData): string[] => + Array.from({ length: 12 }, (_, m) => + formatDateMonth(addMonths(startOfYear(new Date()), m), locale) + ) +); diff --git a/src/components/chart/chart-date-adapter.ts b/src/components/chart/chart-date-adapter.ts index 3819a22e88..462ee52dc1 100644 --- a/src/components/chart/chart-date-adapter.ts +++ b/src/components/chart/chart-date-adapter.ts @@ -40,7 +40,7 @@ import { formatDateMonth, formatDateMonthYear, formatDateShort, - formatDateWeekday, + formatDateWeekdayDay, formatDateYear, } from "../../common/datetime/format_date"; import { @@ -92,7 +92,7 @@ _adapters._date.override({ case "hour": return formatTime(new Date(time), this.options.locale); case "weekday": - return formatDateWeekday(new Date(time), this.options.locale); + return formatDateWeekdayDay(new Date(time), this.options.locale); case "date": return formatDate(new Date(time), this.options.locale); case "day": diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 533f6c2efc..a19ee9e011 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -3,7 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; import { stringCompare } from "../../common/string/compare"; import { diff --git a/src/components/ha-chip.ts b/src/components/ha-chip.ts index 88bfc8be3c..d9fa23971f 100644 --- a/src/components/ha-chip.ts +++ b/src/components/ha-chip.ts @@ -77,6 +77,10 @@ export class HaChip extends LitElement { span[role="gridcell"] { line-height: 14px; } + + :host { + outline: none; + } `; } } diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index 21b7fb1c1d..797b5bdbd9 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -31,7 +31,7 @@ export class HaExpansionPanel extends LitElement { protected render(): TemplateResult { return html` -
+
{ (Date.now() - new Date(stateObj.attributes.media_position_updated_at!).getTime()) / 1000.0; - return progress; + // Prevent negative values, so we do not go back to 59:59 at the start + // for example if there are slight clock sync deltas between backend and frontend and + // therefore media_position_updated_at might be slightly larger than Date.now(). + return progress < 0 ? 0 : progress; }; export const computeMediaDescription = ( @@ -402,7 +405,13 @@ export const cleanupMediaTitle = (title?: string): string | undefined => { } const index = title.indexOf("?authSig="); - return index > 0 ? title.slice(0, index) : title; + let cleanTitle = index > 0 ? title.slice(0, index) : title; + + if (cleanTitle.startsWith("http")) { + cleanTitle = decodeURIComponent(cleanTitle.split("/").pop()!); + } + + return cleanTitle; }; /** diff --git a/src/data/recorder.ts b/src/data/recorder.ts index 510212c1e8..1f12f5e9e2 100644 --- a/src/data/recorder.ts +++ b/src/data/recorder.ts @@ -267,7 +267,7 @@ export const adjustStatisticsSum = ( return hass.callWS({ type: "recorder/adjust_sum_statistics", statistic_id, - start_time_iso, + start_time: start_time_iso, adjustment, adjustment_unit_of_measurement, }); diff --git a/src/data/script.ts b/src/data/script.ts index 52a3f10f29..ed0a2c8e6a 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -15,6 +15,7 @@ import { Describe, boolean, } from "superstruct"; +import { arrayLiteralIncludes } from "../common/array/literal-includes"; import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; import { @@ -28,11 +29,7 @@ import { BlueprintInput } from "./blueprint"; export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"] as const; - -export const isMaxMode = ( - mode: typeof MODES[number] -): mode is typeof MODES_MAX[number] => - MODES_MAX.includes(mode as typeof MODES_MAX[number]); +export const isMaxMode = arrayLiteralIncludes(MODES_MAX); export const baseActionStruct = object({ alias: optional(string()), diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 163f13e2e0..1d99a9c2d5 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -1,6 +1,6 @@ import { formatDuration } from "../common/datetime/format_duration"; import secondsToDuration from "../common/datetime/seconds_to_duration"; -import { ensureArray } from "../common/ensure-array"; +import { ensureArray } from "../common/array/ensure-array"; import { computeStateName } from "../common/entity/compute_state_name"; import { isTemplate } from "../common/string/has-template"; import { HomeAssistant } from "../types"; diff --git a/src/data/shopping-list.ts b/src/data/shopping-list.ts index 1d1d16b26e..7a1d60a1ca 100644 --- a/src/data/shopping-list.ts +++ b/src/data/shopping-list.ts @@ -39,6 +39,15 @@ export const addItem = ( name, }); +export const removeItem = ( + hass: HomeAssistant, + item_id: string +): Promise => + hass.callWS({ + type: "shopping_list/items/remove", + item_id, + }); + export const reorderItems = ( hass: HomeAssistant, itemIds: string[] diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index a680dbd754..01a091b146 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -14,7 +14,7 @@ import { TemplateResult, } from "lit"; import { customElement, property } from "lit/decorators"; -import { formatDateWeekday } from "../../../common/datetime/format_date"; +import { formatDateWeekdayDay } from "../../../common/datetime/format_date"; import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatNumber } from "../../../common/number/format_number"; import "../../../components/ha-svg-icon"; @@ -170,7 +170,7 @@ class MoreInfoWeather extends LitElement { ` : html`
- ${formatDateWeekday( + ${formatDateWeekdayDay( new Date(item.datetime), this.hass.locale )} diff --git a/src/panels/calendar/dialog-calendar-event-detail.ts b/src/panels/calendar/dialog-calendar-event-detail.ts index cf3db9adb5..ebe36d920c 100644 --- a/src/panels/calendar/dialog-calendar-event-detail.ts +++ b/src/panels/calendar/dialog-calendar-event-detail.ts @@ -3,12 +3,15 @@ import { mdiCalendarClock, mdiClose } from "@mdi/js"; import { addDays, isSameDay } from "date-fns/esm"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; -import { RRule } from "rrule"; +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"; @@ -84,8 +87,13 @@ class DialogCalendarEventDetail extends LitElement {
${this._formatDateRange()}
${this._data!.rrule - ? this._renderRruleAsText(this._data.rrule) + ? this._renderRRuleAsText(this._data.rrule) : ""} + ${this._data.description + ? html`
+
${this._data.description}
+
` + : html``}
@@ -108,7 +116,8 @@ class DialogCalendarEventDetail extends LitElement { ${this.hass.localize("ui.components.calendar.event.delete")} ` - : ""}${this._params.canEdit + : ""} + ${this._params.canEdit ? html`${readableText}
`; - } catch (e) { + private _renderRRuleAsText(value: string) { + if (!value) { 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 + ) + )} +
`; + } + + 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() { const start = new Date(this._data!.dtstart); // All day events should be displayed as a day earlier @@ -227,7 +278,7 @@ class DialogCalendarEventDetail extends LitElement { ha-svg-icon { width: 40px; margin-right: 8px; - margin-inline-end: 8px; + margin-inline-end: 16px; margin-inline-start: initial; direction: var(--direction); vertical-align: top; @@ -235,6 +286,11 @@ class DialogCalendarEventDetail extends LitElement { .field { display: flex; } + .description { + color: var(--secondary-text-color); + max-width: 300px; + overflow-wrap: break-word; + } `, ]; } diff --git a/src/panels/calendar/dialog-calendar-event-editor.ts b/src/panels/calendar/dialog-calendar-event-editor.ts index 790312570f..06dc41c1fe 100644 --- a/src/panels/calendar/dialog-calendar-event-editor.ts +++ b/src/panels/calendar/dialog-calendar-event-editor.ts @@ -7,6 +7,7 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isDate } from "../../common/string/is_date"; import "../../components/ha-date-input"; +import "../../components/ha-textarea"; import "../../components/ha-time-input"; import { Calendar, @@ -42,6 +43,8 @@ class DialogCalendarEventEditor extends LitElement { @state() private _summary = ""; + @state() private _description = ""; + @state() private _rrule?: string; @state() private _allDay = false; @@ -73,7 +76,11 @@ class DialogCalendarEventEditor extends LitElement { } } else { this._allDay = false; - this._dtstart = startOfHour(new Date()); + // If we have been provided a selected date (e.g. based on the currently displayed + // day in a calendar view), use that as the starting value. + this._dtstart = startOfHour( + params.selectedDate ? params.selectedDate : new Date() + ); this._dtend = addHours(this._dtstart, 1); } } @@ -123,6 +130,15 @@ class DialogCalendarEventEditor extends LitElement { error-message=${this.hass.localize("ui.common.error_required")} dialogInitialFocus > + @@ -247,6 +264,10 @@ class DialogCalendarEventEditor extends LitElement { this._summary = ev.target.value; } + private _handleDescriptionChanged(ev) { + this._description = ev.target.value; + } + private _handleRRuleChanged(ev) { this._rrule = ev.detail.value; } @@ -286,6 +307,7 @@ class DialogCalendarEventEditor extends LitElement { ); const data: CalendarEventMutableParams = { summary: this._summary, + description: this._description, rrule: this._rrule, dtstart: "", dtend: "", @@ -308,6 +330,13 @@ class DialogCalendarEventEditor extends LitElement { } private async _createEvent() { + if (!this._summary || !this._calendarId) { + this._error = this.hass.localize( + "ui.components.calendar.event.not_all_required_fields" + ); + return; + } + this._submitting = true; try { await createCalendarEvent( @@ -385,6 +414,7 @@ class DialogCalendarEventEditor extends LitElement { this._dtstart = undefined; this._dtend = undefined; this._summary = ""; + this._description = ""; this._rrule = undefined; } @@ -395,9 +425,16 @@ class DialogCalendarEventEditor extends LitElement { state-info { line-height: 40px; } - ha-textfield { + ha-alert { display: block; - margin-bottom: 24px; + margin-bottom: 16px; + } + ha-textfield, + ha-textarea { + display: block; + } + ha-textarea { + margin-bottom: 16px; } ha-formfield { display: block; @@ -430,12 +467,11 @@ class DialogCalendarEventEditor extends LitElement { } ha-combo-box { display: block; - margin-bottom: 24px; } ha-svg-icon { width: 40px; margin-right: 8px; - margin-inline-end: 8px; + margin-inline-end: 16px; margin-inline-start: initial; direction: var(--direction); vertical-align: top; diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 5fcde29675..3cd139d303 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -29,28 +29,29 @@ import { } from "lit"; import { property, state } from "lit/decorators"; import memoize from "memoize-one"; +import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { useAmPm } from "../../common/datetime/use_am_pm"; import { fireEvent } from "../../common/dom/fire_event"; +import { supportsFeature } from "../../common/entity/supports-feature"; +import { LocalizeFunc } from "../../common/translations/localize"; +import { computeRTLDirection } from "../../common/util/compute_rtl"; import "../../components/ha-button-toggle-group"; import "../../components/ha-fab"; -import "../../components/ha-icon-button-prev"; import "../../components/ha-icon-button-next"; +import "../../components/ha-icon-button-prev"; +import type { + Calendar as CalendarData, + CalendarEvent, +} from "../../data/calendar"; +import { CalendarEntityFeature } from "../../data/calendar"; import { haStyle } from "../../resources/styles"; -import { computeRTLDirection } from "../../common/util/compute_rtl"; import type { CalendarViewChanged, FullCalendarView, HomeAssistant, ToggleButton, } from "../../types"; -import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; -import { supportsFeature } from "../../common/entity/supports-feature"; import { showCalendarEventDetailDialog } from "./show-dialog-calendar-event-detail"; -import type { - Calendar as CalendarData, - CalendarEvent, -} from "../../data/calendar"; -import { CalendarEntityFeature } from "../../data/calendar"; import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor"; declare global { @@ -62,15 +63,6 @@ declare global { } } -const getListWeekRange = (currentDate: Date): { start: Date; end: Date } => { - const startDate = new Date(currentDate.valueOf()); - const endDate = new Date(currentDate.valueOf()); - - endDate.setDate(endDate.getDate() + 7); - - return { start: startDate, end: endDate }; -}; - const defaultFullCalendarConfig: CalendarOptions = { headerToolbar: false, plugins: [dayGridPlugin, listPlugin, interactionPlugin], @@ -80,19 +72,13 @@ const defaultFullCalendarConfig: CalendarOptions = { eventDisplay: "list-item", locales: allLocales, views: { - list: { - visibleRange: getListWeekRange, + listWeek: { + type: "list", + duration: { days: 7 }, }, }, }; -const viewButtons: ToggleButton[] = [ - { label: "Month View", value: "dayGridMonth", iconPath: mdiViewModule }, - { label: "Week View", value: "dayGridWeek", iconPath: mdiViewWeek }, - { label: "Day View", value: "dayGridDay", iconPath: mdiViewDay }, - { label: "List View", value: "list", iconPath: mdiViewAgenda }, -]; - export class HAFullCalendar extends LitElement { public hass!: HomeAssistant; @@ -106,12 +92,15 @@ export class HAFullCalendar extends LitElement { "dayGridMonth", "dayGridWeek", "dayGridDay", + "listWeek", ]; @property() public initialView: FullCalendarView = "dayGridMonth"; private calendar?: Calendar; + private _viewButtons?: ToggleButton[]; + @state() private _activeView = this.initialView; public updateSize(): void { @@ -119,7 +108,10 @@ export class HAFullCalendar extends LitElement { } protected render(): TemplateResult { - const viewToggleButtons = this._viewToggleButtons(this.views); + const viewToggleButtons = this._viewToggleButtons( + this.views, + this.hass.localize + ); return html` ${this.calendar @@ -276,8 +268,19 @@ export class HAFullCalendar extends LitElement { } private _createEvent(_info) { + // Logic for selectedDate: In week and day view, use the start of the week or the selected day. + // If we are in month view, we only use the start of the month, if we are not showing the + // current actual month, as for that one the current day is automatically highlighted and + // defaulting to a different day in the event creation dialog would be weird. showCalendarEventEditDialog(this, { calendars: this._mutableCalendars, + selectedDate: + this._activeView === "dayGridWeek" || + this._activeView === "dayGridDay" || + (this._activeView === "dayGridMonth" && + this.calendar!.view.currentStart.getMonth() !== new Date().getMonth()) + ? this.calendar!.view.currentStart + : undefined, updated: () => { this._fireViewChanged(); }, @@ -338,11 +341,44 @@ export class HAFullCalendar extends LitElement { }); } - private _viewToggleButtons = memoize((views) => - viewButtons.filter((button) => + private _viewToggleButtons = memoize((views, localize: LocalizeFunc) => { + if (!this._viewButtons) { + this._viewButtons = [ + { + label: localize( + "ui.panel.lovelace.editor.card.calendar.views.dayGridMonth" + ), + value: "dayGridMonth", + iconPath: mdiViewModule, + }, + { + label: localize( + "ui.panel.lovelace.editor.card.calendar.views.dayGridWeek" + ), + value: "dayGridWeek", + iconPath: mdiViewWeek, + }, + { + label: localize( + "ui.panel.lovelace.editor.card.calendar.views.dayGridDay" + ), + value: "dayGridDay", + iconPath: mdiViewDay, + }, + { + label: localize( + "ui.panel.lovelace.editor.card.calendar.views.listWeek" + ), + value: "listWeek", + iconPath: mdiViewAgenda, + }, + ]; + } + + return this._viewButtons.filter((button) => views.includes(button.value as FullCalendarView) - ) - ); + ); + }); static get styles(): CSSResultGroup { return [ @@ -380,7 +416,7 @@ export class HAFullCalendar extends LitElement { } a { - color: var(--primary-text-color); + color: var(--primary-color); } .controls { @@ -442,6 +478,12 @@ export class HAFullCalendar extends LitElement { .fc-theme-standard .fc-scrollgrid { border: 1px solid var(--divider-color); + border-radius: var(--mdc-shape-small, 4px); + } + + .fc-theme-standard td { + border-bottom-left-radius: var(--mdc-shape-small, 4px); + border-bottom-right-radius: var(--mdc-shape-small, 4px); } .fc-scrollgrid-section-header td { @@ -449,9 +491,10 @@ export class HAFullCalendar extends LitElement { } th.fc-col-header-cell.fc-day { - color: var(--secondary-text-color); + background-color: var(--table-header-background-color); + color: var(--primary-text-color); font-size: 11px; - font-weight: 400; + font-weight: bold; text-transform: uppercase; } diff --git a/src/panels/calendar/ha-recurrence-rule-editor.ts b/src/panels/calendar/ha-recurrence-rule-editor.ts index b2c97a9aaa..08e239b62a 100644 --- a/src/panels/calendar/ha-recurrence-rule-editor.ts +++ b/src/panels/calendar/ha-recurrence-rule-editor.ts @@ -34,6 +34,8 @@ export class RecurrenceRuleEditor extends LitElement { @property({ attribute: false }) public locale!: HomeAssistant["locale"]; + @property() public timezone?: string; + @state() private _computedRRule = ""; @state() private _freq?: RepeatFrequency = "none"; @@ -292,6 +294,7 @@ export class RecurrenceRuleEditor extends LitElement { byweekday: ruleByWeekDay(this._weekday), count: this._count, until: this._until, + tzid: this.timezone, }; const contentline = RRule.optionsToString(options); return contentline.slice(6); // Strip "RRULE:" prefix diff --git a/src/panels/calendar/show-dialog-calendar-event-detail.ts b/src/panels/calendar/show-dialog-calendar-event-detail.ts index 902e8746b6..9946ed9bdb 100644 --- a/src/panels/calendar/show-dialog-calendar-event-detail.ts +++ b/src/panels/calendar/show-dialog-calendar-event-detail.ts @@ -2,7 +2,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { Calendar, CalendarEventData } from "../../data/calendar"; export interface CalendarEventDetailDialogParams { - calendars: Calendar[]; // When creating new events, is the list of events that support creation + calendars: Calendar[]; // When creating new events, is the list of calendar entities that support creation calendarId?: string; entry?: CalendarEventData; canDelete?: boolean; diff --git a/src/panels/calendar/show-dialog-calendar-event-editor.ts b/src/panels/calendar/show-dialog-calendar-event-editor.ts index 695832d164..f2ab58a4f6 100644 --- a/src/panels/calendar/show-dialog-calendar-event-editor.ts +++ b/src/panels/calendar/show-dialog-calendar-event-editor.ts @@ -2,8 +2,9 @@ import { fireEvent } from "../../common/dom/fire_event"; import { Calendar, CalendarEventData } from "../../data/calendar"; export interface CalendarEventEditDialogParams { - calendars: Calendar[]; // When creating new events, is the list of events that support creation + calendars: Calendar[]; // When creating new events, is the list of calendar entities that support creation calendarId?: string; + selectedDate?: Date; // When provided is used as the pre-filled date for the event creation dialog entry?: CalendarEventData; canDelete?: boolean; updated: () => void; diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index b9b15a384d..bbc9b5a039 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -2,7 +2,7 @@ import { mdiDelete, mdiPlus } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import "../../../../../components/ha-icon-button"; import { Condition } from "../../../../../data/automation"; import { Action, ChooseAction } from "../../../../../data/script"; diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index 83102c1d82..e59c1800dd 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -1,22 +1,13 @@ import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { any, assert, object, optional, string } from "superstruct"; +import { assert } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; -import { entityIdOrAll } from "../../../../../common/structs/is-entity-id"; import "../../../../../components/ha-service-control"; -import { ServiceAction } from "../../../../../data/script"; +import { ServiceAction, serviceActionStruct } from "../../../../../data/script"; import type { HomeAssistant } from "../../../../../types"; import { ActionElement } from "../ha-automation-action-row"; -const actionStruct = object({ - alias: optional(string()), - service: optional(string()), - entity_id: optional(entityIdOrAll()), - target: optional(any()), - data: optional(any()), -}); - @customElement("ha-automation-action-service") export class HaServiceAction extends LitElement implements ActionElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -38,7 +29,7 @@ export class HaServiceAction extends LitElement implements ActionElement { return; } try { - assert(this.action, actionStruct); + assert(this.action, serviceActionStruct); } catch (err: any) { fireEvent(this, "ui-mode-not-available", err); return; diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index ceabac3ce7..89fdb53585 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -10,7 +10,7 @@ import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; import "../../../../../components/ha-duration-input"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { TimeChangedEvent } from "../../../../../components/ha-base-time-input"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; @customElement("ha-automation-action-wait_for_trigger") export class HaWaitForTriggerAction diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index 8c5e54f645..3eb72d48d2 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -3,7 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import "../../../../../components/ha-select"; import type { AutomationConfig, diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index abce03227e..b17d3bfd71 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -11,7 +11,7 @@ import { union, } from "superstruct"; import memoizeOne from "memoize-one"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import { StateTrigger } from "../../../../../data/automation"; diff --git a/src/panels/config/helpers/const.ts b/src/panels/config/helpers/const.ts index 87b03eff10..82992ca6b4 100644 --- a/src/panels/config/helpers/const.ts +++ b/src/panels/config/helpers/const.ts @@ -1,3 +1,4 @@ +import { arrayLiteralIncludes } from "../../../common/array/literal-includes"; import type { Counter } from "../../../data/counter"; import type { InputBoolean } from "../../../data/input_boolean"; import type { InputButton } from "../../../data/input_button"; @@ -18,7 +19,10 @@ export const HELPER_DOMAINS = [ "counter", "timer", "schedule", -]; +] as const; + +export type HelperDomain = typeof HELPER_DOMAINS[number]; +export const isHelperDomain = arrayLiteralIncludes(HELPER_DOMAINS); export type Helper = | InputBoolean diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index 672f40a26e..ab0e7c5d64 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -25,7 +25,7 @@ import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-c import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; -import { Helper } from "./const"; +import { Helper, HelperDomain } from "./const"; import "./forms/ha-counter-form"; import "./forms/ha-input_boolean-form"; import "./forms/ha-input_button-form"; @@ -37,7 +37,18 @@ import "./forms/ha-schedule-form"; import "./forms/ha-timer-form"; import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail"; -const HELPERS = { +type HelperCreators = { + [domain in HelperDomain]: ( + hass: HomeAssistant, + // Not properly typed because there is currently a mismatch for this._item between: + // 1. Type passed to form should be Helper + // 2. Type received by creator should be MutableParams version + // The two are not compatible. + params: any + ) => Promise; +}; + +const HELPERS: HelperCreators = { input_boolean: createInputBoolean, input_button: createInputButton, input_text: createInputText, @@ -57,7 +68,7 @@ export class DialogHelperDetail extends LitElement { @state() private _opened = false; - @state() private _domain?: string; + @state() private _domain?: HelperDomain; @state() private _error?: string; @@ -127,7 +138,7 @@ export class DialogHelperDetail extends LitElement { } else { const items: [string, string][] = []; - for (const helper of Object.keys(HELPERS)) { + for (const helper of Object.keys(HELPERS) as (keyof typeof HELPERS)[]) { items.push([ helper, this.hass.localize(`ui.panel.config.helpers.types.${helper}`) || diff --git a/src/panels/config/helpers/forms/ha-schedule-form.ts b/src/panels/config/helpers/forms/ha-schedule-form.ts index 2c238c1ef4..ba7376f492 100644 --- a/src/panels/config/helpers/forms/ha-schedule-form.ts +++ b/src/panels/config/helpers/forms/ha-schedule-form.ts @@ -208,7 +208,10 @@ class HaScheduleForm extends LitElement { private get _events() { const events: any[] = []; const currentDay = new Date().getDay(); - const baseDay = currentDay === 0 ? 7 : currentDay; + const baseDay = + currentDay === 0 && firstWeekdayIndex(this.hass.locale) === 1 + ? 7 + : currentDay; for (const [i, day] of weekdays.entries()) { if (!this[`_${day}`].length) { @@ -216,8 +219,11 @@ class HaScheduleForm extends LitElement { } this[`_${day}`].forEach((item: ScheduleDay, index: number) => { - // Add 7 to 0 because we start the calendar on Monday - const distance = i - baseDay + (i === 0 ? 7 : 0); + // Add 7 to 0 because we start the calendar on Monday, except when the locale says otherwise (firstWeekdayIndex() != 1) + const distance = + i - + baseDay + + (i === 0 && firstWeekdayIndex(this.hass.locale) === 1 ? 7 : 0); const start = new Date(); start.setDate(start.getDate() + distance); @@ -388,6 +394,8 @@ class HaScheduleForm extends LitElement { -webkit-user-select: none; -ms-user-select: none; user-select: none; + --fc-border-color: var(--divider-color); + --fc-event-border-color: var(--divider-color); } .fc-scroller { overflow-x: visible !important; @@ -395,6 +403,18 @@ class HaScheduleForm extends LitElement { .fc-v-event .fc-event-time { white-space: inherit; } + + a { + color: inherit !important; + } + + th.fc-col-header-cell.fc-day { + background-color: var(--table-header-background-color); + color: var(--primary-text-color); + font-size: 11px; + font-weight: bold; + text-transform: uppercase; + } `, ]; } diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 4861cffa72..8e8c8268fe 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -35,7 +35,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; -import { HELPER_DOMAINS } from "./const"; +import { HelperDomain, isHelperDomain } from "./const"; import { showHelperDetailDialog } from "./show-dialog-helper-detail"; // This groups items by a key but only returns last entry per key. @@ -118,7 +118,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { sortable: true, width: "25%", filterable: true, - template: (type, row) => + template: (type: HelperDomain, row) => row.configEntry ? domainToName(localize, type) : html` @@ -243,7 +243,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { if (!domain) { return; } - if (HELPER_DOMAINS.includes(domain)) { + if (isHelperDomain(domain)) { showHelperDetailDialog(this, { domain, }); @@ -330,7 +330,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { const newStates = Object.values(this.hass!.states).filter( (entity) => extraEntities.has(entity.entity_id) || - HELPER_DOMAINS.includes(computeStateDomain(entity)) + isHelperDomain(computeStateDomain(entity)) ); if ( diff --git a/src/panels/config/helpers/show-dialog-helper-detail.ts b/src/panels/config/helpers/show-dialog-helper-detail.ts index bbee0bc619..da1942c108 100644 --- a/src/panels/config/helpers/show-dialog-helper-detail.ts +++ b/src/panels/config/helpers/show-dialog-helper-detail.ts @@ -1,10 +1,11 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { DataEntryFlowDialogParams } from "../../../dialogs/config-flow/show-dialog-data-entry-flow"; +import { HelperDomain } from "./const"; export const loadHelperDetailDialog = () => import("./dialog-helper-detail"); export interface ShowDialogHelperDetailParams { - domain?: string; + domain?: HelperDomain; // Only used for config entries dialogClosedCallback?: DataEntryFlowDialogParams["dialogClosedCallback"]; } diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 0ea91c848b..3ba289ae99 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -70,7 +70,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; -import { HELPER_DOMAINS } from "../helpers/const"; +import { isHelperDomain } from "../helpers/const"; import "./ha-config-flow-card"; import "./ha-ignored-config-entry-card"; import "./ha-integration-card"; @@ -785,7 +785,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { } // If not an integration or supported brand, try helper else show alert - if (HELPER_DOMAINS.includes(domain)) { + if (isHelperDomain(domain)) { navigate(`/config/helpers/add?domain=${domain}`, { replace: true, }); diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 9a145617ba..5d3b7096c7 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -548,6 +548,9 @@ export class HaIntegrationCard extends LitElement { } private async _handleDisableDebugLogging(ev: MouseEvent) { + // Stop propagation since otherwise we end up here twice while we await the log level change + // and trigger two identical debug log downloads. + ev.stopPropagation(); const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any) .configEntry; const integration = configEntry.domain; 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 1d81f69fe4..f658f0b6fb 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 @@ -82,7 +82,7 @@ class HaPanelDevMqtt extends LitElement { } private _handleTopic(ev: CustomEvent) { - this.topic = ev.detail.value; + this.topic = (ev.target! as any).value; if (localStorage && this.inited) { localStorage["panel-dev-mqtt-topic"] = this.topic; } diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 95573534bd..8f07895d24 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -18,7 +18,7 @@ import { css, html, LitElement, PropertyValues } from "lit"; import { property, state } from "lit/decorators"; import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { LocalStorage } from "../../common/decorators/local-storage"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { navigate } from "../../common/navigate"; import { createSearchParam, diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index 025d8369ce..bfeaf28c67 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -1,7 +1,7 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { throttle } from "../../common/util/throttle"; import "../../components/ha-circular-progress"; diff --git a/src/panels/lovelace/cards/hui-calendar-card.ts b/src/panels/lovelace/cards/hui-calendar-card.ts index 313762e918..6b551746c3 100644 --- a/src/panels/lovelace/cards/hui-calendar-card.ts +++ b/src/panels/lovelace/cards/hui-calendar-card.ts @@ -119,8 +119,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard { } const views: FullCalendarView[] = this._veryNarrow - ? ["list"] - : ["list", "dayGridMonth", "dayGridDay"]; + ? ["listWeek"] + : ["dayGridMonth", "dayGridDay", "listWeek"]; return html` diff --git a/src/panels/lovelace/cards/hui-shopping-list-card.ts b/src/panels/lovelace/cards/hui-shopping-list-card.ts index 8886fdddbd..f1d82f45e3 100644 --- a/src/panels/lovelace/cards/hui-shopping-list-card.ts +++ b/src/panels/lovelace/cards/hui-shopping-list-card.ts @@ -8,32 +8,33 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { guard } from "lit/directives/guard"; import { repeat } from "lit/directives/repeat"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import "../../../components/ha-card"; -import "../../../components/ha-svg-icon"; import "../../../components/ha-checkbox"; +import "../../../components/ha-svg-icon"; import "../../../components/ha-textfield"; +import type { HaTextField } from "../../../components/ha-textfield"; import { addItem, clearItems, fetchItems, + removeItem, reorderItems, ShoppingListItem, updateItem, } from "../../../data/shopping-list"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCard, LovelaceCardEditor } from "../types"; -import { SensorCardConfig, ShoppingListCardConfig } from "./types"; -import type { HaTextField } from "../../../components/ha-textfield"; import { loadSortable, SortableInstance, } from "../../../resources/sortable.ondemand"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { SensorCardConfig, ShoppingListCardConfig } from "./types"; @customElement("hui-shopping-list-card") class HuiShoppingListCard @@ -264,9 +265,14 @@ class HuiShoppingListCard } private _saveEdit(ev): void { - updateItem(this.hass!, ev.target.itemId, { - name: ev.target.value, - }).catch(() => this._fetchData()); + // If name is not empty, update the item otherwise remove it + if (ev.target.value) { + updateItem(this.hass!, ev.target.itemId, { + name: ev.target.value, + }).catch(() => this._fetchData()); + } else { + removeItem(this.hass!, ev.target.itemId).catch(() => this._fetchData()); + } ev.target.blur(); } diff --git a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts index 7f0f6ff8a0..ed25fd5201 100644 --- a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts @@ -31,7 +31,7 @@ const cardConfigStruct = assign( }) ); -const views = ["dayGridMonth", "dayGridDay", "list"] as const; +const views = ["dayGridMonth", "dayGridDay", "listWeek"] as const; @customElement("hui-calendar-card-editor") export class HuiCalendarCardEditor diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index 0f911d8826..a491318179 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -20,7 +20,7 @@ import { union, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../common/ensure-array"; +import { ensureArray } from "../../../../common/array/ensure-array"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import { deepEqual } from "../../../../common/util/deep-equal"; import { diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 1953f1b6de..b46902e0b2 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -212,7 +212,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) { const mediaDescription = computeMediaDescription(stateObj); const mediaDuration = formatMediaTime(stateObj.attributes.media_duration); const mediaTitleClean = cleanupMediaTitle( - stateObj.attributes.media_title || "" + stateObj.attributes.media_title || stateObj.attributes.media_content_id ); const mediaArt = stateObj.attributes.entity_picture_local || @@ -232,7 +232,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {