Skip to content

Commit 364c793

Browse files
authored
Support suggested name and icon for dashboards, add to map (#51592)
1 parent 99f36e1 commit 364c793

File tree

12 files changed

+205
-17
lines changed

12 files changed

+205
-17
lines changed

src/data/lovelace/config/strategy.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,21 @@ export interface LovelaceStrategyConfig {
22
type: string;
33
[key: string]: any;
44
}
5+
6+
/** Must stay aligned with `STRATEGIES.dashboard` in `panels/lovelace/strategies/get-strategy.ts`. */
7+
export const LOVELACE_BUILTIN_DASHBOARD_STRATEGY_TYPES = [
8+
"original-states",
9+
"map",
10+
"iframe",
11+
"areas",
12+
"home",
13+
"energy",
14+
] as const;
15+
16+
export type LovelaceBuiltinDashboardStrategyType =
17+
(typeof LOVELACE_BUILTIN_DASHBOARD_STRATEGY_TYPES)[number];
18+
19+
/** Dashboard strategy id from the new-dashboard picker: built-in key or `custom:…`. */
20+
export type LovelaceDashboardStrategyTypeId =
21+
| LovelaceBuiltinDashboardStrategyType
22+
| `custom:${string}`;

src/data/lovelace/dashboard.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ export interface LovelaceDashboardCreateParams extends LovelaceDashboardMutableP
3434
mode: "storage";
3535
}
3636

37+
/** Optional suggested values for dashboard creation (for example from a strategy). */
38+
export interface LovelaceDashboardSuggestions {
39+
title?: string;
40+
icon?: string;
41+
}
42+
3743
export const fetchDashboards = (
3844
hass: HomeAssistant
3945
): Promise<LovelaceDashboard[]> =>

src/panels/config/dashboard/dialog-new-dashboard.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
type CustomStrategyEntry,
2020
} from "../../../data/lovelace_custom_strategies";
2121
import { fetchResources } from "../../../data/lovelace/resource";
22+
import type { LovelaceDashboardStrategyTypeId } from "../../../data/lovelace/config/strategy";
2223
import type {
2324
LovelaceConfig,
2425
LovelaceDashboardStrategyConfig,
@@ -345,13 +346,15 @@ class DialogNewDashboard extends LitElement implements HassDialog {
345346
return;
346347
}
347348

349+
let strategyType: LovelaceDashboardStrategyTypeId | undefined;
348350
if (target.config) {
349351
config = target.config;
350352
} else if (target.strategy) {
353+
strategyType = target.strategy as LovelaceDashboardStrategyTypeId;
351354
config = this._generateStrategyConfig(target.strategy);
352355
}
353356

354-
await this._params?.selectConfig(config);
357+
await this._params?.selectConfig({ config, strategyType });
355358
this.closeDialog();
356359
}
357360

src/panels/config/dashboard/show-dialog-new-dashboard.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { fireEvent } from "../../../common/dom/fire_event";
2+
import type { LovelaceDashboardStrategyTypeId } from "../../../data/lovelace/config/strategy";
23
import type { LovelaceRawConfig } from "../../../data/lovelace/config/types";
34

5+
/** Payload when the user picks a dashboard template in the new-dashboard dialog. */
6+
export interface NewDashboardSelection {
7+
config: LovelaceRawConfig | undefined;
8+
/** Strategy id when the user chose a strategy (built-in or custom); omitted for an empty dashboard. */
9+
strategyType?: LovelaceDashboardStrategyTypeId;
10+
}
11+
412
export interface NewDashboardDialogParams {
5-
selectConfig: (config: LovelaceRawConfig | undefined) => void | Promise<void>;
13+
selectConfig: (selection: NewDashboardSelection) => void | Promise<void>;
614
}
715

816
export const loadNewDashboardDialog = () => import("./dialog-new-dashboard");

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
import { haStyleDialog } from "../../../../resources/styles";
1818
import type { HomeAssistant } from "../../../../types";
1919
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
20+
import { pickAvailableDashboardUrlPath } from "./pick-available-dashboard-url-path";
2021

2122
@customElement("dialog-lovelace-dashboard-detail")
2223
export class DialogLovelaceDashboardDetail extends LitElement {
@@ -42,13 +43,17 @@ export class DialogLovelaceDashboardDetail extends LitElement {
4243
if (this._params.dashboard) {
4344
this._data = this._params.dashboard;
4445
} else {
46+
const suggestions = this._params.suggestions;
4547
this._data = {
4648
show_in_sidebar: true,
47-
icon: undefined,
48-
title: "",
49+
icon: suggestions?.icon,
50+
title: suggestions?.title ?? "",
4951
require_admin: false,
5052
mode: "storage",
5153
};
54+
if (suggestions?.title) {
55+
this._fillUrlPath(suggestions.title);
56+
}
5257
}
5358
}
5459

@@ -255,11 +260,16 @@ export class DialogLovelaceDashboardDetail extends LitElement {
255260
}
256261

257262
const slugifyTitle = slugify(title, "-");
263+
const baseSlug = slugifyTitle.includes("-")
264+
? slugifyTitle
265+
: `dashboard-${slugifyTitle}`;
266+
const taken = this._params?.takenUrlPaths;
258267
this._data = {
259268
...this._data,
260-
url_path: slugifyTitle.includes("-")
261-
? slugifyTitle
262-
: `dashboard-${slugifyTitle}`,
269+
url_path:
270+
taken !== undefined
271+
? pickAvailableDashboardUrlPath(baseSlug, taken)
272+
: baseSlug,
263273
};
264274
}
265275

