mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-11 16:26:39 +01:00
Merge branch 'rc'
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
diff --git a/dist/tinykeys.cjs b/dist/tinykeys.cjs
|
||||
index 08c98b6eff3b8fb4b727fe8e6b096951d6ef6347..9c44f14862f582766ea1733b6dc0e97f962800d8 100644
|
||||
--- a/dist/tinykeys.cjs
|
||||
+++ b/dist/tinykeys.cjs
|
||||
@@ -61,6 +61,18 @@ function defaultKeybindingsHandlerIgnore(event) {
|
||||
function getModifierState(event, mod) {
|
||||
return typeof event.getModifierState === "function" ? event.getModifierState(mod) || ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState("AltGraph") : false;
|
||||
}
|
||||
+function splitKeybindingPress(press) {
|
||||
+ let parts = [];
|
||||
+ let start = 0;
|
||||
+ for (let index = 0; index < press.length; index++) {
|
||||
+ if (press[index] === "+" && /[\w\]]/.test(press[index - 1] || "")) {
|
||||
+ parts.push(press.slice(start, index));
|
||||
+ start = index + 1;
|
||||
+ }
|
||||
+ }
|
||||
+ parts.push(press.slice(start));
|
||||
+ return parts;
|
||||
+}
|
||||
/**
|
||||
* Parses a keybinding string into its parts.
|
||||
*
|
||||
@@ -76,10 +88,10 @@ function getModifierState(event, mod) {
|
||||
*/
|
||||
function parseKeybinding(str) {
|
||||
return str.trim().split(" ").map((press) => {
|
||||
- let parts = press.split(/(?<=\w|\])\+/);
|
||||
+ let parts = splitKeybindingPress(press);
|
||||
let last = parts.pop();
|
||||
let regex = last.match(/^\((.+)\)$/);
|
||||
- let key = regex ? new RegExp(`^(?:${regex[1]})$`, "iv") : last;
|
||||
+ let key = regex ? new RegExp(`^(?:${regex[1]})$`, "i") : last;
|
||||
let requiredModifiers = [];
|
||||
let optionalModifiers = [];
|
||||
for (const part of parts) {
|
||||
@@ -201,5 +213,3 @@ exports.defaultKeybindingsHandlerIgnore = defaultKeybindingsHandlerIgnore;
|
||||
exports.matchKeybindingPress = matchKeybindingPress;
|
||||
exports.parseKeybinding = parseKeybinding;
|
||||
exports.tinykeys = tinykeys;
|
||||
-
|
||||
-//# sourceMappingURL=tinykeys.cjs.map
|
||||
\ No newline at end of file
|
||||
diff --git a/dist/tinykeys.mjs b/dist/tinykeys.mjs
|
||||
index c289972d2728e03d9b272268c38fd3392e8845bf..e22897b00aae6cdb0dbbb971445227c07be52918 100644
|
||||
--- a/dist/tinykeys.mjs
|
||||
+++ b/dist/tinykeys.mjs
|
||||
@@ -60,6 +60,18 @@ function defaultKeybindingsHandlerIgnore(event) {
|
||||
function getModifierState(event, mod) {
|
||||
return typeof event.getModifierState === "function" ? event.getModifierState(mod) || ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState("AltGraph") : false;
|
||||
}
|
||||
+function splitKeybindingPress(press) {
|
||||
+ let parts = [];
|
||||
+ let start = 0;
|
||||
+ for (let index = 0; index < press.length; index++) {
|
||||
+ if (press[index] === "+" && /[\w\]]/.test(press[index - 1] || "")) {
|
||||
+ parts.push(press.slice(start, index));
|
||||
+ start = index + 1;
|
||||
+ }
|
||||
+ }
|
||||
+ parts.push(press.slice(start));
|
||||
+ return parts;
|
||||
+}
|
||||
/**
|
||||
* Parses a keybinding string into its parts.
|
||||
*
|
||||
@@ -75,10 +87,10 @@ function getModifierState(event, mod) {
|
||||
*/
|
||||
function parseKeybinding(str) {
|
||||
return str.trim().split(" ").map((press) => {
|
||||
- let parts = press.split(/(?<=\w|\])\+/);
|
||||
+ let parts = splitKeybindingPress(press);
|
||||
let last = parts.pop();
|
||||
let regex = last.match(/^\((.+)\)$/);
|
||||
- let key = regex ? new RegExp(`^(?:${regex[1]})$`, "iv") : last;
|
||||
+ let key = regex ? new RegExp(`^(?:${regex[1]})$`, "i") : last;
|
||||
let requiredModifiers = [];
|
||||
let optionalModifiers = [];
|
||||
for (const part of parts) {
|
||||
@@ -196,5 +208,3 @@ function tinykeys(target, keybindingMap, options = {}) {
|
||||
}
|
||||
//#endregion
|
||||
export { createKeybindingsHandler, defaultKeybindingsHandlerIgnore, matchKeybindingPress, parseKeybinding, tinykeys };
|
||||
-
|
||||
-//# sourceMappingURL=tinykeys.mjs.map
|
||||
\ No newline at end of file
|
||||
+1
-1
@@ -115,7 +115,7 @@
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "4.0.0",
|
||||
"tinykeys": "patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "7.4.1",
|
||||
"workbox-core": "7.4.1",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20260527.4"
|
||||
version = "20260527.5"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
@@ -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<this>) {
|
||||
if (
|
||||
(!this.hasUpdated && !this.statisticIds) ||
|
||||
@@ -341,6 +358,15 @@ export class HaStatisticPicker extends LitElement {
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${this.canEdit
|
||||
? html`<ha-icon-button
|
||||
slot="end"
|
||||
.value=${statisticId}
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
.path=${mdiPencil}
|
||||
@click=${this._editItem}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -350,6 +376,12 @@ export class HaStatisticPicker extends LitElement {
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = this._makeValueRenderer();
|
||||
|
||||
private _editItem(ev: HASSDomEvent<StatisticElementChangedEvent>) {
|
||||
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];
|
||||
|
||||
|
||||
@@ -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}
|
||||
></ha-statistic-picker>
|
||||
</div>
|
||||
`
|
||||
@@ -122,6 +127,17 @@ class HaStatisticsPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _editItem(ev: HASSDomEvent<StatisticElementChangedEvent>) {
|
||||
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 || [];
|
||||
}
|
||||
|
||||
@@ -1600,8 +1600,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
// Filter states based on what's typed
|
||||
const filteredStates = typedText
|
||||
? states.filter((entityState) =>
|
||||
entityState.label
|
||||
.toLowerCase()
|
||||
entityState.displayLabel
|
||||
?.toLowerCase()
|
||||
.startsWith(typedText.toLowerCase())
|
||||
)
|
||||
: states;
|
||||
@@ -1658,8 +1658,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
// Filter states based on what's typed
|
||||
const filteredStates = typedText
|
||||
? states.filter((entityState) =>
|
||||
entityState.label
|
||||
.toLowerCase()
|
||||
entityState.displayLabel
|
||||
?.toLowerCase()
|
||||
.startsWith(typedText.toLowerCase())
|
||||
)
|
||||
: states;
|
||||
|
||||
@@ -169,6 +169,9 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card:hover {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
}
|
||||
.card-content.has-footer {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
|
||||
+39
-35
@@ -11,6 +11,11 @@ import type {
|
||||
MediaPlayerSoundModeCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-media-player-sound-mode-card-feature-editor")
|
||||
export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
@@ -28,28 +33,20 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(hass: HomeAssistant, stateObj?: MediaPlayerEntity) =>
|
||||
[
|
||||
{
|
||||
name: "sound_modes",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list" as const,
|
||||
reorder: true,
|
||||
options:
|
||||
stateObj?.attributes.sound_mode_list?.map((mode) => ({
|
||||
value: mode,
|
||||
label: hass.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"sound_mode",
|
||||
mode
|
||||
),
|
||||
})) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
(stateObj: MediaPlayerEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "sound_modes",
|
||||
customize,
|
||||
options:
|
||||
stateObj?.attributes.sound_mode_list?.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass!.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"sound_mode",
|
||||
mode
|
||||
),
|
||||
})) ?? [],
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -63,12 +60,13 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
const schema = this._schema(this.hass!, stateObj);
|
||||
const data = customizableListData(this._config, "sound_modes");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -79,21 +77,27 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerSoundModeCardFeatureConfig>
|
||||
): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
const stateObj = this.context?.entity_id
|
||||
? (this.hass!.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined)
|
||||
: undefined;
|
||||
const defaults = stateObj?.attributes.sound_mode_list ?? [];
|
||||
const config =
|
||||
processCustomizableListValue<MediaPlayerSoundModeCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"sound_modes",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "sound_modes":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-sound-mode.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-sound-mode.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+39
-31
@@ -11,6 +11,11 @@ import type {
|
||||
MediaPlayerSourceCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-media-player-source-card-feature-editor")
|
||||
export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
@@ -28,24 +33,20 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj?: MediaPlayerEntity) =>
|
||||
[
|
||||
{
|
||||
name: "sources",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list" as const,
|
||||
reorder: true,
|
||||
options:
|
||||
stateObj?.attributes.source_list?.map((source) => ({
|
||||
value: source,
|
||||
label: source,
|
||||
})) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
(stateObj: MediaPlayerEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "sources",
|
||||
customize,
|
||||
options:
|
||||
stateObj?.attributes.source_list?.map((source) => ({
|
||||
value: source,
|
||||
label: this.hass!.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"source",
|
||||
source
|
||||
),
|
||||
})) ?? [],
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -59,12 +60,13 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
const schema = this._schema(stateObj);
|
||||
const data = customizableListData(this._config, "sources");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -75,21 +77,27 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerSourceCardFeatureConfig>
|
||||
): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
const stateObj = this.context?.entity_id
|
||||
? (this.hass!.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined)
|
||||
: undefined;
|
||||
const defaults = stateObj?.attributes.source_list ?? [];
|
||||
const config =
|
||||
processCustomizableListValue<MediaPlayerSourceCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"sources",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "sources":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-source.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-source.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -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`
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
.form=${this._subForm(this.hass.localize)}
|
||||
@go-back=${this._goBack}
|
||||
@config-changed=${this._handleSubEntityChanged}
|
||||
>
|
||||
</hui-sub-element-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
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}
|
||||
></ha-statistics-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): 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<void> {
|
||||
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<StatisticsGraphCardConfig> {
|
||||
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
|
||||
|
||||
@@ -136,7 +136,10 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
}
|
||||
|
||||
ha-yaml-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
|
||||
@@ -296,6 +296,9 @@ export const getMyRedirects = (): Redirects => ({
|
||||
component: "history",
|
||||
redirect: "/history",
|
||||
},
|
||||
maintenance: {
|
||||
redirect: "/maintenance",
|
||||
},
|
||||
overview: {
|
||||
redirect: "/home/overview",
|
||||
},
|
||||
|
||||
@@ -10136,11 +10136,13 @@
|
||||
},
|
||||
"media-player-sound-mode": {
|
||||
"label": "Media player sound mode",
|
||||
"sound_modes": "Sound modes"
|
||||
"sound_modes": "Sound modes",
|
||||
"customize": "Customize sound modes"
|
||||
},
|
||||
"media-player-source": {
|
||||
"label": "Media player source",
|
||||
"sources": "Sources"
|
||||
"sources": "Sources",
|
||||
"customize": "Customize sources"
|
||||
},
|
||||
"media-player-volume-buttons": {
|
||||
"label": "Media player volume buttons",
|
||||
|
||||
Reference in New Issue
Block a user