1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 00:27:49 +01:00

Allow customizing home page summaries and adding quick links

Add ability to hide built-in summaries (light, climate, security,
media players, weather, energy) and add custom quick links to
dashboards, sidebar items, or other pages from the edit overview dialog.

https://claude.ai/code/session_01AqgbQULH5vfETibiba5RXH
This commit is contained in:
Claude
2026-03-31 10:53:00 +00:00
parent ae21017de8
commit 798a73ac3c
6 changed files with 272 additions and 2 deletions

View File

@@ -17,9 +17,17 @@ export interface CoreFrontendSystemData {
onboarded_date?: string;
}
export interface HomeQuickLink {
name: string;
icon?: string;
navigation_path: string;
}
export interface HomeFrontendSystemData {
favorite_entities?: string[];
welcome_banner_dismissed?: boolean;
hidden_summaries?: string[];
quick_links?: HomeQuickLink[];
}
declare global {

View File

@@ -1,17 +1,35 @@
import { mdiClose, mdiPlus } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entities-picker";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-dialog-footer";
import "../../../components/ha-dialog";
import type { HomeFrontendSystemData } from "../../../data/frontend";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-picker";
import "../../../components/ha-navigation-picker";
import "../../../components/ha-svg-icon";
import "../../../components/ha-switch";
import "../../../components/ha-textfield";
import type {
HomeFrontendSystemData,
HomeQuickLink,
} from "../../../data/frontend";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import {
HOME_SUMMARIES,
getSummaryLabel,
type HomeSummary,
} from "../../../panels/lovelace/strategies/home/helpers/home-summaries";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import type { EditHomeDialogParams } from "./show-dialog-edit-home";
const ALL_SUMMARY_KEYS = [...HOME_SUMMARIES, "weather"] as const;
@customElement("dialog-edit-home")
export class DialogEditHome
extends LitElement
@@ -50,6 +68,9 @@ export class DialogEditHome
return nothing;
}
const hiddenSummaries = new Set(this._config?.hidden_summaries || []);
const quickLinks = this._config?.quick_links || [];
return html`
<ha-dialog
.hass=${this.hass}
@@ -79,6 +100,88 @@ export class DialogEditHome
@value-changed=${this._favoriteEntitiesChanged}
></ha-entities-picker>
<h3 class="section-header">
${this.hass.localize("ui.panel.home.editor.summaries")}
</h3>
<p class="section-description">
${this.hass.localize("ui.panel.home.editor.summaries_description")}
</p>
<div class="summary-toggles">
${ALL_SUMMARY_KEYS.map((key) => {
const label = this._getSummaryLabel(key);
return html`
<label class="summary-toggle">
<ha-switch
.checked=${!hiddenSummaries.has(key)}
.summary=${key}
@change=${this._summaryToggleChanged}
></ha-switch>
<span>${label}</span>
</label>
`;
})}
</div>
<h3 class="section-header">
${this.hass.localize("ui.panel.home.editor.quick_links")}
</h3>
<p class="section-description">
${this.hass.localize("ui.panel.home.editor.quick_links_description")}
</p>
<div class="quick-links">
${repeat(
quickLinks,
(_link, index) => index,
(link, index) => html`
<div class="quick-link-row">
<ha-icon-picker
.hass=${this.hass}
.value=${link.icon || ""}
.label=${this.hass.localize(
"ui.panel.home.editor.quick_link_icon"
)}
@value-changed=${(ev: CustomEvent) =>
this._quickLinkChanged(index, "icon", ev.detail.value)}
></ha-icon-picker>
<ha-textfield
.value=${link.name}
.label=${this.hass.localize(
"ui.panel.home.editor.quick_link_name"
)}
@input=${(ev: InputEvent) =>
this._quickLinkChanged(
index,
"name",
(ev.target as HTMLInputElement).value
)}
></ha-textfield>
<ha-navigation-picker
.hass=${this.hass}
.value=${link.navigation_path}
.label=${this.hass.localize(
"ui.panel.home.editor.quick_link_path"
)}
@value-changed=${(ev: CustomEvent) =>
this._quickLinkChanged(
index,
"navigation_path",
ev.detail.value
)}
></ha-navigation-picker>
<ha-icon-button
.path=${mdiClose}
.label=${this.hass.localize("ui.common.remove")}
@click=${() => this._removeQuickLink(index)}
></ha-icon-button>
</div>
`
)}
<ha-button @click=${this._addQuickLink}>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
${this.hass.localize("ui.panel.home.editor.add_quick_link")}
</ha-button>
</div>
<ha-alert alert-type="info">
${this.hass.localize("ui.panel.home.editor.areas_hint", {
areas_page: html`<a
@@ -110,6 +213,69 @@ export class DialogEditHome
`;
}
private _getSummaryLabel(key: string): string {
if (key === "weather") {
return this.hass.localize(
"ui.panel.lovelace.strategy.home.summary_list.weather"
);
}
return getSummaryLabel(this.hass.localize, key as HomeSummary);
}
private _summaryToggleChanged(ev: Event): void {
const target = ev.target as HTMLElement & {
checked: boolean;
summary: string;
};
const summary = target.summary;
const checked = target.checked;
const hiddenSummaries = new Set(this._config?.hidden_summaries || []);
if (checked) {
hiddenSummaries.delete(summary);
} else {
hiddenSummaries.add(summary);
}
this._config = {
...this._config,
hidden_summaries:
hiddenSummaries.size > 0 ? [...hiddenSummaries] : undefined,
};
}
private _quickLinkChanged(
index: number,
field: keyof HomeQuickLink,
value: string
): void {
const quickLinks = [...(this._config?.quick_links || [])];
quickLinks[index] = { ...quickLinks[index], [field]: value };
this._config = {
...this._config,
quick_links: quickLinks,
};
}
private _removeQuickLink(index: number): void {
const quickLinks = [...(this._config?.quick_links || [])];
quickLinks.splice(index, 1);
this._config = {
...this._config,
quick_links: quickLinks.length > 0 ? quickLinks : undefined,
};
}
private _addQuickLink(): void {
const quickLinks = [...(this._config?.quick_links || [])];
quickLinks.push({ name: "", icon: "mdi:link", navigation_path: "" });
this._config = {
...this._config,
quick_links: quickLinks,
};
}
private _favoriteEntitiesChanged(ev: CustomEvent): void {
const entities = ev.detail.value as string[];
this._config = {
@@ -148,6 +314,63 @@ export class DialogEditHome
color: var(--secondary-text-color);
}
.section-header {
font-size: 16px;
font-weight: 500;
margin: var(--ha-space-6) 0 var(--ha-space-1) 0;
}
.section-description {
margin: 0 0 var(--ha-space-2) 0;
color: var(--secondary-text-color);
font-size: 14px;
}
.summary-toggles {
display: flex;
flex-direction: column;
gap: var(--ha-space-2);
}
.summary-toggle {
display: flex;
align-items: center;
gap: var(--ha-space-3);
padding: var(--ha-space-1) 0;
cursor: pointer;
}
.quick-links {
display: flex;
flex-direction: column;
gap: var(--ha-space-3);
}
.quick-link-row {
display: flex;
align-items: center;
gap: var(--ha-space-2);
}
.quick-link-row ha-icon-picker {
width: 80px;
flex-shrink: 0;
}
.quick-link-row ha-textfield {
flex: 1;
min-width: 0;
}
.quick-link-row ha-navigation-picker {
flex: 2;
min-width: 0;
}
.quick-link-row ha-icon-button {
flex-shrink: 0;
}
ha-entities-picker {
display: block;
}

View File

@@ -318,6 +318,8 @@ class PanelHome extends LitElement {
type: "home",
favorite_entities: this._config.favorite_entities,
home_panel: true,
hidden_summaries: this._config.hidden_summaries,
quick_links: this._config.quick_links,
},
};

View File

@@ -17,6 +17,8 @@ export interface HomeDashboardStrategyConfig {
type: "home";
favorite_entities?: string[];
home_panel?: boolean;
hidden_summaries?: string[];
quick_links?: { name: string; icon?: string; navigation_path: string }[];
}
@customElement("home-dashboard-strategy")
@@ -94,6 +96,8 @@ export class HomeDashboardStrategy extends ReactiveElement {
type: "home-overview",
favorite_entities: config.favorite_entities,
home_panel: config.home_panel,
hidden_summaries: config.hidden_summaries,
quick_links: config.quick_links,
} satisfies HomeOverviewViewStrategyConfig,
},
...areaViews,

View File

@@ -39,6 +39,8 @@ export interface HomeOverviewViewStrategyConfig {
type: "home-overview";
favorite_entities?: string[];
home_panel?: boolean;
hidden_summaries?: string[];
quick_links?: { name: string; icon?: string; navigation_path: string }[];
}
const computeAreaCard = (
@@ -254,6 +256,8 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
) ??
false);
const hiddenSummaries = new Set(config.hidden_summaries || []);
// Build summary cards (used in both mobile section and sidebar)
const summaryCards: LovelaceCardConfig[] = [
// Repairs card - only visible to admins, hides when empty
@@ -280,6 +284,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
hide_empty: true,
} satisfies DiscoveredDevicesCardConfig,
hasLights &&
!hiddenSummaries.has("light") &&
({
type: "home-summary",
summary: "light",
@@ -289,6 +294,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
},
} satisfies HomeSummaryCard),
hasClimate &&
!hiddenSummaries.has("climate") &&
({
type: "home-summary",
summary: "climate",
@@ -298,6 +304,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
},
} satisfies HomeSummaryCard),
hasSecurity &&
!hiddenSummaries.has("security") &&
({
type: "home-summary",
summary: "security",
@@ -307,6 +314,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
},
} satisfies HomeSummaryCard),
hasMediaPlayers &&
!hiddenSummaries.has("media_players") &&
({
type: "home-summary",
summary: "media_players",
@@ -316,6 +324,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
},
} satisfies HomeSummaryCard),
weatherEntity &&
!hiddenSummaries.has("weather") &&
({
type: "tile",
entity: weatherEntity,
@@ -325,6 +334,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
state_content: ["temperature", "state"],
} satisfies TileCardConfig),
hasEnergy &&
!hiddenSummaries.has("energy") &&
({
type: "home-summary",
summary: "energy",
@@ -335,6 +345,21 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
: "/energy?historyBack=1",
},
} satisfies HomeSummaryCard),
// Quick links (chip-like cards without summary line)
...(config.quick_links || []).map(
(link) =>
({
type: "tile",
entity: "zone.home",
name: link.name,
icon: link.icon || "mdi:link",
hide_state: true,
tap_action: {
action: "navigate",
navigation_path: link.navigation_path,
},
}) satisfies TileCardConfig
),
].filter(Boolean) as LovelaceCardConfig[];
// Build summary cards for sidebar (full width: columns 12)

View File

@@ -2444,7 +2444,15 @@
"favorite_entities_helper": "Display your favorite entities. Home Assistant will still suggest based on commonly used up to 8 slots.",
"save_failed": "Failed to save Overview page configuration",
"areas_hint": "You can rearrange your floors and areas in the order that best represents your house on the {areas_page}.",
"areas_page": "areas page"
"areas_page": "areas page",
"summaries": "Summaries",
"summaries_description": "Choose which summaries to show on your overview page.",
"quick_links": "Quick links",
"quick_links_description": "Add links to dashboards, sidebar items, or other pages.",
"add_quick_link": "Add quick link",
"quick_link_name": "Name",
"quick_link_icon": "Icon",
"quick_link_path": "Navigation path"
},
"new_overview_dialog": {
"title": "Welcome to your new overview",