Skip to content

Commit 1ac3cf1

Browse files
piitayaMindFreeze
andauthored
Save default panel at user and system level (home-assistant#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 <[email protected]>
1 parent 8d96679 commit 1ac3cf1

File tree

19 files changed

+230
-100
lines changed

19 files changed

+230
-100
lines changed

src/components/ha-navigation-picker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { fireEvent } from "../common/dom/fire_event";
66
import { titleCase } from "../common/string/title-case";
77
import { fetchConfig } from "../data/lovelace/config/types";
88
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
9+
import { getDefaultPanelUrlPath } from "../data/panel";
910
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
1011
import "./ha-combo-box";
1112
import type { HaComboBox } from "./ha-combo-box";
@@ -44,7 +45,7 @@ const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
4445
path: `/${panel.url_path}`,
4546
icon: panel.icon ?? "mdi:view-dashboard",
4647
title:
47-
panel.url_path === hass.defaultPanel
48+
panel.url_path === getDefaultPanelUrlPath(hass)
4849
? hass.localize("panel.states")
4950
: hass.localize(`panel.${panel.title}`) ||
5051
panel.title ||

src/components/ha-sidebar.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { computeRTL } from "../common/util/compute_rtl";
3333
import { throttle } from "../common/util/throttle";
3434
import { subscribeFrontendUserData } from "../data/frontend";
3535
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
36+
import { getDefaultPanelUrlPath } from "../data/panel";
3637
import type { PersistentNotification } from "../data/persistent_notification";
3738
import { subscribeNotifications } from "../data/persistent_notification";
3839
import { subscribeRepairsIssueRegistry } from "../data/repairs";
@@ -142,7 +143,7 @@ const defaultPanelSorter = (
142143
export const computePanels = memoizeOne(
143144
(
144145
panels: HomeAssistant["panels"],
145-
defaultPanel: HomeAssistant["defaultPanel"],
146+
defaultPanel: string,
146147
panelsOrder: string[],
147148
hiddenPanels: string[],
148149
locale: HomeAssistant["locale"]
@@ -298,7 +299,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
298299
hass.localize !== oldHass.localize ||
299300
hass.locale !== oldHass.locale ||
300301
hass.states !== oldHass.states ||
301-
hass.defaultPanel !== oldHass.defaultPanel ||
302+
hass.userData !== oldHass.userData ||
303+
hass.systemData !== oldHass.systemData ||
302304
hass.connected !== oldHass.connected
303305
);
304306
}
@@ -401,9 +403,11 @@ class HaSidebar extends SubscribeMixin(LitElement) {
401403
`;
402404
}
403405

406+
const defaultPanel = getDefaultPanelUrlPath(this.hass);
407+
404408
const [beforeSpacer, afterSpacer] = computePanels(
405409
this.hass.panels,
406-
this.hass.defaultPanel,
410+
defaultPanel,
407411
this._panelOrder,
408412
this._hiddenPanels,
409413
this.hass.locale
@@ -418,23 +422,27 @@ class HaSidebar extends SubscribeMixin(LitElement) {
418422
@scroll=${this._listboxScroll}
419423
@keydown=${this._listboxKeydown}
420424
>
421-
${this._renderPanels(beforeSpacer, selectedPanel)}
425+
${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)}
422426
${this._renderSpacer()}
423-
${this._renderPanels(afterSpacer, selectedPanel)}
427+
${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)}
424428
${this._renderExternalConfiguration()}
425429
</ha-md-list>
426430
`;
427431
}
428432

429-
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
433+
private _renderPanels(
434+
panels: PanelInfo[],
435+
selectedPanel: string,
436+
defaultPanel: string
437+
) {
430438
return panels.map((panel) =>
431439
this._renderPanel(
432440
panel.url_path,
433-
panel.url_path === this.hass.defaultPanel
441+
panel.url_path === defaultPanel
434442
? panel.title || this.hass.localize("panel.states")
435443
: this.hass.localize(`panel.${panel.title}`) || panel.title,
436444
panel.icon,
437-
panel.url_path === this.hass.defaultPanel && !panel.icon
445+
panel.url_path === defaultPanel && !panel.icon
438446
? PANEL_ICONS.lovelace
439447
: panel.url_path in PANEL_ICONS
440448
? PANEL_ICONS[panel.url_path]

src/data/frontend.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,32 @@ import type { Connection } from "home-assistant-js-websocket";
33
export interface CoreFrontendUserData {
44
showAdvanced?: boolean;
55
showEntityIdPicker?: boolean;
6+
defaultPanel?: string;
67
}
78

89
export interface SidebarFrontendUserData {
910
panelOrder: string[];
1011
hiddenPanels: string[];
1112
}
1213

14+
export interface CoreFrontendSystemData {
15+
defaultPanel?: string;
16+
}
17+
1318
declare global {
1419
interface FrontendUserData {
1520
core: CoreFrontendUserData;
1621
sidebar: SidebarFrontendUserData;
1722
}
23+
interface FrontendSystemData {
24+
core: CoreFrontendSystemData;
25+
}
1826
}
1927

2028
export type ValidUserDataKey = keyof FrontendUserData;
2129

30+
export type ValidSystemDataKey = keyof FrontendSystemData;
31+
2232
export const fetchFrontendUserData = async <
2333
UserDataKey extends ValidUserDataKey,
2434
>(
@@ -59,3 +69,46 @@ export const subscribeFrontendUserData = <UserDataKey extends ValidUserDataKey>(
5969
key: userDataKey,
6070
}
6171
);
72+
73+
export const fetchFrontendSystemData = async <
74+
SystemDataKey extends ValidSystemDataKey,
75+
>(
76+
conn: Connection,
77+
key: SystemDataKey
78+
): Promise<FrontendSystemData[SystemDataKey] | null> => {
79+
const result = await conn.sendMessagePromise<{
80+
value: FrontendSystemData[SystemDataKey] | null;
81+
}>({
82+
type: "frontend/get_system_data",
83+
key,
84+
});
85+
return result.value;
86+
};
87+
88+
export const saveFrontendSystemData = async <
89+
SystemDataKey extends ValidSystemDataKey,
90+
>(
91+
conn: Connection,
92+
key: SystemDataKey,
93+
value: FrontendSystemData[SystemDataKey]
94+
): Promise<void> =>
95+
conn.sendMessagePromise<undefined>({
96+
type: "frontend/set_system_data",
97+
key,
98+
value,
99+
});
100+
101+
export const subscribeFrontendSystemData = <
102+
SystemDataKey extends ValidSystemDataKey,
103+
>(
104+
conn: Connection,
105+
systemDataKey: SystemDataKey,
106+
onChange: (data: { value: FrontendSystemData[SystemDataKey] | null }) => void
107+
) =>
108+
conn.subscribeMessage<{ value: FrontendSystemData[SystemDataKey] | null }>(
109+
onChange,
110+
{
111+
type: "frontend/subscribe_system_data",
112+
key: systemDataKey,
113+
}
114+
);

src/data/panel.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1-
import { fireEvent } from "../common/dom/fire_event";
21
import type { HomeAssistant, PanelInfo } from "../types";
32

43
/** Panel to show when no panel is picked. */
54
export const DEFAULT_PANEL = "lovelace";
65

7-
export const getStorageDefaultPanelUrlPath = (): string => {
6+
export const getLegacyDefaultPanelUrlPath = (): string | null => {
87
const defaultPanel = window.localStorage.getItem("defaultPanel");
9-
10-
return defaultPanel ? JSON.parse(defaultPanel) : DEFAULT_PANEL;
8+
return defaultPanel ? JSON.parse(defaultPanel) : null;
119
};
1210

13-
export const setDefaultPanel = (
14-
element: HTMLElement,
15-
urlPath: string
16-
): void => {
17-
fireEvent(element, "hass-default-panel", { defaultPanel: urlPath });
18-
};
11+
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
12+
hass.userData?.defaultPanel ||
13+
hass.systemData?.defaultPanel ||
14+
getLegacyDefaultPanelUrlPath() ||
15+
DEFAULT_PANEL;
1916

20-
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo =>
21-
hass.panels[hass.defaultPanel]
22-
? hass.panels[hass.defaultPanel]
23-
: hass.panels[DEFAULT_PANEL];
17+
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => {
18+
const panel = getDefaultPanelUrlPath(hass);
19+
20+
return (panel ? hass.panels[panel] : undefined) ?? hass.panels[DEFAULT_PANEL];
21+
};
2422

2523
export const getPanelNameTranslationKey = (panel: PanelInfo) => {
2624
if (panel.url_path === "lovelace") {

src/dialogs/sidebar/dialog-edit-sidebar.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "../../data/frontend";
2121
import type { HomeAssistant } from "../../types";
2222
import { showConfirmationDialog } from "../generic/show-dialog-box";
23+
import { getDefaultPanelUrlPath } from "../../data/panel";
2324

2425
@customElement("dialog-edit-sidebar")
2526
class DialogEditSidebar extends LitElement {
@@ -94,9 +95,11 @@ class DialogEditSidebar extends LitElement {
9495

9596
const panels = this._panels(this.hass.panels);
9697

98+
const defaultPanel = getDefaultPanelUrlPath(this.hass);
99+
97100
const [beforeSpacer, afterSpacer] = computePanels(
98101
this.hass.panels,
99-
this.hass.defaultPanel,
102+
defaultPanel,
100103
this._order,
101104
this._hidden,
102105
this.hass.locale
@@ -120,12 +123,12 @@ class DialogEditSidebar extends LitElement {
120123
].map((panel) => ({
121124
value: panel.url_path,
122125
label:
123-
panel.url_path === this.hass.defaultPanel
126+
panel.url_path === defaultPanel
124127
? panel.title || this.hass.localize("panel.states")
125128
: this.hass.localize(`panel.${panel.title}`) || panel.title || "?",
126129
icon: panel.icon || undefined,
127130
iconPath:
128-
panel.url_path === this.hass.defaultPanel && !panel.icon
131+
panel.url_path === defaultPanel && !panel.icon
129132
? PANEL_ICONS.lovelace
130133
: panel.url_path in PANEL_ICONS
131134
? PANEL_ICONS[panel.url_path]

src/layouts/home-assistant-main.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PropertyValues, TemplateResult } from "lit";
2-
import { css, html, LitElement } from "lit";
2+
import { css, html, LitElement, nothing } from "lit";
33
import { customElement, property, state } from "lit/decorators";
44
import type { HASSDomEvent } from "../common/dom/fire_event";
55
import { fireEvent } from "../common/dom/fire_event";
@@ -46,10 +46,13 @@ export class HomeAssistantMain extends LitElement {
4646
protected render(): TemplateResult {
4747
const sidebarNarrow = this._sidebarNarrow || this._externalSidebar;
4848

49+
const isPanelReady =
50+
this.hass.panels && this.hass.userData && this.hass.systemData;
51+
4952
return html`
5053
<ha-drawer
5154
.type=${sidebarNarrow ? "modal" : ""}
52-
.open=${sidebarNarrow ? this._drawerOpen : undefined}
55+
.open=${sidebarNarrow ? this._drawerOpen : false}
5356
.direction=${computeRTLDirection(this.hass)}
5457
@MDCDrawer:closed=${this._drawerClosed}
5558
>
@@ -59,12 +62,14 @@ export class HomeAssistantMain extends LitElement {
5962
.route=${this.route}
6063
.alwaysExpand=${sidebarNarrow || this.hass.dockedSidebar === "docked"}
6164
></ha-sidebar>
62-
<partial-panel-resolver
63-
.narrow=${this.narrow}
64-
.hass=${this.hass}
65-
.route=${this.route}
66-
slot="appContent"
67-
></partial-panel-resolver>
65+
${isPanelReady
66+
? html`<partial-panel-resolver
67+
.narrow=${this.narrow}
68+
.hass=${this.hass}
69+
.route=${this.route}
70+
slot="appContent"
71+
></partial-panel-resolver>`
72+
: nothing}
6873
</ha-drawer>
6974
`;
7075
}

src/layouts/home-assistant.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import type { Connection } from "home-assistant-js-websocket";
12
import type { PropertyValues } from "lit";
23
import { html } from "lit";
34
import { customElement, state } from "lit/decorators";
4-
import type { Connection } from "home-assistant-js-websocket";
5+
import { storage } from "../common/decorators/storage";
56
import { isNavigationClick } from "../common/dom/is-navigation-click";
67
import { navigate } from "../common/navigate";
7-
import { getStorageDefaultPanelUrlPath } from "../data/panel";
88
import type { WindowWithPreloads } from "../data/preloads";
99
import type { RecorderInfo } from "../data/recorder";
1010
import { getRecorderInfo } from "../data/recorder";
@@ -23,7 +23,6 @@ import {
2323
} from "../util/register-service-worker";
2424
import "./ha-init-page";
2525
import "./home-assistant-main";
26-
import { storage } from "../common/decorators/storage";
2726

2827
const useHash = __DEMO__;
2928
const curPath = () =>
@@ -53,11 +52,6 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
5352
super();
5453
const path = curPath();
5554

56-
if (["", "/"].includes(path)) {
57-
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
58-
replace: true,
59-
});
60-
}
6155
this._route = {
6256
prefix: "",
6357
path,

src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import { customElement, property, state } from "lit/decorators";
44
import memoizeOne from "memoize-one";
55
import { fireEvent } from "../../../../common/dom/fire_event";
66
import { slugify } from "../../../../common/string/slugify";
7+
import "../../../../components/ha-button";
78
import { createCloseHeading } from "../../../../components/ha-dialog";
89
import "../../../../components/ha-form/ha-form";
9-
import "../../../../components/ha-button";
1010
import type { SchemaUnion } from "../../../../components/ha-form/types";
11+
import { saveFrontendSystemData } from "../../../../data/frontend";
1112
import type {
1213
LovelaceDashboard,
1314
LovelaceDashboardCreateParams,
1415
LovelaceDashboardMutableParams,
1516
} from "../../../../data/lovelace/dashboard";
16-
import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel";
17+
import { DEFAULT_PANEL } from "../../../../data/panel";
1718
import { haStyleDialog } from "../../../../resources/styles";
1819
import type { HomeAssistant } from "../../../../types";
20+
import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers";
1921
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
2022

2123
@customElement("dialog-lovelace-dashboard-detail")
@@ -59,7 +61,8 @@ export class DialogLovelaceDashboardDetail extends LitElement {
5961
if (!this._params || !this._data) {
6062
return nothing;
6163
}
62-
const defaultPanelUrlPath = this.hass.defaultPanel;
64+
const defaultPanelUrlPath =
65+
this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
6366
const titleInvalid = !this._data.title || !this._data.title.trim();
6467

6568
return html`
@@ -251,15 +254,38 @@ export class DialogLovelaceDashboardDetail extends LitElement {
251254
};
252255
}
253256

254-
private _toggleDefault() {
257+
private async _toggleDefault() {
255258
const urlPath = this._params?.urlPath;
256259
if (!urlPath) {
257260
return;
258261
}
259-
setDefaultPanel(
260-
this,
261-
urlPath === this.hass.defaultPanel ? DEFAULT_PANEL : urlPath
262-
);
262+
263+
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
264+
// Add warning dialog to saying that this will change the default dashboard for all users
265+
const confirm = await showConfirmationDialog(this, {
266+
title: this.hass.localize(
267+
urlPath === defaultPanel
268+
? "ui.panel.config.lovelace.dashboards.detail.remove_default_confirm_title"
269+
: "ui.panel.config.lovelace.dashboards.detail.set_default_confirm_title"
270+
),
271+
text: this.hass.localize(
272+
urlPath === defaultPanel
273+
? "ui.panel.config.lovelace.dashboards.detail.remove_default_confirm_text"
274+
: "ui.panel.config.lovelace.dashboards.detail.set_default_confirm_text"
275+
),
276+
confirmText: this.hass.localize("ui.common.ok"),
277+
dismissText: this.hass.localize("ui.common.cancel"),
278+
destructive: false,
279+
});
280+
281+
if (!confirm) {
282+
return;
283+
}
284+
285+
saveFrontendSystemData(this.hass.connection, "core", {
286+
...this.hass.systemData,
287+
defaultPanel: urlPath === defaultPanel ? undefined : urlPath,
288+
});
263289
}
264290

265291
private async _updateDashboard() {

0 commit comments

Comments
 (0)