From 993d390ea57a90684e2329f2ebe35e647c029795 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 23 Sep 2019 14:13:44 +0200 Subject: [PATCH] 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 --- package.json | 5 +- src/components/ha-chips.ts | 69 ++++++++++++++ src/data/automation.ts | 17 ++++ .../config/automation/ha-automation-editor.ts | 4 +- .../config/automation/ha-config-automation.js | 5 + .../device-detail/ha-device-actions-card.ts | 26 ++++++ .../ha-device-automation-card.ts | 92 +++++++++++++++++++ .../{ => device-detail}/ha-device-card.js | 20 ++-- .../ha-device-conditions-card.ts | 28 ++++++ .../device-detail/ha-device-triggers-card.ts | 26 ++++++ .../config/devices/ha-config-device-page.ts | 56 +++++++++-- .../config-entry/ha-config-entry-page.ts | 2 +- src/translations/en.json | 13 ++- yarn.lock | 60 +++++++----- 14 files changed, 376 insertions(+), 47 deletions(-) create mode 100644 src/components/ha-chips.ts create mode 100644 src/panels/config/devices/device-detail/ha-device-actions-card.ts create mode 100644 src/panels/config/devices/device-detail/ha-device-automation-card.ts rename src/panels/config/devices/{ => device-detail}/ha-device-card.js (92%) create mode 100644 src/panels/config/devices/device-detail/ha-device-conditions-card.ts create mode 100644 src/panels/config/devices/device-detail/ha-device-triggers-card.ts diff --git a/package.json b/package.json index 425d086ab1..a886375cf7 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,13 @@ "author": "Paulus Schoutsen (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", diff --git a/src/components/ha-chips.ts b/src/components/ha-chips.ts new file mode 100644 index 0000000000..132fb1f99d --- /dev/null +++ b/src/components/ha-chips.ts @@ -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` +
+ ${this.items.map( + (item, idx) => + html` + + ` + )} +
+ `; + } + + 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; + } +} diff --git a/src/data/automation.ts b/src/data/automation.ts index 241edf3008..263be8b443 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -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 | undefined; + +export const showAutomationEditor = ( + el: HTMLElement, + data?: Partial +) => { + inititialAutomationEditorData = data; + navigate(el, "/config/automation/new"); +}; + +export const getAutomationEditorInitData = () => { + const data = inititialAutomationEditorData; + inititialAutomationEditorData = undefined; + return data; +}; diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index c54c4d918c..7054ad5365 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -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(), }; } diff --git a/src/panels/config/automation/ha-config-automation.js b/src/panels/config/automation/ha-config-automation.js index f90282aa10..ec685ae329 100644 --- a/src/panels/config/automation/ha-config-automation.js +++ b/src/panels/config/automation/ha-config-automation.js @@ -75,6 +75,11 @@ class HaConfigAutomation extends PolymerElement { }; } + disconnectedCallback() { + super.disconnectedCallback(); + this.route = { path: "", prefix: "" }; + } + computeAutomation(automations, edittingAddon, routeData) { if (!automations || !edittingAddon) { return null; diff --git a/src/panels/config/devices/device-detail/ha-device-actions-card.ts b/src/panels/config/devices/device-detail/ha-device-actions-card.ts new file mode 100644 index 0000000000..a799b96593 --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-actions-card.ts @@ -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 { + 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; + } +} diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts new file mode 100644 index 0000000000..3f7cb43b7b --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-automation-card.ts @@ -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; + + constructor( + localizeDeviceAutomation: HaDeviceAutomationCard< + T + >["_localizeDeviceAutomation"], + fetchDeviceAutomations: HaDeviceAutomationCard["_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 { + 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` + +
+ ${this.hass.localize(this.headerKey)} +
+
+ + this._localizeDeviceAutomation(this.hass, automation) + )} + > + +
+
+ `; + } + + private _handleAutomationClicked(ev: CustomEvent) { + const automation = this._automations[ev.detail.index]; + if (!automation) { + return; + } + const data = {}; + data[this.type] = [automation]; + showAutomationEditor(this, data); + } +} diff --git a/src/panels/config/devices/ha-device-card.js b/src/panels/config/devices/device-detail/ha-device-card.js similarity index 92% rename from src/panels/config/devices/ha-device-card.js rename to src/panels/config/devices/device-detail/ha-device-card.js index f96783e2c1..2bad52c16b 100644 --- a/src/panels/config/devices/ha-device-card.js +++ b/src/panels/config/devices/device-detail/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; diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts new file mode 100644 index 0000000000..b98e1a67c4 --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts @@ -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; + } +} diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts new file mode 100644 index 0000000000..76dba47577 --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts @@ -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; + } +} diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index a88a082c1a..e477d4b049 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -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} > - +
+ + + + +
`; } @@ -77,4 +101,16 @@ export class HaConfigDevicePage extends LitElement { }, }); } + + static get styles(): CSSResult { + return css` + .content { + padding: 16px; + } + .content > * { + display: block; + margin-bottom: 16px; + } + `; + } } 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 2235fcc5aa..5a10e4b87c 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 "../../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"; diff --git a/src/translations/en.json b/src/translations/en.json index 547ce38011..b9b7a2e873 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 3abd40d2a4..a06413dcfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"