diff --git a/src/data/frontend.ts b/src/data/frontend.ts index 107c4402bc..b9f395f0bb 100644 --- a/src/data/frontend.ts +++ b/src/data/frontend.ts @@ -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 = ( key: userDataKey, } ); + +export const fetchFrontendSystemData = async < + SystemDataKey extends ValidSystemDataKey, +>( + conn: Connection, + key: SystemDataKey +): Promise => { + 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 => + conn.sendMessagePromise({ + 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, + } + ); diff --git a/src/data/panel.ts b/src/data/panel.ts index 1a1a9d8467..fc3c9860a3 100644 --- a/src/data/panel.ts +++ b/src/data/panel.ts @@ -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") { diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 1e6064cfc1..a6e632c2b4 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -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: "", diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts index 671867750c..a09a2c4e3a 100644 --- a/src/layouts/partial-panel-resolver.ts +++ b/src/layouts/partial-panel-resolver.ts @@ -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 ( diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 66300f0502..a6a1149e24 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -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 = >( superClass: T @@ -73,7 +78,6 @@ export const connectionMixin = >( }, resources: null as any, localize: () => "", - translationMetadata, dockedSidebar: "docked", vibrate: true, @@ -204,14 +208,28 @@ export const connectionMixin = >( 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 = >( 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) { diff --git a/src/types.ts b/src/types.ts index 3c00587775..f6df62855d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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[4] ): Promise; loadFragmentTranslation(fragment: string): Promise; + loadDefaultPanel(): Promise; formatEntityState(stateObj: HassEntity, state?: string): string; formatEntityAttributeValue( stateObj: HassEntity, diff --git a/src/util/ha-pref-storage.ts b/src/util/ha-pref-storage.ts index 4ef259ff0c..2c099b9541 100644 --- a/src/util/ha-pref-storage.ts +++ b/src/util/ha-pref-storage.ts @@ -9,7 +9,9 @@ const STORED_STATE = [ "suspendWhenHidden", "enableShortcuts", "defaultPanel", -]; +] as const; + +type StoredHomeAssistant = Pick; export function storeState(hass: HomeAssistant) { try { @@ -31,8 +33,8 @@ export function storeState(hass: HomeAssistant) { } } -export function getState() { - const state = {}; +export function getState(): Partial { + const state = {} as Partial; STORED_STATE.forEach((key) => { const storageItem = window.localStorage.getItem(key);