mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +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,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../dom/fire_event";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
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")
|
||||
class SearchInput extends LitElement {
|
||||
@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() {
|
||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
||||
@ -22,18 +26,24 @@ class SearchInput extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.no-underline {
|
||||
--paper-input-container-underline: {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="search-container">
|
||||
<paper-input
|
||||
class=${classMap({ "no-underline": this.noUnderline })}
|
||||
autofocus
|
||||
label="Search"
|
||||
.value=${this.filter}
|
||||
@value-changed=${this._filterInputChanged}
|
||||
.noLabelFloat=${this.noLabelFloat}
|
||||
>
|
||||
<iron-icon
|
||||
icon="hass:magnify"
|
||||
slot="prefix"
|
||||
class="prefix"
|
||||
></iron-icon>
|
||||
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon>
|
||||
${this.filter &&
|
||||
html`
|
||||
<paper-icon-button
|
||||
|
@ -101,6 +101,8 @@ export class HaDataTable extends BaseElement {
|
||||
@property({ type: String }) private _sortColumn?: string;
|
||||
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
||||
@property({ type: Array }) private _filteredData: DataTableRowData[] = [];
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
@query(".scroller") private _scroller!: HTMLDivElement;
|
||||
private _sortColumns: {
|
||||
[key: string]: DataTableSortColumnData;
|
||||
} = {};
|
||||
@ -170,7 +172,7 @@ export class HaDataTable extends BaseElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="mdc-data-table">
|
||||
<slot name="header">
|
||||
<slot name="header" @slotchange=${this._calcScrollHeight}>
|
||||
${this._filterable
|
||||
? html`
|
||||
<div class="table-header">
|
||||
@ -181,112 +183,114 @@ export class HaDataTable extends BaseElement {
|
||||
`
|
||||
: ""}
|
||||
</slot>
|
||||
<table class="mdc-data-table__table">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
${this.selectable
|
||||
? html`
|
||||
<div class="scroller">
|
||||
<table class="mdc-data-table__table">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
${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
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
@click=${this._handleHeaderClick}
|
||||
data-column-id="${key}"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleHeaderRowCheckboxChange}
|
||||
.indeterminate=${this._headerIndeterminate}
|
||||
.checked=${this._headerChecked}
|
||||
>
|
||||
</ha-checkbox>
|
||||
${column.sortable
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${sorted && this._sortDirection === "desc"
|
||||
? "hass:arrow-down"
|
||||
: "hass:arrow-up"}
|
||||
></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
<span>${column.title}</span>
|
||||
</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
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
@click=${this._handleHeaderClick}
|
||||
data-column-id="${key}"
|
||||
`;
|
||||
})}
|
||||
</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"
|
||||
>
|
||||
${column.sortable
|
||||
${this.selectable
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${sorted && this._sortDirection === "desc"
|
||||
? "hass:arrow-down"
|
||||
: "hass:arrow-up"}
|
||||
></ha-icon>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleRowCheckboxChange}
|
||||
.checked=${this._checkedRows.includes(
|
||||
String(row[this.id])
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</td>
|
||||
`
|
||||
: ""}
|
||||
<span>${column.title}</span>
|
||||
</th>
|
||||
`;
|
||||
})}
|
||||
</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`
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
return html`
|
||||
<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
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleRowCheckboxChange}
|
||||
.checked=${this._checkedRows.includes(
|
||||
String(row[this.id])
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
return html`
|
||||
<td
|
||||
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"
|
||||
),
|
||||
})}"
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -434,6 +438,11 @@ export class HaDataTable extends BaseElement {
|
||||
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 {
|
||||
return css`
|
||||
/* default mdc styles, colors changed, without checkbox styles */
|
||||
@ -584,8 +593,14 @@ export class HaDataTable extends BaseElement {
|
||||
|
||||
/* custom from here */
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mdc-data-table {
|
||||
display: block;
|
||||
border-width: var(--data-table-border-width, 1px);
|
||||
height: 100%;
|
||||
}
|
||||
.mdc-data-table__header-cell {
|
||||
overflow: hidden;
|
||||
@ -614,6 +629,16 @@ export class HaDataTable extends BaseElement {
|
||||
.table-header {
|
||||
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 {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
DeviceEntityLookup,
|
||||
} from "../../data/device_registry";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@ -30,7 +31,6 @@ import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
} from "../../data/device_registry";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@ -29,7 +30,6 @@ import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
|
@ -17,6 +17,10 @@ export interface DeviceRegistryEntry {
|
||||
name_by_user?: string;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: 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}
|
||||
@click=${this._backTapped}
|
||||
></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 })}>
|
||||
${this.tabs.map((page, index) =>
|
||||
(!page.component ||
|
||||
@ -138,11 +143,6 @@ class HassTabsSubpage extends LitElement {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host([narrow]) .toolbar {
|
||||
background-color: var(--primary-background-color);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
|
@ -77,154 +77,155 @@ export class HaAutomationEditor extends LitElement {
|
||||
<div class="errors">${this._errors}</div>
|
||||
`
|
||||
: ""}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}"
|
||||
>
|
||||
${this._config
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
${this.creatingNew
|
||||
? ""
|
||||
: html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.automation}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button @click=${this._excuteAutomation}>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this._config
|
||||
? html`
|
||||
${this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config?.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
${this.creatingNew
|
||||
? ""
|
||||
: html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.automation}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
<mwc-button @click=${this._excuteAutomation}>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/trigger/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-trigger
|
||||
.triggers=${this._config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/trigger/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<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}>
|
||||
<span slot="header">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-condition
|
||||
.conditions=${this._config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<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}>
|
||||
<span slot="header">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/action/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/action/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: ""}
|
||||
<ha-fab
|
||||
?is-wide="${this.isWide}"
|
||||
?narrow="${this.narrow}"
|
||||
|
@ -121,6 +121,14 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.tabs=${configSections.integrations}
|
||||
.route=${this.route}
|
||||
>
|
||||
${
|
||||
this.narrow
|
||||
? html`
|
||||
<span slot="header">${device.name_by_user || device.name}</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<paper-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:settings"
|
||||
@ -130,7 +138,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<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
|
||||
.hass=${this.hass}
|
||||
.areas=${this.areas}
|
||||
@ -498,6 +512,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) .container > *:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import "./ha-devices-data-table";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
@ -11,11 +10,24 @@ import {
|
||||
css,
|
||||
} from "lit-element";
|
||||
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 { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
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")
|
||||
export class HaConfigDeviceDashboard extends LitElement {
|
||||
@ -29,30 +41,219 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
@property() public domain!: string;
|
||||
@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 {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.tabs=${configSections.integrations}
|
||||
.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">
|
||||
<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>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
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 {
|
||||
return css`
|
||||
.content {
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
} from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
@ -35,10 +36,6 @@ export interface DeviceRowData extends DeviceRegistryEntry {
|
||||
battery_entity?: string;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
@customElement("ha-devices-data-table")
|
||||
export class HaDevicesDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
@ -19,8 +19,6 @@ import memoize from "memoize-one";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
DataTableColumnData,
|
||||
@ -29,6 +27,7 @@ import {
|
||||
SelectionChangedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../common/search/search-input";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
EntityRegistryEntry,
|
||||
@ -38,7 +37,7 @@ import {
|
||||
} from "../../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||
@ -47,6 +46,7 @@ import {
|
||||
showEntityRegistryDetailDialog,
|
||||
} from "./show-dialog-entity-registry-detail";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("ha-config-entities")
|
||||
export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@ -223,154 +223,136 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
<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`
|
||||
<hass-tabs-subpage
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.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="intro">
|
||||
<h2>
|
||||
${this.hass.localize("ui.panel.config.entities.picker.header")}
|
||||
</h2>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.introduction"
|
||||
)}
|
||||
</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>
|
||||
<div class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})} slot="header">
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</ha-data-table>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -520,14 +502,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
font-weight: var(--paper-font-subhead_-_font-weight);
|
||||
line-height: var(--paper-font-subhead_-_line-height);
|
||||
}
|
||||
.intro {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
ha-data-table {
|
||||
width: 100%;
|
||||
--data-table-border-width: 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-data-table {
|
||||
height: calc(100vh - 65px);
|
||||
display: block;
|
||||
}
|
||||
ha-switch {
|
||||
margin-top: 16px;
|
||||
@ -540,12 +521,26 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
search-input {
|
||||
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 {
|
||||
font-weight: bold;
|
||||
margin-top: 38px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.table-header .selected-txt {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.search-toolbar .selected-txt {
|
||||
font-size: 16px;
|
||||
}
|
||||
.header-btns > mwc-button,
|
||||
.header-btns > paper-icon-button {
|
||||
margin: 8px;
|
||||
|
@ -160,6 +160,10 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
this._deviceEntityLookup,
|
||||
this._deviceRegistryEntries
|
||||
);
|
||||
const name = this.scene
|
||||
? computeStateName(this.scene)
|
||||
: this.hass.localize("ui.panel.config.scene.editor.default_name");
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@ -191,6 +195,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.narrow
|
||||
? html`
|
||||
<span slot="header">${name}</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div
|
||||
id="root"
|
||||
class="${classMap({
|
||||
@ -198,15 +209,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
})}"
|
||||
>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${
|
||||
this.scene
|
||||
? computeStateName(this.scene)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.scene.editor.default_name"
|
||||
)
|
||||
}
|
||||
</div>
|
||||
${
|
||||
!this.narrow
|
||||
? html`
|
||||
<span slot="header">${name}</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.introduction"
|
||||
|
@ -63,7 +63,11 @@ export class HaScriptEditor extends LitElement {
|
||||
@click=${this._deleteConfirm}
|
||||
></paper-icon-button>
|
||||
`}
|
||||
|
||||
${this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config?.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<div class="content">
|
||||
${this._errors
|
||||
? html`
|
||||
@ -78,7 +82,11 @@ export class HaScriptEditor extends LitElement {
|
||||
${this._config
|
||||
? html`
|
||||
<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">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.introduction"
|
||||
|
Loading…
x
Reference in New Issue
Block a user