Merge pull request #3742 from home-assistant/dev

20190917.2
This commit is contained in:
Bram Kragten 2019-09-17 21:13:48 +02:00 committed by GitHub
commit f5b3a82922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 730 additions and 260 deletions

View File

@ -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",

View File

@ -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";

View File

@ -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) {

View File

@ -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 = <T extends LitElement>(
): Constructor<T & HassSubscribeElement> =>
// @ts-ignore
class extends superClass {
private hass?: HomeAssistant;
/* tslint:disable-next-line */
private __unsubs?: UnsubscribeFunc[];
@ -56,7 +58,7 @@ export const SubscribeMixin = <T extends LitElement>(
if (
this.__unsubs !== undefined ||
!((this as unknown) as Element).isConnected ||
super.hass === undefined
this.hass === undefined
) {
return;
}

View File

@ -92,6 +92,17 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
<a href='/config/devices/dashboard' tabindex="-1">
<paper-item>
<paper-item-body two-line>
[[localize('ui.panel.config.devices.caption')]]
<div secondary>
[[localize('ui.panel.config.devices.description')]]
</div>
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
<a href='/config/users' tabindex="-1">
<paper-item>

View File

@ -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`
<hass-error-screen error="Device not found."></hass-error-screen>
`;
}
return html`
<hass-subpage .header=${device.name_by_user || device.name}>
<paper-icon-button
slot="toolbar-icon"
icon="hass:settings"
@click=${this._showSettings}
></paper-icon-button>
<ha-device-card
.hass=${this.hass}
.areas=${this.areas}
.devices=${this.devices}
.device=${device}
.entities=${this.entities}
hide-settings
></ha-device-card>
</hass-subpage>
`;
}
private _showSettings() {
showDeviceRegistryDetailDialog(this, {
device: this._device(this.deviceId, this.devices)!,
updateEntry: async (updates) => {
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
},
});
}
}

View File

@ -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}<br />
${device.area} | ${device.integration}<br />
${battery
? html`
${battery.state}%
<ha-state-icon
.hass=${this.hass!}
.stateObj=${battery}
></ha-state-icon>
`
: ""}
`;
},
},
}
: {
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}%
<ha-state-icon
.hass=${this.hass!}
.stateObj=${battery}
></ha-state-icon>
`
: html`
n/a
`;
},
},
}
);
protected render(): TemplateResult {
return html`
<hass-subpage
header=${this.hass.localize("ui.panel.config.devices.caption")}
>
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._devices(
this.devices,
this.entries,
this.entities,
this.areas,
this.domain
).map((device: DeviceRowData) => {
// 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}
></ha-data-table>
</hass-subpage>
`;
}
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;
}
}

View File

@ -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;
}
}

View File

@ -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)) {
</style>
<ha-card>
<div class="card-header">
<div class="name">[[_deviceName(device)]]</div>
<paper-icon-button
icon="hass:settings"
on-click="_gotoSettings"
></paper-icon-button>
<template is="dom-if" if="[[!hideSettings]]">
<div class="name">[[_deviceName(device)]]</div>
<paper-icon-button
icon="hass:settings"
on-click="_gotoSettings"
></paper-icon-button>
</template>
</div>
<div class="card-content">
<div class="info">
@ -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)",

View File

@ -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: () =>

View File

