mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 04:36:36 +00:00
Merge pull request #7875 from home-assistant/dev
This commit is contained in:
commit
b7ccf3e0e5
@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [
|
||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||
media_artist: "Technohead",
|
||||
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
|
||||
supported_features: 195135,
|
||||
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||
supported_features: 64063,
|
||||
entity_picture: "/images/album_cover_2.jpg",
|
||||
media_duration: 300,
|
||||
media_position: 50,
|
||||
@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [
|
||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||
media_artist: "Technohead",
|
||||
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||
supported_features: 64063,
|
||||
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
|
||||
supported_features: 195135,
|
||||
entity_picture: "/images/album_cover.jpg",
|
||||
media_duration: 300,
|
||||
media_position: 0,
|
||||
|
@ -146,6 +146,16 @@ const CONFIGS = [
|
||||
entity: media_player.receiver_off
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Grid Full Size",
|
||||
config: `
|
||||
- type: grid
|
||||
columns: 1
|
||||
cards:
|
||||
- type: media-control
|
||||
entity: media_player.music_paused
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoHuiMediControlCard extends PolymerElement {
|
||||
|
@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement {
|
||||
"Supervisor",
|
||||
this.supervisor.supervisor,
|
||||
"hassio/supervisor/update",
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${
|
||||
this.supervisor.supervisor.version_latest
|
||||
}`
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
|
||||
)}
|
||||
${this.supervisor.host.features.includes("hassos")
|
||||
? this._renderUpdateCard(
|
||||
|
@ -137,8 +137,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
)}
|
||||
${this._interface?.type === "wireless"
|
||||
? html`
|
||||
<ha-expansion-panel outlined>
|
||||
<span slot="title">Wi-Fi</span>
|
||||
<ha-expansion-panel header="Wi-Fi" outlined>
|
||||
${this._interface?.wifi?.ssid
|
||||
? html`<p>Connected to: ${this._interface?.wifi?.ssid}</p>`
|
||||
: ""}
|
||||
@ -281,8 +280,10 @@ export class DialogHassioNetwork extends LitElement
|
||||
|
||||
private _renderIPConfiguration(version: string) {
|
||||
return html`
|
||||
<ha-expansion-panel outlined>
|
||||
<span slot="title">IPv${version.charAt(version.length - 1)}</span>
|
||||
<ha-expansion-panel
|
||||
.header=${`IPv${version.charAt(version.length - 1)}`}
|
||||
outlined
|
||||
>
|
||||
<div class="radio-row">
|
||||
<ha-formfield label="DHCP">
|
||||
<ha-radio
|
||||
@ -591,6 +592,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
}
|
||||
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
paper-input {
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20201126.0",
|
||||
version="20201202.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
6
src/common/ensure-array.ts
Normal file
6
src/common/ensure-array.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const ensureArray = (value?: any) => {
|
||||
if (!value || Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
return [value];
|
||||
};
|
@ -67,6 +67,10 @@ export const computeStateDisplay = (
|
||||
}
|
||||
}
|
||||
|
||||
if (domain === "counter") {
|
||||
return formatNumber(compareState, language);
|
||||
}
|
||||
|
||||
return (
|
||||
// Return device class translation
|
||||
(stateObj.attributes.device_class &&
|
||||
|
@ -139,7 +139,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _filteredDevices: DeviceRegistryEntry[] = [];
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
private _getAreasWithDevices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
@ -277,7 +277,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
if (!this._devices || !this._areas || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
const areas = this._getDevices(
|
||||
const areas = this._getAreasWithDevices(
|
||||
this._devices,
|
||||
this._areas,
|
||||
this._entities,
|
||||
|
@ -111,6 +111,18 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
@ -126,14 +138,17 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
|
||||
if (includeDomains || excludeDomains || includeDeviceClasses) {
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
@ -141,7 +156,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
let inputDevices = [...devices];
|
||||
let inputDevices = devices.filter(
|
||||
(device) => device.id === this.value || !device.disabled_by
|
||||
);
|
||||
|
||||
if (includeDomains) {
|
||||
inputDevices = inputDevices.filter((device) => {
|
||||
|
@ -101,6 +101,18 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _initedStates = false;
|
||||
|
||||
private _states: HassEntity[] = [];
|
||||
|
@ -28,6 +28,18 @@ import {
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
DeviceEntityLookup,
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../data/entity_registry";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
@ -68,39 +80,227 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public _areas?: AreaRegistryEntry[];
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
public noAdd?: boolean;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show no areas with entities of these domains.
|
||||
* @type {Array}
|
||||
* @attr exclude-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only areas with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||
|
||||
@internalProperty() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@internalProperty() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@internalProperty() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@internalProperty() private _opened?: boolean;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this._areas = this.noAdd
|
||||
? areas
|
||||
: [
|
||||
...areas,
|
||||
{
|
||||
area_id: "add_new",
|
||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||
},
|
||||
];
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _getAreas = memoizeOne(
|
||||
(
|
||||
areas: AreaRegistryEntry[],
|
||||
devices: DeviceRegistryEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
deviceFilter: this["deviceFilter"],
|
||||
entityFilter: this["entityFilter"],
|
||||
noAdd: this["noAdd"]
|
||||
): AreaRegistryEntry[] => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
let inputDevices: DeviceRegistryEntry[] | undefined;
|
||||
let inputEntities: EntityRegistryEntry[] | undefined;
|
||||
|
||||
if (includeDomains || excludeDomains || includeDeviceClasses) {
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
inputDevices = devices;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
} else if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
} else if (entityFilter) {
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) =>
|
||||
includeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) =>
|
||||
includeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeDomains) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return true;
|
||||
}
|
||||
return entities.every(
|
||||
(entity) =>
|
||||
!excludeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
});
|
||||
inputEntities = inputEntities!.filter(
|
||||
(entity) => !excludeDomains.includes(computeDomain(entity.entity_id))
|
||||
);
|
||||
}
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
inputDevices = inputDevices!.filter((device) => {
|
||||
const devEntities = deviceEntityLookup[device.id];
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
return deviceEntityLookup[device.id].some((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
});
|
||||
inputEntities = inputEntities!.filter((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceFilter) {
|
||||
inputDevices = inputDevices!.filter((device) => deviceFilter!(device));
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
entities = entities.filter((entity) => entityFilter!(entity));
|
||||
}
|
||||
|
||||
let outputAreas = areas;
|
||||
|
||||
let areaIds: string[] | undefined;
|
||||
|
||||
if (inputDevices) {
|
||||
areaIds = inputDevices
|
||||
.filter((device) => device.area_id)
|
||||
.map((device) => device.area_id!);
|
||||
}
|
||||
|
||||
if (inputEntities) {
|
||||
areaIds = (areaIds ?? []).concat(
|
||||
inputEntities
|
||||
.filter((entity) => entity.area_id)
|
||||
.map((entity) => entity.area_id!)
|
||||
);
|
||||
}
|
||||
|
||||
if (areaIds) {
|
||||
outputAreas = areas.filter((area) => areaIds!.includes(area.area_id));
|
||||
}
|
||||
|
||||
return noAdd
|
||||
? outputAreas
|
||||
: [
|
||||
...outputAreas,
|
||||
{
|
||||
area_id: "add_new",
|
||||
name: this.hass.localize("ui.components.area-picker.add_new"),
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._areas) {
|
||||
if (!this._devices || !this._areas || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
const areas = this._getAreas(
|
||||
this._areas,
|
||||
this._devices,
|
||||
this._entities,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter,
|
||||
this.entityFilter,
|
||||
this.noAdd
|
||||
);
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="area_id"
|
||||
item-id-path="area_id"
|
||||
item-label-path="name"
|
||||
.items=${this._areas}
|
||||
.items=${areas}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@ -110,6 +310,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.area-picker.area")
|
||||
: this.label}
|
||||
.placeholder=${this.placeholder
|
||||
? this._area(this.placeholder)?.name
|
||||
: undefined}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
@ -132,7 +335,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this._areas.length > 0
|
||||
${areas.length > 0
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
@ -151,6 +354,12 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _area = memoizeOne((areaId: string):
|
||||
| AreaRegistryEntry
|
||||
| undefined => {
|
||||
return this._areas?.find((area) => area.area_id === areaId);
|
||||
});
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue("");
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { ToggleButton } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
|
||||
@customElement("ha-button-toggle-group")
|
||||
export class HaButtonToggleGroup extends LitElement {
|
||||
@ -21,17 +22,22 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
${this.buttons.map(
|
||||
(button) => html`
|
||||
<mwc-icon-button
|
||||
.label=${button.label}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
${this.buttons.map((button) =>
|
||||
button.iconPath
|
||||
? html`<mwc-icon-button
|
||||
.label=${button.label}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
|
||||
</mwc-icon-button>`
|
||||
: html`<mwc-button
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>${button.label}</mwc-button
|
||||
>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
@ -49,13 +55,15 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||
}
|
||||
mwc-icon-button {
|
||||
mwc-icon-button,
|
||||
mwc-button {
|
||||
border: 1px solid var(--primary-color);
|
||||
border-right-width: 0px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
mwc-icon-button::before {
|
||||
mwc-icon-button::before,
|
||||
mwc-button::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@ -67,17 +75,21 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
content: "";
|
||||
transition: opacity 15ms linear, background-color 15ms linear;
|
||||
}
|
||||
mwc-icon-button[active]::before {
|
||||
mwc-icon-button[active]::before,
|
||||
mwc-button[active]::before {
|
||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||
}
|
||||
mwc-icon-button:first-child {
|
||||
mwc-icon-button:first-child,
|
||||
mwc-button:first-child {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
mwc-icon-button:last-child {
|
||||
mwc-icon-button:last-child,
|
||||
mwc-button:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
mwc-icon-button:only-child {
|
||||
mwc-icon-button:only-child,
|
||||
mwc-button:only-child {
|
||||
border-radius: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
@ -19,12 +19,14 @@ class HaExpansionPanel extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) outlined = false;
|
||||
|
||||
@property() header?: string;
|
||||
|
||||
@query(".container") private _container!: HTMLDivElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="summary" @click=${this._toggleContainer}>
|
||||
<slot name="title"></slot>
|
||||
<slot name="header">${this.header}</slot>
|
||||
<ha-svg-icon
|
||||
.path=${mdiChevronDown}
|
||||
class="summary-icon ${classMap({ expanded: this.expanded })}"
|
||||
@ -76,7 +78,7 @@ class HaExpansionPanel extends LitElement {
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
padding: var(--expansion-panel-summary-padding, 0px 16px);
|
||||
padding: var(--expansion-panel-summary-padding, 0);
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
20
src/components/ha-fab.ts
Normal file
20
src/components/ha-fab.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { Fab } from "@material/mwc-fab";
|
||||
import "@material/mwc-fab";
|
||||
import { customElement } from "lit-element";
|
||||
import { Constructor } from "../types";
|
||||
|
||||
const MwcFab = customElements.get("mwc-fab") as Constructor<Fab>;
|
||||
|
||||
@customElement("ha-fab")
|
||||
export class HaFab extends MwcFab {
|
||||
protected firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-fab": HaFab;
|
||||
}
|
||||
}
|
@ -60,8 +60,9 @@ export class HaIconInput extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon {
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
45
src/components/ha-selector/ha-selector-action.ts
Normal file
45
src/components/ha-selector/ha-selector-action.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { ActionSelector } from "../../data/selector";
|
||||
import { Action } from "../../data/script";
|
||||
import "../../panels/config/automation/action/ha-automation-action";
|
||||
|
||||
@customElement("ha-selector-action")
|
||||
export class HaActionSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: ActionSelector;
|
||||
|
||||
@property() public value?: Action;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-automation-action
|
||||
.actions=${this.value || []}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-automation-action {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-action": HaActionSelector;
|
||||
}
|
||||
}
|
@ -1,7 +1,16 @@
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { AreaSelector } from "../../data/selector";
|
||||
import "../ha-area-picker";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
|
||||
@customElement("ha-selector-area")
|
||||
export class HaAreaSelector extends LitElement {
|
||||
@ -13,14 +22,76 @@ export class HaAreaSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@internalProperty() public _configEntries?: ConfigEntry[];
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (
|
||||
oldSelector !== this.selector &&
|
||||
this.selector.area.device?.integration
|
||||
) {
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
no-add
|
||||
.deviceFilter=${(device) => this._filterDevices(device)}
|
||||
.entityFilter=${(entity) => this._filterEntities(entity)}
|
||||
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
||||
? [this.selector.area.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.area.entity?.domain
|
||||
? [this.selector.area.entity.domain]
|
||||
: undefined}
|
||||
></ha-area-picker>`;
|
||||
}
|
||||
|
||||
private _filterEntities(entity: EntityRegistryEntry): boolean {
|
||||
if (this.selector.area.entity?.integration) {
|
||||
if (entity.platform !== this.selector.area.entity.integration) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
||||
if (
|
||||
this.selector.area.device?.manufacturer &&
|
||||
device.manufacturer !== this.selector.area.device.manufacturer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.selector.area.device?.model &&
|
||||
device.model !== this.selector.area.device.model
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.selector.area.device?.integration) {
|
||||
if (
|
||||
!this._configEntries?.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) => entry.domain === this.selector.area.device?.integration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -19,7 +19,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public selector!: EntitySelector;
|
||||
|
||||
@internalProperty() private _entities?: Record<string, string>;
|
||||
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@ -45,7 +45,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
entityLookup[confEnt.entity_id] = confEnt.platform;
|
||||
}
|
||||
this._entities = entityLookup;
|
||||
this._entityPlaformLookup = entityLookup;
|
||||
}),
|
||||
];
|
||||
}
|
||||
@ -66,8 +66,9 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
if (this.selector.entity.integration) {
|
||||
if (
|
||||
!this._entities ||
|
||||
this._entities[entity.entity_id] !== this.selector.entity.integration
|
||||
!this._entityPlaformLookup ||
|
||||
this._entityPlaformLookup[entity.entity_id] !==
|
||||
this.selector.entity.integration
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
153
src/components/ha-selector/ha-selector-target.ts
Normal file
153
src/components/ha-selector/ha-selector-target.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { TargetSelector } from "../../data/selector";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import "../ha-target-picker";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { Target } from "../../data/target";
|
||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||
import "@material/mwc-tab/mwc-tab";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
|
||||
@customElement("ha-selector-target")
|
||||
export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: TargetSelector;
|
||||
|
||||
@property() public value?: Target;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@internalProperty() private _entityPlaformLookup?: Record<string, string>;
|
||||
|
||||
@internalProperty() private _configEntries?: ConfigEntry[];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
const entityLookup = {};
|
||||
for (const confEnt of entities) {
|
||||
if (!confEnt.platform) {
|
||||
continue;
|
||||
}
|
||||
entityLookup[confEnt.entity_id] = confEnt.platform;
|
||||
}
|
||||
this._entityPlaformLookup = entityLookup;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (
|
||||
oldSelector !== this.selector &&
|
||||
this.selector.target.device?.integration
|
||||
) {
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.deviceFilter=${(device) => this._filterDevices(device)}
|
||||
.entityRegFilter=${(entity: EntityRegistryEntry) =>
|
||||
this._filterRegEntities(entity)}
|
||||
.entityFilter=${(entity: HassEntity) => this._filterEntities(entity)}
|
||||
.includeDeviceClasses=${this.selector.target.entity?.device_class
|
||||
? [this.selector.target.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.target.entity?.domain
|
||||
? [this.selector.target.entity.domain]
|
||||
: undefined}
|
||||
></ha-target-picker>`;
|
||||
}
|
||||
|
||||
private _filterEntities(entity: HassEntity): boolean {
|
||||
if (this.selector.target.entity?.integration) {
|
||||
if (
|
||||
!this._entityPlaformLookup ||
|
||||
this._entityPlaformLookup[entity.entity_id] !==
|
||||
this.selector.target.entity.integration
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _filterRegEntities(entity: EntityRegistryEntry): boolean {
|
||||
if (this.selector.target.entity?.integration) {
|
||||
if (entity.platform !== this.selector.target.entity.integration) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
||||
if (
|
||||
this.selector.target.device?.manufacturer &&
|
||||
device.manufacturer !== this.selector.target.device.manufacturer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.selector.target.device?.model &&
|
||||
device.model !== this.selector.target.device.model
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.selector.target.device?.integration) {
|
||||
if (
|
||||
!this._configEntries?.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) => entry.domain === this.selector.target.device?.integration
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-target-picker {
|
||||
margin: 0 -8px;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-target": HaTargetSelector;
|
||||
}
|
||||
}
|
@ -5,9 +5,11 @@ import { HomeAssistant } from "../../types";
|
||||
import "./ha-selector-entity";
|
||||
import "./ha-selector-device";
|
||||
import "./ha-selector-area";
|
||||
import "./ha-selector-target";
|
||||
import "./ha-selector-number";
|
||||
import "./ha-selector-boolean";
|
||||
import "./ha-selector-time";
|
||||
import "./ha-selector-action";
|
||||
import { Selector } from "../../data/selector";
|
||||
|
||||
@customElement("ha-selector")
|
||||
|
595
src/components/ha-target-picker.ts
Normal file
595
src/components/ha-target-picker.ts
Normal file
@ -0,0 +1,595 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
unsafeCSS,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../types";
|
||||
// @ts-ignore
|
||||
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
|
||||
import {
|
||||
mdiSofa,
|
||||
mdiDevices,
|
||||
mdiClose,
|
||||
mdiPlus,
|
||||
mdiUnfoldMoreVertical,
|
||||
} from "@mdi/js";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-icon";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../data/entity_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../common/entity/state_icon";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { Target } from "../data/target";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import "./entity/ha-entity-picker";
|
||||
import "./device/ha-device-picker";
|
||||
import "./ha-area-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: Target;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
/**
|
||||
* Show only targets with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only targets with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property() public entityRegFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
||||
|
||||
@internalProperty() private _devices?: {
|
||||
[deviceId: string]: DeviceRegistryEntry;
|
||||
};
|
||||
|
||||
@internalProperty() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@internalProperty() private _addMode?: "area_id" | "entity_id" | "device_id";
|
||||
|
||||
@query("#input") private _inputElement?;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
this._areas = areaLookup;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
this._devices = deviceLookup;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="mdc-chip-set items">
|
||||
${ensureArray(this.value?.area_id)?.map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
mdiSofa
|
||||
);
|
||||
})}
|
||||
${ensureArray(this.value?.device_id)?.map((device_id) => {
|
||||
const device = this._devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device?.name || device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})}
|
||||
${ensureArray(this.value?.entity_id)?.map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity ? stateIcon(entity) : undefined
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
${this._renderPicker()}
|
||||
<div class="mdc-chip-set">
|
||||
<div
|
||||
class="mdc-chip area_id add"
|
||||
.type=${"area_id"}
|
||||
@click=${this._showPicker}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_area_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mdc-chip device_id add"
|
||||
.type=${"device_id"}
|
||||
@click=${this._showPicker}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_device_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mdc-chip entity_id add"
|
||||
.type=${"entity_id"}
|
||||
@click=${this._showPicker}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_entity_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private async _showPicker(ev) {
|
||||
this._addMode = ev.currentTarget.type;
|
||||
await this.updateComplete;
|
||||
setTimeout(() => {
|
||||
this._inputElement?.open();
|
||||
this._inputElement?.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private _renderChip(
|
||||
type: string,
|
||||
id: string,
|
||||
name: string,
|
||||
icon?: string,
|
||||
iconPath?: string
|
||||
) {
|
||||
return html`
|
||||
<div
|
||||
class="mdc-chip ${classMap({
|
||||
[type]: true,
|
||||
})}"
|
||||
>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${icon
|
||||
? html`<ha-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.icon=${icon}
|
||||
></ha-icon>`
|
||||
: ""}
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text">${name}</span>
|
||||
</span>
|
||||
</span>
|
||||
${type === "entity_id"
|
||||
? ""
|
||||
: html` <span role="gridcell">
|
||||
<mwc-icon-button
|
||||
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${"Expand"}
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleExpand}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiUnfoldMoreVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip class="expand" animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.expand_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>`}
|
||||
<span role="gridcell">
|
||||
<mwc-icon-button
|
||||
class="mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${"Remove"}
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleRemove}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPicker() {
|
||||
switch (this._addMode) {
|
||||
case "area_id":
|
||||
return html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"area_id"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_area_id"
|
||||
)}
|
||||
no-add
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-area-picker>`;
|
||||
case "device_id":
|
||||
return html`<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"device_id"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_device_id"
|
||||
)}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-device-picker>`;
|
||||
case "entity_id":
|
||||
return html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
id="input"
|
||||
.type=${"entity_id"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_entity_id"
|
||||
)}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-entity-picker>`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
private _targetPicked(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
const value = ev.detail.value;
|
||||
const target = ev.currentTarget;
|
||||
target.value = "";
|
||||
this._addMode = undefined;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.value
|
||||
? {
|
||||
...this.value,
|
||||
[target.type]: this.value[target.type]
|
||||
? [...ensureArray(this.value[target.type]), value]
|
||||
: value,
|
||||
}
|
||||
: { [target.type]: value },
|
||||
});
|
||||
}
|
||||
|
||||
private _handleExpand(ev) {
|
||||
const target = ev.currentTarget as any;
|
||||
const newDevices: string[] = [];
|
||||
const newEntities: string[] = [];
|
||||
if (target.type === "area_id") {
|
||||
Object.values(this._devices!).forEach((device) => {
|
||||
if (
|
||||
device.area_id === target.id &&
|
||||
!this.value!.device_id?.includes(device.id) &&
|
||||
this._deviceMeetsFilter(device)
|
||||
) {
|
||||
newDevices.push(device.id);
|
||||
}
|
||||
});
|
||||
this._entities!.forEach((entity) => {
|
||||
if (
|
||||
entity.area_id === target.id &&
|
||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||
this._entityRegMeetsFilter(entity)
|
||||
) {
|
||||
newEntities.push(entity.entity_id);
|
||||
}
|
||||
});
|
||||
} else if (target.type === "device_id") {
|
||||
this._entities!.forEach((entity) => {
|
||||
if (
|
||||
entity.device_id === target.id &&
|
||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||
this._entityRegMeetsFilter(entity)
|
||||
) {
|
||||
newEntities.push(entity.entity_id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let value = this.value;
|
||||
if (newEntities.length) {
|
||||
value = this._addItems(value, "entity_id", newEntities);
|
||||
}
|
||||
if (newDevices.length) {
|
||||
value = this._addItems(value, "device_id", newDevices);
|
||||
}
|
||||
value = this._removeItem(value, target.type, target.id);
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _handleRemove(ev) {
|
||||
const target = ev.currentTarget as any;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this._removeItem(this.value, target.type, target.id),
|
||||
});
|
||||
}
|
||||
|
||||
private _addItems(
|
||||
value: this["value"],
|
||||
type: string,
|
||||
ids: string[]
|
||||
): this["value"] {
|
||||
return {
|
||||
...value,
|
||||
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
|
||||
};
|
||||
}
|
||||
|
||||
private _removeItem(
|
||||
value: this["value"],
|
||||
type: string,
|
||||
id: string
|
||||
): this["value"] {
|
||||
return {
|
||||
...value,
|
||||
[type]: ensureArray(value![type])!.filter((val) => val !== id),
|
||||
};
|
||||
}
|
||||
|
||||
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
|
||||
const devEntities = this._entities?.filter(
|
||||
(entity) => entity.device_id === device.id
|
||||
);
|
||||
if (this.includeDomains) {
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!devEntities.some((entity) =>
|
||||
this.includeDomains!.includes(computeDomain(entity.entity_id))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.includeDeviceClasses) {
|
||||
if (!devEntities || !devEntities.length) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!devEntities.some((entity) => {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
stateObj.attributes.device_class &&
|
||||
this.includeDeviceClasses!.includes(
|
||||
stateObj.attributes.device_class
|
||||
)
|
||||
);
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.deviceFilter) {
|
||||
return this.deviceFilter(device);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
|
||||
if (
|
||||
this.includeDomains &&
|
||||
!this.includeDomains.includes(computeDomain(entity.entity_id))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.includeDeviceClasses) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!stateObj.attributes.device_class ||
|
||||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.entityRegFilter) {
|
||||
return this.entityRegFilter(entity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.mdc-chip {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.items {
|
||||
z-index: 2;
|
||||
}
|
||||
.mdc-chip.add {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
.mdc-chip:not(.add) {
|
||||
cursor: default;
|
||||
}
|
||||
.mdc-chip mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
}
|
||||
.mdc-chip mwc-icon-button ha-svg-icon {
|
||||
border-radius: 50%;
|
||||
background: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-chip__icon.mdc-chip__icon--trailing {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
--mdc-icon-size: 14px;
|
||||
color: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip__icon--leading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: 20px;
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
margin-left: -14px !important;
|
||||
}
|
||||
.expand-btn {
|
||||
margin-right: 0;
|
||||
}
|
||||
.mdc-chip.area_id:not(.add) {
|
||||
border: 2px solid #fed6a4;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.area_id.add {
|
||||
background: #fed6a4;
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) {
|
||||
border: 2px solid #a8e1fb;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.device_id.add {
|
||||
background: #a8e1fb;
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) {
|
||||
border: 2px solid #d2e7b9;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.entity_id.add {
|
||||
background: #d2e7b9;
|
||||
}
|
||||
.mdc-chip:hover {
|
||||
z-index: 5;
|
||||
}
|
||||
paper-tooltip.expand {
|
||||
min-width: 200px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-target-picker": HaTargetPicker;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab/mwc-fab";
|
||||
import "../ha-fab";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
@ -170,7 +170,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
>
|
||||
${this._narrow && currentItem?.can_play
|
||||
? html`
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
mini
|
||||
.item=${currentItem}
|
||||
@click=${this._actionClicked}
|
||||
@ -185,7 +185,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@ -927,7 +927,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
transition: width 0.4s, height 0.4s, padding-bottom 0.4s;
|
||||
}
|
||||
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: absolute;
|
||||
--mdc-theme-secondary: var(--primary-color);
|
||||
bottom: -20px;
|
||||
@ -1011,7 +1011,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:host([scroll]) mwc-fab {
|
||||
:host([scroll]) ha-fab {
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
--mdc-fab-box-shadow: none;
|
||||
|
@ -6,7 +6,7 @@ import { navigate } from "../common/navigate";
|
||||
import { Context, HomeAssistant } from "../types";
|
||||
import { BlueprintInput } from "./blueprint";
|
||||
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import { Action } from "./script";
|
||||
import { Action, MODES } from "./script";
|
||||
|
||||
export interface AutomationEntity extends HassEntityBase {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
@ -26,7 +26,7 @@ export interface ManualAutomationConfig {
|
||||
trigger: Trigger[];
|
||||
condition?: Condition[];
|
||||
action: Action[];
|
||||
mode?: "single" | "restart" | "queued" | "parallel";
|
||||
mode?: typeof MODES[number];
|
||||
max?: number;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ export interface DeviceRegistryEntry {
|
||||
area_id?: string;
|
||||
name_by_user?: string;
|
||||
entry_type: "service" | null;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
@ -26,6 +27,7 @@ export interface DeviceEntityLookup {
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: string | null;
|
||||
name_by_user?: string | null;
|
||||
disabled_by?: string | null;
|
||||
}
|
||||
|
||||
export const fallbackDeviceName = (
|
||||
|
@ -10,6 +10,7 @@ export interface EntityRegistryEntry {
|
||||
platform: string;
|
||||
config_entry_id?: string;
|
||||
device_id?: string;
|
||||
area_id?: string;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
@ -29,6 +30,7 @@ export interface UpdateEntityRegistryEntryResult {
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
area_id?: string | null;
|
||||
disabled_by?: string | null;
|
||||
new_entity_id?: string;
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Condition, Trigger } from "./automation";
|
||||
|
||||
export const MODES = ["single", "restart", "queued", "parallel"];
|
||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||
export const MODES_MAX = ["queued", "parallel"];
|
||||
|
||||
export interface ScriptEntity extends HassEntityBase {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
last_triggered: string;
|
||||
mode: "single" | "restart" | "queued" | "parallel";
|
||||
mode: typeof MODES[number];
|
||||
current?: number;
|
||||
max?: number;
|
||||
};
|
||||
@ -23,7 +23,7 @@ export interface ScriptConfig {
|
||||
alias: string;
|
||||
sequence: Action[];
|
||||
icon?: string;
|
||||
mode?: "single" | "restart" | "queued" | "parallel";
|
||||
mode?: typeof MODES[number];
|
||||
max?: number;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,11 @@ export type Selector =
|
||||
| EntitySelector
|
||||
| DeviceSelector
|
||||
| AreaSelector
|
||||
| TargetSelector
|
||||
| NumberSelector
|
||||
| BooleanSelector
|
||||
| TimeSelector;
|
||||
| TimeSelector
|
||||
| ActionSelector;
|
||||
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
@ -19,13 +21,41 @@ export interface DeviceSelector {
|
||||
integration?: string;
|
||||
manufacturer?: string;
|
||||
model?: string;
|
||||
entity?: EntitySelector["entity"];
|
||||
entity?: {
|
||||
domain?: EntitySelector["entity"]["domain"];
|
||||
device_class?: EntitySelector["entity"]["device_class"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AreaSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
area: {};
|
||||
area: {
|
||||
entity?: {
|
||||
integration?: EntitySelector["entity"]["integration"];
|
||||
domain?: EntitySelector["entity"]["domain"];
|
||||
device_class?: EntitySelector["entity"]["device_class"];
|
||||
};
|
||||
device?: {
|
||||
integration?: DeviceSelector["device"]["integration"];
|
||||
manufacturer?: DeviceSelector["device"]["manufacturer"];
|
||||
model?: DeviceSelector["device"]["model"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface TargetSelector {
|
||||
target: {
|
||||
entity?: {
|
||||
integration?: EntitySelector["entity"]["integration"];
|
||||
domain?: EntitySelector["entity"]["domain"];
|
||||
device_class?: EntitySelector["entity"]["device_class"];
|
||||
};
|
||||
device?: {
|
||||
integration?: DeviceSelector["device"]["integration"];
|
||||
manufacturer?: DeviceSelector["device"]["manufacturer"];
|
||||
model?: DeviceSelector["device"]["model"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface NumberSelector {
|
||||
@ -47,3 +77,8 @@ export interface TimeSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
time: {};
|
||||
}
|
||||
|
||||
export interface ActionSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
action: {};
|
||||
}
|
||||
|
5
src/data/target.ts
Normal file
5
src/data/target.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Target {
|
||||
entity_id?: string[];
|
||||
device_id?: string[];
|
||||
area_id?: string[];
|
||||
}
|
@ -9,6 +9,7 @@ export const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN];
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string | null;
|
||||
name: string;
|
||||
is_owner: boolean;
|
||||
is_active: boolean;
|
||||
@ -19,6 +20,7 @@ export interface User {
|
||||
|
||||
export interface UpdateUserParams {
|
||||
name?: User["name"];
|
||||
is_active?: User["is_active"];
|
||||
group_ids?: User["group_ids"];
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ export interface ZHADevice {
|
||||
device_type: string;
|
||||
signature: any;
|
||||
neighbors: Neighbor[];
|
||||
pairing_status?: string;
|
||||
}
|
||||
|
||||
export interface Neighbor {
|
||||
@ -270,3 +271,23 @@ export const addGroup = (
|
||||
group_name: groupName,
|
||||
members: membersToAdd,
|
||||
});
|
||||
|
||||
export const INITIALIZED = "INITIALIZED";
|
||||
export const INTERVIEW_COMPLETE = "INTERVIEW_COMPLETE";
|
||||
export const CONFIGURED = "CONFIGURED";
|
||||
export const PAIRED = "PAIRED";
|
||||
export const INCOMPLETE_PAIRING_STATUSES = [
|
||||
PAIRED,
|
||||
CONFIGURED,
|
||||
INTERVIEW_COMPLETE,
|
||||
];
|
||||
|
||||
export const DEVICE_JOINED = "device_joined";
|
||||
export const RAW_DEVICE_INITIALIZED = "raw_device_initialized";
|
||||
export const DEVICE_FULLY_INITIALIZED = "device_fully_initialized";
|
||||
export const DEVICE_MESSAGE_TYPES = [
|
||||
DEVICE_JOINED,
|
||||
RAW_DEVICE_INITIALIZED,
|
||||
DEVICE_FULLY_INITIALIZED,
|
||||
];
|
||||
export const LOG_OUTPUT = "log_output";
|
||||
|
@ -46,7 +46,7 @@ export class HuiNotificationItemTemplate extends LitElement {
|
||||
}
|
||||
|
||||
.actions {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
border-top: 1px solid var(--divider-color, #e8e8e8);
|
||||
padding: 5px 16px;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
#ha-init-skeleton::before {
|
||||
display: block;
|
||||
content: "";
|
||||
height: 112px;
|
||||
height: 56px;
|
||||
background-color: #THEMEC;
|
||||
}
|
||||
html {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -124,7 +124,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
icon="hass:help-circle"
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.create_area"
|
||||
@ -133,7 +133,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
@click=${this._createArea}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export class HaWaitForTriggerAction extends LitElement
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${continue_on_timeout}
|
||||
.checked=${continue_on_timeout ?? true}
|
||||
@change=${this._continueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
|
@ -63,7 +63,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
|
||||
protected render() {
|
||||
const blueprint = this._blueprint;
|
||||
return html`<ha-config-section .isWide=${this.isWide}>
|
||||
return html`<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this.config.alias}</span> `
|
||||
: ""}
|
||||
@ -119,7 +119,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.header"
|
||||
@ -185,6 +185,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
></ha-selector>`
|
||||
: html`<paper-input
|
||||
.key=${key}
|
||||
required
|
||||
.value=${this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]}
|
||||
@value-changed=${this._inputChanged}
|
||||
@ -275,20 +276,9 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.errors {
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--error-color);
|
||||
}
|
||||
.padding {
|
||||
padding: 16px;
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.blueprint-picker-container {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
@ -312,24 +302,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 50%;
|
||||
width: 60%;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 50%;
|
||||
}
|
||||
mwc-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
mwc-fab.dirty {
|
||||
bottom: 0;
|
||||
}
|
||||
.selected_menu_item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
width: 60%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
@ -206,6 +206,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
? html`<blueprint-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
@ -213,6 +214,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
: html`<manual-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
@ -271,7 +273,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${classMap({ dirty: this._dirty })}
|
||||
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||
@ -279,7 +281,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
@click=${this._saveAutomation}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@ -524,21 +526,18 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ha-entity-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
mwc-fab.dirty {
|
||||
ha-fab.dirty {
|
||||
bottom: 0;
|
||||
}
|
||||
.selected_menu_item {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlus, mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
@ -170,7 +170,7 @@ class HaAutomationPicker extends LitElement {
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.add_automation"
|
||||
@ -179,7 +179,7 @@ class HaAutomationPicker extends LitElement {
|
||||
@click=${this._createNew}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -309,14 +309,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.errors {
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--error-color);
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
@ -326,20 +318,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
ha-entity-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
mwc-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
mwc-fab.dirty {
|
||||
bottom: 0;
|
||||
}
|
||||
.selected_menu_item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
@ -97,12 +96,11 @@ class DialogImportBlueprint extends LitElement {
|
||||
)}
|
||||
></paper-input>
|
||||
`}
|
||||
<ha-expansion-panel>
|
||||
<span slot="title"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.raw_blueprint"
|
||||
)}</span
|
||||
>
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.raw_blueprint"
|
||||
)}
|
||||
>
|
||||
<pre>${this._result.raw_data}</pre>
|
||||
</ha-expansion-panel>`
|
||||
: html`${this.hass.localize(
|
||||
@ -201,15 +199,8 @@ class DialogImportBlueprint extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles(): CSSResult {
|
||||
return haStyleDialog;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlus, mdiHelpCircle, mdiDelete, mdiRobot } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
@ -174,7 +174,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.add_blueprint"
|
||||
@ -183,7 +183,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
@click=${this._addBlueprint}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -31,7 +30,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
|
||||
@property() public entities!: EntityRegistryStateEntry[];
|
||||
|
||||
@internalProperty() private _showDisabled = false;
|
||||
@property() public showDisabled = false;
|
||||
|
||||
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
|
||||
|
||||
@ -68,7 +67,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
})}
|
||||
</div>
|
||||
${disabledEntities.length
|
||||
? !this._showDisabled
|
||||
? !this.showDisabled
|
||||
? html`
|
||||
<button
|
||||
class="show-more"
|
||||
@ -119,7 +118,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
}
|
||||
|
||||
private _toggleShowDisabled() {
|
||||
this._showDisabled = !this._showDisabled;
|
||||
this.showDisabled = !this.showDisabled;
|
||||
}
|
||||
|
||||
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
|
||||
@ -227,3 +226,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-device-entities-card": HaDeviceEntitiesCard;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
} from "../../../../data/device_registry";
|
||||
import { loadDeviceRegistryDetailDialog } from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { loadDeviceRegistryDetailDialog } from "../device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-device-info-card")
|
||||
|
@ -3,8 +3,8 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-area-picker";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-area-picker";
|
||||
|
||||
import {
|
||||
CSSResult,
|
||||
@ -18,11 +18,12 @@ import {
|
||||
} from "lit-element";
|
||||
|
||||
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { computeDeviceName } from "../../data/device_registry";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { computeDeviceName } from "../../../../data/device_registry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
|
||||
@customElement("dialog-device-registry-detail")
|
||||
class DialogDeviceRegistryDetail extends LitElement {
|
||||
@ -36,6 +37,8 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _areaId?: string;
|
||||
|
||||
@internalProperty() private _disabledBy!: string | null;
|
||||
|
||||
@internalProperty() private _submitting?: boolean;
|
||||
|
||||
public async showDialog(
|
||||
@ -45,6 +48,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
this._error = undefined;
|
||||
this._nameByUser = this._params.device.name_by_user || "";
|
||||
this._areaId = this._params.device.area_id;
|
||||
this._disabledBy = this._params.device.disabled_by;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@ -80,6 +84,32 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
.value=${this._areaId}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${!this._disabledBy}
|
||||
@change=${this._disabledByChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
<div>
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.devices.enabled_label")}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this._disabledBy && this._disabledBy !== "user"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.devices.enabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`config_entry.disabled_by.${this._disabledBy}`
|
||||
)
|
||||
)
|
||||
: ""}
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.enabled_description"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button
|
||||
@ -109,12 +139,17 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
this._areaId = event.detail.value;
|
||||
}
|
||||
|
||||
private _disabledByChanged(ev: Event): void {
|
||||
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
|
||||
}
|
||||
|
||||
private async _updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
try {
|
||||
await this._params!.updateEntry({
|
||||
name_by_user: this._nameByUser.trim() || null,
|
||||
area_id: this._areaId || null,
|
||||
disabled_by: this._disabledBy || null,
|
||||
});
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
@ -128,6 +163,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
@ -139,6 +175,15 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-switch {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
DeviceRegistryEntryMutableParams,
|
||||
} from "../../data/device_registry";
|
||||
} from "../../../../data/device_registry";
|
||||
|
||||
export interface DeviceRegistryDetailDialogParams {
|
||||
device: DeviceRegistryEntry;
|
@ -35,7 +35,7 @@ import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
@ -46,6 +46,7 @@ import "./device-detail/ha-device-entities-card";
|
||||
import "./device-detail/ha-device-info-card";
|
||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@ -246,6 +247,28 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
>
|
||||
${
|
||||
device.disabled_by
|
||||
? html`
|
||||
<div>
|
||||
<p class="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.enabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.devices.disabled_by.${device.disabled_by}`
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: html``
|
||||
}
|
||||
${this._renderIntegrationInfo(device, integrations)}
|
||||
</ha-device-info-card>
|
||||
|
||||
@ -255,6 +278,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.entities=${entities}
|
||||
.showDisabled=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
@ -272,9 +296,14 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
<ha-icon-button
|
||||
@click=${this._showAutomationDialog}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.create"
|
||||
)}
|
||||
.disabled=${device.disabled_by}
|
||||
title=${device.disabled_by
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.devices.automation.create_disabled"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.automation.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
@ -342,9 +371,16 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
<ha-icon-button
|
||||
@click=${this._createScene}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create"
|
||||
)}
|
||||
.disabled=${device.disabled_by}
|
||||
title=${
|
||||
device.disabled_by
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create_disabled"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create"
|
||||
)
|
||||
}
|
||||
icon="hass:plus-circle"
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
@ -415,9 +451,14 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
<ha-icon-button
|
||||
@click=${this._showScriptDialog}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.script.create"
|
||||
)}
|
||||
.disabled=${device.disabled_by}
|
||||
title=${device.disabled_by
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.devices.script.create_disabled"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.script.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
@ -632,128 +673,137 @@ export class HaConfigDevicePage extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: auto;
|
||||
max-width: 1000px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
private async _enableDevice(): Promise<void> {
|
||||
await updateDeviceRegistryEntry(this.hass, this.deviceId, {
|
||||
disabled_by: null,
|
||||
});
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: auto;
|
||||
max-width: 1000px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.card-header ha-icon-button {
|
||||
margin-right: -8px;
|
||||
color: var(--primary-color);
|
||||
height: auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
padding: 16px;
|
||||
}
|
||||
.card-header ha-icon-button {
|
||||
margin-right: -8px;
|
||||
color: var(--primary-color);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
}
|
||||
.device-info {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-headline_-_font-size);
|
||||
font-weight: var(--paper-font-headline_-_font-weight);
|
||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||
line-height: var(--paper-font-headline_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
.show-more {
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-headline_-_font-size);
|
||||
font-weight: var(--paper-font-headline_-_font-weight);
|
||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||
line-height: var(--paper-font-headline_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.column {
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
align-self: center;
|
||||
}
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.column {
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header-right img {
|
||||
height: 30px;
|
||||
}
|
||||
.header-right {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
}
|
||||
.header-right img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.header-right:first-child {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.header-right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-right > *:not(:first-child) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.header-right:first-child {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.battery {
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.header-right > *:not(:first-child) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.column > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.battery {
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
}
|
||||
.column > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
margin-top: 0;
|
||||
}
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
font-size: var(--paper-font-body1_-_font-size);
|
||||
}
|
||||
:host([narrow]) .container {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
paper-item.no-link {
|
||||
cursor: default;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
font-size: var(--paper-font-body1_-_font-size);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-item.no-link {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`;
|
||||
ha-card {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
ha-card a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { mdiPlus, mdiFilterVariant, mdiCancel } from "@mdi/js";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
@ -6,16 +11,20 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
DataTableRowData,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-button-menu";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
@ -32,6 +41,7 @@ import { domainToName } from "../../../data/integration";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
||||
interface DeviceRowData extends DeviceRegistryEntry {
|
||||
device?: DeviceRowData;
|
||||
@ -62,6 +72,12 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
window.location.search
|
||||
);
|
||||
|
||||
@internalProperty() private _showDisabled = false;
|
||||
|
||||
@internalProperty() private _filter = "";
|
||||
|
||||
@internalProperty() private _numHiddenDevices = 0;
|
||||
|
||||
private _activeFilters = memoizeOne(
|
||||
(
|
||||
entries: ConfigEntry[],
|
||||
@ -72,6 +88,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
filters.forEach((value, key) => {
|
||||
switch (key) {
|
||||
case "config_entry": {
|
||||
// If we are requested to show the devices for a given config entry,
|
||||
// also show the disabled ones by default.
|
||||
this._showDisabled = true;
|
||||
|
||||
const configEntry = entries.find(
|
||||
(entry) => entry.entry_id === value
|
||||
);
|
||||
@ -96,13 +116,14 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _devices = memoizeOne(
|
||||
private _devicesAndFilterDomains = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
filters: URLSearchParams,
|
||||
showDisabled: boolean,
|
||||
localize: LocalizeFunc
|
||||
) => {
|
||||
// Some older installations might have devices pointing at invalid entryIDs
|
||||
@ -115,6 +136,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
|
||||
// If nothing gets filtered, this is our correct count of devices
|
||||
let startLength = outputDevices.length;
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
@ -136,16 +160,25 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
const filterDomains: string[] = [];
|
||||
|
||||
filters.forEach((value, key) => {
|
||||
switch (key) {
|
||||
case "config_entry":
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.includes(value)
|
||||
);
|
||||
break;
|
||||
if (key === "config_entry") {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.includes(value)
|
||||
);
|
||||
startLength = outputDevices.length;
|
||||
const configEntry = entries.find((entry) => entry.entry_id === value);
|
||||
if (configEntry) {
|
||||
filterDomains.push(configEntry.domain);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!showDisabled) {
|
||||
outputDevices = outputDevices.filter((device) => !device.disabled_by);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
@ -176,16 +209,19 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
this._numHiddenDevices = startLength - outputDevices.length;
|
||||
return { devicesOutput: outputDevices, filteredDomains: filterDomains };
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer => {
|
||||
(narrow: boolean, showDisabled: boolean): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Device",
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.device"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
@ -271,6 +307,24 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
: html` - `;
|
||||
},
|
||||
};
|
||||
if (showDisabled) {
|
||||
columns.disabled_by = {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (disabled_by) =>
|
||||
disabled_by
|
||||
? html`<div
|
||||
tabindex="0"
|
||||
style="display:inline-block; position: relative;"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize("ui.panel.config.devices.disabled")}
|
||||
</paper-tooltip>
|
||||
</div>`
|
||||
: "",
|
||||
};
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
@ -286,6 +340,126 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const { devicesOutput, filteredDomains } = this._devicesAndFilterDomains(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this.hass.localize
|
||||
);
|
||||
const includeZHAFab = filteredDomains.includes("zha");
|
||||
const activeFilters = this._activeFilters(
|
||||
this.entries,
|
||||
this._searchParms,
|
||||
this.hass.localize
|
||||
);
|
||||
|
||||
const headerToolbar = html`
|
||||
<search-input
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize("ui.panel.config.devices.picker.search")}
|
||||
></search-input
|
||||
>${activeFilters
|
||||
? html`<div class="active-filters">
|
||||
${this.narrow
|
||||
? html` <div>
|
||||
<ha-icon icon="hass:filter-variant"></ha-icon>
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.filtering.filtering_by"
|
||||
)}
|
||||
${activeFilters.join(", ")}
|
||||
${this._numHiddenDevices
|
||||
? "(" +
|
||||
this.hass.localize(
|
||||
"ui.panel.config.devices.picker.filter.hidden_devices",
|
||||
"number",
|
||||
this._numHiddenDevices
|
||||
) +
|
||||
")"
|
||||
: ""}
|
||||
</paper-tooltip>
|
||||
</div>`
|
||||
: `${this.hass.localize(
|
||||
"ui.panel.config.filtering.filtering_by"
|
||||
)} ${activeFilters.join(", ")}
|
||||
${
|
||||
this._numHiddenDevices
|
||||
? "(" +
|
||||
this.hass.localize(
|
||||
"ui.panel.config.devices.picker.filter.hidden_devices",
|
||||
"number",
|
||||
this._numHiddenDevices
|
||||
) +
|
||||
")"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
<mwc-button @click=${this._clearFilter}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.filtering.clear"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
${this._numHiddenDevices && !activeFilters
|
||||
? html`<div class="active-filters">
|
||||
${this.narrow
|
||||
? html` <div>
|
||||
<ha-icon icon="hass:filter-variant"></ha-icon>
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.picker.filter.hidden_devices",
|
||||
"number",
|
||||
this._numHiddenDevices
|
||||
)}
|
||||
</paper-tooltip>
|
||||
</div>`
|
||||
: `${this.hass.localize(
|
||||
"ui.panel.config.devices.picker.filter.hidden_devices",
|
||||
"number",
|
||||
this._numHiddenDevices
|
||||
)}`}
|
||||
<mwc-button @click=${this._showAll}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.devices.picker.filter.show_all"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>`
|
||||
: ""}
|
||||
<ha-button-menu corner="BOTTOM_START" multi>
|
||||
<mwc-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.devices.picker.filter.filter"
|
||||
)}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.config.devices.picker.filter.filter"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item
|
||||
@request-selected="${this._showDisabledChanged}"
|
||||
graphic="control"
|
||||
.selected=${this._showDisabled}
|
||||
>
|
||||
<ha-checkbox
|
||||
slot="graphic"
|
||||
.checked=${this._showDisabled}
|
||||
></ha-checkbox>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.devices.picker.filter.show_disabled"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
@ -295,23 +469,33 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
: "/config"}
|
||||
.tabs=${configSections.integrations}
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._devices(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this._searchParms,
|
||||
this.hass.localize
|
||||
)}
|
||||
.activeFilters=${this._activeFilters(
|
||||
this.entries,
|
||||
this._searchParms,
|
||||
this.hass.localize
|
||||
)}
|
||||
.columns=${this._columns(this.narrow, this._showDisabled)}
|
||||
.data=${devicesOutput}
|
||||
.filter=${this._filter}
|
||||
@row-click=${this._handleRowClicked}
|
||||
clickable
|
||||
.hasFab=${includeZHAFab}
|
||||
>
|
||||
${includeZHAFab
|
||||
? html`<a href="/config/zha/add" slot="fab">
|
||||
<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||
extended
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</a>`
|
||||
: html``}
|
||||
<div
|
||||
class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})}
|
||||
slot="header"
|
||||
>
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
@ -342,6 +526,136 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
const deviceId = ev.detail.id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
}
|
||||
|
||||
private _showDisabledChanged(ev: CustomEvent<RequestSelectedDetail>) {
|
||||
if (ev.detail.source !== "property") {
|
||||
return;
|
||||
}
|
||||
this._showDisabled = ev.detail.selected;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _clearFilter() {
|
||||
navigate(this, window.location.pathname, true);
|
||||
}
|
||||
|
||||
private _showAll() {
|
||||
this._showDisabled = true;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
hass-loading-screen {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
--app-header-text-color: var(--sidebar-text-color);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-headline_-_font-size);
|
||||
font-weight: var(--paper-font-headline_-_font-weight);
|
||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||
line-height: var(--paper-font-headline_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
p {
|
||||
font-family: var(--paper-font-subhead_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-subhead_-_-webkit-font-smoothing
|
||||
);
|
||||
font-weight: var(--paper-font-subhead_-_font-weight);
|
||||
line-height: var(--paper-font-subhead_-_line-height);
|
||||
}
|
||||
ha-data-table {
|
||||
width: 100%;
|
||||
--data-table-border-width: 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-data-table {
|
||||
height: calc(100vh - 1px - var(--header-height));
|
||||
display: block;
|
||||
}
|
||||
ha-button-menu {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||
}
|
||||
search-input {
|
||||
margin-left: 16px;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.search-toolbar search-input {
|
||||
margin-left: 8px;
|
||||
top: 1px;
|
||||
}
|
||||
.search-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.search-toolbar ha-button-menu {
|
||||
position: static;
|
||||
}
|
||||
.selected-txt {
|
||||
font-weight: bold;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.table-header .selected-txt {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.search-toolbar .selected-txt {
|
||||
font-size: 16px;
|
||||
}
|
||||
.header-btns > mwc-button,
|
||||
.header-btns > ha-icon-button {
|
||||
margin: 8px;
|
||||
}
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 2px 2px 8px;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.active-filters ha-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.active-filters mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.active-filters::before {
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.12;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
content: "";
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -20,9 +20,16 @@ import {
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../../components/ha-area-picker";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
|
||||
@customElement("ha-registry-basic-editor")
|
||||
export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@ -31,16 +38,26 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
|
||||
@internalProperty() private _entityId!: string;
|
||||
|
||||
@internalProperty() private _areaId?: string;
|
||||
|
||||
@internalProperty() private _disabledBy!: string | null;
|
||||
|
||||
private _deviceLookup?: Record<string, DeviceRegistryEntry>;
|
||||
|
||||
@internalProperty() private _device?: DeviceRegistryEntry;
|
||||
|
||||
@internalProperty() private _submitting?: boolean;
|
||||
|
||||
public async updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
new_entity_id: this._entityId.trim(),
|
||||
area_id: this._areaId || null,
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
if (
|
||||
this.entry.disabled_by !== this._disabledBy &&
|
||||
(this._disabledBy === null || this._disabledBy === "user")
|
||||
) {
|
||||
params.disabled_by = this._disabledBy;
|
||||
}
|
||||
try {
|
||||
@ -70,6 +87,20 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._deviceLookup = {};
|
||||
for (const device of devices) {
|
||||
this._deviceLookup[device.id] = device;
|
||||
}
|
||||
if (!this._device && this.entry.device_id) {
|
||||
this._device = this._deviceLookup[this.entry.device_id];
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("entry")) {
|
||||
@ -79,6 +110,11 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
this._areaId = this.entry.area_id;
|
||||
this._device =
|
||||
this.entry.device_id && this._deviceLookup
|
||||
? this._deviceLookup[this.entry.device_id]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +141,12 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
.placeholder=${this._device?.area_id}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${!this._disabledBy}
|
||||
@ -139,6 +181,10 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _areaPicked(ev: CustomEvent) {
|
||||
this._areaId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -31,9 +31,18 @@ import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import "../../../components/ha-area-picker";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../data/device_registry";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
export class EntityRegistrySettings extends LitElement {
|
||||
export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@ -44,14 +53,34 @@ export class EntityRegistrySettings extends LitElement {
|
||||
|
||||
@internalProperty() private _entityId!: string;
|
||||
|
||||
@internalProperty() private _areaId?: string | null;
|
||||
|
||||
@internalProperty() private _disabledBy!: string | null;
|
||||
|
||||
private _deviceLookup?: Record<string, DeviceRegistryEntry>;
|
||||
|
||||
@internalProperty() private _device?: DeviceRegistryEntry;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _submitting?: boolean;
|
||||
|
||||
private _origEntityId!: string;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._deviceLookup = {};
|
||||
for (const device of devices) {
|
||||
this._deviceLookup[device.id] = device;
|
||||
}
|
||||
if (this.entry.device_id) {
|
||||
this._device = this._deviceLookup[this.entry.device_id];
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entry")) {
|
||||
@ -59,8 +88,13 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._name = this.entry.name || "";
|
||||
this._icon = this.entry.icon || "";
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._areaId = this.entry.area_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
this._device =
|
||||
this.entry.device_id && this._deviceLookup
|
||||
? this._deviceLookup[this.entry.device_id]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,10 +111,19 @@ export class EntityRegistrySettings extends LitElement {
|
||||
return html`
|
||||
${!stateObj
|
||||
? html`
|
||||
<div class="container">
|
||||
<div class="container warning">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.entity_registry.editor.unavailable"
|
||||
)}
|
||||
${this._device?.disabled_by
|
||||
? html`<br />${this.hass!.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_disabled"
|
||||
)}<br /><mwc-button @click=${this._openDeviceSettings}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.entity_registry.editor.open_device_settings"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@ -117,9 +160,17 @@ export class EntityRegistrySettings extends LitElement {
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
${!this.entry.device_id
|
||||
? html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>`
|
||||
: ""}
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${!this._disabledBy}
|
||||
.disabled=${this._device?.disabled_by}
|
||||
@change=${this._disabledByChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
@ -148,6 +199,31 @@ export class EntityRegistrySettings extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.entry.device_id
|
||||
? html`<ha-expansion-panel .header=${"Advanced"}>
|
||||
<p>
|
||||
By default the entities of a device are in the same area as the
|
||||
device. If you change the area of this entity, it will no longer
|
||||
follow the area of the device.
|
||||
</p>
|
||||
${this._areaId
|
||||
? html`<mwc-button @click=${this._clearArea}
|
||||
>Follow device area</mwc-button
|
||||
>`
|
||||
: this._device
|
||||
? html`<mwc-button @click=${this._openDeviceSettings}
|
||||
>Change device area</mwc-button
|
||||
>`
|
||||
: ""}
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
.placeholder=${this._device?.area_id}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker
|
||||
></ha-expansion-panel>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button
|
||||
@ -183,14 +259,37 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _areaPicked(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
this._areaId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _clearArea() {
|
||||
this._error = undefined;
|
||||
this._areaId = null;
|
||||
}
|
||||
|
||||
private _openDeviceSettings() {
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
device: this._device!,
|
||||
updateEntry: async (updates) => {
|
||||
await updateDeviceRegistryEntry(this.hass, this._device!.id, updates);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
name: this._name.trim() || null,
|
||||
icon: this._icon.trim() || null,
|
||||
area_id: this._areaId || null,
|
||||
new_entity_id: this._entityId.trim(),
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
if (
|
||||
this.entry.disabled_by !== this._disabledBy &&
|
||||
(this._disabledBy === null || this._disabledBy === "user")
|
||||
) {
|
||||
params.disabled_by = this._disabledBy;
|
||||
}
|
||||
try {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiFilterVariant } from "@mdi/js";
|
||||
import { mdiFilterVariant, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -62,6 +62,15 @@ import {
|
||||
} from "./show-dialog-entity-editor";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
|
||||
export interface StateEntity extends EntityRegistryEntry {
|
||||
readonly?: boolean;
|
||||
@ -73,6 +82,7 @@ export interface EntityRow extends StateEntity {
|
||||
unavailable: boolean;
|
||||
restored: boolean;
|
||||
status: string;
|
||||
area?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-config-entities")
|
||||
@ -87,6 +97,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@internalProperty() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@internalProperty() private _areas: AreaRegistryEntry[] = [];
|
||||
|
||||
@internalProperty() private _stateEntities: StateEntity[] = [];
|
||||
|
||||
@property() public _entries?: ConfigEntry[];
|
||||
@ -175,9 +189,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
? (name, entity: any) =>
|
||||
html`
|
||||
${name}<br />
|
||||
${entity.entity_id} |
|
||||
${this.hass.localize(`component.${entity.platform}.title`) ||
|
||||
entity.platform}
|
||||
<div class="secondary">
|
||||
${entity.entity_id} |
|
||||
${this.hass.localize(`component.${entity.platform}.title`) ||
|
||||
entity.platform}
|
||||
</div>
|
||||
`
|
||||
: undefined,
|
||||
},
|
||||
@ -201,6 +217,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
template: (platform) =>
|
||||
this.hass.localize(`component.${platform}.title`) || platform,
|
||||
},
|
||||
area: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entities.picker.headers.area"
|
||||
),
|
||||
sortable: true,
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
status: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entities.picker.headers.status"
|
||||
@ -252,48 +277,87 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
);
|
||||
|
||||
private _filteredEntities = memoize(
|
||||
private _filteredEntitiesAndDomains = memoize(
|
||||
(
|
||||
entities: EntityRegistryEntry[],
|
||||
devices: DeviceRegistryEntry[] | undefined,
|
||||
areas: AreaRegistryEntry[] | undefined,
|
||||
stateEntities: StateEntity[],
|
||||
filters: URLSearchParams,
|
||||
showDisabled: boolean,
|
||||
showUnavailable: boolean,
|
||||
showReadOnly: boolean
|
||||
): EntityRow[] => {
|
||||
showReadOnly: boolean,
|
||||
entries?: ConfigEntry[]
|
||||
) => {
|
||||
const result: EntityRow[] = [];
|
||||
|
||||
// If nothing gets filtered, this is our correct count of entities
|
||||
let startLength = entities.length + stateEntities.length;
|
||||
|
||||
entities = showReadOnly ? entities.concat(stateEntities) : entities;
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
|
||||
if (areas) {
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
if (devices) {
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entities.forEach((entity) => {
|
||||
return entity;
|
||||
});
|
||||
|
||||
let filteredEntities = showReadOnly
|
||||
? entities.concat(stateEntities)
|
||||
: entities;
|
||||
|
||||
const filteredDomains: string[] = [];
|
||||
|
||||
filters.forEach((value, key) => {
|
||||
switch (key) {
|
||||
case "config_entry":
|
||||
entities = entities.filter(
|
||||
if (key === "config_entry") {
|
||||
filteredEntities = filteredEntities.filter(
|
||||
(entity) => entity.config_entry_id === value
|
||||
);
|
||||
// If we have an active filter and `showReadOnly` is true, the length of `entities` is correct.
|
||||
// If however, the read-only entities were not added before, we need to check how many would
|
||||
// have matched the active filter and add that number to the count.
|
||||
startLength = filteredEntities.length;
|
||||
if (!showReadOnly) {
|
||||
startLength += stateEntities.filter(
|
||||
(entity) => entity.config_entry_id === value
|
||||
);
|
||||
// If we have an active filter and `showReadOnly` is true, the length of `entities` is correct.
|
||||
// If however, the read-only entities were not added before, we need to check how many would
|
||||
// have matched the active filter and add that number to the count.
|
||||
startLength = entities.length;
|
||||
if (!showReadOnly) {
|
||||
startLength += stateEntities.filter(
|
||||
(entity) => entity.config_entry_id === value
|
||||
).length;
|
||||
}
|
||||
break;
|
||||
).length;
|
||||
}
|
||||
|
||||
if (!entries) {
|
||||
this._loadConfigEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
const configEntry = entries.find((entry) => entry.entry_id === value);
|
||||
|
||||
if (configEntry) {
|
||||
filteredDomains.push(configEntry.domain);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!showDisabled) {
|
||||
entities = entities.filter((entity) => !entity.disabled_by);
|
||||
filteredEntities = filteredEntities.filter(
|
||||
(entity) => !entity.disabled_by
|
||||
);
|
||||
}
|
||||
|
||||
for (const entry of entities) {
|
||||
for (const entry of filteredEntities) {
|
||||
const entity = this.hass.states[entry.entity_id];
|
||||
const unavailable = entity?.state === UNAVAILABLE;
|
||||
const restored = entity?.attributes.restored;
|
||||
const areaId = entry.area_id ?? deviceLookup[entry.device_id!]?.area_id;
|
||||
const area = areaId ? areaLookup[areaId] : undefined;
|
||||
|
||||
if (!showUnavailable && unavailable) {
|
||||
continue;
|
||||
@ -309,6 +373,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
this.hass.localize("state.default.unavailable"),
|
||||
unavailable,
|
||||
restored,
|
||||
area: area ? area.name : undefined,
|
||||
status: restored
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.entities.picker.status.restored"
|
||||
@ -326,7 +391,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
this._numHiddenEntities = startLength - result.length;
|
||||
return result;
|
||||
return { filteredEntities: result, filteredDomains: filteredDomains };
|
||||
}
|
||||
);
|
||||
|
||||
@ -345,6 +410,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@ -370,15 +441,22 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
this._entries
|
||||
);
|
||||
|
||||
const entityData = this._filteredEntities(
|
||||
const {
|
||||
filteredEntities,
|
||||
filteredDomains,
|
||||
} = this._filteredEntitiesAndDomains(
|
||||
this._entities,
|
||||
this._devices,
|
||||
this._areas,
|
||||
this._stateEntities,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly
|
||||
this._showReadOnly,
|
||||
this._entries
|
||||
);
|
||||
|
||||
const includeZHAFab = filteredDomains.includes("zha");
|
||||
const headerToolbar = this._selectedEntities.length
|
||||
? html`
|
||||
<p class="selected-txt">
|
||||
@ -584,13 +662,14 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.integrations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${entityData}
|
||||
.data=${filteredEntities}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
clickable
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
.hasFab=${includeZHAFab}
|
||||
>
|
||||
<div
|
||||
class=${classMap({
|
||||
@ -601,6 +680,17 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${headerToolbar}
|
||||
</div>
|
||||
${includeZHAFab
|
||||
? html`<a href="/config/zha/add" slot="fab">
|
||||
<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||
extended
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</a>`
|
||||
: html``}
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
export class HaConfigSection extends LitElement {
|
||||
@property() public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public vertical = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
@ -16,8 +18,8 @@ export class HaConfigSection extends LitElement {
|
||||
<div
|
||||
class="together layout ${classMap({
|
||||
narrow: !this.isWide,
|
||||
vertical: !this.isWide,
|
||||
horizontal: this.isWide,
|
||||
vertical: this.vertical || !this.isWide,
|
||||
horizontal: !this.vertical && this.isWide,
|
||||
})}"
|
||||
>
|
||||
<div class="intro"><slot name="introduction"></slot></div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
@ -158,7 +158,7 @@ export class HaConfigHelpers extends LitElement {
|
||||
"ui.panel.config.helpers.picker.no_helpers"
|
||||
)}
|
||||
>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.add_helper"
|
||||
@ -167,7 +167,7 @@ export class HaConfigHelpers extends LitElement {
|
||||
@click=${this._createHelpler}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
@ -474,7 +474,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.add_integration"
|
||||
@ -483,7 +483,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
@click=${this._createFlow}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import { mdiCheckCircle, mdiCircle, mdiCloseCircle, mdiZWave } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import { mdiCheckCircle, mdiCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import { mdiAlert, mdiCheck } from "@mdi/js";
|
||||
import {
|
||||
CSSResult,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
|
@ -14,13 +14,17 @@ import {
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../../../types";
|
||||
import "./zha-device-card";
|
||||
import "./zha-device-pairing-status-card";
|
||||
import { zhaTabs } from "./zha-config-dashboard";
|
||||
import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea";
|
||||
import {
|
||||
DEVICE_MESSAGE_TYPES,
|
||||
LOG_OUTPUT,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
|
||||
@customElement("zha-add-devices-page")
|
||||
class ZHAAddDevicesPage extends LitElement {
|
||||
@ -34,7 +38,10 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _discoveredDevices: ZHADevice[] = [];
|
||||
@internalProperty() private _discoveredDevices: Record<
|
||||
string,
|
||||
ZHADevice
|
||||
> = {};
|
||||
|
||||
@internalProperty() private _formattedEvents = "";
|
||||
|
||||
@ -64,7 +71,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribe();
|
||||
this._error = undefined;
|
||||
this._discoveredDevices = [];
|
||||
this._discoveredDevices = {};
|
||||
this._formattedEvents = "";
|
||||
}
|
||||
|
||||
@ -115,7 +122,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
</div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="content">
|
||||
${this._discoveredDevices.length < 1
|
||||
${Object.keys(this._discoveredDevices).length < 1
|
||||
? html`
|
||||
<div class="discovery-text">
|
||||
<h4>
|
||||
@ -133,15 +140,15 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
${this._discoveredDevices.map(
|
||||
${Object.values(this._discoveredDevices).map(
|
||||
(device) => html`
|
||||
<zha-device-card
|
||||
<zha-device-pairing-status-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
.narrow=${this.narrow}
|
||||
.showHelp=${this._showHelp}
|
||||
></zha-device-card>
|
||||
></zha-device-pairing-status-card>
|
||||
`
|
||||
)}
|
||||
`}
|
||||
@ -164,7 +171,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
}
|
||||
|
||||
private _handleMessage(message: any): void {
|
||||
if (message.type === "log_output") {
|
||||
if (message.type === LOG_OUTPUT) {
|
||||
this._formattedEvents += message.log_entry.message + "\n";
|
||||
if (this.shadowRoot) {
|
||||
const paperTextArea = this.shadowRoot.querySelector("paper-textarea");
|
||||
@ -175,8 +182,8 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.type && message.type === "device_fully_initialized") {
|
||||
this._discoveredDevices.push(message.device_info);
|
||||
if (message.type && DEVICE_MESSAGE_TYPES.includes(message.type)) {
|
||||
this._discoveredDevices[message.device_info.ieee] = message.device_info;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../../components/ha-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
@ -88,13 +88,13 @@ class ZHAConfigDashboard extends LitElement {
|
||||
: ""}
|
||||
</ha-card>
|
||||
<a href="/config/zha/add" slot="fab">
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||
extended
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</a>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
|
@ -0,0 +1,147 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/entity/state-badge";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import {
|
||||
CONFIGURED,
|
||||
INCOMPLETE_PAIRING_STATUSES,
|
||||
INITIALIZED,
|
||||
INTERVIEW_COMPLETE,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-area-picker";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import "./zha-device-card";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("zha-device-pairing-status-card")
|
||||
class ZHADevicePairingStatusCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device?: ZHADevice;
|
||||
|
||||
@property({ type: Boolean }) public narrow?: boolean;
|
||||
|
||||
@internalProperty() private _showHelp = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="discovered ${classMap({
|
||||
initialized: this.device.pairing_status === INITIALIZED,
|
||||
})}"
|
||||
><div
|
||||
class="header"
|
||||
>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}`
|
||||
)}
|
||||
</h1>
|
||||
<h4>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}_status_text`
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
${[INTERVIEW_COMPLETE, CONFIGURED].includes(
|
||||
this.device.pairing_status!
|
||||
)
|
||||
? html`
|
||||
<div class="model">${this.device.model}</div>
|
||||
<div class="manuf">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.manuf",
|
||||
"manufacturer",
|
||||
this.device.manufacturer
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
<div class="info">
|
||||
${INCOMPLETE_PAIRING_STATUSES.includes(this.device.pairing_status!)
|
||||
? html`
|
||||
<div class="text">IEEE: ${this.device.ieee}</div>
|
||||
<div class="text">
|
||||
NWK: ${formatAsPaddedHex(this.device.nwk)}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
${this.device.pairing_status === INITIALIZED
|
||||
? html`
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
.narrow=${this.narrow}
|
||||
.showHelp=${this._showHelp}
|
||||
></zha-device-card>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.discovered {
|
||||
--ha-card-border-color: var(--primary-color);
|
||||
}
|
||||
.discovered.initialized {
|
||||
--ha-card-border-color: var(--success-color);
|
||||
}
|
||||
.discovered .header {
|
||||
background: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.discovered.initialized .header {
|
||||
background: var(--success-color);
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
.text,
|
||||
.manuf,
|
||||
.model {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-pairing-status-card": ZHADevicePairingStatusCard;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../../components/ha-fab";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
@ -127,14 +127,14 @@ export class ZHAGroupsDashboard extends LitElement {
|
||||
clickable
|
||||
>
|
||||
<a href="/config/zha/group-add" slot="fab">
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.add_group"
|
||||
)}
|
||||
extended
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</a>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
@ -223,7 +223,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
hasFab
|
||||
clickable
|
||||
>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.add_dashboard"
|
||||
@ -232,7 +232,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
@click=${this._addDashboard}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
@ -103,7 +103,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
hasFab
|
||||
clickable
|
||||
>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.resources.picker.add_resource"
|
||||
@ -112,7 +112,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
@click=${this._addResource}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -68,6 +68,8 @@ class DialogPersonDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _submitting = false;
|
||||
|
||||
@internalProperty() private _personExists = false;
|
||||
|
||||
private _deviceTrackersAvailable = memoizeOne((hass) => {
|
||||
return Object.keys(hass.states).some(
|
||||
(entityId) =>
|
||||
@ -79,6 +81,7 @@ class DialogPersonDetail extends LitElement {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.entry) {
|
||||
this._personExists = true;
|
||||
this._name = this._params.entry.name || "";
|
||||
this._userId = this._params.entry.user_id || undefined;
|
||||
this._deviceTrackers = this._params.entry.device_trackers || [];
|
||||
@ -88,6 +91,7 @@ class DialogPersonDetail extends LitElement {
|
||||
: undefined;
|
||||
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
} else {
|
||||
this._personExists = false;
|
||||
this._name = "";
|
||||
this._userId = undefined;
|
||||
this._user = undefined;
|
||||
@ -398,6 +402,7 @@ class DialogPersonDetail extends LitElement {
|
||||
await this._params!.updateEntry(values);
|
||||
} else {
|
||||
await this._params!.createEntry(values);
|
||||
this._personExists = true;
|
||||
}
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
@ -422,6 +427,14 @@ class DialogPersonDetail extends LitElement {
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
// If we do not have a person ID yet (= person creation dialog was just cancelled), but
|
||||
// we already created a user ID for it, delete it now to not have it "free floating".
|
||||
if (!this._personExists && this._userId) {
|
||||
deleteUser(this.hass, this._userId);
|
||||
this._params?.refreshUsers();
|
||||
this._userId = undefined;
|
||||
}
|
||||
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -146,14 +146,14 @@ class HaConfigPerson extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</ha-config-section>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${hass.localize("ui.panel.config.person.add_person")}
|
||||
extended
|
||||
@click=${this._createPerson}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlus, mdiHelpCircle } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
@ -152,14 +152,14 @@ class HaSceneDashboard extends LitElement {
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<a href="/config/scene/edit/new" slot="fab">
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.add_scene"
|
||||
)}
|
||||
extended
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</a>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@ -25,7 +25,7 @@ import "../../../components/device/ha-device-picker";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-input";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@ -403,7 +403,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize("ui.panel.config.scene.editor.save")}
|
||||
extended
|
||||
@ -411,7 +411,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
class=${classMap({ dirty: this._dirty })}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@ -786,12 +786,12 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
mwc-fab.dirty {
|
||||
ha-fab.dirty {
|
||||
bottom: 0;
|
||||
}
|
||||
`,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiContentSave,
|
||||
@ -388,7 +388,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
`
|
||||
: ``}
|
||||
</div>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.save_script"
|
||||
@ -400,7 +400,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@ -690,12 +690,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
mwc-fab.dirty {
|
||||
ha-fab.dirty {
|
||||
bottom: 0;
|
||||
}
|
||||
.selected_menu_item {
|
||||
|
@ -16,7 +16,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import { triggerScript } from "../../../data/script";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@ -147,7 +147,7 @@ class HaScriptPicker extends LitElement {
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<a href="/config/script/edit/new" slot="fab">
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
.label=${this.hass.localize(
|
||||
@ -157,7 +157,7 @@ class HaScriptPicker extends LitElement {
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</a>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import {
|
||||
mdiCog,
|
||||
@ -207,14 +207,14 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
<mwc-icon-button slot="toolbar-icon" @click=${this._showHelp}>
|
||||
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize("ui.panel.config.tags.add_tag")}
|
||||
extended
|
||||
@click=${this._addTag}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export class DialogAddUser extends LitElement {
|
||||
class="name"
|
||||
name="name"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.name"
|
||||
"ui.panel.config.users.editor.name"
|
||||
)}
|
||||
.value=${this._name}
|
||||
required
|
||||
@ -113,7 +113,7 @@ export class DialogAddUser extends LitElement {
|
||||
class="username"
|
||||
name="username"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.username"
|
||||
"ui.panel.config.users.editor.username"
|
||||
)}
|
||||
.value=${this._username}
|
||||
required
|
||||
@ -241,7 +241,7 @@ export class DialogAddUser extends LitElement {
|
||||
user = userResponse.user;
|
||||
} catch (err) {
|
||||
this._loading = false;
|
||||
this._error = err.code;
|
||||
this._error = err.message;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -255,10 +255,11 @@ export class DialogAddUser extends LitElement {
|
||||
} catch (err) {
|
||||
await deleteUser(this.hass, user.id);
|
||||
this._loading = false;
|
||||
this._error = err.code;
|
||||
this._error = err.message;
|
||||
return;
|
||||
}
|
||||
|
||||
user.username = this._username;
|
||||
this._params!.userAddedCallback(user);
|
||||
this._close();
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-help-tooltip";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-switch";
|
||||
import { adminChangePassword } from "../../../data/auth";
|
||||
@ -37,6 +38,8 @@ class DialogUserDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _isAdmin?: boolean;
|
||||
|
||||
@internalProperty() private _isActive?: boolean;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _params?: UserDetailDialogParams;
|
||||
@ -48,6 +51,7 @@ class DialogUserDetail extends LitElement {
|
||||
this._error = undefined;
|
||||
this._name = params.entry.name || "";
|
||||
this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
this._isActive = params.entry.is_active;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@ -67,7 +71,10 @@ class DialogUserDetail extends LitElement {
|
||||
<div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.panel.config.users.editor.id")}: ${user.id}
|
||||
${this.hass.localize("ui.panel.config.users.editor.id")}:
|
||||
${user.id}<br />
|
||||
${this.hass.localize("ui.panel.config.users.editor.username")}:
|
||||
${user.username}
|
||||
</div>
|
||||
<div>
|
||||
${user.is_owner
|
||||
@ -88,15 +95,6 @@ class DialogUserDetail extends LitElement {
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
${user.is_active
|
||||
? html`
|
||||
<span class="state"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active"
|
||||
)}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="form">
|
||||
<paper-input
|
||||
@ -107,17 +105,21 @@ class DialogUserDetail extends LitElement {
|
||||
"ui.panel.config.users.editor.name"
|
||||
)}"
|
||||
></paper-input>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-switch
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${!this._isAdmin
|
||||
? html`
|
||||
<br />
|
||||
@ -126,6 +128,27 @@ class DialogUserDetail extends LitElement {
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isActive}
|
||||
@change=${this._activeChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active_tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -189,11 +212,16 @@ class DialogUserDetail extends LitElement {
|
||||
this._isAdmin = ev.target.checked;
|
||||
}
|
||||
|
||||
private async _activeChanged(ev): Promise<void> {
|
||||
this._isActive = ev.target.checked;
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
await this._params!.updateEntry({
|
||||
name: this._name.trim(),
|
||||
is_active: this._isActive,
|
||||
group_ids: [
|
||||
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
|
||||
],
|
||||
@ -290,8 +318,13 @@ class DialogUserDetail extends LitElement {
|
||||
.state:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-switch {
|
||||
margin-top: 8px;
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
}
|
||||
ha-help-tooltip {
|
||||
margin-left: 4px;
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
@ -35,18 +35,40 @@ export class HaConfigUsers extends LitElement {
|
||||
@property() public route!: Route;
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
(narrow: boolean, _language): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = {
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.picker.headers.name"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "25%",
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name) => html`
|
||||
${name ||
|
||||
template: (name, user: any) =>
|
||||
narrow
|
||||
? html` ${name}<br />
|
||||
<div class="secondary">
|
||||
${user.username} |
|
||||
${this.hass.localize(`groups.${user.group_ids[0]}`)}
|
||||
</div>`
|
||||
: html` ${name ||
|
||||
this.hass!.localize(
|
||||
"ui.panel.config.users.editor.unnamed_user"
|
||||
)}`,
|
||||
},
|
||||
username: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.picker.headers.username"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "20%",
|
||||
direction: "asc",
|
||||
hidden: narrow,
|
||||
template: (username) => html`
|
||||
${username ||
|
||||
this.hass!.localize("ui.panel.config.users.editor.unnamed_user")}
|
||||
`,
|
||||
},
|
||||
@ -56,26 +78,38 @@ export class HaConfigUsers extends LitElement {
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "30%",
|
||||
width: "20%",
|
||||
direction: "asc",
|
||||
hidden: narrow,
|
||||
template: (groupIds) => html`
|
||||
${this.hass.localize(`groups.${groupIds[0]}`)}
|
||||
`,
|
||||
},
|
||||
is_active: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.picker.headers.is_active"
|
||||
),
|
||||
type: "icon",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "80px",
|
||||
template: (is_active) =>
|
||||
is_active ? html`<ha-icon icon="hass:check"> </ha-icon>` : "",
|
||||
},
|
||||
system_generated: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.picker.headers.system"
|
||||
),
|
||||
type: "icon",
|
||||
width: "80px",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (generated) => html`
|
||||
${generated
|
||||
? html` <ha-icon icon="hass:check-circle-outline"></ha-icon> `
|
||||
: ""}
|
||||
`,
|
||||
width: "160px",
|
||||
template: (generated) =>
|
||||
generated ? html`<ha-icon icon="hass:check"> </ha-icon>` : "",
|
||||
},
|
||||
};
|
||||
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
@ -92,26 +126,32 @@ export class HaConfigUsers extends LitElement {
|
||||
.route=${this.route}
|
||||
backPath="/config"
|
||||
.tabs=${configSections.persons}
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._users}
|
||||
@row-click=${this._editUser}
|
||||
hasFab
|
||||
clickable
|
||||
>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize("ui.panel.config.users.picker.add_user")}
|
||||
extended
|
||||
@click=${this._addUser}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchUsers() {
|
||||
this._users = await fetchUsers(this.hass);
|
||||
|
||||
this._users.forEach(function (user) {
|
||||
if (user.is_owner) {
|
||||
user.group_ids.unshift("owner");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _editUser(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPencil, mdiPencilOff, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -255,14 +255,14 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${hass.localize("ui.panel.config.zone.add_zone")}
|
||||
extended
|
||||
@click=${this._createZone}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class PanelDeveloperTools extends LitElement {
|
||||
ha-tabs {
|
||||
margin-left: max(env(safe-area-inset-left), 24px);
|
||||
margin-right: max(env(safe-area-inset-right), 24px);
|
||||
--paper-tabs-selection-bar-color: #fff;
|
||||
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`,
|
||||
|
@ -109,7 +109,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public setConfig(config: EntitiesCardConfig): void {
|
||||
if (!config || !config.entities.length) {
|
||||
if (!config.entities || !Array.isArray(config.entities)) {
|
||||
throw new Error("Entities must be specified");
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import "../../../components/state-history-charts";
|
||||
import { CacheConfig, getRecentWithCache } from "../../../data/cached-history";
|
||||
import { HistoryResult } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
@ -30,22 +29,9 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
return document.createElement("hui-history-graph-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): HistoryGraphCardConfig {
|
||||
const includeDomains = ["sensor"];
|
||||
const maxEntities = 1;
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFallback,
|
||||
includeDomains
|
||||
);
|
||||
|
||||
return { type: "history-graph", entities: foundEntities };
|
||||
public static getStubConfig(): HistoryGraphCardConfig {
|
||||
// Hard coded to sun.sun to prevent high server load when it would pick an entity with a lot of state changes
|
||||
return { type: "history-graph", entities: ["sun.sun"] };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@ -71,12 +57,12 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public setConfig(config: HistoryGraphCardConfig): void {
|
||||
if (!config.entities.length) {
|
||||
throw new Error("Entities must be specified");
|
||||
if (!config.entities || !Array.isArray(config.entities)) {
|
||||
throw new Error("Entities need to be an array");
|
||||
}
|
||||
|
||||
if (config.entities && !Array.isArray(config.entities)) {
|
||||
throw new Error("Entities need to be an array");
|
||||
if (!config.entities.length) {
|
||||
throw new Error("You must include at least one entity");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
|
@ -29,7 +29,7 @@ class HuiHorizontalStackCard extends HuiStackCard {
|
||||
}
|
||||
#root > * {
|
||||
flex: 1 1 0;
|
||||
margin: 0 4px;
|
||||
margin: var(--horizontal-stack-card-margin, var(--stack-card-margin, 0 4px));
|
||||
min-width: 0;
|
||||
}
|
||||
#root > *:first-child {
|
||||
|
@ -246,78 +246,73 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
${!isUnavailable &&
|
||||
(mediaDescription || stateObj.attributes.media_title || showControls)
|
||||
? html`
|
||||
<div
|
||||
class="title-controls"
|
||||
style=${styleMap({
|
||||
paddingRight: isOffState
|
||||
? "0"
|
||||
: `${this._cardHeight - 40}px`,
|
||||
})}
|
||||
>
|
||||
${!mediaDescription && !stateObj.attributes.media_title
|
||||
? ""
|
||||
: html`
|
||||
<div class="media-info">
|
||||
<hui-marquee
|
||||
.text=${stateObj.attributes.media_title ||
|
||||
mediaDescription}
|
||||
.active=${this._marqueeActive}
|
||||
@mouseover=${this._marqueeMouseOver}
|
||||
@mouseleave=${this._marqueeMouseLeave}
|
||||
></hui-marquee>
|
||||
${!stateObj.attributes.media_title
|
||||
? ""
|
||||
: mediaDescription}
|
||||
</div>
|
||||
`}
|
||||
${!showControls
|
||||
? ""
|
||||
: html`
|
||||
<div class="controls">
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.title=${this.hass.localize(
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
.icon=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>
|
||||
`
|
||||
)}
|
||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="browse-media"
|
||||
<div>
|
||||
<div class="title-controls">
|
||||
${!mediaDescription && !stateObj.attributes.media_title
|
||||
? ""
|
||||
: html`
|
||||
<div class="media-info">
|
||||
<hui-marquee
|
||||
.text=${stateObj.attributes.media_title ||
|
||||
mediaDescription}
|
||||
.active=${this._marqueeActive}
|
||||
@mouseover=${this._marqueeMouseOver}
|
||||
@mouseleave=${this._marqueeMouseLeave}
|
||||
></hui-marquee>
|
||||
${!stateObj.attributes.media_title
|
||||
? ""
|
||||
: mediaDescription}
|
||||
</div>
|
||||
`}
|
||||
${!showControls
|
||||
? ""
|
||||
: html`
|
||||
<div class="controls">
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.title=${this.hass.localize(
|
||||
"ui.card.media_player.browse_media"
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
@click=${this._handleBrowseMedia}
|
||||
><ha-svg-icon
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
></ha-svg-icon
|
||||
></mwc-icon-button>
|
||||
.icon=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
)}
|
||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="browse-media"
|
||||
.title=${this.hass.localize(
|
||||
"ui.card.media_player.browse_media"
|
||||
)}
|
||||
@click=${this._handleBrowseMedia}
|
||||
><ha-svg-icon
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
></ha-svg-icon
|
||||
></mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
${!this._showProgressBar
|
||||
? ""
|
||||
: html`
|
||||
<paper-progress
|
||||
.max=${stateObj.attributes.media_duration}
|
||||
style=${styleMap({
|
||||
"--paper-progress-active-color":
|
||||
this._foregroundColor || "var(--accent-color)",
|
||||
cursor: supportsFeature(stateObj, SUPPORT_SEEK)
|
||||
? "pointer"
|
||||
: "initial",
|
||||
})}
|
||||
@click=${this._handleSeek}
|
||||
></paper-progress>
|
||||
`}
|
||||
</div>
|
||||
${!this._showProgressBar
|
||||
? ""
|
||||
: html`
|
||||
<paper-progress
|
||||
.max=${stateObj.attributes.media_duration}
|
||||
style=${styleMap({
|
||||
"--paper-progress-active-color":
|
||||
this._foregroundColor || "var(--accent-color)",
|
||||
cursor: supportsFeature(stateObj, SUPPORT_SEEK)
|
||||
? "pointer"
|
||||
: "initial",
|
||||
})}
|
||||
@click=${this._handleSeek}
|
||||
></paper-progress>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@ -635,6 +630,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
.player {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: var(--text-primary-color);
|
||||
transition-property: color, padding;
|
||||
transition-duration: 0.4s;
|
||||
@ -671,7 +671,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
|
||||
mwc-icon-button.browse-media {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: 4px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
|
||||
@ -693,7 +693,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
.more-info {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.media-info {
|
||||
|
@ -29,7 +29,7 @@ class HuiVerticalStackCard extends HuiStackCard {
|
||||
height: 100%;
|
||||
}
|
||||
#root > * {
|
||||
margin: 4px 0 4px 0;
|
||||
margin: var(--vertical-stack-card-margin, var(--stack-card-margin, 4px 0));
|
||||
}
|
||||
#root > *:first-child {
|
||||
margin-top: 0;
|
||||
|
@ -72,7 +72,7 @@ class HuiMarquee extends LitElement {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
height: 1em;
|
||||
height: 1.2em;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
|
@ -353,11 +353,9 @@ export class HuiCardPicker extends LitElement {
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
background: var(--primary-background-color, #fafafa);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -375,7 +373,6 @@ export class HuiCardPicker extends LitElement {
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-radius: 0 0 4px 4px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
@ -408,6 +405,10 @@ export class HuiCardPicker extends LitElement {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
border: var(--ha-card-border-width, 1px) solid
|
||||
var(--ha-card-border-color, var(--divider-color));
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
}
|
||||
|
||||
.manual {
|
||||
|
@ -450,6 +450,10 @@ export class HuiDialogEditCard extends LitElement
|
||||
}
|
||||
.element-preview {
|
||||
position: relative;
|
||||
height: max-content;
|
||||
background: var(--primary-background-color);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.element-preview ha-circular-progress {
|
||||
top: 50%;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../components/ha-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -108,13 +108,13 @@ export class HuiUnusedEntities extends LitElement {
|
||||
selected: this._selectedEntities.length,
|
||||
})}"
|
||||
>
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass.localize("ui.panel.lovelace.editor.edit_card.add")}
|
||||
extended
|
||||
@click=${this._addToLovelaceView}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -189,12 +189,12 @@ export class HuiUnusedEntities extends LitElement {
|
||||
padding-right: 16px;
|
||||
padding-left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: relative;
|
||||
bottom: calc(-80px - env(safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
.fab.selected mwc-fab {
|
||||
.fab.selected ha-fab {
|
||||
bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
@ -25,7 +25,7 @@ export class HuiPictureHeaderFooter extends LitElement
|
||||
public static getStubConfig(): Record<string, unknown> {
|
||||
return {
|
||||
image:
|
||||
"https://www.home-assistant.io/images/merchandise/shirt-frontpage.png",
|
||||
"https://www.home-assistant.io/images/lovelace/header-footer/balloons-header.png",
|
||||
tap_action: { action: "none" },
|
||||
hold_action: { action: "none" },
|
||||
};
|
||||
|
@ -793,10 +793,6 @@ class HUIRoot extends LitElement {
|
||||
|
||||
ha-app-layout {
|
||||
min-height: 100%;
|
||||
background: var(
|
||||
--lovelace-background,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
}
|
||||
ha-tabs {
|
||||
width: 100%;
|
||||
@ -884,6 +880,12 @@ class HUIRoot extends LitElement {
|
||||
.menu-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
hui-view {
|
||||
background: var(
|
||||
--lovelace-background,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -57,7 +57,8 @@ class HuiAttributeRow extends LitElement implements LovelaceRow {
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<div>
|
||||
${this._config.prefix} ${attribute || "-"} ${this._config.suffix}
|
||||
${this._config.prefix} ${attribute ?? "-"}
|
||||
${this._config.suffix}
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
|
@ -1,3 +1,3 @@
|
||||
// hui-view dependencies for when in edit mode.
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-fab";
|
||||
import "../components/hui-card-options";
|
||||
|
@ -83,7 +83,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
<div id="columns"></div>
|
||||
${this.lovelace?.editMode
|
||||
? html`
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.add"
|
||||
)}
|
||||
@ -94,7 +94,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@ -293,10 +293,10 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
.column > * {
|
||||
display: block;
|
||||
margin: 4px 4px 8px;
|
||||
margin: var(--masonry-view-card-margin, 4px 4px 8px);
|
||||
}
|
||||
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: sticky;
|
||||
float: right;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
@ -304,7 +304,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
mwc-fab.rtl {
|
||||
ha-fab.rtl {
|
||||
float: left;
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
|
@ -75,7 +75,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
|
||||
${this._card}
|
||||
${this.lovelace?.editMode && this.cards.length === 0
|
||||
? html`
|
||||
<mwc-fab
|
||||
<ha-fab
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.add"
|
||||
)}
|
||||
@ -86,7 +86,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</ha-fab>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@ -137,7 +137,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
mwc-fab {
|
||||
ha-fab {
|
||||
position: sticky;
|
||||
float: right;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
@ -145,7 +145,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
mwc-fab.rtl {
|
||||
ha-fab.rtl {
|
||||
float: left;
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
|
@ -1,154 +0,0 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "../../components/ha-circular-progress";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../components/ha-card";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import "../../styles/polymer-ha-style";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaChangePasswordCard extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
.status {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.error,
|
||||
.status {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
}
|
||||
.currentPassword {
|
||||
margin-top: -4px;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.profile.change_password.header')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
<template is="dom-if" if="[[_errorMsg]]">
|
||||
<div class="error">[[_errorMsg]]</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_statusMsg]]">
|
||||
<div class="status">[[_statusMsg]]</div>
|
||||
</template>
|
||||
<paper-input
|
||||
class="currentPassword"
|
||||
label="[[localize('ui.panel.profile.change_password.current_password')]]"
|
||||
type="password"
|
||||
value="{{_currentPassword}}"
|
||||
required
|
||||
auto-validate
|
||||
error-message="[[localize('ui.panel.profile.change_password.error_required')]]"
|
||||
></paper-input>
|
||||
<template is="dom-if" if="[[_currentPassword]]">
|
||||
<paper-input
|
||||
label="[[localize('ui.panel.profile.change_password.new_password')]]"
|
||||
type="password"
|
||||
value="{{_password1}}"
|
||||
required
|
||||
auto-validate
|
||||
error-message="[[localize('ui.panel.profile.change_password.error_required')]]"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="[[localize('ui.panel.profile.change_password.confirm_new_password')]]"
|
||||
type="password"
|
||||
value="{{_password2}}"
|
||||
required
|
||||
auto-validate
|
||||
error-message="[[localize('ui.panel.profile.change_password.error_required')]]"
|
||||
></paper-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<template is="dom-if" if="[[_loading]]">
|
||||
<div><ha-circular-progress active></ha-circular-progress></div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_loading]]">
|
||||
<mwc-button on-click="_changePassword"
|
||||
>[[localize('ui.panel.profile.change_password.submit')]]</mwc-button
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
// Error message when can't talk to server etc
|
||||
_statusMsg: String,
|
||||
_errorMsg: String,
|
||||
|
||||
_currentPassword: String,
|
||||
_password1: String,
|
||||
_password2: String,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
this._statusMsg = null;
|
||||
if (ev.keyCode === 13) {
|
||||
this._changePassword();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async _changePassword() {
|
||||
this._statusMsg = null;
|
||||
if (!this._currentPassword || !this._password1 || !this._password2) return;
|
||||
|
||||
if (this._password1 !== this._password2) {
|
||||
this._errorMsg = "New password confirmation doesn't match";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentPassword === this._password1) {
|
||||
this._errorMsg = "New password must be different than current password";
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
this._errorMsg = null;
|
||||
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "config/auth_provider/homeassistant/change_password",
|
||||
current_password: this._currentPassword,
|
||||
new_password: this._password1,
|
||||
});
|
||||
|
||||
this.setProperties({
|
||||
_statusMsg: "Password changed successfully",
|
||||
_currentPassword: null,
|
||||
_password1: null,
|
||||
_password2: null,
|
||||
});
|
||||
} catch (err) {
|
||||
this._errorMsg = err.message;
|
||||
}
|
||||
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-change-password-card", HaChangePasswordCard);
|
195
src/panels/profile/ha-change-password-card.ts
Normal file
195
src/panels/profile/ha-change-password-card.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-card";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-change-password-card")
|
||||
class HaChangePasswordCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _loading = false;
|
||||
|
||||
@internalProperty() private _statusMsg?: string;
|
||||
|
||||
@internalProperty() private _errorMsg?: string;
|
||||
|
||||
@internalProperty() private _currentPassword?: string;
|
||||
|
||||
@internalProperty() private _password?: string;
|
||||
|
||||
@internalProperty() private _passwordConfirm?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.header"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._errorMsg
|
||||
? html` <div class="error">${this._errorMsg}</div> `
|
||||
: ""}
|
||||
${this._statusMsg
|
||||
? html` <div class="status">${this._statusMsg}</div> `
|
||||
: ""}
|
||||
|
||||
<paper-input
|
||||
id="currentPassword"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.current_password"
|
||||
)}
|
||||
type="password"
|
||||
.value=${this._currentPassword}
|
||||
@value-changed=${this._currentPasswordChanged}
|
||||
required
|
||||
></paper-input>
|
||||
|
||||
${this._currentPassword
|
||||
? html` <paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.new_password"
|
||||
)}
|
||||
name="password"
|
||||
type="password"
|
||||
.value=${this._password}
|
||||
@value-changed=${this._newPasswordChanged}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.confirm_new_password"
|
||||
)}
|
||||
name="passwordConfirm"
|
||||
type="password"
|
||||
.value=${this._passwordConfirm}
|
||||
@value-changed=${this._newPasswordConfirmChanged}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
${this._loading
|
||||
? html`<div>
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
</div>`
|
||||
: html`<mwc-button
|
||||
@click=${this._changePassword}
|
||||
.disabled=${!this._passwordConfirm}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.profile.change_password.submit"
|
||||
)}</mwc-button
|
||||
>`}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _currentPasswordChanged(ev: CustomEvent) {
|
||||
this._currentPassword = ev.detail.value;
|
||||
}
|
||||
|
||||
private _newPasswordChanged(ev: CustomEvent) {
|
||||
this._password = ev.detail.value;
|
||||
}
|
||||
|
||||
private _newPasswordConfirmChanged(ev: CustomEvent) {
|
||||
this._passwordConfirm = ev.detail.value;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
this._statusMsg = undefined;
|
||||
if (ev.keyCode === 13) {
|
||||
this._changePassword();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _changePassword() {
|
||||
this._statusMsg = undefined;
|
||||
if (!this._currentPassword || !this._password || !this._passwordConfirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._password !== this._passwordConfirm) {
|
||||
this._errorMsg = this.hass.localize(
|
||||
"ui.panel.profile.change_password.error_new_mismatch"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentPassword === this._password) {
|
||||
this._errorMsg = this.hass.localize(
|
||||
"ui.panel.profile.change_password.error_new_is_old"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
this._errorMsg = undefined;
|
||||
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: "config/auth_provider/homeassistant/change_password",
|
||||
current_password: this._currentPassword,
|
||||
new_password: this._password,
|
||||
});
|
||||
} catch (err) {
|
||||
this._errorMsg = err.message;
|
||||
return;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
this._statusMsg = this.hass.localize(
|
||||
"ui.panel.profile.change_password.success"
|
||||
);
|
||||
this._currentPassword = undefined;
|
||||
this._password = undefined;
|
||||
this._passwordConfirm = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.status {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
#currentPassword {
|
||||
margin-top: -8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-change-password-card": HaChangePasswordCard;
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ class HaPushNotificationsRow extends LocalizeMixin(PolymerElement) {
|
||||
>[[localize('ui.panel.profile.push_notifications.header')]]</span
|
||||
>
|
||||
<span slot="description">
|
||||
[[_description(_platformLoaded, _pushSupported)]]
|
||||
[[localize(_descrLocalizeKey)]]
|
||||
<a
|
||||
href="[[_computeDocumentationUrl(hass)]]"
|
||||
target="_blank"
|
||||
@ -45,6 +45,10 @@ class HaPushNotificationsRow extends LocalizeMixin(PolymerElement) {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
_descrLocalizeKey: {
|
||||
type: String,
|
||||
computed: "_descriptionKey(_platformLoaded, _pushSupported)",
|
||||
},
|
||||
_platformLoaded: {
|
||||
type: Boolean,
|
||||
computed: "_compPlatformLoaded(hass)",
|
||||
@ -72,7 +76,7 @@ class HaPushNotificationsRow extends LocalizeMixin(PolymerElement) {
|
||||
return !platformLoaded || !pushSupported_;
|
||||
}
|
||||
|
||||
_description(platformLoaded, pushSupported_) {
|
||||
_descriptionKey(platformLoaded, pushSupported_) {
|
||||
let key;
|
||||
if (!pushSupported_) {
|
||||
key = "error_use_https";
|
||||
@ -81,7 +85,7 @@ class HaPushNotificationsRow extends LocalizeMixin(PolymerElement) {
|
||||
} else {
|
||||
key = "description";
|
||||
}
|
||||
return this.localize(`ui.panel.profile.push_notifications.${key}`);
|
||||
return `ui.panel.profile.push_notifications.${key}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Owner",
|
||||
"system-admin": "Administrators",
|
||||
"system-users": "Users",
|
||||
"system-read-only": "Read-Only Users"
|
||||
@ -97,7 +98,8 @@
|
||||
"disabled_by": {
|
||||
"user": "User",
|
||||
"integration": "Integration",
|
||||
"config_entry": "Config Entry"
|
||||
"config_entry": "Config Entry",
|
||||
"device": "Device"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
@ -333,6 +335,16 @@
|
||||
"show_attributes": "Show attributes"
|
||||
}
|
||||
},
|
||||
"target-picker": {
|
||||
"expand_area_id": "Expand this area in the seperate devices and entities that it contains. After expanding it will not update the devices and entities when the area changes.",
|
||||
"expand_device_id": "Expand this device in seperate entities. After expanding it will not update the entities when the device changes.",
|
||||
"remove_area_id": "Remove area",
|
||||
"remove_device_id": "Remove device",
|
||||
"remove_entity_id": "Remove entity",
|
||||
"add_area_id": "Pick area",
|
||||
"add_device_id": "Pick device",
|
||||
"add_entity_id": "Pick entity"
|
||||
},
|
||||
"user-picker": {
|
||||
"no_user": "No user",
|
||||
"add_user": "Add user",
|
||||
@ -611,6 +623,8 @@
|
||||
"unavailable": "This entity is not currently available.",
|
||||
"enabled_label": "Enable entity",
|
||||
"enabled_cause": "Disabled by {cause}.",
|
||||
"device_disabled": "The device of this entity is disabled.",
|
||||
"open_device_settings": "Open device settings",
|
||||
"enabled_description": "Disabled entities will not be added to Home Assistant.",
|
||||
"enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds",
|
||||
"enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities",
|
||||
@ -786,7 +800,7 @@
|
||||
},
|
||||
"areas": {
|
||||
"caption": "Areas",
|
||||
"description": "Manage areas in your home",
|
||||
"description": "Group devices into areas",
|
||||
"data_table": {
|
||||
"area": "Area",
|
||||
"devices": "Devices"
|
||||
@ -816,7 +830,7 @@
|
||||
},
|
||||
"tags": {
|
||||
"caption": "Tags",
|
||||
"description": "Manage tags",
|
||||
"description": "Trigger automations when a NFC tag, QR code, etc. is scanned",
|
||||
"learn_more": "Learn more about tags",
|
||||
"no_tags": "No tags",
|
||||
"add_tag": "Add tag",
|
||||
@ -847,7 +861,7 @@
|
||||
},
|
||||
"helpers": {
|
||||
"caption": "Helpers",
|
||||
"description": "Manage elements that help build automations",
|
||||
"description": "Elements that help build automations",
|
||||
"types": {
|
||||
"input_text": "Text",
|
||||
"input_number": "Number",
|
||||
@ -875,7 +889,7 @@
|
||||
},
|
||||
"core": {
|
||||
"caption": "General",
|
||||
"description": "Change your general Home Assistant configuration",
|
||||
"description": "Unit system, location, time zone & other general parameters",
|
||||
"section": {
|
||||
"core": {
|
||||
"header": "General Configuration",
|
||||
@ -904,7 +918,7 @@
|
||||
"caption": "Info",
|
||||
"copy_raw": "Raw Text",
|
||||
"copy_github": "For GitHub",
|
||||
"description": "View info about your Home Assistant installation",
|
||||
"description": "Version, system health and links to documentation",
|
||||
"home_assistant_logo": "Home Assistant logo",
|
||||
"path_configuration": "Path to configuration.yaml: {path}",
|
||||
"developed_by": "Developed by a bunch of awesome people.",
|
||||
@ -939,7 +953,7 @@
|
||||
},
|
||||
"lovelace": {
|
||||
"caption": "Lovelace Dashboards",
|
||||
"description": "Manage your Lovelace Dashboards",
|
||||
"description": "Create customized sets of cards to control your home",
|
||||
"dashboards": {
|
||||
"default_dashboard": "This is the default dashboard",
|
||||
"caption": "Dashboards",
|
||||
@ -1094,7 +1108,7 @@
|
||||
},
|
||||
"automation": {
|
||||
"caption": "Automations",
|
||||
"description": "Manage automations",
|
||||
"description": "Create custom behavior rules for your home",
|
||||
"picker": {
|
||||
"header": "Automation Editor",
|
||||
"introduction": "The automation editor allows you to create and edit automations. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.",
|
||||
@ -1474,7 +1488,7 @@
|
||||
},
|
||||
"script": {
|
||||
"caption": "Scripts",
|
||||
"description": "Manage scripts",
|
||||
"description": "Execute a sequence of actions",
|
||||
"picker": {
|
||||
"header": "Script Editor",
|
||||
"introduction": "The script editor allows you to create and edit scripts. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.",
|
||||
@ -1524,7 +1538,7 @@
|
||||
},
|
||||
"scene": {
|
||||
"caption": "Scenes",
|
||||
"description": "Manage scenes",
|
||||
"description": "Capture device states and easily recall them later",
|
||||
"activated": "Activated scene {name}.",
|
||||
"picker": {
|
||||
"header": "Scene Editor",
|
||||
@ -1570,7 +1584,7 @@
|
||||
"cloud": {
|
||||
"description_login": "Logged in as {email}",
|
||||
"description_not_login": "Not logged in",
|
||||
"description_features": "Control away from home, integrate with Alexa and Google Assistant.",
|
||||
"description_features": "Control home when away and integrate with Alexa and Google Assistant",
|
||||
"login": {
|
||||
"title": "Cloud Login",
|
||||
"introduction": "Home Assistant Cloud provides you with a secure remote connection to your instance while away from home. It also allows you to connect with cloud-only services: Amazon Alexa and Google Assistant.",
|
||||
@ -1737,18 +1751,27 @@
|
||||
"devices": {
|
||||
"add_prompt": "No {name} have been added using this device yet. You can add one by clicking the + button above.",
|
||||
"caption": "Devices",
|
||||
"description": "Manage connected devices",
|
||||
"description": "Manage configured devices",
|
||||
"device_info": "Device info",
|
||||
"unnamed_device": "Unnamed device",
|
||||
"unknown_error": "Unknown error",
|
||||
"name": "Name",
|
||||
"update": "Update",
|
||||
"no_devices": "No devices",
|
||||
"enabled_label": "Enable device",
|
||||
"enabled_cause": "The device is disabled by {cause}.",
|
||||
"disabled_by": {
|
||||
"user": "User",
|
||||
"integration": "Integration",
|
||||
"config_entry": "Config Entry"
|
||||
},
|
||||
"enabled_description": "Disabled devices will not be shown and entities belonging to the device will be disabled and not added to Home Assistant.",
|
||||
"automation": {
|
||||
"automations": "Automations",
|
||||
"no_automations": "No automations",
|
||||
"unknown_automation": "Unknown automation",
|
||||
"create": "Create automation with device",
|
||||
"create_disable": "Can't create automation with disabled device",
|
||||
"triggers": {
|
||||
"caption": "Do something when...",
|
||||
"no_triggers": "No triggers",
|
||||
@ -1769,12 +1792,14 @@
|
||||
"script": {
|
||||
"scripts": "Scripts",
|
||||
"no_scripts": "No scripts",
|
||||
"create": "Create script with device"
|
||||
"create": "Create script with device",
|
||||
"create_disable": "Can't create script with disabled device"
|
||||
},
|
||||
"scene": {
|
||||
"scenes": "Scenes",
|
||||
"no_scenes": "No scenes",
|
||||
"create": "Create scene with device"
|
||||
"create": "Create scene with device",
|
||||
"create_disable": "Can't create scene with disabled device"
|
||||
},
|
||||
"cant_edit": "You can only edit items that are created in the UI.",
|
||||
"device_not_found": "Device not found.",
|
||||
@ -1789,6 +1814,7 @@
|
||||
"scenes": "Scenes",
|
||||
"confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?",
|
||||
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!",
|
||||
"disabled": "Disabled",
|
||||
"data_table": {
|
||||
"device": "Device",
|
||||
"manufacturer": "Manufacturer",
|
||||
@ -1800,7 +1826,16 @@
|
||||
"no_area": "No area"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"confirm_delete": "Are you sure you want to delete this device?"
|
||||
"confirm_delete": "Are you sure you want to delete this device?",
|
||||
"picker": {
|
||||
"search": "Search devices",
|
||||
"filter": {
|
||||
"filter": "Filter",
|
||||
"show_disabled": "Show disabled devices",
|
||||
"hidden_devices": "{number} hidden {number, plural,\n one {device}\n other {devices}\n}",
|
||||
"show_all": "Show all"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entities",
|
||||
@ -1829,6 +1864,7 @@
|
||||
"name": "Name",
|
||||
"entity_id": "Entity ID",
|
||||
"integration": "Integration",
|
||||
"area": "Area",
|
||||
"status": "Status"
|
||||
},
|
||||
"selected": "{number} selected",
|
||||
@ -1914,7 +1950,7 @@
|
||||
},
|
||||
"integrations": {
|
||||
"caption": "Integrations",
|
||||
"description": "Manage integrations",
|
||||
"description": "Manage integrations with services, devices, ...",
|
||||
"integration": "integration",
|
||||
"discovered": "Discovered",
|
||||
"attention": "Attention required",
|
||||
@ -1991,15 +2027,19 @@
|
||||
"users_privileges_note": "The user group feature is a work in progress. The user will be unable to administer the instance via the UI. We're still auditing all management API endpoints to ensure that they correctly limit access to administrators.",
|
||||
"picker": {
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"name": "Display name",
|
||||
"username": "Username",
|
||||
"group": "Group",
|
||||
"system": "System"
|
||||
"system": "System generated",
|
||||
"is_active": "Active",
|
||||
"is_owner": "Owner"
|
||||
},
|
||||
"add_user": "Add user"
|
||||
},
|
||||
"editor": {
|
||||
"caption": "View user",
|
||||
"name": "Name",
|
||||
"name": "Display name",
|
||||
"username": "Username",
|
||||
"change_password": "Change password",
|
||||
"new_password": "New Password",
|
||||
"password_changed": "Password was changed successfully",
|
||||
@ -2016,12 +2056,11 @@
|
||||
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
||||
"system_generated_users_not_editable": "Unable to update system generated users.",
|
||||
"unnamed_user": "Unnamed User",
|
||||
"confirm_user_deletion": "Are you sure you want to delete {name}?"
|
||||
"confirm_user_deletion": "Are you sure you want to delete {name}?",
|
||||
"active_tooltip": "Controls if user can login"
|
||||
},
|
||||
"add_user": {
|
||||
"caption": "Add user",
|
||||
"name": "Name",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"password_confirm": "Confirm Password",
|
||||
"password_not_match": "Passwords don't match",
|
||||
@ -2194,6 +2233,16 @@
|
||||
"issue_zigbee_command": "Issue Zigbee Command",
|
||||
"help_command_dropdown": "Select a command to interact with."
|
||||
},
|
||||
"device_pairing_card": {
|
||||
"PAIRED": "Device Found",
|
||||
"PAIRED_status_text": "Starting Interview",
|
||||
"INTERVIEW_COMPLETE": "Interview Complete",
|
||||
"INTERVIEW_COMPLETE_status_text": "Configuring",
|
||||
"CONFIGURED": "Configuration Complete",
|
||||
"CONFIGURED_status_text": "Initializing",
|
||||
"INITIALIZED": "Initialization Complete",
|
||||
"INITIALIZED_status_text": "The device is ready to use"
|
||||
},
|
||||
"network": {
|
||||
"caption": "Network"
|
||||
},
|
||||
@ -2857,7 +2906,10 @@
|
||||
"new_password": "New Password",
|
||||
"confirm_new_password": "Confirm New Password",
|
||||
"error_required": "Required",
|
||||
"submit": "Submit"
|
||||
"submit": "Submit",
|
||||
"error_new_mismatch": "Entered new password values do not match",
|
||||
"error_new_is_old": "New password must be different than current password",
|
||||
"success": "Password changed successfully"
|
||||
},
|
||||
"mfa": {
|
||||
"header": "Multi-factor Authentication Modules",
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Vlastník",
|
||||
"system-admin": "Správci",
|
||||
"system-read-only": "Uživatelé jen pro čtení",
|
||||
"system-users": "Uživatelé"
|
||||
@ -587,7 +588,7 @@
|
||||
"by_service": "službou",
|
||||
"entries_not_found": "Nenalezeny žádné záznamy.",
|
||||
"messages": {
|
||||
"became_unavailable": "stalo se nedostupným",
|
||||
"became_unavailable": "bylo nedostupné",
|
||||
"changed_to_state": "změněno na {state}",
|
||||
"cleared_device_class": "zrušeno (nebylo zjištěno {device_class})",
|
||||
"detected_device_class": "zjištěno {device_class}",
|
||||
@ -1068,7 +1069,7 @@
|
||||
"delete_confirm": "Opravdu smazat?",
|
||||
"duplicate": "Duplikovat",
|
||||
"header": "Akce",
|
||||
"introduction": "Akce jsou to, co Home Assistant provede při spuštění automatizace. \n\n [Více informací o akcích.] (Https://home-assistant.io/docs/automation/action/)",
|
||||
"introduction": "Akce jsou to, co Home Assistant provede při spuštění automatizace.",
|
||||
"learn_more": "Další informace o akcích",
|
||||
"name": "Akce",
|
||||
"type_select": "Typ akce",
|
||||
@ -1150,7 +1151,7 @@
|
||||
"delete_confirm": "Opravdu smazat?",
|
||||
"duplicate": "Duplikovat",
|
||||
"header": "Podmínky",
|
||||
"introduction": "Podmínky jsou volitelnou součástí automatizačního pravidla a mohou být použity k zabránění spuštění akce. Podmínky vypadají velmi podobně jako spouštěče, ale jsou velmi odlišné. Spouštěč se podívá na události, ke kterým dochází v systému, zatímco podmínka se zabývá pouze tím, jak systém vypadá právě teď. Pro spouštěč se může jevit že je spínač zapnutý. Stav může vidět pouze tehdy, je-li přepínač aktuálně zapnutý nebo vypnutý. \n\n [Další informace o podmínkách.] (Https://home-assistant.io/docs/scripts/conditions/)",
|
||||
"introduction": "Podmínky jsou volitelné a pokud nejsou všechny splněny, zabrání dalšímu provádění automatizace.",
|
||||
"learn_more": "Další informace o podmínkách",
|
||||
"name": "Podmínka",
|
||||
"type_select": "Typ podmínky",
|
||||
@ -1229,7 +1230,7 @@
|
||||
"edit_ui": "Upravit pomocí uživatelského rozhraní",
|
||||
"edit_yaml": "Upravit jako YAML",
|
||||
"enable_disable": "Povolit / zakázat automatizaci",
|
||||
"introduction": "Použijte automatizace k oživení svého domova",
|
||||
"introduction": "Pomocí automatizací oživte svůj domov.",
|
||||
"load_error_not_editable": "Lze upravovat pouze automatizace v automations.yaml.",
|
||||
"load_error_unknown": "Chyba při načítání automatizace ({err_no}).",
|
||||
"max": {
|
||||
@ -1254,7 +1255,7 @@
|
||||
"delete_confirm": "Opravdu smazat?",
|
||||
"duplicate": "Duplikovat",
|
||||
"header": "Spouštěče",
|
||||
"introduction": "Spouštěče spouštějí zpracování automatizačního pravidla. Pro stejné pravidlo je možné zadat více spouštěčů. Po spuštění spouštěče ověří Home Assistant případné podmínky a zavolá akci. \n\n [Další informace o spouštěčích.] (Https://home-assistant.io/docs/automation/trigger/)",
|
||||
"introduction": "Spouštěče spouštějí automatizaci. Pro jednu automatizaci je možné zadat více spouštěčů. Po spuštění ověří Home Assistant případné podmínky a zavolá akci.",
|
||||
"learn_more": "Další informace o spouštěčích",
|
||||
"name": "Spouštěč",
|
||||
"type_select": "Typ spouštěče",
|
||||
@ -1391,7 +1392,7 @@
|
||||
"error_no_url": "Zadejte adresu URL šablonky konfigurace",
|
||||
"header": "Přidat novou šablonku konfigurace",
|
||||
"import_btn": "Importovat šablonku konfigurace",
|
||||
"import_header": "Importovat {name} ({domain})",
|
||||
"import_header": "Importovat \"{name}\" (typ: {domain})",
|
||||
"import_introduction": "Z Githubu a komunitních fór můžete importovat šablonky konfigurace sdílené ostatními uživateli. Zadejte adresu URL šablonky konfigurace.",
|
||||
"importing": "Importuje se šablonka konfigurace",
|
||||
"save_btn": "Uložit šablonku konfigurace",
|
||||
@ -1399,13 +1400,14 @@
|
||||
"url": "URL šablonky konfigurace"
|
||||
},
|
||||
"overview": {
|
||||
"add_blueprint": "Přidat šablonku konfigurace",
|
||||
"confirm_delete_header": "Odstranit tuto šablonku konfigurace?",
|
||||
"confirm_delete_text": "Opravdu chcete smazat tuto šablonku konfigurace?",
|
||||
"headers": {
|
||||
"domain": "Doména",
|
||||
"file_name": "Název souboru",
|
||||
"name": "Jméno"
|
||||
},
|
||||
"learn_more": "Další informace o šablonkách konfigurace"
|
||||
"learn_more": "Další informace o šablonkách konfigurace",
|
||||
"use_blueprint": "Vytvořit automatizaci"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
@ -1722,6 +1724,7 @@
|
||||
},
|
||||
"header": "Entity",
|
||||
"headers": {
|
||||
"area": "Oblast",
|
||||
"entity_id": "ID entity",
|
||||
"integration": "Integrace",
|
||||
"name": "Název",
|
||||
@ -2378,7 +2381,7 @@
|
||||
"delete_user": "Odstranit uživatele",
|
||||
"group": "Skupina",
|
||||
"id": "ID",
|
||||
"name": "Jméno",
|
||||
"name": "Zobrazované jméno",
|
||||
"new_password": "Nové heslo",
|
||||
"owner": "Vlastník",
|
||||
"password_changed": "Heslo bylo změněno",
|
||||
@ -2386,19 +2389,24 @@
|
||||
"system_generated_users_not_editable": "Nelze aktualizovat uživatele generované systémem.",
|
||||
"system_generated_users_not_removable": "Nelze odebrat uživatele generované systémem.",
|
||||
"unnamed_user": "Nepojmenovaný uživatel",
|
||||
"update_user": "Aktualizovat"
|
||||
"update_user": "Aktualizovat",
|
||||
"username": "Uživatelské jméno"
|
||||
},
|
||||
"picker": {
|
||||
"add_user": "Přidat uživatele",
|
||||
"headers": {
|
||||
"group": "Skupina",
|
||||
"name": "Název",
|
||||
"system": "Systémový"
|
||||
"is_active": "Aktivní",
|
||||
"is_owner": "Vlastník",
|
||||
"name": "Zobrazované jméno",
|
||||
"system": "Vygenerovaný systémem",
|
||||
"username": "Uživatelské jméno"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "Skupiny uživatelů jsou v přípravě. Uživatel je nebude moci spravovat prostřednictvím uživatelského rozhraní. Stále kontrolujeme API pro správu, abychom zajistili, že správně omezuje přístup pouze pro administrátory."
|
||||
},
|
||||
"zha": {
|
||||
"add_device": "Přidat zařízení",
|
||||
"add_device_page": {
|
||||
"discovered_text": "Jakmile se objeví nalezne, zobrazí se zde.",
|
||||
"discovery_text": "Zde se objeví nalezená zařízení. Postupujte dle pokynů pro vaše zařízení a uveďte je do režimu párování.",
|
||||
@ -2444,6 +2452,16 @@
|
||||
"value": "Hodnota"
|
||||
},
|
||||
"description": "Správa Zigbee Home Automation",
|
||||
"device_pairing_card": {
|
||||
"CONFIGURED": "Nastavení dokončeno",
|
||||
"CONFIGURED_status_text": "Inicializuji",
|
||||
"INITIALIZED": "Inicializace dokončena",
|
||||
"INITIALIZED_status_text": "Zařízení je připraveno k použití",
|
||||
"INTERVIEW_COMPLETE": "Dotazování dokončeno",
|
||||
"INTERVIEW_COMPLETE_status_text": "Nastavuji",
|
||||
"PAIRED": "Zařízení nalezeno",
|
||||
"PAIRED_status_text": "Začínám dotazování"
|
||||
},
|
||||
"devices": {
|
||||
"header": "Zigbee Home Automation - Zařízení"
|
||||
},
|
||||
@ -2459,6 +2477,7 @@
|
||||
"unbind_button_label": "Odpojit skupinu"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Přidat do skupiny",
|
||||
"add_members": "Přidání členů",
|
||||
"adding_members": "Přidání členů",
|
||||
"caption": "Skupiny",
|
||||
@ -2501,7 +2520,11 @@
|
||||
"hint_wakeup": "Některá zařízení, jako jsou senzory Xiaomi, mají tlačítko probuzení, které můžete stisknout v intervalu ~ 5 sekund, které udržuje zařízení probuzené, když s nimi komunikujete.",
|
||||
"introduction": "Spouštějte ZHA příkazy, které ovlivňují specifické zařízení. Vyberte zařízení pro zobrazení seznamu dostupných příkazů."
|
||||
},
|
||||
"title": "Zigbee Home Automation"
|
||||
"title": "Zigbee Home Automation",
|
||||
"visualization": {
|
||||
"caption": "Vizualizace",
|
||||
"header": "Vizualizace sítě"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"add_zone": "Přidat zónu",
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Besitzer",
|
||||
"system-admin": "Administratoren",
|
||||
"system-read-only": "Nur-Lesen Benutzer",
|
||||
"system-users": "Benutzer"
|
||||
@ -932,7 +933,7 @@
|
||||
"telegram": "Telegram-Benachrichtigungsdienst neu laden",
|
||||
"template": "Templates neu laden",
|
||||
"trend": "Trend Entitäten neu laden",
|
||||
"universal": "Universelle Medien Player Entitäten neu laden ",
|
||||
"universal": "Universelle Medien Player Entitäten neu laden",
|
||||
"zone": "Zonen neu laden"
|
||||
},
|
||||
"server_control": {
|
||||
@ -1239,7 +1240,7 @@
|
||||
"edit_ui": "Mit der Benutzeroberfläche bearbeiten",
|
||||
"edit_yaml": "Als YAML bearbeiten",
|
||||
"enable_disable": "Automatisierung aktivieren / deaktivieren",
|
||||
"introduction": "Benutze Automatisierungen, um deinem Zuhause Leben einzuhauchen",
|
||||
"introduction": "Benutze Automatisierungen, um deinem Zuhause Leben einzuhauchen.",
|
||||
"load_error_not_editable": "Nur Automatisierungen in automations.yaml sind editierbar.",
|
||||
"load_error_unknown": "Fehler beim Laden der Automatisierung ({err_no}).",
|
||||
"max": {
|
||||
@ -1398,7 +1399,7 @@
|
||||
},
|
||||
"blueprint": {
|
||||
"add": {
|
||||
"error_no_url": "Bitte gebe die URL des Blueprints ein.",
|
||||
"error_no_url": "Bitte gebe die URL des Bauplans ein.",
|
||||
"header": "Neuen Bauplan hinzufügen",
|
||||
"import_btn": "Bauplan importieren",
|
||||
"import_header": "{name}({domain}) importieren",
|
||||
@ -1407,7 +1408,7 @@
|
||||
"save_btn": "Bauplan speichern",
|
||||
"saving": "Bauplan wir gespeichert...",
|
||||
"unsupported_blueprint": "Dieser Bauplan wird nicht unterstützt",
|
||||
"url": "URL des Blueprints"
|
||||
"url": "URL des Bauplans"
|
||||
},
|
||||
"caption": "Baupläne",
|
||||
"description": "Baupläne verwalten",
|
||||
@ -1417,10 +1418,13 @@
|
||||
"confirm_delete_text": "Bist du sicher, dass du diesen Bauplan löschen möchtest?",
|
||||
"header": "Bauplan-Editor",
|
||||
"headers": {
|
||||
"domain": "Domain",
|
||||
"file_name": "Dateiname",
|
||||
"name": "Name"
|
||||
},
|
||||
"introduction": "Mit dem Bauplan-Editor kannst du Baupläne erstellen und bearbeiten.",
|
||||
"learn_more": "Erfahre mehr über Baupläne"
|
||||
"learn_more": "Erfahre mehr über Baupläne",
|
||||
"use_blueprint": "Automatisierung erstellen"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
@ -1737,6 +1741,7 @@
|
||||
},
|
||||
"header": "Entitäten",
|
||||
"headers": {
|
||||
"area": "Bereich",
|
||||
"entity_id": "Entitäts-ID",
|
||||
"integration": "Integration",
|
||||
"name": "Name",
|
||||
@ -2401,19 +2406,24 @@
|
||||
"system_generated_users_not_editable": "Systemgenerierte Benutzer können nicht aktualisiert werden.",
|
||||
"system_generated_users_not_removable": "Vom System generierte Benutzer können nicht entfernt werden.",
|
||||
"unnamed_user": "Unbenannter Benutzer",
|
||||
"update_user": "Aktualisieren"
|
||||
"update_user": "Aktualisieren",
|
||||
"username": "Benutzername"
|
||||
},
|
||||
"picker": {
|
||||
"add_user": "Benutzer hinzufügen",
|
||||
"headers": {
|
||||
"group": "Gruppe",
|
||||
"is_active": "Aktiv",
|
||||
"is_owner": "Besitzer",
|
||||
"name": "Name",
|
||||
"system": "System"
|
||||
"system": "System",
|
||||
"username": "Benutzername"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "Benutzergruppen sind derzeit noch in Entwicklung. Der Benutzer wird nicht in der Lage sein, Änderungen an der Instanz über das UI vorzunehmen. Derzeit überprüfen wir noch alle API Endpunkte um sicherzustellen dass diese nur von Administratoren genutzt werden können."
|
||||
},
|
||||
"zha": {
|
||||
"add_device": "Gerät hinzufügen",
|
||||
"add_device_page": {
|
||||
"discovered_text": "Geräte werden hier angezeigt sobald sie erkannt worden sind.",
|
||||
"discovery_text": "Erkannte Geräte werden hier angezeigt. Befolgen Sie die Anweisungen für Ihr Gerät und versetzen Sie das Gerät in den Pairing-Modus.",
|
||||
@ -2459,6 +2469,14 @@
|
||||
"value": "Wert"
|
||||
},
|
||||
"description": "Zigbee Home Automation Netzwerkmanagement",
|
||||
"device_pairing_card": {
|
||||
"CONFIGURED": "Konfiguration abgeschlossen",
|
||||
"CONFIGURED_status_text": "Initialisieren",
|
||||
"INITIALIZED": "Initialisierung abgeschlossen",
|
||||
"INITIALIZED_status_text": "Das Gerät ist einsatzbereit",
|
||||
"INTERVIEW_COMPLETE_status_text": "Konfigurieren",
|
||||
"PAIRED": "Gerät gefunden"
|
||||
},
|
||||
"devices": {
|
||||
"header": "Zigbee Home Automation - Gerät"
|
||||
},
|
||||
@ -2474,6 +2492,7 @@
|
||||
"unbind_button_label": "Gruppe auflösen"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Gruppe hinzufügen",
|
||||
"add_members": "Mitglieder hinzufügen",
|
||||
"adding_members": "Füge Mitglieder hinzu",
|
||||
"caption": "Gruppen",
|
||||
@ -2516,7 +2535,11 @@
|
||||
"hint_wakeup": "Einige Geräte, z. B. Xiaomi-Sensoren, verfügen über eine Wecktaste, die in Intervallen von ~5 Sekunden gedrückt werden kann, um die Geräte wach zu halten, während mit ihnen interagiert wird.",
|
||||
"introduction": "ZHA-Befehle ausführen, die sich auf ein einzelnes Gerät auswirken. Wählen Sie ein Gerät aus, um eine Liste der verfügbaren Befehle anzuzeigen."
|
||||
},
|
||||
"title": "Zigbee Home Automation"
|
||||
"title": "Zigbee Home Automation",
|
||||
"visualization": {
|
||||
"caption": "Visualisierung",
|
||||
"header": "Netzwerkvisualisierung"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"add_zone": "Zone hinzufügen",
|
||||
@ -3401,7 +3424,7 @@
|
||||
},
|
||||
"enable_shortcuts": {
|
||||
"description": "Aktiviere oder deaktiviere Tastaturkürzel, um verschiedene Aktionen in der Benutzeroberfläche auszuführen.",
|
||||
"header": "Tastatürkürzel"
|
||||
"header": "Tastaturkürzel"
|
||||
},
|
||||
"force_narrow": {
|
||||
"description": "Dies blendet die Seitenleiste standardmäßig aus, ähnlich der Nutzung auf Mobilgeräten.",
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Owner",
|
||||
"system-admin": "Administrators",
|
||||
"system-read-only": "Read-Only Users",
|
||||
"system-users": "Users"
|
||||
@ -1239,7 +1240,7 @@
|
||||
"edit_ui": "Edit with UI",
|
||||
"edit_yaml": "Edit as YAML",
|
||||
"enable_disable": "Enable/Disable automation",
|
||||
"introduction": "Use automations to bring your home alive.",
|
||||
"introduction": "Use automations to bring your home to live.",
|
||||
"load_error_not_editable": "Only automations in automations.yaml are editable.",
|
||||
"load_error_unknown": "Error loading automation ({err_no}).",
|
||||
"max": {
|
||||
@ -1399,11 +1400,13 @@
|
||||
"blueprint": {
|
||||
"add": {
|
||||
"error_no_url": "Please enter the URL of the blueprint.",
|
||||
"file_name": "Local blueprint file name",
|
||||
"header": "Add new blueprint",
|
||||
"import_btn": "Import blueprint",
|
||||
"import_header": "Import {name} ({domain})",
|
||||
"import_header": "Import \"{name}\" (type: {domain})",
|
||||
"import_introduction": "You can import blueprints of other users from Github and the community forums. Enter the URL of the blueprint below.",
|
||||
"importing": "Importing blueprint...",
|
||||
"raw_blueprint": "Blueprint content",
|
||||
"save_btn": "Save blueprint",
|
||||
"saving": "Saving blueprint...",
|
||||
"unsupported_blueprint": "This blueprint is not supported",
|
||||
@ -1412,15 +1415,19 @@
|
||||
"caption": "Blueprints",
|
||||
"description": "Manage blueprints",
|
||||
"overview": {
|
||||
"add_blueprint": "Add blueprint",
|
||||
"add_blueprint": "Import blueprint",
|
||||
"confirm_delete_header": "Delete this blueprint?",
|
||||
"confirm_delete_text": "Are you sure you want to delete this blueprint",
|
||||
"confirm_delete_text": "Are you sure you want to delete this blueprint?",
|
||||
"delete_blueprint": "Delete blueprint",
|
||||
"header": "Blueprint Editor",
|
||||
"headers": {
|
||||
"domain": "Domain",
|
||||
"file_name": "File name",
|
||||
"name": "Name"
|
||||
},
|
||||
"introduction": "The blueprint editor allows you to create and edit blueprints.",
|
||||
"learn_more": "Learn more about blueprints"
|
||||
"learn_more": "Learn more about blueprints",
|
||||
"use_blueprint": "Create automation"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
@ -1737,6 +1744,7 @@
|
||||
},
|
||||
"header": "Entities",
|
||||
"headers": {
|
||||
"area": "Area",
|
||||
"entity_id": "Entity ID",
|
||||
"integration": "Integration",
|
||||
"name": "Name",
|
||||
@ -2209,7 +2217,7 @@
|
||||
"without_device": "Entities without device"
|
||||
},
|
||||
"icon": "Icon",
|
||||
"introduction": "Use scenes to bring your home alive.",
|
||||
"introduction": "Use scenes to bring your home to live.",
|
||||
"load_error_not_editable": "Only scenes in scenes.yaml are editable.",
|
||||
"load_error_unknown": "Error loading scene ({err_no}).",
|
||||
"name": "Name",
|
||||
@ -2393,7 +2401,7 @@
|
||||
"delete_user": "Delete user",
|
||||
"group": "Group",
|
||||
"id": "ID",
|
||||
"name": "Name",
|
||||
"name": "Display name",
|
||||
"new_password": "New Password",
|
||||
"owner": "Owner",
|
||||
"password_changed": "Password was changed successfully",
|
||||
@ -2401,19 +2409,24 @@
|
||||
"system_generated_users_not_editable": "Unable to update system generated users.",
|
||||
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
||||
"unnamed_user": "Unnamed User",
|
||||
"update_user": "Update"
|
||||
"update_user": "Update",
|
||||
"username": "Username"
|
||||
},
|
||||
"picker": {
|
||||
"add_user": "Add user",
|
||||
"headers": {
|
||||
"group": "Group",
|
||||
"name": "Name",
|
||||
"system": "System"
|
||||
"is_active": "Active",
|
||||
"is_owner": "Owner",
|
||||
"name": "Display name",
|
||||
"system": "System generated",
|
||||
"username": "Username"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "The user group feature is a work in progress. The user will be unable to administer the instance via the UI. We're still auditing all management API endpoints to ensure that they correctly limit access to administrators."
|
||||
},
|
||||
"zha": {
|
||||
"add_device": "Add Device",
|
||||
"add_device_page": {
|
||||
"discovered_text": "Devices will show up here once discovered.",
|
||||
"discovery_text": "Discovered devices will show up here. Follow the instructions for your device(s) and place the device(s) in pairing mode.",
|
||||
@ -2459,6 +2472,16 @@
|
||||
"value": "Value"
|
||||
},
|
||||
"description": "Zigbee Home Automation network management",
|
||||
"device_pairing_card": {
|
||||
"CONFIGURED": "Configuration Complete",
|
||||
"CONFIGURED_status_text": "Initializing",
|
||||
"INITIALIZED": "Initialization Complete",
|
||||
"INITIALIZED_status_text": "The device is ready to use",
|
||||
"INTERVIEW_COMPLETE": "Interview Complete",
|
||||
"INTERVIEW_COMPLETE_status_text": "Configuring",
|
||||
"PAIRED": "Device Found",
|
||||
"PAIRED_status_text": "Starting Interview"
|
||||
},
|
||||
"devices": {
|
||||
"header": "Zigbee Home Automation - Device"
|
||||
},
|
||||
@ -2474,6 +2497,7 @@
|
||||
"unbind_button_label": "Unbind Group"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Add Group",
|
||||
"add_members": "Add Members",
|
||||
"adding_members": "Adding Members",
|
||||
"caption": "Groups",
|
||||
@ -2516,7 +2540,11 @@
|
||||
"hint_wakeup": "Some devices such as Xiaomi sensors have a wake up button that you can press at ~5 second intervals that keep devices awake while you interact with them.",
|
||||
"introduction": "Run ZHA commands that affect a single device. Pick a device to see a list of available commands."
|
||||
},
|
||||
"title": "Zigbee Home Automation"
|
||||
"title": "Zigbee Home Automation",
|
||||
"visualization": {
|
||||
"caption": "Visualization",
|
||||
"header": "Network Visualization"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"add_zone": "Add Zone",
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Propietario",
|
||||
"system-admin": "Administradores",
|
||||
"system-read-only": "Usuarios de solo lectura",
|
||||
"system-users": "Usuarios"
|
||||
@ -1147,7 +1148,7 @@
|
||||
},
|
||||
"alias": "Nombre",
|
||||
"blueprint": {
|
||||
"blueprint_to_use": "Plano a utilizar",
|
||||
"blueprint_to_use": "Plano a usar",
|
||||
"header": "Plano",
|
||||
"inputs": "Entradas",
|
||||
"manage_blueprints": "Administrar planos",
|
||||
@ -1239,7 +1240,7 @@
|
||||
"edit_ui": "Editar con la interfaz de usuario",
|
||||
"edit_yaml": "Editar como YAML",
|
||||
"enable_disable": "Activar/Desactivar automatización",
|
||||
"introduction": "Utiliza automatizaciones para darle vida a tu hogar.",
|
||||
"introduction": "Usa automatizaciones para darle vida a tu hogar.",
|
||||
"load_error_not_editable": "Solo las automatizaciones en automations.yaml son editables.",
|
||||
"load_error_unknown": "Error al cargar la automatización ({err_no}).",
|
||||
"max": {
|
||||
@ -1399,11 +1400,13 @@
|
||||
"blueprint": {
|
||||
"add": {
|
||||
"error_no_url": "Por favor, introduce la URL del plano.",
|
||||
"file_name": "Nombre de archivo del plano local",
|
||||
"header": "Añadir nuevo plano",
|
||||
"import_btn": "Importar plano",
|
||||
"import_header": "Importar {name} ({domain})",
|
||||
"import_header": "Importar {name} (tipo: {domain})",
|
||||
"import_introduction": "Puedes importar planos de otros usuarios desde Github y los foros de la comunidad. Introduce la URL del plano a continuación.",
|
||||
"importing": "Importando plano...",
|
||||
"raw_blueprint": "Contenido del plano",
|
||||
"save_btn": "Guardar plano",
|
||||
"saving": "Guardando plano...",
|
||||
"unsupported_blueprint": "Este plano no es compatible",
|
||||
@ -1412,15 +1415,19 @@
|
||||
"caption": "Planos",
|
||||
"description": "Administrar planos",
|
||||
"overview": {
|
||||
"add_blueprint": "Añadir plano",
|
||||
"add_blueprint": "Importar plano",
|
||||
"confirm_delete_header": "¿Eliminar este plano?",
|
||||
"confirm_delete_text": "¿Estás seguro de que quieres borrar este plano?",
|
||||
"delete_blueprint": "Eliminar plano",
|
||||
"header": "Editor de planos",
|
||||
"headers": {
|
||||
"domain": "Dominio",
|
||||
"file_name": "Nombre de archivo",
|
||||
"name": "Nombre"
|
||||
},
|
||||
"introduction": "El editor de planos te permite crear y editar planos.",
|
||||
"learn_more": "Más información sobre planos"
|
||||
"learn_more": "Más información sobre planos",
|
||||
"use_blueprint": "Crear automatización"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
@ -1737,6 +1744,7 @@
|
||||
},
|
||||
"header": "Entidades",
|
||||
"headers": {
|
||||
"area": "Área",
|
||||
"entity_id": "ID de entidad",
|
||||
"integration": "Integración",
|
||||
"name": "Nombre",
|
||||
@ -2209,7 +2217,7 @@
|
||||
"without_device": "Entidades sin dispositivo"
|
||||
},
|
||||
"icon": "Icono",
|
||||
"introduction": "Usa escenas para dar vida a tu hogar.",
|
||||
"introduction": "Usa escenas para darle vida a tu hogar.",
|
||||
"load_error_not_editable": "Solo las escenas de scenes.yaml son editables.",
|
||||
"load_error_unknown": "Error al cargar la escena ({err_no}).",
|
||||
"name": "Nombre",
|
||||
@ -2393,7 +2401,7 @@
|
||||
"delete_user": "Eliminar usuario",
|
||||
"group": "Grupo",
|
||||
"id": "ID",
|
||||
"name": "Nombre",
|
||||
"name": "Nombre para mostrar",
|
||||
"new_password": "Nueva contraseña",
|
||||
"owner": "Propietario",
|
||||
"password_changed": "La contraseña se ha cambiado correctamente",
|
||||
@ -2401,19 +2409,24 @@
|
||||
"system_generated_users_not_editable": "No se pueden actualizar los usuarios generados por el sistema.",
|
||||
"system_generated_users_not_removable": "No se pueden eliminar los usuarios generados por el sistema.",
|
||||
"unnamed_user": "Usuario sin nombre",
|
||||
"update_user": "Actualizar"
|
||||
"update_user": "Actualizar",
|
||||
"username": "Nombre de usuario"
|
||||
},
|
||||
"picker": {
|
||||
"add_user": "Añadir usuario",
|
||||
"headers": {
|
||||
"group": "Grupo",
|
||||
"name": "Nombre",
|
||||
"system": "Sistema"
|
||||
"is_active": "Activo",
|
||||
"is_owner": "Propietario",
|
||||
"name": "Nombre para mostrar",
|
||||
"system": "Generado por el sistema",
|
||||
"username": "Nombre de usuario"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "El grupo de usuarios es un trabajo en progreso. El usuario no podrá administrar la instancia a través de la IU. Todavía estamos auditando todos los endpoints de la API de administración para garantizar que se limita correctamente el acceso sólo a los administradores."
|
||||
},
|
||||
"zha": {
|
||||
"add_device": "Añadir dispositivo",
|
||||
"add_device_page": {
|
||||
"discovered_text": "Los dispositivos aparecerán aquí una vez descubiertos.",
|
||||
"discovery_text": "Los dispositivos detectados aparecerán aquí. Ponlos en modo emparejamiento siguiendo sus instrucciones.",
|
||||
@ -2459,6 +2472,16 @@
|
||||
"value": "Valor"
|
||||
},
|
||||
"description": "Administración de red de Domótica Zigbee",
|
||||
"device_pairing_card": {
|
||||
"CONFIGURED": "Configuración completada",
|
||||
"CONFIGURED_status_text": "Inicializando",
|
||||
"INITIALIZED": "Inicialización completada",
|
||||
"INITIALIZED_status_text": "El dispositivo está listo para ser usado",
|
||||
"INTERVIEW_COMPLETE": "Entrevista completada",
|
||||
"INTERVIEW_COMPLETE_status_text": "Configurando",
|
||||
"PAIRED": "Dispositivo encontrado",
|
||||
"PAIRED_status_text": "Iniciando entrevista"
|
||||
},
|
||||
"devices": {
|
||||
"header": "Domótica Zigbee - Dispositivo"
|
||||
},
|
||||
@ -2474,6 +2497,7 @@
|
||||
"unbind_button_label": "Desvincular grupo"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Añadir grupo",
|
||||
"add_members": "Añadir miembros",
|
||||
"adding_members": "Agregar miembros",
|
||||
"caption": "Grupos",
|
||||
@ -2516,7 +2540,11 @@
|
||||
"hint_wakeup": "Algunos dispositivos, como los sensores Xiaomi, tienen un botón de activación que puedes presionar a intervalos de ~ 5 segundos para mantener los dispositivos despiertos mientras interactúas con ellos.",
|
||||
"introduction": "Ejecuta comandos ZHA que afecten a un único dispositivo. Elije un dispositivo para ver una lista de comandos disponibles."
|
||||
},
|
||||
"title": "Domótica Zigbee"
|
||||
"title": "Domótica Zigbee",
|
||||
"visualization": {
|
||||
"caption": "Visualización",
|
||||
"header": "Visualización de la red"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"add_zone": "Añadir zona",
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"owner": "Omanik",
|
||||
"system-admin": "Administraatorid",
|
||||
"system-read-only": "Ainult lugemisõigusega kasutajad",
|
||||
"system-users": "Kasutajad"
|
||||
@ -967,7 +968,7 @@
|
||||
"manuf": "{manufacturer} järgi",
|
||||
"no_area": "Ala puudub",
|
||||
"power_source": "Toiteallikas",
|
||||
"quirk": "Erim",
|
||||
"quirk": "Pistikäpp",
|
||||
"services": {
|
||||
"reconfigure": "Taasseadista (tervenda) ZHA seade. Kasuta seda, kui seadmega on probleeme. Kui seade on akutoitega, siis veendu, et see oleks ärkvel ja oleks valmis käske vastu võtma.",
|
||||
"remove": "Eemalda seade Zigbee võrgust.",
|
||||
@ -1399,11 +1400,13 @@
|
||||
"blueprint": {
|
||||
"add": {
|
||||
"error_no_url": "Sisesta kavandi URL.",
|
||||
"file_name": "Kohaliku kavandifaili nimi",
|
||||
"header": "Uue kavandi lisamine",
|
||||
"import_btn": "Impordi kavand",
|
||||
"import_header": "Impordi {name} ( {domain} )",
|
||||
"import_header": "Impordi {name} ( type:{domain} )",
|
||||
"import_introduction": "Teiste kasutajate kavandeid saad importida Githubist ja kogukonna foorumitest. Sisesta alloleva kavandi URL.",
|
||||
"importing": "Kavandi importimine ...",
|
||||
"raw_blueprint": "Kavandi sisu",
|
||||
"save_btn": "Salvesta kavand",
|
||||
"saving": "Kavandi salvestamine ...",
|
||||
"unsupported_blueprint": "Seda kavandit ei toetata",
|
||||
@ -1412,15 +1415,19 @@
|
||||
"caption": "",
|
||||
"description": "Kavandite haldamine",
|
||||
"overview": {
|
||||
"add_blueprint": "Lisa kavand",
|
||||
"add_blueprint": "Laadi kavand",
|
||||
"confirm_delete_header": "Kas kustutada see kavand?",
|
||||
"confirm_delete_text": "Kas soovid kindlasti selle kavandi kustutada?",
|
||||
"delete_blueprint": "Kustuta kavand",
|
||||
"header": "Kavandi redaktor",
|
||||
"headers": {
|
||||
"domain": "Domeen",
|
||||
"file_name": "Faili nimi",
|
||||
"name": "Nimi"
|
||||
},
|
||||
"introduction": "Kavandite redaktor võimaldab luua ja muuta kavandeid.",
|
||||
"learn_more": "Lisateave kavandite kohta"
|
||||
"learn_more": "Lisateave kavandite kohta",
|
||||
"use_blueprint": "Loo automatiseering"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
@ -1737,6 +1744,7 @@
|
||||
},
|
||||
"header": "Olemid",
|
||||
"headers": {
|
||||
"area": "Ala",
|
||||
"entity_id": "Olemi ID",
|
||||
"integration": "Sidumine",
|
||||
"name": "Nimi",
|
||||
@ -2393,7 +2401,7 @@
|
||||
"delete_user": "Kustuta kasutaja",
|
||||
"group": "Grupp",
|
||||
"id": "ID",
|
||||
"name": "Nimi",
|
||||
"name": "Kuvatav nimi",
|
||||
"new_password": "Uus salasõna",
|
||||
"owner": "Omanik",
|
||||
"password_changed": "Salasõna muutmine õnnestus",
|
||||
@ -2401,19 +2409,24 @@
|
||||
"system_generated_users_not_editable": "Süsteemi loodud kasutajaid ei saa värskendada.",
|
||||
"system_generated_users_not_removable": "Süsteemi loodud kasutajaid ei saa eemaldada.",
|
||||
"unnamed_user": "Nimetu kasutaja",
|
||||
"update_user": "Uuenda"
|
||||
"update_user": "Uuenda",
|
||||
"username": "Kasutajanimi"
|
||||
},
|
||||
"picker": {
|
||||
"add_user": "Lisa kasutaja",
|
||||
"headers": {
|
||||
"group": "Grupp",
|
||||
"name": "Nimi",
|
||||
"system": "Süsteem"
|
||||
"is_active": "Aktiivne",
|
||||
"is_owner": "Omanik",
|
||||
"name": "Kuvatav nimi",
|
||||
"system": "Süsteemi poolt loodud",
|
||||
"username": "Kasutajanimi"
|
||||
}
|
||||
},
|
||||
"users_privileges_note": "Kasutajarühma funktsioon on pooleli. Kasutajal ei ole võimalik kasutajaliidese kaudu Home Assistanti hallata. Auditeerime endiselt kõiki haldus-API lõpp-punkte tagamaks, et need lubaks juurdepääsu ainult administraatoritele."
|
||||
},
|
||||
"zha": {
|
||||
"add_device": "Lisa seade",
|
||||
"add_device_page": {
|
||||
"discovered_text": "Seadmed ilmuvad siia kui avastatakse.",
|
||||
"discovery_text": "Leitud seadmed kuvatakse siin. Järgige seadme(te) juhiseid ja pange seade või seadmed sidumisrežiimile.",
|
||||
@ -2459,6 +2472,16 @@
|
||||
"value": "Väärtus"
|
||||
},
|
||||
"description": "Zigbee Home Automation võrgu haldamine",
|
||||
"device_pairing_card": {
|
||||
"CONFIGURED": "Seadistamine on lõpetatud",
|
||||
"CONFIGURED_status_text": "Lähtestan",
|
||||
"INITIALIZED": "Lähtestamine on lõpetatud",
|
||||
"INITIALIZED_status_text": "Seade on kasutamiseks valmis",
|
||||
"INTERVIEW_COMPLETE": "Küsitlemine on lõpetatud",
|
||||
"INTERVIEW_COMPLETE_status_text": "Seadistan",
|
||||
"PAIRED": "Seade on leitud",
|
||||
"PAIRED_status_text": "Alustan küsitlemist"
|
||||
},
|
||||
"devices": {
|
||||
"header": "Zigbee Home Automation - Seade"
|
||||
},
|
||||
@ -2474,6 +2497,7 @@
|
||||
"unbind_button_label": "Sidesta grupp lahti"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Lisa grupp",
|
||||
"add_members": "Lisa liikmeid",
|
||||
"adding_members": "Liikmete lisamine",
|
||||
"caption": "Grupid",
|
||||
@ -2516,7 +2540,11 @@
|
||||
"hint_wakeup": "Mõnel seadmel (näiteks Xiaomi anduritel) on äratusnupp mida saate vajutada ~ 5-sekundiliste intervallidega ja mis hoiab seadmeid ärkvel kui te nendega suhtlete.",
|
||||
"introduction": "Käivitage ZHA käsud, mis mõjutavad ühte seadet. Saadaolevate käskude loendi kuvamiseks valige seade."
|
||||
},
|
||||
"title": "Zigbee Home Automation"
|
||||
"title": "Zigbee Home Automation",
|
||||
"visualization": {
|
||||
"caption": "Visualiseerimine",
|
||||
"header": "Võrgu visualiseerimine"
|
||||
}
|
||||
},
|
||||
"zone": {
|
||||
"add_zone": "Lisa tsoon",
|
||||
|
@ -1171,7 +1171,7 @@
|
||||
"trigger": "Laukaisin"
|
||||
},
|
||||
"event": {
|
||||
"context_user_pick": "Lisää käyttäjä",
|
||||
"context_user_pick": "Valitse käyttäjä",
|
||||
"context_user_picked": "Käyttäjän laukaisutapahtuma",
|
||||
"context_users": "Rajoita tapahtumiin, jotka ovat käynnistäneet",
|
||||
"event_data": "Tapahtuman tietosisältö",
|
||||
@ -2113,6 +2113,7 @@
|
||||
"person": "Lataa henkilöt uudelleen",
|
||||
"ping": "Lataa ping-binaarianturin entiteetit uudelleen",
|
||||
"reload": "Lataa uudelleen {domain}",
|
||||
"rest": "Uudelleenlataa rest-entiteetit ja ilmoita palveluiden kuuntelijoille",
|
||||
"rpi_gpio": "Lataa Raspberry Pi GPIO -entiteetit uudelleen",
|
||||
"scene": "Lataa tilanteet uudelleen",
|
||||
"script": "Lataa skriptit uudelleen",
|
||||
@ -2862,7 +2863,7 @@
|
||||
},
|
||||
"raw_editor": {
|
||||
"confirm_remove_config_text": "Lovelace käyttöliittymän näkymät luodaan automaattisesti alueistasi ja laitteistasi, jos poistat nykyisen määrityksen.",
|
||||
"confirm_remove_config_title": "Haluatko varmasti poistaa Lovelace-käyttöliittymän asetukset? Lovelace käyttöliittymän asetukset luodaan automaattisesti alueistasi ja laitteistasi.",
|
||||
"confirm_remove_config_title": "Haluatko varmasti poistaa Lovelace-käyttöliittymän asetukset?",
|
||||
"confirm_unsaved_changes": "Sinulla on tallentamattomia muutoksia. Haluatko varmasti poistua?",
|
||||
"confirm_unsaved_comments": "Asetuksesi sisältää kommentoituja rivejä. Kommentoituja rivejä ei tallenneta. Haluatko jatkaa?",
|
||||
"error_invalid_config": "Asetuksesi ovat virheelliset: {error}",
|
||||
@ -2906,7 +2907,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"close": "Sulje",
|
||||
"configure_ui": "Määrittele käyttöliittymä",
|
||||
"configure_ui": "Muokkaa käyttöliittymää",
|
||||
"exit_edit_mode": "Poistu käyttöliittymän muokkaustilasta",
|
||||
"help": "Apua",
|
||||
"refresh": "Päivitä",
|
||||
@ -2915,7 +2916,7 @@
|
||||
},
|
||||
"reload_lovelace": "Lataa Lovelace uudelleen",
|
||||
"reload_resources": {
|
||||
"refresh_body": "Sinun täytyy päivittää sivu viimeistelläksesi uudelleenlatauksen, haluatko päivittää nyt?",
|
||||
"refresh_body": "Sinun täytyy päivittää sivu viimeistelläksesi uudelleenlatauksen. Haluatko päivittää nyt?",
|
||||
"refresh_header": "Haluatko päivittää?"
|
||||
},
|
||||
"unused_entities": {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user