${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;
diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts
index 221f5ec6ad..9685340d59 100644
--- a/src/panels/config/automation/ha-automation-editor.ts
+++ b/src/panels/config/automation/ha-automation-editor.ts
@@ -77,154 +77,155 @@ export class HaAutomationEditor extends LitElement {
- ${this._config
- ? html`
-
- ${this._config.alias}
-
- ${this.hass.localize(
- "ui.panel.config.automation.editor.introduction"
- )}
-
-
-
- ${this.creatingNew
- ? ""
- : html`
-
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.learn_more"
+ )}
+
+
+
+
+ `
+ : ""}
+ ${
+ this.narrow
+ ? html`
+ ${device.name_by_user || device.name}
+ `
+ : ""
+ }
+
-
${device.name_by_user || device.name}
+ ${
+ this.narrow
+ ? ""
+ : html`
+
${device.name_by_user || device.name}
+ `
+ }
*:first-child {
+ padding-top: 0;
+ }
+
:host([narrow]) .container {
margin-top: 0;
}
diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts
index e60b990e92..27b40308a5 100644
--- a/src/panels/config/devices/ha-config-devices-dashboard.ts
+++ b/src/panels/config/devices/ha-config-devices-dashboard.ts
@@ -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 || "",
+ manufacturer: device.manufacturer || "",
+ 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}
+ ${device.area} | ${device.integration}
+ ${battery && !isNaN(battery.state as any)
+ ? html`
+ ${battery.state}%
+
+ `
+ : ""}
+ `;
+ },
+ },
+ }
+ : {
+ 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}%
+
+ `
+ : html`
+ -
+ `;
+ },
+ },
+ }
+ );
+
protected render(): TemplateResult {
return html`
-
-
-
-
-
+
`;
}
+ 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 {
diff --git a/src/panels/config/devices/ha-devices-data-table.ts b/src/panels/config/devices/ha-devices-data-table.ts
index fbed2b5365..8a05bd1f95 100644
--- a/src/panels/config/devices/ha-devices-data-table.ts
+++ b/src/panels/config/devices/ha-devices-data-table.ts
@@ -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;
diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts
index f16cfd5fb2..77282d6f94 100644
--- a/src/panels/config/entities/ha-config-entities.ts
+++ b/src/panels/config/entities/ha-config-entities.ts
@@ -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) {
`;
}
+ const headerToolbar = this._selectedEntities.length
+ ? html`
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.selected",
+ "number",
+ this._selectedEntities.length
+ )}
+
+
+ `
+ : html`
+
+
+
+
+
+
+ ${this.hass!.localize(
+ "ui.panel.config.entities.picker.filter.show_disabled"
+ )}
+
+
+
+ ${this.hass!.localize(
+ "ui.panel.config.entities.picker.filter.show_unavailable"
+ )}
+
+
+
+ `;
+
return html`
-
-
-
+
+ ${headerToolbar}
+
+
+
`;
}
@@ -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;
diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts
index 283195e62a..a7a7254b08 100644
--- a/src/panels/config/scene/ha-scene-editor.ts
+++ b/src/panels/config/scene/ha-scene-editor.ts
@@ -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`
${name}
+ `
+ : ""
+ }
-
- ${
- this.scene
- ? computeStateName(this.scene)
- : this.hass.localize(
- "ui.panel.config.scene.editor.default_name"
- )
- }
-
+ ${
+ !this.narrow
+ ? html`
+ ${name}
+ `
+ : ""
+ }
${this.hass.localize(
"ui.panel.config.scene.editor.introduction"
diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts
index a27b1a73c3..f98c2997a4 100644
--- a/src/panels/config/script/ha-script-editor.ts
+++ b/src/panels/config/script/ha-script-editor.ts
@@ -63,7 +63,11 @@ export class HaScriptEditor extends LitElement {
@click=${this._deleteConfirm}
>
`}
-
+ ${this.narrow
+ ? html`
+
${this._config?.alias}
+ `
+ : ""}
${this._errors
? html`
@@ -78,7 +82,11 @@ export class HaScriptEditor extends LitElement {
${this._config
? html`
- ${this._config.alias}
+ ${!this.narrow
+ ? html`
+ ${this._config.alias}
+ `
+ : ""}
${this.hass.localize(
"ui.panel.config.script.editor.introduction"