From 92b7a3b477324fed396cdafcf2c086e0bd3911b0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Apr 2024 21:54:27 +0200 Subject: [PATCH 01/10] Adjust integration filter height for search bar (#20382) --- src/components/ha-filter-integrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-filter-integrations.ts b/src/components/ha-filter-integrations.ts index cab9726f44..86295e1095 100644 --- a/src/components/ha-filter-integrations.ts +++ b/src/components/ha-filter-integrations.ts @@ -92,7 +92,7 @@ export class HaFilterIntegrations extends LitElement { setTimeout(() => { if (!this.expanded) return; this.renderRoot.querySelector("mwc-list")!.style.height = - `${this.clientHeight - 49}px`; + `${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input }, 300); } } From 5f5ac5419b399960cd4d298b618bfdfd6457183a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 00:06:03 +0200 Subject: [PATCH 02/10] Add floor and label support to history panel (#20388) --- src/panels/history/ha-panel-history.ts | 212 +++++++++++-------------- 1 file changed, 94 insertions(+), 118 deletions(-) diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index fde97f8aeb..dc837141ae 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -9,6 +9,7 @@ import { property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import { storage } from "../../common/decorators/storage"; +import { computeDomain } from "../../common/entity/compute_domain"; import { navigate } from "../../common/navigate"; import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { @@ -27,37 +28,29 @@ import "../../components/ha-menu-button"; import "../../components/ha-target-picker"; import "../../components/ha-top-app-bar-fixed"; import { - AreaDeviceLookup, - AreaEntityLookup, - getAreaDeviceLookup, - getAreaEntityLookup, -} from "../../data/area_registry"; -import { - DeviceEntityLookup, - getDeviceEntityLookup, - subscribeDeviceRegistry, -} from "../../data/device_registry"; -import { subscribeEntityRegistry } from "../../data/entity_registry"; -import { - HistoryResult, - computeHistory, - subscribeHistory, - HistoryStates, EntityHistoryState, + HistoryResult, + HistoryStates, + LineChartState, LineChartUnit, computeGroupKey, - LineChartState, + computeHistory, + subscribeHistory, } from "../../data/history"; -import { fetchStatistics, Statistics } from "../../data/recorder"; +import { Statistics, fetchStatistics } from "../../data/recorder"; +import { + expandAreaTarget, + expandDeviceTarget, + expandFloorTarget, + expandLabelTarget, +} from "../../data/selector"; import { getSensorNumericDeviceClasses } from "../../data/sensor"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { fileDownload } from "../../util/file_download"; -import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; -import { computeDomain } from "../../common/entity/compute_domain"; -class HaPanelHistory extends SubscribeMixin(LitElement) { +class HaPanelHistory extends LitElement { @property({ attribute: false }) hass!: HomeAssistant; @property({ reflect: true, type: Boolean }) public narrow = false; @@ -83,12 +76,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { @state() private _statisticsHistory?: HistoryResult; - @state() private _deviceEntityLookup?: DeviceEntityLookup; - - @state() private _areaEntityLookup?: AreaEntityLookup; - - @state() private _areaDeviceLookup?: AreaDeviceLookup; - @state() private _showBack?: boolean; @@ -123,18 +110,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { this._unsubscribeHistory(); } - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._deviceEntityLookup = getDeviceEntityLookup(entities); - this._areaEntityLookup = getAreaEntityLookup(entities); - }), - subscribeDeviceRegistry(this.hass.connection!, (devices) => { - this._areaDeviceLookup = getAreaDeviceLookup(devices); - }), - ]; - } - private _goBack(): void { history.back(); } @@ -332,7 +307,9 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { const entityIds = searchParams.entity_id; const deviceIds = searchParams.device_id; const areaIds = searchParams.area_id; - if (entityIds || deviceIds || areaIds) { + const floorIds = searchParams.floor_id; + const labelsIds = searchParams.label_id; + if (entityIds || deviceIds || areaIds || floorIds || labelsIds) { this._targetPickerValue = {}; } if (entityIds) { @@ -347,6 +324,14 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { const splitIds = areaIds.split(","); this._targetPickerValue!.area_id = splitIds; } + if (floorIds) { + const splitIds = floorIds.split(","); + this._targetPickerValue!.floor_id = splitIds; + } + if (labelsIds) { + const splitIds = labelsIds.split(","); + this._targetPickerValue!.label_id = splitIds; + } const startDate = searchParams.start_date; if (startDate) { @@ -522,95 +507,80 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { private _getEntityIds(): string[] { return this.__getEntityIds( this._targetPickerValue, - this._deviceEntityLookup, - this._areaEntityLookup, - this._areaDeviceLookup + this.hass.entities, + this.hass.devices, + this.hass.areas ); } private __getEntityIds = memoizeOne( ( targetPickerValue: HassServiceTarget, - deviceEntityLookup: DeviceEntityLookup | undefined, - areaEntityLookup: AreaEntityLookup | undefined, - areaDeviceLookup: AreaDeviceLookup | undefined + entities: HomeAssistant["entities"], + devices: HomeAssistant["devices"], + areas: HomeAssistant["areas"] ): string[] => { - if ( - !targetPickerValue || - deviceEntityLookup === undefined || - areaEntityLookup === undefined || - areaDeviceLookup === undefined - ) { + if (!targetPickerValue) { return []; } - const entityIds = new Set(); - let { - area_id: searchingAreaId, - device_id: searchingDeviceId, - entity_id: searchingEntityId, - } = targetPickerValue; - - if (searchingAreaId) { - searchingAreaId = ensureArray(searchingAreaId); - for (const singleSearchingAreaId of searchingAreaId) { - const foundEntities = areaEntityLookup[singleSearchingAreaId]; - if (foundEntities?.length) { - for (const foundEntity of foundEntities) { - if (foundEntity.entity_category === null) { - entityIds.add(foundEntity.entity_id); - } - } - } - - const foundDevices = areaDeviceLookup[singleSearchingAreaId]; - if (!foundDevices?.length) { - continue; - } - - for (const foundDevice of foundDevices) { - const foundDeviceEntities = deviceEntityLookup[foundDevice.id]; - if (!foundDeviceEntities?.length) { - continue; - } - - for (const foundDeviceEntity of foundDeviceEntities) { - if ( - (!foundDeviceEntity.area_id || - foundDeviceEntity.area_id === singleSearchingAreaId) && - foundDeviceEntity.entity_category === null - ) { - entityIds.add(foundDeviceEntity.entity_id); - } - } - } - } + const targetSelector = { target: {} }; + const targetEntities = + ensureArray(targetPickerValue.entity_id)?.slice() || []; + const targetDevices = + ensureArray(targetPickerValue.device_id)?.slice() || []; + const targetAreas = ensureArray(targetPickerValue.area_id)?.slice() || []; + const targetFloors = ensureArray(targetPickerValue.floor_id)?.slice(); + const targetLabels = ensureArray(targetPickerValue.label_id)?.slice(); + if (targetLabels) { + targetLabels.forEach((labelId) => { + const expanded = expandLabelTarget( + this.hass, + labelId, + areas, + devices, + entities, + targetSelector + ); + targetDevices.push(...expanded.devices); + targetEntities.push(...expanded.entities); + targetAreas.push(...expanded.areas); + }); + } + if (targetFloors) { + targetFloors.forEach((floorId) => { + const expanded = expandFloorTarget( + this.hass, + floorId, + areas, + targetSelector + ); + targetAreas.push(...expanded.areas); + }); + } + if (targetAreas.length) { + targetAreas.forEach((areaId) => { + const expanded = expandAreaTarget( + this.hass, + areaId, + devices, + entities, + targetSelector + ); + targetEntities.push(...expanded.entities); + targetDevices.push(...expanded.devices); + }); + } + if (targetDevices.length) { + targetDevices.forEach((deviceId) => { + targetEntities.push( + ...expandDeviceTarget(this.hass, deviceId, entities, targetSelector) + .entities + ); + }); } - if (searchingDeviceId) { - searchingDeviceId = ensureArray(searchingDeviceId); - for (const singleSearchingDeviceId of searchingDeviceId) { - const foundEntities = deviceEntityLookup[singleSearchingDeviceId]; - if (!foundEntities?.length) { - continue; - } - - for (const foundEntity of foundEntities) { - if (foundEntity.entity_category === null) { - entityIds.add(foundEntity.entity_id); - } - } - } - } - - if (searchingEntityId) { - searchingEntityId = ensureArray(searchingEntityId); - for (const singleSearchingEntityId of searchingEntityId) { - entityIds.add(singleSearchingEntityId); - } - } - - return [...entityIds]; + return targetEntities; } ); @@ -639,6 +609,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { "," ); } + if (this._targetPickerValue.label_id) { + params.label_id = ensureArray(this._targetPickerValue.label_id).join(","); + } + if (this._targetPickerValue.floor_id) { + params.floor_id = ensureArray(this._targetPickerValue.floor_id).join(","); + } if (this._targetPickerValue.area_id) { params.area_id = ensureArray(this._targetPickerValue.area_id).join(","); } From 3a4e9b6856b8cd35719aadc033d27848494676ce Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 4 Apr 2024 12:12:04 +0200 Subject: [PATCH 03/10] Avoid duplicate entity ids in history (#20402) * Avoid duplicate entity ids in history * Don't need to check for size --- src/panels/history/ha-panel-history.ts | 107 ++++++++++++------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index dc837141ae..0fe7fc98c5 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -525,62 +525,59 @@ class HaPanelHistory extends LitElement { } const targetSelector = { target: {} }; - const targetEntities = - ensureArray(targetPickerValue.entity_id)?.slice() || []; - const targetDevices = - ensureArray(targetPickerValue.device_id)?.slice() || []; - const targetAreas = ensureArray(targetPickerValue.area_id)?.slice() || []; - const targetFloors = ensureArray(targetPickerValue.floor_id)?.slice(); - const targetLabels = ensureArray(targetPickerValue.label_id)?.slice(); - if (targetLabels) { - targetLabels.forEach((labelId) => { - const expanded = expandLabelTarget( - this.hass, - labelId, - areas, - devices, - entities, - targetSelector - ); - targetDevices.push(...expanded.devices); - targetEntities.push(...expanded.entities); - targetAreas.push(...expanded.areas); - }); - } - if (targetFloors) { - targetFloors.forEach((floorId) => { - const expanded = expandFloorTarget( - this.hass, - floorId, - areas, - targetSelector - ); - targetAreas.push(...expanded.areas); - }); - } - if (targetAreas.length) { - targetAreas.forEach((areaId) => { - const expanded = expandAreaTarget( - this.hass, - areaId, - devices, - entities, - targetSelector - ); - targetEntities.push(...expanded.entities); - targetDevices.push(...expanded.devices); - }); - } - if (targetDevices.length) { - targetDevices.forEach((deviceId) => { - targetEntities.push( - ...expandDeviceTarget(this.hass, deviceId, entities, targetSelector) - .entities - ); - }); - } + const targetEntities = new Set(ensureArray(targetPickerValue.entity_id)); + const targetDevices = new Set(ensureArray(targetPickerValue.device_id)); + const targetAreas = new Set(ensureArray(targetPickerValue.area_id)); + const targetFloors = new Set(ensureArray(targetPickerValue.floor_id)); + const targetLabels = new Set(ensureArray(targetPickerValue.label_id)); - return targetEntities; + targetLabels.forEach((labelId) => { + const expanded = expandLabelTarget( + this.hass, + labelId, + areas, + devices, + entities, + targetSelector + ); + expanded.devices.forEach((id) => targetDevices.add(id)); + expanded.entities.forEach((id) => targetEntities.add(id)); + expanded.areas.forEach((id) => targetAreas.add(id)); + }); + + targetFloors.forEach((floorId) => { + const expanded = expandFloorTarget( + this.hass, + floorId, + areas, + targetSelector + ); + expanded.areas.forEach((id) => targetAreas.add(id)); + }); + + targetAreas.forEach((areaId) => { + const expanded = expandAreaTarget( + this.hass, + areaId, + devices, + entities, + targetSelector + ); + expanded.devices.forEach((id) => targetDevices.add(id)); + expanded.entities.forEach((id) => targetEntities.add(id)); + }); + + targetDevices.forEach((deviceId) => { + const expanded = expandDeviceTarget( + this.hass, + deviceId, + entities, + targetSelector + ); + expanded.entities.forEach((id) => targetEntities.add(id)); + }); + + return Array.from(targetEntities); } ); From 848637760468166b42cabd19b27591c3dc45bc5c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 12:51:04 +0200 Subject: [PATCH 04/10] Fix z-index create category dialog (#20406) --- src/layouts/hass-tabs-subpage-data-table.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index e5cf082d3c..de33a19a5b 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -779,7 +779,6 @@ export class HaTabsSubpageDataTable extends LitElement { } ha-dialog { - --dialog-z-index: 100; --mdc-dialog-min-width: calc( 100vw - env(safe-area-inset-right) - env(safe-area-inset-left) ); From 2d4a8e2e45f1a160e1a1d35dacb69e4a1d190f29 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 4 Apr 2024 12:53:03 +0200 Subject: [PATCH 05/10] Fix search input outlined background color and margin (#20407) --- build-scripts/webpack.cjs | 1 + src/components/ha-outlined-field.ts | 40 ++++++++++++++++++++++++ src/components/ha-outlined-text-field.ts | 10 +++--- src/components/search-input-outlined.ts | 2 +- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/components/ha-outlined-field.ts diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs index e1047cadd4..b08d3b2eeb 100644 --- a/build-scripts/webpack.cjs +++ b/build-scripts/webpack.cjs @@ -161,6 +161,7 @@ const createWebpackConfig = ({ resolve: { extensions: [".ts", ".js", ".json"], alias: { + "lit/static-html$": "lit/static-html.js", "lit/decorators$": "lit/decorators.js", "lit/directive$": "lit/directive.js", "lit/directives/until$": "lit/directives/until.js", diff --git a/src/components/ha-outlined-field.ts b/src/components/ha-outlined-field.ts new file mode 100644 index 0000000000..c0df46cf29 --- /dev/null +++ b/src/components/ha-outlined-field.ts @@ -0,0 +1,40 @@ +import { MdOutlinedField } from "@material/web/field/outlined-field"; +import "element-internals-polyfill"; +import { css } from "lit"; +import { customElement } from "lit/decorators"; +import { literal } from "lit/static-html"; + +@customElement("ha-outlined-field") +export class HaOutlinedField extends MdOutlinedField { + protected readonly fieldTag = literal`ha-outlined-field`; + + static override styles = [ + ...super.styles, + css` + .container::before { + display: block; + content: ""; + position: absolute; + inset: 0; + background-color: var(--ha-outlined-field-container-color, transparent); + opacity: var(--ha-outlined-field-container-opacity, 1); + border-start-start-radius: var(--_container-shape-start-start); + border-start-end-radius: var(--_container-shape-start-end); + border-end-start-radius: var(--_container-shape-end-start); + border-end-end-radius: var(--_container-shape-end-end); + } + .with-start .start { + margin-inline-end: var(--ha-outlined-field-start-margin, 4px); + } + .with-end .end { + margin-inline-start: var(--ha-outlined-field-end-margin, 4px); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-outlined-field": HaOutlinedField; + } +} diff --git a/src/components/ha-outlined-text-field.ts b/src/components/ha-outlined-text-field.ts index 02ed120d6e..d1645b5dd7 100644 --- a/src/components/ha-outlined-text-field.ts +++ b/src/components/ha-outlined-text-field.ts @@ -2,9 +2,13 @@ import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field import "element-internals-polyfill"; import { css } from "lit"; import { customElement } from "lit/decorators"; +import { literal } from "lit/static-html"; +import "./ha-outlined-field"; @customElement("ha-outlined-text-field") export class HaOutlinedTextField extends MdOutlinedTextField { + protected readonly fieldTag = literal`ha-outlined-field`; + static override styles = [ ...super.styles, css` @@ -25,12 +29,10 @@ export class HaOutlinedTextField extends MdOutlinedTextField { --md-outlined-field-container-shape-end-end: 10px; --md-outlined-field-container-shape-end-start: 10px; --md-outlined-field-focus-outline-width: 1px; + --ha-outlined-field-start-margin: -4px; + --ha-outlined-field-end-margin: -4px; --mdc-icon-size: var(--md-input-chip-icon-size, 18px); } - md-outlined-field { - background: var(--ha-outlined-text-field-container-color, transparent); - opacity: var(--ha-outlined-text-field-container-opacity, 1); - } .input { font-family: Roboto, sans-serif; } diff --git a/src/components/search-input-outlined.ts b/src/components/search-input-outlined.ts index f0ce00c673..693be1feac 100644 --- a/src/components/search-input-outlined.ts +++ b/src/components/search-input-outlined.ts @@ -97,7 +97,7 @@ class SearchInputOutlined extends LitElement { ha-outlined-text-field { display: block; width: 100%; - --ha-outlined-text-field-container-color: var(--card-background-color); + --ha-outlined-field-container-color: var(--card-background-color); } ha-svg-icon, ha-icon-button { From 08b25f9c2a8ef924bcd4b8ca772020db1249f191 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 13:00:05 +0200 Subject: [PATCH 06/10] Add floor and label support to describe action (#20403) --- .../src/pages/automation/describe-action.ts | 4 +- src/components/trace/hat-trace-timeline.ts | 61 +++++++++++++------ src/data/context.ts | 6 ++ src/data/script_i18n.ts | 40 +++++++++++- .../action/ha-automation-action-row.ts | 34 ++++++++++- src/panels/config/ha-panel-config.ts | 24 +++++++- src/translations/en.json | 4 +- 7 files changed, 145 insertions(+), 28 deletions(-) diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 7deccf6927..3340429dc4 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
${this._action - ? describeAction(this.hass, [], this._action) + ? describeAction(this.hass, [], [], [], this._action) : ""} html`
- ${describeAction(this.hass, [], conf as any)} + ${describeAction(this.hass, [], [], [], conf as any)}
${dump(conf)}
` diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 78eb1ec8e1..eab5998d1d 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -1,3 +1,4 @@ +import { consume } from "@lit-labs/context"; import { mdiAlertCircle, mdiCircle, @@ -6,14 +7,13 @@ import { mdiProgressWrench, mdiRecordCircleOutline, } from "@mdi/js"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, TemplateResult, + css, + html, nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -23,27 +23,31 @@ import { relativeTime } from "../../common/datetime/relative_time"; import { fireEvent } from "../../common/dom/fire_event"; import { toggleAttribute } from "../../common/dom/toggle_attribute"; import { - EntityRegistryEntry, - subscribeEntityRegistry, -} from "../../data/entity_registry"; + floorsContext, + fullEntitiesContext, + labelsContext, +} from "../../data/context"; +import { EntityRegistryEntry } from "../../data/entity_registry"; +import { FloorRegistryEntry } from "../../data/floor_registry"; +import { LabelRegistryEntry } from "../../data/label_registry"; import { LogbookEntry } from "../../data/logbook"; import { ChooseAction, ChooseActionChoice, - getActionType, IfAction, ParallelAction, RepeatAction, + getActionType, } from "../../data/script"; import { describeAction } from "../../data/script_i18n"; import { ActionTraceStep, AutomationTraceExtended, ChooseActionTraceStep, - getDataFromPath, IfActionTraceStep, - isTriggerPath, TriggerTraceStep, + getDataFromPath, + isTriggerPath, } from "../../data/trace"; import { HomeAssistant } from "../../types"; import "./ha-timeline"; @@ -200,6 +204,8 @@ class ActionRenderer { constructor( private hass: HomeAssistant, private entityReg: EntityRegistryEntry[], + private labelReg: LabelRegistryEntry[], + private floorReg: FloorRegistryEntry[], private entries: TemplateResult[], private trace: AutomationTraceExtended, private logbookRenderer: LogbookRenderer, @@ -310,7 +316,14 @@ class ActionRenderer { this._renderEntry( path, - describeAction(this.hass, this.entityReg, data, actionType), + describeAction( + this.hass, + this.entityReg, + this.labelReg, + this.floorReg, + data, + actionType + ), undefined, data.enabled === false ); @@ -475,7 +488,13 @@ class ActionRenderer { const name = repeatConfig.alias || - describeAction(this.hass, this.entityReg, repeatConfig); + describeAction( + this.hass, + this.entityReg, + this.labelReg, + this.floorReg, + repeatConfig + ); this._renderEntry(repeatPath, name, undefined, disabled); @@ -631,15 +650,17 @@ export class HaAutomationTracer extends LitElement { @property({ type: Boolean }) public allowPick = false; - @state() private _entityReg: EntityRegistryEntry[] = []; + @state() + @consume({ context: fullEntitiesContext, subscribe: true }) + _entityReg!: EntityRegistryEntry[]; - public hassSubscribe(): UnsubscribeFunc[] { - return [ - subscribeEntityRegistry(this.hass.connection!, (entities) => { - this._entityReg = entities; - }), - ]; - } + @state() + @consume({ context: labelsContext, subscribe: true }) + _labelReg!: LabelRegistryEntry[]; + + @state() + @consume({ context: floorsContext, subscribe: true }) + _floorReg!: FloorRegistryEntry[]; protected render() { if (!this.trace) { @@ -657,6 +678,8 @@ export class HaAutomationTracer extends LitElement { const actionRenderer = new ActionRenderer( this.hass, this._entityReg, + this._labelReg, + this._floorReg, entries, this.trace, logbookRenderer, diff --git a/src/data/context.ts b/src/data/context.ts index b5d914522c..75ebe5ae3d 100644 --- a/src/data/context.ts +++ b/src/data/context.ts @@ -2,6 +2,8 @@ import { createContext } from "@lit-labs/context"; import { HassConfig } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; import { EntityRegistryEntry } from "./entity_registry"; +import { FloorRegistryEntry } from "./floor_registry"; +import { LabelRegistryEntry } from "./label_registry"; export const connectionContext = createContext("connection"); @@ -25,3 +27,7 @@ export const panelsContext = createContext("panels"); export const fullEntitiesContext = createContext("extendedEntities"); + +export const floorsContext = createContext("floors"); + +export const labelsContext = createContext("labels"); diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 22d5e3c0a7..b08cb49c49 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -14,7 +14,9 @@ import { computeEntityRegistryName, entityRegistryById, } from "./entity_registry"; +import { FloorRegistryEntry } from "./floor_registry"; import { domainToName } from "./integration"; +import { LabelRegistryEntry } from "./label_registry"; import { ActionType, ActionTypes, @@ -40,6 +42,8 @@ const actionTranslationBaseKey = export const describeAction = ( hass: HomeAssistant, entityRegistry: EntityRegistryEntry[], + labelRegistry: LabelRegistryEntry[], + floorRegistry: FloorRegistryEntry[], action: ActionTypes[T], actionType?: T, ignoreAlias = false @@ -48,6 +52,8 @@ export const describeAction = ( return tryDescribeAction( hass, entityRegistry, + labelRegistry, + floorRegistry, action, actionType, ignoreAlias @@ -66,6 +72,8 @@ export const describeAction = ( const tryDescribeAction = ( hass: HomeAssistant, entityRegistry: EntityRegistryEntry[], + labelRegistry: LabelRegistryEntry[], + floorRegistry: FloorRegistryEntry[], action: ActionTypes[T], actionType?: T, ignoreAlias = false @@ -82,10 +90,12 @@ const tryDescribeAction = ( const targets: string[] = []; if (config.target) { - for (const [key, label] of Object.entries({ + for (const [key, name] of Object.entries({ area_id: "areas", device_id: "devices", entity_id: "entities", + floor_id: "floors", + label_id: "labels", })) { if (!(key in config.target)) { continue; @@ -99,7 +109,7 @@ const tryDescribeAction = ( targets.push( hass.localize( `${actionTranslationBaseKey}.service.description.target_template`, - { name: label } + { name } ) ); break; @@ -147,6 +157,32 @@ const tryDescribeAction = ( ) ); } + } else if (key === "floor_id") { + const floor = floorRegistry.find( + (flr) => flr.floor_id === targetThing + ); + if (floor?.name) { + targets.push(floor.name); + } else { + targets.push( + hass.localize( + `${actionTranslationBaseKey}.service.description.target_unknown_floor` + ) + ); + } + } else if (key === "label_id") { + const label = labelRegistry.find( + (lbl) => lbl.label_id === targetThing + ); + if (label?.name) { + targets.push(label.name); + } else { + targets.push( + hass.localize( + `${actionTranslationBaseKey}.service.description.target_unknown_label` + ) + ); + } } else { targets.push(targetThing); } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 6986aba974..bac8c011fb 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -42,8 +42,14 @@ import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action"; import { AutomationClipboard } from "../../../../data/automation"; import { validateConfig } from "../../../../data/config"; -import { fullEntitiesContext } from "../../../../data/context"; +import { + floorsContext, + fullEntitiesContext, + labelsContext, +} from "../../../../data/context"; import { EntityRegistryEntry } from "../../../../data/entity_registry"; +import { FloorRegistryEntry } from "../../../../data/floor_registry"; +import { LabelRegistryEntry } from "../../../../data/label_registry"; import { Action, NonConditionAction, @@ -146,6 +152,14 @@ export default class HaAutomationActionRow extends LitElement { @consume({ context: fullEntitiesContext, subscribe: true }) _entityReg!: EntityRegistryEntry[]; + @state() + @consume({ context: labelsContext, subscribe: true }) + _labelReg!: LabelRegistryEntry[]; + + @state() + @consume({ context: floorsContext, subscribe: true }) + _floorReg!: FloorRegistryEntry[]; + @state() private _warnings?: string[]; @state() private _uiModeAvailable = true; @@ -210,7 +224,13 @@ export default class HaAutomationActionRow extends LitElement { .path=${ACTION_ICONS[type!]} >`} ${capitalizeFirstLetter( - describeAction(this.hass, this._entityReg, this.action) + describeAction( + this.hass, + this._entityReg, + this._labelReg, + this._floorReg, + this.action + ) )} @@ -573,7 +593,15 @@ export default class HaAutomationActionRow extends LitElement { ), inputType: "string", placeholder: capitalizeFirstLetter( - describeAction(this.hass, this._entityReg, this.action, undefined, true) + describeAction( + this.hass, + this._entityReg, + this._labelReg, + this._floorReg, + this.action, + undefined, + true + ) ), defaultValue: this.action.alias, confirmText: this.hass.localize("ui.common.submit"), diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index f7fd2fd195..fe3151b260 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -35,7 +35,11 @@ import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { listenMediaQuery } from "../../common/dom/media_query"; import { CloudStatus, fetchCloudStatus } from "../../data/cloud"; -import { fullEntitiesContext } from "../../data/context"; +import { + floorsContext, + fullEntitiesContext, + labelsContext, +} from "../../data/context"; import { entityRegistryByEntityId, entityRegistryById, @@ -45,6 +49,8 @@ import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../types"; +import { subscribeLabelRegistry } from "../../data/label_registry"; +import { subscribeFloorRegistry } from "../../data/floor_registry"; declare global { // for fire event @@ -379,11 +385,27 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) { initialValue: [], }); + private _labelsContext = new ContextProvider(this, { + context: labelsContext, + initialValue: [], + }); + + private _floorsContext = new ContextProvider(this, { + context: floorsContext, + initialValue: [], + }); + public hassSubscribe(): UnsubscribeFunc[] { return [ subscribeEntityRegistry(this.hass.connection!, (entities) => { this._entitiesContext.setValue(entities); }), + subscribeLabelRegistry(this.hass.connection!, (labels) => { + this._labelsContext.setValue(labels); + }), + subscribeFloorRegistry(this.hass.connection!, (floors) => { + this._floorsContext.setValue(floors); + }), ]; } diff --git a/src/translations/en.json b/src/translations/en.json index d4e2852c0a..facdbb7080 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3241,7 +3241,9 @@ "target_template": "templated {name}", "target_unknown_entity": "unknown entity", "target_unknown_device": "unknown device", - "target_unknown_area": "unknown area" + "target_unknown_area": "unknown area", + "target_unknown_floor": "unknown floor", + "target_unknown_label": "unknown label" } }, "play_media": { From 61c9072a08dcc54a8c70c0772b6e6d3ed1d97cc9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 13:00:14 +0200 Subject: [PATCH 07/10] Fix icons in entity settings (#20405) --- .../config/entities/ha-config-entities.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 5a7f5461aa..cf7f63d8d2 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -27,7 +27,6 @@ import { import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; -import { until } from "lit/directives/until"; import memoize from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; import type { HASSDomEvent } from "../../../common/dom/fire_event"; @@ -67,7 +66,6 @@ import { removeEntityRegistryEntry, updateEntityRegistryEntry, } from "../../../data/entity_registry"; -import { entryIcon } from "../../../data/icons"; import { LabelRegistryEntry, createLabelRegistryEntry, @@ -207,21 +205,19 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { type: "icon", template: (entry) => entry.icon - ? html` - - ` - : html` - - `, + ? html`` + : entry.entity + ? html` + + ` + : html``, }, name: { main: true, From 72d1e37a236de1020abfc0dbc9d960e714e79b8e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 13:26:26 +0200 Subject: [PATCH 08/10] Fix integration filter search (#20408) --- src/components/ha-filter-integrations.ts | 40 +++++++----------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/components/ha-filter-integrations.ts b/src/components/ha-filter-integrations.ts index 86295e1095..748cb87eeb 100644 --- a/src/components/ha-filter-integrations.ts +++ b/src/components/ha-filter-integrations.ts @@ -1,4 +1,3 @@ -import { SelectedDetail } from "@material/mwc-list"; import { mdiFilterVariantRemove } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -56,11 +55,7 @@ export class HaFilterIntegrations extends LitElement { @value-changed=${this._handleSearchChange} > - + ${repeat( this._integrations(this._manifests, this._filter, this.value), (i) => i.domain, @@ -131,34 +126,21 @@ export class HaFilterIntegrations extends LitElement { ) ); - private async _integrationsSelected( - ev: CustomEvent>> - ) { - const integrations = this._integrations( - this._manifests!, - this._filter, - this.value - ); - - if (!ev.detail.index.size) { - fireEvent(this, "data-table-filter-changed", { - value: [], - items: undefined, - }); - this.value = []; + private _handleItemClick(ev) { + const listItem = ev.target.closest("ha-check-list-item"); + const value = listItem?.value; + if (!value) { return; } - - const value: string[] = []; - - for (const index of ev.detail.index) { - const domain = integrations[index].domain; - value.push(domain); + if (this.value?.includes(value)) { + this.value = this.value?.filter((val) => val !== value); + } else { + this.value = [...(this.value || []), value]; } - this.value = value; + listItem.selected = this.value?.includes(value); fireEvent(this, "data-table-filter-changed", { - value, + value: this.value, items: undefined, }); } From 5c42c5130c3ecc7d16ba83a5eb378f75d601843b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 16:15:13 +0200 Subject: [PATCH 09/10] Add count of items (#20410) * Add count of items * Adjust layout, correct filter count --- src/layouts/hass-tabs-subpage-data-table.ts | 24 +++++++++---- .../config/automation/ha-automation-picker.ts | 31 +++++++++++------ .../devices/ha-config-devices-dashboard.ts | 12 +++++-- .../config/entities/ha-config-entities.ts | 13 +++++-- .../config/helpers/ha-config-helpers.ts | 34 ++++++++++++------- src/panels/config/scene/ha-scene-dashboard.ts | 30 ++++++++++------ src/panels/config/script/ha-script-picker.ts | 30 ++++++++++------ src/translations/en.json | 15 +++++--- 8 files changed, 130 insertions(+), 59 deletions(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index de33a19a5b..2c7aab1dc1 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -496,8 +496,9 @@ export class HaTabsSubpageDataTable extends LitElement { ${this.showFilters && !showPane ? html` ${localize("ui.components.subpage-data-table.filters")}${localize("ui.components.subpage-data-table.filters", { + number: this.data.length, + })} ${this.filters ? html`
-
` + +
+
+ + ${this.hass.localize( + "ui.components.subpage-data-table.show_results", + { number: this.data.length } + )} + +
+ ` : nothing} `; } @@ -793,7 +805,7 @@ export class HaTabsSubpageDataTable extends LitElement { } .filter-dialog-content { - height: calc(100vh - 1px - var(--header-height)); + height: calc(100vh - 1px - 61px - var(--header-height)); display: flex; flex-direction: column; } diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index ecb40f5b71..9ec4cb6b01 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -422,6 +422,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { const labelsInOverflow = (this._sizeController.value && this._sizeController.value < 700) || (!this._sizeController.value && this.hass.dockedSidebar === "docked"); + const automations = this._automations( + this.automations, + this._entityReg, + this.hass.areas, + this._categories, + this._labels, + this._filteredAutomations + ); return html` filter.value?.length) - .length + Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) + ).length } .columns=${this._columns( this.narrow, @@ -446,14 +464,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { this.hass.locale )} initialGroupColumn="category" - .data=${this._automations( - this.automations, - this._entityReg, - this.hass.areas, - this._categories, - this._labels, - this._filteredAutomations - )} + .data=${automations} .empty=${!this.automations.length} @row-click=${this._handleRowClicked} .noDataText=${this.hass.localize( diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index f9f2fd16e6..68c402ee5c 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -591,7 +591,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { .tabs=${configSections.devices} .route=${this.route} .searchLabel=${this.hass.localize( - "ui.panel.config.devices.picker.search" + "ui.panel.config.devices.picker.search", + { number: devicesOutput.length } )} .columns=${this._columns(this.hass.localize, this.narrow)} .data=${devicesOutput} @@ -600,8 +601,13 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { @selection-changed=${this._handleSelectionChanged} .filter=${this._filter} hasFilters - .filters=${Object.values(this._filters).filter( - (filter) => filter.value?.length + .filters=${Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) ).length} @clear-filter=${this._clearFilter} @search-changed=${this._handleSearchChange} diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index cf7f63d8d2..e98bc7b037 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -573,12 +573,19 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { )} .data=${filteredEntities} .searchLabel=${this.hass.localize( - "ui.panel.config.entities.picker.search" + "ui.panel.config.entities.picker.search", + { number: filteredEntities.length } )} hasFilters .filters=${ - Object.values(this._filters).filter((filter) => filter.value?.length) - .length + Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) + ).length } .filter=${this._filter} selectable diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index b32fa20750..e44659e34b 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -486,6 +486,16 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { const labelsInOverflow = (this._sizeController.value && this._sizeController.value < 700) || (!this._sizeController.value && this.hass.dockedSidebar === "docked"); + const helpers = this._getItems( + this.hass.localize, + this._stateItems, + this._entityEntries, + this._configEntries, + this._entityReg, + this._categories, + this._labels, + this._filteredStateItems + ); return html` filter.value?.length + .filters=${Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) ).length} .columns=${this._columns(this.narrow, this.hass.localize)} - .data=${this._getItems( - this.hass.localize, - this._stateItems, - this._entityEntries, - this._configEntries, - this._entityReg, - this._categories, - this._labels, - this._filteredStateItems - )} + .data=${helpers} initialGroupColumn="category" .activeFilters=${this._activeFilters} @clear-filter=${this._clearFilter} diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index c07d31f7be..b798c0e3de 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -425,6 +425,14 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { const labelsInOverflow = (this._sizeController.value && this._sizeController.value < 700) || (!this._sizeController.value && this.hass.dockedSidebar === "docked"); + const scenes = this._scenes( + this.scenes, + this._entityReg, + this.hass.areas, + this._categories, + this._labels, + this._filteredScenes + ); return html` filter.value?.length + .filters=${Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) ).length} .columns=${this._columns(this.narrow, this.hass.localize)} id="entity_id" initialGroupColumn="category" - .data=${this._scenes( - this.scenes, - this._entityReg, - this.hass.areas, - this._categories, - this._labels, - this._filteredScenes - )} + .data=${scenes} .empty=${!this.scenes.length} .activeFilters=${this._activeFilters} .noDataText=${this.hass.localize( diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 2e769e8371..941d78a417 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -437,6 +437,14 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { const labelsInOverflow = (this._sizeController.value && this._sizeController.value < 700) || (!this._sizeController.value && this.hass.dockedSidebar === "docked"); + const scripts = this._scripts( + this.scripts, + this._entityReg, + this.hass.areas, + this._categories, + this._labels, + this._filteredScripts + ); return html` filter.value?.length + .filters=${Object.values(this._filters).filter((filter) => + Array.isArray(filter.value) + ? filter.value.length + : filter.value && + Object.values(filter.value).some((val) => + Array.isArray(val) ? val.length : val + ) ).length} .columns=${this._columns( this.narrow, this.hass.localize, this.hass.locale )} - .data=${this._scripts( - this.scripts, - this._entityReg, - this.hass.areas, - this._categories, - this._labels, - this._filteredScripts - )} + .data=${scripts} .empty=${!this.scripts.length} .activeFilters=${this._activeFilters} id="entity_id" diff --git a/src/translations/en.json b/src/translations/en.json index facdbb7080..bf3e8b9053 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -501,6 +501,7 @@ }, "subpage-data-table": { "filters": "Filters", + "show_results": "show {number} results", "clear_filter": "Clear filter", "close_filter": "Close filters", "exit_selection_mode": "Exit selection mode", @@ -2270,7 +2271,8 @@ "category": "Category" }, "create_helper": "Create helper", - "no_helpers": "Looks like you don't have any helpers yet!" + "no_helpers": "Looks like you don't have any helpers yet!", + "search": "Search {number} helpers" }, "dialog": { "create": "Create", @@ -2684,6 +2686,7 @@ "assign_category": "Assign category", "no_category_support": "You can't assign an category to this automation", "no_category_entity_reg": "To assign an category to an automation it needs to have a unique ID.", + "search": "Search {number} automations", "headers": { "toggle": "Enable/disable", "name": "Name", @@ -3577,7 +3580,8 @@ "delete": "[%key:ui::common::delete%]", "duplicate": "[%key:ui::common::duplicate%]", "empty_header": "Create your first script", - "empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay." + "empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay.", + "search": "Search {number} scripts" }, "dialog_new": { "header": "Create script", @@ -3685,7 +3689,8 @@ "no_category_support": "You can't assign an category to this scene", "no_category_entity_reg": "To assign an category to an scene it needs to have a unique ID.", "empty_header": "Create your first scene", - "empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV." + "empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV.", + "search": "Search {number} scenes" }, "editor": { "default_name": "New scene", @@ -4010,7 +4015,7 @@ "confirm_delete": "Are you sure you want to delete this device?", "confirm_delete_integration": "Are you sure you want to remove this device from {integration}?", "picker": { - "search": "Search devices", + "search": "Search {number} devices", "state": "State" } }, @@ -4021,7 +4026,7 @@ "header": "Entities", "introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.", "introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant.", - "search": "Search entities", + "search": "Search {number} entities", "unnamed_entity": "Unnamed entity", "status": { "restored": "Restored", From 0580a3196153c7eee13541d57cc4740c83d59a9f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Apr 2024 16:16:30 +0200 Subject: [PATCH 10/10] Bumped version to 20240404.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 92bef3e682..71b4ce5600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240403.1" +version = "20240404.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"