mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-09 02:49:51 +00:00
Person: Pick device tracker (#2726)
* Allow picking devices to track * Tweak translation * Update translation
This commit is contained in:
120
src/components/entity/ha-entities-picker.ts
Normal file
120
src/components/entity/ha-entities-picker.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
html,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-icon-button/paper-icon-button-light";
|
||||
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import isValidEntityId from "../../common/entity/valid_entity_id";
|
||||
|
||||
import "./ha-entity-picker";
|
||||
// Not a duplicate, type import
|
||||
// tslint:disable-next-line
|
||||
import { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
@customElement("ha-entities-picker")
|
||||
class HaEntitiesPickerLight extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public value?: string[];
|
||||
@property() public domainFilter?: string;
|
||||
@property() public pickedEntityLabel?: string;
|
||||
@property() public pickEntityLabel?: string;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const currentEntities = this._currentEntities;
|
||||
return html`
|
||||
${currentEntities.map(
|
||||
(entityId) => html`
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.curValue=${entityId}
|
||||
.hass=${this.hass}
|
||||
.domainFilter=${this.domainFilter}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedEntityLabel}
|
||||
@value-changed=${this._entityChanged}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.domainFilter=${this.domainFilter}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityFilter: HaEntityPickerEntityFilterFunc = (
|
||||
stateObj: HassEntity
|
||||
) => !this.value || !this.value.includes(stateObj.entity_id);
|
||||
|
||||
private get _currentEntities() {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private async _updateEntities(entities) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: entities,
|
||||
});
|
||||
|
||||
this.value = entities;
|
||||
}
|
||||
|
||||
private _entityChanged(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const curValue = (event.currentTarget as any).curValue;
|
||||
const newValue = event.detail.value;
|
||||
if (
|
||||
newValue === curValue ||
|
||||
(newValue !== "" && !isValidEntityId(newValue))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (newValue === "") {
|
||||
this._updateEntities(
|
||||
this._currentEntities.filter((ent) => ent !== curValue)
|
||||
);
|
||||
} else {
|
||||
this._updateEntities(
|
||||
this._currentEntities.map((ent) => (ent === curValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _addEntity(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const toAdd = event.detail.value;
|
||||
(event.currentTarget as any).value = "";
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
const currentEntities = this._currentEntities;
|
||||
if (currentEntities.includes(toAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateEntities([...currentEntities, toAdd]);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entities-picker": HaEntitiesPickerLight;
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-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 "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
|
||||
|
||||
import "./state-badge";
|
||||
|
||||
import computeStateName from "../../common/entity/compute_state_name";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaEntityPicker extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-input > paper-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<vaadin-combo-box-light
|
||||
items="[[_states]]"
|
||||
item-value-path="entity_id"
|
||||
item-label-path="entity_id"
|
||||
value="{{value}}"
|
||||
opened="{{opened}}"
|
||||
allow-custom-value="[[allowCustomEntity]]"
|
||||
on-change="_fireChanged"
|
||||
>
|
||||
<paper-input
|
||||
autofocus="[[autofocus]]"
|
||||
label="[[_computeLabel(label, localize)]]"
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
value="[[value]]"
|
||||
disabled="[[disabled]]"
|
||||
>
|
||||
<paper-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
no-ripple=""
|
||||
hidden$="[[!value]]"
|
||||
>Clear</paper-icon-button
|
||||
>
|
||||
<paper-icon-button
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
icon="[[_computeToggleIcon(opened)]]"
|
||||
hidden="[[!_states.length]]"
|
||||
>Toggle</paper-icon-button
|
||||
>
|
||||
</paper-input>
|
||||
<template>
|
||||
<style>
|
||||
paper-icon-item {
|
||||
margin: -10px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-icon-item>
|
||||
<state-badge state-obj="[[item]]" slot="item-icon"></state-badge>
|
||||
<paper-item-body two-line="">
|
||||
<div>[[_computeStateName(item)]]</div>
|
||||
<div secondary="">[[item.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
allowCustomEntity: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: "_hassChanged",
|
||||
},
|
||||
_hass: Object,
|
||||
_states: {
|
||||
type: Array,
|
||||
computed: "_computeStates(_hass, domainFilter, entityFilter)",
|
||||
},
|
||||
autofocus: Boolean,
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
opened: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: "_openedChanged",
|
||||
},
|
||||
domainFilter: {
|
||||
type: String,
|
||||
value: null,
|
||||
},
|
||||
entityFilter: {
|
||||
type: Function,
|
||||
value: null,
|
||||
},
|
||||
disabled: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
_computeLabel(label, localize) {
|
||||
return label === undefined
|
||||
? localize("ui.components.entity.entity-picker.entity")
|
||||
: label;
|
||||
}
|
||||
|
||||
_computeStates(hass, domainFilter, entityFilter) {
|
||||
if (!hass) return [];
|
||||
|
||||
let entityIds = Object.keys(hass.states);
|
||||
|
||||
if (domainFilter) {
|
||||
entityIds = entityIds.filter(
|
||||
(eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
|
||||
);
|
||||
}
|
||||
|
||||
let entities = entityIds.sort().map((key) => hass.states[key]);
|
||||
|
||||
if (entityFilter) {
|
||||
entities = entities.filter(entityFilter);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
_computeStateName(state) {
|
||||
return computeStateName(state);
|
||||
}
|
||||
|
||||
_openedChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this._hass = this.hass;
|
||||
}
|
||||
}
|
||||
|
||||
_hassChanged(newVal) {
|
||||
if (!this.opened) {
|
||||
this._hass = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
_computeToggleIcon(opened) {
|
||||
return opened ? "hass:menu-up" : "hass:menu-down";
|
||||
}
|
||||
|
||||
_fireChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
this.fire("change");
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-picker", HaEntityPicker);
|
||||
200
src/components/entity/ha-entity-picker.ts
Normal file
200
src/components/entity/ha-entity-picker.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import "./state-badge";
|
||||
|
||||
import computeStateName from "../../common/entity/compute_state_name";
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: HassEntity }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-icon-item {
|
||||
margin: -10px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-icon-item>
|
||||
<state-badge state-obj="[[item]]" slot="item-icon"></state-badge>
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[_computeStateName(item)]]</div>
|
||||
<div secondary>[[item.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector("state-badge")!.stateObj = model.item;
|
||||
root.querySelector(".name")!.textContent = computeStateName(model.item);
|
||||
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
|
||||
};
|
||||
|
||||
class HaEntityPicker extends LitElement {
|
||||
@property({ type: Boolean }) public autofocus?: boolean;
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
@property({ type: Boolean }) public allowCustomEntity;
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public label?: string;
|
||||
@property() public value?: string;
|
||||
@property() public domainFilter?: string;
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
@property({ type: Boolean }) private _opened?: boolean;
|
||||
@property() private _hass?: HomeAssistant;
|
||||
|
||||
private _getStates = memoizeOne(
|
||||
(
|
||||
hass: this["hass"],
|
||||
domainFilter: this["domainFilter"],
|
||||
entityFilter: this["entityFilter"]
|
||||
) => {
|
||||
let states: HassEntity[] = [];
|
||||
|
||||
if (!hass) {
|
||||
return [];
|
||||
}
|
||||
let entityIds = Object.keys(hass.states);
|
||||
|
||||
if (domainFilter) {
|
||||
entityIds = entityIds.filter(
|
||||
(eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
|
||||
);
|
||||
}
|
||||
|
||||
states = entityIds.sort().map((key) => hass!.states[key]);
|
||||
|
||||
if (entityFilter) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
// We always want to include the entity of the current value
|
||||
stateObj.entity_id === this.value || entityFilter!(stateObj)
|
||||
);
|
||||
}
|
||||
return states;
|
||||
}
|
||||
);
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("hass") && !this._opened) {
|
||||
this._hass = this.hass;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const states = this._getStates(
|
||||
this._hass,
|
||||
this.domainFilter,
|
||||
this.entityFilter
|
||||
);
|
||||
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="entity_id"
|
||||
item-label-path="entity_id"
|
||||
.items=${states}
|
||||
.value=${this._value}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-input
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label === undefined && this._hass
|
||||
? this._hass.localize("ui.components.entity.entity-picker.entity")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.disabled=${this.disabled}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<paper-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</paper-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${states.length > 0
|
||||
? html`
|
||||
<paper-icon-button
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</paper-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this._value) {
|
||||
this.value = ev.detail.value;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > paper-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-picker", HaEntityPicker);
|
||||
Reference in New Issue
Block a user