From 2b1f9460a8db21f5b16366cf7ffba1fa6108605a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 17 Sep 2019 18:06:53 +0200 Subject: [PATCH 1/4] Fix styling mwc-button (#3740) --- src/resources/ha-style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 25bcb40c96..9e981a5584 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -137,7 +137,7 @@ documentContainer.innerHTML = ` --mdc-theme-surface: var(--paper-card-background-color, var(--card-background-color)); /* mwc text styles */ - --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-on-primary: var(--text-primary-color); --mdc-theme-on-secondary: var(--text-primary-color); --mdc-theme-on-surface: var(--primary-text-color); } From 27264b27a924bbec4a25a00fee3e9387c4fd7cd7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 17 Sep 2019 18:50:51 +0200 Subject: [PATCH 2/4] Fix (#3741) --- src/mixins/subscribe-mixin.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mixins/subscribe-mixin.ts b/src/mixins/subscribe-mixin.ts index 25cca233b5..63e9f1436b 100644 --- a/src/mixins/subscribe-mixin.ts +++ b/src/mixins/subscribe-mixin.ts @@ -5,6 +5,7 @@ import { PropertyDeclarations, } from "lit-element"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HomeAssistant } from "../types"; export interface HassSubscribeElement { hassSubscribe(): UnsubscribeFunc[]; @@ -16,6 +17,7 @@ export const SubscribeMixin = ( ): Constructor => // @ts-ignore class extends superClass { + private hass?: HomeAssistant; /* tslint:disable-next-line */ private __unsubs?: UnsubscribeFunc[]; @@ -56,7 +58,7 @@ export const SubscribeMixin = ( if ( this.__unsubs !== undefined || !((this as unknown) as Element).isConnected || - super.hass === undefined + this.hass === undefined ) { return; } From e30f9d4a668a160c4fa13b671f091e5727f2b316 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 17 Sep 2019 20:59:43 +0200 Subject: [PATCH 3/4] Add device config page (#3695) * Add device config page * Remove unused imports * Revert a lot Make the PR smaller * Change columns add battery level * memoize * don't bubble * Add mobile view * fix filter mobile * Comments --- src/components/device/ha-device-picker.ts | 2 +- src/components/ha-data-table.ts | 5 +- .../config/dashboard/ha-config-dashboard.js | 11 + .../config/devices/ha-config-device-page.ts | 80 ++++++ .../devices/ha-config-devices-dashboard.ts | 238 ++++++++++++++++++ .../config/devices/ha-config-devices.ts | 127 ++++++++++ .../ha-device-card.js | 33 +-- src/panels/config/ha-panel-config.ts | 5 + .../config-entry/ha-config-entry-page.ts | 3 +- .../ha-config-entries-dashboard.js | 235 ----------------- .../ha-config-entries-dashboard.ts | 237 +++++++++++++++++ src/translations/en.json | 6 +- 12 files changed, 725 insertions(+), 257 deletions(-) create mode 100644 src/panels/config/devices/ha-config-device-page.ts create mode 100644 src/panels/config/devices/ha-config-devices-dashboard.ts create mode 100644 src/panels/config/devices/ha-config-devices.ts rename src/panels/config/{integrations/config-entry => devices}/ha-device-card.js (88%) delete mode 100644 src/panels/config/integrations/ha-config-entries-dashboard.js create mode 100644 src/panels/config/integrations/ha-config-entries-dashboard.ts diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index cd7dfe7688..edea4378c5 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -14,7 +14,7 @@ import { property, } from "lit-element"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { SubscribeMixin } from "../../../src/mixins/subscribe-mixin"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../types"; import { fireEvent } from "../../common/dom/fire_event"; diff --git a/src/components/ha-data-table.ts b/src/components/ha-data-table.ts index 73ccd31c79..73e0ad8d2b 100644 --- a/src/components/ha-data-table.ts +++ b/src/components/ha-data-table.ts @@ -68,7 +68,6 @@ export interface DataTabelColumnData { } export interface DataTabelRowData { - id: string; [key: string]: any; } @@ -402,9 +401,7 @@ export class HaDataTable extends BaseElement { const rowId = (ev.target as HTMLElement) .closest("tr")! .getAttribute("data-row-id")!; - fireEvent(this, "row-click", { - id: rowId, - }); + fireEvent(this, "row-click", { id: rowId }, { bubbles: false }); } private _setRowChecked(rowId: string, checked: boolean) { diff --git a/src/panels/config/dashboard/ha-config-dashboard.js b/src/panels/config/dashboard/ha-config-dashboard.js index edd8f16daf..e47377fa84 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.js +++ b/src/panels/config/dashboard/ha-config-dashboard.js @@ -92,6 +92,17 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) { + + + + [[localize('ui.panel.config.devices.caption')]] +
+ [[localize('ui.panel.config.devices.description')]] +
+
+ +
+
diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts new file mode 100644 index 0000000000..a88a082c1a --- /dev/null +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -0,0 +1,80 @@ +import { property, LitElement, html, customElement } from "lit-element"; + +import memoizeOne from "memoize-one"; + +import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-error-screen"; + +import "./ha-device-card"; +import { HomeAssistant } from "../../../types"; +import { ConfigEntry } from "../../../data/config_entries"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { + DeviceRegistryEntry, + updateDeviceRegistryEntry, +} from "../../../data/device_registry"; +import { AreaRegistryEntry } from "../../../data/area_registry"; +import { + loadDeviceRegistryDetailDialog, + showDeviceRegistryDetailDialog, +} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; + +@customElement("ha-config-device-page") +export class HaConfigDevicePage extends LitElement { + @property() public hass!: HomeAssistant; + @property() public devices!: DeviceRegistryEntry[]; + @property() public entries!: ConfigEntry[]; + @property() public entities!: EntityRegistryEntry[]; + @property() public areas!: AreaRegistryEntry[]; + @property() public deviceId!: string; + + private _device = memoizeOne( + ( + deviceId: string, + devices: DeviceRegistryEntry[] + ): DeviceRegistryEntry | undefined => + devices ? devices.find((device) => device.id === deviceId) : undefined + ); + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + loadDeviceRegistryDetailDialog(); + } + + protected render() { + const device = this._device(this.deviceId, this.devices); + + if (!device) { + return html` + + `; + } + + return html` + + + + + `; + } + + private _showSettings() { + showDeviceRegistryDetailDialog(this, { + device: this._device(this.deviceId, this.devices)!, + updateEntry: async (updates) => { + await updateDeviceRegistryEntry(this.hass, this.deviceId, updates); + }, + }); + } +} diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts new file mode 100644 index 0000000000..ce6bc77c91 --- /dev/null +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -0,0 +1,238 @@ +import "@polymer/paper-tooltip/paper-tooltip"; +import "@material/mwc-button"; +import "@polymer/iron-icon/iron-icon"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-item-body"; + +import "../../../components/ha-card"; +import "../../../components/ha-data-table"; +import "../../../components/entity/ha-state-icon"; +import "../../../layouts/hass-subpage"; +import "../../../resources/ha-style"; +import "../../../components/ha-icon-next"; + +import "../ha-config-section"; + +import memoizeOne from "memoize-one"; + +import { + LitElement, + html, + TemplateResult, + property, + customElement, +} from "lit-element"; +import { HomeAssistant } from "../../../types"; +// tslint:disable-next-line +import { + DataTabelColumnContainer, + RowClickedEvent, + DataTabelRowData, +} from "../../../components/ha-data-table"; +// tslint:disable-next-line +import { DeviceRegistryEntry } from "../../../data/device_registry"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { ConfigEntry } from "../../../data/config_entries"; +import { AreaRegistryEntry } from "../../../data/area_registry"; +import { navigate } from "../../../common/navigate"; + +interface DeviceRowData extends DeviceRegistryEntry { + device?: DeviceRowData; + area?: string; + integration?: string; + battery_entity?: string; +} + +@customElement("ha-config-devices-dashboard") +export class HaConfigDeviceDashboard extends LitElement { + @property() public hass!: HomeAssistant; + @property() public narrow = false; + @property() public devices!: DeviceRegistryEntry[]; + @property() public entries!: ConfigEntry[]; + @property() public entities!: EntityRegistryEntry[]; + @property() public areas!: AreaRegistryEntry[]; + @property() public domain!: string; + + private _devices = memoizeOne( + ( + devices: DeviceRegistryEntry[], + entries: ConfigEntry[], + entities: EntityRegistryEntry[], + areas: AreaRegistryEntry[], + domain: string + ) => { + let outputDevices: DeviceRowData[] = devices; + if (domain) { + outputDevices = outputDevices.filter( + (device) => + entries.find((entry) => + device.config_entries.includes(entry.entry_id) + )!.domain === domain + ); + } + + outputDevices = outputDevices.map((device) => { + const output = { ...device }; + output.name = device.name_by_user || device.name || "No name"; + + output.area = + !areas || !device || !device.area_id + ? "No area" + : areas.find((area) => area.area_id === device.area_id)!.name; + + output.integration = + !entries || !device || !device.config_entries + ? "No integration" + : entries.find((entry) => + device.config_entries.includes(entry.entry_id) + )!.domain; + + output.battery_entity = this._batteryEntity(device, entities); + + return output; + }); + + return outputDevices; + } + ); + + private _columns = memoizeOne( + (narrow: boolean): DataTabelColumnContainer => + narrow + ? { + device: { + title: "Device", + sortable: true, + filterKey: "name", + filterable: true, + direction: "asc", + template: (device: DeviceRowData) => { + const battery = device.battery_entity + ? this.hass.states[device.battery_entity] + : undefined; + // Have to work on a nice layout for mobile + return html` + ${device.name_by_user || device.name}
+ ${device.area} | ${device.integration}
+ ${battery + ? html` + ${battery.state}% + + ` + : ""} + `; + }, + }, + } + : { + device_name: { + title: "Device", + sortable: true, + filterable: true, + direction: "asc", + }, + manufacturer: { + title: "Manufacturer", + sortable: true, + filterable: true, + }, + model: { + title: "Model", + sortable: true, + filterable: true, + }, + area: { + title: "Area", + sortable: true, + filterable: true, + }, + integration: { + title: "Integration", + sortable: true, + filterable: true, + }, + battery: { + title: "Battery", + sortable: true, + type: "numeric", + template: (batteryEntity: string) => { + const battery = batteryEntity + ? this.hass.states[batteryEntity] + : undefined; + return battery + ? html` + ${battery.state}% + + ` + : html` + n/a + `; + }, + }, + } + ); + + protected render(): TemplateResult { + return html` + + { + // We don't need a lot of this data for mobile view, but kept it for filtering... + const data: DataTabelRowData = { + device_name: device.name, + id: device.id, + manufacturer: device.manufacturer, + model: device.model, + area: device.area, + integration: device.integration, + }; + if (this.narrow) { + data.device = device; + return data; + } + data.battery = device.battery_entity; + return data; + })} + @row-click=${this._handleRowClicked} + > + + `; + } + + private _batteryEntity(device, entities): string | undefined { + const batteryEntity = entities.find( + (entity) => + entity.device_id === device.id && + 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}`); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-devices-dashboard": HaConfigDeviceDashboard; + } +} diff --git a/src/panels/config/devices/ha-config-devices.ts b/src/panels/config/devices/ha-config-devices.ts new file mode 100644 index 0000000000..363e83a3ec --- /dev/null +++ b/src/panels/config/devices/ha-config-devices.ts @@ -0,0 +1,127 @@ +import "@polymer/app-route/app-route"; + +import "./ha-config-devices-dashboard"; +import "./ha-config-device-page"; +import { compare } from "../../../common/string/compare"; +import { + subscribeAreaRegistry, + AreaRegistryEntry, +} from "../../../data/area_registry"; +import { + HassRouterPage, + RouterOptions, +} from "../../../layouts/hass-router-page"; +import { property, customElement, PropertyValues } from "lit-element"; +import { HomeAssistant } from "../../../types"; +import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../../../data/device_registry"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; + +@customElement("ha-config-devices") +class HaConfigDevices extends HassRouterPage { + @property() public hass!: HomeAssistant; + @property() public narrow!: boolean; + + protected routerOptions: RouterOptions = { + defaultPage: "dashboard", + routes: { + dashboard: { + tag: "ha-config-devices-dashboard", + }, + device: { + tag: "ha-config-device-page", + }, + }, + }; + + @property() private _configEntries?: ConfigEntry[]; + @property() private _entityRegistryEntries?: EntityRegistryEntry[]; + @property() private _deviceRegistryEntries?: DeviceRegistryEntry[]; + @property() private _areas?: AreaRegistryEntry[]; + + private _unsubs?: UnsubscribeFunc[]; + + public connectedCallback() { + super.connectedCallback(); + + if (!this.hass) { + return; + } + this._loadData(); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubs) { + while (this._unsubs.length) { + this._unsubs.pop()!(); + } + this._unsubs = undefined; + } + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this.addEventListener("hass-reload-entries", () => { + this._loadData(); + }); + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (!this._unsubs && changedProps.has("hass")) { + this._loadData(); + } + } + + protected updatePageEl(pageEl) { + pageEl.hass = this.hass; + + if (this._currentPage === "dashboard") { + pageEl.domain = this.routeTail.path.substr(1); + } else if (this._currentPage === "device") { + pageEl.deviceId = this.routeTail.path.substr(1); + } + + pageEl.entities = this._entityRegistryEntries; + pageEl.entries = this._configEntries; + pageEl.devices = this._deviceRegistryEntries; + pageEl.areas = this._areas; + pageEl.narrow = this.narrow; + } + + private _loadData() { + getConfigEntries(this.hass).then((configEntries) => { + this._configEntries = configEntries.sort((conf1, conf2) => + compare(conf1.title, conf2.title) + ); + }); + if (this._unsubs) { + return; + } + this._unsubs = [ + subscribeAreaRegistry(this.hass.connection, (areas) => { + this._areas = areas; + }), + subscribeEntityRegistry(this.hass.connection, (entries) => { + this._entityRegistryEntries = entries; + }), + subscribeDeviceRegistry(this.hass.connection, (entries) => { + this._deviceRegistryEntries = entries; + }), + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-devices": HaConfigDevices; + } +} diff --git a/src/panels/config/integrations/config-entry/ha-device-card.js b/src/panels/config/devices/ha-device-card.js similarity index 88% rename from src/panels/config/integrations/config-entry/ha-device-card.js rename to src/panels/config/devices/ha-device-card.js index 40d9b49cee..fe6a391dd7 100644 --- a/src/panels/config/integrations/config-entry/ha-device-card.js +++ b/src/panels/config/devices/ha-device-card.js @@ -6,23 +6,23 @@ import "@polymer/paper-listbox/paper-listbox"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../../components/ha-card"; -import "../../../../layouts/hass-subpage"; +import "../../../components/ha-card"; +import "../../../layouts/hass-subpage"; -import { EventsMixin } from "../../../../mixins/events-mixin"; -import LocalizeMixin from "../../../../mixins/localize-mixin"; -import computeStateName from "../../../../common/entity/compute_state_name"; -import "../../../../components/entity/state-badge"; -import { compare } from "../../../../common/string/compare"; +import { EventsMixin } from "../../../mixins/events-mixin"; +import LocalizeMixin from "../../../mixins/localize-mixin"; +import computeStateName from "../../../common/entity/compute_state_name"; +import "../../../components/entity/state-badge"; +import { compare } from "../../../common/string/compare"; import { subscribeDeviceRegistry, updateDeviceRegistryEntry, -} from "../../../../data/device_registry"; -import { subscribeAreaRegistry } from "../../../../data/area_registry"; +} from "../../../data/device_registry"; +import { subscribeAreaRegistry } from "../../../data/area_registry"; import { loadDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog, -} from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; +} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; function computeEntityName(hass, entity) { if (entity.name) return entity.name; @@ -83,11 +83,13 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
-
[[_deviceName(device)]]
- +
@@ -154,6 +156,7 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) { type: Boolean, reflectToAttribute: true, }, + hideSettings: { type: Boolean, value: false }, _childDevices: { type: Array, computed: "_computeChildDevices(device, devices)", diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 1c249c39ea..6998abdd73 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -48,6 +48,11 @@ class HaPanelConfig extends HassRouterPage { load: () => import(/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"), }, + devices: { + tag: "ha-config-devices", + load: () => + import(/* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices"), + }, server_control: { tag: "ha-config-server-control", load: () => diff --git a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts index 979a10887d..2235fcc5aa 100644 --- a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts +++ b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts @@ -5,7 +5,7 @@ import "../../../../layouts/hass-error-screen"; import "../../../../components/entity/state-badge"; import { compare } from "../../../../common/string/compare"; -import "./ha-device-card"; +import "../../devices/ha-device-card"; import "./ha-ce-entities-card"; import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow"; import { property, LitElement, CSSResult, css, html } from "lit-element"; @@ -106,6 +106,7 @@ class HaConfigEntryPage extends LitElement { icon="hass:delete" @click=${this._removeEntry} > +
${configEntryDevices.length === 0 && noDeviceEntities.length === 0 ? html` diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.js b/src/panels/config/integrations/ha-config-entries-dashboard.js deleted file mode 100644 index 48e4ff6022..0000000000 --- a/src/panels/config/integrations/ha-config-entries-dashboard.js +++ /dev/null @@ -1,235 +0,0 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-tooltip/paper-tooltip"; -import "@material/mwc-button"; -import "@polymer/iron-icon/iron-icon"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../../../components/ha-card"; -import "../../../components/ha-fab"; -import "../../../components/entity/ha-state-icon"; -import "../../../layouts/hass-subpage"; -import "../../../resources/ha-style"; -import "../../../components/ha-icon-next"; - -import { computeRTL } from "../../../common/util/compute_rtl"; -import "../ha-config-section"; -import { EventsMixin } from "../../../mixins/events-mixin"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import computeStateName from "../../../common/entity/compute_state_name"; -import { - loadConfigFlowDialog, - showConfigFlowDialog, -} from "../../../dialogs/config-flow/show-dialog-config-flow"; -import { localizeConfigFlowTitle } from "../../../data/config_flow"; - -/* - * @appliesMixin LocalizeMixin - * @appliesMixin EventsMixin - */ -class HaConfigManagerDashboard extends LocalizeMixin( - EventsMixin(PolymerElement) -) { - static get template() { - return html` - - - - - - - [[localize('ui.panel.config.integrations.configured')]] - - - - - - - - - `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - - /** - * Existing entries. - */ - entries: Array, - - /** - * Entity Registry entries. - */ - entities: Array, - - /** - * Current flows that are in progress and have not been started by a user. - * For example, can be discovered devices that require more config. - */ - progress: Array, - - rtl: { - type: Boolean, - reflectToAttribute: true, - computed: "_computeRTL(hass)", - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - loadConfigFlowDialog(); - } - - _createFlow() { - showConfigFlowDialog(this, { - dialogClosedCallback: () => this.fire("hass-reload-entries"), - }); - } - - _continueFlow(ev) { - showConfigFlowDialog(this, { - continueFlowId: ev.model.item.flow_id, - dialogClosedCallback: () => this.fire("hass-reload-entries"), - }); - } - - _computeIntegrationTitle(localize, integration) { - return localize(`component.${integration}.config.title`); - } - - _computeActiveFlowTitle(localize, flow) { - return localizeConfigFlowTitle(localize, flow); - } - - _computeConfigEntryEntities(hass, configEntry, entities) { - if (!entities) { - return []; - } - const states = []; - entities.forEach((entity) => { - if ( - entity.config_entry_id === configEntry.entry_id && - entity.entity_id in hass.states - ) { - states.push(hass.states[entity.entity_id]); - } - }); - return states; - } - - _computeStateName(stateObj) { - return computeStateName(stateObj); - } - - _computeRTL(hass) { - return computeRTL(hass); - } -} - -customElements.define("ha-config-entries-dashboard", HaConfigManagerDashboard); diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.ts b/src/panels/config/integrations/ha-config-entries-dashboard.ts new file mode 100644 index 0000000000..7b3cd73673 --- /dev/null +++ b/src/panels/config/integrations/ha-config-entries-dashboard.ts @@ -0,0 +1,237 @@ +import "@polymer/iron-flex-layout/iron-flex-layout-classes"; +import "@polymer/paper-tooltip/paper-tooltip"; +import "@material/mwc-button"; +import "@polymer/iron-icon/iron-icon"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-item-body"; + +import { HassEntity } from "home-assistant-js-websocket"; + +import "../../../components/ha-card"; +import "../../../components/ha-icon-next"; +import "../../../components/ha-fab"; +import "../../../components/entity/ha-state-icon"; +import "../../../layouts/hass-subpage"; +import "../../../resources/ha-style"; +import "../../../components/ha-icon"; + +import { computeRTL } from "../../../common/util/compute_rtl"; +import "../ha-config-section"; + +import computeStateName from "../../../common/entity/compute_state_name"; +import { + loadConfigFlowDialog, + showConfigFlowDialog, +} from "../../../dialogs/config-flow/show-dialog-config-flow"; +import { localizeConfigFlowTitle } from "../../../data/config_flow"; +import { + LitElement, + TemplateResult, + html, + property, + customElement, + css, + CSSResult, +} from "lit-element"; +import { HomeAssistant } from "../../../types"; +import { ConfigEntry } from "../../../data/config_entries"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; + +@customElement("ha-config-entries-dashboard") +export class HaConfigManagerDashboard extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public isWide = false; + + @property() private entries = []; + + /** + * Entity Registry entries. + */ + @property() private entities: EntityRegistryEntry[] = []; + + /** + * Current flows that are in progress and have not been started by a user. + * For example, can be discovered devices that require more config. + */ + @property() private progress = []; + + public connectedCallback() { + super.connectedCallback(); + loadConfigFlowDialog(); + } + + protected render(): TemplateResult { + return html` + + ${this.progress.length + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.discovered" + )} + + ${this.progress.map( + (flow) => html` +
+ + ${localizeConfigFlowTitle(this.hass.localize, flow)} + + ${this.hass.localize( + "ui.panel.config.integrations.configure" + )} +
+ ` + )} +
+
+ ` + : ""} + + + ${this.hass.localize( + "ui.panel.config.integrations.configured" + )} + + ${this.entities.length + ? this.entries.map( + (item: any, idx) => html` +
+ + +
+ ${this.hass.localize( + `component.${item.domain}.config.title` + )}: + ${item.title} +
+
+ ${this._getEntities(item).map( + (entity) => html` + + + ${computeStateName(entity)} + + ` + )} +
+
+ +
+
+ ` + ) + : html` +
+ +
+ ${this.hass.localize( + "ui.panel.config.integrations.none" + )} +
+
+
+ `} + + + + + + `; + } + + private _createFlow() { + showConfigFlowDialog(this, { + dialogClosedCallback: () => fireEvent(this, "hass-reload-entries"), + }); + } + + private _continueFlow(ev) { + showConfigFlowDialog(this, { + continueFlowId: ev.model.item.flow_id, + dialogClosedCallback: () => fireEvent(this, "hass-reload-entries"), + }); + } + + private _getEntities(configEntry: ConfigEntry): HassEntity[] { + if (!this.entities) { + return []; + } + const states: HassEntity[] = []; + this.entities.forEach((entity) => { + if ( + entity.config_entry_id === configEntry.entry_id && + entity.entity_id in this.hass.states + ) { + states.push(this.hass.states[entity.entity_id]); + } + }); + return states; + } + static get styles(): CSSResult { + return css` + ha-card { + overflow: hidden; + } + mwc-button { + top: 3px; + margin-right: -0.57em; + } + .config-entry-row { + display: flex; + padding: 0 16px; + } + ha-icon { + cursor: pointer; + margin: 8px; + } + .configured a { + color: var(--primary-text-color); + text-decoration: none; + } + ha-fab { + position: fixed; + bottom: 16px; + right: 16px; + z-index: 1; + } + + ha-fab[is-wide] { + bottom: 24px; + right: 24px; + } + + ha-fab[rtl] { + right: auto; + left: 16px; + } + + ha-fab[rtl][is-wide] { + bottom: 24px; + right: auto; + left: 24px; + } + `; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c4db454d5b..9ef2dd6626 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -889,6 +889,10 @@ "description_not_login": "Not logged in", "description_features": "Control away from home, integrate with Alexa and Google Assistant." }, + "devices": { + "caption": "Devices", + "description": "Manage connected devices" + }, "entity_registry": { "caption": "Entity Registry", "description": "Overview of all known entities.", @@ -920,7 +924,7 @@ }, "integrations": { "caption": "Integrations", - "description": "Manage connected devices and services", + "description": "Manage and setup integrations", "discovered": "Discovered", "configured": "Configured", "new": "Set up a new integration", From 54beaad7e51b0fd433e27ab0e3cd2fb5caf18449 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 17 Sep 2019 21:00:13 +0200 Subject: [PATCH 4/4] Bumped version to 20190917.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 212d7e958d..2265178d7c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20190917.1", + version="20190917.2", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors",