diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 39ef6a8768d4..3e71beff6285 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -1,11 +1,16 @@ import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize"; -import { mdiChartLine, mdiHelpCircleOutline, mdiShape } from "@mdi/js"; +import { + mdiChartLine, + mdiHelpCircleOutline, + mdiPencil, + mdiShape, +} from "@mdi/js"; import type { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement, nothing, type PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; -import { fireEvent } from "../../common/dom/fire_event"; +import { type HASSDomEvent, fireEvent } from "../../common/dom/fire_event"; import { computeEntityNameList } from "../../common/entity/compute_entity_name_display"; import { computeStateName } from "../../common/entity/compute_state_name"; import { computeRTL } from "../../common/util/compute_rtl"; @@ -53,6 +58,16 @@ const SEARCH_KEYS = [ { name: "id", weight: 2 }, ]; +export interface StatisticElementChangedEvent { + statisticId: string; +} + +declare global { + interface HASSDomEvents { + "edit-statistics-element": StatisticElementChangedEvent; + } +} + @customElement("ha-statistic-picker") export class HaStatisticPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -130,6 +145,8 @@ export class HaStatisticPicker extends LitElement { @query("ha-generic-picker") private _picker?: HaGenericPicker; + @property({ attribute: "can-edit", type: Boolean }) public canEdit?: boolean; + public willUpdate(changedProps: PropertyValues) { if ( (!this.hasUpdated && !this.statisticIds) || @@ -341,6 +358,15 @@ export class HaStatisticPicker extends LitElement { ${item.secondary ? html`${item.secondary}` : nothing} + ${this.canEdit + ? html`` + : nothing} `; } @@ -350,6 +376,12 @@ export class HaStatisticPicker extends LitElement { private _valueRenderer: PickerValueRenderer = this._makeValueRenderer(); + private _editItem(ev: HASSDomEvent) { + ev.stopPropagation(); + const statisticId = (ev.currentTarget as any).value; + fireEvent(this, "edit-statistics-element", { statisticId }); + } + private _computeItem(statisticId: string): StatisticComboBoxItem { const stateObj = this.hass.states[statisticId]; diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index d8dedfa9e77a..b919d191cff6 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -1,9 +1,10 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; -import { fireEvent } from "../../common/dom/fire_event"; +import { type HASSDomEvent, fireEvent } from "../../common/dom/fire_event"; import type { ValueChangedEvent, HomeAssistant } from "../../types"; import "./ha-statistic-picker"; +import type { StatisticElementChangedEvent } from "./ha-statistic-picker"; @customElement("ha-statistics-picker") class HaStatisticsPicker extends LitElement { @@ -59,6 +60,8 @@ class HaStatisticsPicker extends LitElement { }) public ignoreRestrictionsOnFirstStatistic = false; + @property({ attribute: "can-edit", type: Boolean }) public canEdit?; + protected render() { if (!this.hass) { return nothing; @@ -99,7 +102,9 @@ class HaStatisticsPicker extends LitElement { .statisticIds=${this.statisticIds} .excludeStatistics=${this.value} .allowCustomEntity=${this.allowCustomEntity} + .canEdit=${this.canEdit} @value-changed=${this._statisticChanged} + @edit-statistics-element=${this._editItem} > ` @@ -122,6 +127,17 @@ class HaStatisticsPicker extends LitElement { `; } + private _editItem(ev: HASSDomEvent) { + const statisticId = ev.detail.statisticId; + const index = this._currentStatistics!.findIndex((e) => e === statisticId); + fireEvent(this, "edit-detail-element", { + subElementConfig: { + index, + type: "row", + }, + }); + } + private get _currentStatistics() { return this.value || []; } 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 7739890a2f1f..adb0733842aa 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 @@ -15,7 +15,10 @@ import { union, } from "superstruct"; import { ensureArray } from "../../../../common/array/ensure-array"; -import { fireEvent } from "../../../../common/dom/fire_event"; +import { + type HASSDomEvent, + fireEvent, +} from "../../../../common/dom/fire_event"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import { deepEqual } from "../../../../common/util/deep-equal"; import { supportedStatTypeMap } from "../../../../components/chart/statistics-chart"; @@ -32,13 +35,19 @@ import { isExternalStatistic, statisticsMetaHasType, } from "../../../../data/recorder"; +import type { EntityConfig } from "../../entity-rows/types"; import type { HomeAssistant } from "../../../../types"; import { DEFAULT_DAYS_TO_SHOW } from "../../cards/hui-statistics-graph-card"; -import type { StatisticsGraphCardConfig } from "../../cards/types"; +import type { + GraphEntityConfig, + StatisticsGraphCardConfig, +} from "../../cards/types"; import { processConfigEntities } from "../../common/process-config-entities"; import type { LovelaceCardEditor } from "../../types"; +import "../hui-sub-element-editor"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { graphEntitiesConfigStruct } from "../structs/entities-struct"; +import type { EditDetailElementEvent, SubElementEditorConfig } from "../types"; import { orderPropertiesGraphCard } from "./order-properties/order-properties-graph"; const statTypeStruct = union([ @@ -114,6 +123,8 @@ export class HuiStatisticsGraphCardEditor @state() private _metaDatas?: StatisticsMetaData[]; + @state() private _subElementEditorConfig?: SubElementEditorConfig; + public setConfig(config: StatisticsGraphCardConfig): void { assert(config, cardConfigStruct); this._config = config; @@ -334,11 +345,54 @@ export class HuiStatisticsGraphCardEditor } ); + private _subForm = memoizeOne((localize: LocalizeFunc) => ({ + schema: [ + { name: "entity", required: true, selector: { statistic: {} } }, + { + name: "name", + selector: { entity_name: {} }, + context: { + entity: "entity", + }, + }, + { + name: "color", + selector: { ui_color: {} }, + }, + ] as const, + computeLabel: (item: HaFormSchema) => { + switch (item.name) { + case "entity": + return localize( + "ui.panel.lovelace.editor.card.statistics-graph.picked_statistic" + ); + case "name": + case "color": + return localize(`ui.panel.lovelace.editor.card.generic.${item.name}`); + default: + return undefined; + } + }, + })); + protected render() { if (!this.hass || !this._config) { return nothing; } + if (this._subElementEditorConfig) { + return html` + + + `; + } + const schema = this._schema( this.hass.localize, this._configEntities, @@ -388,11 +442,29 @@ export class HuiStatisticsGraphCardEditor .ignoreRestrictionsOnFirstStatistic=${true} .value=${this._configEntities} .configValue=${"entities"} + can-edit @value-changed=${this._entitiesChanged} + @edit-detail-element=${this._editDetailElement} > `; } + private _goBack(): void { + this._subElementEditorConfig = undefined; + } + + private _editDetailElement(ev: HASSDomEvent): void { + const index = ev.detail.subElementConfig.index!; + let elementConfig = this._config!.entities[index]; + if (typeof elementConfig === "string") { + elementConfig = { entity: elementConfig }; + } + this._subElementEditorConfig = { + ...ev.detail.subElementConfig, + ...{ elementConfig: elementConfig as EntityConfig }, + }; + } + private _valueChanged(ev: CustomEvent): void { const config = this._orderProperties(ev.detail.value); fireEvent(this, "config-changed", { config }); @@ -410,15 +482,66 @@ export class HuiStatisticsGraphCardEditor }); let config = { ...this._config!, entities: newEntities }; + + // remove inappropriate stat options dependently on entities + config = await this._cleanConfig(config); + // normalize a generated yaml code + config = this._orderProperties(config); + + fireEvent(this, "config-changed", { + config, + }); + } + + private async _handleSubEntityChanged(ev: CustomEvent): Promise { + ev.stopPropagation(); + + // get updated entity config + const newEntityConfig = ev.detail.config as GraphEntityConfig; + + // update card config with updated entity config + const index = this._subElementEditorConfig!.index!; + const newEntities = [...this._config!.entities]; + newEntities[index] = newEntityConfig; + let config = this._config!; + config = { ...config, entities: newEntities }; + + // remove inappropriate stat options dependently on entities + config = await this._cleanConfig(config); + // normalize a generated yaml code + config = this._orderProperties(config); + this._config = config; + + // update sub-element editor config + this._subElementEditorConfig = { + ...this._subElementEditorConfig!, + elementConfig: { + ...(this._config!.entities[index] as GraphEntityConfig), + }, + }; + + fireEvent(this, "config-changed", { config }); + } + + // remove inappropriate stat options dependently on entities + private async _cleanConfig( + config: StatisticsGraphCardConfig + ): Promise { + const entityIds = config.entities.map((entityConf) => { + if (typeof entityConf === "string") { + return entityConf; + } + return entityConf.entity ?? undefined; + }); if ( - newEntityIds?.some((statistic_id) => isExternalStatistic(statistic_id)) && + entityIds.some((statistic_id) => isExternalStatistic(statistic_id)) && config.period === "5minute" ) { delete config.period; } const metadata = config.stat_types || config.unit - ? await getStatisticMetadata(this.hass!, newEntityIds) + ? await getStatisticMetadata(this.hass!, entityIds) : undefined; if (config.stat_types && config.entities.length) { config.stat_types = ensureArray(config.stat_types).filter((stat_type) => @@ -438,10 +561,8 @@ export class HuiStatisticsGraphCardEditor ) { delete config.unit; } - config = this._orderProperties(config); - fireEvent(this, "config-changed", { - config, - }); + + return config; } // normalize a generated yaml code by placing lines in a consistent order