Add device automation options to device page (#3776)

* Add device automation options to device page

* Update

* Fill automation editor with data

* Update ha-automation-editor.ts

* Remove dupe deps

* Fix imports
This commit is contained in:
Bram Kragten 2019-09-23 14:13:44 +02:00 committed by GitHub
parent 1f4d359050
commit 993d390ea5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 376 additions and 47 deletions

View File

@ -17,12 +17,13 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@material/data-table": "^3.1.1",
"@material/chips": "^3.2.0",
"@material/data-table": "^3.2.0",
"@material/mwc-base": "^0.8.0",
"@material/mwc-button": "^0.8.0",
"@material/mwc-checkbox": "^0.8.0",
"@material/mwc-fab": "^0.8.0",
"@material/mwc-ripple": "0.8.0",
"@material/mwc-ripple": "^0.8.0",
"@material/mwc-switch": "^0.8.0",
"@mdi/svg": "4.4.95",
"@polymer/app-layout": "^3.0.2",

View File

@ -0,0 +1,69 @@
import {
css,
CSSResult,
html,
LitElement,
property,
TemplateResult,
customElement,
unsafeCSS,
} from "lit-element";
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import { fireEvent } from "../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"chip-clicked": { index: string };
}
}
@customElement("ha-chips")
export class HaChips extends LitElement {
@property() public items = [];
protected render(): TemplateResult {
if (this.items.length === 0) {
return html``;
}
return html`
<div class="mdc-chip-set">
${this.items.map(
(item, idx) =>
html`
<button
class="mdc-chip"
.index=${idx}
@click=${this._handleClick}
>
<span class="mdc-chip__text">${item}</span>
</button>
`
)}
</div>
`;
}
private _handleClick(ev) {
fireEvent(
this,
"chip-clicked",
{ index: ev.target.closest("button").index },
{ bubbles: false }
);
}
static get styles(): CSSResult {
return css`
${unsafeCSS(chipStyles)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-chips": HaChips;
}
}

View File

@ -3,6 +3,7 @@ import {
HassEntityAttributeBase,
} from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@ -21,3 +22,19 @@ export interface AutomationConfig {
export const deleteAutomation = (hass: HomeAssistant, id: string) =>
hass.callApi("DELETE", `config/automation/config/${id}`);
let inititialAutomationEditorData: Partial<AutomationConfig> | undefined;
export const showAutomationEditor = (
el: HTMLElement,
data?: Partial<AutomationConfig>
) => {
inititialAutomationEditorData = data;
navigate(el, "/config/automation/new");
};
export const getAutomationEditorInitData = () => {
const data = inititialAutomationEditorData;
inititialAutomationEditorData = undefined;
return data;
};

View File

@ -28,6 +28,7 @@ import {
AutomationEntity,
AutomationConfig,
deleteAutomation,
getAutomationEditorInitData,
} from "../../../data/automation";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
@ -36,7 +37,7 @@ function AutomationEditor(mountEl, props, mergeEl) {
return render(h(Automation, props), mountEl, mergeEl);
}
class HaAutomationEditor extends LitElement {
export class HaAutomationEditor extends LitElement {
public hass!: HomeAssistant;
public automation!: AutomationEntity;
public isWide?: boolean;
@ -187,6 +188,7 @@ class HaAutomationEditor extends LitElement {
trigger: [{ platform: "state" }],
condition: [],
action: [{ service: "" }],
...getAutomationEditorInitData(),
};
}

View File

@ -75,6 +75,11 @@ class HaConfigAutomation extends PolymerElement {
};
}
disconnectedCallback() {
super.disconnectedCallback();
this.route = { path: "", prefix: "" };
}
computeAutomation(automations, edittingAddon, routeData) {
if (!automations || !edittingAddon) {
return null;

View File

@ -0,0 +1,26 @@
import { customElement } from "lit-element";
import {
DeviceAction,
fetchDeviceActions,
localizeDeviceAutomationAction,
} from "../../../../data/device_automation";
import "../../../../components/ha-card";
import { HaDeviceAutomationCard } from "./ha-device-automation-card";
@customElement("ha-device-actions-card")
export class HaDeviceActionsCard extends HaDeviceAutomationCard<DeviceAction> {
protected type = "action";
protected headerKey = "ui.panel.config.devices.automation.actions.caption";
constructor() {
super(localizeDeviceAutomationAction, fetchDeviceActions);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-actions-card": HaDeviceActionsCard;
}
}

View File

@ -0,0 +1,92 @@
import { LitElement, TemplateResult, html, property } from "lit-element";
import { HomeAssistant } from "../../../../types";
import { DeviceAutomation } from "../../../../data/device_automation";
import "../../../../components/ha-card";
import "../../../../components/ha-chips";
import { showAutomationEditor } from "../../../../data/automation";
export abstract class HaDeviceAutomationCard<
T extends DeviceAutomation
> extends LitElement {
@property() public hass!: HomeAssistant;
@property() public deviceId?: string;
protected headerKey = "";
protected type = "";
@property() private _automations: T[] = [];
private _localizeDeviceAutomation: (
hass: HomeAssistant,
automation: T
) => string;
private _fetchDeviceAutomations: (
hass: HomeAssistant,
deviceId: string
) => Promise<T[]>;
constructor(
localizeDeviceAutomation: HaDeviceAutomationCard<
T
>["_localizeDeviceAutomation"],
fetchDeviceAutomations: HaDeviceAutomationCard<T>["_fetchDeviceAutomations"]
) {
super();
this._localizeDeviceAutomation = localizeDeviceAutomation;
this._fetchDeviceAutomations = fetchDeviceAutomations;
}
protected shouldUpdate(changedProps): boolean {
if (changedProps.has("deviceId") || changedProps.has("_automations")) {
return true;
}
const oldHass = changedProps.get("hass");
if (!oldHass || this.hass.language !== oldHass.language) {
return true;
}
return false;
}
protected async updated(changedProps): Promise<void> {
super.updated(changedProps);
if (changedProps.has("deviceId")) {
this._automations = this.deviceId
? await this._fetchDeviceAutomations(this.hass, this.deviceId)
: [];
}
}
protected render(): TemplateResult {
if (this._automations.length === 0) {
return html``;
}
return html`
<ha-card>
<div class="card-header">
${this.hass.localize(this.headerKey)}
</div>
<div class="card-content">
<ha-chips
@chip-clicked=${this._handleAutomationClicked}
.items=${this._automations.map((automation) =>
this._localizeDeviceAutomation(this.hass, automation)
)}
>
</ha-chips>
</div>
</ha-card>
`;
}
private _handleAutomationClicked(ev: CustomEvent) {
const automation = this._automations[ev.detail.index];
if (!automation) {
return;
}
const data = {};
data[this.type] = [automation];
showAutomationEditor(this, data);
}
}

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;

View File

@ -0,0 +1,28 @@
import { customElement } from "lit-element";
import {
DeviceCondition,
fetchDeviceConditions,
localizeDeviceAutomationCondition,
} from "../../../../data/device_automation";
import "../../../../components/ha-card";
import { HaDeviceAutomationCard } from "./ha-device-automation-card";
@customElement("ha-device-conditions-card")
export class HaDeviceConditionsCard extends HaDeviceAutomationCard<
DeviceCondition
> {
protected type = "condition";
protected headerKey = "ui.panel.config.devices.automation.conditions.caption";
constructor() {
super(localizeDeviceAutomationCondition, fetchDeviceConditions);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-conditions-card": HaDeviceConditionsCard;
}
}

View File

@ -0,0 +1,26 @@
import { customElement } from "lit-element";
import {
DeviceTrigger,
fetchDeviceTriggers,
localizeDeviceAutomationTrigger,
} from "../../../../data/device_automation";
import { HaDeviceAutomationCard } from "./ha-device-automation-card";
@customElement("ha-device-triggers-card")
export class HaDeviceTriggersCard extends HaDeviceAutomationCard<
DeviceTrigger
> {
protected type = "trigger";
protected headerKey = "ui.panel.config.devices.automation.triggers.caption";
constructor() {
super(localizeDeviceAutomationTrigger, fetchDeviceTriggers);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-triggers-card": HaDeviceTriggersCard;
}
}

View File

@ -1,11 +1,21 @@
import { property, LitElement, html, customElement } from "lit-element";
import {
property,
LitElement,
html,
customElement,
css,
CSSResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-error-screen";
import "./ha-device-card";
import "./device-detail/ha-device-card";
import "./device-detail/ha-device-triggers-card";
import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card";
import { HomeAssistant } from "../../../types";
import { ConfigEntry } from "../../../data/config_entries";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@ -57,14 +67,28 @@ export class HaConfigDevicePage extends LitElement {
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>
<div class="content">
<ha-device-card
.hass=${this.hass}
.areas=${this.areas}
.devices=${this.devices}
.device=${device}
.entities=${this.entities}
hide-settings
></ha-device-card>
<ha-device-triggers-card
.hass=${this.hass}
.deviceId=${this.deviceId}
></ha-device-triggers-card>
<ha-device-conditions-card
.hass=${this.hass}
.deviceId=${this.deviceId}
></ha-device-conditions-card>
<ha-device-actions-card
.hass=${this.hass}
.deviceId=${this.deviceId}
></ha-device-actions-card>
</div>
</hass-subpage>
`;
}
@ -77,4 +101,16 @@ export class HaConfigDevicePage extends LitElement {
},
});
}
static get styles(): CSSResult {
return css`
.content {
padding: 16px;
}
.content > * {
display: block;
margin-bottom: 16px;
}
`;
}
}