@ -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}
></paper-icon-button>
<div class="content">
${configEntryDevices.length === 0 && noDeviceEntities.length === 0
? html`

View File

@ -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`
<style include="iron-flex ha-style">
ha-card {
overflow: hidden;
}
mwc-button {
top: 3px;
margin-right: -0.57em;
}
.config-entry-row {
display: flex;
padding: 0 16px;
}
ha-state-icon {
cursor: pointer;
}
.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;
}
</style>
<hass-subpage
header="[[localize('ui.panel.config.integrations.caption')]]"
>
<template is="dom-if" if="[[progress.length]]">
<ha-config-section>
<span slot="header"
>[[localize('ui.panel.config.integrations.discovered')]]</span
>
<ha-card>
<template is="dom-repeat" items="[[progress]]">
<div class="config-entry-row">
<paper-item-body>
[[_computeActiveFlowTitle(localize, item)]]
</paper-item-body>
<mwc-button on-click="_continueFlow"
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
>
</div>
</template>
</ha-card>
</ha-config-section>
</template>
<ha-config-section class="configured">
<span slot="header"
>[[localize('ui.panel.config.integrations.configured')]]</span
>
<ha-card>
<template is="dom-if" if="[[!entries.length]]">
<div class="config-entry-row">
<paper-item-body two-line>
<div>[[localize('ui.panel.config.integrations.none')]]</div>
</paper-item-body>
</div>
</template>
<template is="dom-repeat" items="[[entries]]">
<a href="/config/integrations/config_entry/[[item.entry_id]]">
<paper-item>
<paper-item-body two-line>
<div>
[[_computeIntegrationTitle(localize, item.domain)]]:
[[item.title]]
</div>
<div secondary>
<template
is="dom-repeat"
items="[[_computeConfigEntryEntities(hass, item, entities)]]"
>
<span>
<ha-state-icon state-obj="[[item]]"></ha-state-icon>
<paper-tooltip position="bottom"
>[[_computeStateName(item)]]</paper-tooltip
>
</span>
</template>
</div>
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
</template>
</ha-card>
</ha-config-section>
<ha-fab
icon="hass:plus"
title="[[localize('ui.panel.config.integrations.new')]]"
on-click="_createFlow"
is-wide$="[[isWide]]"
rtl$="[[rtl]]"
></ha-fab>
</hass-subpage>
`;
}
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);

View File

@ -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`
<hass-subpage
header=${this.hass.localize("ui.panel.config.integrations.caption")}
>
${this.progress.length
? html`
<ha-config-section>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.integrations.discovered"
)}</span
>
<ha-card>
${this.progress.map(
(flow) => html`
<div class="config-entry-row">
<paper-item-body>
${localizeConfigFlowTitle(this.hass.localize, flow)}
</paper-item-body>
<mwc-button @click=${this._continueFlow}
>${this.hass.localize(
"ui.panel.config.integrations.configure"
)}</mwc-button
>
</div>
`
)}
</ha-card>
</ha-config-section>
`
: ""}
<ha-config-section class="configured">
<span slot="header"
>${this.hass.localize(
"ui.panel.config.integrations.configured"
)}</span
>
<ha-card>
${this.entities.length
? this.entries.map(
(item: any, idx) => html`
<a
href="/config/integrations/config_entry/${item.entry_id}"
>
<paper-item data-index=${idx}>
<paper-item-body two-line>
<div>
${this.hass.localize(
`component.${item.domain}.config.title`
)}:
${item.title}
</div>
<div secondary>
${this._getEntities(item).map(
(entity) => html`
<span>
<ha-state-icon
.stateObj=${entity}
></ha-state-icon>
<paper-tooltip position="bottom"
>${computeStateName(entity)}</paper-tooltip
>
</span>
`
)}
</div>
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`
)
: html`
<div class="config-entry-row">
<paper-item-body two-line>
<div>
${this.hass.localize(
"ui.panel.config.integrations.none"
)}
</div>
</paper-item-body>
</div>
`}
</ha-card>
</ha-config-section>
<ha-fab
icon="hass:plus"
title=${this.hass.localize("ui.panel.config.integrations.new")}
@click=${this._createFlow}
?rtl=${computeRTL(this.hass!)}
?isWide=${this.isWide}
></ha-fab>
</hass-subpage>
`;
}
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;
}
`;
}
}

View File

@ -137,7 +137,7 @@ documentContainer.innerHTML = `<custom-style>
--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);
}

View File

@ -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",