Skip to content

Commit b022128

Browse files
authored
Merge pull request #3567 from home-assistant/dev
20190901.0
2 parents b154903 + 1bc2e6f commit b022128

33 files changed

+921
-132
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"roboto-fontface": "^0.10.0",
9797
"round-slider": "^1.3.3",
9898
"superstruct": "^0.6.1",
99+
"tslib": "^1.10.0",
99100
"unfetch": "^4.1.0",
100101
"web-animations-js": "^2.3.1",
101102
"xss": "^1.0.6"

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name="home-assistant-frontend",
5-
version="20190828.0",
5+
version="20190901.0",
66
description="The Home Assistant frontend",
77
url="https://github.com/home-assistant/home-assistant-polymer",
88
author="The Home Assistant Authors",

src/components/buttons/ha-call-service-button.js

+7
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,17 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
4242
type: Object,
4343
value: {},
4444
},
45+
46+
confirmation: {
47+
type: String,
48+
},
4549
};
4650
}
4751

4852
buttonTapped() {
53+
if (this.confirmation && !window.confirm(this.confirmation)) {
54+
return;
55+
}
4956
this.progress = true;
5057
var el = this;
5158
var eventData = {
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import "@polymer/paper-input/paper-input";
2+
import "@polymer/paper-item/paper-item";
3+
import "@polymer/paper-item/paper-item-body";
4+
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
5+
import "@polymer/paper-listbox/paper-listbox";
6+
import memoizeOne from "memoize-one";
7+
import {
8+
LitElement,
9+
TemplateResult,
10+
html,
11+
css,
12+
CSSResult,
13+
customElement,
14+
property,
15+
} from "lit-element";
16+
import { UnsubscribeFunc } from "home-assistant-js-websocket";
17+
import { HomeAssistant } from "../../types";
18+
import { fireEvent } from "../../common/dom/fire_event";
19+
import {
20+
DeviceRegistryEntry,
21+
subscribeDeviceRegistry,
22+
} from "../../data/device_registry";
23+
import { compare } from "../../common/string/compare";
24+
25+
@customElement("ha-device-picker")
26+
class HaDevicePicker extends LitElement {
27+
public hass?: HomeAssistant;
28+
@property() public label?: string;
29+
@property() public value?: string;
30+
@property() public devices?: DeviceRegistryEntry[];
31+
private _unsubDevices?: UnsubscribeFunc;
32+
33+
private _sortedDevices = memoizeOne((devices?: DeviceRegistryEntry[]) => {
34+
if (!devices || devices.length === 1) {
35+
return devices || [];
36+
}
37+
const sorted = [...devices];
38+
sorted.sort((a, b) => compare(a.name || "", b.name || ""));
39+
return sorted;
40+
});
41+
42+
public connectedCallback() {
43+
super.connectedCallback();
44+
this._unsubDevices = subscribeDeviceRegistry(
45+
this.hass!.connection!,
46+
(devices) => {
47+
this.devices = devices;
48+
}
49+
);
50+
}
51+
52+
public disconnectedCallback() {
53+
super.disconnectedCallback();
54+
if (this._unsubDevices) {
55+
this._unsubDevices();
56+
this._unsubDevices = undefined;
57+
}
58+
}
59+
60+
protected render(): TemplateResult | void {
61+
return html`
62+
<paper-dropdown-menu-light .label=${this.label}>
63+
<paper-listbox
64+
slot="dropdown-content"
65+
.selected=${this._value}
66+
attr-for-selected="data-device-id"
67+
@iron-select=${this._deviceChanged}
68+
>
69+
<paper-item data-device-id="">
70+
No device
71+
</paper-item>
72+
${this._sortedDevices(this.devices).map(
73+
(device) => html`
74+
<paper-item data-device-id=${device.id}>
75+
${device.name_by_user || device.name}
76+
</paper-item>
77+
`
78+
)}
79+
</paper-listbox>
80+
</paper-dropdown-menu-light>
81+
`;
82+
}
83+
84+
private get _value() {
85+
return this.value || "";
86+
}
87+
88+
private _deviceChanged(ev) {
89+
const newValue = ev.detail.item.dataset.deviceId;
90+
91+
if (newValue !== this._value) {
92+
this.value = newValue;
93+
setTimeout(() => {
94+
fireEvent(this, "value-changed", { value: newValue });
95+
fireEvent(this, "change");
96+
}, 0);
97+
}
98+
}
99+
100+
static get styles(): CSSResult {
101+
return css`
102+
paper-dropdown-menu-light {
103+
width: 100%;
104+
}
105+
paper-listbox {
106+
min-width: 200px;
107+
}
108+
paper-item {
109+
cursor: pointer;
110+
}
111+
`;
112+
}
113+
}
114+
115+
declare global {
116+
interface HTMLElementTagNameMap {
117+
"ha-device-picker": HaDevicePicker;
118+
}
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import "@polymer/paper-input/paper-input";
2+
import "@polymer/paper-item/paper-item";
3+
import "@polymer/paper-item/paper-item-body";
4+
import "@polymer/paper-listbox/paper-listbox";
5+
import {
6+
LitElement,
7+
TemplateResult,
8+
html,
9+
css,
10+
CSSResult,
11+
customElement,
12+
property,
13+
} from "lit-element";
14+
import { HomeAssistant } from "../../types";
15+
import { fireEvent } from "../../common/dom/fire_event";
16+
import {
17+
DeviceTrigger,
18+
fetchDeviceTriggers,
19+
deviceAutomationTriggersEqual,
20+
localizeDeviceAutomationTrigger,
21+
} from "../../data/device_automation";
22+
import "../../components/ha-paper-dropdown-menu";
23+
24+
const NO_TRIGGER_KEY = "NO_TRIGGER";
25+
const UNKNOWN_TRIGGER_KEY = "UNKNOWN_TRIGGER";
26+
27+
@customElement("ha-device-trigger-picker")
28+
class HaDeviceTriggerPicker extends LitElement {
29+
public hass!: HomeAssistant;
30+
@property() public label?: string;
31+
@property() public deviceId?: string;
32+
@property() public value?: DeviceTrigger;
33+
@property() private _triggers: DeviceTrigger[] = [];
34+
35+
// Trigger an empty render so we start with a clean DOM.
36+
// paper-listbox does not like changing things around.
37+
@property() private _renderEmpty = false;
38+
39+
private get _key() {
40+
if (!this.value) {
41+
return NO_TRIGGER_KEY;
42+
}
43+
44+
const idx = this._triggers.findIndex((trigger) =>
45+
deviceAutomationTriggersEqual(trigger, this.value!)
46+
);
47+
48+
if (idx === -1) {
49+
return UNKNOWN_TRIGGER_KEY;
50+
}
51+
52+
return `${this._triggers[idx].device_id}_${idx}`;
53+
}
54+
55+
protected render(): TemplateResult | void {
56+
if (this._renderEmpty) {
57+
return html``;
58+
}
59+
return html`
60+
<ha-paper-dropdown-menu
61+
.label=${this.label}
62+
.value=${this.value
63+
? localizeDeviceAutomationTrigger(this.hass, this.value)
64+
: ""}
65+
?disabled=${this._triggers.length === 0}
66+
>
67+
<paper-listbox
68+
slot="dropdown-content"
69+
.selected=${this._key}
70+
attr-for-selected="key"
71+
@iron-select=${this._triggerChanged}
72+
>
73+
<paper-item key=${NO_TRIGGER_KEY} .trigger=${this._noTrigger} hidden>
74+
No triggers
75+
</paper-item>
76+
<paper-item key=${UNKNOWN_TRIGGER_KEY} .trigger=${this.value} hidden>
77+
Unknown trigger
78+
</paper-item>
79+
${this._triggers.map(
80+
(trigger, idx) => html`
81+
<paper-item key=${`${this.deviceId}_${idx}`} .trigger=${trigger}>
82+
${localizeDeviceAutomationTrigger(this.hass, trigger)}
83+
</paper-item>
84+
`
85+
)}
86+
</paper-listbox>
87+
</ha-paper-dropdown-menu>
88+
`;
89+
}
90+
91+
protected updated(changedProps) {
92+
super.updated(changedProps);
93+
94+
if (changedProps.has("deviceId")) {
95+
this._updateDeviceInfo();
96+
}
97+
98+
// The value has changed, force the listbox to update
99+
if (changedProps.has("value") || changedProps.has("_renderEmpty")) {
100+
const listbox = this.shadowRoot!.querySelector("paper-listbox")!;
101+
if (listbox) {
102+
listbox._selectSelected(this._key);
103+
}
104+
}
105+
}
106+
107+
private async _updateDeviceInfo() {
108+
this._triggers = this.deviceId
109+
? await fetchDeviceTriggers(this.hass!, this.deviceId!)
110+
: // No device, clear the list of triggers
111+
[];
112+
113+
// If there is no value, or if we have changed the device ID, reset the value.
114+
if (!this.value || this.value.device_id !== this.deviceId) {
115+
this._setValue(
116+
this._triggers.length ? this._triggers[0] : this._noTrigger
117+
);
118+
}
119+
this._renderEmpty = true;
120+
await this.updateComplete;
121+
this._renderEmpty = false;
122+
}
123+
124+
private get _noTrigger() {
125+
return {
126+
device_id: this.deviceId || "",
127+
platform: "device",
128+
domain: "",
129+
entity_id: "",
130+
};
131+
}
132+
133+
private _triggerChanged(ev) {
134+
this._setValue(ev.detail.item.trigger);
135+
}
136+
137+
private _setValue(trigger: DeviceTrigger) {
138+
if (this.value && deviceAutomationTriggersEqual(trigger, this.value)) {
139+
return;
140+
}
141+
this.value = trigger;
142+
setTimeout(() => {
143+
fireEvent(this, "change");
144+
}, 0);
145+
}
146+
147+
static get styles(): CSSResult {
148+
return css`
149+
ha-paper-dropdown-menu {
150+
width: 100%;
151+
}
152+
paper-listbox {
153+
min-width: 200px;
154+
}
155+
paper-item {
156+
cursor: pointer;
157+
}
158+
`;
159+
}
160+
}
161+
162+
declare global {
163+
interface HTMLElementTagNameMap {
164+
"ha-device-trigger-picker": HaDeviceTriggerPicker;
165+
}
166+
}

src/data/device_automation.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { HomeAssistant } from "../types";
2+
import compute_state_name from "../common/entity/compute_state_name";
3+
4+
export interface DeviceTrigger {
5+
platform: string;
6+
device_id: string;
7+
domain: string;
8+
entity_id: string;
9+
type?: string;
10+
event?: string;
11+
}
12+
13+
export interface DeviceTriggerList {
14+
triggers: DeviceTrigger[];
15+
}
16+
17+
export const fetchDeviceTriggers = (hass: HomeAssistant, deviceId: string) =>
18+
hass
19+
.callWS<DeviceTriggerList>({
20+
type: "device_automation/trigger/list",
21+
device_id: deviceId,
22+
})
23+
.then((response) => response.triggers);
24+
25+
export const deviceAutomationTriggersEqual = (
26+
a: DeviceTrigger,
27+
b: DeviceTrigger
28+
) => {
29+
if (typeof a !== typeof b) {
30+
return false;
31+
}
32+
33+
for (const property in a) {
34+
if (!Object.is(a[property], b[property])) {
35+
return false;
36+
}
37+
}
38+
for (const property in b) {
39+
if (!Object.is(a[property], b[property])) {
40+
return false;
41+
}
42+
}
43+
44+
return true;
45+
};
46+
47+
export const localizeDeviceAutomationTrigger = (
48+
hass: HomeAssistant,
49+
trigger: DeviceTrigger
50+
) =>
51+
hass.localize(
52+
`component.${trigger.domain}.device_automation.trigger_type.${
53+
trigger.type
54+
}`,
55+
"name",
56+
trigger.entity_id ? compute_state_name(hass!.states[trigger.entity_id]) : ""
57+
);

0 commit comments

Comments
 (0)