mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-21 16:57:09 +00:00
Compare commits
45 Commits
default_pa
...
add-automa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c15354302 | ||
|
|
fedcf85264 | ||
|
|
4b9a716df7 | ||
|
|
e319d116dc | ||
|
|
c8b1fd074c | ||
|
|
ec9bf7dd94 | ||
|
|
6d01949ff5 | ||
|
|
8a8d7dc77a | ||
|
|
5fc629f5c5 | ||
|
|
d070754bb9 | ||
|
|
a2a0f900a8 | ||
|
|
b18d3bff21 | ||
|
|
b06b51d03d | ||
|
|
022343c113 | ||
|
|
5ca10f4a38 | ||
|
|
33af5b6a08 | ||
|
|
0c89415c1c | ||
|
|
bd6b2c685c | ||
|
|
cdb37edc5f | ||
|
|
d68b1571ae | ||
|
|
a274727713 | ||
|
|
58a1aac131 | ||
|
|
4cdbda25ed | ||
|
|
618c6e1ef2 | ||
|
|
10b308b906 | ||
|
|
563c6f0fbf | ||
|
|
1dc7f0ab73 | ||
|
|
5c8954b714 | ||
|
|
e506e4b082 | ||
|
|
9628d69998 | ||
|
|
350d8e7a49 | ||
|
|
61a5b607e0 | ||
|
|
2f3a2b8418 | ||
|
|
12c1e4eec4 | ||
|
|
b704b621f2 | ||
|
|
0eb993a8df | ||
|
|
cda97766ac | ||
|
|
e6936a9294 | ||
|
|
5ad73287a2 | ||
|
|
591b464508 | ||
|
|
acf963d38c | ||
|
|
68f383c293 | ||
|
|
0a25d8106c | ||
|
|
5c3cf17df9 | ||
|
|
e905fa6f23 |
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.0",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
|
||||
@@ -59,8 +59,7 @@ export class HaAuthFlow extends LitElement {
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated && this.clientId === genClientId()) {
|
||||
// Preselect store token when logging in to own instance
|
||||
if (!this.hasUpdated) {
|
||||
this._storeToken = this.initStoreToken;
|
||||
}
|
||||
|
||||
|
||||
@@ -597,15 +597,10 @@ export class HaChartBase extends LitElement {
|
||||
aria: { show: true },
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
toolbox: {
|
||||
top: Number.MAX_SAFE_INTEGER,
|
||||
left: Number.MAX_SAFE_INTEGER,
|
||||
top: Infinity,
|
||||
left: Infinity,
|
||||
feature: {
|
||||
dataZoom: {
|
||||
show: true,
|
||||
yAxisIndex: false,
|
||||
filterMode: "none",
|
||||
showTitle: false,
|
||||
},
|
||||
dataZoom: { show: true, yAxisIndex: false, filterMode: "none" },
|
||||
},
|
||||
iconStyle: { opacity: 0 },
|
||||
},
|
||||
|
||||
@@ -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,7 +103,6 @@ export class HaIconOverflowMenu extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
cursor: initial;
|
||||
}
|
||||
div[role="separator"] {
|
||||
border-right: 1px solid var(--divider-color);
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface DisplayItem {
|
||||
label: string;
|
||||
description?: string;
|
||||
disableSorting?: boolean;
|
||||
disableHiding?: boolean;
|
||||
}
|
||||
|
||||
export interface DisplayValue {
|
||||
@@ -102,7 +101,6 @@ export class HaItemDisplayEditor extends LitElement {
|
||||
icon,
|
||||
iconPath,
|
||||
disableSorting,
|
||||
disableHiding,
|
||||
} = item;
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
@@ -157,21 +155,18 @@ export class HaItemDisplayEditor extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${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}
|
||||
<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 && !disableSorting
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
|
||||
@@ -154,7 +154,10 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
return this._getLabelsMemoized(
|
||||
this.hass,
|
||||
this.hass.states,
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities,
|
||||
this._labels,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
|
||||
@@ -36,11 +36,6 @@ 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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 { getPanelIcon, getPanelTitle } from "../data/panel";
|
||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
||||
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
@@ -43,8 +43,13 @@ const createViewNavigationItem = (
|
||||
|
||||
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
||||
path: `/${panel.url_path}`,
|
||||
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||
title: getPanelTitle(hass, panel) || "",
|
||||
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) : ""),
|
||||
});
|
||||
|
||||
@customElement("ha-navigation-picker")
|
||||
|
||||
@@ -192,7 +192,7 @@ export class HaPickerComboBox extends LitElement {
|
||||
@focus=${this._focusList}
|
||||
@visibilityChanged=${this._visibilityChanged}
|
||||
>
|
||||
</lit-virtualizer> `;
|
||||
</lit-virtualizer>`;
|
||||
}
|
||||
|
||||
private _renderSectionButtons() {
|
||||
|
||||
28
src/components/ha-section-title.ts
Normal file
28
src/components/ha-section-title.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-section-title")
|
||||
class HaSectionTitle extends LitElement {
|
||||
protected render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-section-title": HaSectionTitle;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
@@ -33,14 +33,7 @@ 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 {
|
||||
FIXED_PANELS,
|
||||
getDefaultPanelUrlPath,
|
||||
getPanelIcon,
|
||||
getPanelIconPath,
|
||||
getPanelTitle,
|
||||
SHOW_AFTER_SPACER_PANELS,
|
||||
} from "../data/panel";
|
||||
import { getDefaultPanelUrlPath } from "../data/panel";
|
||||
import type { PersistentNotification } from "../data/persistent_notification";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||
@@ -61,6 +54,8 @@ 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 = {
|
||||
@@ -72,7 +67,7 @@ const SORT_VALUE_URL_PATHS = {
|
||||
config: 11,
|
||||
};
|
||||
|
||||
export const PANEL_ICON_PATHS = {
|
||||
export const PANEL_ICONS = {
|
||||
calendar: mdiCalendar,
|
||||
"developer-tools": mdiHammer,
|
||||
energy: mdiLightningBolt,
|
||||
@@ -160,23 +155,16 @@ export const computePanels = memoizeOne(
|
||||
const beforeSpacer: PanelInfo[] = [];
|
||||
const afterSpacer: PanelInfo[] = [];
|
||||
|
||||
const allPanels = Object.values(panels).filter(
|
||||
(panel) => !FIXED_PANELS.includes(panel.url_path)
|
||||
);
|
||||
|
||||
allPanels.forEach((panel) => {
|
||||
const isDefaultPanel = panel.url_path === defaultPanel;
|
||||
|
||||
Object.values(panels).forEach((panel) => {
|
||||
if (
|
||||
!isDefaultPanel &&
|
||||
(!panel.title ||
|
||||
hiddenPanels.includes(panel.url_path) ||
|
||||
(panel.default_visible === false &&
|
||||
!panelsOrder.includes(panel.url_path)))
|
||||
hiddenPanels.includes(panel.url_path) ||
|
||||
(!panel.title && panel.url_path !== defaultPanel) ||
|
||||
(panel.default_visible === false &&
|
||||
!panelsOrder.includes(panel.url_path))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
(SHOW_AFTER_SPACER_PANELS.includes(panel.url_path)
|
||||
(SHOW_AFTER_SPACER.includes(panel.url_path)
|
||||
? afterSpacer
|
||||
: beforeSpacer
|
||||
).push(panel);
|
||||
@@ -264,7 +252,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
// Show the supervisor as being part of configuration
|
||||
const selectedPanel = this.hass.panelUrl;
|
||||
const selectedPanel = this.route.path?.startsWith("/hassio/")
|
||||
? "config"
|
||||
: this.hass.panelUrl;
|
||||
|
||||
// prettier-ignore
|
||||
return html`
|
||||
@@ -407,9 +397,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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -423,6 +413,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
this.hass.locale
|
||||
);
|
||||
|
||||
// prettier-ignore
|
||||
return html`
|
||||
<ha-md-list
|
||||
class="ha-scrollbar"
|
||||
@@ -431,42 +422,61 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
@scroll=${this._listboxScroll}
|
||||
@keydown=${this._listboxKeydown}
|
||||
>
|
||||
${this._renderPanels(beforeSpacer, selectedPanel)}
|
||||
${this._renderPanels(beforeSpacer, selectedPanel, defaultPanel)}
|
||||
${this._renderSpacer()}
|
||||
${this._renderPanels(afterSpacer, selectedPanel)}
|
||||
${this.hass.user?.is_admin
|
||||
? this._renderConfiguration(selectedPanel)
|
||||
: this._renderExternalConfiguration()}
|
||||
${this._renderPanels(afterSpacer, selectedPanel, defaultPanel)}
|
||||
${this._renderExternalConfiguration()}
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
|
||||
private _renderPanels(
|
||||
panels: PanelInfo[],
|
||||
selectedPanel: string,
|
||||
defaultPanel: string
|
||||
) {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(panel, panel.url_path === selectedPanel)
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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 _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 _renderDivider() {
|
||||
@@ -477,15 +487,10 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
return html`<div class="spacer" disabled></div>`;
|
||||
}
|
||||
|
||||
private _renderConfiguration(selectedPanel: string) {
|
||||
if (!this.hass.user?.is_admin) {
|
||||
return nothing;
|
||||
}
|
||||
const isSelected =
|
||||
selectedPanel === "config" || this.route.path?.startsWith("/hassio/");
|
||||
private _renderConfiguration(title: string | null, selectedPanel: string) {
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
class="configuration ${classMap({ selected: isSelected })}"
|
||||
class="configuration${selectedPanel === "config" ? " selected" : ""}"
|
||||
type="button"
|
||||
href="/config"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@@ -499,17 +504,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
${this._updatesCount + this._issuesCount}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<span class="item-text" slot="headline"
|
||||
>${this.hass.localize("panel.config")}</span
|
||||
>
|
||||
: ""}
|
||||
<span class="item-text" slot="headline">${title}</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>
|
||||
`;
|
||||
}
|
||||
@@ -532,20 +535,19 @@ 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
|
||||
@@ -553,7 +555,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
type="link"
|
||||
class=${classMap({
|
||||
user: true,
|
||||
selected: isSelected,
|
||||
selected: selectedPanel === "profile",
|
||||
rtl: isRTL,
|
||||
})}
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@@ -564,30 +566,31 @@ 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() {
|
||||
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>
|
||||
`;
|
||||
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>
|
||||
`
|
||||
: ""}`;
|
||||
}
|
||||
|
||||
private _handleExternalAppConfiguration(ev: Event) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
areaMeetsFilter,
|
||||
deviceMeetsFilter,
|
||||
entityRegMeetsFilter,
|
||||
getTargetComboBoxItemType,
|
||||
type TargetType,
|
||||
type TargetTypeFloorless,
|
||||
} from "../data/target";
|
||||
@@ -47,7 +48,6 @@ import "./ha-tree-indicator";
|
||||
import "./target-picker/ha-target-picker-item-group";
|
||||
import "./target-picker/ha-target-picker-value-chip";
|
||||
|
||||
const EMPTY_SEARCH = "___EMPTY_SEARCH___";
|
||||
const SEPARATOR = "________";
|
||||
const CREATE_ID = "___create-new-entity___";
|
||||
|
||||
@@ -634,35 +634,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _getRowType = (
|
||||
item:
|
||||
| PickerComboBoxItem
|
||||
| (FloorComboBoxItem & { last?: boolean | undefined })
|
||||
| EntityComboBoxItem
|
||||
| DevicePickerItem
|
||||
) => {
|
||||
if (
|
||||
(item as FloorComboBoxItem).type === "area" ||
|
||||
(item as FloorComboBoxItem).type === "floor"
|
||||
) {
|
||||
return (item as FloorComboBoxItem).type;
|
||||
}
|
||||
|
||||
if ("domain" in item) {
|
||||
return "device";
|
||||
}
|
||||
|
||||
if ("stateObj" in item) {
|
||||
return "entity";
|
||||
}
|
||||
|
||||
if (item.id === EMPTY_SEARCH) {
|
||||
return "empty";
|
||||
}
|
||||
|
||||
return "label";
|
||||
};
|
||||
|
||||
private _sectionTitleFunction = ({
|
||||
firstIndex,
|
||||
lastIndex,
|
||||
@@ -686,7 +657,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = this._getRowType(firstItem as PickerComboBoxItem);
|
||||
const type = getTargetComboBoxItemType(firstItem as PickerComboBoxItem);
|
||||
const translationType:
|
||||
| "areas"
|
||||
| "entities"
|
||||
@@ -858,7 +829,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
if (!filterType || filterType === "label") {
|
||||
let labels = this._getLabelsMemoized(
|
||||
this.hass,
|
||||
this.hass.states,
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities,
|
||||
this._labelRegistry,
|
||||
includeDomains,
|
||||
undefined,
|
||||
@@ -974,7 +948,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const type = this._getRowType(item);
|
||||
const type = getTargetComboBoxItemType(item);
|
||||
let hasFloor = false;
|
||||
let rtl = false;
|
||||
let showEntityId = false;
|
||||
|
||||
@@ -235,7 +235,7 @@ export class HaWaDialog extends LitElement {
|
||||
}
|
||||
|
||||
:host([width="large"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
|
||||
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
|
||||
}
|
||||
|
||||
:host([width="full"]) wa-dialog {
|
||||
|
||||
@@ -21,11 +21,52 @@ export interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||
area?: AreaRegistryEntry;
|
||||
}
|
||||
|
||||
export interface FloorNestedComboBoxItem extends PickerComboBoxItem {
|
||||
floor?: FloorRegistryEntry;
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
export interface UnassignedAreasFloorComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
export interface AreaFloorValue {
|
||||
id: string;
|
||||
type: "floor" | "area";
|
||||
}
|
||||
|
||||
export const getAreasNestedInFloors = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
haAreas: HomeAssistant["areas"],
|
||||
haDevices: HomeAssistant["devices"],
|
||||
haEntities: HomeAssistant["entities"],
|
||||
formatId: (value: AreaFloorValue) => string,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
includeDeviceClasses?: string[],
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeAreas?: string[],
|
||||
excludeFloors?: string[]
|
||||
) =>
|
||||
getAreasAndFloorsItems(
|
||||
states,
|
||||
haFloors,
|
||||
haAreas,
|
||||
haDevices,
|
||||
haEntities,
|
||||
formatId,
|
||||
includeDomains,
|
||||
excludeDomains,
|
||||
includeDeviceClasses,
|
||||
deviceFilter,
|
||||
entityFilter,
|
||||
excludeAreas,
|
||||
excludeFloors,
|
||||
true
|
||||
) as (FloorNestedComboBoxItem | UnassignedAreasFloorComboBoxItem)[];
|
||||
|
||||
export const getAreasAndFloors = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
@@ -40,7 +81,43 @@ export const getAreasAndFloors = (
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeAreas?: string[],
|
||||
excludeFloors?: string[]
|
||||
): FloorComboBoxItem[] => {
|
||||
) =>
|
||||
getAreasAndFloorsItems(
|
||||
states,
|
||||
haFloors,
|
||||
haAreas,
|
||||
haDevices,
|
||||
haEntities,
|
||||
formatId,
|
||||
includeDomains,
|
||||
excludeDomains,
|
||||
includeDeviceClasses,
|
||||
deviceFilter,
|
||||
entityFilter,
|
||||
excludeAreas,
|
||||
excludeFloors
|
||||
) as FloorComboBoxItem[];
|
||||
|
||||
export const getAreasAndFloorsItems = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
haAreas: HomeAssistant["areas"],
|
||||
haDevices: HomeAssistant["devices"],
|
||||
haEntities: HomeAssistant["entities"],
|
||||
formatId: (value: AreaFloorValue) => string,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
includeDeviceClasses?: string[],
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeAreas?: string[],
|
||||
excludeFloors?: string[],
|
||||
nested = false
|
||||
): (
|
||||
| FloorComboBoxItem
|
||||
| FloorNestedComboBoxItem
|
||||
| UnassignedAreasFloorComboBoxItem
|
||||
)[] => {
|
||||
const floors = Object.values(haFloors);
|
||||
const areas = Object.values(haAreas);
|
||||
const devices = Object.values(haDevices);
|
||||
@@ -181,7 +258,11 @@ export const getAreasAndFloors = (
|
||||
|
||||
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
|
||||
|
||||
const items: FloorComboBoxItem[] = [];
|
||||
const items: (
|
||||
| FloorComboBoxItem
|
||||
| FloorNestedComboBoxItem
|
||||
| UnassignedAreasFloorComboBoxItem
|
||||
)[] = [];
|
||||
|
||||
hierarchy.floors.forEach((f) => {
|
||||
const floor = haFloors[f.id];
|
||||
@@ -196,7 +277,7 @@ export const getAreasAndFloors = (
|
||||
})
|
||||
.flat();
|
||||
|
||||
items.push({
|
||||
const floorItem: FloorComboBoxItem | FloorNestedComboBoxItem = {
|
||||
id: formatId({ id: floor.floor_id, type: "floor" }),
|
||||
type: "floor",
|
||||
primary: floorName,
|
||||
@@ -208,41 +289,53 @@ export const getAreasAndFloors = (
|
||||
...floor.aliases,
|
||||
...areaSearchLabels,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
items.push(
|
||||
...floorAreas.map((area) => {
|
||||
const areaName = computeAreaName(area);
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: [
|
||||
area.area_id,
|
||||
...(areaName ? [areaName] : []),
|
||||
...area.aliases,
|
||||
],
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
items.push(floorItem);
|
||||
|
||||
items.push(
|
||||
...hierarchy.areas.map((areaId) => {
|
||||
const area = haAreas[areaId];
|
||||
const areaName = computeAreaName(area) || area.area_id;
|
||||
const floorAreasItems = floorAreas.map((area) => {
|
||||
const areaName = computeAreaName(area);
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName,
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||
search_labels: [
|
||||
area.area_id,
|
||||
...(areaName ? [areaName] : []),
|
||||
...area.aliases,
|
||||
],
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
if (nested && floor) {
|
||||
(floorItem as unknown as FloorNestedComboBoxItem).areas = floorAreasItems;
|
||||
} else {
|
||||
items.push(...floorAreasItems);
|
||||
}
|
||||
});
|
||||
|
||||
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
||||
const area = haAreas[areaId];
|
||||
const areaName = computeAreaName(area) || area.area_id;
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||
};
|
||||
});
|
||||
|
||||
if (nested && unassignedAreaItems.length) {
|
||||
items.push({
|
||||
areas: unassignedAreaItems,
|
||||
} as UnassignedAreasFloorComboBoxItem);
|
||||
} else {
|
||||
items.push(...unassignedAreaItems);
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type { EntityRegistryEntry } from "./entity_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "./entity_registry";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
export { subscribeAreaRegistry } from "./ws-area_registry";
|
||||
@@ -18,7 +21,10 @@ export interface AreaRegistryEntry extends RegistryEntry {
|
||||
temperature_entity_id: string | null;
|
||||
}
|
||||
|
||||
export type AreaEntityLookup = Record<string, EntityRegistryEntry[]>;
|
||||
export type AreaEntityLookup = Record<
|
||||
string,
|
||||
(EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
>;
|
||||
|
||||
export type AreaDeviceLookup = Record<string, DeviceRegistryEntry[]>;
|
||||
|
||||
@@ -69,11 +75,17 @@ export const reorderAreaRegistryEntries = (
|
||||
});
|
||||
|
||||
export const getAreaEntityLookup = (
|
||||
entities: EntityRegistryEntry[]
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
): AreaEntityLookup => {
|
||||
const areaEntityLookup: AreaEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.area_id) {
|
||||
if (
|
||||
!entity.area_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.area_id in areaEntityLookup)) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { formatTime } from "../common/datetime/format_time";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import { fileDownload } from "../util/file_download";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import type { BackupManagerState, ManagerStateEvent } from "./backup_manager";
|
||||
@@ -415,7 +414,7 @@ ${hass.auth.data.hassUrl}
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.encryption_key")}
|
||||
${encryptionKey}
|
||||
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: documentationUrl(hass, "/more-info/backup-emergency-kit") })}`);
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: "https://www.home-assistant.io/more-info/backup-emergency-kit" })}`);
|
||||
|
||||
export const geneateEmergencyKitFileName = (
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -50,7 +50,10 @@ export type DeviceEntityDisplayLookup = Record<
|
||||
EntityRegistryDisplayEntry[]
|
||||
>;
|
||||
|
||||
export type DeviceEntityLookup = Record<string, EntityRegistryEntry[]>;
|
||||
export type DeviceEntityLookup = Record<
|
||||
string,
|
||||
(EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
>;
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: string | null;
|
||||
@@ -107,11 +110,17 @@ export const sortDeviceRegistryByName = (
|
||||
);
|
||||
|
||||
export const getDeviceEntityLookup = (
|
||||
entities: EntityRegistryEntry[]
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
): DeviceEntityLookup => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
if (
|
||||
!entity.device_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
|
||||
@@ -7,18 +7,14 @@ export interface CoreFrontendUserData {
|
||||
}
|
||||
|
||||
export interface SidebarFrontendUserData {
|
||||
panelOrder?: string[];
|
||||
hiddenPanels?: string[];
|
||||
panelOrder: string[];
|
||||
hiddenPanels: string[];
|
||||
}
|
||||
|
||||
export interface CoreFrontendSystemData {
|
||||
defaultPanel?: string;
|
||||
}
|
||||
|
||||
export interface HomeFrontendSystemData {
|
||||
favorite_entities?: string[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface FrontendUserData {
|
||||
core: CoreFrontendUserData;
|
||||
@@ -26,7 +22,6 @@ declare global {
|
||||
}
|
||||
interface FrontendSystemData {
|
||||
core: CoreFrontendSystemData;
|
||||
home: HomeFrontendSystemData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const integrationsWithPanel = {
|
||||
bluetooth: "config/bluetooth",
|
||||
@@ -25,6 +25,8 @@ export type IntegrationType =
|
||||
| "entity"
|
||||
| "system";
|
||||
|
||||
export type DomainManifestLookup = Record<string, IntegrationManifest>;
|
||||
|
||||
export interface IntegrationManifest {
|
||||
is_built_in: boolean;
|
||||
overwrites_built_in?: boolean;
|
||||
|
||||
@@ -101,7 +101,10 @@ export const deleteLabelRegistryEntry = (
|
||||
});
|
||||
|
||||
export const getLabels = (
|
||||
hass: HomeAssistant,
|
||||
hassStates: HomeAssistant["states"],
|
||||
hassAreas: HomeAssistant["areas"],
|
||||
hassDevices: HomeAssistant["devices"],
|
||||
hassEntities: HomeAssistant["entities"],
|
||||
labels?: LabelRegistryEntry[],
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
@@ -115,8 +118,8 @@ export const getLabels = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const devices = Object.values(hass.devices);
|
||||
const entities = Object.values(hass.entities);
|
||||
const devices = Object.values(hassDevices);
|
||||
const entities = Object.values(hassEntities);
|
||||
|
||||
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
|
||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||
@@ -170,7 +173,7 @@ export const getLabels = (
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
const stateObj = hassStates[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
@@ -181,7 +184,7 @@ export const getLabels = (
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
const stateObj = hassStates[entity.entity_id];
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
@@ -200,7 +203,7 @@ export const getLabels = (
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
const stateObj = hassStates[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
@@ -208,7 +211,7 @@ export const getLabels = (
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = hass.states[entity.entity_id];
|
||||
const stateObj = hassStates[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
@@ -245,7 +248,7 @@ export const getLabels = (
|
||||
|
||||
if (areaIds) {
|
||||
areaIds.forEach((areaId) => {
|
||||
const area = hass.areas[areaId];
|
||||
const area = hassAreas[areaId];
|
||||
area.labels.forEach((label) => usedLabels.add(label));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
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. */
|
||||
@@ -72,7 +60,7 @@ export const getPanelTitleFromUrlPath = (
|
||||
return getPanelTitle(hass, panel);
|
||||
};
|
||||
|
||||
export const getPanelIcon = (panel: PanelInfo): string | undefined => {
|
||||
export const getPanelIcon = (panel: PanelInfo): string | null => {
|
||||
if (!panel.icon) {
|
||||
switch (panel.component_name) {
|
||||
case "profile":
|
||||
@@ -82,24 +70,5 @@ export const getPanelIcon = (panel: PanelInfo): string | undefined => {
|
||||
}
|
||||
}
|
||||
|
||||
return panel.icon || undefined;
|
||||
return panel.icon;
|
||||
};
|
||||
|
||||
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,11 +1,18 @@
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { FloorComboBoxItem } from "./area_floor";
|
||||
import type { AreaRegistryEntry } from "./area_registry";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type { DevicePickerItem, DeviceRegistryEntry } from "./device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import type { EntityRegistryDisplayEntry } from "./entity_registry";
|
||||
import type {
|
||||
EntityComboBoxItem,
|
||||
EntityRegistryDisplayEntry,
|
||||
} from "./entity_registry";
|
||||
|
||||
export const TARGET_SEPARATOR = "________";
|
||||
|
||||
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
|
||||
export type TargetTypeFloorless = Exclude<TargetType, "floor">;
|
||||
@@ -35,6 +42,28 @@ export const extractFromTarget = async (
|
||||
target,
|
||||
});
|
||||
|
||||
export const getTriggersForTarget = async (
|
||||
callWS: HomeAssistant["callWS"],
|
||||
target: HassServiceTarget,
|
||||
expandGroup = true
|
||||
) =>
|
||||
callWS<string[]>({
|
||||
type: "get_triggers_for_target",
|
||||
target,
|
||||
expand_group: expandGroup,
|
||||
});
|
||||
|
||||
export const getServicesForTarget = async (
|
||||
callWS: HomeAssistant["callWS"],
|
||||
target: HassServiceTarget,
|
||||
expandGroup = true
|
||||
) =>
|
||||
callWS<string[]>({
|
||||
type: "get_services_for_target",
|
||||
target,
|
||||
expand_group: expandGroup,
|
||||
});
|
||||
|
||||
export const areaMeetsFilter = (
|
||||
area: AreaRegistryEntry,
|
||||
devices: HomeAssistant["devices"],
|
||||
@@ -162,3 +191,32 @@ export const entityRegMeetsFilter = (
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getTargetComboBoxItemType = (
|
||||
item:
|
||||
| PickerComboBoxItem
|
||||
| (FloorComboBoxItem & { last?: boolean | undefined })
|
||||
| EntityComboBoxItem
|
||||
| DevicePickerItem
|
||||
) => {
|
||||
if (
|
||||
(item as FloorComboBoxItem).type === "area" ||
|
||||
(item as FloorComboBoxItem).type === "floor"
|
||||
) {
|
||||
return (item as FloorComboBoxItem).type;
|
||||
}
|
||||
|
||||
if ("domain" in item) {
|
||||
return "device";
|
||||
}
|
||||
|
||||
if ("stateObj" in item) {
|
||||
return "entity";
|
||||
}
|
||||
|
||||
if (item.id === "___EMPTY_SEARCH___") {
|
||||
return "empty";
|
||||
}
|
||||
|
||||
return "label";
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiClose, mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||
import { mdiClose } 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,30 +9,18 @@ import "../../components/ha-dialog-header";
|
||||
import "../../components/ha-fade-in";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-items-display-editor";
|
||||
import type {
|
||||
DisplayItem,
|
||||
DisplayValue,
|
||||
} from "../../components/ha-items-display-editor";
|
||||
import "../../components/ha-md-button-menu";
|
||||
import type { DisplayValue } from "../../components/ha-items-display-editor";
|
||||
import "../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||
import "../../components/ha-md-menu-item";
|
||||
import { computePanels } from "../../components/ha-sidebar";
|
||||
import { computePanels, PANEL_ICONS } 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 {
|
||||
@@ -117,53 +105,48 @@ class DialogEditSidebar extends LitElement {
|
||||
this.hass.locale
|
||||
);
|
||||
|
||||
const orderSet = new Set(this._order);
|
||||
const hiddenSet = new Set(this._hidden);
|
||||
|
||||
// Add default hidden panels that are missing in hidden
|
||||
for (const panel of panels) {
|
||||
if (
|
||||
panel.default_visible === false &&
|
||||
!orderSet.has(panel.url_path) &&
|
||||
!hiddenSet.has(panel.url_path)
|
||||
!this._order.includes(panel.url_path) &&
|
||||
!this._hidden.includes(panel.url_path)
|
||||
) {
|
||||
hiddenSet.add(panel.url_path);
|
||||
this._hidden.push(panel.url_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenSet.has(defaultPanel)) {
|
||||
hiddenSet.delete(defaultPanel);
|
||||
}
|
||||
|
||||
const hiddenPanels = Array.from(hiddenSet);
|
||||
|
||||
const items = [
|
||||
...beforeSpacer,
|
||||
...panels.filter((panel) => hiddenPanels.includes(panel.url_path)),
|
||||
...afterSpacer,
|
||||
].map<DisplayItem>((panel) => ({
|
||||
...panels.filter((panel) => this._hidden!.includes(panel.url_path)),
|
||||
...afterSpacer.filter((panel) => panel.url_path !== "config"),
|
||||
].map((panel) => ({
|
||||
value: panel.url_path,
|
||||
label:
|
||||
(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,
|
||||
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",
|
||||
}));
|
||||
|
||||
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>
|
||||
`;
|
||||
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>`;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -188,22 +171,6 @@ 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">
|
||||
@@ -227,26 +194,6 @@ 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, {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { atLeastVersion } from "../common/config/version";
|
||||
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
||||
import "../components/ha-card";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./hass-subpage";
|
||||
|
||||
@@ -58,7 +57,7 @@ class SupervisorErrorScreen extends LitElement {
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/help/")}
|
||||
href="https://www.home-assistant.io/help/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-card";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { showAppDialog } from "./dialogs/show-app-dialog";
|
||||
import { showCommunityDialog } from "./dialogs/show-community-dialog";
|
||||
@@ -23,10 +22,7 @@ class OnboardingWelcomeLinks extends LitElement {
|
||||
return html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/blog/2016/01/19/perfect-home-automation/"
|
||||
)}
|
||||
href="https://www.home-assistant.io/blog/2016/01/19/perfect-home-automation/"
|
||||
>
|
||||
<onboarding-welcome-link
|
||||
noninteractive
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ import { mdiClose, mdiOpenInNew } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-code-editor";
|
||||
@@ -141,7 +140,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
<ha-button
|
||||
size="small"
|
||||
appearance="plain"
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
|
||||
@@ -299,7 +299,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
href="https://www.home-assistant.io/get-blueprints"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
size="small"
|
||||
|
||||
@@ -2,7 +2,6 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-alert";
|
||||
import type { EnergyValidationIssue } from "../../../../data/energy";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-energy-validation-result")
|
||||
@@ -30,10 +29,7 @@ class EnergyValidationMessage extends LitElement {
|
||||
)}
|
||||
${issue.type === "recorder_untracked"
|
||||
? html`(<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/recorder#configure-filter"
|
||||
)}
|
||||
href="https://www.home-assistant.io/integrations/recorder#configure-filter"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.common.learn_more")}</a
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
mdiBookshelf,
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiOpenInNew,
|
||||
} from "@mdi/js";
|
||||
import { mdiBookshelf, mdiCog, mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -13,11 +7,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import {
|
||||
deleteApplicationCredential,
|
||||
fetchApplicationCredentialsConfigEntry,
|
||||
} from "../../../data/application_credential";
|
||||
import { deleteConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
ATTENTION_SOURCES,
|
||||
DISCOVERY_SOURCES,
|
||||
@@ -26,10 +15,7 @@ import {
|
||||
} from "../../../data/config_flow";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
@@ -74,7 +60,7 @@ export class HaConfigFlowCard extends LitElement {
|
||||
: "ui.common.add"
|
||||
)}
|
||||
</ha-button>
|
||||
${this.flow.context.configuration_url || this.manifest || attention
|
||||
${this.flow.context.configuration_url || this.manifest
|
||||
? html`<ha-button-menu slot="header-button">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -132,22 +118,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${attention
|
||||
? html`<ha-list-item
|
||||
class="warning"
|
||||
graphic="icon"
|
||||
@click=${this._handleDelete}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-integration-action-card>
|
||||
@@ -205,109 +175,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
// Return an application credentials id for this config entry to prompt the
|
||||
// user for removal. This is best effort so we don't stop overall removal
|
||||
// if the integration isn't loaded or there is some other error.
|
||||
private async _fetchApplicationCredentials(entryId: string) {
|
||||
try {
|
||||
return (await fetchApplicationCredentialsConfigEntry(this.hass, entryId))
|
||||
.application_credentials_id;
|
||||
} catch (_err: any) {
|
||||
// We won't prompt the user to remove credentials
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeApplicationCredential(applicationCredentialsId: string) {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_title"
|
||||
),
|
||||
text: html`${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_prompt"
|
||||
)},
|
||||
<br />
|
||||
<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_detail"
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/application_credentials"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.learn_more"
|
||||
)}
|
||||
</a>`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteApplicationCredential(this.hass, applicationCredentialsId);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.application_credentials.delete_error_title"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleDelete() {
|
||||
const entryId = this.flow.context.entry_id;
|
||||
|
||||
if (!entryId) {
|
||||
// This shouldn't happen for reauth flows, but handle gracefully
|
||||
return;
|
||||
}
|
||||
|
||||
const applicationCredentialsId =
|
||||
await this._fetchApplicationCredentials(entryId);
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||
{ title: localizeConfigFlowTitle(this.hass.localize, this.flow) }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_text"
|
||||
),
|
||||
confirmText: this.hass!.localize("ui.common.delete"),
|
||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteConfigEntry(this.hass, entryId);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (applicationCredentialsId) {
|
||||
this._removeApplicationCredential(applicationCredentialsId);
|
||||
}
|
||||
|
||||
this._handleFlowUpdated();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
@@ -324,9 +191,6 @@ export class HaConfigFlowCard extends LitElement {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--ha-card-border-color: var(--error-color);
|
||||
}
|
||||
.warning {
|
||||
--mdc-theme-text-primary-on-background: var(--error-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { showLabsPreviewFeatureEnableDialog } from "./show-dialog-labs-preview-feature-enable";
|
||||
import {
|
||||
@@ -101,7 +100,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
? html`
|
||||
<a
|
||||
slot="toolbar-icon"
|
||||
href=${documentationUrl(this.hass, "/integrations/labs/")}
|
||||
href="https://www.home-assistant.io/integrations/labs/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
.title=${this.hass.localize("ui.common.help")}
|
||||
@@ -125,7 +124,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.labs.empty.description"
|
||||
)}
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/integrations/labs/")}
|
||||
href="https://www.home-assistant.io/integrations/labs/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -8,13 +8,16 @@ 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")
|
||||
@@ -58,8 +61,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
if (!this._params || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
const defaultPanelUrlPath =
|
||||
this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
|
||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||
const isLovelaceDashboard = this._params.urlPath === "lovelace";
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@@ -84,9 +88,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
||||
)
|
||||
: isLovelaceDashboard
|
||||
: this._params.urlPath === "lovelace"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_lovelace"
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_default"
|
||||
)
|
||||
: html`
|
||||
<ha-form
|
||||
@@ -115,9 +119,24 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
)}
|
||||
</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
|
||||
slot="primaryAction"
|
||||
@click=${this._updateDashboard}
|
||||
@@ -235,6 +254,40 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
private async _toggleDefault() {
|
||||
const urlPath = this._params?.urlPath;
|
||||
if (!urlPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultPanel = this.hass.systemData?.defaultPanel || 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,
|
||||
defaultPanel: urlPath === defaultPanel ? undefined : urlPath,
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateDashboard() {
|
||||
if (this._params?.urlPath && !this._params.dashboard?.id) {
|
||||
this.closeDialog();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiCheckCircleOutline,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiHomeCircleOutline,
|
||||
mdiHomeEdit,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
@@ -11,6 +10,7 @@ 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,7 +29,6 @@ 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 {
|
||||
@@ -46,11 +45,7 @@ import {
|
||||
fetchDashboards,
|
||||
updateDashboard,
|
||||
} from "../../../../data/lovelace/dashboard";
|
||||
import {
|
||||
DEFAULT_PANEL,
|
||||
getPanelIcon,
|
||||
getPanelTitle,
|
||||
} from "../../../../data/panel";
|
||||
import { DEFAULT_PANEL } from "../../../../data/panel";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -61,21 +56,12 @@ 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;
|
||||
};
|
||||
|
||||
@@ -126,7 +112,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
state: false,
|
||||
subscribe: false,
|
||||
})
|
||||
private _activeGrouping?: string = "localized_type";
|
||||
private _activeGrouping?: string = "type";
|
||||
|
||||
@storage({
|
||||
key: "lovelace-dashboards-table-collapsed",
|
||||
@@ -181,7 +167,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=${mdiHomeCircleOutline}
|
||||
.path=${mdiCheckCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip
|
||||
.for="default-icon-${dashboard.title}"
|
||||
@@ -197,7 +183,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
},
|
||||
};
|
||||
|
||||
columns.localized_type = {
|
||||
columns.type = {
|
||||
title: localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.type"
|
||||
),
|
||||
@@ -267,15 +253,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
.hass=${this.hass}
|
||||
narrow
|
||||
.items=${[
|
||||
{
|
||||
path: mdiHomeEdit,
|
||||
label: localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.set_as_default"
|
||||
),
|
||||
action: () => this._handleSetAsDefault(dashboard),
|
||||
disabled: dashboard.default,
|
||||
},
|
||||
...(dashboard.type === "user_created"
|
||||
...(this._canEdit(dashboard.url_path)
|
||||
? [
|
||||
{
|
||||
path: mdiPencil,
|
||||
@@ -284,6 +262,10 @@ 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"
|
||||
@@ -306,43 +288,92 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
|
||||
private _getItems = memoize(
|
||||
(dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => {
|
||||
const mode = (this.hass.panels?.lovelace?.config as LovelacePanelConfig)
|
||||
.mode;
|
||||
const defaultMode = (
|
||||
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: true,
|
||||
show_in_sidebar: isDefault,
|
||||
require_admin: false,
|
||||
url_path: "lovelace",
|
||||
mode: mode,
|
||||
filename: mode === "yaml" ? "ui-lovelace.yaml" : "",
|
||||
type: "built_in",
|
||||
localized_type: this._localizeType("built_in"),
|
||||
mode: defaultMode,
|
||||
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
|
||||
type: this._localizeType("built_in"),
|
||||
},
|
||||
];
|
||||
|
||||
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,
|
||||
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: panelInfo.url_path,
|
||||
url_path: "energy",
|
||||
filename: "",
|
||||
default: defaultUrlPath === panelInfo.url_path,
|
||||
default: false,
|
||||
require_admin: false,
|
||||
type: "built_in",
|
||||
localized_type: this._localizeType("built_in"),
|
||||
};
|
||||
result.push(item);
|
||||
});
|
||||
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"),
|
||||
show_in_sidebar: true,
|
||||
mode: "storage",
|
||||
url_path: "light",
|
||||
filename: "",
|
||||
default: false,
|
||||
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"),
|
||||
});
|
||||
}
|
||||
|
||||
result.push(
|
||||
...dashboards
|
||||
@@ -355,8 +386,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
filename: "",
|
||||
...dashboard,
|
||||
default: defaultUrlPath === dashboard.url_path,
|
||||
type: "user_created",
|
||||
localized_type: this._localizeType("user_created"),
|
||||
type: this._localizeType("user_created"),
|
||||
}) satisfies DataTableItem
|
||||
)
|
||||
);
|
||||
@@ -456,15 +486,20 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this._openDetailDialog(dashboard, 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 _canDelete(urlPath: string) {
|
||||
return ![
|
||||
"lovelace",
|
||||
"energy",
|
||||
"light",
|
||||
"security",
|
||||
"climate",
|
||||
"home",
|
||||
].includes(urlPath);
|
||||
}
|
||||
|
||||
private _canEdit(urlPath: string) {
|
||||
return !["light", "security", "climate", "home"].includes(urlPath);
|
||||
}
|
||||
|
||||
private _handleDelete = async (item: DataTableItem) => {
|
||||
const dashboard = this._dashboards.find(
|
||||
@@ -546,6 +581,10 @@ 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",
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
@@ -15,6 +14,8 @@ import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { TagDetailDialogParams } from "./show-dialog-tag-detail";
|
||||
|
||||
const TAG_BASE = "https://www.home-assistant.io/tag/";
|
||||
|
||||
@customElement("dialog-tag-detail")
|
||||
class DialogTagDetail
|
||||
extends LitElement
|
||||
@@ -121,7 +122,7 @@ class DialogTagDetail
|
||||
</div>
|
||||
<div id="qr">
|
||||
<ha-qr-code
|
||||
.data=${`${documentationUrl(this.hass, "/tag/")}${this._params!.entry!.id}`}
|
||||
.data=${`${TAG_BASE}${this._params!.entry!.id}`}
|
||||
center-image="/static/icons/favicon-192x192.png"
|
||||
error-correction-level="quartile"
|
||||
scale="5"
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import type { HomeFrontendSystemData } from "../../../data/frontend";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { EditHomeDialogParams } from "./show-dialog-edit-home";
|
||||
|
||||
@customElement("dialog-edit-home")
|
||||
export class DialogEditHome
|
||||
extends LitElement
|
||||
implements HassDialog<EditHomeDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: EditHomeDialogParams;
|
||||
|
||||
@state() private _config?: HomeFrontendSystemData;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public showDialog(params: EditHomeDialogParams): void {
|
||||
this._params = params;
|
||||
this._config = { ...params.config };
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this._config = undefined;
|
||||
this._submitting = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
.headerTitle=${this.hass.localize("ui.panel.home.editor.title")}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<p class="description">
|
||||
${this.hass.localize("ui.panel.home.editor.description")}
|
||||
</p>
|
||||
|
||||
<ha-entities-picker
|
||||
autofocus
|
||||
.hass=${this.hass}
|
||||
.value=${this._config?.favorite_entities || []}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.strategy.home.favorite_entities"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.strategy.home.add_favorite_entity"
|
||||
)}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.home.editor.favorite_entities_helper"
|
||||
)}
|
||||
reorder
|
||||
allow-custom-entity
|
||||
@value-changed=${this._favoriteEntitiesChanged}
|
||||
></ha-entities-picker>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _favoriteEntitiesChanged(ev: CustomEvent): void {
|
||||
const entities = ev.detail.value as string[];
|
||||
this._config = {
|
||||
...this._config,
|
||||
favorite_entities: entities.length > 0 ? entities : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._params || !this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
await this._params.saveConfig(this._config);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save home configuration:", err);
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 var(--ha-space-4) 0;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-entities-picker {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-edit-home": DialogEditHome;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HomeFrontendSystemData } from "../../../data/frontend";
|
||||
|
||||
export interface EditHomeDialogParams {
|
||||
config: HomeFrontendSystemData;
|
||||
saveConfig: (config: HomeFrontendSystemData) => Promise<void>;
|
||||
}
|
||||
|
||||
export const loadEditHomeDialog = () => import("./dialog-edit-home");
|
||||
|
||||
export const showEditHomeDialog = (
|
||||
element: HTMLElement,
|
||||
params: EditHomeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-edit-home",
|
||||
dialogImport: loadEditHomeDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -3,18 +3,18 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import {
|
||||
fetchFrontendSystemData,
|
||||
saveFrontendSystemData,
|
||||
type HomeFrontendSystemData,
|
||||
} from "../../data/frontend";
|
||||
import type { LovelaceDashboardStrategyConfig } from "../../data/lovelace/config/types";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||
import { showToast } from "../../util/toast";
|
||||
import "../lovelace/hui-root";
|
||||
import { generateLovelaceDashboardStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import { showEditHomeDialog } from "./dialogs/show-dialog-edit-home";
|
||||
import { showAlertDialog } from "../lovelace/custom-card-helpers";
|
||||
|
||||
const HOME_LOVELACE_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-panel-home")
|
||||
class PanelHome extends LitElement {
|
||||
@@ -28,14 +28,12 @@ class PanelHome extends LitElement {
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _config: FrontendSystemData["home"] = {};
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
// Initial setup
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
this._loadConfig();
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,28 +95,9 @@ class PanelHome extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _loadConfig() {
|
||||
try {
|
||||
const data = await fetchFrontendSystemData(this.hass.connection, "home");
|
||||
this._config = data || {};
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load favorites:", err);
|
||||
this._config = {};
|
||||
}
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const strategyConfig: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
favorite_entities: this._config.favorite_entities,
|
||||
},
|
||||
};
|
||||
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
strategyConfig,
|
||||
HOME_LOVELACE_CONFIG,
|
||||
this.hass
|
||||
);
|
||||
|
||||
@@ -142,34 +121,15 @@ class PanelHome extends LitElement {
|
||||
}
|
||||
|
||||
private _setEditMode = () => {
|
||||
showEditHomeDialog(this, {
|
||||
config: this._config,
|
||||
saveConfig: async (config) => {
|
||||
await this._saveConfig(config);
|
||||
},
|
||||
// For now, we just show an alert that edit mode is not supported.
|
||||
// This will be expanded in the future.
|
||||
showAlertDialog(this, {
|
||||
title: "Edit mode not available",
|
||||
text: "The Home panel does not support edit mode.",
|
||||
confirmText: this.hass.localize("ui.common.ok"),
|
||||
});
|
||||
};
|
||||
|
||||
private async _saveConfig(config: HomeFrontendSystemData): Promise<void> {
|
||||
try {
|
||||
await saveFrontendSystemData(this.hass.connection, "home", config);
|
||||
this._config = config || {};
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save home configuration:", err);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.panel.home.editor.save_failed"),
|
||||
duration: 0,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.successfully_saved"),
|
||||
});
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
static readonly styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
@@ -7,7 +7,6 @@ import "../../components/ha-settings-row";
|
||||
import "../../components/ha-switch";
|
||||
import type { CoreFrontendUserData } from "../../data/frontend";
|
||||
import { saveFrontendUserData } from "../../data/frontend";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-advanced-mode-row")
|
||||
@@ -32,10 +31,7 @@ class AdvancedModeRow extends LitElement {
|
||||
<span slot="description">
|
||||
${this.hass.localize("ui.panel.profile.advanced_mode.description")}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/blog/2019/07/17/release-96/#advanced-mode"
|
||||
)}
|
||||
href="https://www.home-assistant.io/blog/2019/07/17/release-96/#advanced-mode"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.profile.advanced_mode.link_promo")}
|
||||
|
||||
@@ -8,9 +8,6 @@ import type { LovelaceDashboard } from "../../data/lovelace/dashboard";
|
||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
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___";
|
||||
|
||||
@@ -50,19 +47,12 @@ 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>
|
||||
${PANEL_DASHBOARDS.map((panel) => {
|
||||
const panelInfo = this.hass.panels[panel];
|
||||
return html`
|
||||
<ha-list-item value="lovelace">
|
||||
${panelInfo ? getPanelTitle(this.hass, panelInfo) : panel}
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
<ha-divider></ha-divider>
|
||||
<ha-list-item value="home">
|
||||
${this.hass.localize("ui.panel.profile.dashboard.home")}
|
||||
</ha-list-item>
|
||||
${this._dashboards.map((dashboard) => {
|
||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||
return "";
|
||||
|
||||
@@ -52,6 +52,8 @@ export const waColorStyles = css`
|
||||
--wa-color-danger-on-normal: var(--ha-color-on-danger-normal);
|
||||
--wa-color-danger-on-quiet: var(--ha-color-on-danger-quiet);
|
||||
|
||||
--wa-color-text-quiet: var(--ha-color-text-secondary);
|
||||
|
||||
--wa-color-text-normal: var(--ha-color-text-primary);
|
||||
--wa-color-surface-default: var(--card-background-color);
|
||||
--wa-color-surface-raised: var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff));
|
||||
@@ -62,5 +64,7 @@ export const waColorStyles = css`
|
||||
|
||||
--wa-focus-ring-color: var(--ha-color-neutral-60);
|
||||
--wa-shadow-l: 4px 8px 12px 0 rgba(0, 0, 0, 0.3);
|
||||
|
||||
--wa-color-text-normal: var(--ha-color-text-primary);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -9,12 +9,16 @@ export const waMainStyles = css`
|
||||
--wa-focus-ring-offset: 2px;
|
||||
--wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width) var(--wa-focus-ring-color);
|
||||
|
||||
--wa-space-xs: var(--ha-space-2);
|
||||
--wa-space-m: var(--ha-space-4);
|
||||
--wa-space-l: var(--ha-space-6);
|
||||
--wa-space-xl: var(--ha-space-8);
|
||||
|
||||
--wa-form-control-padding-block: 0.75em;
|
||||
--wa-form-control-value-line-height: var(--ha-line-height-condensed);
|
||||
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-transition-normal: 150ms;
|
||||
--wa-transition-fast: 75ms;
|
||||
--wa-transition-easing: ease;
|
||||
|
||||
@@ -28,6 +32,7 @@ export const waMainStyles = css`
|
||||
|
||||
--wa-line-height-condensed: var(--ha-line-height-condensed);
|
||||
|
||||
--wa-font-size-m: var(--ha-font-size-m);
|
||||
--wa-shadow-s: var(--ha-box-shadow-s);
|
||||
--wa-shadow-m: var(--ha-box-shadow-m);
|
||||
--wa-shadow-l: var(--ha-box-shadow-l);
|
||||
|
||||
@@ -2217,19 +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.",
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"panel": {
|
||||
"home": {
|
||||
"editor": {
|
||||
"title": "Edit home page",
|
||||
"description": "Configure your home page display preferences.",
|
||||
"favorite_entities_helper": "Display your favorite entities. Home Assistant will still suggest based on commonly used up to 8 slots.",
|
||||
"save_failed": "Failed to save home page configuration"
|
||||
}
|
||||
},
|
||||
"my": {
|
||||
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
|
||||
"component_not_loaded": "This redirect is not supported by your Home Assistant instance. You need the integration {integration} to use this redirect.",
|
||||
@@ -3507,7 +3497,6 @@
|
||||
"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 +3505,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_lovelace": "The Overview dashboard title and icon cannot be changed. You can create a new dashboard to get more customization options.",
|
||||
"cant_edit_default": "The default dashboard, Overview, cannot be edited from the UI. You can hide it by setting another dashboard as default.",
|
||||
"detail": {
|
||||
"edit_dashboard": "Edit dashboard",
|
||||
"new_dashboard": "Add new dashboard",
|
||||
@@ -4009,7 +3998,25 @@
|
||||
"item_pasted": "{item} pasted",
|
||||
"ctrl": "Ctrl",
|
||||
"del": "Del",
|
||||
"targets": "Targets",
|
||||
"select_target": "Select a target",
|
||||
"home": "Home",
|
||||
"unassigned": "Unassigned",
|
||||
"blocks": "Blocks",
|
||||
"show_more": "Show more",
|
||||
"unassigned_entities": "Unassigned entities",
|
||||
"unassigned_devices": "Unassigned devices",
|
||||
"empty_section_search": {
|
||||
"block": "No blocks found for {term}",
|
||||
"entity": "No entities found for {term}",
|
||||
"device": "No devices found for {term}",
|
||||
"area": "No areas or floors found for {term}",
|
||||
"label": "No labels found for {term}"
|
||||
},
|
||||
"load_target_items_failed": "Failed to load target items for",
|
||||
"other_areas": "Other areas",
|
||||
"services": "Services",
|
||||
"helpers": "Helpers",
|
||||
"triggers": {
|
||||
"name": "Triggers",
|
||||
"header": "When",
|
||||
@@ -4017,7 +4024,10 @@
|
||||
"learn_more": "Learn more about triggers",
|
||||
"triggered": "Triggered",
|
||||
"add": "Add trigger",
|
||||
"empty_search": "No triggers found for {term}",
|
||||
"empty_search": {
|
||||
"global": "No triggers and targets found for {term}",
|
||||
"item": "No triggers found for {term}"
|
||||
},
|
||||
"id": "Trigger ID",
|
||||
"optional": "Optional",
|
||||
"edit_id": "Edit ID",
|
||||
@@ -4038,6 +4048,7 @@
|
||||
"copied_to_clipboard": "Trigger copied to clipboard",
|
||||
"cut_to_clipboard": "Trigger cut to clipboard",
|
||||
"select": "Select a trigger",
|
||||
"no_items_for_target": "No triggers available for",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
@@ -4279,7 +4290,10 @@
|
||||
"description": "All conditions added here need to be satisfied for the automation to run. A condition can be satisfied or not at any given time, for example: ''If {user} is home''. You can use building blocks to create more complex conditions.",
|
||||
"learn_more": "Learn more about conditions",
|
||||
"add": "Add condition",
|
||||
"empty_search": "No conditions and blocks found for {term}",
|
||||
"empty_search": {
|
||||
"global": "No conditions, blocks and targets found for {term}",
|
||||
"item": "No conditions found for {term}"
|
||||
},
|
||||
"add_building_block": "Add building block",
|
||||
"test": "Test",
|
||||
"testing_error": "Condition did not pass",
|
||||
@@ -4302,6 +4316,7 @@
|
||||
"copied_to_clipboard": "Condition copied to clipboard",
|
||||
"cut_to_clipboard": "Condition cut to clipboard",
|
||||
"select": "Select a condition",
|
||||
"no_items_for_target": "No conditions available for",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
@@ -4447,7 +4462,10 @@
|
||||
"description": "All actions added here will be performed in sequence when the automation runs. An action usually controls one of your areas, devices, or entities, for example: 'Turn on the lights'. You can use building blocks to create more complex sequences of actions.",
|
||||
"learn_more": "Learn more about actions",
|
||||
"add": "Add action",
|
||||
"empty_search": "No actions and blocks found for {term}",
|
||||
"empty_search": {
|
||||
"global": "No actions, blocks and targets found for {term}",
|
||||
"item": "No actions found for {term}"
|
||||
},
|
||||
"add_building_block": "Add building block",
|
||||
"invalid_action": "Invalid action",
|
||||
"run": "Run action",
|
||||
@@ -4472,6 +4490,7 @@
|
||||
"copied_to_clipboard": "Action copied to clipboard",
|
||||
"cut_to_clipboard": "Action cut to clipboard",
|
||||
"select": "Select an action",
|
||||
"no_items_for_target": "No actions available for",
|
||||
"groups": {
|
||||
"device_id": {
|
||||
"label": "Device"
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -1940,9 +1940,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@home-assistant/webawesome@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0"
|
||||
"@home-assistant/webawesome@npm:3.0.0-ha.0":
|
||||
version: 3.0.0-ha.0
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-ha.0"
|
||||
dependencies:
|
||||
"@ctrl/tinycolor": "npm:4.1.0"
|
||||
"@floating-ui/dom": "npm:^1.6.13"
|
||||
@@ -1953,7 +1953,7 @@ __metadata:
|
||||
lit: "npm:^3.2.1"
|
||||
nanoid: "npm:^5.1.5"
|
||||
qr-creator: "npm:^1.0.0"
|
||||
checksum: 10/03400894cfee8548fd5b1f5c56d31d253830e704b18ba69d36ce6b761d8b1bef2fb52cffba8d9b033033bb582f2f51a2d6444d82622f66d70150e2104fcb49e2
|
||||
checksum: 10/2034d498d5b26bb0573ebc2c9aadd144604bb48c04becbae0c67b16857d8e5d6562626e795974362c3fc41e9b593a9005595d8b5ff434b1569b2d724af13043b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9226,7 +9226,7 @@ __metadata:
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@home-assistant/webawesome": "npm:3.0.0"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-ha.0"
|
||||
"@lezer/highlight": "npm:1.2.3"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
|
||||
Reference in New Issue
Block a user