20240329.0 (#20277)

This commit is contained in:
Paul Bottein 2024-03-29 19:10:34 +01:00 committed by GitHub
commit d24d29e42f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 581 additions and 264 deletions

View File

@ -185,8 +185,8 @@
"@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.3.1",
"@typescript-eslint/parser": "7.3.1",
"@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.4.0",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240328.0"
version = "20240329.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -512,10 +512,6 @@ export class HaDataTable extends LitElement {
items.push({ append: true, content: this.appendRow });
}
if (this.hasFab) {
items.push({ empty: true });
}
if (this.groupColumn) {
const grouped = groupBy(items, (item) => item[this.groupColumn!]);
if (grouped.undefined) {
@ -555,6 +551,10 @@ export class HaDataTable extends LitElement {
} else {
this._items = items;
}
if (this.hasFab) {
this._items = [...this._items, { empty: true }];
}
} else {
this._items = data;
}

View File

@ -157,11 +157,11 @@ export class HaFilterBlueprints extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
`,
];

View File

@ -78,13 +78,15 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
class="ha-scrollbar"
activatable
>
<ha-list-item
.selected=${!this.value?.length}
.activated=${!this.value?.length}
>${this.hass.localize(
"ui.panel.config.category.filter.show_all"
)}</ha-list-item
>
${this._categories.length > 0
? html`<ha-list-item
.selected=${!this.value?.length}
.activated=${!this.value?.length}
>${this.hass.localize(
"ui.panel.config.category.filter.show_all"
)}</ha-list-item
>`
: nothing}
${this._categories.map(
(category) =>
html`<ha-list-item
@ -142,7 +144,11 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
: nothing}
</ha-expansion-panel>
${this.expanded
? html`<ha-list-item graphic="icon" @click=${this._addCategory}>
? html`<ha-list-item
graphic="icon"
@click=${this._addCategory}
class="add"
>
<ha-svg-icon slot="graphic" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.category.editor.add")}
</ha-list-item>`
@ -254,6 +260,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
css`
:host {
border-bottom: 1px solid var(--divider-color);
position: relative;
}
:host([expanded]) {
flex: 1;
@ -277,11 +284,11 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
mwc-list {
--mdc-list-item-meta-size: auto;
@ -291,6 +298,12 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
.warning {
color: var(--error-color);
}
.add {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
`,
];
}

View File

@ -185,11 +185,11 @@ export class HaFilterDevices extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
ha-check-list-item {
width: 100%;

View File

@ -199,11 +199,11 @@ export class HaFilterEntities extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
ha-check-list-item {
--mdc-list-item-graphic-margin: 16px;

View File

@ -267,11 +267,11 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
ha-check-list-item {
--mdc-list-item-graphic-margin: 16px;

View File

@ -165,11 +165,11 @@ export class HaFilterIntegrations extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
`,
];

View File

@ -3,10 +3,12 @@ import "@material/mwc-menu/mwc-menu-surface";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiPlus } from "@mdi/js";
import { computeCssColor } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event";
import {
LabelRegistryEntry,
createLabelRegistryEntry,
subscribeLabelRegistry,
} from "../data/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
@ -16,6 +18,7 @@ import "./ha-check-list-item";
import "./ha-expansion-panel";
import "./ha-icon";
import "./ha-label";
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
@customElement("ha-filter-labels")
export class HaFilterLabels extends SubscribeMixin(LitElement) {
@ -84,6 +87,16 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
`
: nothing}
</ha-expansion-panel>
${this.expanded
? html`<ha-list-item
graphic="icon"
@click=${this._addLabel}
class="add"
>
<ha-svg-icon slot="graphic" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.labels.add_label")}
</ha-list-item>`
: nothing}
`;
}
@ -92,11 +105,17 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
setTimeout(() => {
if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height =
`${this.clientHeight - 49}px`;
`${this.clientHeight - (49 + 48)}px`;
}, 300);
}
}
private _addLabel() {
showLabelDetailDialog(this, {
createEntry: (values) => createLabelRegistryEntry(this.hass, values),
});
}
private _expandedWillChange(ev) {
this._shouldRender = ev.detail.expanded;
}
@ -134,6 +153,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
haStyleScrollbar,
css`
:host {
position: relative;
border-bottom: 1px solid var(--divider-color);
}
:host([expanded]) {
@ -158,11 +178,11 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
.warning {
color: var(--error-color);
@ -171,6 +191,12 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
--ha-label-background-color: var(--color, var(--grey-color));
--ha-label-background-opacity: 0.5;
}
.add {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
`,
];
}

View File

