Compare commits

..

16 Commits

Author SHA1 Message Date
Petar Petrov
a5a3852f8f format 2025-11-18 09:18:57 +02:00
Paul Bottein
3c52ac27e5 Apply suggestions from code review 2025-11-17 15:02:04 +01:00
Paul Bottein
0bfe24de58 Feedbacks 2025-11-17 14:17:45 +01:00
Paul Bottein
5f135bb0a9 Update comment 2025-11-17 14:17:45 +01:00
Paul Bottein
dfe0d9a2f5 Set empty object instead of null 2025-11-17 14:17:45 +01:00
Paul Bottein
75610cea32 Update comment 2025-11-17 14:17:45 +01:00
Paul Bottein
c1b7d12da0 Update comment 2025-11-17 14:17:45 +01:00
Paul Bottein
d64c65b1f6 Wait for panels and user/system data to be loaded 2025-11-17 14:17:45 +01:00
Paul Bottein
1272f8bf3f Fix tests 2025-11-17 14:17:45 +01:00
Paul Bottein
3eb921831c Update url path and update dashboard settings 2025-11-17 14:17:45 +01:00
Paul Bottein
488078d16e Use user data and system data 2025-11-17 14:17:44 +01:00
Paul Bottein
222841da38 Fix user and local storage 2025-11-17 14:17:44 +01:00
Paul Bottein
bb66957796 Fix typings 2025-11-17 14:17:44 +01:00
Paul Bottein
1d03487784 Fix types 2025-11-17 14:17:44 +01:00
Paul Bottein
2434dcab58 Change logic for default panel 2025-11-17 14:17:44 +01:00
Paul Bottein
9f9727946e Save default panel in user data 2025-11-17 14:17:44 +01:00
25 changed files with 242 additions and 126 deletions

View File

@@ -194,7 +194,7 @@
"eslint-plugin-wc": "3.0.2",
"fancy-log": "2.0.0",
"fs-extra": "11.3.2",
"glob": "11.1.0",
"glob": "11.0.3",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0",

View File

