From 1ac3cf199ffec50edd4133aa284e17d9f8d13fcb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 19 Nov 2025 15:03:20 +0100 Subject: [PATCH] Save default panel at user and system level (#27899) * Save default panel in user data * Change logic for default panel * Fix types * Fix typings * Fix user and local storage * Use user data and system data * Update url path and update dashboard settings * Fix tests * Wait for panels and user/system data to be loaded * Update comment * Update comment * Set empty object instead of null * Update comment * Feedbacks * Apply suggestions from code review * format --------- Co-authored-by: Petar Petrov --- src/components/ha-navigation-picker.ts | 3 +- src/components/ha-sidebar.ts | 24 ++++++--- src/data/frontend.ts | 53 +++++++++++++++++++ src/data/panel.ts | 26 +++++---- src/dialogs/sidebar/dialog-edit-sidebar.ts | 9 ++-- src/layouts/home-assistant-main.ts | 21 +++++--- src/layouts/home-assistant.ts | 10 +--- .../dialog-lovelace-dashboard-detail.ts | 42 ++++++++++++--- .../ha-config-lovelace-dashboards.ts | 7 ++- .../hui-dialog-select-dashboard.ts | 11 ++-- .../select-view/hui-dialog-select-view.ts | 6 ++- src/panels/profile/ha-pick-dashboard-row.ts | 30 ++++++++--- .../profile/ha-profile-section-general.ts | 8 +-- src/state/connection-mixin.ts | 36 +++++++++---- src/state/sidebar-mixin.ts | 10 ---- src/translations/en.json | 14 +++-- src/types.ts | 9 ++-- src/util/ha-pref-storage.ts | 9 ++-- test/util/ha-pref-storage.test.ts | 2 +- 19 files changed, 230 insertions(+), 100 deletions(-) diff --git a/src/components/ha-navigation-picker.ts b/src/components/ha-navigation-picker.ts index 7740e325f3..3eba515c62 100644 --- a/src/components/ha-navigation-picker.ts +++ b/src/components/ha-navigation-picker.ts @@ -6,6 +6,7 @@ import { fireEvent } from "../common/dom/fire_event"; import { titleCase } from "../common/string/title-case"; import { fetchConfig } from "../data/lovelace/config/types"; import type { LovelaceViewRawConfig } from "../data/lovelace/config/view"; +import { getDefaultPanelUrlPath } from "../data/panel"; import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types"; import "./ha-combo-box"; import type { HaComboBox } from "./ha-combo-box"; @@ -44,7 +45,7 @@ const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({ path: `/${panel.url_path}`, icon: panel.icon ?? "mdi:view-dashboard", title: - panel.url_path === hass.defaultPanel + panel.url_path === getDefaultPanelUrlPath(hass) ? hass.localize("panel.states") : hass.localize(`panel.${panel.title}`) || panel.title || diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 784df58742..d811b82e4b 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -33,6 +33,7 @@ import { computeRTL } from "../common/util/compute_rtl"; import { throttle } from "../common/util/throttle"; import { subscribeFrontendUserData } from "../data/frontend"; import type { ActionHandlerDetail } from "../data/lovelace/action_handler"; +import { getDefaultPanelUrlPath } from "../data/panel"; import type { PersistentNotification } from "../data/persistent_notification"; import { subscribeNotifications } from "../data/persistent_notification"; import { subscribeRepairsIssueRegistry } from "../data/repairs"; @@ -142,7 +143,7 @@ const defaultPanelSorter = ( export const computePanels = memoizeOne( ( panels: HomeAssistant["panels"], - defaultPanel: HomeAssistant["defaultPanel"], + defaultPanel: string, panelsOrder: string[], hiddenPanels: string[], locale: HomeAssistant["locale"] @@ -298,7 +299,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { hass.localize !== oldHass.localize || hass.locale !== oldHass.locale || hass.states !== oldHass.states || - hass.defaultPanel !== oldHass.defaultPanel || + hass.userData !== oldHass.userData || + hass.systemData !== oldHass.systemData || hass.connected !== oldHass.connected ); } @@ -401,9 +403,11 @@ class HaSidebar extends SubscribeMixin(LitElement) { `; } + const defaultPanel = getDefaultPanelUrlPath(this.hass); + const [beforeSpacer, afterSpacer] = computePanels( this.hass.panels, - this.hass.defaultPanel, + defaultPanel, this._panelOrder, this._hiddenPanels, this.hass.locale @@ -418,23 +422,27 @@ class HaSidebar extends SubscribeMixin(LitElement) { @scroll=${this._listboxScroll} @keydown=${this._listboxKeydown} > - ${this._renderPanels(beforeSpacer, selectedPanel)} + ${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)} ${this._renderSpacer()} - ${this._renderPanels(afterSpacer, selectedPanel)} + ${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)} ${this._renderExternalConfiguration()} `; } - private _renderPanels(panels: PanelInfo[], selectedPanel: string) { + private _renderPanels( + panels: PanelInfo[], + selectedPanel: string, + defaultPanel: string + ) { return panels.map((panel) => this._renderPanel( panel.url_path, - panel.url_path === this.hass.defaultPanel + panel.url_path === defaultPanel ? panel.title || this.hass.localize("panel.states") : this.hass.localize(`panel.${panel.title}`) || panel.title, panel.icon, - panel.url_path === this.hass.defaultPanel && !panel.icon + panel.url_path === defaultPanel && !panel.icon ? PANEL_ICONS.lovelace : panel.url_path in PANEL_ICONS ? PANEL_ICONS[panel.url_path] diff --git a/src/data/frontend.ts b/src/data/frontend.ts index 8f0c6749cc..47ca049a94 100644 --- a/src/data/frontend.ts +++ b/src/data/frontend.ts @@ -3,6 +3,7 @@ import type { Connection } from "home-assistant-js-websocket"; export interface CoreFrontendUserData { showAdvanced?: boolean; showEntityIdPicker?: boolean; + defaultPanel?: string; } export interface SidebarFrontendUserData { @@ -10,15 +11,24 @@ export interface SidebarFrontendUserData { hiddenPanels: string[]; } +export interface CoreFrontendSystemData { + defaultPanel?: string; +} + declare global { interface FrontendUserData { core: CoreFrontendUserData; sidebar: SidebarFrontendUserData; } + interface FrontendSystemData { + core: CoreFrontendSystemData; + } } export type ValidUserDataKey = keyof FrontendUserData; +export type ValidSystemDataKey = keyof FrontendSystemData; + export const fetchFrontendUserData = async < UserDataKey extends ValidUserDataKey, >( @@ -59,3 +69,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..0d02a0301c 100644 --- a/src/data/panel.ts +++ b/src/data/panel.ts @@ -1,26 +1,24 @@ -import { fireEvent } from "../common/dom/fire_event"; 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 getLegacyDefaultPanelUrlPath = (): string | null => { const defaultPanel = window.localStorage.getItem("defaultPanel"); - - return defaultPanel ? JSON.parse(defaultPanel) : DEFAULT_PANEL; + return defaultPanel ? JSON.parse(defaultPanel) : null; }; -export const setDefaultPanel = ( - element: HTMLElement, - urlPath: string -): void => { - fireEvent(element, "hass-default-panel", { defaultPanel: urlPath }); -}; +export const getDefaultPanelUrlPath = (hass: HomeAssistant): string => + hass.userData?.defaultPanel || + hass.systemData?.defaultPanel || + getLegacyDefaultPanelUrlPath() || + DEFAULT_PANEL; -export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => - hass.panels[hass.defaultPanel] - ? hass.panels[hass.defaultPanel] - : hass.panels[DEFAULT_PANEL]; +export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => { + const panel = getDefaultPanelUrlPath(hass); + + return (panel ? hass.panels[panel] : undefined) ?? hass.panels[DEFAULT_PANEL]; +}; export const getPanelNameTranslationKey = (panel: PanelInfo) => { if (panel.url_path === "lovelace") { diff --git a/src/dialogs/sidebar/dialog-edit-sidebar.ts b/src/dialogs/sidebar/dialog-edit-sidebar.ts index 7294a6388a..413c6fed60 100644 --- a/src/dialogs/sidebar/dialog-edit-sidebar.ts +++ b/src/dialogs/sidebar/dialog-edit-sidebar.ts @@ -20,6 +20,7 @@ import { } from "../../data/frontend"; import type { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; +import { getDefaultPanelUrlPath } from "../../data/panel"; @customElement("dialog-edit-sidebar") class DialogEditSidebar extends LitElement { @@ -94,9 +95,11 @@ class DialogEditSidebar extends LitElement { const panels = this._panels(this.hass.panels); + const defaultPanel = getDefaultPanelUrlPath(this.hass); + const [beforeSpacer, afterSpacer] = computePanels( this.hass.panels, - this.hass.defaultPanel, + defaultPanel, this._order, this._hidden, this.hass.locale @@ -120,12 +123,12 @@ class DialogEditSidebar extends LitElement { ].map((panel) => ({ value: panel.url_path, label: - panel.url_path === this.hass.defaultPanel + panel.url_path === defaultPanel ? panel.title || this.hass.localize("panel.states") : this.hass.localize(`panel.${panel.title}`) || panel.title || "?", icon: panel.icon || undefined, iconPath: - panel.url_path === this.hass.defaultPanel && !panel.icon + panel.url_path === defaultPanel && !panel.icon ? PANEL_ICONS.lovelace : panel.url_path in PANEL_ICONS ? PANEL_ICONS[panel.url_path] diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index 0c7d60035f..1127253fe8 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -1,5 +1,5 @@ import type { PropertyValues, TemplateResult } from "lit"; -import { css, html, LitElement } from "lit"; +import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import type { HASSDomEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event"; @@ -46,10 +46,13 @@ export class HomeAssistantMain extends LitElement { protected render(): TemplateResult { const sidebarNarrow = this._sidebarNarrow || this._externalSidebar; + const isPanelReady = + this.hass.panels && this.hass.userData && this.hass.systemData; + return html` @@ -59,12 +62,14 @@ export class HomeAssistantMain extends LitElement { .route=${this.route} .alwaysExpand=${sidebarNarrow || this.hass.dockedSidebar === "docked"} > - + ${isPanelReady + ? html`` + : nothing} `; } diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 1e6064cfc1..7b55a17709 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -1,10 +1,10 @@ +import type { Connection } from "home-assistant-js-websocket"; import type { PropertyValues } from "lit"; import { html } from "lit"; import { customElement, state } from "lit/decorators"; -import type { Connection } from "home-assistant-js-websocket"; +import { storage } from "../common/decorators/storage"; import { isNavigationClick } from "../common/dom/is-navigation-click"; import { navigate } from "../common/navigate"; -import { getStorageDefaultPanelUrlPath } from "../data/panel"; import type { WindowWithPreloads } from "../data/preloads"; import type { RecorderInfo } from "../data/recorder"; import { getRecorderInfo } from "../data/recorder"; @@ -23,7 +23,6 @@ import { } from "../util/register-service-worker"; import "./ha-init-page"; import "./home-assistant-main"; -import { storage } from "../common/decorators/storage"; const useHash = __DEMO__; const curPath = () => @@ -53,11 +52,6 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) { super(); const path = curPath(); - if (["", "/"].includes(path)) { - navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, { - replace: true, - }); - } this._route = { prefix: "", path, diff --git a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts index f633253d00..7d22be5b8a 100644 --- a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts +++ b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts @@ -4,18 +4,20 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import { slugify } from "../../../../common/string/slugify"; +import "../../../../components/ha-button"; import { createCloseHeading } from "../../../../components/ha-dialog"; import "../../../../components/ha-form/ha-form"; -import "../../../../components/ha-button"; import type { SchemaUnion } from "../../../../components/ha-form/types"; +import { saveFrontendSystemData } from "../../../../data/frontend"; import type { LovelaceDashboard, LovelaceDashboardCreateParams, LovelaceDashboardMutableParams, } from "../../../../data/lovelace/dashboard"; -import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel"; +import { DEFAULT_PANEL } from "../../../../data/panel"; import { haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers"; import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail"; @customElement("dialog-lovelace-dashboard-detail") @@ -59,7 +61,8 @@ export class DialogLovelaceDashboardDetail extends LitElement { if (!this._params || !this._data) { return nothing; } - const defaultPanelUrlPath = this.hass.defaultPanel; + const defaultPanelUrlPath = + this.hass.systemData?.defaultPanel || DEFAULT_PANEL; const titleInvalid = !this._data.title || !this._data.title.trim(); return html` @@ -251,15 +254,38 @@ export class DialogLovelaceDashboardDetail extends LitElement { }; } - private _toggleDefault() { + private async _toggleDefault() { const urlPath = this._params?.urlPath; if (!urlPath) { return; } - setDefaultPanel( - this, - urlPath === this.hass.defaultPanel ? DEFAULT_PANEL : urlPath - ); + + const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL; + // Add warning dialog to saying that this will change the default dashboard for all users + const confirm = await showConfirmationDialog(this, { + title: this.hass.localize( + urlPath === defaultPanel + ? "ui.panel.config.lovelace.dashboards.detail.remove_default_confirm_title" + : "ui.panel.config.lovelace.dashboards.detail.set_default_confirm_title" + ), + text: this.hass.localize( + urlPath === defaultPanel + ? "ui.panel.config.lovelace.dashboards.detail.remove_default_confirm_text" + : "ui.panel.config.lovelace.dashboards.detail.set_default_confirm_text" + ), + confirmText: this.hass.localize("ui.common.ok"), + dismissText: this.hass.localize("ui.common.cancel"), + destructive: false, + }); + + if (!confirm) { + return; + } + + saveFrontendSystemData(this.hass.connection, "core", { + ...this.hass.systemData, + defaultPanel: urlPath === defaultPanel ? undefined : urlPath, + }); } private async _updateDashboard() { diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index 9ea8cc91db..1be9ed2888 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -45,6 +45,7 @@ import { fetchDashboards, updateDashboard, } from "../../../../data/lovelace/dashboard"; +import { DEFAULT_PANEL } from "../../../../data/panel"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import "../../../../layouts/hass-loading-screen"; import "../../../../layouts/hass-tabs-subpage-data-table"; @@ -286,7 +287,7 @@ export class HaConfigLovelaceDashboards extends LitElement { ); private _getItems = memoize( - (dashboards: LovelaceDashboard[], defaultUrlPath: string) => { + (dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => { const defaultMode = ( this.hass.panels?.lovelace?.config as LovelacePanelConfig ).mode; @@ -403,6 +404,8 @@ export class HaConfigLovelaceDashboards extends LitElement { return html` `; } + const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL; + return html` @@ -37,14 +40,18 @@ class HaPickDashboardRow extends LitElement { "ui.panel.profile.dashboard.dropdown_label" )} .disabled=${!this._dashboards?.length} - .value=${this.hass.defaultPanel} + .value=${value} @selected=${this._dashboardChanged} naturalMenuWidth > + + ${this.hass.localize("ui.panel.profile.dashboard.system")} + - ${this.hass.localize( - "ui.panel.profile.dashboard.default_dashboard_label" - )} + ${this.hass.localize("ui.panel.profile.dashboard.lovelace")} + + + ${this.hass.localize("ui.panel.profile.dashboard.home")} ${this._dashboards.map((dashboard) => { if (!this.hass.user!.is_admin && dashboard.require_admin) { @@ -72,11 +79,18 @@ class HaPickDashboardRow extends LitElement { } private _dashboardChanged(ev) { - const urlPath = ev.target.value; - if (!urlPath || urlPath === this.hass.defaultPanel) { + const value = ev.target.value as string; + if (!value) { return; } - setDefaultPanel(this, urlPath); + const urlPath = value === USE_SYSTEM_VALUE ? undefined : value; + if (urlPath === this.hass.userData?.defaultPanel) { + return; + } + saveFrontendUserData(this.hass.connection, "core", { + ...this.hass.userData, + defaultPanel: urlPath, + }); } } diff --git a/src/panels/profile/ha-profile-section-general.ts b/src/panels/profile/ha-profile-section-general.ts index dbd4272dca..6ae3cd1c1b 100644 --- a/src/panels/profile/ha-profile-section-general.ts +++ b/src/panels/profile/ha-profile-section-general.ts @@ -154,6 +154,10 @@ class HaProfileSectionGeneral extends LitElement { .narrow=${this.narrow} .hass=${this.hass} > + ${this.hass.localize( @@ -208,10 +212,6 @@ class HaProfileSectionGeneral extends LitElement { .narrow=${this.narrow} .hass=${this.hass} > - ${this.hass.dockedSidebar !== "auto" || !this.narrow ? html` >( superClass: T @@ -59,8 +61,9 @@ export const connectionMixin = >( panels: null as any, services: null as any, user: null as any, + userData: undefined, + systemData: undefined, panelUrl: (this as any)._panelUrl, - defaultPanel: DEFAULT_PANEL, language, selectedLanguage: null, locale: { @@ -73,7 +76,6 @@ export const connectionMixin = >( }, resources: null as any, localize: () => "", - translationMetadata, dockedSidebar: "docked", vibrate: true, @@ -209,9 +211,9 @@ export const connectionMixin = >( 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(); @@ -282,10 +284,26 @@ export const connectionMixin = >( subscribeConfig(conn, (config) => this._updateHass({ config })); subscribeServices(conn, (services) => this._updateHass({ services })); subscribePanels(conn, (panels) => this._updateHass({ panels })); - subscribeFrontendUserData(conn, "core", ({ value: userData }) => { - this._updateHass({ userData }); + // Catch errors to userData and systemData subscription (e.g. if the + // backend isn't up to date) and set to null so frontend can continue + subscribeFrontendUserData(conn, "core", ({ value: userData }) => + this._updateHass({ userData: userData || {} }) + ).catch(() => { + // eslint-disable-next-line no-console + console.error( + "Failed to subscribe to user data, setting to empty object" + ); + this._updateHass({ userData: {} }); + }); + subscribeFrontendSystemData(conn, "core", ({ value: systemData }) => + this._updateHass({ systemData: systemData || {} }) + ).catch(() => { + // eslint-disable-next-line no-console + console.error( + "Failed to subscribe to system data, setting to empty object" + ); + this._updateHass({ systemData: {} }); }); - clearInterval(this.__backendPingInterval); this.__backendPingInterval = setInterval(() => { if (this.hass?.connected) { diff --git a/src/state/sidebar-mixin.ts b/src/state/sidebar-mixin.ts index 59d258da4e..a7e4415792 100644 --- a/src/state/sidebar-mixin.ts +++ b/src/state/sidebar-mixin.ts @@ -7,20 +7,14 @@ interface DockSidebarParams { dock: HomeAssistant["dockedSidebar"]; } -interface DefaultPanelParams { - defaultPanel: HomeAssistant["defaultPanel"]; -} - declare global { // for fire event interface HASSDomEvents { "hass-dock-sidebar": DockSidebarParams; - "hass-default-panel": DefaultPanelParams; } // for add event listener interface HTMLElementEventMap { "hass-dock-sidebar": HASSDomEvent; - "hass-default-panel": HASSDomEvent; } } @@ -32,9 +26,5 @@ export default >(superClass: T) => this._updateHass({ dockedSidebar: ev.detail.dock }); storeState(this.hass!); }); - this.addEventListener("hass-default-panel", (ev) => { - this._updateHass({ defaultPanel: ev.detail.defaultPanel }); - storeState(this.hass!); - }); } }; diff --git a/src/translations/en.json b/src/translations/en.json index b3f59d9c84..309526cf08 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3516,8 +3516,12 @@ "delete": "Delete", "update": "Update", "create": "Create", - "set_default": "Set as default on this device", - "remove_default": "Remove as default on this device" + "set_default": "Set as default", + "remove_default": "Remove as default", + "set_default_confirm_title": "Set as default dashboard?", + "set_default_confirm_text": "This will replace the current default dashboard. Users can still override their default dashboard in their profile settings.", + "remove_default_confirm_title": "Remove default dashboard?", + "remove_default_confirm_text": "The default dashboard will be changed to Overview for every user. Users can still override their default dashboard in their profile settings." } }, "resources": { @@ -8666,9 +8670,11 @@ }, "dashboard": { "header": "Dashboard", - "description": "Pick a default dashboard for this device.", + "description": "Pick a default dashboard to show.", "dropdown_label": "Dashboard", - "default_dashboard_label": "Overview (default)" + "lovelace": "Overview", + "home": "Home", + "system": "Auto (use system settings)" }, "change_password": { "header": "Change password", diff --git a/src/types.ts b/src/types.ts index 3c00587775..b542d6f410 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,7 +18,10 @@ import type { AreaRegistryEntry } from "./data/area_registry"; import type { DeviceRegistryEntry } from "./data/device_registry"; import type { EntityRegistryDisplayEntry } from "./data/entity_registry"; import type { FloorRegistryEntry } from "./data/floor_registry"; -import type { CoreFrontendUserData } from "./data/frontend"; +import type { + CoreFrontendSystemData, + CoreFrontendUserData, +} from "./data/frontend"; import type { FrontendLocaleData, getHassTranslations, @@ -248,10 +251,10 @@ export interface HomeAssistant { vibrate: boolean; debugConnection: boolean; dockedSidebar: "docked" | "always_hidden" | "auto"; - defaultPanel: string; moreInfoEntityId: string | null; user?: CurrentUser; - userData?: CoreFrontendUserData | null; + userData?: CoreFrontendUserData; + systemData?: CoreFrontendSystemData; hassUrl(path?): string; callService( domain: ServiceCallRequest["domain"], diff --git a/src/util/ha-pref-storage.ts b/src/util/ha-pref-storage.ts index 4ef259ff0c..f306a5e959 100644 --- a/src/util/ha-pref-storage.ts +++ b/src/util/ha-pref-storage.ts @@ -8,8 +8,9 @@ const STORED_STATE = [ "debugConnection", "suspendWhenHidden", "enableShortcuts", - "defaultPanel", -]; +] as const; + +type StoredHomeAssistant = Pick; export function storeState(hass: HomeAssistant) { try { @@ -31,8 +32,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); diff --git a/test/util/ha-pref-storage.test.ts b/test/util/ha-pref-storage.test.ts index c984b7112b..d413a126d3 100644 --- a/test/util/ha-pref-storage.test.ts +++ b/test/util/ha-pref-storage.test.ts @@ -24,7 +24,7 @@ describe("ha-pref-storage", () => { window.localStorage.setItem = vi.fn(); storeState(mockHass as unknown as HomeAssistant); - expect(window.localStorage.setItem).toHaveBeenCalledTimes(8); + expect(window.localStorage.setItem).toHaveBeenCalledTimes(7); expect(window.localStorage.setItem).toHaveBeenCalledWith( "dockedSidebar", JSON.stringify("auto")