@ -147,11 +147,11 @@ export class HaFilterStates extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
`,
];

View File

@ -38,10 +38,14 @@ import "./ha-list-item";
type ScorableFloorRegistryEntry = ScorableTextItem & FloorRegistryEntry;
const ADD_NEW_ID = "___ADD_NEW___";
const NO_FLOORS_ID = "___NO_FLOORS___";
const ADD_NEW_SUGGESTION_ID = "___ADD_NEW_SUGGESTION___";
const rowRenderer: ComboBoxLitRenderer<FloorRegistryEntry> = (item) =>
html`<ha-list-item
graphic="icon"
class=${classMap({ "add-new": item.floor_id === "add_new" })}
class=${classMap({ "add-new": item.floor_id === ADD_NEW_ID })}
>
<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>
${item.name}
@ -146,18 +150,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
noAdd: this["noAdd"],
excludeFloors: this["excludeFloors"]
): FloorRegistryEntry[] => {
if (!floors.length) {
return [
{
floor_id: "no_floors",
name: this.hass.localize("ui.components.floor-picker.no_floors"),
icon: null,
level: 0,
aliases: [],
},
];
}
let deviceEntityLookup: DeviceEntityDisplayLookup = {};
let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryDisplayEntry[] | undefined;
@ -297,10 +289,10 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
if (!outputFloors.length) {
outputFloors = [
{
floor_id: "no_floors",
name: this.hass.localize("ui.components.floor-picker.no_match"),
floor_id: NO_FLOORS_ID,
name: this.hass.localize("ui.components.floor-picker.no_floors"),
icon: null,
level: 0,
level: null,
aliases: [],
},
];
@ -311,10 +303,10 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
: [
...outputFloors,
{
floor_id: "add_new",
floor_id: ADD_NEW_ID,
name: this.hass.localize("ui.components.floor-picker.add_new"),
icon: "mdi:plus",
level: 0,
level: null,
aliases: [],
},
];
@ -341,7 +333,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
this.excludeFloors
).map((floor) => ({
...floor,
strings: [floor.floor_id, floor.name], // ...floor.aliases
strings: [floor.floor_id, floor.name, ...floor.aliases],
}));
this.comboBox.items = floors;
this.comboBox.filteredItems = floors;
@ -385,20 +377,36 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableFloorRegistryEntry>(
filterString,
target.items || []
target.items?.filter(
(item) => ![NO_FLOORS_ID, ADD_NEW_ID].includes(item.label_id)
) || []
);
if (!this.noAdd && filteredItems?.length === 0) {
this._suggestion = filterString;
this.comboBox.filteredItems = [
{
floor_id: "add_new_suggestion",
name: this.hass.localize(
"ui.components.floor-picker.add_new_sugestion",
{ name: this._suggestion }
),
picture: null,
},
];
if (filteredItems.length === 0) {
if (this.noAdd) {
this.comboBox.filteredItems = [
{
floor_id: NO_FLOORS_ID,
name: this.hass.localize("ui.components.floor-picker.no_floors"),
icon: null,
level: null,
aliases: [],
},
] as FloorRegistryEntry[];
} else {
this._suggestion = filterString;
this.comboBox.filteredItems = [
{
floor_id: ADD_NEW_SUGGESTION_ID,
name: this.hass.localize(
"ui.components.floor-picker.add_new_sugestion",
{ name: this._suggestion }
),
icon: "mdi:plus",
level: null,
aliases: [],
},
] as FloorRegistryEntry[];
}
} else {
this.comboBox.filteredItems = filteredItems;
}
@ -416,11 +424,13 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
ev.stopPropagation();
let newValue = ev.detail.value;
if (newValue === "no_floors") {
if (newValue === NO_FLOORS_ID) {
newValue = "";
this.comboBox.setInputValue("");
return;
}
if (!["add_new_suggestion", "add_new"].includes(newValue)) {
if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) {
if (newValue !== this._value) {
this._setValue(newValue);
}
@ -438,7 +448,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
"ui.components.floor-picker.add_dialog.name"
),
defaultValue:
newValue === "add_new_suggestion" ? this._suggestion : undefined,
newValue === ADD_NEW_SUGGESTION_ID ? this._suggestion : undefined,
confirm: async (name) => {
if (!name) {
return;

View File

@ -385,8 +385,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableLabelItem>(
filterString,
target.items?.filter((item) =>
[NO_LABELS_ID, ADD_NEW_ID].includes(item.ignoreFilter)
target.items?.filter(
(item) => ![NO_LABELS_ID, ADD_NEW_ID].includes(item.label_id)
) || []
);
if (filteredItems.length === 0) {

View File

@ -0,0 +1,38 @@
import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
import "element-internals-polyfill";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-outlined-text-field")
export class HaOutlinedTextField extends MdOutlinedTextField {
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-primary: var(--primary-text-color);
--md-outlined-text-field-input-text-color: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-outlined-field-outline-color: var(--outline-color);
--md-outlined-field-focus-outline-color: var(--primary-color);
--md-outlined-field-hover-outline-color: var(--outline-hover-color);
}
:host([dense]) {
--md-outlined-field-top-space: 5.5px;
--md-outlined-field-bottom-space: 5.5px;
--md-outlined-field-container-shape-start-start: 10px;
--md-outlined-field-container-shape-start-end: 10px;
--md-outlined-field-container-shape-end-end: 10px;
--md-outlined-field-container-shape-end-start: 10px;
--md-outlined-field-focus-outline-width: 1px;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-outlined-text-field": HaOutlinedTextField;
}
}

View File

@ -1,11 +1,11 @@
import "@material/web/textfield/outlined-text-field";
import type { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
import { mdiMagnify } from "@mdi/js";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { HomeAssistant } from "../types";
import "./ha-icon-button";
import "./ha-outlined-text-field";
import type { HaOutlinedTextField } from "./ha-outlined-text-field";
import "./ha-svg-icon";
@customElement("search-input-outlined")
@ -30,19 +30,22 @@ class SearchInputOutlined extends LitElement {
this._input?.focus();
}
@query("md-outlined-text-field", true) private _input!: MdOutlinedTextField;
@query("ha-outlined-text-field", true) private _input!: HaOutlinedTextField;
protected render(): TemplateResult {
const placeholder =
this.placeholder || this.hass.localize("ui.common.search");
return html`
<md-outlined-text-field
<ha-outlined-text-field
.autofocus=${this.autofocus}
.aria-label=${this.label || this.hass.localize("ui.common.search")}
.placeholder=${this.placeholder ||
this.hass.localize("ui.common.search")}
.placeholder=${placeholder}
.value=${this.filter || ""}
icon
.iconTrailing=${this.filter || this.suffix}
@input=${this._filterInputChanged}
dense
>
<slot name="prefix" slot="leading-icon">
<ha-svg-icon
@ -51,7 +54,7 @@ class SearchInputOutlined extends LitElement {
.path=${mdiMagnify}
></ha-svg-icon>
</slot>
</md-outlined-text-field>
</ha-outlined-text-field>
`;
}
@ -67,40 +70,21 @@ class SearchInputOutlined extends LitElement {
return css`
:host {
display: inline-flex;
/* For iOS */
z-index: 0;
}
md-outlined-text-field {
ha-outlined-text-field {
display: block;
width: 100%;
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-primary: var(--primary-text-color);
--md-outlined-text-field-input-text-color: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-outlined-field-top-space: 5.5px;
--md-outlined-field-bottom-space: 5.5px;
--md-outlined-field-outline-color: var(--outline-color);
--md-outlined-field-container-shape-start-start: 10px;
--md-outlined-field-container-shape-start-end: 10px;
--md-outlined-field-container-shape-end-end: 10px;
--md-outlined-field-container-shape-end-start: 10px;
--md-outlined-field-focus-outline-width: 1px;
--md-outlined-field-focus-outline-color: var(--primary-color);
}
ha-svg-icon,
ha-icon-button {
display: flex;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
color: var(--primary-text-color);
}
ha-svg-icon {
outline: none;
}
.clear-button {
--mdc-icon-size: 20px;
}
.trailing {
display: flex;
align-items: center;
}
`;
}
}

View File

@ -10,7 +10,7 @@ import { computeDomain } from "../common/entity/compute_domain";
export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display";
type entityCategory = "config" | "diagnostic";
type EntityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
@ -20,7 +20,7 @@ export interface EntityRegistryDisplayEntry {
area_id?: string;
labels: string[];
hidden?: boolean;
entity_category?: entityCategory;
entity_category?: EntityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
@ -40,7 +40,7 @@ export interface EntityRegistryDisplayEntryResponse {
hb?: boolean;
dp?: number;
}[];
entity_categories: Record<number, entityCategory>;
entity_categories: Record<number, EntityCategory>;
}
export interface EntityRegistryEntry {
@ -55,7 +55,7 @@ export interface EntityRegistryEntry {
labels: string[];
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: entityCategory | null;
entity_category: EntityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;

View File

@ -1,6 +1,9 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button/mwc-button";
import "@material/web/menu/menu";
import type { MdMenu } from "@material/web/menu/menu";
import "@material/web/menu/menu-item";
import {
mdiArrowDown,
mdiArrowUp,
@ -19,6 +22,7 @@ import {
nothing,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/chips/ha-assist-chip";
@ -173,6 +177,10 @@ export class HaTabsSubpageDataTable extends LitElement {
@query("ha-data-table", true) private _dataTable!: HaDataTable;
@query("#group-by-menu") private _groupByMenu!: MdMenu;
@query("#sort-by-menu") private _sortByMenu!: MdMenu;
private _showPaneController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width > 750,
});
@ -187,6 +195,14 @@ export class HaTabsSubpageDataTable extends LitElement {
}
}
private _toggleGroupBy() {
this._groupByMenu.open = !this._groupByMenu.open;
}
private _toggleSortBy() {
this._sortByMenu.open = !this._sortByMenu.open;
}
protected render(): TemplateResult {
const localize = this.localizeFunc || this.hass.localize;
const showPane = this._showPaneController.value ?? !this.narrow;
@ -226,73 +242,35 @@ export class HaTabsSubpageDataTable extends LitElement {
</search-input-outlined>`;
const sortByMenu = Object.values(this.columns).find((col) => col.sortable)
? html`<ha-button-menu fixed>
? html`
<ha-assist-chip
.label=${localize("ui.components.subpage-data-table.sort_by", {
sortColumn: this._sortColumn
? ` ${this.columns[this._sortColumn].title || this.columns[this._sortColumn].label}`
: "",
})}
slot="trigger"
id="sort-by-anchor"
@click=${this._toggleSortBy}
>
<ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon
></ha-assist-chip>
${Object.entries(this.columns).map(([id, column]) =>
column.sortable
? html`<ha-list-item
.value=${id}
@request-selected=${this._handleSortBy}
hasMeta
.activated=${id === this._sortColumn}
>
${this._sortColumn === id
? html`<ha-svg-icon
slot="meta"
.path=${this._sortDirection === "desc"
? mdiArrowDown
: mdiArrowUp}
></ha-svg-icon>`
: nothing}
${column.title || column.label}
</ha-list-item>`
: nothing
)}
</ha-button-menu>`
`
: nothing;
const groupByMenu = Object.values(this.columns).find((col) => col.groupable)
? html`<ha-button-menu fixed>
? html`
<ha-assist-chip
.label=${localize("ui.components.subpage-data-table.group_by", {
groupColumn: this._groupColumn
? ` ${this.columns[this._groupColumn].title || this.columns[this._groupColumn].label}`
: "",
})}
slot="trigger"
id="group-by-anchor"
@click=${this._toggleGroupBy}
>
<ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon
></ha-assist-chip>
${Object.entries(this.columns).map(([id, column]) =>
column.groupable
? html`<ha-list-item
.value=${id}
@request-selected=${this._handleGroupBy}
.activated=${id === this._groupColumn}
>
${column.title || column.label}
</ha-list-item> `
: nothing
)}
<li divider role="separator"></li>
<ha-list-item
.value=${undefined}
@request-selected=${this._handleGroupBy}
.activated=${this._groupColumn === undefined}
>${localize(
"ui.components.subpage-data-table.dont_group_by"
)}</ha-list-item
>
</ha-button-menu>`
`
: nothing;
return html`
@ -431,6 +409,58 @@ export class HaTabsSubpageDataTable extends LitElement {
</ha-data-table>`}
<div slot="fab"><slot name="fab"></slot></div>
</hass-tabs-subpage>
<md-menu anchor="group-by-anchor" id="group-by-menu" positioning="fixed">
${Object.entries(this.columns).map(([id, column]) =>
column.groupable
? html`
<md-menu-item
.value=${id}
@click=${this._handleGroupBy}
.selected=${id === this._groupColumn}
class=${classMap({ selected: id === this._groupColumn })}
>
${column.title || column.label}
</md-menu-item>
`
: nothing
)}
<li divider role="separator"></li>
<md-menu-item
.value=${undefined}
@click=${this._handleGroupBy}
.selected=${this._groupColumn === undefined}
class=${classMap({ selected: this._groupColumn === undefined })}
>${localize(
"ui.components.subpage-data-table.dont_group_by"
)}</md-menu-item
>
</md-menu>
<md-menu anchor="sort-by-anchor" id="sort-by-menu" positioning="fixed">
${Object.entries(this.columns).map(([id, column]) =>
column.sortable
? html`
<md-menu-item
.value=${id}
@click=${this._handleSortBy}
.selected=${id === this._sortColumn}
class=${classMap({ selected: id === this._sortColumn })}
>
${this._sortColumn === id
? html`
<ha-svg-icon
slot="end"
.path=${this._sortDirection === "desc"
? mdiArrowDown
: mdiArrowUp}
></ha-svg-icon>
`
: nothing}
${column.title || column.label}
</md-menu-item>
`
: nothing
)}
</md-menu>
`;
}
@ -449,6 +479,7 @@ export class HaTabsSubpageDataTable extends LitElement {
private _handleSortBy(ev) {
ev.stopPropagation();
ev.preventDefault();
const columnId = ev.currentTarget.value;
if (!this._sortDirection || this._sortColumn !== columnId) {
this._sortDirection = "asc";
@ -611,11 +642,11 @@ export class HaTabsSubpageDataTable extends LitElement {
border-radius: 50%;
font-weight: 400;
font-size: 11px;
background-color: var(--accent-color);
background-color: var(--primary-color);
line-height: 16px;
text-align: center;
padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color));
color: var(--text-primary-color);
}
.narrow-header-row {
@ -656,13 +687,6 @@ export class HaTabsSubpageDataTable extends LitElement {
ha-assist-chip {
--ha-assist-chip-container-shape: 10px;
}
ha-button-menu {
--mdc-list-item-meta-size: 16px;
--mdc-list-item-meta-display: flex;
}
ha-button-menu ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
.select-mode-chip {
--md-assist-chip-icon-label-space: 0;
@ -688,6 +712,25 @@ export class HaTabsSubpageDataTable extends LitElement {
display: flex;
flex-direction: column;
}
/* TODO: Migrate to ha-menu and ha-menu-item */
md-menu {
--md-menu-container-color: var(--card-background-color);
}
md-menu-item {
--md-menu-item-label-text-color: var(--primary-text-color);
--mdc-icon-size: 16px;
--md-menu-item-selected-container-color: rgba(
var(--rgb-primary-color),
0.15
);
}
md-menu-item.selected {
--md-menu-item-label-text-color: var(--primary-color);
}
#sort-by-anchor,
#group-by-anchor {
--md-assist-chip-trailing-space: 8px;
}
`;
}
}

View File

@ -18,6 +18,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
CSSResultGroup,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
@ -38,13 +39,15 @@ import type {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-fab";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-blueprints";
import "../../../components/ha-filter-categories";
import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-entities";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-labels";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon";
@ -64,6 +67,10 @@ import {
import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import { findRelated } from "../../../data/search";
import {
showAlertDialog,
@ -77,12 +84,6 @@ import { documentationUrl } from "../../../util/documentation-url";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "./show-dialog-new-automation";
import "../../../components/data-table/ha-data-table-labels";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import "../../../components/ha-filter-labels";
type AutomationItem = AutomationEntity & {
name: string;
@ -509,6 +510,13 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
firstUpdated() {
if (this._searchParms.has("blueprint")) {
this._filterBlueprint();

View File

@ -179,7 +179,9 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableCategoryRegistryEntry>(
filterString,
target.items || []
target.items?.filter(
(item) => ![NO_CATEGORIES_ID, ADD_NEW_ID].includes(item.category_id)
) || []
);
if (filteredItems?.length === 0) {
if (this.noAdd) {
@ -224,6 +226,8 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
if (newValue === NO_CATEGORIES_ID) {
newValue = "";
this.comboBox.setInputValue("");
return;
}
if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) {

View File

@ -10,6 +10,7 @@ import {
nothing,
} from "lit";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event";
@ -24,16 +25,18 @@ import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item";
import "../../../components/ha-fab";
import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-labels";
import "../../../components/ha-filter-states";
import "../../../components/ha-icon-button";
import "../../../components/ha-alert";
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context";
import {
@ -47,7 +50,12 @@ import {
findBatteryEntity,
} from "../../../data/entity_registry";
import { IntegrationManifest } from "../../../data/integration";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
@ -60,10 +68,11 @@ interface DeviceRowData extends DeviceRegistryEntry {
area?: string;
integration?: string;
battery_entity?: [string | undefined, string | undefined];
label_entries: EntityRegistryEntry[];
}
@customElement("ha-config-devices-dashboard")
export class HaConfigDeviceDashboard extends LitElement {
export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@ -91,6 +100,9 @@ export class HaConfigDeviceDashboard extends LitElement {
@state() private _expandedFilter?: string;
@state()
_labels!: LabelRegistryEntry[];
private _ignoreLocationChange = false;
public connectedCallback() {
@ -190,11 +202,17 @@ export class HaConfigDeviceDashboard extends LitElement {
string,
{ value: string[] | undefined; items: Set<string> | undefined }
>,
localize: LocalizeFunc
localize: LocalizeFunc,
labelReg?: LabelRegistryEntry[]
) => {
// Some older installations might have devices pointing at invalid entryIDs
// So we guard for that.
let outputDevices: DeviceRowData[] = Object.values(devices);
let outputDevices: DeviceRowData[] = Object.values(devices).map(
(device) => ({
...device,
label_entries: [],
})
);
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
@ -221,16 +239,16 @@ export class HaConfigDeviceDashboard extends LitElement {
const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, flter]) => {
if (key === "config_entry" && flter.value?.length) {
Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && filter.value?.length) {
outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) =>
flter.value?.includes(entryId)
filter.value?.includes(entryId)
)
);
const configEntries = entries.filter(
(entry) => entry.entry_id && flter.value?.includes(entry.entry_id)
(entry) => entry.entry_id && filter.value?.includes(entry.entry_id)
);
configEntries.forEach((configEntry) => {
@ -239,17 +257,21 @@ export class HaConfigDeviceDashboard extends LitElement {
if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0];
}
} else if (key === "ha-filter-integrations" && flter.value?.length) {
} else if (key === "ha-filter-integrations" && filter.value?.length) {
const entryIds = entries
.filter((entry) => flter.value!.includes(entry.domain))
.filter((entry) => filter.value!.includes(entry.domain))
.map((entry) => entry.entry_id);
outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) => entryIds.includes(entryId))
);
flter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (flter.items) {
filter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (key === "ha-filter-labels" && filter.value?.length) {
outputDevices = outputDevices.filter((device) =>
flter.items!.has(device.id)
device.labels.some((lbl) => filter.value!.includes(lbl))
);
} else if (filter.items) {
outputDevices = outputDevices.filter((device) =>
filter.items!.has(device.id)
);
}
});
@ -270,6 +292,12 @@ export class HaConfigDeviceDashboard extends LitElement {
.map((entId) => entryLookup[entId]),
manifestLookup
);
const labels = labelReg && device?.labels;
const labelsEntries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
return {
...device,
name: computeDeviceName(
@ -306,6 +334,7 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.states[
this._batteryEntity(device.id, deviceEntityLookup) || ""
]?.state,
label_entries: labelsEntries,
};
});
@ -351,8 +380,15 @@ export class HaConfigDeviceDashboard extends LitElement {
direction: "asc",
grows: true,
template: (device) => html`
${device.name}
<div style="font-size: 14px;">${device.name}</div>
<div class="secondary">${device.area} | ${device.integration}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
};
} else {
@ -361,8 +397,18 @@ export class HaConfigDeviceDashboard extends LitElement {
main: true,
sortable: true,
filterable: true,
grows: true,
direction: "asc",
grows: true,
template: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
};
}
@ -441,9 +487,25 @@ export class HaConfigDeviceDashboard extends LitElement {
? this.hass.localize("ui.panel.config.devices.disabled")
: "",
};
columns.labels = {
title: "",
hidden: true,
filterable: true,
template: (device) =>
device.label_entries.map((lbl) => lbl.name).join(" "),
};
return columns;
});
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render(): TemplateResult {
const { devicesOutput } = this._devicesAndFilterDomains(
this.hass.devices,
@ -452,7 +514,8 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.areas,
this.manifests,
this._filters,
this.hass.localize
this.hass.localize,
this._labels
);
return html`
@ -479,6 +542,7 @@ export class HaConfigDeviceDashboard extends LitElement {
@row-click=${this._handleRowClicked}
clickable
hasFab
class=${this.narrow ? "narrow" : ""}
>
<ha-integration-overflow-menu
.hass=${this.hass}
@ -531,6 +595,15 @@ export class HaConfigDeviceDashboard extends LitElement {
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-states>
<ha-filter-labels
.hass=${this.hass}
.value=${this._filters["ha-filter-labels"]?.value}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
.expanded=${this._expandedFilter === "ha-filter-labels"}
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-labels>
</hass-tabs-subpage-data-table>
`;
}
@ -590,8 +663,10 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.areas,
this.manifests,
this._filters,
this.hass.localize
this.hass.localize,
this._labels
);
if (
filteredDomains.size === 1 &&
(PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes(
@ -611,6 +686,12 @@ export class HaConfigDeviceDashboard extends LitElement {
static get styles(): CSSResultGroup {
return [
css`
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}
hass-tabs-subpage-data-table.narrow {
--data-table-row-height: 72px;
}
ha-button-menu {
margin-left: 8px;
margin-inline-start: 8px;

View File

@ -10,7 +10,7 @@ import {
mdiRestoreAlert,
mdiUndo,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import {
CSSResultGroup,
LitElement,
@ -37,16 +37,18 @@ import type {
RowClickedEvent,
SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item";
import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-states";
import "../../../components/ha-filter-labels";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-alert";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity";
@ -57,6 +59,10 @@ import {
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { entryIcon } from "../../../data/icons";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import {
showAlertDialog,
showConfirmationDialog,
@ -65,6 +71,7 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
@ -86,10 +93,11 @@ export interface EntityRow extends StateEntity {
status: string | undefined;
area?: string;
localized_platform: string;
label_entries: LabelRegistryEntry[];
}
@customElement("ha-config-entities")
export class HaConfigEntities extends LitElement {
export class HaConfigEntities extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false;
@ -119,6 +127,9 @@ export class HaConfigEntities extends LitElement {
@state() private _expandedFilter?: string;
@state()
_labels!: LabelRegistryEntry[];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
@ -202,14 +213,21 @@ export class HaConfigEntities extends LitElement {
filterable: true,
direction: "asc",
grows: true,
template: narrow
? (entry) => html`
${entry.name}<br />
<div class="secondary">
template: (entry) => html`
<div style="font-size: 14px;">${entry.name}</div>
${narrow
? html`<div class="secondary">
${entry.entity_id} | ${entry.localized_platform}
</div>
`
: undefined,
</div>`
: nothing}
${entry.label_entries.length
? html`
<ha-data-table-labels
.labels=${entry.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
},
entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
@ -301,6 +319,13 @@ export class HaConfigEntities extends LitElement {
`
: "—",
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (entry) =>
entry.label_entries.map((lbl) => lbl.name).join(" "),
},
})
);
@ -315,7 +340,8 @@ export class HaConfigEntities extends LitElement {
string,
{ value: string[] | undefined; items: Set<string> | undefined }
>,
entries?: ConfigEntry[]
entries?: ConfigEntry[],
labelReg?: LabelRegistryEntry[]
) => {
const result: EntityRow[] = [];
@ -337,12 +363,12 @@ export class HaConfigEntities extends LitElement {
let filteredConfigEntry: ConfigEntry | undefined;
const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, flter]) => {
if (key === "config_entry" && flter.value?.length) {
Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && filter.value?.length) {
filteredEntities = filteredEntities.filter(
(entity) =>
entity.config_entry_id &&
flter.value?.includes(entity.config_entry_id)
filter.value?.includes(entity.config_entry_id)
);
if (!entries) {
@ -351,7 +377,7 @@ export class HaConfigEntities extends LitElement {
}
const configEntries = entries.filter(
(entry) => entry.entry_id && flter.value?.includes(entry.entry_id)
(entry) => entry.entry_id && filter.value?.includes(entry.entry_id)
);
configEntries.forEach((configEntry) => {
@ -360,23 +386,27 @@ export class HaConfigEntities extends LitElement {
if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0];
}
} else if (key === "ha-filter-integrations" && flter.value?.length) {
} else if (key === "ha-filter-integrations" && filter.value?.length) {
if (!entries) {
this._loadConfigEntries();
return;
}
const entryIds = entries
.filter((entry) => flter.value!.includes(entry.domain))
.filter((entry) => filter.value!.includes(entry.domain))
.map((entry) => entry.entry_id);
filteredEntities = filteredEntities.filter(
(entity) =>
entity.config_entry_id &&
entryIds.includes(entity.config_entry_id)
);
flter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (flter.items) {
filter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (key === "ha-filter-labels" && filter.value?.length) {
filteredEntities = filteredEntities.filter((entity) =>
flter.items!.has(entity.entity_id)
entity.labels.some((lbl) => filter.value!.includes(lbl))
);
} else if (filter.items) {
filteredEntities = filteredEntities.filter((entity) =>
filter.items!.has(entity.entity_id)
);
}
});
@ -404,6 +434,11 @@ export class HaConfigEntities extends LitElement {
continue;
}
const labels = labelReg && entry?.labels;
const labelsEntries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
result.push({
...entry,
entity,
@ -431,6 +466,7 @@ export class HaConfigEntities extends LitElement {
: localize(
"ui.panel.config.entities.picker.status.available"
),
label_entries: labelsEntries,
});
}
@ -438,6 +474,14 @@ export class HaConfigEntities extends LitElement {
}
);
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render() {
if (!this.hass || this._entities === undefined) {
return html` <hass-loading-screen></hass-loading-screen> `;
@ -451,7 +495,8 @@ export class HaConfigEntities extends LitElement {
this.hass.areas,
this._stateEntities,
this._filters,
this._entries
this._entries,
this._labels
);
const includeAddDeviceFab =
@ -492,6 +537,7 @@ export class HaConfigEntities extends LitElement {
@row-click=${this._openEditEntry}
id="entity_id"
.hasFab=${includeAddDeviceFab}
class=${this.narrow ? "narrow" : ""}
>
<ha-integration-overflow-menu
.hass=${this.hass}
@ -633,6 +679,15 @@ export class HaConfigEntities extends LitElement {
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-states>
<ha-filter-labels
.hass=${this.hass}
.value=${this._filters["ha-filter-labels"]?.value}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
.expanded=${this._expandedFilter === "ha-filter-labels"}
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-labels>
${includeAddDeviceFab
? html`<ha-fab
.label=${this.hass.localize("ui.panel.config.devices.add_device")}
@ -918,7 +973,8 @@ export class HaConfigEntities extends LitElement {
this.hass.areas,
this._stateEntities,
this._filters,
this._entries
this._entries,
this._labels
);
if (
filteredDomains.size === 1 &&
@ -940,6 +996,12 @@ export class HaConfigEntities extends LitElement {
return [
haStyle,
css`
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}
hass-tabs-subpage-data-table.narrow {
--data-table-row-height: 72px;
}
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);

View File

@ -1,4 +1,4 @@
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
import { mdiDelete, mdiHelpCircle, mdiPlus } from "@mdi/js";
import { LitElement, PropertyValues, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@ -11,6 +11,7 @@ import {
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-relative-time";
import "../../../components/ha-icon-overflow-menu";
import {
LabelRegistryEntry,
LabelRegistryEntryMutableParams,
@ -71,6 +72,26 @@ export class HaConfigLabels extends LitElement {
filterable: true,
grows: true,
},
actions: {
title: "",
width: "64px",
type: "overflow-menu",
template: (label) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
label: this.hass.localize("ui.common.delete"),
path: mdiDelete,
action: () => this._removeLabel(label),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
},
};
return columns;
});
@ -189,6 +210,7 @@ export class HaConfigLabels extends LitElement {
}),
dismissText: this.hass!.localize("ui.common.cancel"),
confirmText: this.hass!.localize("ui.common.remove"),
destructive: true,
}))
) {
return false;

View File

@ -16,6 +16,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
CSSResultGroup,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
@ -297,6 +298,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
}
);
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeCategoryRegistry(this.hass.connection, "scene", (categories) => {

View File

@ -15,6 +15,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
CSSResultGroup,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
@ -560,6 +561,13 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
this._filteredScripts = items ? [...items] : undefined;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
firstUpdated() {
if (this._searchParms.has("blueprint")) {
this._filterBlueprint();

View File

@ -98,10 +98,10 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
display: block;
padding: 24px 16px 16px;
}
:host {
--ha-card-border-radius: inherit !important;
--ha-card-border-width: inherit !important;
--ha-card-box-shadow: inherit !important;
#root {
--ha-card-border-radius: var(--restore-card-border-radius, inherit);
--ha-card-border-width: var(--restore-card-border-width, inherit);
--ha-card-box-shadow: var(--restore-card-border-shadow, inherit);
}
`;
}

View File

@ -108,6 +108,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
const card: LovelaceCard = this.cards[0];
card.isPanel = true;
card.toggleAttribute("no-border", true);
if (this.isStrategy || !this.lovelace?.editMode) {
card.editMode = false;
@ -116,6 +117,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
}
const wrapper = document.createElement("hui-card-options");
wrapper.toggleAttribute("no-border", true);
wrapper.hass = this.hass;
wrapper.lovelace = this.lovelace;
wrapper.path = [this.index!, 0];
@ -130,9 +132,12 @@ export class PanelView extends LitElement implements LovelaceViewElement {
:host {
display: block;
height: 100%;
--restore-card-border-radius: var(--ha-card-border-radius, 12px);
--restore-card-border-width: var(--ha-card-border-width, 1px);
--restore-card-box-shadow: var(--ha-card-box-shadow, none);
}
* {
[no-border] {
--ha-card-border-radius: 0;
--ha-card-border-width: 0;
--ha-card-box-shadow: none;

View File

@ -32,6 +32,7 @@ const mainStyles = css`
--accent-color: ${unsafeCSS(DEFAULT_ACCENT_COLOR)};
--divider-color: rgba(0, 0, 0, 0.12);
--outline-color: rgba(0, 0, 0, 0.12);
--outline-hover-color: rgba(0, 0, 0, 0.24);
--scrollbar-thumb-color: rgb(194, 194, 194);

View File

@ -15,6 +15,7 @@ export const darkStyles = {
"switch-unchecked-track-color": "#9b9b9b",
"divider-color": "rgba(225, 225, 225, .12)",
"outline-color": "rgba(225, 225, 225, .12)",
"outline-hover-color": "rgba(225, 225, 225, .24)",
"mdc-ripple-color": "#AAAAAA",
"mdc-linear-progress-buffer-color": "rgba(255, 255, 255, 0.1)",

View File

@ -1959,7 +1959,11 @@
"labels": {
"caption": "Labels",
"description": "Group devices and entities",
"headers": { "name": "Name", "icon": "Icon", "color": "Color" },
"headers": {
"name": "Name",
"icon": "Icon",
"color": "Color"
},
"add_label": "Add label",
"no_labels": "You don't have any labels",
"introduction": "Labels can help you organize your areas, devices and entities. They can be used to filter in the UI, or use them as a target in automations.",
@ -5388,7 +5392,6 @@
"type": "View type",
"type_warning_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.",
"type_warning_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.",
"types": {
"masonry": "Masonry (default)",
"sidebar": "Sidebar",

104
yarn.lock
View File

@ -4543,15 +4543,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/eslint-plugin@npm:7.3.1"
"@typescript-eslint/eslint-plugin@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.4.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:7.3.1"
"@typescript-eslint/type-utils": "npm:7.3.1"
"@typescript-eslint/utils": "npm:7.3.1"
"@typescript-eslint/visitor-keys": "npm:7.3.1"
"@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/type-utils": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4"
@ -4564,44 +4564,44 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10/8ed276113a714d93ab3ababb1179e4785bd9378e6d97726519ea1d2ac502a94475e0be988c2ec427dcfc1e6950329d58da6e64131ee87028fce63493461cc51a
checksum: 10/9bd8852c7e4e9608c3fded94f7c60506cc7d2b6d8a8c1cad6d48969a7363751b20282874e55ccdf180635cf204cb10b3e1e5c3d1cff34d4fcd07762be3fc138e
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/parser@npm:7.3.1"
"@typescript-eslint/parser@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/parser@npm:7.4.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:7.3.1"
"@typescript-eslint/types": "npm:7.3.1"
"@typescript-eslint/typescript-estree": "npm:7.3.1"
"@typescript-eslint/visitor-keys": "npm:7.3.1"
"@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.56.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 10/018326010fec1dcefd75809ccac5102a475bf1e052d824b898d707e7c0bf3e51e101164b410d1b2a513628985c96eb412538644d2005e26b99a22db6eb9402df
checksum: 10/142a9e1187d305ed43b4fef659c36fa4e28359467198c986f0955c70b4067c9799f4c85d9881fbf099c55dfb265e30666e28b3ef290520e242b45ca7cb8e4ca9
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/scope-manager@npm:7.3.1"
"@typescript-eslint/scope-manager@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/scope-manager@npm:7.4.0"
dependencies:
"@typescript-eslint/types": "npm:7.3.1"
"@typescript-eslint/visitor-keys": "npm:7.3.1"
checksum: 10/7384d1f46d7f3678a1135a1ac0bd8b6dfa2f01e93b19e2510c7082766cf6983a1bf80b4ccf498651199a81d9f2bdb65101fd7a19226a723260514204d0c30b34
"@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.4.0"
checksum: 10/8cf9292444f9731017a707cac34bef5ae0eb33b5cd42ed07fcd046e981d97889d9201d48e02f470f2315123f53771435e10b1dc81642af28a11df5352a8e8be2
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/type-utils@npm:7.3.1"
"@typescript-eslint/type-utils@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/type-utils@npm:7.4.0"
dependencies:
"@typescript-eslint/typescript-estree": "npm:7.3.1"
"@typescript-eslint/utils": "npm:7.3.1"
"@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.4.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1"
peerDependencies:
@ -4609,23 +4609,23 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10/fae9003a76a8f2a2a4bb88dc0f82c0a1ca0688633183fac391920e7124a12807aac84bb287a21f61e99523c15223d1c08e7680685ebf21d07429604cba6c420b
checksum: 10/a8bd0929d8237679b2b8a7817f070a4b9658ee976882fba8ff37e4a70dd33f87793e1b157771104111fe8054eaa8ad437a010b6aa465072fbdb932647125db2d
languageName: node
linkType: hard
"@typescript-eslint/types@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/types@npm:7.3.1"
checksum: 10/c9c8eae1cf937cececd99a253bd65eb71b40206e79cf917ad9c3b3ab80cc7ce5fefb2804f9fd2a70e7438951f0d1e63df3031fc61e3a08dfef5fde208a12e0ed
"@typescript-eslint/types@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/types@npm:7.4.0"
checksum: 10/2782c5bf65cd3dfa9cd32bc3023676bbca22144987c3f6c6b67fd96c73d4a60b85a57458c49fd11b9971ac6531824bb3ae0664491e7a6de25d80c523c9be92b7
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/typescript-estree@npm:7.3.1"
"@typescript-eslint/typescript-estree@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/typescript-estree@npm:7.4.0"
dependencies:
"@typescript-eslint/types": "npm:7.3.1"
"@typescript-eslint/visitor-keys": "npm:7.3.1"
"@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4"
globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3"
@ -4635,34 +4635,34 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 10/363ad9864b56394b4000dff7c2b77d0ea52042c3c20e3b86c0f3c66044915632d9890255527c6f3a5ef056886dec72e38fbcfce49d4ad092c160440f54128230
checksum: 10/162ec9d7582f45588342e1be36fdb60e41f50bbdfbc3035c91b517ff5d45244f776921c88d88e543e1c7d0f1e6ada5474a8316b78f1b0e6d2233b101bc45b166
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/utils@npm:7.3.1"
"@typescript-eslint/utils@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/utils@npm:7.4.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:7.3.1"
"@typescript-eslint/types": "npm:7.3.1"
"@typescript-eslint/typescript-estree": "npm:7.3.1"
"@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.4.0"
semver: "npm:^7.5.4"
peerDependencies:
eslint: ^8.56.0
checksum: 10/234d9d65fe5d0f4a31345bd8f5a6f2879a578b3a531a14c2b3edaa7fb587c71d26249f86c41857382c0405384dc104955c02b588b3cee6fc2734f1ae40aef07b
checksum: 10/ffed27e770c486cd000ff892d9049b0afe8b9d6318452a5355b78a37436cbb414bceacae413a2ac813f3e584684825d5e0baa2e6376b7ad6013a108ac91bc19d
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:7.3.1":
version: 7.3.1
resolution: "@typescript-eslint/visitor-keys@npm:7.3.1"
"@typescript-eslint/visitor-keys@npm:7.4.0":
version: 7.4.0
resolution: "@typescript-eslint/visitor-keys@npm:7.4.0"
dependencies:
"@typescript-eslint/types": "npm:7.3.1"
"@typescript-eslint/types": "npm:7.4.0"
eslint-visitor-keys: "npm:^3.4.1"
checksum: 10/163a93597c1d696920a19b3c1627d02368bdd52059f811c0fadd680c38034bb6418ebefe99d8ce26e0dd44ae184f18fab186af775de1a8771256be1a7905c174
checksum: 10/70dc99f2ad116c6e2d9e55af249e4453e06bba2ceea515adef2d2e86e97e557865bb1b1d467667462443eb0d624baba36f7442fd1082f3874339bbc381c26e93
languageName: node
linkType: hard
@ -9688,8 +9688,8 @@ __metadata:
"@types/tar": "npm:6.1.11"
"@types/ua-parser-js": "npm:0.7.39"
"@types/webspeechapi": "npm:0.0.29"
"@typescript-eslint/eslint-plugin": "npm:7.3.1"
"@typescript-eslint/parser": "npm:7.3.1"
"@typescript-eslint/eslint-plugin": "npm:7.4.0"
"@typescript-eslint/parser": "npm:7.4.0"
"@vaadin/combo-box": "npm:24.3.10"
"@vaadin/vaadin-themable-mixin": "npm:24.3.10"
"@vibrant/color": "npm:3.2.1-alpha.1"