Compare commits

..

11 Commits

Author SHA1 Message Date
Paul Bottein
653acdbc5c Fix dashboard picker 2025-11-24 11:06:15 +01:00
Paul Bottein
d1fce8607d Fix disable hiding condition 2025-11-24 11:01:48 +01:00
Paul Bottein
16eab41d7f Remove duplicated panel icon paths 2025-11-21 18:11:32 +01:00
Paul Bottein
6a99558237 Add other panels to profile 2025-11-21 15:32:54 +01:00
Paul Bottein
c6b65cf4cd Simplify navigation picker 2025-11-21 15:32:54 +01:00
Paul Bottein
000e1101c4 Simplify lovelace dashboard logic 2025-11-21 15:32:54 +01:00
Paul Bottein
785e6e4e86 Improve sidebar logic with default panel 2025-11-21 15:32:54 +01:00
Paul Bottein
0735d3614c Allow to set every dashboard as default 2025-11-21 15:32:54 +01:00
Paul Bottein
e3f243eb6b Refactor panel configuration 2025-11-21 15:32:54 +01:00
Paul Bottein
e09f69eeb3 Add reset to default sidebar button 2025-11-21 15:32:54 +01:00
Paul Bottein
fb15c7f87e Simplify sidebar logic 2025-11-21 15:32:54 +01:00
36 changed files with 420 additions and 846 deletions

View File

@@ -21,7 +21,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: dev
@@ -56,7 +56,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: master

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3

View File

@@ -22,7 +22,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: dev
@@ -57,7 +57,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: master

View File

@@ -16,7 +16,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0

View File

@@ -21,7 +21,7 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0

View File

@@ -20,7 +20,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6

View File

@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
@@ -91,7 +91,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -120,7 +120,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Upload Translations
run: |

View File

@@ -11,7 +11,7 @@ A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdo
### Example usage (composition)
```html
<ha-dropdown>
<ha-dropdown open>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>

View File

@@ -28,7 +28,7 @@ export class DemoHaDropdown extends LitElement {
<div class=${mode}>
<ha-card header="ha-button in ${mode}">
<div class="card-content">
<ha-dropdown>
<ha-dropdown open>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>

View File

@@ -2,8 +2,8 @@
import { genClientId } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { customElement, property, state } from "lit/decorators";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert";
import "../components/ha-button";
@@ -118,9 +118,6 @@ export class HaAuthFlow extends LitElement {
display: block;
margin-top: 16px;
}
.action ha-button {
width: 100%;
}
</style>
<form>${this._renderForm()}</form>
`;

View File

@@ -66,7 +66,7 @@ export class HaIconOverflowMenu extends LitElement {
.path=${item.path}
></ha-svg-icon>
${item.label}
</ha-md-menu-item> `
</ha-md-menu-item>`
)}
</ha-md-button-menu>`
: html`
@@ -103,6 +103,7 @@ export class HaIconOverflowMenu extends LitElement {
:host {
display: flex;
justify-content: flex-end;
cursor: initial;
}
div[role="separator"] {
border-right: 1px solid var(--divider-color);

View File

@@ -27,6 +27,7 @@ export interface DisplayItem {
label: string;
description?: string;
disableSorting?: boolean;
disableHiding?: boolean;
}
export interface DisplayValue {
@@ -101,6 +102,7 @@ export class HaItemDisplayEditor extends LitElement {
icon,
iconPath,
disableSorting,
disableHiding,
} = item;
return html`
<ha-md-list-item
@@ -155,18 +157,21 @@ export class HaItemDisplayEditor extends LitElement {
</div>
`
: nothing}
<ha-icon-button
.path=${isVisible ? mdiEye : mdiEyeOff}
slot="end"
.label=${this.hass.localize(
`ui.components.items-display-editor.${isVisible ? "hide" : "show"}`,
{
label: label,
}
)}
.value=${value}
@click=${this._toggle}
></ha-icon-button>
${!isVisible || !disableHiding
? html`<ha-icon-button
.path=${isVisible ? mdiEye : mdiEyeOff}
slot="end"
.label=${this.hass.localize(
`ui.components.items-display-editor.${isVisible ? "hide" : "show"}`,
{
label: label,
}
)}
.value=${value}
@click=${this._toggle}
.disabled=${disableHiding || false}
></ha-icon-button>`
: nothing}
${isVisible && !disableSorting
? html`
<ha-svg-icon

View File

@@ -36,6 +36,11 @@ export class HaMdMenuItem extends MenuItemEl {
::slotted([slot="headline"]) {
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);
}
`,
];
}

View File

@@ -6,7 +6,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { titleCase } from "../common/string/title-case";
import { fetchConfig } from "../data/lovelace/config/types";
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
import { getDefaultPanelUrlPath } from "../data/panel";
import { getPanelIcon, getPanelTitle } from "../data/panel";
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
import "./ha-combo-box";
import type { HaComboBox } from "./ha-combo-box";
@@ -43,13 +43,8 @@ const createViewNavigationItem = (
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
path: `/${panel.url_path}`,
icon: panel.icon ?? "mdi:view-dashboard",
title:
panel.url_path === getDefaultPanelUrlPath(hass)
? hass.localize("panel.states")
: hass.localize(`panel.${panel.title}`) ||
panel.title ||
(panel.url_path ? titleCase(panel.url_path) : ""),
icon: getPanelIcon(panel) || "mdi:view-dashboard",
title: getPanelTitle(hass, panel) || "",
});
@customElement("ha-navigation-picker")

View File

