mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-22 17:27:17 +00:00
Compare commits
9 Commits
analytics-
...
default_pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16eab41d7f | ||
|
|
6a99558237 | ||
|
|
c6b65cf4cd | ||
|
|
000e1101c4 | ||
|
|
785e6e4e86 | ||
|
|
0735d3614c | ||
|
|
e3f243eb6b | ||
|
|
e09f69eeb3 | ||
|
|
fb15c7f87e |
@@ -66,7 +66,7 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
.path=${item.path}
|
.path=${item.path}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
${item.label}
|
${item.label}
|
||||||
</ha-md-menu-item> `
|
</ha-md-menu-item>`
|
||||||
)}
|
)}
|
||||||
</ha-md-button-menu>`
|
</ha-md-button-menu>`
|
||||||
: html`
|
: html`
|
||||||
@@ -103,6 +103,7 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
cursor: initial;
|
||||||
}
|
}
|
||||||
div[role="separator"] {
|
div[role="separator"] {
|
||||||
border-right: 1px solid var(--divider-color);
|
border-right: 1px solid var(--divider-color);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface DisplayItem {
|
|||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
disableSorting?: boolean;
|
disableSorting?: boolean;
|
||||||
|
disableHiding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DisplayValue {
|
export interface DisplayValue {
|
||||||
@@ -101,6 +102,7 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
icon,
|
icon,
|
||||||
iconPath,
|
iconPath,
|
||||||
disableSorting,
|
disableSorting,
|
||||||
|
disableHiding,
|
||||||
} = item;
|
} = item;
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@@ -155,7 +157,8 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-icon-button
|
${isVisible && !disableHiding
|
||||||
|
? html`<ha-icon-button
|
||||||
.path=${isVisible ? mdiEye : mdiEyeOff}
|
.path=${isVisible ? mdiEye : mdiEyeOff}
|
||||||
slot="end"
|
slot="end"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -166,7 +169,9 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.value=${value}
|
.value=${value}
|
||||||
@click=${this._toggle}
|
@click=${this._toggle}
|
||||||
></ha-icon-button>
|
.disabled=${disableHiding || false}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: nothing}
|
||||||
${isVisible && !disableSorting
|
${isVisible && !disableSorting
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ export class HaMdMenuItem extends MenuItemEl {
|
|||||||
::slotted([slot="headline"]) {
|
::slotted([slot="headline"]) {
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) {
|
||||||
|
opacity: 1;
|
||||||
|
--md-menu-item-label-text-color: var(--disabled-text-color);
|
||||||
|
--md-menu-item-leading-icon-color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { titleCase } from "../common/string/title-case";
|
import { titleCase } from "../common/string/title-case";
|
||||||
import { fetchConfig } from "../data/lovelace/config/types";
|
import { fetchConfig } from "../data/lovelace/config/types";
|
||||||
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
||||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
import { getPanelIcon, getPanelTitle } from "../data/panel";
|
||||||
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
import type { HaComboBox } from "./ha-combo-box";
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
@@ -43,13 +43,8 @@ const createViewNavigationItem = (
|
|||||||
|
|
||||||
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
||||||
path: `/${panel.url_path}`,
|
path: `/${panel.url_path}`,
|
||||||
icon: panel.icon ?? "mdi:view-dashboard",
|
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||||
title:
|
title: getPanelTitle(hass, panel) || "",
|
||||||
panel.url_path === getDefaultPanelUrlPath(hass)
|
|
||||||
? hass.localize("panel.states")
|
|
||||||
: hass.localize(`panel.${panel.title}`) ||
|
|
||||||
panel.title ||
|
|
||||||
(panel.url_path ? titleCase(panel.url_path) : ""),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@customElement("ha-navigation-picker")
|
@customElement("ha-navigation-picker")
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
|
||||||
mdiCellphoneCog,
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
|
||||||
mdiClipboardList,
|
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiFormatListBulletedType,
|
|
||||||
mdiHammer,
|
|
||||||
mdiLightningBolt,
|
|
||||||
mdiMenu,
|
mdiMenu,
|
||||||
mdiMenuOpen,
|
mdiMenuOpen,
|
||||||
mdiPlayBoxMultiple,
|
|
||||||
mdiTooltipAccount,
|
|
||||||
mdiViewDashboard,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
eventOptions,
|
eventOptions,
|
||||||
@@ -33,7 +24,14 @@ import { computeRTL } from "../common/util/compute_rtl";
|
|||||||
import { throttle } from "../common/util/throttle";
|
import { throttle } from "../common/util/throttle";
|
||||||
import { subscribeFrontendUserData } from "../data/frontend";
|
import { subscribeFrontendUserData } from "../data/frontend";
|
||||||
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
import {
|
||||||
|
FIXED_PANELS,
|
||||||
|
getDefaultPanelUrlPath,
|
||||||
|
getPanelIcon,
|
||||||
|
getPanelIconPath,
|
||||||
|
getPanelTitle,
|
||||||
|
SHOW_AFTER_SPACER_PANELS,
|
||||||
|
} from "../data/panel";
|
||||||
import type { PersistentNotification } from "../data/persistent_notification";
|
import type { PersistentNotification } from "../data/persistent_notification";
|
||||||
import { subscribeNotifications } from "../data/persistent_notification";
|
import { subscribeNotifications } from "../data/persistent_notification";
|
||||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||||
@@ -54,8 +52,6 @@ import "./ha-spinner";
|
|||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./user/ha-user-badge";
|
import "./user/ha-user-badge";
|
||||||
|
|
||||||
const SHOW_AFTER_SPACER = ["config", "developer-tools"];
|
|
||||||
|
|
||||||
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
|
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
|
||||||
|
|
||||||
const SORT_VALUE_URL_PATHS = {
|
const SORT_VALUE_URL_PATHS = {
|
||||||
@@ -67,18 +63,6 @@ const SORT_VALUE_URL_PATHS = {
|
|||||||
config: 11,
|
config: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PANEL_ICONS = {
|
|
||||||
calendar: mdiCalendar,
|
|
||||||
"developer-tools": mdiHammer,
|
|
||||||
energy: mdiLightningBolt,
|
|
||||||
history: mdiChartBox,
|
|
||||||
logbook: mdiFormatListBulletedType,
|
|
||||||
lovelace: mdiViewDashboard,
|
|
||||||
map: mdiTooltipAccount,
|
|
||||||
"media-browser": mdiPlayBoxMultiple,
|
|
||||||
todo: mdiClipboardList,
|
|
||||||
};
|
|
||||||
|
|
||||||
const panelSorter = (
|
const panelSorter = (
|
||||||
reverseSort: string[],
|
reverseSort: string[],
|
||||||
defaultPanel: string,
|
defaultPanel: string,
|
||||||
@@ -155,16 +139,23 @@ export const computePanels = memoizeOne(
|
|||||||
const beforeSpacer: PanelInfo[] = [];
|
const beforeSpacer: PanelInfo[] = [];
|
||||||
const afterSpacer: PanelInfo[] = [];
|
const afterSpacer: PanelInfo[] = [];
|
||||||
|
|
||||||
Object.values(panels).forEach((panel) => {
|
const allPanels = Object.values(panels).filter(
|
||||||
|
(panel) => !FIXED_PANELS.includes(panel.url_path)
|
||||||
|
);
|
||||||
|
|
||||||
|
allPanels.forEach((panel) => {
|
||||||
|
const isDefaultPanel = panel.url_path === defaultPanel;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
!isDefaultPanel &&
|
||||||
|
(!panel.title ||
|
||||||
hiddenPanels.includes(panel.url_path) ||
|
hiddenPanels.includes(panel.url_path) ||
|
||||||
(!panel.title && panel.url_path !== defaultPanel) ||
|
|
||||||
(panel.default_visible === false &&
|
(panel.default_visible === false &&
|
||||||
!panelsOrder.includes(panel.url_path))
|
!panelsOrder.includes(panel.url_path)))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(SHOW_AFTER_SPACER.includes(panel.url_path)
|
(SHOW_AFTER_SPACER_PANELS.includes(panel.url_path)
|
||||||
? afterSpacer
|
? afterSpacer
|
||||||
: beforeSpacer
|
: beforeSpacer
|
||||||
).push(panel);
|
).push(panel);
|
||||||
@@ -252,9 +243,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show the supervisor as being part of configuration
|
// Show the supervisor as being part of configuration
|
||||||
const selectedPanel = this.route.path?.startsWith("/hassio/")
|
const selectedPanel = this.hass.panelUrl;
|
||||||
? "config"
|
|
||||||
: this.hass.panelUrl;
|
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return html`
|
return html`
|
||||||
@@ -397,9 +386,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
private _renderAllPanels(selectedPanel: string) {
|
private _renderAllPanels(selectedPanel: string) {
|
||||||
if (!this._panelOrder || !this._hiddenPanels) {
|
if (!this._panelOrder || !this._hiddenPanels) {
|
||||||
return html`
|
return html`
|
||||||
<ha-fade-in .delay=${500}
|
<ha-fade-in .delay=${500}>
|
||||||
><ha-spinner size="small"></ha-spinner
|
<ha-spinner size="small"></ha-spinner>
|
||||||
></ha-fade-in>
|
</ha-fade-in>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +402,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list
|
<ha-md-list
|
||||||
class="ha-scrollbar"
|
class="ha-scrollbar"
|
||||||
@@ -422,52 +410,33 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
@scroll=${this._listboxScroll}
|
@scroll=${this._listboxScroll}
|
||||||
@keydown=${this._listboxKeydown}
|
@keydown=${this._listboxKeydown}
|
||||||
>
|
>
|
||||||
${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)}
|
${this._renderPanels(beforeSpacer, selectedPanel)}
|
||||||
${this._renderSpacer()}
|
${this._renderSpacer()}
|
||||||
${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)}
|
${this._renderPanels(afterSpacer, selectedPanel)}
|
||||||
${this._renderExternalConfiguration()}
|
${this.hass.user?.is_admin
|
||||||
|
? this._renderConfiguration(selectedPanel)
|
||||||
|
: this._renderExternalConfiguration()}
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanels(
|
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
|
||||||
panels: PanelInfo[],
|
|
||||||
selectedPanel: string,
|
|
||||||
defaultPanel: string
|
|
||||||
) {
|
|
||||||
return panels.map((panel) =>
|
return panels.map((panel) =>
|
||||||
this._renderPanel(
|
this._renderPanel(panel, panel.url_path === selectedPanel)
|
||||||
panel.url_path,
|
|
||||||
panel.url_path === defaultPanel
|
|
||||||
? panel.title || this.hass.localize("panel.states")
|
|
||||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
|
||||||
panel.icon,
|
|
||||||
panel.url_path === defaultPanel && !panel.icon
|
|
||||||
? PANEL_ICONS.lovelace
|
|
||||||
: panel.url_path in PANEL_ICONS
|
|
||||||
? PANEL_ICONS[panel.url_path]
|
|
||||||
: undefined,
|
|
||||||
selectedPanel
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanel(
|
private _renderPanel(panel: PanelInfo, isSelected: boolean) {
|
||||||
urlPath: string,
|
const title = getPanelTitle(this.hass, panel);
|
||||||
title: string | null,
|
const urlPath = panel.url_path;
|
||||||
icon: string | null | undefined,
|
const icon = getPanelIcon(panel);
|
||||||
iconPath: string | null | undefined,
|
const iconPath = getPanelIconPath(panel);
|
||||||
selectedPanel: string
|
|
||||||
) {
|
return html`
|
||||||
return urlPath === "config"
|
|
||||||
? this._renderConfiguration(title, selectedPanel)
|
|
||||||
: html`
|
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
.href=${`/${urlPath}`}
|
.href=${`/${urlPath}`}
|
||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class=${classMap({ selected: isSelected })}
|
||||||
selected: selectedPanel === urlPath,
|
|
||||||
})}
|
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@mouseleave=${this._itemMouseLeave}
|
@mouseleave=${this._itemMouseLeave}
|
||||||
>
|
>
|
||||||
@@ -487,10 +456,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
return html`<div class="spacer" disabled></div>`;
|
return html`<div class="spacer" disabled></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderConfiguration(title: string | null, selectedPanel: string) {
|
private _renderConfiguration(selectedPanel: string) {
|
||||||
|
if (!this.hass.user?.is_admin) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const isSelected =
|
||||||
|
selectedPanel === "config" || this.route.path?.startsWith("/hassio/");
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
class="configuration${selectedPanel === "config" ? " selected" : ""}"
|
class="configuration ${classMap({ selected: isSelected })}"
|
||||||
type="button"
|
type="button"
|
||||||
href="/config"
|
href="/config"
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@@ -504,15 +478,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
${this._updatesCount + this._issuesCount}
|
${this._updatesCount + this._issuesCount}
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<span class="item-text" slot="headline">${title}</span>
|
<span class="item-text" slot="headline"
|
||||||
|
>${this.hass.localize("panel.config")}</span
|
||||||
|
>
|
||||||
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
|
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
|
||||||
? html`
|
? html`
|
||||||
<span class="badge" slot="end"
|
<span class="badge" slot="end"
|
||||||
>${this._updatesCount + this._issuesCount}</span
|
>${this._updatesCount + this._issuesCount}</span
|
||||||
>
|
>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -535,19 +511,20 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
? html`
|
? html`
|
||||||
<span class="badge" slot="start"> ${notificationCount} </span>
|
<span class="badge" slot="start"> ${notificationCount} </span>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<span class="item-text" slot="headline"
|
<span class="item-text" slot="headline"
|
||||||
>${this.hass.localize("ui.notification_drawer.title")}</span
|
>${this.hass.localize("ui.notification_drawer.title")}</span
|
||||||
>
|
>
|
||||||
${this.alwaysExpand && notificationCount > 0
|
${this.alwaysExpand && notificationCount > 0
|
||||||
? html`<span class="badge" slot="end">${notificationCount}</span>`
|
? html`<span class="badge" slot="end">${notificationCount}</span>`
|
||||||
: ""}
|
: nothing}
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderUserItem(selectedPanel: string) {
|
private _renderUserItem(selectedPanel: string) {
|
||||||
const isRTL = computeRTL(this.hass);
|
const isRTL = computeRTL(this.hass);
|
||||||
|
const isSelected = selectedPanel === "profile";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@@ -555,7 +532,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
user: true,
|
user: true,
|
||||||
selected: selectedPanel === "profile",
|
selected: isSelected,
|
||||||
rtl: isRTL,
|
rtl: isRTL,
|
||||||
})}
|
})}
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@@ -566,18 +543,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
.user=${this.hass.user}
|
.user=${this.hass.user}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-user-badge>
|
></ha-user-badge>
|
||||||
|
<span class="item-text" slot="headline">
|
||||||
<span class="item-text" slot="headline"
|
${this.hass.user ? this.hass.user.name : ""}
|
||||||
>${this.hass.user ? this.hass.user.name : ""}</span
|
</span>
|
||||||
>
|
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderExternalConfiguration() {
|
private _renderExternalConfiguration() {
|
||||||
return html`${!this.hass.user?.is_admin &&
|
if (!this.hass.auth.external?.config.hasSettingsScreen) {
|
||||||
this.hass.auth.external?.config.hasSettingsScreen
|
return nothing;
|
||||||
? html`
|
}
|
||||||
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
@click=${this._handleExternalAppConfiguration}
|
@click=${this._handleExternalAppConfiguration}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -589,8 +566,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
||||||
</span>
|
</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`
|
`;
|
||||||
: ""}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleExternalAppConfiguration(ev: Event) {
|
private _handleExternalAppConfiguration(ev: Event) {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import type { Connection } from "home-assistant-js-websocket";
|
|||||||
export interface CoreFrontendUserData {
|
export interface CoreFrontendUserData {
|
||||||
showAdvanced?: boolean;
|
showAdvanced?: boolean;
|
||||||
showEntityIdPicker?: boolean;
|
showEntityIdPicker?: boolean;
|
||||||
default_panel?: string;
|
defaultPanel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarFrontendUserData {
|
export interface SidebarFrontendUserData {
|
||||||
panelOrder: string[];
|
panelOrder?: string[];
|
||||||
hiddenPanels: string[];
|
hiddenPanels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoreFrontendSystemData {
|
export interface CoreFrontendSystemData {
|
||||||
default_panel?: string;
|
defaultPanel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HomeFrontendSystemData {
|
export interface HomeFrontendSystemData {
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
import {
|
||||||
|
mdiAccount,
|
||||||
|
mdiCalendar,
|
||||||
|
mdiChartBox,
|
||||||
|
mdiClipboardList,
|
||||||
|
mdiFormatListBulletedType,
|
||||||
|
mdiHammer,
|
||||||
|
mdiLightningBolt,
|
||||||
|
mdiPlayBoxMultiple,
|
||||||
|
mdiTooltipAccount,
|
||||||
|
mdiViewDashboard,
|
||||||
|
} from "@mdi/js";
|
||||||
import type { HomeAssistant, PanelInfo } from "../types";
|
import type { HomeAssistant, PanelInfo } from "../types";
|
||||||
|
|
||||||
/** Panel to show when no panel is picked. */
|
/** Panel to show when no panel is picked. */
|
||||||
@@ -9,8 +21,8 @@ export const getLegacyDefaultPanelUrlPath = (): string | null => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
|
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
|
||||||
hass.userData?.default_panel ||
|
hass.userData?.defaultPanel ||
|
||||||
hass.systemData?.default_panel ||
|
hass.systemData?.defaultPanel ||
|
||||||
getLegacyDefaultPanelUrlPath() ||
|
getLegacyDefaultPanelUrlPath() ||
|
||||||
DEFAULT_PANEL;
|
DEFAULT_PANEL;
|
||||||
|
|
||||||
@@ -60,7 +72,7 @@ export const getPanelTitleFromUrlPath = (
|
|||||||
return getPanelTitle(hass, panel);
|
return getPanelTitle(hass, panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPanelIcon = (panel: PanelInfo): string | null => {
|
export const getPanelIcon = (panel: PanelInfo): string | undefined => {
|
||||||
if (!panel.icon) {
|
if (!panel.icon) {
|
||||||
switch (panel.component_name) {
|
switch (panel.component_name) {
|
||||||
case "profile":
|
case "profile":
|
||||||
@@ -70,5 +82,24 @@ export const getPanelIcon = (panel: PanelInfo): string | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return panel.icon;
|
return panel.icon || undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PANEL_ICON_PATHS = {
|
||||||
|
calendar: mdiCalendar,
|
||||||
|
"developer-tools": mdiHammer,
|
||||||
|
energy: mdiLightningBolt,
|
||||||
|
history: mdiChartBox,
|
||||||
|
logbook: mdiFormatListBulletedType,
|
||||||
|
lovelace: mdiViewDashboard,
|
||||||
|
profile: mdiAccount,
|
||||||
|
map: mdiTooltipAccount,
|
||||||
|
"media-browser": mdiPlayBoxMultiple,
|
||||||
|
todo: mdiClipboardList,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPanelIconPath = (panel: PanelInfo): string | undefined =>
|
||||||
|
PANEL_ICON_PATHS[panel.url_path];
|
||||||
|
|
||||||
|
export const FIXED_PANELS = ["profile", "config"];
|
||||||
|
export const SHOW_AFTER_SPACER_PANELS = ["developer-tools"];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose, mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||||
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -9,18 +9,30 @@ import "../../components/ha-dialog-header";
|
|||||||
import "../../components/ha-fade-in";
|
import "../../components/ha-fade-in";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-items-display-editor";
|
import "../../components/ha-items-display-editor";
|
||||||
import type { DisplayValue } from "../../components/ha-items-display-editor";
|
import type {
|
||||||
|
DisplayItem,
|
||||||
|
DisplayValue,
|
||||||
|
} from "../../components/ha-items-display-editor";
|
||||||
|
import "../../components/ha-md-button-menu";
|
||||||
import "../../components/ha-md-dialog";
|
import "../../components/ha-md-dialog";
|
||||||
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||||
import { computePanels, PANEL_ICONS } from "../../components/ha-sidebar";
|
import "../../components/ha-md-menu-item";
|
||||||
|
import { computePanels } from "../../components/ha-sidebar";
|
||||||
import "../../components/ha-spinner";
|
import "../../components/ha-spinner";
|
||||||
|
import "../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
fetchFrontendUserData,
|
fetchFrontendUserData,
|
||||||
saveFrontendUserData,
|
saveFrontendUserData,
|
||||||
} from "../../data/frontend";
|
} from "../../data/frontend";
|
||||||
|
import {
|
||||||
|
getDefaultPanelUrlPath,
|
||||||
|
getPanelIcon,
|
||||||
|
getPanelIconPath,
|
||||||
|
getPanelTitle,
|
||||||
|
SHOW_AFTER_SPACER_PANELS,
|
||||||
|
} from "../../data/panel";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||||
import { getDefaultPanelUrlPath } from "../../data/panel";
|
|
||||||
|
|
||||||
@customElement("dialog-edit-sidebar")
|
@customElement("dialog-edit-sidebar")
|
||||||
class DialogEditSidebar extends LitElement {
|
class DialogEditSidebar extends LitElement {
|
||||||
@@ -105,48 +117,53 @@ class DialogEditSidebar extends LitElement {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add default hidden panels that are missing in hidden
|
const orderSet = new Set(this._order);
|
||||||
|
const hiddenSet = new Set(this._hidden);
|
||||||
|
|
||||||
for (const panel of panels) {
|
for (const panel of panels) {
|
||||||
if (
|
if (
|
||||||
panel.default_visible === false &&
|
panel.default_visible === false &&
|
||||||
!this._order.includes(panel.url_path) &&
|
!orderSet.has(panel.url_path) &&
|
||||||
!this._hidden.includes(panel.url_path)
|
!hiddenSet.has(panel.url_path)
|
||||||
) {
|
) {
|
||||||
this._hidden.push(panel.url_path);
|
hiddenSet.add(panel.url_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hiddenSet.has(defaultPanel)) {
|
||||||
|
hiddenSet.delete(defaultPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hiddenPanels = Array.from(hiddenSet);
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
...beforeSpacer,
|
...beforeSpacer,
|
||||||
...panels.filter((panel) => this._hidden!.includes(panel.url_path)),
|
...panels.filter((panel) => hiddenPanels.includes(panel.url_path)),
|
||||||
...afterSpacer.filter((panel) => panel.url_path !== "config"),
|
...afterSpacer,
|
||||||
].map((panel) => ({
|
].map<DisplayItem>((panel) => ({
|
||||||
value: panel.url_path,
|
value: panel.url_path,
|
||||||
label:
|
label:
|
||||||
panel.url_path === defaultPanel
|
(getPanelTitle(this.hass, panel) || panel.url_path) +
|
||||||
? panel.title || this.hass.localize("panel.states")
|
`${defaultPanel === panel.url_path ? " (default)" : ""}`,
|
||||||
: this.hass.localize(`panel.${panel.title}`) || panel.title || "?",
|
icon: getPanelIcon(panel),
|
||||||
icon: panel.icon || undefined,
|
iconPath: getPanelIconPath(panel),
|
||||||
iconPath:
|
disableSorting: SHOW_AFTER_SPACER_PANELS.includes(panel.url_path),
|
||||||
panel.url_path === defaultPanel && !panel.icon
|
disableHiding: panel.url_path === defaultPanel,
|
||||||
? PANEL_ICONS.lovelace
|
|
||||||
: panel.url_path in PANEL_ICONS
|
|
||||||
? PANEL_ICONS[panel.url_path]
|
|
||||||
: undefined,
|
|
||||||
disableSorting: panel.url_path === "developer-tools",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return html`<ha-items-display-editor
|
return html`
|
||||||
|
<ha-items-display-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${{
|
.value=${{
|
||||||
order: this._order,
|
order: this._order,
|
||||||
hidden: this._hidden,
|
hidden: hiddenPanels,
|
||||||
}}
|
}}
|
||||||
.items=${items}
|
.items=${items}
|
||||||
@value-changed=${this._changed}
|
@value-changed=${this._changed}
|
||||||
dont-sort-visible
|
dont-sort-visible
|
||||||
>
|
>
|
||||||
</ha-items-display-editor>`;
|
</ha-items-display-editor>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -171,6 +188,22 @@ class DialogEditSidebar extends LitElement {
|
|||||||
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
|
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
|
||||||
>`
|
>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
<ha-md-button-menu
|
||||||
|
slot="actionItems"
|
||||||
|
positioning="popover"
|
||||||
|
anchor-corner="end-end"
|
||||||
|
menu-corner="start-end"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.sidebar.reset_to_defaults")}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
</ha-md-button-menu>
|
||||||
</ha-dialog-header>
|
</ha-dialog-header>
|
||||||
<div slot="content" class="content">${this._renderContent()}</div>
|
<div slot="content" class="content">${this._renderContent()}</div>
|
||||||
<div slot="actions">
|
<div slot="actions">
|
||||||
@@ -194,6 +227,26 @@ class DialogEditSidebar extends LitElement {
|
|||||||
this._hidden = [...hidden];
|
this._hidden = [...hidden];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _resetToDefaults = async () => {
|
||||||
|
const confirmation = await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize("ui.sidebar.reset_confirmation"),
|
||||||
|
confirmText: this.hass.localize("ui.common.reset"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._order = [];
|
||||||
|
this._hidden = [];
|
||||||
|
try {
|
||||||
|
await saveFrontendUserData(this.hass.connection, "sidebar", {});
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = err.message || err;
|
||||||
|
}
|
||||||
|
this.closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
private async _save() {
|
private async _save() {
|
||||||
if (this._migrateToUserData) {
|
if (this._migrateToUserData) {
|
||||||
const confirmation = await showConfirmationDialog(this, {
|
const confirmation = await showConfirmationDialog(this, {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
downloadFileSupported,
|
downloadFileSupported,
|
||||||
fileDownload,
|
fileDownload,
|
||||||
} from "../../../util/file_download";
|
} from "../../../util/file_download";
|
||||||
import "../../../components/ha-dropdown-item";
|
|
||||||
import "../../../components/ha-dropdown";
|
|
||||||
|
|
||||||
@customElement("ha-config-section-analytics")
|
@customElement("ha-config-section-analytics")
|
||||||
class HaConfigSectionAnalytics extends LitElement {
|
class HaConfigSectionAnalytics extends LitElement {
|
||||||
@@ -35,19 +33,22 @@ class HaConfigSectionAnalytics extends LitElement {
|
|||||||
>
|
>
|
||||||
${downloadFileSupported(this.hass)
|
${downloadFileSupported(this.hass)
|
||||||
? html`
|
? html`
|
||||||
<ha-dropdown
|
<ha-button-menu
|
||||||
@wa-select=${this._handleOverflowAction}
|
@action=${this._handleOverflowAction}
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
>
|
>
|
||||||
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
|
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
<ha-dropdown-item .value=${"download_device_info"}>
|
<ha-list-item graphic="icon">
|
||||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiDownload}
|
||||||
|
></ha-svg-icon>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.analytics.download_device_info"
|
"ui.panel.config.analytics.download_device_info"
|
||||||
)}
|
)}
|
||||||
</ha-dropdown-item>
|
</ha-list-item>
|
||||||
</ha-dropdown>
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -57,17 +58,10 @@ class HaConfigSectionAnalytics extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleOverflowAction(
|
private async _handleOverflowAction(): Promise<void> {
|
||||||
ev: CustomEvent<{ item: { value: string } }>
|
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
|
||||||
): Promise<void> {
|
|
||||||
if (ev.detail.item.value === "download_device_info") {
|
|
||||||
const signedPath = await getSignedPath(
|
|
||||||
this.hass,
|
|
||||||
"/api/analytics/devices"
|
|
||||||
);
|
|
||||||
fileDownload(signedPath.path);
|
fileDownload(signedPath.path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
color:
|
color:
|
||||||
route.route_status === "Active"
|
route.route_status === "Active"
|
||||||
? primaryColor
|
? primaryColor
|
||||||
: style.getPropertyValue("--dark-primary-color"),
|
: style.getPropertyValue("--disabled-color"),
|
||||||
type: ["Child", "Parent"].includes(neighbor.relationship)
|
type: ["Child", "Parent"].includes(neighbor.relationship)
|
||||||
? "solid"
|
? "solid"
|
||||||
: "dotted",
|
: "dotted",
|
||||||
@@ -335,7 +335,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
symbolSize: 5,
|
symbolSize: 5,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 1,
|
width: 1,
|
||||||
color: style.getPropertyValue("--dark-primary-color"),
|
color: style.getPropertyValue("--disabled-color"),
|
||||||
type: "dotted",
|
type: "dotted",
|
||||||
},
|
},
|
||||||
ignoreForceLayout: true,
|
ignoreForceLayout: true,
|
||||||
|
|||||||
@@ -8,16 +8,13 @@ import "../../../../components/ha-button";
|
|||||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||||
import "../../../../components/ha-form/ha-form";
|
import "../../../../components/ha-form/ha-form";
|
||||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||||
import { saveFrontendSystemData } from "../../../../data/frontend";
|
|
||||||
import type {
|
import type {
|
||||||
LovelaceDashboard,
|
LovelaceDashboard,
|
||||||
LovelaceDashboardCreateParams,
|
LovelaceDashboardCreateParams,
|
||||||
LovelaceDashboardMutableParams,
|
LovelaceDashboardMutableParams,
|
||||||
} from "../../../../data/lovelace/dashboard";
|
} from "../../../../data/lovelace/dashboard";
|
||||||
import { DEFAULT_PANEL } from "../../../../data/panel";
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers";
|
|
||||||
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
|
|
||||||
@customElement("dialog-lovelace-dashboard-detail")
|
@customElement("dialog-lovelace-dashboard-detail")
|
||||||
@@ -61,9 +58,8 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
|||||||
if (!this._params || !this._data) {
|
if (!this._params || !this._data) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
const defaultPanelUrlPath =
|
|
||||||
this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
|
||||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||||
|
const isLovelaceDashboard = this._params.urlPath === "lovelace";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
@@ -88,9 +84,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
|||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
||||||
)
|
)
|
||||||
: this._params.urlPath === "lovelace"
|
: isLovelaceDashboard
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
"ui.panel.config.lovelace.dashboards.cant_edit_default"
|
"ui.panel.config.lovelace.dashboards.cant_edit_lovelace"
|
||||||
)
|
)
|
||||||
: html`
|
: html`
|
||||||
<ha-form
|
<ha-form
|
||||||
@@ -119,24 +115,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<ha-button
|
|
||||||
slot="secondaryAction"
|
|
||||||
appearance="plain"
|
|
||||||
@click=${this._toggleDefault}
|
|
||||||
.disabled=${this._params.urlPath === "lovelace" &&
|
|
||||||
defaultPanelUrlPath === "lovelace"}
|
|
||||||
>
|
|
||||||
${this._params.urlPath === defaultPanelUrlPath
|
|
||||||
? this.hass.localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.detail.remove_default"
|
|
||||||
)
|
|
||||||
: this.hass.localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.detail.set_default"
|
|
||||||
)}
|
|
||||||
</ha-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
<ha-button
|
<ha-button
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
@click=${this._updateDashboard}
|
@click=${this._updateDashboard}
|
||||||
@@ -254,40 +235,6 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _toggleDefault() {
|
|
||||||
const urlPath = this._params?.urlPath;
|
|
||||||
if (!urlPath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultPanel = this.hass.systemData?.default_panel || 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,
|
|
||||||
default_panel: urlPath === defaultPanel ? undefined : urlPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _updateDashboard() {
|
private async _updateDashboard() {
|
||||||
if (this._params?.urlPath && !this._params.dashboard?.id) {
|
if (this._params?.urlPath && !this._params.dashboard?.id) {
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiCheckCircleOutline,
|
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
|
mdiHomeCircleOutline,
|
||||||
|
mdiHomeEdit,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@@ -10,7 +11,6 @@ import type { PropertyValues } from "lit";
|
|||||||
import { LitElement, html, nothing } from "lit";
|
import { LitElement, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { navigate } from "../../../../common/navigate";
|
import { navigate } from "../../../../common/navigate";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
@@ -29,6 +29,7 @@ import "../../../../components/ha-md-button-menu";
|
|||||||
import "../../../../components/ha-md-list-item";
|
import "../../../../components/ha-md-list-item";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import "../../../../components/ha-tooltip";
|
import "../../../../components/ha-tooltip";
|
||||||
|
import { saveFrontendSystemData } from "../../../../data/frontend";
|
||||||
import type { LovelacePanelConfig } from "../../../../data/lovelace";
|
import type { LovelacePanelConfig } from "../../../../data/lovelace";
|
||||||
import type { LovelaceRawConfig } from "../../../../data/lovelace/config/types";
|
import type { LovelaceRawConfig } from "../../../../data/lovelace/config/types";
|
||||||
import {
|
import {
|
||||||
@@ -45,7 +46,11 @@ import {
|
|||||||
fetchDashboards,
|
fetchDashboards,
|
||||||
updateDashboard,
|
updateDashboard,
|
||||||
} from "../../../../data/lovelace/dashboard";
|
} from "../../../../data/lovelace/dashboard";
|
||||||
import { DEFAULT_PANEL } from "../../../../data/panel";
|
import {
|
||||||
|
DEFAULT_PANEL,
|
||||||
|
getPanelIcon,
|
||||||
|
getPanelTitle,
|
||||||
|
} from "../../../../data/panel";
|
||||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../../layouts/hass-loading-screen";
|
import "../../../../layouts/hass-loading-screen";
|
||||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||||
@@ -56,12 +61,21 @@ import { lovelaceTabs } from "../ha-config-lovelace";
|
|||||||
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
|
|
||||||
|
export const PANEL_DASHBOARDS = [
|
||||||
|
"home",
|
||||||
|
"light",
|
||||||
|
"security",
|
||||||
|
"climate",
|
||||||
|
"energy",
|
||||||
|
] as string[];
|
||||||
|
|
||||||
type DataTableItem = Pick<
|
type DataTableItem = Pick<
|
||||||
LovelaceDashboard,
|
LovelaceDashboard,
|
||||||
"icon" | "title" | "show_in_sidebar" | "require_admin" | "mode" | "url_path"
|
"icon" | "title" | "show_in_sidebar" | "require_admin" | "mode" | "url_path"
|
||||||
> & {
|
> & {
|
||||||
default: boolean;
|
default: boolean;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
localized_type: string;
|
||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,7 +126,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
state: false,
|
state: false,
|
||||||
subscribe: false,
|
subscribe: false,
|
||||||
})
|
})
|
||||||
private _activeGrouping?: string = "type";
|
private _activeGrouping?: string = "localized_type";
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
key: "lovelace-dashboards-table-collapsed",
|
key: "lovelace-dashboards-table-collapsed",
|
||||||
@@ -167,7 +181,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.id="default-icon-${dashboard.title}"
|
.id="default-icon-${dashboard.title}"
|
||||||
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
|
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
|
||||||
.path=${mdiCheckCircleOutline}
|
.path=${mdiHomeCircleOutline}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<ha-tooltip
|
<ha-tooltip
|
||||||
.for="default-icon-${dashboard.title}"
|
.for="default-icon-${dashboard.title}"
|
||||||
@@ -183,7 +197,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.type = {
|
columns.localized_type = {
|
||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.type"
|
"ui.panel.config.lovelace.dashboards.picker.headers.type"
|
||||||
),
|
),
|
||||||
@@ -253,7 +267,15 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
narrow
|
narrow
|
||||||
.items=${[
|
.items=${[
|
||||||
...(this._canEdit(dashboard.url_path)
|
{
|
||||||
|
path: mdiHomeEdit,
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.set_as_default"
|
||||||
|
),
|
||||||
|
action: () => this._handleSetAsDefault(dashboard),
|
||||||
|
disabled: dashboard.default,
|
||||||
|
},
|
||||||
|
...(dashboard.type === "user_created"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
path: mdiPencil,
|
path: mdiPencil,
|
||||||
@@ -262,10 +284,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
),
|
),
|
||||||
action: () => this._handleEdit(dashboard),
|
action: () => this._handleEdit(dashboard),
|
||||||
},
|
},
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(this._canDelete(dashboard.url_path)
|
|
||||||
? [
|
|
||||||
{
|
{
|
||||||
label: this.hass.localize(
|
label: this.hass.localize(
|
||||||
"ui.panel.config.lovelace.dashboards.picker.delete"
|
"ui.panel.config.lovelace.dashboards.picker.delete"
|
||||||
@@ -288,92 +306,43 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
|
|
||||||
private _getItems = memoize(
|
private _getItems = memoize(
|
||||||
(dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => {
|
(dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => {
|
||||||
const defaultMode = (
|
const mode = (this.hass.panels?.lovelace?.config as LovelacePanelConfig)
|
||||||
this.hass.panels?.lovelace?.config as LovelacePanelConfig
|
.mode;
|
||||||
).mode;
|
|
||||||
const isDefault = defaultUrlPath === "lovelace";
|
const isDefault = defaultUrlPath === "lovelace";
|
||||||
const result: DataTableItem[] = [
|
const result: DataTableItem[] = [
|
||||||
{
|
{
|
||||||
icon: "mdi:view-dashboard",
|
icon: "mdi:view-dashboard",
|
||||||
title: this.hass.localize("panel.states"),
|
title: this.hass.localize("panel.states"),
|
||||||
default: isDefault,
|
default: isDefault,
|
||||||
show_in_sidebar: isDefault,
|
show_in_sidebar: true,
|
||||||
require_admin: false,
|
require_admin: false,
|
||||||
url_path: "lovelace",
|
url_path: "lovelace",
|
||||||
mode: defaultMode,
|
mode: mode,
|
||||||
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
|
filename: mode === "yaml" ? "ui-lovelace.yaml" : "",
|
||||||
type: this._localizeType("built_in"),
|
type: "built_in",
|
||||||
|
localized_type: this._localizeType("built_in"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (isComponentLoaded(this.hass, "energy")) {
|
|
||||||
result.push({
|
|
||||||
icon: "mdi:lightning-bolt",
|
|
||||||
title: this.hass.localize(`ui.panel.config.dashboard.energy.main`),
|
|
||||||
show_in_sidebar: true,
|
|
||||||
mode: "storage",
|
|
||||||
url_path: "energy",
|
|
||||||
filename: "",
|
|
||||||
default: false,
|
|
||||||
require_admin: false,
|
|
||||||
type: this._localizeType("built_in"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hass.panels.light) {
|
PANEL_DASHBOARDS.forEach((panel) => {
|
||||||
result.push({
|
const panelInfo = this.hass.panels[panel];
|
||||||
icon: this.hass.panels.light.icon || "mdi:lamps",
|
if (!panel) {
|
||||||
title: this.hass.localize("panel.light"),
|
return;
|
||||||
|
}
|
||||||
|
const item: DataTableItem = {
|
||||||
|
icon: getPanelIcon(panelInfo),
|
||||||
|
title: getPanelTitle(this.hass, panelInfo) || panelInfo.url_path,
|
||||||
show_in_sidebar: true,
|
show_in_sidebar: true,
|
||||||
mode: "storage",
|
mode: "storage",
|
||||||
url_path: "light",
|
url_path: panelInfo.url_path,
|
||||||
filename: "",
|
filename: "",
|
||||||
default: false,
|
default: defaultUrlPath === panelInfo.url_path,
|
||||||
require_admin: false,
|
require_admin: false,
|
||||||
type: this._localizeType("built_in"),
|
type: "built_in",
|
||||||
|
localized_type: this._localizeType("built_in"),
|
||||||
|
};
|
||||||
|
result.push(item);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hass.panels.security) {
|
|
||||||
result.push({
|
|
||||||
icon: this.hass.panels.security.icon || "mdi:security",
|
|
||||||
title: this.hass.localize("panel.security"),
|
|
||||||
show_in_sidebar: true,
|
|
||||||
mode: "storage",
|
|
||||||
url_path: "security",
|
|
||||||
filename: "",
|
|
||||||
default: false,
|
|
||||||
require_admin: false,
|
|
||||||
type: this._localizeType("built_in"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hass.panels.climate) {
|
|
||||||
result.push({
|
|
||||||
icon: this.hass.panels.climate.icon || "mdi:home-thermometer",
|
|
||||||
title: this.hass.localize("panel.climate"),
|
|
||||||
show_in_sidebar: true,
|
|
||||||
mode: "storage",
|
|
||||||
url_path: "climate",
|
|
||||||
filename: "",
|
|
||||||
default: false,
|
|
||||||
require_admin: false,
|
|
||||||
type: this._localizeType("built_in"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hass.panels.home) {
|
|
||||||
result.push({
|
|
||||||
icon: this.hass.panels.home.icon || "mdi:home",
|
|
||||||
title: this.hass.localize("panel.home"),
|
|
||||||
show_in_sidebar: true,
|
|
||||||
mode: "storage",
|
|
||||||
url_path: "home",
|
|
||||||
filename: "",
|
|
||||||
default: false,
|
|
||||||
require_admin: false,
|
|
||||||
type: this._localizeType("built_in"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(
|
result.push(
|
||||||
...dashboards
|
...dashboards
|
||||||
@@ -386,7 +355,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
filename: "",
|
filename: "",
|
||||||
...dashboard,
|
...dashboard,
|
||||||
default: defaultUrlPath === dashboard.url_path,
|
default: defaultUrlPath === dashboard.url_path,
|
||||||
type: this._localizeType("user_created"),
|
type: "user_created",
|
||||||
|
localized_type: this._localizeType("user_created"),
|
||||||
}) satisfies DataTableItem
|
}) satisfies DataTableItem
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -404,7 +374,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
@@ -486,20 +456,15 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
this._openDetailDialog(dashboard, urlPath);
|
this._openDetailDialog(dashboard, urlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _canDelete(urlPath: string) {
|
private _handleSetAsDefault = async (item: DataTableItem) => {
|
||||||
return ![
|
if (item.default) {
|
||||||
"lovelace",
|
return;
|
||||||
"energy",
|
|
||||||
"light",
|
|
||||||
"security",
|
|
||||||
"climate",
|
|
||||||
"home",
|
|
||||||
].includes(urlPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _canEdit(urlPath: string) {
|
|
||||||
return !["light", "security", "climate", "home"].includes(urlPath);
|
|
||||||
}
|
}
|
||||||
|
await saveFrontendSystemData(this.hass.connection, "core", {
|
||||||
|
...this.hass.systemData,
|
||||||
|
defaultPanel: item.url_path === DEFAULT_PANEL ? undefined : item.url_path,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private _handleDelete = async (item: DataTableItem) => {
|
private _handleDelete = async (item: DataTableItem) => {
|
||||||
const dashboard = this._dashboards.find(
|
const dashboard = this._dashboards.find(
|
||||||
@@ -581,10 +546,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
private async _deleteDashboard(
|
private async _deleteDashboard(
|
||||||
dashboard: LovelaceDashboard
|
dashboard: LovelaceDashboard
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!this._canDelete(dashboard.url_path)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirm = await showConfirmationDialog(this, {
|
const confirm = await showConfirmationDialog(this, {
|
||||||
title: this.hass!.localize(
|
title: this.hass!.localize(
|
||||||
"ui.panel.config.lovelace.dashboards.confirm_delete_title",
|
"ui.panel.config.lovelace.dashboards.confirm_delete_title",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import type { LovelaceDashboard } from "../../data/lovelace/dashboard";
|
|||||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { saveFrontendUserData } from "../../data/frontend";
|
import { saveFrontendUserData } from "../../data/frontend";
|
||||||
|
import { PANEL_DASHBOARDS } from "../config/lovelace/dashboards/ha-config-lovelace-dashboards";
|
||||||
|
import { getPanelTitle } from "../../data/panel";
|
||||||
|
import "../../components/ha-divider";
|
||||||
|
|
||||||
const USE_SYSTEM_VALUE = "___use_system___";
|
const USE_SYSTEM_VALUE = "___use_system___";
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@ class HaPickDashboardRow extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const value = this.hass.userData?.default_panel || USE_SYSTEM_VALUE;
|
const value = this.hass.userData?.defaultPanel || USE_SYSTEM_VALUE;
|
||||||
return html`
|
return html`
|
||||||
<ha-settings-row .narrow=${this.narrow}>
|
<ha-settings-row .narrow=${this.narrow}>
|
||||||
<span slot="heading">
|
<span slot="heading">
|
||||||
@@ -47,12 +50,19 @@ class HaPickDashboardRow extends LitElement {
|
|||||||
<ha-list-item .value=${USE_SYSTEM_VALUE}>
|
<ha-list-item .value=${USE_SYSTEM_VALUE}>
|
||||||
${this.hass.localize("ui.panel.profile.dashboard.system")}
|
${this.hass.localize("ui.panel.profile.dashboard.system")}
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
<ha-divider></ha-divider>
|
||||||
<ha-list-item value="lovelace">
|
<ha-list-item value="lovelace">
|
||||||
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
|
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
<ha-list-item value="home">
|
${PANEL_DASHBOARDS.map((panel) => {
|
||||||
${this.hass.localize("ui.panel.profile.dashboard.home")}
|
const panelInfo = this.hass.panels[panel];
|
||||||
|
return html`
|
||||||
|
<ha-list-item value="lovelace">
|
||||||
|
${panelInfo ? getPanelTitle(this.hass, panelInfo) : panel}
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<ha-divider></ha-divider>
|
||||||
${this._dashboards.map((dashboard) => {
|
${this._dashboards.map((dashboard) => {
|
||||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||||
return "";
|
return "";
|
||||||
@@ -84,12 +94,12 @@ class HaPickDashboardRow extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const urlPath = value === USE_SYSTEM_VALUE ? undefined : value;
|
const urlPath = value === USE_SYSTEM_VALUE ? undefined : value;
|
||||||
if (urlPath === this.hass.userData?.default_panel) {
|
if (urlPath === this.hass.userData?.defaultPanel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
saveFrontendUserData(this.hass.connection, "core", {
|
saveFrontendUserData(this.hass.connection, "core", {
|
||||||
...this.hass.userData,
|
...this.hass.userData,
|
||||||
default_panel: urlPath,
|
defaultPanel: urlPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2217,7 +2217,9 @@
|
|||||||
"sidebar_toggle": "Sidebar toggle",
|
"sidebar_toggle": "Sidebar toggle",
|
||||||
"edit_sidebar": "Edit sidebar",
|
"edit_sidebar": "Edit sidebar",
|
||||||
"edit_subtitle": "Synced on all devices",
|
"edit_subtitle": "Synced on all devices",
|
||||||
"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."
|
"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.",
|
||||||
|
"reset_to_defaults": "Reset to defaults",
|
||||||
|
"reset_confirmation": "Are you sure you want to reset the sidebar to its default configuration? This will restore the original order and visibility of all panels."
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
"home": {
|
"home": {
|
||||||
@@ -3505,6 +3507,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"add_dashboard": "Add dashboard",
|
"add_dashboard": "Add dashboard",
|
||||||
|
"set_as_default": "Set as default",
|
||||||
"type": {
|
"type": {
|
||||||
"user_created": "User created",
|
"user_created": "User created",
|
||||||
"built_in": "Built-in"
|
"built_in": "Built-in"
|
||||||
@@ -3513,7 +3516,7 @@
|
|||||||
"confirm_delete_title": "Delete {dashboard_title}?",
|
"confirm_delete_title": "Delete {dashboard_title}?",
|
||||||
"confirm_delete_text": "This dashboard will be permanently deleted.",
|
"confirm_delete_text": "This dashboard will be permanently deleted.",
|
||||||
"cant_edit_yaml": "Dashboards created in YAML cannot be edited from the UI. Change them in configuration.yaml.",
|
"cant_edit_yaml": "Dashboards created in YAML cannot be edited from the UI. Change them in configuration.yaml.",
|
||||||
"cant_edit_default": "The default dashboard, Overview, cannot be edited from the UI. You can hide it by setting another dashboard as default.",
|
"cant_edit_lovelace": "The Overview dashboard title and icon cannot be changed. You can create a new dashboard to get more customization options.",
|
||||||
"detail": {
|
"detail": {
|
||||||
"edit_dashboard": "Edit dashboard",
|
"edit_dashboard": "Edit dashboard",
|
||||||
"new_dashboard": "Add new dashboard",
|
"new_dashboard": "Add new dashboard",
|
||||||
|
|||||||
Reference in New Issue
Block a user