View File

@ -5,7 +5,7 @@ import "../../../../layouts/hass-error-screen";
import "../../../../components/entity/state-badge";
import { compare } from "../../../../common/string/compare";
import "../../devices/ha-device-card";
import "../../devices/device-detail/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";

View File

@ -895,7 +895,18 @@
},
"devices": {
"caption": "Devices",
"description": "Manage connected devices"
"description": "Manage connected devices",
"automation": {
"triggers": {
"caption": "Do something when..."
},
"conditions": {
"caption": "Only do something if..."
},
"actions": {
"caption": "When something is triggered..."
}
}
},
"entity_registry": {
"caption": "Entity Registry",

View File

@ -532,33 +532,49 @@
"@material/theme" "^3.1.0"
"@material/typography" "^3.1.0"
"@material/checkbox@^3.0.0", "@material/checkbox@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-3.1.0.tgz#bb8eadda0d260e75e8a7479418490eec846a8520"
integrity sha512-Rcv6Srj2p3MTsODPLJLgRzGW142ovQTKblkCy9AxABZriQUPRCV/fkJwB0LlqecHgubhnjhtj2Zui0o9jhfu/w==
"@material/checkbox@^3.0.0", "@material/checkbox@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-3.2.0.tgz#168d4e16e160bec17948d06416935250fa10fac5"
integrity sha512-4XgQ4sM40j60n4RN43BxXtkFVvyFQgo/vc0W5hf9Qz2uwEah46Shg1nHMYZNvcNW+FXRm96gH8zz3qFgEf1ytA==
dependencies:
"@material/animation" "^3.1.0"
"@material/base" "^3.1.0"
"@material/dom" "^3.1.0"
"@material/feature-targeting" "^3.1.0"
"@material/ripple" "^3.1.0"
"@material/rtl" "^3.1.0"
"@material/ripple" "^3.2.0"
"@material/rtl" "^3.2.0"
"@material/theme" "^3.1.0"
tslib "^1.9.3"
"@material/data-table@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-3.1.1.tgz#3e88e2f8ba7d8a56208cbe506b7db342911c5bd3"
integrity sha512-6p85gotXObC47KYaEOM1sJKqrXOFkhfHutmrsHMFDLr4B3mCS7XH9KxvBFX4uw9uEZlgiUJBJtbiUIXuHhLIEQ==
"@material/chips@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@material/chips/-/chips-3.2.0.tgz#29973a0b92b99f6d30fdcc086ec13f1a06d27663"
integrity sha512-XPm2RkqPFRog7hCMBTP4lM8AH9fqysXDMqf0ZomeJbFj4mkyalKsp45zrCR384gYjymwu99EHpcIs8L+gjVsrQ==
dependencies:
"@material/animation" "^3.1.0"
"@material/base" "^3.1.0"
"@material/checkbox" "^3.1.0"
"@material/checkbox" "^3.2.0"
"@material/elevation" "^3.1.0"
"@material/feature-targeting" "^3.1.0"
"@material/ripple" "^3.2.0"
"@material/shape" "^3.1.0"
"@material/theme" "^3.1.0"
"@material/typography" "^3.1.0"
tslib "^1.9.3"
"@material/data-table@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-3.2.0.tgz#4751a83bc4f01252ba7aa908e78dd389f452ed83"
integrity sha512-67Bjo4B3kiB2zPEhd1OyHGWCNyQtoFTLNmOAPDFAxbmfaBquvjzAEYos2/cJpvzP8yUCIMJlOYaP0uvv94n2og==
dependencies:
"@material/animation" "^3.1.0"
"@material/base" "^3.1.0"
"@material/checkbox" "^3.2.0"
"@material/dom" "^3.1.0"
"@material/elevation" "^3.1.0"
"@material/feature-targeting" "^3.1.0"
"@material/ripple" "^3.1.0"
"@material/rtl" "^3.1.0"
"@material/ripple" "^3.2.0"
"@material/rtl" "^3.2.0"
"@material/shape" "^3.1.0"
"@material/theme" "^3.1.0"
"@material/typography" "^3.1.0"
@ -650,7 +666,7 @@
"@material/mwc-base" "^0.8.0"
tslib "^1.10.0"
"@material/mwc-ripple@0.8.0", "@material/mwc-ripple@^0.8.0":
"@material/mwc-ripple@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.8.0.tgz#a18e43a087e4356de8740d082378d58a166aa93c"
integrity sha512-hJL+8xNunE+GUk+dtgeIVL9BJM5QPl5uyIufxzGEbVu+pmUfVDml+3HQLapO6Q5MQZMZpO4tDNwJNx9HOAo5KQ==
@ -671,10 +687,10 @@
"@material/switch" "^3.0.0"
tslib "^1.10.0"
"@material/ripple@^3.0.0", "@material/ripple@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-3.1.0.tgz#5cb581e9a70415c50c8b92ecd8628d5eeae34c74"
integrity sha512-mYvd2iWbQyVd6aLS9alHShoL05p/D0cvh5h1ga3atz55azooMLhGsbbE1YlEqUDKHKNuNvdVFm+0IfWdvvRgsw==
"@material/ripple@^3.0.0", "@material/ripple@^3.1.0", "@material/ripple@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-3.2.0.tgz#f4b714834b73b793b280024d4ebcca01018df3bd"
integrity sha512-GtwkfNakALmfGLs6TpdFIVeAWjRqbyT7WfEw9aU7elUokABfHES+O0KoSKQSMQiSQ8Vjl90MONzNsN1Evi/1YQ==
dependencies:
"@material/animation" "^3.1.0"
"@material/base" "^3.1.0"
@ -683,10 +699,10 @@
"@material/theme" "^3.1.0"
tslib "^1.9.3"
"@material/rtl@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-3.1.0.tgz#8a5254bcf6c4d897e16206d52ba98b8eb98d45b7"
integrity sha512-HH19edQNb139zC+1SZ6/C9G92E54fUrnnW9AAF7t5eGjGdF26YJXJ/uhz+TnFhNUMi/QGrKUSycd4o73nU1m4A==
"@material/rtl@^3.1.0", "@material/rtl@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-3.2.0.tgz#0b2f7321463100674dfbf4507b54ccd052f05378"
integrity sha512-L/w9m9Yx1ceOw/VjEfeJoqD4rW9QP3IBb9MamXAg3qUi/zsztoXD/FUw179pxkLn4huFFNlVYZ4Y1y6BpM0PMA==
"@material/shape@^3.1.0":
version "3.1.0"