Change logic for default panel

This commit is contained in:
Paul Bottein
2025-11-12 17:24:51 +01:00
parent 13146575a0
commit 2044deefb8
7 changed files with 123 additions and 19 deletions

View File

@@ -14,12 +14,17 @@ declare global {
interface FrontendUserData {
core: CoreFrontendUserData;
sidebar: SidebarFrontendUserData;
default_panel: string;
default_panel?: string;
}
interface FrontendSystemData {
default_panel?: string;
}
}
export type ValidUserDataKey = keyof FrontendUserData;
export type ValidSystemDataKey = keyof FrontendSystemData;
export const fetchFrontendUserData = async <
UserDataKey extends ValidUserDataKey,
>(
@@ -60,3 +65,46 @@ export const subscribeFrontendUserData = <UserDataKey extends ValidUserDataKey>(
key: userDataKey,
}
);
export const fetchFrontendSystemData = async <
SystemDataKey extends ValidSystemDataKey,
>(
conn: Connection,
key: SystemDataKey
): Promise<FrontendSystemData[SystemDataKey] | null> => {
const result = await conn.sendMessagePromise<{
value: FrontendSystemData[SystemDataKey] | null;
}>({
type: "frontend/get_system_data",
key,
});
return result.value;
};
export const saveFrontendSystemData = async <
SystemDataKey extends ValidSystemDataKey,
>(
conn: Connection,
key: SystemDataKey,
value: FrontendSystemData[SystemDataKey]
): Promise<void> =>
conn.sendMessagePromise<undefined>({
type: "frontend/set_system_data",
key,
value,
});
export const subscribeFrontendSystemData = <
SystemDataKey extends ValidSystemDataKey,
>(
conn: Connection,
systemDataKey: SystemDataKey,
onChange: (data: { value: FrontendSystemData[SystemDataKey] | null }) => void
) =>
conn.subscribeMessage<{ value: FrontendSystemData[SystemDataKey] | null }>(
onChange,
{
type: "frontend/subscribe_system_data",
key: systemDataKey,
}
);

View File

