mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-09 14:46:37 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48062f9a82 | ||
|
|
a9f542b4cf |
@@ -27,6 +27,7 @@ import type {
|
||||
LovelaceConfig,
|
||||
LovelaceDashboardStrategyConfig,
|
||||
} from "../../../../src/data/lovelace/config/types";
|
||||
import { normalizeLovelaceConfig } from "../../../../src/data/lovelace/config/normalize";
|
||||
import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types";
|
||||
import { fetchResources } from "../../../../src/data/lovelace/resource";
|
||||
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
|
||||
@@ -34,7 +35,6 @@ import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||
import { checkLovelaceConfig } from "../../../../src/panels/lovelace/common/check-lovelace-config";
|
||||
|
||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
@@ -359,7 +359,7 @@ export class HcMain extends HassElement {
|
||||
this._urlPath || "lovelace"
|
||||
);
|
||||
castContext.setApplicationState(title || "");
|
||||
this._lovelaceConfig = checkLovelaceConfig(
|
||||
this._lovelaceConfig = normalizeLovelaceConfig(
|
||||
lovelaceConfig
|
||||
) as LovelaceConfig;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { LovelaceBadgeConfig } from "./badge";
|
||||
import { ensureBadgeConfig } from "./badge";
|
||||
import type { LovelaceSectionRawConfig } from "./section";
|
||||
import { isStrategySection } from "./section";
|
||||
import type { LovelaceRawConfig } from "./types";
|
||||
import { isStrategyDashboard } from "./types";
|
||||
import type { LovelaceViewConfig } from "./view";
|
||||
import { isStrategyView } from "./view";
|
||||
|
||||
export const normalizeLovelaceConfig = (
|
||||
config: LovelaceRawConfig
|
||||
): LovelaceRawConfig => {
|
||||
if (isStrategyDashboard(config)) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const updatedConfig = { ...config };
|
||||
|
||||
if (updatedConfig.views) {
|
||||
updatedConfig.views = updatedConfig.views
|
||||
.filter(Boolean)
|
||||
.map((view) => (isStrategyView(view) ? view : normalizeViewConfig(view)));
|
||||
}
|
||||
|
||||
return updatedConfig;
|
||||
};
|
||||
|
||||
export const normalizeViewConfig = (
|
||||
view: LovelaceViewConfig
|
||||
): LovelaceViewConfig => {
|
||||
const updatedView = { ...view };
|
||||
|
||||
// Remove empty badges and expand the entity id shorthand
|
||||
if (updatedView.badges) {
|
||||
const badges = updatedView.badges as (
|
||||
Partial<LovelaceBadgeConfig> | string
|
||||
)[];
|
||||
updatedView.badges = badges.filter(Boolean).map(ensureBadgeConfig);
|
||||
}
|
||||
|
||||
if (updatedView.cards) {
|
||||
updatedView.cards = updatedView.cards.filter(Boolean);
|
||||
}
|
||||
|
||||
// Migrate sections
|
||||
if (updatedView.sections) {
|
||||
updatedView.sections = updatedView.sections
|
||||
.filter(Boolean)
|
||||
.map(normalizeSectionConfig);
|
||||
}
|
||||
|
||||
return updatedView;
|
||||
};
|
||||
|
||||
export const normalizeSectionConfig = (
|
||||
section: LovelaceSectionRawConfig
|
||||
): LovelaceSectionRawConfig => {
|
||||
const updatedSection = { ...section };
|
||||
|
||||
// Move title to a heading card
|
||||
if (section.title) {
|
||||
// Only add card if it's not a strategy section
|
||||
if (!isStrategySection(updatedSection)) {
|
||||
const card = { type: "heading", heading: updatedSection.title };
|
||||
updatedSection.cards = [card, ...(updatedSection.cards || [])];
|
||||
}
|
||||
|
||||
delete updatedSection.title;
|
||||
}
|
||||
|
||||
if (!isStrategySection(updatedSection) && updatedSection.cards) {
|
||||
updatedSection.cards = updatedSection.cards.filter(Boolean);
|
||||
}
|
||||
|
||||
return updatedSection;
|
||||
};
|
||||
@@ -75,7 +75,7 @@ export interface LovelaceBaseViewConfig {
|
||||
|
||||
export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
||||
type?: string;
|
||||
badges?: (string | Partial<LovelaceBadgeConfig>)[]; // Badge can be just an entity_id or without type
|
||||
badges?: LovelaceBadgeConfig[];
|
||||
cards?: LovelaceCardConfig[];
|
||||
sections?: LovelaceSectionRawConfig[];
|
||||
header?: LovelaceViewHeaderConfig;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import type { LovelaceSectionRawConfig } from "../../../data/lovelace/config/section";
|
||||
import { isStrategySection } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceRawConfig } from "../../../data/lovelace/config/types";
|
||||
import { isStrategyDashboard } from "../../../data/lovelace/config/types";
|
||||
import type { LovelaceViewRawConfig } from "../../../data/lovelace/config/view";
|
||||
import { isStrategyView } from "../../../data/lovelace/config/view";
|
||||
|
||||
export const checkLovelaceConfig = (
|
||||
config: LovelaceRawConfig
|
||||
): LovelaceRawConfig => {
|
||||
if (isStrategyDashboard(config)) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const updatedConfig = { ...config };
|
||||
|
||||
if (updatedConfig.views) {
|
||||
updatedConfig.views = updatedConfig.views.map(checkViewConfig);
|
||||
}
|
||||
|
||||
return updatedConfig;
|
||||
};
|
||||
|
||||
export const checkViewConfig = (
|
||||
view: LovelaceViewRawConfig
|
||||
): LovelaceViewRawConfig => {
|
||||
if (isStrategyView(view)) {
|
||||
return view;
|
||||
}
|
||||
|
||||
const updatedView = { ...view };
|
||||
|
||||
// Remove empty badges
|
||||
if (updatedView.badges && !updatedView.badges.every(Boolean)) {
|
||||
updatedView.badges = updatedView.badges!.filter(Boolean);
|
||||
}
|
||||
|
||||
// Migrate sections
|
||||
if (updatedView.sections) {
|
||||
updatedView.sections = updatedView.sections!.map(checkSectionConfig);
|
||||
}
|
||||
|
||||
return updatedView;
|
||||
};
|
||||
|
||||
export const checkSectionConfig = (
|
||||
section: LovelaceSectionRawConfig
|
||||
): LovelaceSectionRawConfig => {
|
||||
const updatedSection = { ...section };
|
||||
|
||||
// Move title to a heading card
|
||||
if (section.title) {
|
||||
// Only add card if it's not a strategy section
|
||||
if (!isStrategySection(updatedSection)) {
|
||||
const card = { type: "heading", heading: updatedSection.title };
|
||||
updatedSection.cards = [card, ...(updatedSection.cards || [])];
|
||||
}
|
||||
|
||||
delete updatedSection.title;
|
||||
}
|
||||
return updatedSection;
|
||||
};
|
||||
@@ -21,10 +21,7 @@ import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
ensureBadgeConfig,
|
||||
type LovelaceBadgeConfig,
|
||||
} from "../../../data/lovelace/config/badge";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { showEditBadgeDialog } from "../editor/badge-editor/show-edit-badge-dialog";
|
||||
import type { LovelacePath } from "../editor/lovelace-path";
|
||||
@@ -66,12 +63,7 @@ export class HuiBadgeEditMode extends LitElement {
|
||||
protected _clipboard?: LovelaceBadgeConfig;
|
||||
|
||||
private get _badgeConfig() {
|
||||
return ensureBadgeConfig(
|
||||
getAtPath<Partial<LovelaceBadgeConfig> | string>(
|
||||
this.lovelace.config,
|
||||
this.path
|
||||
)!
|
||||
);
|
||||
return getAtPath<LovelaceBadgeConfig>(this.lovelace.config, this.path)!;
|
||||
}
|
||||
|
||||
private _touchStarted = false;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-button";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { subscribeLovelaceUpdates } from "../../data/lovelace";
|
||||
import { normalizeLovelaceConfig } from "../../data/lovelace/config/normalize";
|
||||
import type {
|
||||
LovelaceConfig,
|
||||
LovelaceRawConfig,
|
||||
@@ -31,7 +32,6 @@ import "../../layouts/hass-loading-screen";
|
||||
import type { ShowToastParams } from "../../managers/notification-manager";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||
import { showToast } from "../../util/toast";
|
||||
import { checkLovelaceConfig } from "./common/check-lovelace-config";
|
||||
import { loadLovelaceResources } from "./common/load-resources";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
import "./hui-root";
|
||||
@@ -379,9 +379,9 @@ export class LovelacePanel extends LitElement {
|
||||
this._setLovelaceConfig(conf, rawConf, confMode);
|
||||
}
|
||||
|
||||
private _checkLovelaceConfig(config: LovelaceRawConfig) {
|
||||
const checkedConfig = checkLovelaceConfig(config);
|
||||
return deepFreeze(checkedConfig);
|
||||
private _normalizeLovelaceConfig(config: LovelaceRawConfig) {
|
||||
const normalizedConfig = normalizeLovelaceConfig(config);
|
||||
return deepFreeze(normalizedConfig);
|
||||
}
|
||||
|
||||
private _setLovelaceConfig(
|
||||
@@ -389,7 +389,7 @@ export class LovelacePanel extends LitElement {
|
||||
rawConfig: LovelaceRawConfig,
|
||||
mode: Lovelace["mode"]
|
||||
) {
|
||||
config = this._checkLovelaceConfig(config);
|
||||
config = this._normalizeLovelaceConfig(config);
|
||||
const urlPath = this.urlPath;
|
||||
this.lovelace = {
|
||||
config,
|
||||
@@ -436,11 +436,13 @@ export class LovelacePanel extends LitElement {
|
||||
rawConfig: previousRawConfig,
|
||||
mode: previousMode,
|
||||
} = this.lovelace!;
|
||||
newConfig = this._checkLovelaceConfig(newConfig);
|
||||
newConfig = this._normalizeLovelaceConfig(newConfig);
|
||||
let conf: LovelaceConfig;
|
||||
// If strategy defined, apply it here.
|
||||
if (isStrategyDashboard(newConfig)) {
|
||||
conf = await generateLovelaceDashboardStrategy(newConfig, this.hass!);
|
||||
conf = this._normalizeLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(newConfig, this.hass!)
|
||||
);
|
||||
} else {
|
||||
conf = newConfig;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import { ensureBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type {
|
||||
LovelaceViewConfig,
|
||||
LovelaceViewRawConfig,
|
||||
} from "../../../data/lovelace/config/view";
|
||||
import { normalizeViewConfig } from "../../../data/lovelace/config/normalize";
|
||||
import { isStrategyView } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
@@ -232,7 +232,7 @@ export class HUIView extends ReactiveElement {
|
||||
"ll-edit-badge",
|
||||
(ev: HASSDomEvent<HASSDomEvents["ll-edit-badge"]>) => {
|
||||
const path = ev.detail.path;
|
||||
const badge = getAtPath<Partial<LovelaceBadgeConfig> | string>(
|
||||
const badge = getAtPath<LovelaceBadgeConfig>(
|
||||
this.lovelace.config,
|
||||
path
|
||||
);
|
||||
@@ -249,7 +249,7 @@ export class HUIView extends ReactiveElement {
|
||||
);
|
||||
await this.lovelace.saveConfig(newConfig);
|
||||
},
|
||||
badgeConfig: ensureBadgeConfig(badge),
|
||||
badgeConfig: badge,
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -435,10 +435,11 @@ export class HUIView extends ReactiveElement {
|
||||
config: LovelaceViewRawConfig
|
||||
): Promise<LovelaceViewConfig> {
|
||||
if (isStrategyView(config)) {
|
||||
const generatedConfig = await generateLovelaceViewStrategy(
|
||||
const strategyConfig = await generateLovelaceViewStrategy(
|
||||
config,
|
||||
this.hass!
|
||||
);
|
||||
const generatedConfig = normalizeViewConfig(strategyConfig);
|
||||
return {
|
||||
...generatedConfig,
|
||||
type: getViewType(generatedConfig),
|
||||
@@ -525,11 +526,7 @@ export class HUIView extends ReactiveElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._badges = config.badges.map((badge) => {
|
||||
const badgeConfig = ensureBadgeConfig(badge);
|
||||
const element = this.createBadgeElement(badgeConfig);
|
||||
return element;
|
||||
});
|
||||
this._badges = config.badges.map((badge) => this.createBadgeElement(badge));
|
||||
}
|
||||
|
||||
private _createCards(config: LovelaceViewConfig): void {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
normalizeLovelaceConfig,
|
||||
normalizeViewConfig,
|
||||
} from "../../../../src/data/lovelace/config/normalize";
|
||||
import type { LovelaceRawConfig } from "../../../../src/data/lovelace/config/types";
|
||||
import type { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
|
||||
|
||||
const rawConfig = (config: unknown) => config as LovelaceRawConfig;
|
||||
|
||||
describe("normalizeLovelaceConfig", () => {
|
||||
it("expands badge shorthands into entity badges", () => {
|
||||
const config = rawConfig({
|
||||
views: [
|
||||
{
|
||||
badges: [
|
||||
"sensor.temperature",
|
||||
{ entity: "sensor.humidity" },
|
||||
{ type: "custom:my-badge" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(normalizeLovelaceConfig(config)).toEqual({
|
||||
views: [
|
||||
{
|
||||
badges: [
|
||||
{ type: "entity", entity: "sensor.temperature", show_name: true },
|
||||
{ type: "entity", entity: "sensor.humidity" },
|
||||
{ type: "custom:my-badge" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("removes empty entries at every level", () => {
|
||||
const config = rawConfig({
|
||||
views: [
|
||||
null,
|
||||
{
|
||||
badges: [null, "sensor.temperature"],
|
||||
cards: [null, { type: "tile" }],
|
||||
sections: [null, { type: "grid", cards: [{ type: "tile" }, null] }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(normalizeLovelaceConfig(config)).toEqual({
|
||||
views: [
|
||||
{
|
||||
badges: [
|
||||
{ type: "entity", entity: "sensor.temperature", show_name: true },
|
||||
],
|
||||
cards: [{ type: "tile" }],
|
||||
sections: [{ type: "grid", cards: [{ type: "tile" }] }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves strategies untouched", () => {
|
||||
const dashboard = rawConfig({ strategy: { type: "original-states" } });
|
||||
expect(normalizeLovelaceConfig(dashboard)).toBe(dashboard);
|
||||
|
||||
const view = { strategy: { type: "areas" } };
|
||||
const section = { strategy: { type: "common-controls" } };
|
||||
const config = rawConfig({ views: [view, { sections: [section] }] });
|
||||
|
||||
expect(normalizeLovelaceConfig(config)).toEqual({
|
||||
views: [view, { sections: [section] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeViewConfig", () => {
|
||||
it("expands badge shorthands in a generated view", () => {
|
||||
const view = {
|
||||
badges: ["sensor.temperature"],
|
||||
} as unknown as LovelaceViewConfig;
|
||||
|
||||
expect(normalizeViewConfig(view)).toEqual({
|
||||
badges: [
|
||||
{ type: "entity", entity: "sensor.temperature", show_name: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,7 +25,10 @@ const createConfig = (): LovelaceConfig => ({
|
||||
views: [
|
||||
{
|
||||
title: "Home",
|
||||
badges: ["sensor.badge0", { type: "entity", entity: "sensor.badge1" }],
|
||||
badges: [
|
||||
{ type: "entity", entity: "sensor.badge0" },
|
||||
{ type: "entity", entity: "sensor.badge1" },
|
||||
],
|
||||
cards: [{ type: "v0-c0" }, { type: "v0-c1" }],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user