mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-05 10:24:07 +00:00
Compare commits
11 Commits
fix-form-i
...
strategy-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6b7a88f3e | ||
|
|
26210c84b9 | ||
|
|
b7d1ee874f | ||
|
|
a390b1aa6c | ||
|
|
6e69811941 | ||
|
|
1e23fda276 | ||
|
|
4729073b8c | ||
|
|
4d54b732cf | ||
|
|
09b1f7dd93 | ||
|
|
2c800de025 | ||
|
|
2e70d70e38 |
@@ -9,6 +9,8 @@ import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
|
||||
@customElement("energy-overview-view-strategy")
|
||||
export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
|
||||
@customElement("energy-view-strategy")
|
||||
export class EnergyViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -9,6 +9,8 @@ import type { LovelaceSectionConfig } from "../../../data/lovelace/config/sectio
|
||||
|
||||
@customElement("gas-view-strategy")
|
||||
export class GasViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -11,6 +11,8 @@ import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
|
||||
@customElement("power-view-strategy")
|
||||
export class PowerViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -10,6 +10,8 @@ import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
|
||||
@customElement("water-view-strategy")
|
||||
export class WaterViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type HomeFrontendSystemData,
|
||||
} from "../../data/frontend";
|
||||
import type { LovelaceDashboardStrategyConfig } from "../../data/lovelace/config/types";
|
||||
import { hasLegacyOverviewPanel } from "../../data/panel";
|
||||
import { mdiHomeAssistant } from "../../resources/home-assistant-logo-svg";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||
import { showToast } from "../../util/toast";
|
||||
@@ -26,11 +27,13 @@ import { showDeviceRegistryDetailDialog } from "../config/devices/device-registr
|
||||
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
|
||||
import "../lovelace/hui-root";
|
||||
import type { ExtraActionItem } from "../lovelace/hui-root";
|
||||
import { expandLovelaceConfigStrategies } from "../lovelace/strategies/get-strategy";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceDashboardStrategy,
|
||||
} from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import { showEditHomeDialog } from "./dialogs/show-dialog-edit-home";
|
||||
import { showNewOverviewDialog } from "./dialogs/show-dialog-new-overview";
|
||||
import { hasLegacyOverviewPanel } from "../../data/panel";
|
||||
|
||||
@customElement("ha-panel-home")
|
||||
class PanelHome extends LitElement {
|
||||
@@ -95,26 +98,19 @@ class PanelHome extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldHass && this.hass) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
oldHass.config.state !== "RUNNING"
|
||||
) {
|
||||
if (oldHass && this.hass && this.hass.config.state === "RUNNING") {
|
||||
if (oldHass.config.state !== "RUNNING") {
|
||||
this._setup();
|
||||
return;
|
||||
}
|
||||
const shouldRegenerate = checkStrategyShouldRegenerate(
|
||||
"dashboard",
|
||||
this._strategyConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
);
|
||||
if (shouldRegenerate) {
|
||||
this._debounceRegenerateStrategy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,12 +131,12 @@ class PanelHome extends LitElement {
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
private _debounceRegenerateStrategy = debounce(
|
||||
() => this._regenerateStrategyConfig(),
|
||||
200
|
||||
);
|
||||
|
||||
private _registriesChanged = async () => {
|
||||
private _regenerateStrategyConfig() {
|
||||
// If on an area view that no longer exists, redirect to overview
|
||||
const path = this.route?.path?.split("/")[1];
|
||||
if (path?.startsWith("areas-")) {
|
||||
@@ -151,7 +147,7 @@ class PanelHome extends LitElement {
|
||||
}
|
||||
}
|
||||
this._setLovelace();
|
||||
};
|
||||
}
|
||||
|
||||
private _updateExtraActionItems() {
|
||||
const path = this.route?.path?.split("/")[1];
|
||||
@@ -312,17 +308,19 @@ class PanelHome extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const strategyConfig: LovelaceDashboardStrategyConfig = {
|
||||
private get _strategyConfig(): LovelaceDashboardStrategyConfig {
|
||||
return {
|
||||
strategy: {
|
||||
type: "home",
|
||||
favorite_entities: this._config.favorite_entities,
|
||||
home_panel: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const config = await expandLovelaceConfigStrategies(
|
||||
strategyConfig,
|
||||
private async _setLovelace() {
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
this._strategyConfig,
|
||||
this.hass
|
||||
);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
isStrategyDashboard,
|
||||
saveConfig,
|
||||
} from "../../data/lovelace/config/types";
|
||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||
import { fetchResources } from "../../data/lovelace/resource";
|
||||
import type { WindowWithPreloads } from "../../data/preloads";
|
||||
import "../../layouts/hass-error-screen";
|
||||
@@ -36,10 +37,12 @@ import { checkLovelaceConfig } from "./common/check-lovelace-config";
|
||||
import { loadLovelaceResources } from "./common/load-resources";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
import "./hui-root";
|
||||
import { generateLovelaceDashboardStrategy } from "./strategies/get-strategy";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceDashboardStrategy,
|
||||
} from "./strategies/get-strategy";
|
||||
import type { Lovelace } from "./types";
|
||||
import { generateDefaultView } from "./views/default-view";
|
||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||
|
||||
(window as any).loadCardHelpers = () => import("./custom-card-helpers");
|
||||
|
||||
@@ -50,12 +53,6 @@ interface LovelacePanelConfig {
|
||||
let editorLoaded = false;
|
||||
let resourcesLoaded = false;
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"strategy-config-changed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-panel-lovelace")
|
||||
export class LovelacePanel extends LitElement {
|
||||
@property({ attribute: false }) public panel?: PanelInfo<
|
||||
@@ -129,7 +126,6 @@ export class LovelacePanel extends LitElement {
|
||||
.route=${this.route}
|
||||
.narrow=${this.narrow}
|
||||
@config-refresh=${this._forceFetchConfig}
|
||||
@strategy-config-changed=${this._strategyConfigChanged}
|
||||
></hui-root>
|
||||
`;
|
||||
}
|
||||
@@ -195,61 +191,26 @@ export class LovelacePanel extends LitElement {
|
||||
this.lovelace &&
|
||||
isStrategyDashboard(this.lovelace.rawConfig)
|
||||
) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
oldHass.config.state !== "RUNNING"
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"dashboard",
|
||||
this.lovelace.rawConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
) {
|
||||
this._regenerateStrategyConfig();
|
||||
this._debounceRegenerateStrategy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
private _debounceRegenerateStrategy = debounce(
|
||||
() => this._regenerateStrategyConfig(),
|
||||
200
|
||||
);
|
||||
|
||||
private _registriesChanged = async () => {
|
||||
if (!this.hass || !this.lovelace) {
|
||||
return;
|
||||
}
|
||||
const rawConfig = this.lovelace.rawConfig;
|
||||
|
||||
if (!isStrategyDashboard(rawConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldConfig = this.lovelace.config;
|
||||
const generatedConfig = await generateLovelaceDashboardStrategy(
|
||||
rawConfig,
|
||||
this.hass!
|
||||
);
|
||||
|
||||
const newConfig = checkLovelaceConfig(generatedConfig) as LovelaceConfig;
|
||||
|
||||
// Regenerate if the config changed
|
||||
if (!deepEqual(newConfig, oldConfig)) {
|
||||
this._regenerateStrategyConfig();
|
||||
}
|
||||
};
|
||||
|
||||
private _strategyConfigChanged = (ev: CustomEvent) => {
|
||||
ev.stopPropagation();
|
||||
this._regenerateStrategyConfig();
|
||||
};
|
||||
|
||||
private async _regenerateStrategyConfig() {
|
||||
if (!this.hass || !this.lovelace) {
|
||||
return;
|
||||
@@ -263,6 +224,10 @@ export class LovelacePanel extends LitElement {
|
||||
|
||||
try {
|
||||
const conf = await generateLovelaceDashboardStrategy(rawConf, this.hass!);
|
||||
|
||||
if (deepEqual(conf, this.lovelace.config)) {
|
||||
return;
|
||||
}
|
||||
this._setLovelaceConfig(conf, rawConf, "generated");
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
extractSearchParamsObject,
|
||||
removeSearchParam,
|
||||
} from "../../common/url/search-params";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-dropdown";
|
||||
@@ -156,7 +155,7 @@ class HUIRoot extends LitElement {
|
||||
|
||||
private _configChangedByUndo = false;
|
||||
|
||||
private _viewCache?: Record<string, HUIView>;
|
||||
private _viewCache: Record<string, HUIView> = {};
|
||||
|
||||
private _viewScrollPositions: Record<string, number> = {};
|
||||
|
||||
@@ -170,23 +169,10 @@ class HUIRoot extends LitElement {
|
||||
}),
|
||||
});
|
||||
|
||||
private _debouncedConfigChanged: () => void;
|
||||
|
||||
private _conversation = memoizeOne((_components) =>
|
||||
isComponentLoaded(this.hass, "conversation")
|
||||
);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// The view can trigger a re-render when it knows that certain
|
||||
// web components have been loaded.
|
||||
this._debouncedConfigChanged = debounce(
|
||||
() => this._selectView(this._curView, true),
|
||||
100,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private _renderActionItems(): TemplateResult {
|
||||
const result: TemplateResult[] = [];
|
||||
|
||||
@@ -632,7 +618,6 @@ class HUIRoot extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.theme=${curViewConfig?.theme}
|
||||
id="view"
|
||||
@ll-rebuild=${this._debouncedConfigChanged}
|
||||
>
|
||||
<hui-view-background .hass=${this.hass} .background=${background}>
|
||||
</hui-view-background>
|
||||
@@ -762,7 +747,6 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
|
||||
let newSelectView;
|
||||
let force = false;
|
||||
|
||||
let viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
viewPath = viewPath ? decodeURI(viewPath) : undefined;
|
||||
@@ -794,9 +778,8 @@ class HUIRoot extends LitElement {
|
||||
| Lovelace
|
||||
| undefined;
|
||||
|
||||
if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) {
|
||||
// On config change, recreate the current view from scratch.
|
||||
force = true;
|
||||
if (oldLovelace && oldLovelace.config !== this.lovelace!.config) {
|
||||
this._cleanupViewCache(oldLovelace);
|
||||
}
|
||||
|
||||
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
||||
@@ -815,15 +798,12 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
if (!force && huiView) {
|
||||
if (huiView) {
|
||||
huiView.lovelace = this.lovelace!;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectView !== undefined || force) {
|
||||
if (force && newSelectView === undefined) {
|
||||
newSelectView = this._curView;
|
||||
}
|
||||
if (newSelectView !== undefined) {
|
||||
// Will allow for ripples to start rendering
|
||||
afterNextRender(() => {
|
||||
if (changedProperties.has("route")) {
|
||||
@@ -835,7 +815,7 @@ class HUIRoot extends LitElement {
|
||||
scrollTo({ behavior: "auto", top: position })
|
||||
);
|
||||
}
|
||||
this._selectView(newSelectView, force);
|
||||
this._selectView(newSelectView);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1162,8 +1142,27 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _selectView(viewIndex: HUIRoot["_curView"], force: boolean): void {
|
||||
if (!force && this._curView === viewIndex) {
|
||||
private _cleanupViewCache(oldLovelace: Lovelace): void {
|
||||
// Clean up cache entries for views that no longer exist
|
||||
const newViewPaths = new Set(
|
||||
this.lovelace!.config.views.map((v, i) => v.path ?? String(i))
|
||||
);
|
||||
const keys = new Set([
|
||||
...Object.keys(this._viewCache),
|
||||
...Object.keys(this._viewScrollPositions),
|
||||
]);
|
||||
for (const key of keys) {
|
||||
const index = Number(key);
|
||||
const oldPath = oldLovelace.config.views[index]?.path ?? String(index);
|
||||
if (!newViewPaths.has(oldPath)) {
|
||||
delete this._viewCache[key];
|
||||
delete this._viewScrollPositions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _selectView(viewIndex: HUIRoot["_curView"]): void {
|
||||
if (this._curView === viewIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1176,11 +1175,6 @@ class HUIRoot extends LitElement {
|
||||
|
||||
this._curView = viewIndex;
|
||||
|
||||
if (force) {
|
||||
this._viewCache = {};
|
||||
this._viewScrollPositions = {};
|
||||
}
|
||||
|
||||
// Recreate a new element to clear the applied themes.
|
||||
const root = this._viewRoot;
|
||||
|
||||
@@ -1208,12 +1202,12 @@ class HUIRoot extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
if (this._viewCache[viewIndex]) {
|
||||
view = this._viewCache[viewIndex];
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
this._viewCache[viewIndex] = view;
|
||||
}
|
||||
|
||||
view.lovelace = this.lovelace;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceSectionElement } from "../../../data/lovelace";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
@@ -12,8 +14,8 @@ import type {
|
||||
LovelaceSectionRawConfig,
|
||||
} from "../../../data/lovelace/config/section";
|
||||
import { isStrategySection } from "../../../data/lovelace/config/section";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { ConditionalListenerMixin } from "../../../mixins/conditional-listener-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../cards/hui-card";
|
||||
import type { HuiCard } from "../cards/hui-card";
|
||||
import { checkConditionsMet } from "../common/validate-condition";
|
||||
@@ -23,7 +25,10 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
|
||||
import { addCard, replaceCard } from "../editor/config-util";
|
||||
import { performDeleteCard } from "../editor/delete-card";
|
||||
import { parseLovelaceCardPath } from "../editor/lovelace-path";
|
||||
import { generateLovelaceSectionStrategy } from "../strategies/get-strategy";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceSectionStrategy,
|
||||
} from "../strategies/get-strategy";
|
||||
import type { Lovelace } from "../types";
|
||||
import { DEFAULT_SECTION_LAYOUT } from "./const";
|
||||
|
||||
@@ -104,9 +109,36 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
|
||||
(!oldConfig || this.config !== oldConfig)
|
||||
) {
|
||||
this._initializeConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProperties.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProperties.get("hass") as HomeAssistant | undefined;
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass &&
|
||||
isStrategySection(this.config) &&
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"section",
|
||||
this.config.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
) {
|
||||
this._debounceRefreshConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRefreshConfig = debounce(
|
||||
() => this._initializeConfig(),
|
||||
200
|
||||
);
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
@@ -165,6 +197,11 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
|
||||
...sectionConfig,
|
||||
type: sectionConfig.type || DEFAULT_SECTION_LAYOUT,
|
||||
};
|
||||
|
||||
if (isStrategy && deepEqual(sectionConfig, this._config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = sectionConfig;
|
||||
|
||||
// Create a new layout element if necessary.
|
||||
|
||||
@@ -34,6 +34,8 @@ const computeHeadingCard = (
|
||||
|
||||
@customElement("area-view-strategy")
|
||||
export class AreaViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = ["entities", "devices", "areas"];
|
||||
|
||||
static async generate(
|
||||
config: AreaViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface AreasDashboardStrategyConfig {
|
||||
|
||||
@customElement("areas-dashboard-strategy")
|
||||
export class AreasDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies = ["areas"];
|
||||
|
||||
static async generate(
|
||||
config: AreasDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { AsyncReturnType, HomeAssistant } from "../../../types";
|
||||
import { cleanLegacyStrategyConfig, isLegacyStrategy } from "./legacy-strategy";
|
||||
import type {
|
||||
LovelaceDashboardStrategy,
|
||||
LovelaceStrategyRegistryKey,
|
||||
LovelaceSectionStrategy,
|
||||
LovelaceStrategy,
|
||||
LovelaceViewStrategy,
|
||||
@@ -26,6 +27,13 @@ import type {
|
||||
const MAX_WAIT_STRATEGY_LOAD = 5000;
|
||||
const CUSTOM_PREFIX = "custom:";
|
||||
|
||||
const DEFAULT_REGISTRY_DEPENDENCIES: readonly LovelaceStrategyRegistryKey[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
dashboard: {
|
||||
"original-states": () =>
|
||||
@@ -235,6 +243,45 @@ export const generateLovelaceSectionStrategy = async (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously checks whether a strategy needs regeneration.
|
||||
* Falls back to checking common registries if the strategy doesn't implement shouldRegenerate.
|
||||
*/
|
||||
export const checkStrategyShouldRegenerate = (
|
||||
configType: LovelaceStrategyConfigType,
|
||||
strategyConfig: LovelaceStrategyConfig,
|
||||
oldHass: HomeAssistant,
|
||||
newHass: HomeAssistant
|
||||
): boolean => {
|
||||
const strategyType = strategyConfig.type;
|
||||
if (!strategyType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let strategy: LovelaceStrategy | undefined;
|
||||
|
||||
if (strategyType in STRATEGIES[configType]) {
|
||||
const tag = `${strategyType}-${configType}-strategy`;
|
||||
strategy = customElements.get(tag) as unknown as
|
||||
| LovelaceStrategy
|
||||
| undefined;
|
||||
} else if (strategyType.startsWith(CUSTOM_PREFIX)) {
|
||||
const name = strategyType.slice(CUSTOM_PREFIX.length);
|
||||
const tag = `ll-strategy-${configType}-${name}`;
|
||||
const legacyTag = `ll-strategy-${name}`;
|
||||
strategy = (customElements.get(tag) ??
|
||||
customElements.get(legacyTag)) as unknown as LovelaceStrategy | undefined;
|
||||
}
|
||||
|
||||
if (strategy?.shouldRegenerate) {
|
||||
return strategy.shouldRegenerate(strategyConfig, oldHass, newHass);
|
||||
}
|
||||
|
||||
const dependencies =
|
||||
strategy?.registryDependencies ?? DEFAULT_REGISTRY_DEPENDENCIES;
|
||||
return dependencies.some((key) => oldHass[key] !== newHass[key]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all references to strategies and replaces them with the generated output
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,8 @@ export interface HomeAreaViewStrategyConfig {
|
||||
|
||||
@customElement("home-area-view-strategy")
|
||||
export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = ["entities", "devices", "areas", "panels"];
|
||||
|
||||
static async generate(
|
||||
config: HomeAreaViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface HomeDashboardStrategyConfig {
|
||||
|
||||
@customElement("home-dashboard-strategy")
|
||||
export class HomeDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies = ["areas"];
|
||||
|
||||
static async generate(
|
||||
config: HomeDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -72,6 +72,14 @@ const computeAreaCard = (
|
||||
|
||||
@customElement("home-overview-view-strategy")
|
||||
export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
"panels",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: HomeOverviewViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -8,6 +8,8 @@ export type IframeDashboardStrategyConfig = IframeViewStrategyConfig;
|
||||
|
||||
@customElement("iframe-dashboard-strategy")
|
||||
export class IframeDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
config: IframeDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface IframeViewStrategyConfig {
|
||||
|
||||
@customElement("iframe-view-strategy")
|
||||
export class IframeViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
config: IframeViewStrategyConfig
|
||||
): Promise<LovelaceViewConfig> {
|
||||
|
||||
@@ -7,6 +7,8 @@ export type MapDashboardStrategyConfig = MapViewStrategyConfig;
|
||||
|
||||
@customElement("map-dashboard-strategy")
|
||||
export class MapDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
config: MapDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface MapViewStrategyConfig {
|
||||
|
||||
@customElement("map-view-strategy")
|
||||
export class MapViewStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
_config: MapViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -9,6 +9,8 @@ export type OriginalStatesDashboardStrategyConfig =
|
||||
|
||||
@customElement("original-states-dashboard-strategy")
|
||||
export class OriginalStatesDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
config: OriginalStatesDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -5,8 +5,22 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceGenericElementEditor } from "../types";
|
||||
|
||||
export type LovelaceStrategyRegistryKey =
|
||||
| "entities"
|
||||
| "devices"
|
||||
| "areas"
|
||||
| "floors"
|
||||
| "labels"
|
||||
| "panels";
|
||||
|
||||
export interface LovelaceStrategy<T = any> {
|
||||
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
|
||||
shouldRegenerate?(
|
||||
config: LovelaceStrategyConfig,
|
||||
oldHass: HomeAssistant,
|
||||
newHass: HomeAssistant
|
||||
): boolean;
|
||||
registryDependencies?: readonly LovelaceStrategyRegistryKey[];
|
||||
getConfigElement?: () => LovelaceStrategyEditor;
|
||||
noEditor?: boolean;
|
||||
configRequired?: boolean;
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface CommonControlSectionStrategyConfig {
|
||||
|
||||
@customElement("common-controls-section-strategy")
|
||||
export class CommonControlsSectionStrategy extends ReactiveElement {
|
||||
static registryDependencies = [];
|
||||
|
||||
static async generate(
|
||||
config: CommonControlSectionStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PropertyValues } from "lit";
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import "../../../components/entity/ha-state-label-badge";
|
||||
@@ -42,7 +42,10 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path";
|
||||
import { createErrorSectionConfig } from "../sections/hui-error-section";
|
||||
import "../sections/hui-section";
|
||||
import type { HuiSection } from "../sections/hui-section";
|
||||
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceViewStrategy,
|
||||
} from "../strategies/get-strategy";
|
||||
import type { Lovelace } from "../types";
|
||||
import { getViewType } from "./get-view-type";
|
||||
|
||||
@@ -90,9 +93,7 @@ export class HUIView extends ReactiveElement {
|
||||
|
||||
private _layoutElement?: LovelaceViewElement;
|
||||
|
||||
private _layoutElementConfig?: LovelaceViewConfig;
|
||||
|
||||
private _rendered = false;
|
||||
private _config?: LovelaceViewConfig;
|
||||
|
||||
@storage({
|
||||
key: "dashboardCardClipboard",
|
||||
@@ -139,11 +140,8 @@ export class HUIView extends ReactiveElement {
|
||||
element.addEventListener(
|
||||
"ll-rebuild",
|
||||
(ev: Event) => {
|
||||
// In edit mode let it go to hui-root and rebuild whole view.
|
||||
if (!this.lovelace!.editMode) {
|
||||
ev.stopPropagation();
|
||||
this._rebuildSection(element, sectionConfig);
|
||||
}
|
||||
ev.stopPropagation();
|
||||
this._rebuildSection(element, sectionConfig);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
@@ -154,18 +152,6 @@ export class HUIView extends ReactiveElement {
|
||||
return this;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => {
|
||||
this._rendered = true;
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._rendered = false;
|
||||
}
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues<typeof this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
@@ -201,51 +187,25 @@ export class HUIView extends ReactiveElement {
|
||||
const viewConfig = this.lovelace.config.views[this.index];
|
||||
if (oldHass && this.hass && this.lovelace && isStrategyView(viewConfig)) {
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"view",
|
||||
viewConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
// If the page is not rendered yet, we can force the refresh
|
||||
if (this._rendered) {
|
||||
this._debounceRefreshConfig(false);
|
||||
} else {
|
||||
this._refreshConfig(true);
|
||||
}
|
||||
}
|
||||
this._debounceRefreshConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRefreshConfig = debounce(
|
||||
(force: boolean) => this._refreshConfig(force),
|
||||
() => this._initializeConfig(),
|
||||
200
|
||||
);
|
||||
|
||||
private _refreshConfig = async (force: boolean) => {
|
||||
if (!this.hass || !this.lovelace) {
|
||||
return;
|
||||
}
|
||||
const viewConfig = this.lovelace.config.views[this.index];
|
||||
|
||||
if (!isStrategyView(viewConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldConfig = this._layoutElementConfig;
|
||||
const newConfig = await this._generateConfig(viewConfig);
|
||||
|
||||
// Don't ask if the config is the same
|
||||
if (!deepEqual(newConfig, oldConfig)) {
|
||||
if (force) {
|
||||
this._setConfig(newConfig, true);
|
||||
} else {
|
||||
fireEvent(this, "strategy-config-changed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected update(changedProperties: PropertyValues) {
|
||||
super.update(changedProperties);
|
||||
|
||||
@@ -325,6 +285,12 @@ export class HUIView extends ReactiveElement {
|
||||
viewConfig: LovelaceViewConfig,
|
||||
isStrategy: boolean
|
||||
) {
|
||||
if (isStrategy && deepEqual(viewConfig, this._config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._config = viewConfig;
|
||||
|
||||
// Create a new layout element if necessary.
|
||||
let addLayoutElement = false;
|
||||
|
||||
@@ -332,7 +298,6 @@ export class HUIView extends ReactiveElement {
|
||||
addLayoutElement = true;
|
||||
this._createLayoutElement(viewConfig);
|
||||
}
|
||||
this._layoutElementConfig = viewConfig;
|
||||
this._createBadges(viewConfig);
|
||||
this._createCards(viewConfig);
|
||||
this._createSections(viewConfig);
|
||||
@@ -355,9 +320,9 @@ export class HUIView extends ReactiveElement {
|
||||
|
||||
private async _initializeConfig() {
|
||||
const rawConfig = this.lovelace.config.views[this.index];
|
||||
const isStrategy = isStrategyView(rawConfig);
|
||||
|
||||
const viewConfig = await this._generateConfig(rawConfig);
|
||||
const isStrategy = isStrategyView(viewConfig);
|
||||
|
||||
this._setConfig(viewConfig, isStrategy);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user