Skip to content

Commit 0595f72

Browse files
authored
Add basic editor to edit favorites entities for home panel (#28028)
* Add basic editor to edit favorites entities for home panel * Rename favorites * Rename favorites * Feedbacks
1 parent 1c03158 commit 0595f72

File tree

5 files changed

+239
-15
lines changed

5 files changed

+239
-15
lines changed

src/data/frontend.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ export interface CoreFrontendSystemData {
1515
defaultPanel?: string;
1616
}
1717

18+
export interface HomeFrontendSystemData {
19+
favorite_entities?: string[];
20+
}
21+
1822
declare global {
1923
interface FrontendUserData {
2024
core: CoreFrontendUserData;
2125
sidebar: SidebarFrontendUserData;
2226
}
2327
interface FrontendSystemData {
2428
core: CoreFrontendSystemData;
29+
home: HomeFrontendSystemData;
2530
}
2631
}
2732

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { css, html, LitElement, nothing } from "lit";
2+
import { customElement, property, state } from "lit/decorators";
3+
import { fireEvent } from "../../../common/dom/fire_event";
4+
import "../../../components/entity/ha-entities-picker";
5+
import "../../../components/ha-button";
6+
import "../../../components/ha-dialog-footer";
7+
import "../../../components/ha-wa-dialog";
8+
import type { HomeFrontendSystemData } from "../../../data/frontend";
9+
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
10+
import { haStyleDialog } from "../../../resources/styles";
11+
import type { HomeAssistant } from "../../../types";
12+
import type { EditHomeDialogParams } from "./show-dialog-edit-home";
13+
14+
@customElement("dialog-edit-home")
15+
export class DialogEditHome
16+
extends LitElement
17+
implements HassDialog<EditHomeDialogParams>
18+
{
19+
@property({ attribute: false }) public hass!: HomeAssistant;
20+
21+
@state() private _params?: EditHomeDialogParams;
22+
23+
@state() private _config?: HomeFrontendSystemData;
24+
25+
@state() private _open = false;
26+
27+
@state() private _submitting = false;
28+
29+
public showDialog(params: EditHomeDialogParams): void {
30+
this._params = params;
31+
this._config = { ...params.config };
32+
this._open = true;
33+
}
34+
35+
public closeDialog(): boolean {
36+
this._open = false;
37+
return true;
38+
}
39+
40+
private _dialogClosed(): void {
41+
this._params = undefined;
42+
this._config = undefined;
43+
this._submitting = false;
44+
fireEvent(this, "dialog-closed", { dialog: this.localName });
45+
}
46+
47+
protected render() {
48+
if (!this._params) {
49+
return nothing;
50+
}
51+
52+
return html`
53+
<ha-wa-dialog
54+
.hass=${this.hass}
55+
.open=${this._open}
56+
.headerTitle=${this.hass.localize("ui.panel.home.editor.title")}
57+
@closed=${this._dialogClosed}
58+
>
59+
<p class="description">
60+
${this.hass.localize("ui.panel.home.editor.description")}
61+
</p>
62+
63+
<ha-entities-picker
64+
autofocus
65+
.hass=${this.hass}
66+
.value=${this._config?.favorite_entities || []}
67+
.label=${this.hass.localize(
68+
"ui.panel.lovelace.editor.strategy.home.favorite_entities"
69+
)}
70+
.placeholder=${this.hass.localize(
71+
"ui.panel.lovelace.editor.strategy.home.add_favorite_entity"
72+
)}
73+
.helper=${this.hass.localize(
74+
"ui.panel.home.editor.favorite_entities_helper"
75+
)}
76+
reorder
77+
allow-custom-entity
78+
@value-changed=${this._favoriteEntitiesChanged}
79+
></ha-entities-picker>
80+
81+
<ha-dialog-footer slot="footer">
82+
<ha-button
83+
appearance="plain"
84+
slot="secondaryAction"
85+
@click=${this.closeDialog}
86+
.disabled=${this._submitting}
87+
>
88+
${this.hass.localize("ui.common.cancel")}
89+
</ha-button>
90+
<ha-button
91+
slot="primaryAction"
92+
@click=${this._save}
93+
.disabled=${this._submitting}
94+
>
95+
${this.hass.localize("ui.common.save")}
96+
</ha-button>
97+
</ha-dialog-footer>
98+
</ha-wa-dialog>
99+
`;
100+
}
101+
102+
private _favoriteEntitiesChanged(ev: CustomEvent): void {
103+
const entities = ev.detail.value as string[];
104+
this._config = {
105+
...this._config,
106+
favorite_entities: entities.length > 0 ? entities : undefined,
107+
};
108+
}
109+
110+
private async _save(): Promise<void> {
111+
if (!this._params || !this._config) {
112+
return;
113+
}
114+
115+
this._submitting = true;
116+
117+
try {
118+
await this._params.saveConfig(this._config);
119+
this.closeDialog();
120+
} catch (err: any) {
121+
// eslint-disable-next-line no-console
122+
console.error("Failed to save home configuration:", err);
123+
} finally {
124+
this._submitting = false;
125+
}
126+
}
127+
128+
static styles = [
129+
haStyleDialog,
130+
css`
131+
ha-wa-dialog {
132+
--dialog-content-padding: var(--ha-space-6);
133+
}
134+
135+
.description {
136+
margin: 0 0 var(--ha-space-4) 0;
137+
color: var(--secondary-text-color);
138+
}
139+
140+
ha-entities-picker {
141+
display: block;
142+
}
143+
`,
144+
];
145+
}
146+
147+
declare global {
148+
interface HTMLElementTagNameMap {
149+
"dialog-edit-home": DialogEditHome;
150+
}
151+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { fireEvent } from "../../../common/dom/fire_event";
2+
import type { HomeFrontendSystemData } from "../../../data/frontend";
3+
4+
export interface EditHomeDialogParams {
5+
config: HomeFrontendSystemData;
6+
saveConfig: (config: HomeFrontendSystemData) => Promise<void>;
7+
}
8+
9+
export const loadEditHomeDialog = () => import("./dialog-edit-home");
10+
11+
export const showEditHomeDialog = (
12+
element: HTMLElement,
13+
params: EditHomeDialogParams
14+
): void => {
15+
fireEvent(element, "show-dialog", {
16+
dialogTag: "dialog-edit-home",
17+
dialogImport: loadEditHomeDialog,
18+
dialogParams: params,
19+
});
20+
};

src/panels/home/ha-panel-home.ts

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import { LitElement, css, html, nothing } from "lit";
33
import { customElement, property, state } from "lit/decorators";
44
import { debounce } from "../../common/util/debounce";
55
import { deepEqual } from "../../common/util/deep-equal";
6+
import {
7+
fetchFrontendSystemData,
8+
saveFrontendSystemData,
9+
type HomeFrontendSystemData,
10+
} from "../../data/frontend";
611
import type { LovelaceDashboardStrategyConfig } from "../../data/lovelace/config/types";
712
import type { HomeAssistant, PanelInfo, Route } from "../../types";
13+
import { showToast } from "../../util/toast";
814
import "../lovelace/hui-root";
915
import { generateLovelaceDashboardStrategy } from "../lovelace/strategies/get-strategy";
1016
import type { Lovelace } from "../lovelace/types";
11-
import { showAlertDialog } from "../lovelace/custom-card-helpers";
12-
13-
const HOME_LOVELACE_CONFIG: LovelaceDashboardStrategyConfig = {
14-
strategy: {
15-
type: "home",
16-
},
17-
};
17+
import { showEditHomeDialog } from "./dialogs/show-dialog-edit-home";
1818

1919
@customElement("ha-panel-home")
2020
class PanelHome extends LitElement {
@@ -28,12 +28,14 @@ class PanelHome extends LitElement {
2828

2929
@state() private _lovelace?: Lovelace;
3030

31+
@state() private _config: FrontendSystemData["home"] = {};
32+
3133
public willUpdate(changedProps: PropertyValues) {
3234
super.willUpdate(changedProps);
3335
// Initial setup
3436
if (!this.hasUpdated) {
3537
this.hass.loadFragmentTranslation("lovelace");
36-
this._setLovelace();
38+
this._loadConfig();
3739
return;
3840
}
3941

@@ -95,9 +97,28 @@ class PanelHome extends LitElement {
9597
`;
9698
}
9799

100+
private async _loadConfig() {
101+
try {
102+
const data = await fetchFrontendSystemData(this.hass.connection, "home");
103+
this._config = data || {};
104+
} catch (err) {
105+
// eslint-disable-next-line no-console
106+
console.error("Failed to load favorites:", err);
107+
this._config = {};
108+
}
109+
this._setLovelace();
110+
}
111+
98112
private async _setLovelace() {
113+
const strategyConfig: LovelaceDashboardStrategyConfig = {
114+
strategy: {
115+
type: "home",
116+
favorite_entities: this._config.favorite_entities,
117+
},
118+
};
119+
99120
const config = await generateLovelaceDashboardStrategy(
100-
HOME_LOVELACE_CONFIG,
121+
strategyConfig,
101122
this.hass
102123
);
103124

@@ -121,15 +142,34 @@ class PanelHome extends LitElement {
121142
}
122143

123144
private _setEditMode = () => {
124-
// For now, we just show an alert that edit mode is not supported.
125-
// This will be expanded in the future.
126-
showAlertDialog(this, {
127-
title: "Edit mode not available",
128-
text: "The Home panel does not support edit mode.",
129-
confirmText: this.hass.localize("ui.common.ok"),
145+
showEditHomeDialog(this, {
146+
config: this._config,
147+
saveConfig: async (config) => {
148+
await this._saveConfig(config);
149+
},
130150
});
131151
};
132152

153+
private async _saveConfig(config: HomeFrontendSystemData): Promise<void> {
154+
try {
155+
await saveFrontendSystemData(this.hass.connection, "home", config);
156+
this._config = config || {};
157+
} catch (err: any) {
158+
// eslint-disable-next-line no-console
159+
console.error("Failed to save home configuration:", err);
160+
showToast(this, {
161+
message: this.hass.localize("ui.panel.home.editor.save_failed"),
162+
duration: 0,
163+
dismissable: true,
164+
});
165+
return;
166+
}
167+
showToast(this, {
168+
message: this.hass.localize("ui.common.successfully_saved"),
169+
});
170+
this._setLovelace();
171+
}
172+
133173
static readonly styles: CSSResultGroup = css`
134174
:host {
135175
display: block;

src/translations/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,14 @@
22202220
"migrate_to_user_data": "This will change the sidebar on all the devices you are logged in to. To create a sidebar per device, you should use a different user for that device."
22212221
},
22222222
"panel": {
2223+
"home": {
2224+
"editor": {
2225+
"title": "Edit home page",
2226+
"description": "Configure your home page display preferences.",
2227+
"favorite_entities_helper": "Display your favorite entities. Home Assistant will still suggest based on commonly used up to 8 slots.",
2228+
"save_failed": "Failed to save home page configuration"
2229+
}
2230+
},
22232231
"my": {
22242232
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
22252233
"component_not_loaded": "This redirect is not supported by your Home Assistant instance. You need the integration {integration} to use this redirect.",

0 commit comments

Comments
 (0)