mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Add toolbars and mobile headers + layout tweaks (#4803)
* Add toolbars and mobile headers + layout tweaks * Comments
This commit is contained in:
parent
c93e1b0123
commit
7903541689
@ -7,14 +7,18 @@ import {
|
|||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../dom/fire_event";
|
||||||
import "@polymer/iron-icon/iron-icon";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@material/mwc-button";
|
import "../../components/ha-icon";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@customElement("search-input")
|
@customElement("search-input")
|
||||||
class SearchInput extends LitElement {
|
class SearchInput extends LitElement {
|
||||||
@property() public filter?: string;
|
@property() public filter?: string;
|
||||||
|
@property({ type: Boolean, attribute: "no-label-float" })
|
||||||
|
public noLabelFloat? = false;
|
||||||
|
@property({ type: Boolean, attribute: "no-underline" })
|
||||||
|
public noUnderline = false;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
this.shadowRoot!.querySelector("paper-input")!.focus();
|
||||||
@ -22,18 +26,24 @@ class SearchInput extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<style>
|
||||||
|
.no-underline {
|
||||||
|
--paper-input-container-underline: {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<paper-input
|
<paper-input
|
||||||
|
class=${classMap({ "no-underline": this.noUnderline })}
|
||||||
autofocus
|
autofocus
|
||||||
label="Search"
|
label="Search"
|
||||||
.value=${this.filter}
|
.value=${this.filter}
|
||||||
@value-changed=${this._filterInputChanged}
|
@value-changed=${this._filterInputChanged}
|
||||||
|
.noLabelFloat=${this.noLabelFloat}
|
||||||
>
|
>
|
||||||
<iron-icon
|
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon>
|
||||||
icon="hass:magnify"
|
|
||||||
slot="prefix"
|
|
||||||
class="prefix"
|
|
||||||
></iron-icon>
|
|
||||||
${this.filter &&
|
${this.filter &&
|
||||||
html`
|
html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
@ -101,6 +101,8 @@ export class HaDataTable extends BaseElement {
|
|||||||
@property({ type: String }) private _sortColumn?: string;
|
@property({ type: String }) private _sortColumn?: string;
|
||||||
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
||||||
@property({ type: Array }) private _filteredData: DataTableRowData[] = [];
|
@property({ type: Array }) private _filteredData: DataTableRowData[] = [];
|
||||||
|
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||||
|
@query(".scroller") private _scroller!: HTMLDivElement;
|
||||||
private _sortColumns: {
|
private _sortColumns: {
|
||||||
[key: string]: DataTableSortColumnData;
|
[key: string]: DataTableSortColumnData;
|
||||||
} = {};
|
} = {};
|
||||||
@ -170,7 +172,7 @@ export class HaDataTable extends BaseElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="mdc-data-table">
|
<div class="mdc-data-table">
|
||||||
<slot name="header">
|
<slot name="header" @slotchange=${this._calcScrollHeight}>
|
||||||
${this._filterable
|
${this._filterable
|
||||||
? html`
|
? html`
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
@ -181,112 +183,114 @@ export class HaDataTable extends BaseElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</slot>
|
</slot>
|
||||||
<table class="mdc-data-table__table">
|
<div class="scroller">
|
||||||
<thead>
|
<table class="mdc-data-table__table">
|
||||||
<tr class="mdc-data-table__header-row">
|
<thead>
|
||||||
${this.selectable
|
<tr class="mdc-data-table__header-row">
|
||||||
? html`
|
${this.selectable
|
||||||
|
? html`
|
||||||
|
<th
|
||||||
|
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
|
||||||
|
role="columnheader"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<ha-checkbox
|
||||||
|
class="mdc-data-table__row-checkbox"
|
||||||
|
@change=${this._handleHeaderRowCheckboxChange}
|
||||||
|
.indeterminate=${this._headerIndeterminate}
|
||||||
|
.checked=${this._headerChecked}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
</th>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${Object.entries(this.columns).map((columnEntry) => {
|
||||||
|
const [key, column] = columnEntry;
|
||||||
|
const sorted = key === this._sortColumn;
|
||||||
|
const classes = {
|
||||||
|
"mdc-data-table__header-cell--numeric": Boolean(
|
||||||
|
column.type && column.type === "numeric"
|
||||||
|
),
|
||||||
|
"mdc-data-table__header-cell--icon": Boolean(
|
||||||
|
column.type && column.type === "icon"
|
||||||
|
),
|
||||||
|
sortable: Boolean(column.sortable),
|
||||||
|
"not-sorted": Boolean(column.sortable && !sorted),
|
||||||
|
};
|
||||||
|
return html`
|
||||||
<th
|
<th
|
||||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
|
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||||
role="columnheader"
|
role="columnheader"
|
||||||
scope="col"
|
scope="col"
|
||||||
|
@click=${this._handleHeaderClick}
|
||||||
|
data-column-id="${key}"
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
${column.sortable
|
||||||
class="mdc-data-table__row-checkbox"
|
? html`
|
||||||
@change=${this._handleHeaderRowCheckboxChange}
|
<ha-icon
|
||||||
.indeterminate=${this._headerIndeterminate}
|
.icon=${sorted && this._sortDirection === "desc"
|
||||||
.checked=${this._headerChecked}
|
? "hass:arrow-down"
|
||||||
>
|
: "hass:arrow-up"}
|
||||||
</ha-checkbox>
|
></ha-icon>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<span>${column.title}</span>
|
||||||
</th>
|
</th>
|
||||||
`
|
`;
|
||||||
: ""}
|
})}
|
||||||
${Object.entries(this.columns).map((columnEntry) => {
|
</tr>
|
||||||
const [key, column] = columnEntry;
|
</thead>
|
||||||
const sorted = key === this._sortColumn;
|
<tbody class="mdc-data-table__content">
|
||||||
const classes = {
|
${repeat(
|
||||||
"mdc-data-table__header-cell--numeric": Boolean(
|
this._filteredData!,
|
||||||
column.type && column.type === "numeric"
|
(row: DataTableRowData) => row[this.id],
|
||||||
),
|
(row: DataTableRowData) => html`
|
||||||
"mdc-data-table__header-cell--icon": Boolean(
|
<tr
|
||||||
column.type && column.type === "icon"
|
data-row-id="${row[this.id]}"
|
||||||
),
|
@click=${this._handleRowClick}
|
||||||
sortable: Boolean(column.sortable),
|
class="mdc-data-table__row"
|
||||||
"not-sorted": Boolean(column.sortable && !sorted),
|
|
||||||
};
|
|
||||||
return html`
|
|
||||||
<th
|
|
||||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
|
||||||
role="columnheader"
|
|
||||||
scope="col"
|
|
||||||
@click=${this._handleHeaderClick}
|
|
||||||
data-column-id="${key}"
|
|
||||||
>
|
>
|
||||||
${column.sortable
|
${this.selectable
|
||||||
? html`
|
? html`
|
||||||
<ha-icon
|
<td
|
||||||
.icon=${sorted && this._sortDirection === "desc"
|
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||||
? "hass:arrow-down"
|
>
|
||||||
: "hass:arrow-up"}
|
<ha-checkbox
|
||||||
></ha-icon>
|
class="mdc-data-table__row-checkbox"
|
||||||
|
@change=${this._handleRowCheckboxChange}
|
||||||
|
.checked=${this._checkedRows.includes(
|
||||||
|
String(row[this.id])
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
</td>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<span>${column.title}</span>
|
${Object.entries(this.columns).map((columnEntry) => {
|
||||||
</th>
|
const [key, column] = columnEntry;
|
||||||
`;
|
return html`
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="mdc-data-table__content">
|
|
||||||
${repeat(
|
|
||||||
this._filteredData!,
|
|
||||||
(row: DataTableRowData) => row[this.id],
|
|
||||||
(row: DataTableRowData) => html`
|
|
||||||
<tr
|
|
||||||
data-row-id="${row[this.id]}"
|
|
||||||
@click=${this._handleRowClick}
|
|
||||||
class="mdc-data-table__row"
|
|
||||||
>
|
|
||||||
${this.selectable
|
|
||||||
? html`
|
|
||||||
<td
|
<td
|
||||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
class="mdc-data-table__cell ${classMap({
|
||||||
|
"mdc-data-table__cell--numeric": Boolean(
|
||||||
|
column.type && column.type === "numeric"
|
||||||
|
),
|
||||||
|
"mdc-data-table__cell--icon": Boolean(
|
||||||
|
column.type && column.type === "icon"
|
||||||
|
),
|
||||||
|
})}"
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
${column.template
|
||||||
class="mdc-data-table__row-checkbox"
|
? column.template(row[key], row)
|
||||||
@change=${this._handleRowCheckboxChange}
|
: row[key]}
|
||||||
.checked=${this._checkedRows.includes(
|
|
||||||
String(row[this.id])
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-checkbox>
|
|
||||||
</td>
|
</td>
|
||||||
`
|
`;
|
||||||
: ""}
|
})}
|
||||||
${Object.entries(this.columns).map((columnEntry) => {
|
</tr>
|
||||||
const [key, column] = columnEntry;
|
`
|
||||||
return html`
|
)}
|
||||||
<td
|
</tbody>
|
||||||
class="mdc-data-table__cell ${classMap({
|
</table>
|
||||||
"mdc-data-table__cell--numeric": Boolean(
|
</div>
|
||||||
column.type && column.type === "numeric"
|
|
||||||
),
|
|
||||||
"mdc-data-table__cell--icon": Boolean(
|
|
||||||
column.type && column.type === "icon"
|
|
||||||
),
|
|
||||||
})}"
|
|
||||||
>
|
|
||||||
${column.template
|
|
||||||
? column.template(row[key], row)
|
|
||||||
: row[key]}
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -434,6 +438,11 @@ export class HaDataTable extends BaseElement {
|
|||||||
this._debounceSearch(ev.detail.value);
|
this._debounceSearch(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _calcScrollHeight() {
|
||||||
|
await this.updateComplete;
|
||||||
|
this._scroller.style.maxHeight = `calc(100% - ${this._header.clientHeight}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
/* default mdc styles, colors changed, without checkbox styles */
|
/* default mdc styles, colors changed, without checkbox styles */
|
||||||
@ -584,8 +593,14 @@ export class HaDataTable extends BaseElement {
|
|||||||
|
|
||||||
/* custom from here */
|
/* custom from here */
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-data-table {
|
.mdc-data-table {
|
||||||
display: block;
|
display: block;
|
||||||
|
border-width: var(--data-table-border-width, 1px);
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell {
|
.mdc-data-table__header-cell {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -614,6 +629,16 @@ export class HaDataTable extends BaseElement {
|
|||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||||
}
|
}
|
||||||
|
search-input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.scroller {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
slot[name="header"] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import {
|
import {
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
|
DeviceEntityLookup,
|
||||||
} from "../../data/device_registry";
|
} from "../../data/device_registry";
|
||||||
import { compare } from "../../common/string/compare";
|
import { compare } from "../../common/string/compare";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
@ -30,7 +31,6 @@ import {
|
|||||||
AreaRegistryEntry,
|
AreaRegistryEntry,
|
||||||
subscribeAreaRegistry,
|
subscribeAreaRegistry,
|
||||||
} from "../../data/area_registry";
|
} from "../../data/area_registry";
|
||||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
|
DeviceEntityLookup,
|
||||||
} from "../../data/device_registry";
|
} from "../../data/device_registry";
|
||||||
import { compare } from "../../common/string/compare";
|
import { compare } from "../../common/string/compare";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
@ -29,7 +30,6 @@ import {
|
|||||||
AreaRegistryEntry,
|
AreaRegistryEntry,
|
||||||
subscribeAreaRegistry,
|
subscribeAreaRegistry,
|
||||||
} from "../../data/area_registry";
|
} from "../../data/area_registry";
|
||||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
|
@ -17,6 +17,10 @@ export interface DeviceRegistryEntry {
|
|||||||
name_by_user?: string;
|
name_by_user?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceEntityLookup {
|
||||||
|
[deviceId: string]: EntityRegistryEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeviceRegistryEntryMutableParams {
|
export interface DeviceRegistryEntryMutableParams {
|
||||||
area_id?: string | null;
|
area_id?: string | null;
|
||||||
name_by_user?: string | null;
|
name_by_user?: string | null;
|
||||||
|
157
src/layouts/hass-tabs-subpage-data-table.ts
Normal file
157
src/layouts/hass-tabs-subpage-data-table.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
query,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../components/data-table/ha-data-table";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import {
|
||||||
|
HaDataTable,
|
||||||
|
DataTableColumnContainer,
|
||||||
|
DataTableRowData,
|
||||||
|
} from "../components/data-table/ha-data-table";
|
||||||
|
import "./hass-tabs-subpage";
|
||||||
|
import { HomeAssistant, Route } from "../types";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PageNavigation } from "./hass-tabs-subpage";
|
||||||
|
|
||||||
|
@customElement("hass-tabs-subpage-data-table")
|
||||||
|
export class HaTabsSubpageDataTable extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public isWide!: boolean;
|
||||||
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
|
/**
|
||||||
|
* Object with the columns.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||||
|
/**
|
||||||
|
* Data to show in the table.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||||
|
/**
|
||||||
|
* Should rows be selectable.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) public selectable = false;
|
||||||
|
/**
|
||||||
|
* Field with a unique id per entry in data.
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
@property({ type: String }) public id = "id";
|
||||||
|
/**
|
||||||
|
* String to filter the data in the data table on.
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
@property({ type: String }) public filter = "";
|
||||||
|
/**
|
||||||
|
* What path to use when the back button is pressed.
|
||||||
|
* @type {String}
|
||||||
|
* @attr back-path
|
||||||
|
*/
|
||||||
|
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||||
|
/**
|
||||||
|
* Function to call when the back button is pressed.
|
||||||
|
* @type {() => void}
|
||||||
|
*/
|
||||||
|
@property() public backCallback?: () => void;
|
||||||
|
@property() public route!: Route;
|
||||||
|
/**
|
||||||
|
* Array of tabs to show on the page.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
@property() public tabs!: PageNavigation[];
|
||||||
|
@query("ha-data-table") private _dataTable!: HaDataTable;
|
||||||
|
|
||||||
|
public clearSelection() {
|
||||||
|
this._dataTable.clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.backPath=${this.backPath}
|
||||||
|
.backCallback=${this.backCallback}
|
||||||
|
.route=${this.route}
|
||||||
|
.tabs=${this.tabs}
|
||||||
|
>
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<div slot="header">
|
||||||
|
<slot name="header">
|
||||||
|
<div class="search-toolbar">
|
||||||
|
<search-input
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
></search-input>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-data-table
|
||||||
|
.columns=${this.columns}
|
||||||
|
.data=${this.data}
|
||||||
|
.filter=${this.filter}
|
||||||
|
.selectable=${this.selectable}
|
||||||
|
.id=${this.id}
|
||||||
|
>
|
||||||
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<div slot="header">
|
||||||
|
<slot name="header">
|
||||||
|
<slot name="header">
|
||||||
|
<div class="table-header">
|
||||||
|
<search-input
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
></search-input></div></slot
|
||||||
|
></slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div slot="header"></div>
|
||||||
|
`}
|
||||||
|
</ha-data-table>
|
||||||
|
</hass-tabs-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
|
this.filter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-data-table {
|
||||||
|
width: 100%;
|
||||||
|
--data-table-border-width: 0;
|
||||||
|
}
|
||||||
|
:host(:not([narrow])) ha-data-table {
|
||||||
|
height: calc(100vh - 65px);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.table-header {
|
||||||
|
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||||
|
}
|
||||||
|
.search-toolbar {
|
||||||
|
margin-left: -24px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
search-input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,11 @@ class HassTabsSubpage extends LitElement {
|
|||||||
.hassio=${this.hassio}
|
.hassio=${this.hassio}
|
||||||
@click=${this._backTapped}
|
@click=${this._backTapped}
|
||||||
></ha-paper-icon-button-arrow-prev>
|
></ha-paper-icon-button-arrow-prev>
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<div main-title><slot name="header"></slot></div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||||
${this.tabs.map((page, index) =>
|
${this.tabs.map((page, index) =>
|
||||||
(!page.component ||
|
(!page.component ||
|
||||||
@ -138,11 +143,6 @@ class HassTabsSubpage extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) .toolbar {
|
|
||||||
background-color: var(--primary-background-color);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabbar {
|
#tabbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -77,154 +77,155 @@ export class HaAutomationEditor extends LitElement {
|
|||||||
<div class="errors">${this._errors}</div>
|
<div class="errors">${this._errors}</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div
|
${this._config
|
||||||
class="content ${classMap({
|
? html`
|
||||||
rtl: computeRTL(this.hass),
|
${this.narrow
|
||||||
})}"
|
? html`
|
||||||
>
|
<span slot="header">${this._config?.alias}</span>
|
||||||
${this._config
|
`
|
||||||
? html`
|
: ""}
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">${this._config.alias}</span>
|
${!this.narrow
|
||||||
<span slot="introduction">
|
? html`
|
||||||
${this.hass.localize(
|
<span slot="header">${this._config.alias}</span>
|
||||||
"ui.panel.config.automation.editor.introduction"
|
`
|
||||||
)}
|
: ""}
|
||||||
</span>
|
<span slot="introduction">
|
||||||
<ha-card>
|
${this.hass.localize(
|
||||||
<div class="card-content">
|
"ui.panel.config.automation.editor.introduction"
|
||||||
<paper-input
|
)}
|
||||||
.label=${this.hass.localize(
|
</span>
|
||||||
"ui.panel.config.automation.editor.alias"
|
<ha-card>
|
||||||
)}
|
<div class="card-content">
|
||||||
name="alias"
|
<paper-input
|
||||||
.value=${this._config.alias}
|
.label=${this.hass.localize(
|
||||||
@value-changed=${this._valueChanged}
|
"ui.panel.config.automation.editor.alias"
|
||||||
>
|
)}
|
||||||
</paper-input>
|
name="alias"
|
||||||
<ha-textarea
|
.value=${this._config.alias}
|
||||||
.label=${this.hass.localize(
|
@value-changed=${this._valueChanged}
|
||||||
"ui.panel.config.automation.editor.description.label"
|
>
|
||||||
)}
|
</paper-input>
|
||||||
.placeholder=${this.hass.localize(
|
<ha-textarea
|
||||||
"ui.panel.config.automation.editor.description.placeholder"
|
.label=${this.hass.localize(
|
||||||
)}
|
"ui.panel.config.automation.editor.description.label"
|
||||||
name="description"
|
)}
|
||||||
.value=${this._config.description}
|
.placeholder=${this.hass.localize(
|
||||||
@value-changed=${this._valueChanged}
|
"ui.panel.config.automation.editor.description.placeholder"
|
||||||
></ha-textarea>
|
)}
|
||||||
</div>
|
name="description"
|
||||||
${this.creatingNew
|
.value=${this._config.description}
|
||||||
? ""
|
@value-changed=${this._valueChanged}
|
||||||
: html`
|
></ha-textarea>
|
||||||
<div
|
</div>
|
||||||
class="card-actions layout horizontal justified center"
|
${this.creatingNew
|
||||||
>
|
? ""
|
||||||
<div class="layout horizontal center">
|
: html`
|
||||||
<ha-entity-toggle
|
<div
|
||||||
.hass=${this.hass}
|
class="card-actions layout horizontal justified center"
|
||||||
.stateObj=${this.automation}
|
>
|
||||||
></ha-entity-toggle>
|
<div class="layout horizontal center">
|
||||||
${this.hass.localize(
|
<ha-entity-toggle
|
||||||
"ui.panel.config.automation.editor.enable_disable"
|
.hass=${this.hass}
|
||||||
)}
|
.stateObj=${this.automation}
|
||||||
</div>
|
></ha-entity-toggle>
|
||||||
<mwc-button @click=${this._excuteAutomation}>
|
${this.hass.localize(
|
||||||
${this.hass.localize(
|
"ui.panel.config.automation.editor.enable_disable"
|
||||||
"ui.card.automation.trigger"
|
)}
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
</div>
|
||||||
`}
|
<mwc-button @click=${this._excuteAutomation}>
|
||||||
</ha-card>
|
${this.hass.localize("ui.card.automation.trigger")}
|
||||||
</ha-config-section>
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.header"
|
"ui.panel.config.automation.editor.triggers.introduction"
|
||||||
)}
|
)}
|
||||||
</span>
|
</p>
|
||||||
<span slot="introduction">
|
<a
|
||||||
<p>
|
href="https://home-assistant.io/docs/automation/trigger/"
|
||||||
${this.hass.localize(
|
target="_blank"
|
||||||
"ui.panel.config.automation.editor.triggers.introduction"
|
>
|
||||||
)}
|
${this.hass.localize(
|
||||||
</p>
|
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||||
<a
|
)}
|
||||||
href="https://home-assistant.io/docs/automation/trigger/"
|
</a>
|
||||||
target="_blank"
|
</span>
|
||||||
>
|
<ha-automation-trigger
|
||||||
${this.hass.localize(
|
.triggers=${this._config.trigger}
|
||||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
@value-changed=${this._triggerChanged}
|
||||||
)}
|
.hass=${this.hass}
|
||||||
</a>
|
></ha-automation-trigger>
|
||||||
</span>
|
</ha-config-section>
|
||||||
<ha-automation-trigger
|
|
||||||
.triggers=${this._config.trigger}
|
|
||||||
@value-changed=${this._triggerChanged}
|
|
||||||
.hass=${this.hass}
|
|
||||||
></ha-automation-trigger>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.header"
|
"ui.panel.config.automation.editor.conditions.introduction"
|
||||||
)}
|
)}
|
||||||
</span>
|
</p>
|
||||||
<span slot="introduction">
|
<a
|
||||||
<p>
|
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||||
${this.hass.localize(
|
target="_blank"
|
||||||
"ui.panel.config.automation.editor.conditions.introduction"
|
>
|
||||||
)}
|
${this.hass.localize(
|
||||||
</p>
|
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||||
<a
|
)}
|
||||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
</a>
|
||||||
target="_blank"
|
</span>
|
||||||
>
|
<ha-automation-condition
|
||||||
${this.hass.localize(
|
.conditions=${this._config.condition || []}
|
||||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
@value-changed=${this._conditionChanged}
|
||||||
)}
|
.hass=${this.hass}
|
||||||
</a>
|
></ha-automation-condition>
|
||||||
</span>
|
</ha-config-section>
|
||||||
<ha-automation-condition
|
|
||||||
.conditions=${this._config.condition || []}
|
|
||||||
@value-changed=${this._conditionChanged}
|
|
||||||
.hass=${this.hass}
|
|
||||||
></ha-automation-condition>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.header"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="introduction">
|
||||||
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.header"
|
"ui.panel.config.automation.editor.actions.introduction"
|
||||||
)}
|
)}
|
||||||
</span>
|
</p>
|
||||||
<span slot="introduction">
|
<a
|
||||||
<p>
|
href="https://home-assistant.io/docs/automation/action/"
|
||||||
${this.hass.localize(
|
target="_blank"
|
||||||
"ui.panel.config.automation.editor.actions.introduction"
|
>
|
||||||
)}
|
${this.hass.localize(
|
||||||
</p>
|
"ui.panel.config.automation.editor.actions.learn_more"
|
||||||
<a
|
)}
|
||||||
href="https://home-assistant.io/docs/automation/action/"
|
</a>
|
||||||
target="_blank"
|
</span>
|
||||||
>
|
<ha-automation-action
|
||||||
${this.hass.localize(
|
.actions=${this._config.action}
|
||||||
"ui.panel.config.automation.editor.actions.learn_more"
|
@value-changed=${this._actionChanged}
|
||||||
)}
|
.hass=${this.hass}
|
||||||
</a>
|
></ha-automation-action>
|
||||||
</span>
|
</ha-config-section>
|
||||||
<ha-automation-action
|
`
|
||||||
.actions=${this._config.action}
|
: ""}
|
||||||
@value-changed=${this._actionChanged}
|
|
||||||
.hass=${this.hass}
|
|
||||||
></ha-automation-action>
|
|
||||||
</ha-config-section>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
<ha-fab
|
<ha-fab
|
||||||
?is-wide="${this.isWide}"
|
?is-wide="${this.isWide}"
|
||||||
?narrow="${this.narrow}"
|
?narrow="${this.narrow}"
|
||||||
|
@ -121,6 +121,14 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
>
|
>
|
||||||
|
${
|
||||||
|
this.narrow
|
||||||
|
? html`
|
||||||
|
<span slot="header">${device.name_by_user || device.name}</span>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
icon="hass:settings"
|
icon="hass:settings"
|
||||||
@ -130,7 +138,13 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="device-info">
|
<div class="device-info">
|
||||||
<h1>${device.name_by_user || device.name}</h1>
|
${
|
||||||
|
this.narrow
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<h1>${device.name_by_user || device.name}</h1>
|
||||||
|
`
|
||||||
|
}
|
||||||
<ha-device-card
|
<ha-device-card
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.areas=${this.areas}
|
.areas=${this.areas}
|
||||||
@ -498,6 +512,10 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .container > *:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
:host([narrow]) .container {
|
:host([narrow]) .container {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "../../../layouts/hass-tabs-subpage";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import "./ha-devices-data-table";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LitElement,
|
LitElement,
|
||||||
@ -11,11 +10,24 @@ import {
|
|||||||
css,
|
css,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
import {
|
||||||
|
DeviceRegistryEntry,
|
||||||
|
computeDeviceName,
|
||||||
|
DeviceEntityLookup,
|
||||||
|
} from "../../../data/device_registry";
|
||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import { ConfigEntry } from "../../../data/config_entries";
|
import { ConfigEntry } from "../../../data/config_entries";
|
||||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import { DeviceRowData } from "./ha-devices-data-table";
|
||||||
|
import {
|
||||||
|
DataTableColumnContainer,
|
||||||
|
DataTableRowData,
|
||||||
|
RowClickedEvent,
|
||||||
|
} from "../../../components/data-table/ha-data-table";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
|
||||||
@customElement("ha-config-devices-dashboard")
|
@customElement("ha-config-devices-dashboard")
|
||||||
export class HaConfigDeviceDashboard extends LitElement {
|
export class HaConfigDeviceDashboard extends LitElement {
|
||||||
@ -29,30 +41,219 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
@property() public domain!: string;
|
@property() public domain!: string;
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
|
private _devices = memoizeOne(
|
||||||
|
(
|
||||||
|
devices: DeviceRegistryEntry[],
|
||||||
|
entries: ConfigEntry[],
|
||||||
|
entities: EntityRegistryEntry[],
|
||||||
|
areas: AreaRegistryEntry[],
|
||||||
|
domain: string,
|
||||||
|
localize: LocalizeFunc
|
||||||
|
) => {
|
||||||
|
// Some older installations might have devices pointing at invalid entryIDs
|
||||||
|
// So we guard for that.
|
||||||
|
|
||||||
|
let outputDevices: DeviceRowData[] = devices;
|
||||||
|
|
||||||
|
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||||
|
for (const device of devices) {
|
||||||
|
deviceLookup[device.id] = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||||
|
for (const entity of entities) {
|
||||||
|
if (!entity.device_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(entity.device_id in deviceEntityLookup)) {
|
||||||
|
deviceEntityLookup[entity.device_id] = [];
|
||||||
|
}
|
||||||
|
deviceEntityLookup[entity.device_id].push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||||
|
for (const entry of entries) {
|
||||||
|
entryLookup[entry.entry_id] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||||
|
for (const area of areas) {
|
||||||
|
areaLookup[area.area_id] = area;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain) {
|
||||||
|
outputDevices = outputDevices.filter((device) =>
|
||||||
|
device.config_entries.find(
|
||||||
|
(entryId) =>
|
||||||
|
entryId in entryLookup && entryLookup[entryId].domain === domain
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputDevices = outputDevices.map((device) => {
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
name: computeDeviceName(
|
||||||
|
device,
|
||||||
|
this.hass,
|
||||||
|
deviceEntityLookup[device.id]
|
||||||
|
),
|
||||||
|
model: device.model || "<unknown>",
|
||||||
|
manufacturer: device.manufacturer || "<unknown>",
|
||||||
|
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||||
|
integration: device.config_entries.length
|
||||||
|
? device.config_entries
|
||||||
|
.filter((entId) => entId in entryLookup)
|
||||||
|
.map(
|
||||||
|
(entId) =>
|
||||||
|
localize(
|
||||||
|
`component.${entryLookup[entId].domain}.config.title`
|
||||||
|
) || entryLookup[entId].domain
|
||||||
|
)
|
||||||
|
.join(", ")
|
||||||
|
: "No integration",
|
||||||
|
battery_entity: this._batteryEntity(device.id, deviceEntityLookup),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputDevices;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _columns = memoizeOne(
|
||||||
|
(narrow: boolean): DataTableColumnContainer =>
|
||||||
|
narrow
|
||||||
|
? {
|
||||||
|
name: {
|
||||||
|
title: "Device",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
template: (name, device: DataTableRowData) => {
|
||||||
|
const battery = device.battery_entity
|
||||||
|
? this.hass.states[device.battery_entity]
|
||||||
|
: undefined;
|
||||||
|
// Have to work on a nice layout for mobile
|
||||||
|
return html`
|
||||||
|
${name}<br />
|
||||||
|
${device.area} | ${device.integration}<br />
|
||||||
|
${battery && !isNaN(battery.state as any)
|
||||||
|
? html`
|
||||||
|
${battery.state}%
|
||||||
|
<ha-state-icon
|
||||||
|
.hass=${this.hass!}
|
||||||
|
.stateObj=${battery}
|
||||||
|
></ha-state-icon>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
name: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.device"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
},
|
||||||
|
manufacturer: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.manufacturer"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.model"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.area"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
integration: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.integration"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
battery_entity: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.devices.data_table.battery"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
type: "numeric",
|
||||||
|
template: (batteryEntity: string) => {
|
||||||
|
const battery = batteryEntity
|
||||||
|
? this.hass.states[batteryEntity]
|
||||||
|
: undefined;
|
||||||
|
return battery && !isNaN(battery.state as any)
|
||||||
|
? html`
|
||||||
|
${battery.state}%
|
||||||
|
<ha-state-icon
|
||||||
|
.hass=${this.hass!}
|
||||||
|
.stateObj=${battery}
|
||||||
|
></ha-state-icon>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
-
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
|
.columns=${this._columns(this.narrow)}
|
||||||
|
.data=${this._devices(
|
||||||
|
this.devices,
|
||||||
|
this.entries,
|
||||||
|
this.entities,
|
||||||
|
this.areas,
|
||||||
|
this.domain,
|
||||||
|
this.hass.localize
|
||||||
|
)}
|
||||||
|
@row-click=${this._handleRowClicked}
|
||||||
>
|
>
|
||||||
<div class="content">
|
</hass-tabs-subpage-data-table>
|
||||||
<ha-devices-data-table
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
.devices=${this.devices}
|
|
||||||
.entries=${this.entries}
|
|
||||||
.entities=${this.entities}
|
|
||||||
.areas=${this.areas}
|
|
||||||
.domain=${this.domain}
|
|
||||||
></ha-devices-data-table>
|
|
||||||
</div>
|
|
||||||
</hass-tabs-subpage>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _batteryEntity(
|
||||||
|
deviceId: string,
|
||||||
|
deviceEntityLookup: DeviceEntityLookup
|
||||||
|
): string | undefined {
|
||||||
|
const batteryEntity = (deviceEntityLookup[deviceId] || []).find(
|
||||||
|
(entity) =>
|
||||||
|
this.hass.states[entity.entity_id] &&
|
||||||
|
this.hass.states[entity.entity_id].attributes.device_class === "battery"
|
||||||
|
);
|
||||||
|
|
||||||
|
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleRowClicked(ev: CustomEvent) {
|
||||||
|
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||||
|
navigate(this, `/config/devices/device/${deviceId}`);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
.content {
|
.content {
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
|
DeviceEntityLookup,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import { ConfigEntry } from "../../../data/config_entries";
|
import { ConfigEntry } from "../../../data/config_entries";
|
||||||
@ -35,10 +36,6 @@ export interface DeviceRowData extends DeviceRegistryEntry {
|
|||||||
battery_entity?: string;
|
battery_entity?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceEntityLookup {
|
|
||||||
[deviceId: string]: EntityRegistryEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-devices-data-table")
|
@customElement("ha-devices-data-table")
|
||||||
export class HaDevicesDataTable extends LitElement {
|
export class HaDevicesDataTable extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
@ -19,8 +19,6 @@ import memoize from "memoize-one";
|
|||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
import { stateIcon } from "../../../common/entity/state_icon";
|
import { stateIcon } from "../../../common/entity/state_icon";
|
||||||
import "../../../components/data-table/ha-data-table";
|
|
||||||
// tslint:disable-next-line
|
|
||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
DataTableColumnData,
|
DataTableColumnData,
|
||||||
@ -29,6 +27,7 @@ import {
|
|||||||
SelectionChangedEvent,
|
SelectionChangedEvent,
|
||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
|
import "../../../common/search/search-input";
|
||||||
import {
|
import {
|
||||||
computeEntityRegistryName,
|
computeEntityRegistryName,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
@ -38,7 +37,7 @@ import {
|
|||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-loading-screen";
|
import "../../../layouts/hass-loading-screen";
|
||||||
import "../../../layouts/hass-tabs-subpage";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||||
@ -47,6 +46,7 @@ import {
|
|||||||
showEntityRegistryDetailDialog,
|
showEntityRegistryDetailDialog,
|
||||||
} from "./show-dialog-entity-registry-detail";
|
} from "./show-dialog-entity-registry-detail";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
@customElement("ha-config-entities")
|
@customElement("ha-config-entities")
|
||||||
export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||||
@ -223,154 +223,136 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
<hass-loading-screen></hass-loading-screen>
|
<hass-loading-screen></hass-loading-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
const headerToolbar = this._selectedEntities.length
|
||||||
|
? html`
|
||||||
|
<p class="selected-txt">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.selected",
|
||||||
|
"number",
|
||||||
|
this._selectedEntities.length
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="header-btns">
|
||||||
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<mwc-button @click=${this._enableSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.enable_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button @click=${this._disableSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.disable_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button @click=${this._removeSelected}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.remove_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-icon-button
|
||||||
|
id="enable-btn"
|
||||||
|
icon="hass:undo"
|
||||||
|
@click=${this._enableSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="enable-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.enable_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
<paper-icon-button
|
||||||
|
id="disable-btn"
|
||||||
|
icon="hass:cancel"
|
||||||
|
@click=${this._disableSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="disable-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.disable_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
<paper-icon-button
|
||||||
|
id="remove-btn"
|
||||||
|
icon="hass:delete"
|
||||||
|
@click=${this._removeSelected}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-tooltip for="remove-btn">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.entities.picker.remove_selected.button"
|
||||||
|
)}
|
||||||
|
</paper-tooltip>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<search-input
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
.filter=${this._filter}
|
||||||
|
></search-input>
|
||||||
|
<paper-menu-button no-animations horizontal-align="right">
|
||||||
|
<paper-icon-button
|
||||||
|
aria-label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.entities.picker.filter.filter"
|
||||||
|
)}
|
||||||
|
title="${this.hass!.localize(
|
||||||
|
"ui.panel.config.entities.picker.filter.filter"
|
||||||
|
)}"
|
||||||
|
icon="hass:filter-variant"
|
||||||
|
slot="dropdown-trigger"
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-listbox slot="dropdown-content">
|
||||||
|
<paper-icon-item @tap="${this._showDisabledChanged}">
|
||||||
|
<paper-checkbox
|
||||||
|
.checked=${this._showDisabled}
|
||||||
|
slot="item-icon"
|
||||||
|
></paper-checkbox>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.entities.picker.filter.show_disabled"
|
||||||
|
)}
|
||||||
|
</paper-icon-item>
|
||||||
|
<paper-icon-item @tap="${this._showRestoredChanged}">
|
||||||
|
<paper-checkbox
|
||||||
|
.checked=${this._showUnavailable}
|
||||||
|
slot="item-icon"
|
||||||
|
></paper-checkbox>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.entities.picker.filter.show_unavailable"
|
||||||
|
)}
|
||||||
|
</paper-icon-item>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
`;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
|
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||||
|
.data=${this._filteredEntities(
|
||||||
|
this._entities,
|
||||||
|
this._showDisabled,
|
||||||
|
this._showUnavailable
|
||||||
|
)}
|
||||||
|
.filter=${this._filter}
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
|
@row-click=${this._openEditEntry}
|
||||||
|
id="entity_id"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class=${classMap({
|
||||||
<div class="intro">
|
"search-toolbar": this.narrow,
|
||||||
<h2>
|
"table-header": !this.narrow,
|
||||||
${this.hass.localize("ui.panel.config.entities.picker.header")}
|
})} slot="header">
|
||||||
</h2>
|
${headerToolbar}
|
||||||
<p>
|
</div>
|
||||||
${this.hass.localize(
|
</ha-data-table>
|
||||||
"ui.panel.config.entities.picker.introduction"
|
</hass-tabs-subpage-data-table>
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.introduction2"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<a href="/config/integrations">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.integrations_page"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ha-data-table
|
|
||||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
|
||||||
.data=${this._filteredEntities(
|
|
||||||
this._entities,
|
|
||||||
this._showDisabled,
|
|
||||||
this._showUnavailable
|
|
||||||
)}
|
|
||||||
.filter=${this._filter}
|
|
||||||
selectable
|
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
|
||||||
@row-click=${this._openEditEntry}
|
|
||||||
id="entity_id"
|
|
||||||
>
|
|
||||||
<div class="table-header" slot="header">
|
|
||||||
${this._selectedEntities.length
|
|
||||||
? html`
|
|
||||||
<p class="selected-txt">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.selected",
|
|
||||||
"number",
|
|
||||||
this._selectedEntities.length
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div class="header-btns">
|
|
||||||
${!this.narrow
|
|
||||||
? html`
|
|
||||||
<mwc-button @click=${this._enableSelected}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.enable_selected.button"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button @click=${this._disableSelected}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.disable_selected.button"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button @click=${this._removeSelected}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.remove_selected.button"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<paper-icon-button
|
|
||||||
id="enable-btn"
|
|
||||||
icon="hass:undo"
|
|
||||||
@click=${this._enableSelected}
|
|
||||||
></paper-icon-button>
|
|
||||||
<paper-tooltip for="enable-btn">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.enable_selected.button"
|
|
||||||
)}
|
|
||||||
</paper-tooltip>
|
|
||||||
<paper-icon-button
|
|
||||||
id="disable-btn"
|
|
||||||
icon="hass:cancel"
|
|
||||||
@click=${this._disableSelected}
|
|
||||||
></paper-icon-button>
|
|
||||||
<paper-tooltip for="disable-btn">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.disable_selected.button"
|
|
||||||
)}
|
|
||||||
</paper-tooltip>
|
|
||||||
<paper-icon-button
|
|
||||||
id="remove-btn"
|
|
||||||
icon="hass:delete"
|
|
||||||
@click=${this._removeSelected}
|
|
||||||
></paper-icon-button>
|
|
||||||
<paper-tooltip for="remove-btn">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.picker.remove_selected.button"
|
|
||||||
)}
|
|
||||||
</paper-tooltip>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<search-input
|
|
||||||
@value-changed=${this._handleSearchChange}
|
|
||||||
.filter=${this._filter}
|
|
||||||
></search-input>
|
|
||||||
<paper-menu-button no-animations horizontal-align="right">
|
|
||||||
<paper-icon-button
|
|
||||||
aria-label=${this.hass!.localize(
|
|
||||||
"ui.panel.config.entities.picker.filter.filter"
|
|
||||||
)}
|
|
||||||
title="${this.hass!.localize(
|
|
||||||
"ui.panel.config.entities.picker.filter.filter"
|
|
||||||
)}"
|
|
||||||
icon="hass:filter-variant"
|
|
||||||
slot="dropdown-trigger"
|
|
||||||
></paper-icon-button>
|
|
||||||
<paper-listbox slot="dropdown-content">
|
|
||||||
<paper-icon-item @tap="${this._showDisabledChanged}">
|
|
||||||
<paper-checkbox
|
|
||||||
.checked=${this._showDisabled}
|
|
||||||
slot="item-icon"
|
|
||||||
></paper-checkbox>
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.config.entities.picker.filter.show_disabled"
|
|
||||||
)}
|
|
||||||
</paper-icon-item>
|
|
||||||
<paper-icon-item @tap="${this._showRestoredChanged}">
|
|
||||||
<paper-checkbox
|
|
||||||
.checked=${this._showUnavailable}
|
|
||||||
slot="item-icon"
|
|
||||||
></paper-checkbox>
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.config.entities.picker.filter.show_unavailable"
|
|
||||||
)}
|
|
||||||
</paper-icon-item>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
</ha-data-table>
|
|
||||||
</div>
|
|
||||||
</hass-tabs-subpage>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,14 +502,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
font-weight: var(--paper-font-subhead_-_font-weight);
|
font-weight: var(--paper-font-subhead_-_font-weight);
|
||||||
line-height: var(--paper-font-subhead_-_line-height);
|
line-height: var(--paper-font-subhead_-_line-height);
|
||||||
}
|
}
|
||||||
.intro {
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
ha-data-table {
|
ha-data-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
--data-table-border-width: 0;
|
||||||
|
}
|
||||||
|
:host(:not([narrow])) ha-data-table {
|
||||||
|
height: calc(100vh - 65px);
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
ha-switch {
|
ha-switch {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
@ -540,12 +521,26 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.search-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-left: -24px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
.selected-txt {
|
.selected-txt {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 38px;
|
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
|
.table-header .selected-txt {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.search-toolbar .selected-txt {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
.header-btns > mwc-button,
|
.header-btns > mwc-button,
|
||||||
.header-btns > paper-icon-button {
|
.header-btns > paper-icon-button {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
|
@ -160,6 +160,10 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
|||||||
this._deviceEntityLookup,
|
this._deviceEntityLookup,
|
||||||
this._deviceRegistryEntries
|
this._deviceRegistryEntries
|
||||||
);
|
);
|
||||||
|
const name = this.scene
|
||||||
|
? computeStateName(this.scene)
|
||||||
|
: this.hass.localize("ui.panel.config.scene.editor.default_name");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -191,6 +195,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
${
|
||||||
|
this.narrow
|
||||||
|
? html`
|
||||||
|
<span slot="header">${name}</span>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
<div
|
<div
|
||||||
id="root"
|
id="root"
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
@ -198,15 +209,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
|||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<div slot="header">
|
${
|
||||||
${
|
!this.narrow
|
||||||
this.scene
|
? html`
|
||||||
? computeStateName(this.scene)
|
<span slot="header">${name}</span>
|
||||||
: this.hass.localize(
|
`
|
||||||
"ui.panel.config.scene.editor.default_name"
|
: ""
|
||||||
)
|
}
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div slot="introduction">
|
<div slot="introduction">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.scene.editor.introduction"
|
"ui.panel.config.scene.editor.introduction"
|
||||||
|
@ -63,7 +63,11 @@ export class HaScriptEditor extends LitElement {
|
|||||||
@click=${this._deleteConfirm}
|
@click=${this._deleteConfirm}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
`}
|
`}
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<span slot="header">${this._config?.alias}</span>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._errors
|
${this._errors
|
||||||
? html`
|
? html`
|
||||||
@ -78,7 +82,11 @@ export class HaScriptEditor extends LitElement {
|
|||||||
${this._config
|
${this._config
|
||||||
? html`
|
? html`
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">${this._config.alias}</span>
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<span slot="header">${this._config.alias}</span>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<span slot="introduction">
|
<span slot="introduction">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.script.editor.introduction"
|
"ui.panel.config.script.editor.introduction"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user