@@ -4,10 +4,10 @@ import type { HomeAssistant, PanelInfo } from "../types";
/** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "lovelace";
export const getStorageDefaultPanelUrlPath = (): string => {
export const getStorageDefaultPanelUrlPath = (): string | undefined => {
const defaultPanel = window.localStorage.getItem("defaultPanel");
return defaultPanel ? JSON.parse(defaultPanel) : DEFAULT_PANEL;
return defaultPanel ? JSON.parse(defaultPanel) : undefined;
};
export const setDefaultPanel = (
@@ -17,10 +17,15 @@ export const setDefaultPanel = (
fireEvent(element, "hass-default-panel", { defaultPanel: urlPath });
};
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo =>
hass.panels[hass.defaultPanel]
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => {
if (!hass.defaultPanel) {
return hass.panels[DEFAULT_PANEL];
}
return hass.panels[hass.defaultPanel]
? hass.panels[hass.defaultPanel]
: hass.panels[DEFAULT_PANEL];
};
export const getPanelNameTranslationKey = (panel: PanelInfo) => {
if (panel.url_path === "lovelace") {

View File

@@ -54,9 +54,10 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
const path = curPath();
if (["", "/"].includes(path)) {
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
replace: true,
});
const defaultPanel = getStorageDefaultPanelUrlPath();
if (defaultPanel) {
navigate(`/${defaultPanel}${location.search}`, { replace: true });
}
}
this._route = {
prefix: "",

View File

@@ -188,6 +188,10 @@ class PartialPanelResolver extends HassRouterPage {
}
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
if (this.hass.defaultPanel) {
await this.hass.loadDefaultPanel();
}
this.routerOptions = this._getRoutes(this.hass.panels);
if (

View File

@@ -8,13 +8,18 @@ import {
subscribeServices,
} from "home-assistant-js-websocket";
import { fireEvent } from "../common/dom/fire_event";
import { computeStateName } from "../common/entity/compute_state_name";
import { promiseTimeout } from "../common/util/promise-timeout";
import { subscribeAreaRegistry } from "../data/area_registry";
import { broadcastConnectionStatus } from "../data/connection-status";
import { subscribeDeviceRegistry } from "../data/device_registry";
import { subscribeFrontendUserData } from "../data/frontend";
import {
fetchFrontendSystemData,
fetchFrontendUserData,
subscribeFrontendSystemData,
subscribeFrontendUserData,
} from "../data/frontend";
import { forwardHaptic } from "../data/haptics";
import { DEFAULT_PANEL } from "../data/panel";
import { serviceCallWillDisconnect } from "../data/service";
import {
DateFormat,
@@ -33,7 +38,7 @@ import { fetchWithAuth } from "../util/fetch-with-auth";
import { getState, storeState } from "../util/ha-pref-storage";
import hassCallApi, { hassCallApiRaw } from "../util/hass-call-api";
import type { HassBaseEl } from "./hass-base-mixin";
import { computeStateName } from "../common/entity/compute_state_name";
import { DEFAULT_PANEL } from "../data/panel";
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
@@ -73,7 +78,6 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
},
resources: null as any,
localize: () => "",
translationMetadata,
dockedSidebar: "docked",
vibrate: true,
@@ -204,14 +208,28 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
loadFragmentTranslation: (fragment) =>
// @ts-ignore
this._loadFragmentTranslations(this.hass?.language, fragment),
loadDefaultPanel: async () => {
const [user, system] = await Promise.all([
fetchFrontendUserData(conn, "default_panel"),
fetchFrontendSystemData(conn, "default_panel"),
]);
const defaultPanel = user || system;
this._updateHass({
userDefaultPanel: user,
systemDefaultPanel: system,
defaultPanel,
});
storeState(this.hass!);
return defaultPanel;
},
formatEntityState: (stateObj, state) =>
(state != null ? state : stateObj.state) ?? "",
formatEntityAttributeName: (_stateObj, attribute) => attribute,
formatEntityAttributeValue: (stateObj, attribute, value) =>
value != null ? value : (stateObj.attributes[attribute] ?? ""),
formatEntityName: (stateObj) => computeStateName(stateObj),
...getState(),
...this._pendingHass,
formatEntityName: (stateObj) => computeStateName(stateObj),
};
this.hassConnected();
@@ -289,11 +307,34 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
conn,
"default_panel",
({ value: defaultPanel }) => {
this._updateHass({ defaultPanel: defaultPanel || DEFAULT_PANEL });
this._updateHass({
userDefaultPanel: defaultPanel,
});
// Update default panel taking into account user and system default panel
this._updateHass({
defaultPanel:
this.hass!.userDefaultPanel || this.hass!.systemDefaultPanel,
});
// Store updated default panel in local storage
storeState(this.hass!);
}
);
subscribeFrontendSystemData(
conn,
"default_panel",
({ value: defaultPanel }) => {
this._updateHass({
systemDefaultPanel: defaultPanel,
});
// Update default panel taking into account user and system default panel
this._updateHass({
defaultPanel:
this.hass!.userDefaultPanel || this.hass!.systemDefaultPanel,
});
// Store updated default panel in local storage
storeState(this.hass!);
}
);
clearInterval(this.__backendPingInterval);
this.__backendPingInterval = setInterval(() => {
if (this.hass?.connected) {

View File

@@ -248,7 +248,9 @@ export interface HomeAssistant {
vibrate: boolean;
debugConnection: boolean;
dockedSidebar: "docked" | "always_hidden" | "auto";
defaultPanel: string;
defaultPanel?: string | null;
userDefaultPanel?: string | null;
systemDefaultPanel?: string | null;
moreInfoEntityId: string | null;
user?: CurrentUser;
userData?: CoreFrontendUserData | null;
@@ -283,6 +285,7 @@ export interface HomeAssistant {
configFlow?: Parameters<typeof getHassTranslations>[4]
): Promise<LocalizeFunc>;
loadFragmentTranslation(fragment: string): Promise<LocalizeFunc | undefined>;
loadDefaultPanel(): Promise<string | null | undefined>;
formatEntityState(stateObj: HassEntity, state?: string): string;
formatEntityAttributeValue(
stateObj: HassEntity,

View File

@@ -9,7 +9,9 @@ const STORED_STATE = [
"suspendWhenHidden",
"enableShortcuts",
"defaultPanel",
];
] as const;
type StoredHomeAssistant = Pick<HomeAssistant, (typeof STORED_STATE)[number]>;
export function storeState(hass: HomeAssistant) {
try {
@@ -31,8 +33,8 @@ export function storeState(hass: HomeAssistant) {
}
}
export function getState() {
const state = {};
export function getState(): Partial<StoredHomeAssistant> {
const state = {} as Partial<StoredHomeAssistant>;
STORED_STATE.forEach((key) => {
const storageItem = window.localStorage.getItem(key);