@@ -1,22 +1,13 @@
import {
mdiBell,
mdiCalendar,
mdiCellphoneCog,
mdiChartBox,
mdiClipboardList,
mdiCog,
mdiFormatListBulletedType,
mdiHammer,
mdiLightningBolt,
mdiMenu,
mdiMenuOpen,
mdiPlayBoxMultiple,
mdiTooltipAccount,
mdiViewDashboard,
} from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { css, html, LitElement, nothing } from "lit";
import {
customElement,
eventOptions,
@@ -33,7 +24,14 @@ import { computeRTL } from "../common/util/compute_rtl";
import { throttle } from "../common/util/throttle";
import { subscribeFrontendUserData } from "../data/frontend";
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
import { getDefaultPanelUrlPath } from "../data/panel";
import {
FIXED_PANELS,
getDefaultPanelUrlPath,
getPanelIcon,
getPanelIconPath,
getPanelTitle,
SHOW_AFTER_SPACER_PANELS,
} from "../data/panel";
import type { PersistentNotification } from "../data/persistent_notification";
import { subscribeNotifications } from "../data/persistent_notification";
import { subscribeRepairsIssueRegistry } from "../data/repairs";
@@ -54,8 +52,6 @@ import "./ha-spinner";
import "./ha-svg-icon";
import "./user/ha-user-badge";
const SHOW_AFTER_SPACER = ["config", "developer-tools"];
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
const SORT_VALUE_URL_PATHS = {
@@ -67,18 +63,6 @@ const SORT_VALUE_URL_PATHS = {
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 = (
reverseSort: string[],
defaultPanel: string,
@@ -155,16 +139,23 @@ export const computePanels = memoizeOne(
const beforeSpacer: 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 (
hiddenPanels.includes(panel.url_path) ||
(!panel.title && panel.url_path !== defaultPanel) ||
(panel.default_visible === false &&
!panelsOrder.includes(panel.url_path))
!isDefaultPanel &&
(!panel.title ||
hiddenPanels.includes(panel.url_path) ||
(panel.default_visible === false &&
!panelsOrder.includes(panel.url_path)))
) {
return;
}
(SHOW_AFTER_SPACER.includes(panel.url_path)
(SHOW_AFTER_SPACER_PANELS.includes(panel.url_path)
? afterSpacer
: beforeSpacer
).push(panel);
@@ -252,9 +243,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
}
// Show the supervisor as being part of configuration
const selectedPanel = this.route.path?.startsWith("/hassio/")
? "config"
: this.hass.panelUrl;
const selectedPanel = this.hass.panelUrl;
// prettier-ignore
return html`
@@ -397,9 +386,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
private _renderAllPanels(selectedPanel: string) {
if (!this._panelOrder || !this._hiddenPanels) {
return html`
<ha-fade-in .delay=${500}
><ha-spinner size="small"></ha-spinner
></ha-fade-in>
<ha-fade-in .delay=${500}>
<ha-spinner size="small"></ha-spinner>
</ha-fade-in>
`;
}
@@ -413,7 +402,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
this.hass.locale
);
// prettier-ignore
return html`
<ha-md-list
class="ha-scrollbar"
@@ -422,61 +410,42 @@ class HaSidebar extends SubscribeMixin(LitElement) {
@scroll=${this._listboxScroll}
@keydown=${this._listboxKeydown}
>
${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)}
${this._renderPanels(beforeSpacer, selectedPanel)}
${this._renderSpacer()}
${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)}
${this._renderExternalConfiguration()}
${this._renderPanels(afterSpacer, selectedPanel)}
${this.hass.user?.is_admin
? this._renderConfiguration(selectedPanel)
: this._renderExternalConfiguration()}
</ha-md-list>
`;
}
private _renderPanels(
panels: PanelInfo[],
selectedPanel: string,
defaultPanel: string
) {
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
return panels.map((panel) =>
this._renderPanel(
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
)
this._renderPanel(panel, panel.url_path === selectedPanel)
);
}
private _renderPanel(
urlPath: string,
title: string | null,
icon: string | null | undefined,
iconPath: string | null | undefined,
selectedPanel: string
) {
return urlPath === "config"
? this._renderConfiguration(title, selectedPanel)
: html`
<ha-md-list-item
.href=${`/${urlPath}`}
type="link"
class=${classMap({
selected: selectedPanel === urlPath,
})}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
${iconPath
? html`<ha-svg-icon slot="start" .path=${iconPath}></ha-svg-icon>`
: html`<ha-icon slot="start" .icon=${icon}></ha-icon>`}
<span class="item-text" slot="headline">${title}</span>
</ha-md-list-item>
`;
private _renderPanel(panel: PanelInfo, isSelected: boolean) {
const title = getPanelTitle(this.hass, panel);
const urlPath = panel.url_path;
const icon = getPanelIcon(panel);
const iconPath = getPanelIconPath(panel);
return html`
<ha-md-list-item
.href=${`/${urlPath}`}
type="link"
class=${classMap({ selected: isSelected })}
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
${iconPath
? html`<ha-svg-icon slot="start" .path=${iconPath}></ha-svg-icon>`
: html`<ha-icon slot="start" .icon=${icon}></ha-icon>`}
<span class="item-text" slot="headline">${title}</span>
</ha-md-list-item>
`;
}
private _renderDivider() {
@@ -487,10 +456,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
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`
<ha-md-list-item
class="configuration${selectedPanel === "config" ? " selected" : ""}"
class="configuration ${classMap({ selected: isSelected })}"
type="button"
href="/config"
@mouseenter=${this._itemMouseEnter}
@@ -504,15 +478,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
${this._updatesCount + this._issuesCount}
</span>
`
: ""}
<span class="item-text" slot="headline">${title}</span>
: nothing}
<span class="item-text" slot="headline"
>${this.hass.localize("panel.config")}</span
>
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
? html`
<span class="badge" slot="end"
>${this._updatesCount + this._issuesCount}</span
>
`
: ""}
: nothing}
</ha-md-list-item>
`;
}
@@ -535,19 +511,20 @@ class HaSidebar extends SubscribeMixin(LitElement) {
? html`
<span class="badge" slot="start"> ${notificationCount} </span>
`
: ""}
: nothing}
<span class="item-text" slot="headline"
>${this.hass.localize("ui.notification_drawer.title")}</span
>
${this.alwaysExpand && notificationCount > 0
? html`<span class="badge" slot="end">${notificationCount}</span>`
: ""}
: nothing}
</ha-md-list-item>
`;
}
private _renderUserItem(selectedPanel: string) {
const isRTL = computeRTL(this.hass);
const isSelected = selectedPanel === "profile";
return html`
<ha-md-list-item
@@ -555,7 +532,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
type="link"
class=${classMap({
user: true,
selected: selectedPanel === "profile",
selected: isSelected,
rtl: isRTL,
})}
@mouseenter=${this._itemMouseEnter}
@@ -566,31 +543,30 @@ class HaSidebar extends SubscribeMixin(LitElement) {
.user=${this.hass.user}
.hass=${this.hass}
></ha-user-badge>
<span class="item-text" slot="headline"
>${this.hass.user ? this.hass.user.name : ""}</span
>
<span class="item-text" slot="headline">
${this.hass.user ? this.hass.user.name : ""}
</span>
</ha-md-list-item>
`;
}
private _renderExternalConfiguration() {
return html`${!this.hass.user?.is_admin &&
this.hass.auth.external?.config.hasSettingsScreen
? html`
<ha-md-list-item
@click=${this._handleExternalAppConfiguration}
type="button"
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
<ha-svg-icon slot="start" .path=${mdiCellphoneCog}></ha-svg-icon>
<span class="item-text" slot="headline">
${this.hass.localize("ui.sidebar.external_app_configuration")}
</span>
</ha-md-list-item>
`
: ""}`;
if (!this.hass.auth.external?.config.hasSettingsScreen) {
return nothing;
}
return html`
<ha-md-list-item
@click=${this._handleExternalAppConfiguration}
type="button"
@mouseenter=${this._itemMouseEnter}
@mouseleave=${this._itemMouseLeave}
>
<ha-svg-icon slot="start" .path=${mdiCellphoneCog}></ha-svg-icon>
<span class="item-text" slot="headline">
${this.hass.localize("ui.sidebar.external_app_configuration")}
</span>
</ha-md-list-item>
`;
}
private _handleExternalAppConfiguration(ev: Event) {

View File

@@ -1,178 +0,0 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { HomeAssistant } from "../types";
import { subscribeLabFeatures } from "../data/labs";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
interface Snowflake {
id: number;
left: number;
size: number;
duration: number;
delay: number;
blur: number;
}
@customElement("ha-snowflakes")
export class HaSnowflakes extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@state() private _enabled = false;
@state() private _snowflakes: Snowflake[] = [];
private _maxSnowflakes = 50;
public hassSubscribe() {
return [
subscribeLabFeatures(this.hass!.connection, (features) => {
this._enabled =
features.find(
(f) =>
f.domain === "frontend" && f.preview_feature === "winter_mode"
)?.enabled ?? false;
}),
];
}
private _generateSnowflakes() {
if (!this._enabled) {
this._snowflakes = [];
return;
}
const snowflakes: Snowflake[] = [];
for (let i = 0; i < this._maxSnowflakes; i++) {
snowflakes.push({
id: i,
left: Math.random() * 100, // Random position from 0-100%
size: Math.random() * 12 + 8, // Random size between 8-20px
duration: Math.random() * 8 + 8, // Random duration between 8-16s
delay: Math.random() * 8, // Random delay between 0-8s
blur: Math.random() * 1, // Random blur between 0-1px
});
}
this._snowflakes = snowflakes;
}
protected willUpdate(changedProps: Map<string, unknown>) {
super.willUpdate(changedProps);
if (changedProps.has("_enabled")) {
this._generateSnowflakes();
}
}
protected render() {
if (!this._enabled) {
return nothing;
}
const isDark = this.hass?.themes.darkMode ?? false;
return html`
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
${this._snowflakes.map(
(flake) => html`
<div
class="snowflake ${this.narrow && flake.id >= 30
? "hide-narrow"
: ""}"
style="
left: ${flake.left}%;
font-size: ${flake.size}px;
animation-duration: ${flake.duration}s;
animation-delay: ${flake.delay}s;
filter: blur(${flake.blur}px);
"
>
</div>
`
)}
</div>
`;
}
static readonly styles = css`
:host {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
.snowflakes {
position: absolute;
top: -10%;
left: 0;
width: 100%;
height: 110%;
pointer-events: none;
}
.snowflake {
position: absolute;
top: -10%;
opacity: 0.7;
user-select: none;
pointer-events: none;
animation: fall linear infinite;
}
.light .snowflake {
color: #00bcd4;
text-shadow:
0 0 5px #00bcd4,
0 0 10px #00e5ff;
}
.dark .snowflake {
color: #fff;
text-shadow:
0 0 5px rgba(255, 255, 255, 0.8),
0 0 10px rgba(255, 255, 255, 0.5);
}
.snowflake.hide-narrow {
display: none;
}
@keyframes fall {
0% {
transform: translateY(-10vh) translateX(0);
}
25% {
transform: translateY(30vh) translateX(10px);
}
50% {
transform: translateY(60vh) translateX(-10px);
}
75% {
transform: translateY(85vh) translateX(10px);
}
100% {
transform: translateY(120vh) translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.snowflake {
animation: none;
display: none;
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-snowflakes": HaSnowflakes;
}
}

View File

@@ -3,16 +3,16 @@ import type { Connection } from "home-assistant-js-websocket";
export interface CoreFrontendUserData {
showAdvanced?: boolean;
showEntityIdPicker?: boolean;
default_panel?: string;
defaultPanel?: string;
}
export interface SidebarFrontendUserData {
panelOrder: string[];
hiddenPanels: string[];
panelOrder?: string[];
hiddenPanels?: string[];
}
export interface CoreFrontendSystemData {
default_panel?: string;
defaultPanel?: string;
}
export interface HomeFrontendSystemData {

View File

@@ -1,3 +1,15 @@
import {
mdiAccount,
mdiCalendar,
mdiChartBox,
mdiClipboardList,
mdiFormatListBulletedType,
mdiHammer,
mdiLightningBolt,
mdiPlayBoxMultiple,
mdiTooltipAccount,
mdiViewDashboard,
} from "@mdi/js";
import type { HomeAssistant, PanelInfo } from "../types";
/** Panel to show when no panel is picked. */
@@ -9,8 +21,8 @@ export const getLegacyDefaultPanelUrlPath = (): string | null => {
};
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
hass.userData?.default_panel ||
hass.systemData?.default_panel ||
hass.userData?.defaultPanel ||
hass.systemData?.defaultPanel ||
getLegacyDefaultPanelUrlPath() ||
DEFAULT_PANEL;
@@ -60,7 +72,7 @@ export const getPanelTitleFromUrlPath = (
return getPanelTitle(hass, panel);
};
export const getPanelIcon = (panel: PanelInfo): string | null => {
export const getPanelIcon = (panel: PanelInfo): string | undefined => {
if (!panel.icon) {
switch (panel.component_name) {
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"];

View File

@@ -11,7 +11,6 @@ import type {
import { showToast } from "../../util/toast";
import type { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("ha-more-info-add-to")
export class HaMoreInfoAddTo extends LitElement {
@@ -52,7 +51,6 @@ export class HaMoreInfoAddTo extends LitElement {
app_payload: action.app_payload,
},
});
fireEvent(this, "add-to-action-selected");
} catch (err: any) {
showToast(this, {
message: this.hass.localize(
@@ -151,8 +149,4 @@ declare global {
interface HTMLElementTagNameMap {
"ha-more-info-add-to": HaMoreInfoAddTo;
}
interface HASSDomEvents {
"add-to-action-selected": undefined;
}
}

View File

@@ -645,7 +645,6 @@ export class MoreInfoDialog extends LitElement {
<ha-more-info-add-to
.hass=${this.hass}
.entityId=${entityId}
@add-to-action-selected=${this._goBack}
></ha-more-info-add-to>
`
: nothing

View File

@@ -1,5 +1,5 @@
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 { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -9,18 +9,30 @@ import "../../components/ha-dialog-header";
import "../../components/ha-fade-in";
import "../../components/ha-icon-button";
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 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-svg-icon";
import {
fetchFrontendUserData,
saveFrontendUserData,
} from "../../data/frontend";
import {
getDefaultPanelUrlPath,
getPanelIcon,
getPanelIconPath,
getPanelTitle,
SHOW_AFTER_SPACER_PANELS,
} from "../../data/panel";
import type { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { getDefaultPanelUrlPath } from "../../data/panel";
@customElement("dialog-edit-sidebar")
class DialogEditSidebar extends LitElement {
@@ -105,48 +117,53 @@ class DialogEditSidebar extends LitElement {
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) {
if (
panel.default_visible === false &&
!this._order.includes(panel.url_path) &&
!this._hidden.includes(panel.url_path)
!orderSet.has(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 = [
...beforeSpacer,
...panels.filter((panel) => this._hidden!.includes(panel.url_path)),
...afterSpacer.filter((panel) => panel.url_path !== "config"),
].map((panel) => ({
...panels.filter((panel) => hiddenPanels.includes(panel.url_path)),
...afterSpacer,
].map<DisplayItem>((panel) => ({
value: panel.url_path,
label:
panel.url_path === defaultPanel
? panel.title || this.hass.localize("panel.states")
: this.hass.localize(`panel.${panel.title}`) || panel.title || "?",
icon: panel.icon || undefined,
iconPath:
panel.url_path === defaultPanel && !panel.icon
? PANEL_ICONS.lovelace
: panel.url_path in PANEL_ICONS
? PANEL_ICONS[panel.url_path]
: undefined,
disableSorting: panel.url_path === "developer-tools",
(getPanelTitle(this.hass, panel) || panel.url_path) +
`${defaultPanel === panel.url_path ? " (default)" : ""}`,
icon: getPanelIcon(panel),
iconPath: getPanelIconPath(panel),
disableSorting: SHOW_AFTER_SPACER_PANELS.includes(panel.url_path),
disableHiding: panel.url_path === defaultPanel,
}));
return html`<ha-items-display-editor
.hass=${this.hass}
.value=${{
order: this._order,
hidden: this._hidden,
}}
.items=${items}
@value-changed=${this._changed}
dont-sort-visible
>
</ha-items-display-editor>`;
return html`
<ha-items-display-editor
.hass=${this.hass}
.value=${{
order: this._order,
hidden: hiddenPanels,
}}
.items=${items}
@value-changed=${this._changed}
dont-sort-visible
>
</ha-items-display-editor>
`;
}
protected render() {
@@ -171,6 +188,22 @@ class DialogEditSidebar extends LitElement {
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
>`
: 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>
<div slot="content" class="content">${this._renderContent()}</div>
<div slot="actions">
@@ -194,6 +227,26 @@ class DialogEditSidebar extends LitElement {
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() {
if (this._migrateToUserData) {
const confirmation = await showConfirmationDialog(this, {

View File

@@ -7,7 +7,6 @@ import { listenMediaQuery } from "../common/dom/media_query";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { computeRTLDirection } from "../common/util/compute_rtl";
import "../components/ha-drawer";
import "../components/ha-snowflakes";
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
import type { HomeAssistant, Route } from "../types";
import "./partial-panel-resolver";
@@ -51,7 +50,6 @@ export class HomeAssistantMain extends LitElement {
this.hass.panels && this.hass.userData && this.hass.systemData;
return html`
<ha-snowflakes .hass=${this.hass} .narrow=${this.narrow}></ha-snowflakes>
<ha-drawer
.type=${sidebarNarrow ? "modal" : ""}
.open=${sidebarNarrow ? this._drawerOpen : false}

View File

@@ -188,7 +188,6 @@ export default class HaAutomationSidebar extends LitElement {
class="handle ${this._resizing ? "resizing" : ""}"
@mousedown=${this._handleMouseDown}
@touchstart=${this._handleMouseDown}
@dblclick=${this._handleDoubleClick}
@focus=${this._startKeyboardResizing}
@blur=${this._stopKeyboardResizing}
tabindex="0"
@@ -259,17 +258,6 @@ export default class HaAutomationSidebar extends LitElement {
);
};
private _handleDoubleClick = (ev: MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
this._unregisterResizeHandlers();
this._tinykeysUnsub?.();
this._tinykeysUnsub = undefined;
this._resizing = false;
document.body.style.removeProperty("cursor");
fireEvent(this, "sidebar-reset-size");
};
private _startResizing(clientX: number) {
// register event listeners for drag handling
document.addEventListener("mousemove", this._handleMouseMove);
@@ -434,6 +422,5 @@ declare global {
deltaInPx: number;
};
"sidebar-resizing-stopped": undefined;
"sidebar-reset-size": undefined;
}
}

View File

@@ -317,7 +317,6 @@ export class HaManualAutomationEditor extends LitElement {
@value-changed=${this._sidebarConfigChanged}
@sidebar-resized=${this._resizeSidebar}
@sidebar-resizing-stopped=${this._stopResizeSidebar}
@sidebar-reset-size=${this._resetSidebarWidth}
></ha-automation-sidebar>
</div>
</div>
@@ -701,16 +700,6 @@ export class HaManualAutomationEditor extends LitElement {
this._prevSidebarWidthPx = undefined;
}
private _resetSidebarWidth(ev: Event) {
ev.stopPropagation();
this._prevSidebarWidthPx = undefined;
this._sidebarWidthPx = SIDEBAR_DEFAULT_WIDTH;
this.style.setProperty(
"--sidebar-dynamic-width",
`${this._sidebarWidthPx}px`
);
}
static get styles(): CSSResultGroup {
return [
saveFabStyles,

View File

@@ -43,7 +43,6 @@ import {
} from "../../../data/blueprint";
import { showScriptEditor } from "../../../data/script";
import { findRelated } from "../../../data/search";
import "../../../components/chips/ha-assist-chip";
import {
showAlertDialog,
showConfirmationDialog,
@@ -61,7 +60,6 @@ type BlueprintMetaDataPath = BlueprintMetaData & {
error: boolean;
type: "automation" | "script";
fullpath: string;
usageCount?: number;
};
const createNewFunctions = {
@@ -130,20 +128,14 @@ class HaBlueprintOverview extends LitElement {
})
private _filter = "";
@state() private _usageCounts: Record<string, number> = {};
private _usageCountRequest = 0;
private _processedBlueprints = memoizeOne(
(
blueprints: Record<string, Blueprints>,
localize: LocalizeFunc,
usageCounts: Record<string, number>
localize: LocalizeFunc
): BlueprintMetaDataPath[] => {
const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
const fullpath = `${type}/${path}`;
if ("error" in blueprint) {
result.push({
name: blueprint.error,
@@ -153,8 +145,7 @@ class HaBlueprintOverview extends LitElement {
),
error: true,
path,
fullpath,
usageCount: 0,
fullpath: `${type}/${path}`,
});
} else {
result.push({
@@ -165,8 +156,7 @@ class HaBlueprintOverview extends LitElement {
),
error: false,
path,
fullpath,
usageCount: usageCounts[fullpath] || 0,
fullpath: `${type}/${path}`,
});
}
})
@@ -199,34 +189,6 @@ class HaBlueprintOverview extends LitElement {
filterable: true,
flex: 2,
},
usage_count: {
title: localize(
"ui.panel.config.blueprint.overview.headers.usage_count"
),
sortable: true,
valueColumn: "usageCount",
type: "numeric",
minWidth: "100px",
maxWidth: "120px",
template: (blueprint) => {
const count = blueprint.usageCount ?? 0;
return html`
<ha-assist-chip
filled
.active=${count > 0}
label=${String(count)}
title=${blueprint.error
? String(count)
: this.hass.localize(
`ui.panel.config.blueprint.overview.view_${blueprint.type}`
)}
?disabled=${blueprint.error}
data-fullpath=${blueprint.fullpath}
@click=${this._handleUsageClick}
></ha-assist-chip>
`;
},
},
fullpath: {
title: "fullpath",
hidden: true,
@@ -304,7 +266,6 @@ class HaBlueprintOverview extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._loadUsageCounts();
if (this.route.path === "/import") {
const url = extractSearchParam("blueprint_url");
navigate("/config/blueprint/dashboard", { replace: true });
@@ -314,13 +275,6 @@ class HaBlueprintOverview extends LitElement {
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("blueprints")) {
this._loadUsageCounts();
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
@@ -330,11 +284,7 @@ class HaBlueprintOverview extends LitElement {
.route=${this.route}
.tabs=${configSections.automations}
.columns=${this._columns(this.hass.localize)}
.data=${this._processedBlueprints(
this.blueprints,
this.hass.localize,
this._usageCounts
)}
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
id="fullpath"
.noDataText=${this.hass.localize(
"ui.panel.config.blueprint.overview.no_blueprints"
@@ -430,51 +380,10 @@ class HaBlueprintOverview extends LitElement {
fireEvent(this, "reload-blueprints");
}
private async _loadUsageCounts() {
if (!this.blueprints) {
return;
}
const request = ++this._usageCountRequest;
const usageCounts: Record<string, number> = {};
const blueprintList = this._processedBlueprints(
this.blueprints,
this.hass.localize,
{}
);
await Promise.all(
blueprintList.map(async (blueprint) => {
if (blueprint.error) {
usageCounts[blueprint.fullpath] = 0;
return;
}
try {
const related = await findRelated(
this.hass,
`${blueprint.domain}_blueprint`,
blueprint.path
);
const count =
(related.automation?.length || 0) + (related.script?.length || 0);
usageCounts[blueprint.fullpath] = count;
} catch (_err) {
usageCounts[blueprint.fullpath] = 0;
}
})
);
if (request === this._usageCountRequest) {
this._usageCounts = usageCounts;
}
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const blueprint = this._processedBlueprints(
this.blueprints,
this.hass.localize,
this._usageCounts
this.hass.localize
).find((b) => b.fullpath === ev.detail.id)!;
if (blueprint.error) {
showAlertDialog(this, {
@@ -488,25 +397,6 @@ class HaBlueprintOverview extends LitElement {
this._createNew(blueprint);
}
private _handleUsageClick = (ev: Event) => {
ev.stopPropagation();
ev.preventDefault();
const target = ev.currentTarget as HTMLElement | null;
const fullpath = target?.dataset.fullpath;
if (!fullpath) {
return;
}
const blueprint = this._processedBlueprints(
this.blueprints,
this.hass.localize,
this._usageCounts
).find((item) => item.fullpath === fullpath);
if (!blueprint || blueprint.error) {
return;
}
this._showUsed(blueprint);
};
private _showUsed = (blueprint: BlueprintMetaDataPath) => {
navigate(
`/config/${blueprint.domain}/dashboard?blueprint=${encodeURIComponent(

View File

@@ -2,7 +2,9 @@ import { mdiDotsVertical, mdiDownload } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import "../../../layouts/hass-subpage";
@@ -12,8 +14,6 @@ import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-dropdown";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@@ -33,19 +33,22 @@ class HaConfigSectionAnalytics extends LitElement {
>
${downloadFileSupported(this.hass)
? html`
<ha-dropdown
@wa-select=${this._handleOverflowAction}
<ha-button-menu
@action=${this._handleOverflowAction}
slot="toolbar-icon"
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-dropdown-item .value=${"download_device_info"}>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiDownload}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.analytics.download_device_info"
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
`
: nothing}
<div class="content">
@@ -55,16 +58,9 @@ class HaConfigSectionAnalytics extends LitElement {
`;
}
private async _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
): Promise<void> {
if (ev.detail.item.value === "download_device_info") {
const signedPath = await getSignedPath(
this.hass,
"/api/analytics/devices"
);
fileDownload(signedPath.path);
}
private async _handleOverflowAction(): Promise<void> {
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
fileDownload(signedPath.path);
}
static styles = css`

View File

@@ -1,3 +1,4 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
import type { HassEntities } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
@@ -5,9 +6,13 @@ import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import "../../../components/ha-alert";
import "../../../components/ha-bar";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-list-item";
import "../../../components/ha-metric";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type {
@@ -28,9 +33,6 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import "../dashboard/ha-config-updates";
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/divider/divider";
@customElement("ha-config-section-updates")
class HaConfigSectionUpdates extends LitElement {
@@ -71,25 +73,24 @@ class HaConfigSectionUpdates extends LitElement {
.path=${mdiRefresh}
@click=${this._checkUpdates}
></ha-icon-button>
<ha-dropdown @wa-select=${this._handleOverflowAction}>
<ha-button-menu multi>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item
type="checkbox"
value="show_skipped"
.checked=${this._showSkipped}
<ha-check-list-item
left
@request-selected=${this._toggleSkipped}
.selected=${this._showSkipped}
>
${this.hass.localize("ui.panel.config.updates.show_skipped")}
</ha-dropdown-item>
</ha-check-list-item>
${this._supervisorInfo
? html`
<wa-divider></wa-divider>
<ha-dropdown-item
value="toggle_beta"
<li divider role="separator"></li>
<ha-list-item
@request-selected=${this._toggleBeta}
.disabled=${this._supervisorInfo.channel === "dev"}
>
${this._supervisorInfo.channel === "stable"
@@ -97,10 +98,10 @@ class HaConfigSectionUpdates extends LitElement {
: this.hass.localize(
"ui.panel.config.updates.leave_beta"
)}
</ha-dropdown-item>
</ha-list-item>
`
: ""}
</ha-dropdown>
</ha-button-menu>
</div>
<div class="content">
<ha-card outlined>
@@ -132,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
}
private async _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
if (ev.detail.source !== "property") {
return;
}
this._showSkipped = !this._showSkipped;
}
private async _toggleBeta(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (ev.detail.item.value === "toggle_beta") {
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
}
} else if (ev.detail.item.value === "show_skipped") {
this._showSkipped = !this._showSkipped;
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
}
}

View File

@@ -5,7 +5,6 @@ import {
mdiCancel,
mdiChevronRight,
mdiCog,
mdiDelete,
mdiDotsVertical,
mdiMenuDown,
mdiPencilOff,
@@ -110,11 +109,10 @@ import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
import { renderConfigEntryError } from "../integrations/ha-config-integration-page";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { isHelperDomain, type HelperDomain } from "./const";
import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { slugify } from "../../../common/string/slugify";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import {
fetchDiagnosticHandlers,
getConfigEntryDiagnosticsDownloadUrl,
@@ -453,19 +451,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
},
]
: []),
...(helper.editable && helper.entity
? [
{
divider: true,
},
{
path: mdiDelete,
label: this.hass.localize("ui.common.delete"),
warning: true,
action: () => this._deleteHelper(helper),
},
]
: []),
]}
>
</ha-icon-overflow-menu>
@@ -1295,62 +1280,6 @@ ${rejected
}
}
private async _deleteHelper(helper: HelperItem) {
if (!helper.entity_id) {
return;
}
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.helpers.picker.delete_confirm_title"
),
text: this.hass.localize(
"ui.panel.config.helpers.picker.delete_confirm_text",
{ name: helper.name }
),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirmed) {
return;
}
try {
// For old-style helpers (input_boolean, etc.), use HELPERS_CRUD
if (isHelperDomain(helper.type)) {
const entityReg = this._entityReg.find(
(e) => e.entity_id === helper.entity_id
);
if (
!entityReg?.unique_id ||
!isComponentLoaded(this.hass, helper.type)
) {
throw new Error(
this.hass.localize("ui.panel.config.helpers.picker.delete_failed")
);
}
await HELPERS_CRUD[helper.type as HelperDomain].delete(
this.hass,
entityReg.unique_id
);
return;
}
// For config entry-based helpers, delete the config entry
if (helper.configEntry) {
await deleteConfigEntry(this.hass, helper.configEntry.entry_id);
}
} catch (err: any) {
showAlertDialog(this, {
text:
err.message ||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed"),
});
}
}
private _createHelper() {
showHelperDetailDialog(this, {});
}

View File

@@ -295,7 +295,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
color:
route.route_status === "Active"
? primaryColor
: style.getPropertyValue("--dark-primary-color"),
: style.getPropertyValue("--disabled-color"),
type: ["Child", "Parent"].includes(neighbor.relationship)
? "solid"
: "dotted",
@@ -335,7 +335,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
symbolSize: 5,
lineStyle: {
width: 1,
color: style.getPropertyValue("--dark-primary-color"),
color: style.getPropertyValue("--disabled-color"),
type: "dotted",
},
ignoreForceLayout: true,

View File

@@ -8,16 +8,13 @@ import "../../../../components/ha-button";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import { saveFrontendSystemData } from "../../../../data/frontend";
import type {
LovelaceDashboard,
LovelaceDashboardCreateParams,
LovelaceDashboardMutableParams,
} from "../../../../data/lovelace/dashboard";
import { DEFAULT_PANEL } from "../../../../data/panel";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers";
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
@customElement("dialog-lovelace-dashboard-detail")
@@ -61,9 +58,8 @@ export class DialogLovelaceDashboardDetail extends LitElement {
if (!this._params || !this._data) {
return nothing;
}
const defaultPanelUrlPath =
this.hass.systemData?.default_panel || DEFAULT_PANEL;
const titleInvalid = !this._data.title || !this._data.title.trim();
const isLovelaceDashboard = this._params.urlPath === "lovelace";
return html`
<ha-dialog
@@ -88,9 +84,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
? this.hass.localize(
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
)
: this._params.urlPath === "lovelace"
: isLovelaceDashboard
? this.hass.localize(
"ui.panel.config.lovelace.dashboards.cant_edit_default"
"ui.panel.config.lovelace.dashboards.cant_edit_lovelace"
)
: html`
<ha-form
@@ -119,24 +115,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
)}
</ha-button>
`
: ""}
<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}
`
: ""}
: nothing}
<ha-button
slot="primaryAction"
@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() {
if (this._params?.urlPath && !this._params.dashboard?.id) {
this.closeDialog();
@@ -309,20 +256,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
this.closeDialog();
} catch (err: any) {
let localizedErrorMessage: string | undefined;
if (err?.translation_domain && err?.translation_key) {
const localize = await this.hass.loadBackendTranslation(
"exceptions",
err.translation_domain
);
localizedErrorMessage = localize(
`component.${err.translation_domain}.exceptions.${err.translation_key}.message`,
err.translation_placeholders
);
}
this._error = {
base: localizedErrorMessage || err?.message || "Unknown error",
};
this._error = { base: err?.message || "Unknown error" };
} finally {
this._submitting = false;
}

View File

@@ -1,8 +1,9 @@
import {
mdiCheck,
mdiCheckCircleOutline,
mdiDelete,
mdiDotsVertical,
mdiHomeCircleOutline,
mdiHomeEdit,
mdiPencil,
mdiPlus,
} from "@mdi/js";
@@ -10,7 +11,6 @@ import type { PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoize from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { storage } from "../../../../common/decorators/storage";
import { navigate } from "../../../../common/navigate";
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-svg-icon";
import "../../../../components/ha-tooltip";
import { saveFrontendSystemData } from "../../../../data/frontend";
import type { LovelacePanelConfig } from "../../../../data/lovelace";
import type { LovelaceRawConfig } from "../../../../data/lovelace/config/types";
import {
@@ -45,7 +46,11 @@ import {
fetchDashboards,
updateDashboard,
} 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 "../../../../layouts/hass-loading-screen";
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 { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
export const PANEL_DASHBOARDS = [
"home",
"light",
"security",
"climate",
"energy",
] as string[];
type DataTableItem = Pick<
LovelaceDashboard,
"icon" | "title" | "show_in_sidebar" | "require_admin" | "mode" | "url_path"
> & {
default: boolean;
filename: string;
localized_type: string;
type: string;
};
@@ -112,7 +126,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
state: false,
subscribe: false,
})
private _activeGrouping?: string = "type";
private _activeGrouping?: string = "localized_type";
@storage({
key: "lovelace-dashboards-table-collapsed",
@@ -167,7 +181,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
<ha-svg-icon
.id="default-icon-${dashboard.title}"
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
.path=${mdiCheckCircleOutline}
.path=${mdiHomeCircleOutline}
></ha-svg-icon>
<ha-tooltip
.for="default-icon-${dashboard.title}"
@@ -183,7 +197,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
},
};
columns.type = {
columns.localized_type = {
title: localize(
"ui.panel.config.lovelace.dashboards.picker.headers.type"
),
@@ -253,7 +267,15 @@ export class HaConfigLovelaceDashboards extends LitElement {
.hass=${this.hass}
narrow
.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,
@@ -262,10 +284,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
),
action: () => this._handleEdit(dashboard),
},
]
: []),
...(this._canDelete(dashboard.url_path)
? [
{
label: this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.delete"
@@ -288,92 +306,43 @@ export class HaConfigLovelaceDashboards extends LitElement {
private _getItems = memoize(
(dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => {
const defaultMode = (
this.hass.panels?.lovelace?.config as LovelacePanelConfig
).mode;
const mode = (this.hass.panels?.lovelace?.config as LovelacePanelConfig)
.mode;
const isDefault = defaultUrlPath === "lovelace";
const result: DataTableItem[] = [
{
icon: "mdi:view-dashboard",
title: this.hass.localize("panel.states"),
default: isDefault,
show_in_sidebar: isDefault,
show_in_sidebar: true,
require_admin: false,
url_path: "lovelace",
mode: defaultMode,
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
type: this._localizeType("built_in"),
mode: mode,
filename: mode === "yaml" ? "ui-lovelace.yaml" : "",
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) {
result.push({
icon: this.hass.panels.light.icon || "mdi:lamps",
title: this.hass.localize("panel.light"),
PANEL_DASHBOARDS.forEach((panel) => {
const panelInfo = this.hass.panels[panel];
if (!panel) {
return;
}
const item: DataTableItem = {
icon: getPanelIcon(panelInfo),
title: getPanelTitle(this.hass, panelInfo) || panelInfo.url_path,
show_in_sidebar: true,
mode: "storage",
url_path: "light",
url_path: panelInfo.url_path,
filename: "",
default: false,
default: defaultUrlPath === panelInfo.url_path,
require_admin: false,
type: this._localizeType("built_in"),
});
}
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"),
});
}
type: "built_in",
localized_type: this._localizeType("built_in"),
};
result.push(item);
});
result.push(
...dashboards
@@ -386,7 +355,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
filename: "",
...dashboard,
default: defaultUrlPath === dashboard.url_path,
type: this._localizeType("user_created"),
type: "user_created",
localized_type: this._localizeType("user_created"),
}) satisfies DataTableItem
)
);
@@ -404,7 +374,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
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`
<hass-tabs-subpage-data-table
@@ -486,20 +456,15 @@ export class HaConfigLovelaceDashboards extends LitElement {
this._openDetailDialog(dashboard, urlPath);
}
private _canDelete(urlPath: string) {
return ![
"lovelace",
"energy",
"light",
"security",
"climate",
"home",
].includes(urlPath);
}
private _canEdit(urlPath: string) {
return !["light", "security", "climate", "home"].includes(urlPath);
}
private _handleSetAsDefault = async (item: DataTableItem) => {
if (item.default) {
return;
}
await saveFrontendSystemData(this.hass.connection, "core", {
...this.hass.systemData,
defaultPanel: item.url_path === DEFAULT_PANEL ? undefined : item.url_path,
});
};
private _handleDelete = async (item: DataTableItem) => {
const dashboard = this._dashboards.find(
@@ -581,10 +546,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
private async _deleteDashboard(
dashboard: LovelaceDashboard
): Promise<boolean> {
if (!this._canDelete(dashboard.url_path)) {
return false;
}
const confirm = await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.lovelace.dashboards.confirm_delete_title",

View File

@@ -270,7 +270,6 @@ export class HaManualScriptEditor extends LitElement {
@value-changed=${this._sidebarConfigChanged}
@sidebar-resized=${this._resizeSidebar}
@sidebar-resizing-stopped=${this._stopResizeSidebar}
@sidebar-reset-size=${this._resetSidebarWidth}
></ha-automation-sidebar>
</div>
</div>
@@ -619,16 +618,6 @@ export class HaManualScriptEditor extends LitElement {
this._prevSidebarWidthPx = undefined;
}
private _resetSidebarWidth(ev: Event) {
ev.stopPropagation();
this._prevSidebarWidthPx = undefined;
this._sidebarWidthPx = SIDEBAR_DEFAULT_WIDTH;
this.style.setProperty(
"--sidebar-dynamic-width",
`${this._sidebarWidthPx}px`
);
}
static get styles(): CSSResultGroup {
return [
saveFabStyles,

View File

@@ -1,13 +1,16 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../components/ha-divider";
import "../../components/ha-list-item";
import "../../components/ha-select";
import "../../components/ha-settings-row";
import { saveFrontendUserData } from "../../data/frontend";
import type { LovelaceDashboard } from "../../data/lovelace/dashboard";
import { fetchDashboards } from "../../data/lovelace/dashboard";
import type { HomeAssistant } from "../../types";
import { saveFrontendUserData } from "../../data/frontend";
import { getPanelTitle } from "../../data/panel";
import type { HomeAssistant, PanelInfo } from "../../types";
import { PANEL_DASHBOARDS } from "../config/lovelace/dashboards/ha-config-lovelace-dashboards";
const USE_SYSTEM_VALUE = "___use_system___";
@@ -25,7 +28,7 @@ class HaPickDashboardRow extends LitElement {
}
protected render(): TemplateResult {
const value = this.hass.userData?.default_panel || USE_SYSTEM_VALUE;
const value = this.hass.userData?.defaultPanel || USE_SYSTEM_VALUE;
return html`
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
@@ -47,12 +50,24 @@ class HaPickDashboardRow extends LitElement {
<ha-list-item .value=${USE_SYSTEM_VALUE}>
${this.hass.localize("ui.panel.profile.dashboard.system")}
</ha-list-item>
<ha-divider></ha-divider>
<ha-list-item value="lovelace">
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
</ha-list-item>
<ha-list-item value="home">
${this.hass.localize("ui.panel.profile.dashboard.home")}
</ha-list-item>
${PANEL_DASHBOARDS.map((panel) => {
const panelInfo = this.hass.panels[panel] as
| PanelInfo
| undefined;
if (!panelInfo) {
return nothing;
}
return html`
<ha-list-item value=${panelInfo.url_path}>
${getPanelTitle(this.hass, panelInfo)}
</ha-list-item>
`;
})}
<ha-divider></ha-divider>
${this._dashboards.map((dashboard) => {
if (!this.hass.user!.is_admin && dashboard.require_admin) {
return "";
@@ -84,12 +99,12 @@ class HaPickDashboardRow extends LitElement {
return;
}
const urlPath = value === USE_SYSTEM_VALUE ? undefined : value;
if (urlPath === this.hass.userData?.default_panel) {
if (urlPath === this.hass.userData?.defaultPanel) {
return;
}
saveFrontendUserData(this.hass.connection, "core", {
...this.hass.userData,
default_panel: urlPath,
defaultPanel: urlPath,
});
}
}

View File

@@ -2217,7 +2217,9 @@
"sidebar_toggle": "Sidebar toggle",
"edit_sidebar": "Edit sidebar",
"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": {
"home": {
@@ -3265,10 +3267,7 @@
"create_helper": "Create helper",
"no_helpers": "Looks like you don't have any helpers yet!",
"search": "Search {number} {number, plural,\n one {helper}\n other {helpers}\n}",
"error_information": "Error information",
"delete_confirm_title": "Delete helper?",
"delete_confirm_text": "Are you sure you want to delete {name}?",
"delete_failed": "Failed to delete helper"
"error_information": "Error information"
},
"dialog": {
"create": "Create",
@@ -3508,6 +3507,7 @@
"edit": "Edit",
"delete": "Delete",
"add_dashboard": "Add dashboard",
"set_as_default": "Set as default",
"type": {
"user_created": "User created",
"built_in": "Built-in"
@@ -3516,7 +3516,7 @@
"confirm_delete_title": "Delete {dashboard_title}?",
"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_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": {
"edit_dashboard": "Edit dashboard",
"new_dashboard": "Add new dashboard",
@@ -4796,8 +4796,7 @@
"headers": {
"name": "Name",
"type": "Type",
"file_name": "File name",
"usage_count": "In use"
"file_name": "File name"
},
"types": {
"automation": "Automation",