src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { fetchResources } from "../../../../data/lovelace/resource";
3939
import type {
4040
LovelaceDashboard,
4141
LovelaceDashboardCreateParams,
42+
LovelaceDashboardSuggestions,
4243
} from "../../../../data/lovelace/dashboard";
4344
import {
4445
createDashboard,
@@ -61,7 +62,8 @@ import "../../../../layouts/hass-loading-screen";
6162
import "../../../../layouts/hass-tabs-subpage-data-table";
6263
import type { HomeAssistant, Route } from "../../../../types";
6364
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
64-
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
65+
import { loadDashboardStrategyWithCreateSuggestions } from "../../../lovelace/strategies/get-strategy";
66+
import type { NewDashboardSelection } from "../../dashboard/show-dialog-new-dashboard";
6567
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
6668
import { lovelaceTabs } from "../ha-config-lovelace";
6769
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
@@ -565,40 +567,58 @@ export class HaConfigLovelaceDashboards extends LitElement {
565567

566568
private async _addDashboard() {
567569
showNewDashboardDialog(this, {
568-
selectConfig: async (config) => {
570+
selectConfig: async ({ config }: NewDashboardSelection) => {
571+
let fieldSuggestions: LovelaceDashboardSuggestions | undefined;
572+
569573
if (config && isStrategyDashboard(config)) {
570-
const strategyType = config.strategy.type;
571-
const strategyClass = await getLovelaceStrategy(
572-
"dashboard",
573-
strategyType
574-
);
574+
const { strategyClass, fieldSuggestions: suggested } =
575+
await loadDashboardStrategyWithCreateSuggestions(
576+
this.hass,
577+
config.strategy.type
578+
);
579+
fieldSuggestions = suggested;
575580

576581
if (strategyClass.configRequired) {
577582
showDashboardConfigureStrategyDialog(this, {
578583
config: config,
579584
saveConfig: async (updatedConfig) => {
580-
this._openDetailDialog(undefined, undefined, updatedConfig);
585+
const { fieldSuggestions: afterConfigure } =
586+
await loadDashboardStrategyWithCreateSuggestions(
587+
this.hass,
588+
updatedConfig.strategy.type
589+
);
590+
this._openDetailDialog(
591+
undefined,
592+
undefined,
593+
updatedConfig,
594+
afterConfigure
595+
);
581596
},
582597
});
583598
return;
584599
}
585600
}
586601

587-
this._openDetailDialog(undefined, undefined, config);
602+
this._openDetailDialog(undefined, undefined, config, fieldSuggestions);
588603
},
589604
});
590605
}
591606

592607
private async _openDetailDialog(
593608
dashboard?: LovelaceDashboard,
594609
urlPath?: string,
595-
defaultConfig?: LovelaceRawConfig
610+
defaultConfig?: LovelaceRawConfig,
611+
fieldSuggestions?: LovelaceDashboardSuggestions
596612
): Promise<void> {
597613
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
598614
showDashboardDetailDialog(this, {
599615
dashboard,
600616
urlPath,
601617
isDefault: dashboard?.url_path === defaultPanel,
618+
suggestions: fieldSuggestions,
619+
takenUrlPaths: dashboard
620+
? undefined
621+
: this._collectTakenDashboardUrlPaths(),
602622
createDashboard: async (values: LovelaceDashboardCreateParams) => {
603623
const created = await createDashboard(this.hass!, values);
604624
this._dashboards = this._dashboards!.concat(created).sort(
@@ -632,6 +652,18 @@ export class HaConfigLovelaceDashboards extends LitElement {
632652
});
633653
}
634654

655+
private _collectTakenDashboardUrlPaths(): ReadonlySet<string> {
656+
const taken = new Set<string>();
657+
for (const d of this._dashboards ?? []) {
658+
taken.add(d.url_path);
659+
}
660+
for (const path of Object.keys(this.hass.panels)) {
661+
taken.add(path);
662+
}
663+
taken.add("lovelace");
664+
return taken;
665+
}
666+
635667
private async _deleteDashboard(
636668
dashboard: LovelaceDashboard
637669
): Promise<boolean> {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Returns `baseSlug` if it is not in `taken`, otherwise `baseSlug-2`, `baseSlug-3`, …
3+
*/
4+
export function pickAvailableDashboardUrlPath(
5+
baseSlug: string,
6+
taken: ReadonlySet<string>
7+
): string {
8+
if (!taken.has(baseSlug)) {
9+
return baseSlug;
10+
}
11+
let n = 2;
12+
while (taken.has(`${baseSlug}-${n}`)) {
13+
n += 1;
14+
}
15+
return `${baseSlug}-${n}`;
16+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ import { fireEvent } from "../../../../common/dom/fire_event";
22
import type {
33
LovelaceDashboard,
44
LovelaceDashboardCreateParams,
5+
LovelaceDashboardSuggestions,
56
LovelaceDashboardMutableParams,
67
} from "../../../../data/lovelace/dashboard";
78

89
export interface LovelaceDashboardDetailsDialogParams {
910
dashboard?: LovelaceDashboard;
1011
urlPath?: string;
1112
isDefault?: boolean;
13+
/** Create flow only; optional suggested values for the form. */
14+
suggestions?: LovelaceDashboardSuggestions;
15+
/**
16+
* Create flow only; reserved url paths (dashboards, panels, and so on) so
17+
* auto-generated paths avoid collisions by appending -2, -3, and so on.
18+
*/
19+
takenUrlPaths?: ReadonlySet<string>;
1220
createDashboard?: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
1321
updateDashboard: (
1422
updates: Partial<LovelaceDashboardMutableParams>

src/panels/lovelace/strategies/get-strategy.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import type {
1515
LovelaceViewConfig,
1616
} from "../../../data/lovelace/config/view";
1717
import { isStrategyView } from "../../../data/lovelace/config/view";
18+
import type { LovelaceDashboardSuggestions } from "../../../data/lovelace/dashboard";
1819
import type { AsyncReturnType, HomeAssistant } from "../../../types";
1920
import { cleanLegacyStrategyConfig, isLegacyStrategy } from "./legacy-strategy";
2021
import type {
2122
LovelaceDashboardStrategy,
23+
LovelaceDashboardStrategyGetCreateSuggestions,
2224
LovelaceSectionStrategy,
2325
LovelaceStrategy,
2426
LovelaceViewStrategy,
@@ -273,3 +275,37 @@ export const expandLovelaceConfigStrategies = async (
273275

274276
return newConfig;
275277
};
278+
279+
interface DashboardStrategyClassWithSuggestions extends LovelaceDashboardStrategy {
280+
getCreateSuggestions?: LovelaceDashboardStrategyGetCreateSuggestions;
281+
}
282+
283+
async function readDashboardCreateSuggestions(
284+
hass: HomeAssistant,
285+
strategyClass: DashboardStrategyClassWithSuggestions
286+
): Promise<LovelaceDashboardSuggestions | undefined> {
287+
const fn = strategyClass.getCreateSuggestions;
288+
if (typeof fn !== "function") {
289+
return undefined;
290+
}
291+
return fn.call(strategyClass, hass);
292+
}
293+
294+
/** Loads a dashboard strategy and any optional create-dialog field suggestions. */
295+
export async function loadDashboardStrategyWithCreateSuggestions(
296+
hass: HomeAssistant,
297+
strategyType: string
298+
): Promise<{
299+
strategyClass: LovelaceDashboardStrategy;
300+
fieldSuggestions: LovelaceDashboardSuggestions | undefined;
301+
}> {
302+
const strategyClass = (await getLovelaceStrategy(
303+
"dashboard",
304+
strategyType
305+
)) as LovelaceDashboardStrategy;
306+
const fieldSuggestions = await readDashboardCreateSuggestions(
307+
hass,
308+
strategyClass
309+
);
310+
return { strategyClass, fieldSuggestions };
311+
}

src/panels/lovelace/strategies/map/map-dashboard-strategy.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ReactiveElement } from "lit";
22
import { customElement } from "lit/decorators";
3+
import type { LovelaceDashboardSuggestions } from "../../../../data/lovelace/dashboard";
34
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
5+
import type { HomeAssistant } from "../../../../types";
46
import type { MapViewStrategyConfig } from "./map-view-strategy";
57

68
export type MapDashboardStrategyConfig = MapViewStrategyConfig;
@@ -20,6 +22,15 @@ export class MapDashboardStrategy extends ReactiveElement {
2022
}
2123

2224
static noEditor = true;
25+
26+
static getCreateSuggestions(
27+
hass: HomeAssistant
28+
): LovelaceDashboardSuggestions {
29+
return {
30+
title: hass.localize("panel.map"),
31+
icon: "mdi:map",
32+
};
33+
}
2334
}
2435

2536
declare global {

0 commit comments

Comments
 (0)