@@ -94,12 +94,6 @@ export class HaDateInput extends LitElement {
}
private _keyDown(ev: KeyboardEvent) {
if (["Space", "Enter"].includes(ev.code)) {
ev.preventDefault();
ev.stopPropagation();
this._openDialog();
return;
}
if (!this.canClear) {
return;
}

View File

@@ -60,10 +60,6 @@ class HaHLSPlayer extends LitElement {
private static streamCount = 0;
private _handleVisibilityChange = () => {
if (document.pictureInPictureElement) {
// video is playing in picture-in-picture mode, don't do anything
return;
}
if (document.hidden) {
this._cleanUp();
} else {

View File

@@ -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 ||

View File

@@ -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()}
</ha-md-list>
`;
}
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]

View File

@@ -62,10 +62,6 @@ class HaWebRtcPlayer extends LitElement {
private _candidatesList: RTCIceCandidate[] = [];
private _handleVisibilityChange = () => {
if (document.pictureInPictureElement) {
// video is playing in picture-in-picture mode, don't do anything
return;
}
if (document.hidden) {
this._cleanUp();
} else {

View File

@@ -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 = <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

@@ -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") {

View File

@@ -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]

View File

@@ -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`
<ha-drawer
.type=${sidebarNarrow ? "modal" : ""}
.open=${sidebarNarrow ? this._drawerOpen : undefined}
.open=${sidebarNarrow ? this._drawerOpen : false}
.direction=${computeRTLDirection(this.hass)}
@MDCDrawer:closed=${this._drawerClosed}
>
@@ -59,12 +62,14 @@ export class HomeAssistantMain extends LitElement {
.route=${this.route}
.alwaysExpand=${sidebarNarrow || this.hass.dockedSidebar === "docked"}
></ha-sidebar>
<partial-panel-resolver
.narrow=${this.narrow}
.hass=${this.hass}
.route=${this.route}
slot="appContent"
></partial-panel-resolver>
${isPanelReady
? html`<partial-panel-resolver
.narrow=${this.narrow}
.hass=${this.hass}
.route=${this.route}
slot="appContent"
></partial-panel-resolver>`
: nothing}
</ha-drawer>
`;
}

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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` <hass-loading-screen></hass-loading-screen> `;
}
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
@@ -416,7 +419,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
this._dashboards,
this.hass.localize
)}
.data=${this._getItems(this._dashboards, this.hass.defaultPanel)}
.data=${this._getItems(this._dashboards, defaultPanel)}
.initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting}

View File

@@ -3,20 +3,21 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-md-dialog";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import "../../../../components/ha-md-select";
import "../../../../components/ha-md-select-option";
import "../../../../components/ha-button";
import "../../../../components/ha-spinner";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { LovelaceDashboard } from "../../../../data/lovelace/dashboard";
import { fetchDashboards } from "../../../../data/lovelace/dashboard";
import { getDefaultPanelUrlPath } from "../../../../data/panel";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import type { SelectDashboardDialogParams } from "./show-select-dashboard-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
@customElement("hui-dialog-select-dashboard")
export class HuiDialogSelectDashboard extends LitElement {
@@ -143,7 +144,9 @@ export class HuiDialogSelectDashboard extends LitElement {
...(this._params!.dashboards || (await fetchDashboards(this.hass))),
];
const currentPath = this._fromUrlPath || this.hass.defaultPanel;
const defaultPanel = getDefaultPanelUrlPath(this.hass);
const currentPath = this._fromUrlPath || defaultPanel;
for (const dashboard of this._dashboards!) {
if (dashboard.url_path !== currentPath) {
this._toUrlPath = dashboard.url_path;

View File

@@ -16,6 +16,7 @@ import { fetchConfig } from "../../../../data/lovelace/config/types";
import { isStrategyView } from "../../../../data/lovelace/config/view";
import type { LovelaceDashboard } from "../../../../data/lovelace/dashboard";
import { fetchDashboards } from "../../../../data/lovelace/dashboard";
import { getDefaultPanelUrlPath } from "../../../../data/panel";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import type { SelectViewDialogParams } from "./show-select-view-dialog";
@@ -60,6 +61,9 @@ export class HuiDialogSelectView extends LitElement {
if (!this._params) {
return nothing;
}
const defaultPanel = getDefaultPanelUrlPath(this.hass);
return html`
<ha-dialog
open
@@ -76,7 +80,7 @@ export class HuiDialogSelectView extends LitElement {
"ui.panel.lovelace.editor.select_view.dashboard_label"
)}
.disabled=${!this._dashboards.length}
.value=${this._urlPath || this.hass.defaultPanel}
.value=${this._urlPath || defaultPanel}
@selected=${this._dashboardChanged}
@closed=${stopPropagation}
fixedMenuPosition

View File

@@ -365,7 +365,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@media (max-width: 600px) {
:host {
--column-gap: var(--ha-view-sections-narrow-column-gap, var(--row-gap));
--column-gap: var(--row-gap);
}
}

View File

@@ -6,8 +6,10 @@ import "../../components/ha-select";
import "../../components/ha-settings-row";
import type { LovelaceDashboard } from "../../data/lovelace/dashboard";
import { fetchDashboards } from "../../data/lovelace/dashboard";
import { setDefaultPanel } from "../../data/panel";
import type { HomeAssistant } from "../../types";
import { saveFrontendUserData } from "../../data/frontend";
const USE_SYSTEM_VALUE = "___use_system___";
@customElement("ha-pick-dashboard-row")
class HaPickDashboardRow extends LitElement {
@@ -23,6 +25,7 @@ class HaPickDashboardRow extends LitElement {
}
protected render(): TemplateResult {
const value = this.hass.userData?.defaultPanel || USE_SYSTEM_VALUE;
return html`
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
@@ -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
>
<ha-list-item .value=${USE_SYSTEM_VALUE}>
${this.hass.localize("ui.panel.profile.dashboard.system")}
</ha-list-item>
<ha-list-item value="lovelace">
${this.hass.localize(
"ui.panel.profile.dashboard.default_dashboard_label"
)}
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
</ha-list-item>
<ha-list-item value="home">
${this.hass.localize("ui.panel.profile.dashboard.home")}
</ha-list-item>
${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,
});
}
}

View File

@@ -154,6 +154,10 @@ class HaProfileSectionGeneral extends LitElement {
.narrow=${this.narrow}
.hass=${this.hass}
></ha-pick-first-weekday-row>
<ha-pick-dashboard-row
.narrow=${this.narrow}
.hass=${this.hass}
></ha-pick-dashboard-row>
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
${this.hass.localize(
@@ -208,10 +212,6 @@ class HaProfileSectionGeneral extends LitElement {
.narrow=${this.narrow}
.hass=${this.hass}
></ha-pick-theme-row>
<ha-pick-dashboard-row
.narrow=${this.narrow}
.hass=${this.hass}
></ha-pick-dashboard-row>
${this.hass.dockedSidebar !== "auto" || !this.narrow
? html`
<ha-force-narrow-row

View File

@@ -8,13 +8,16 @@ 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 {
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 +36,6 @@ import { fetchWithAuth } from "../util/fetch-with-auth";
import { getState } 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";
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
@@ -59,8 +61,9 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
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 = <T extends Constructor<HassBaseEl>>(
},
resources: null as any,
localize: () => "",
translationMetadata,
dockedSidebar: "docked",
vibrate: true,
@@ -209,9 +211,9 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
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 = <T extends Constructor<HassBaseEl>>(
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) {

View File

@@ -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<DockSidebarParams>;
"hass-default-panel": HASSDomEvent<DefaultPanelParams>;
}
}
@@ -32,9 +26,5 @@ export default <T extends Constructor<HassBaseEl>>(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!);
});
}
};

View File

@@ -3515,8 +3515,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": {
@@ -8663,9 +8667,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",

View File

@@ -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<T = any>(
domain: ServiceCallRequest["domain"],

View File

@@ -8,8 +8,9 @@ const STORED_STATE = [
"debugConnection",
"suspendWhenHidden",
"enableShortcuts",
"defaultPanel",
];
] as const;
type StoredHomeAssistant = Pick<HomeAssistant, (typeof STORED_STATE)[number]>;
export function storeState(hass: HomeAssistant) {
try {
@@ -31,8 +32,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);

View File

@@ -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")

View File

@@ -8869,19 +8869,19 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:11.1.0":
version: 11.1.0
resolution: "glob@npm:11.1.0"
"glob@npm:11.0.3":
version: 11.0.3
resolution: "glob@npm:11.0.3"
dependencies:
foreground-child: "npm:^3.3.1"
jackspeak: "npm:^4.1.1"
minimatch: "npm:^10.1.1"
minimatch: "npm:^10.0.3"
minipass: "npm:^7.1.2"
package-json-from-dist: "npm:^1.0.0"
path-scurry: "npm:^2.0.0"
bin:
glob: dist/esm/bin.mjs
checksum: 10/da4501819633daff8822c007bb3f93d5c4d2cbc7b15a8e886660f4497dd251a1fb4f53a85fba1e760b31704eff7164aeb2c7a82db10f9f2c362d12c02fe52cf3
checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893
languageName: node
linkType: hard
@@ -9324,7 +9324,7 @@ __metadata:
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.2"
fuse.js: "npm:7.1.0"
glob: "npm:11.1.0"
glob: "npm:11.0.3"
google-timezones-json: "npm:1.2.0"
gulp: "npm:5.0.1"
gulp-brotli: "npm:3.0.0"
@@ -11167,12 +11167,12 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^10.1.1":
version: 10.1.1
resolution: "minimatch@npm:10.1.1"
"minimatch@npm:^10.0.3":
version: 10.0.3
resolution: "minimatch@npm:10.0.3"
dependencies:
"@isaacs/brace-expansion": "npm:^5.0.0"
checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e
checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8
languageName: node
linkType: hard