mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-30 04:51:35 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88b36ec314 | |||
| f31a7c3af0 | |||
| 44d91eaa4f | |||
| 3cc1cb7893 | |||
| e7354ed5a2 | |||
| e3ac2c149d | |||
| afcd45a780 | |||
| fe87466351 | |||
| bdef924426 | |||
| 86ea3082f7 | |||
| 0374330676 | |||
| d8a68326fb | |||
| 4901d50918 | |||
| a16e41a7ac | |||
| f1d644ac51 | |||
| a9378abe31 | |||
| 5c2fcd7f9b |
@@ -71,7 +71,6 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
unique_id: "co2_intensity",
|
||||
aliases: [],
|
||||
},
|
||||
{
|
||||
config_entry_id: "co2signal",
|
||||
@@ -87,7 +86,6 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
unique_id: "grid_fossil_fuel_percentage",
|
||||
aliases: [],
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -98,7 +98,9 @@ const alerts: {
|
||||
description: "Alert with slotted image",
|
||||
type: "warning",
|
||||
iconSlot: html`<span slot="icon" class="image"
|
||||
><img src="https://www.home-assistant.io/images/home-assistant-logo.svg"
|
||||
><img
|
||||
alt="Home Assistant logo"
|
||||
src="https://www.home-assistant.io/images/home-assistant-logo.svg"
|
||||
/></span>`,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -197,7 +197,6 @@ const createEntityRegistryEntries = (
|
||||
platform: "updater",
|
||||
has_entity_name: false,
|
||||
unique_id: "updater",
|
||||
aliases: [],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -404,6 +404,7 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
<img
|
||||
class="logo"
|
||||
alt=""
|
||||
src="/api/hassio/addons/${this.addon.slug}/logo"
|
||||
/>
|
||||
`
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20221228.0"
|
||||
version = "20230102.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import {
|
||||
DeviceEntityLookup,
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
@@ -45,7 +34,7 @@ const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-area-devices-picker")
|
||||
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
export class HaAreaDevicesPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -82,25 +71,22 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _areaPicker = true;
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
private _selectedDevices: string[] = [];
|
||||
|
||||
private _filteredDevices: DeviceRegistryEntry[] = [];
|
||||
|
||||
private _getAreasWithDevices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
deviceReg: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
entityReg: HomeAssistant["entities"],
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"]
|
||||
): AreaDevices[] => {
|
||||
const devices = Object.values(deviceReg);
|
||||
const entities = Object.values(entityReg);
|
||||
|
||||
if (!devices.length) {
|
||||
return [];
|
||||
}
|
||||
@@ -164,11 +150,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
this._filteredDevices = inputDevices;
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
const devicesByArea: DevicesByArea = {};
|
||||
|
||||
for (const device of inputDevices) {
|
||||
@@ -177,7 +158,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
if (!(areaId in devicesByArea)) {
|
||||
devicesByArea[areaId] = {
|
||||
id: areaId,
|
||||
name: areaLookup[areaId].name,
|
||||
name: areas[areaId].name,
|
||||
devices: [],
|
||||
};
|
||||
}
|
||||
@@ -199,20 +180,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("area") && this.area) {
|
||||
@@ -231,13 +198,10 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._devices || !this._areas || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
const areas = this._getAreasWithDevices(
|
||||
this._devices,
|
||||
this._areas,
|
||||
this._entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.entities,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@@ -7,21 +6,15 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
|
||||
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
|
||||
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
@@ -45,7 +38,7 @@ const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-device-picker")
|
||||
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
export class HaDevicePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -54,12 +47,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public devices?: DeviceRegistryEntry[];
|
||||
|
||||
@property() public areas?: AreaRegistryEntry[];
|
||||
|
||||
@property() public entities?: EntityRegistryEntry[];
|
||||
|
||||
/**
|
||||
* Show only devices with entities from specific domains.
|
||||
* @type {Array}
|
||||
@@ -106,15 +93,18 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
deviceReg: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
entityReg: HomeAssistant["entities"],
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
deviceFilter: this["deviceFilter"],
|
||||
excludeDevices: this["excludeDevices"]
|
||||
): Device[] => {
|
||||
const devices = Object.values(deviceReg);
|
||||
const entities = Object.values(entityReg);
|
||||
|
||||
if (!devices.length) {
|
||||
return [
|
||||
{
|
||||
@@ -138,12 +128,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
let inputDevices = devices.filter(
|
||||
(device) => device.id === this.value || !device.disabled_by
|
||||
);
|
||||
@@ -214,8 +198,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
device.area_id && device.area_id in areas
|
||||
? areas[device.area_id].name
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
}));
|
||||
if (!outputDevices.length) {
|
||||
@@ -246,30 +230,16 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
await this.comboBox?.focus();
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this.devices = devices;
|
||||
}),
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this.areas = areas;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this.entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this._init && this.devices && this.areas && this.entities) ||
|
||||
!this._init ||
|
||||
(this._init && changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
(this.comboBox as any).items = this._getDevices(
|
||||
this.devices!,
|
||||
this.areas!,
|
||||
this.entities!,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.entities,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
|
||||
@@ -28,7 +28,7 @@ class StateInfo extends LitElement {
|
||||
|
||||
const name = computeStateName(this.stateObj);
|
||||
|
||||
return html` <state-badge
|
||||
return html`<state-badge
|
||||
.stateObj=${this.stateObj}
|
||||
.stateColor=${true}
|
||||
.color=${this.color}
|
||||
|
||||
@@ -16,7 +16,11 @@ const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.slug}</span>
|
||||
${item.icon
|
||||
? html`<img slot="graphic" .src="/api/hassio/addons/${item.slug}/icon" />`
|
||||
? html`<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>`
|
||||
: ""}
|
||||
</mwc-list-item>`;
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@ import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { EntityRegistryEntry } from "../data/entity_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-area-picker";
|
||||
|
||||
@customElement("ha-areas-picker")
|
||||
export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
export class HaAreasPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -266,6 +266,9 @@ export class HaBaseTimeInput extends LitElement {
|
||||
seconds: this.seconds,
|
||||
milliseconds: this.milliseconds,
|
||||
};
|
||||
if (this.enableDay) {
|
||||
value.days = this.days;
|
||||
}
|
||||
if (this.format === 12) {
|
||||
value.amPm = this.amPm;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ class HaConfigEntryPicker extends LitElement {
|
||||
>
|
||||
<span slot="secondary">${item.localized_domain_name}</span>
|
||||
<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
src=${brandsUrl({
|
||||
domain: item.domain,
|
||||
|
||||
@@ -3,10 +3,12 @@ import { styles } from "@material/mwc-dialog/mwc-dialog.css";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
|
||||
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button"];
|
||||
|
||||
export const createCloseHeading = (
|
||||
hass: HomeAssistant,
|
||||
title: string | TemplateResult
|
||||
@@ -32,6 +34,14 @@ export class HaDialog extends DialogBase {
|
||||
return html`<slot name="heading"> ${super.renderHeading()} </slot>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
super.firstUpdated();
|
||||
this.suppressDefaultPressSelector = [
|
||||
this.suppressDefaultPressSelector,
|
||||
SUPPRESS_DEFAULT_PRESS_SELECTOR,
|
||||
].join(", ");
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
styles,
|
||||
css`
|
||||
|
||||
@@ -67,6 +67,9 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
@change=${this._valueChanged}
|
||||
></ha-slider>
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -9,23 +9,16 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../data/area_registry";
|
||||
import { AreaRegistryEntry } from "../data/area_registry";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../data/device_registry";
|
||||
import { DeviceRegistryEntry } from "../data/device_registry";
|
||||
import { SceneEntity } from "../data/scene";
|
||||
import { findRelated, ItemType, RelatedResult } from "../data/search";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-switch";
|
||||
|
||||
@customElement("ha-related-items")
|
||||
export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
export class HaRelatedItems extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public itemType!: ItemType;
|
||||
@@ -34,23 +27,8 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _entries?: ConfigEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
@@ -104,11 +82,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
})
|
||||
: ""}
|
||||
${this._related.device && this._devices
|
||||
${this._related.device
|
||||
? this._related.device.map((relatedDeviceId) => {
|
||||
const device: DeviceRegistryEntry | undefined = this._devices!.find(
|
||||
(dev) => dev.id === relatedDeviceId
|
||||
);
|
||||
const device: DeviceRegistryEntry | undefined =
|
||||
this.hass.devices[relatedDeviceId];
|
||||
if (!device) {
|
||||
return "";
|
||||
}
|
||||
@@ -125,11 +102,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
})
|
||||
: ""}
|
||||
${this._related.area && this._areas
|
||||
${this._related.area
|
||||
? this._related.area.map((relatedAreaId) => {
|
||||
const area: AreaRegistryEntry | undefined = this._areas!.find(
|
||||
(ar) => ar.area_id === relatedAreaId
|
||||
);
|
||||
const area: AreaRegistryEntry | undefined =
|
||||
this.hass.areas[relatedAreaId];
|
||||
if (!area) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
@@ -17,13 +14,12 @@ import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
} from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-area-picker";
|
||||
import "../ha-areas-picker";
|
||||
|
||||
@customElement("ha-selector-area")
|
||||
export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
export class HaAreaSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: AreaSelector;
|
||||
@@ -44,12 +40,16 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
if (
|
||||
changedProperties.has("hass") &&
|
||||
(changedProperties.get("hass") as HomeAssistant | undefined)?.entities !==
|
||||
this.hass.entities
|
||||
) {
|
||||
this._entities = Object.values(this.hass.entities).filter(
|
||||
(entity) => entity.device_id !== null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { CSSResultGroup, html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
|
||||
@customElement("ha-tile-image")
|
||||
export class HaTileImage extends LitElement {
|
||||
@property() public imageUrl?: string;
|
||||
|
||||
@property() public imageAlt?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="image">
|
||||
${this.imageUrl ? html`<img src=${this.imageUrl} />` : null}
|
||||
${this.imageUrl
|
||||
? html`<img alt=${ifDefined(this.imageAlt)} src=${this.imageUrl} />`
|
||||
: null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export interface EntityRegistryEntry {
|
||||
original_name?: string;
|
||||
unique_id: string;
|
||||
translation_key?: string;
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
@@ -30,6 +29,7 @@ export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
original_icon?: string;
|
||||
device_class?: string;
|
||||
original_device_class?: string;
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
export interface UpdateEntityRegistryEntryResult {
|
||||
|
||||
@@ -74,7 +74,7 @@ export class HaImagecropperDialog extends LitElement {
|
||||
round: Boolean(this._params?.options.round),
|
||||
})}"
|
||||
>
|
||||
<img />
|
||||
<img alt=${this.hass.localize("ui.dialogs.image_cropper.crop_image")} />
|
||||
</div>
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
|
||||
@@ -19,6 +19,7 @@ class IntegrationBadge extends LitElement {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<img
|
||||
alt=""
|
||||
src=${brandsUrl({
|
||||
domain: this.domain,
|
||||
type: "icon",
|
||||
|
||||
@@ -250,6 +250,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
<ha-recurrence-rule-editor
|
||||
.hass=${this.hass}
|
||||
.dtstart=${this._dtstart}
|
||||
.allDay=${this._allDay}
|
||||
.locale=${this.hass.locale}
|
||||
.timezone=${this.hass.config.time_zone}
|
||||
.value=${this._rrule || ""}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Options, WeekdayStr } from "rrule";
|
||||
import { ByWeekday, RRule, Weekday } from "rrule";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { LocalizeKeys } from "../../common/translations/localize";
|
||||
import "../../components/ha-chip";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-select";
|
||||
@@ -19,12 +20,10 @@ import {
|
||||
getWeekday,
|
||||
getWeekdays,
|
||||
getMonthlyRepeatItems,
|
||||
intervalSuffix,
|
||||
RepeatEnd,
|
||||
RepeatFrequency,
|
||||
ruleByWeekDay,
|
||||
untilValue,
|
||||
WEEKDAY_NAME,
|
||||
MonthlyRepeatItem,
|
||||
getMonthlyRepeatWeekdayFromRule,
|
||||
getMonthdayRepeatFromRule,
|
||||
@@ -41,6 +40,8 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
|
||||
@property() public dtstart?: Date;
|
||||
|
||||
@property() public allDay?: boolean;
|
||||
|
||||
@property({ attribute: false }) public locale!: HomeAssistant["locale"];
|
||||
|
||||
@property() public timezone?: string;
|
||||
@@ -172,18 +173,36 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
return html`
|
||||
<ha-select
|
||||
id="freq"
|
||||
label="Repeat"
|
||||
label=${this.hass.localize("ui.components.calendar.event.repeat.label")}
|
||||
@selected=${this._onRepeatSelected}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._freq}
|
||||
>
|
||||
<ha-list-item value="none">None</ha-list-item>
|
||||
<ha-list-item value="yearly">Yearly</ha-list-item>
|
||||
<ha-list-item value="monthly">Monthly</ha-list-item>
|
||||
<ha-list-item value="weekly">Weekly</ha-list-item>
|
||||
<ha-list-item value="daily">Daily</ha-list-item>
|
||||
<ha-list-item value="none">
|
||||
${this.hass.localize("ui.components.calendar.event.repeat.freq.none")}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="yearly">
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.freq.yearly"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="monthly">
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.freq.monthly"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="weekly">
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.freq.weekly"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="daily">
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.freq.daily"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
@@ -194,7 +213,9 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
${this._monthlyRepeatItems.length > 0
|
||||
? html`<ha-select
|
||||
id="monthly"
|
||||
label="Repeat Monthly"
|
||||
label=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.monthly.label"
|
||||
)}
|
||||
@selected=${this._onMonthlyDetailSelected}
|
||||
.value=${this._monthlyRepeat || this._monthlyRepeatItems[0]?.value}
|
||||
@closed=${stopPropagation}
|
||||
@@ -223,7 +244,11 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
.value=${item}
|
||||
class=${classMap({ active: this._weekday.has(item) })}
|
||||
@click=${this._onWeekdayToggle}
|
||||
>${WEEKDAY_NAME[item]}</ha-chip
|
||||
>${this.hass.localize(
|
||||
`ui.components.calendar.event.repeat.weekly.weekday.${
|
||||
item.toLowerCase() as Lowercase<WeekdayStr>
|
||||
}`
|
||||
)}</ha-chip
|
||||
>
|
||||
`
|
||||
)}
|
||||
@@ -239,11 +264,16 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
return html`
|
||||
<ha-textfield
|
||||
id="interval"
|
||||
label="Repeat interval"
|
||||
label=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.interval.label"
|
||||
)}
|
||||
type="number"
|
||||
min="1"
|
||||
.value=${this._interval}
|
||||
.suffix=${intervalSuffix(this._freq!)}
|
||||
.suffix=${this.hass.localize(
|
||||
`ui.components.calendar.event.repeat.interval.${this
|
||||
._freq!}` as LocalizeKeys
|
||||
)}
|
||||
@change=${this._onIntervalChange}
|
||||
></ha-textfield>
|
||||
`;
|
||||
@@ -253,26 +283,38 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
return html`
|
||||
<ha-select
|
||||
id="end"
|
||||
label="Ends"
|
||||
label=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.end.label"
|
||||
)}
|
||||
.value=${this._end}
|
||||
@selected=${this._onEndSelected}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
<ha-list-item value="never">Never</ha-list-item>
|
||||
<ha-list-item value="after">After</ha-list-item>
|
||||
<ha-list-item value="on">On</ha-list-item>
|
||||
<ha-list-item value="never">
|
||||
${this.hass.localize("ui.components.calendar.event.repeat.end.never")}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="after">
|
||||
${this.hass.localize("ui.components.calendar.event.repeat.end.after")}
|
||||
</ha-list-item>
|
||||
<ha-list-item value="on">
|
||||
${this.hass.localize("ui.components.calendar.event.repeat.end.on")}
|
||||
</ha-list-item>
|
||||
</ha-select>
|
||||
${this._end === "after"
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="after"
|
||||
label="End after"
|
||||
label=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.end_after.label"
|
||||
)}
|
||||
type="number"
|
||||
min="1"
|
||||
.value=${this._count!}
|
||||
suffix="ocurrences"
|
||||
suffix=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.end_after.ocurrences"
|
||||
)}
|
||||
@change=${this._onCountChange}
|
||||
></ha-textfield>
|
||||
`
|
||||
@@ -281,7 +323,9 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
? html`
|
||||
<ha-date-input
|
||||
id="on"
|
||||
label="End on"
|
||||
label=${this.hass.localize(
|
||||
"ui.components.calendar.event.repeat.end_on.label"
|
||||
)}
|
||||
.locale=${this.locale}
|
||||
.value=${this._until!.toISOString()}
|
||||
@value-changed=${this._onUntilChange}
|
||||
@@ -402,7 +446,14 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
byweekday: byweekday,
|
||||
bymonthday: bymonthday,
|
||||
};
|
||||
const contentline = RRule.optionsToString(options);
|
||||
let contentline = RRule.optionsToString(options);
|
||||
if (this._until && this.allDay) {
|
||||
// rrule.js only computes UNTIL values as DATE-TIME however rfc5545 says
|
||||
// The value of the UNTIL rule part MUST have the same value type as the
|
||||
// "DTSTART" property. If needed, strip off any time values as a workaround
|
||||
// This converts "UNTIL=20220512T060000" to "UNTIL=20220512"
|
||||
contentline = contentline.replace(/(UNTIL=\d{8})T\d{6}Z?/, "$1");
|
||||
}
|
||||
return contentline.slice(6); // Strip "RRULE:" prefix
|
||||
}
|
||||
|
||||
|
||||
@@ -42,16 +42,6 @@ export interface MonthlyRepeatItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function intervalSuffix(freq: RepeatFrequency) {
|
||||
if (freq === "monthly") {
|
||||
return "months";
|
||||
}
|
||||
if (freq === "weekly") {
|
||||
return "weeks";
|
||||
}
|
||||
return "days";
|
||||
}
|
||||
|
||||
export function untilValue(freq: RepeatFrequency): Date {
|
||||
const today = new Date();
|
||||
const increment = DEFAULT_COUNT[freq];
|
||||
@@ -102,16 +92,6 @@ export const convertRepeatFrequency = (
|
||||
}
|
||||
};
|
||||
|
||||
export const WEEKDAY_NAME = {
|
||||
SU: "Sun",
|
||||
MO: "Mon",
|
||||
TU: "Tue",
|
||||
WE: "Wed",
|
||||
TH: "Thu",
|
||||
FR: "Fri",
|
||||
SA: "Sat",
|
||||
};
|
||||
|
||||
export const WEEKDAYS = [
|
||||
RRule.SU,
|
||||
RRule.MO,
|
||||
|
||||
@@ -2,10 +2,7 @@ import "@material/mwc-button";
|
||||
import { mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
HassEntity,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket/dist/types";
|
||||
import { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
@@ -22,7 +19,6 @@ import "../../../components/ha-icon-next";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
updateAreaRegistryEntry,
|
||||
} from "../../../data/area_registry";
|
||||
import { AutomationEntity } from "../../../data/automation";
|
||||
@@ -30,20 +26,19 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
sortDeviceRegistryByName,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
EntityRegistryEntry,
|
||||
sortEntityRegistryByName,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
@@ -51,8 +46,6 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@@ -60,7 +53,7 @@ declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
};
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
class HaConfigAreaPage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public areaId!: string;
|
||||
@@ -71,33 +64,19 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public showAdvanced!: boolean;
|
||||
|
||||
@state() public _areas!: AreaRegistryEntry[];
|
||||
|
||||
@state() public _devices!: DeviceRegistryEntry[];
|
||||
|
||||
@state() public _entities!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _area = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
areas: AreaRegistryEntry[]
|
||||
): AreaRegistryEntry | undefined =>
|
||||
areas.find((area) => area.area_id === areaId)
|
||||
);
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
(
|
||||
areaId: string,
|
||||
registryDevices: DeviceRegistryEntry[],
|
||||
registryEntities: EntityRegistryEntry[]
|
||||
registryDevices: HomeAssistant["devices"],
|
||||
registryEntities: HomeAssistant["entities"]
|
||||
) => {
|
||||
const devices = new Map<string, DeviceRegistryEntry>();
|
||||
|
||||
for (const device of registryDevices) {
|
||||
for (const device of Object.values(registryDevices)) {
|
||||
if (device.area_id === areaId) {
|
||||
devices.set(device.id, device);
|
||||
}
|
||||
@@ -106,7 +85,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
const entities: EntityRegistryEntry[] = [];
|
||||
const indirectEntities: EntityRegistryEntry[] = [];
|
||||
|
||||
for (const entity of registryEntities) {
|
||||
for (const entity of Object.values(registryEntities)) {
|
||||
if (entity.area_id) {
|
||||
if (entity.area_id === areaId) {
|
||||
entities.push(entity);
|
||||
@@ -150,26 +129,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
this._entities = entries;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const area = this._area(this.areaId, this._areas);
|
||||
const area = this.hass.areas[this.areaId];
|
||||
|
||||
if (!area) {
|
||||
return html`
|
||||
@@ -182,8 +143,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const memberships = this._memberships(
|
||||
this.areaId,
|
||||
this._devices,
|
||||
this._entities
|
||||
this.hass.devices,
|
||||
this.hass.entities
|
||||
);
|
||||
const { devices, entities } = memberships;
|
||||
|
||||
@@ -258,7 +219,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
<div class="column">
|
||||
${area.picture
|
||||
? html`<div class="img-container">
|
||||
<img src=${area.picture} /><ha-icon-button
|
||||
<img alt=${area.name} src=${area.picture} />
|
||||
<ha-icon-button
|
||||
.path=${mdiPencil}
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
|
||||
@@ -541,15 +541,10 @@ export class HaAutomationTrace extends LitElement {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
|
||||
.linkButton {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.trace-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement {
|
||||
],
|
||||
],
|
||||
},
|
||||
{ name: "offset", selector: { duration: { enable_day: true } } },
|
||||
{ name: "offset", selector: { duration: {} } },
|
||||
{
|
||||
name: "offset_type",
|
||||
type: "select",
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
mdiFormatListChecks,
|
||||
mdiSync,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -43,20 +42,20 @@ import {
|
||||
} from "../../../../data/cloud";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../data/entity_registry";
|
||||
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showEntityAliasesDialog } from "../../entities/entity-aliases/show-dialog-entity-aliases";
|
||||
|
||||
const DEFAULT_CONFIG_EXPOSE = true;
|
||||
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
|
||||
|
||||
@customElement("cloud-alexa")
|
||||
class CloudAlexa extends SubscribeMixin(LitElement) {
|
||||
class CloudAlexa extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
@@ -171,10 +170,17 @@ class CloudAlexa extends SubscribeMixin(LitElement) {
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
${entity.interfaces
|
||||
.filter((ifc) => !IGNORE_INTERFACES.includes(ifc))
|
||||
.map((ifc) => ifc.replace(/(Alexa.|Controller)/g, ""))
|
||||
.join(", ")}
|
||||
${entity.entity_id in this.hass.entities
|
||||
? html`<button
|
||||
class="link"
|
||||
.entityId=${entity.entity_id}
|
||||
@click=${this._openAliasesSettings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.alexa.manage_aliases"
|
||||
)}
|
||||
</button>`
|
||||
: ""}
|
||||
</state-info>
|
||||
${!emptyFilter
|
||||
? html`${iconButton}`
|
||||
@@ -323,23 +329,33 @@ class CloudAlexa extends SubscribeMixin(LitElement) {
|
||||
if (changedProps.has("cloudStatus")) {
|
||||
this._entityConfigs = this.cloudStatus.prefs.alexa_entity_configs;
|
||||
}
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
changedProps.get("hass")?.entities !== this.hass.entities
|
||||
) {
|
||||
const categories = {};
|
||||
|
||||
for (const entry of Object.values(this.hass.entities)) {
|
||||
categories[entry.entity_id] = entry.entity_category;
|
||||
}
|
||||
|
||||
this._entityCategories = categories;
|
||||
}
|
||||
}
|
||||
|
||||
protected override hassSubscribe(): (
|
||||
| UnsubscribeFunc
|
||||
| Promise<UnsubscribeFunc>
|
||||
)[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
const categories = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
categories[entry.entity_id] = entry.entity_category;
|
||||
}
|
||||
|
||||
this._entityCategories = categories;
|
||||
}),
|
||||
];
|
||||
private async _openAliasesSettings(ev) {
|
||||
ev.stopPropagation();
|
||||
const entityId = ev.target.entityId;
|
||||
const entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
showEntityAliasesDialog(this, {
|
||||
entity: entry,
|
||||
updateEntry: async (updates) => {
|
||||
await updateEntityRegistryEntry(this.hass, entry.entity_id, updates);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
mdiFormatListChecks,
|
||||
mdiSync,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -41,7 +40,8 @@ import {
|
||||
} from "../../../../data/cloud";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../data/entity_registry";
|
||||
import {
|
||||
fetchCloudGoogleEntities,
|
||||
@@ -51,15 +51,15 @@ import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show
|
||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { buttonLinkStyle, haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import { showEntityAliasesDialog } from "../../entities/entity-aliases/show-dialog-entity-aliases";
|
||||
|
||||
const DEFAULT_CONFIG_EXPOSE = true;
|
||||
|
||||
@customElement("cloud-google-assistant")
|
||||
class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
class CloudGoogleAssistant extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public cloudStatus!: CloudStatusLoggedIn;
|
||||
@@ -174,15 +174,23 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
${entity.traits
|
||||
.map((trait) => trait.substr(trait.lastIndexOf(".") + 1))
|
||||
.join(", ")}
|
||||
${entity.entity_id in this.hass.entities
|
||||
? html`<button
|
||||
class="link"
|
||||
.entityId=${entity.entity_id}
|
||||
@click=${this._openAliasesSettings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.google.manage_aliases"
|
||||
)}
|
||||
</button>`
|
||||
: ""}
|
||||
</state-info>
|
||||
${!emptyFilter
|
||||
? html`${iconButton}`
|
||||
: html`<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
.entityId=${stateObj.entity_id}
|
||||
.entityId=${entity.entity_id}
|
||||
@action=${this._exposeChanged}
|
||||
>
|
||||
${iconButton}
|
||||
@@ -308,7 +316,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed",
|
||||
"ui.panel.config.cloud.google.exposed",
|
||||
"selected",
|
||||
selected
|
||||
)
|
||||
@@ -329,7 +337,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed",
|
||||
"ui.panel.config.cloud.google.not_exposed",
|
||||
"selected",
|
||||
this._entities.length - selected
|
||||
)
|
||||
@@ -354,23 +362,33 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
if (changedProps.has("cloudStatus")) {
|
||||
this._entityConfigs = this.cloudStatus.prefs.google_entity_configs;
|
||||
}
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
changedProps.get("hass")?.entities !== this.hass.entities
|
||||
) {
|
||||
const categories = {};
|
||||
|
||||
for (const entry of Object.values(this.hass.entities)) {
|
||||
categories[entry.entity_id] = entry.entity_category;
|
||||
}
|
||||
|
||||
this._entityCategories = categories;
|
||||
}
|
||||
}
|
||||
|
||||
protected override hassSubscribe(): (
|
||||
| UnsubscribeFunc
|
||||
| Promise<UnsubscribeFunc>
|
||||
)[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
const categories = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
categories[entry.entity_id] = entry.entity_category;
|
||||
}
|
||||
|
||||
this._entityCategories = categories;
|
||||
}),
|
||||
];
|
||||
private async _openAliasesSettings(ev) {
|
||||
ev.stopPropagation();
|
||||
const entityId = ev.target.entityId;
|
||||
const entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
showEntityAliasesDialog(this, {
|
||||
entity: entry,
|
||||
updateEntry: async (updates) => {
|
||||
await updateEntityRegistryEntry(this.hass, entry.entity_id, updates);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _configIsDomainExposed(
|
||||
@@ -583,6 +601,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
buttonLinkStyle,
|
||||
css`
|
||||
mwc-list-item > [slot="meta"] {
|
||||
margin-left: 4px;
|
||||
|
||||
@@ -659,6 +659,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
integrations.length
|
||||
? html`
|
||||
<img
|
||||
alt=${domainToName(
|
||||
this.hass.localize,
|
||||
integrations[0].domain
|
||||
)}
|
||||
src=${brandsUrl({
|
||||
domain: integrations[0].domain,
|
||||
type: "logo",
|
||||
|
||||
@@ -220,6 +220,7 @@ export class EnergyGridSettings extends LitElement {
|
||||
${this._co2ConfigEntry
|
||||
? html`<div class="row" .entry=${this._co2ConfigEntry}>
|
||||
<img
|
||||
alt=""
|
||||
referrerpolicy="no-referrer"
|
||||
src=${brandsUrl({
|
||||
domain: "co2signal",
|
||||
@@ -244,6 +245,7 @@ export class EnergyGridSettings extends LitElement {
|
||||
: html`
|
||||
<div class="row border-bottom">
|
||||
<img
|
||||
alt=""
|
||||
referrerpolicy="no-referrer"
|
||||
src=${brandsUrl({
|
||||
domain: "co2signal",
|
||||
|
||||
@@ -130,6 +130,7 @@ export class DialogEnergySolarSettings
|
||||
style="display: flex; align-items: center;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
referrerpolicy="no-referrer"
|
||||
style="height: 24px; margin-right: 16px;"
|
||||
src=${brandsUrl({
|
||||
|
||||
@@ -57,7 +57,13 @@ export class EntityRegistrySettingsHelper extends LitElement {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entry")) {
|
||||
this._error = undefined;
|
||||
this._item = undefined;
|
||||
if (
|
||||
this.entry.unique_id !==
|
||||
(changedProperties.get("entry") as ExtEntityRegistryEntry)?.unique_id
|
||||
) {
|
||||
this._item = undefined;
|
||||
}
|
||||
|
||||
this._getItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +72,21 @@ class DialogEntityAliases extends LitElement {
|
||||
dialogInitialFocus=${index}
|
||||
.index=${index}
|
||||
class="flex-auto"
|
||||
label="Alias"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.entity_registry.editor.aliases.input_label",
|
||||
{ number: index + 1 }
|
||||
)}
|
||||
.value=${alias}
|
||||
?data-last=${index === this._aliases.length - 1}
|
||||
@change=${this._editAlias}
|
||||
@input=${this._editAlias}
|
||||
@keydown=${this._keyDownAlias}
|
||||
></ha-textfield>
|
||||
<ha-icon-button
|
||||
.index=${index}
|
||||
slot="navigationIcon"
|
||||
label=${this.hass!.localize(
|
||||
"ui.dialogs.entity_registry.editor.aliases.remove_alias"
|
||||
"ui.dialogs.entity_registry.editor.aliases.remove_alias",
|
||||
{ number: index + 1 }
|
||||
)}
|
||||
@click=${this._removeAlias}
|
||||
.path=${mdiDeleteOutline}
|
||||
@@ -133,6 +138,13 @@ class DialogEntityAliases extends LitElement {
|
||||
this._aliases[index] = (ev.target as any).value;
|
||||
}
|
||||
|
||||
private async _keyDownAlias(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
ev.stopPropagation();
|
||||
this._addAlias();
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeAlias(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
const aliases = [...this._aliases];
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../../../data/entity_registry";
|
||||
|
||||
export interface EntityAliasesDialogParams {
|
||||
entity: EntityRegistryEntry;
|
||||
entity: ExtEntityRegistryEntry;
|
||||
updateEntry: (
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
) => Promise<unknown>;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import "@material/mwc-formfield/mwc-formfield";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiPencil } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
@@ -21,6 +25,7 @@ import {
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showEntityAliasesDialog } from "./entity-aliases/show-dialog-entity-aliases";
|
||||
|
||||
@customElement("ha-registry-basic-editor")
|
||||
export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
@@ -44,6 +49,21 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
private _handleAliasesClicked(ev: CustomEvent) {
|
||||
if (ev.detail.index !== 0) return;
|
||||
showEntityAliasesDialog(this, {
|
||||
entity: this.entry!,
|
||||
updateEntry: async (updates) => {
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass,
|
||||
this.entry.entity_id,
|
||||
updates
|
||||
);
|
||||
fireEvent(this, "entity-entry-updated", result.entity_entry);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
@@ -247,6 +267,33 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="label">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.aliases_section"
|
||||
)}
|
||||
</div>
|
||||
<mwc-list class="aliases" @action=${this._handleAliasesClicked}>
|
||||
<mwc-list-item .twoline=${this.entry.aliases.length > 0} hasMeta>
|
||||
<span>
|
||||
${this.entry.aliases.length > 0
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.configured_aliases",
|
||||
{ count: this.entry.aliases.length }
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.no_aliases"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">${this.entry.aliases.join(", ")}</span>
|
||||
<ha-svg-icon slot="meta" .path=${mdiPencil}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</mwc-list>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.aliases.description"
|
||||
)}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
@@ -300,6 +347,13 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
.label {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.aliases {
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,43 @@ const OVERRIDE_NUMBER_UNITS = {
|
||||
};
|
||||
|
||||
const OVERRIDE_SENSOR_UNITS = {
|
||||
current: ["A", "mA"],
|
||||
data_rate: [
|
||||
"bit/s",
|
||||
"kbit/s",
|
||||
"Mbit/s",
|
||||
"Gbit/s",
|
||||
"B/s",
|
||||
"kB/s",
|
||||
"MB/s",
|
||||
"GB/s",
|
||||
"KiB/s",
|
||||
"MiB/s",
|
||||
"GiB/s",
|
||||
],
|
||||
data_size: [
|
||||
"bit",
|
||||
"kbit",
|
||||
"Mbit",
|
||||
"Gbit",
|
||||
"B",
|
||||
"kB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB",
|
||||
"PB",
|
||||
"EB",
|
||||
"ZB",
|
||||
"YB",
|
||||
"KiB",
|
||||
"MiB",
|
||||
"GiB",
|
||||
"TiB",
|
||||
"PiB",
|
||||
"EiB",
|
||||
"ZiB",
|
||||
"YiB",
|
||||
],
|
||||
distance: ["cm", "ft", "in", "km", "m", "mi", "mm", "yd"],
|
||||
gas: ["CCF", "ft³", "m³"],
|
||||
precipitation: ["cm", "in", "mm"],
|
||||
@@ -125,6 +162,7 @@ const OVERRIDE_SENSOR_UNITS = {
|
||||
pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"],
|
||||
speed: ["ft/s", "in/d", "in/h", "km/h", "kn", "m/s", "mm/d", "mm/h", "mph"],
|
||||
temperature: ["°C", "°F", "K"],
|
||||
voltage: ["V", "mV"],
|
||||
volume: ["CCF", "fl. oz.", "ft³", "gal", "L", "mL", "m³"],
|
||||
water: ["CCF", "ft³", "gal", "L", "m³"],
|
||||
weight: ["g", "kg", "lb", "mg", "oz", "st", "µg"],
|
||||
@@ -769,12 +807,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
"ui.dialogs.entity_registry.editor.aliases_section"
|
||||
)}
|
||||
</div>
|
||||
<mwc-list class="aliases">
|
||||
<mwc-list-item
|
||||
.twoline=${this.entry.aliases.length > 0}
|
||||
hasMeta
|
||||
@click=${this._openAliasesSettings}
|
||||
>
|
||||
<mwc-list class="aliases" @action=${this._handleAliasesClicked}>
|
||||
<mwc-list-item .twoline=${this.entry.aliases.length > 0} hasMeta>
|
||||
<span>
|
||||
${this.entry.aliases.length > 0
|
||||
? this.hass.localize(
|
||||
@@ -977,7 +1011,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _openAliasesSettings() {
|
||||
private _handleAliasesClicked(ev: CustomEvent) {
|
||||
if (ev.detail.index !== 0) return;
|
||||
showEntityAliasesDialog(this, {
|
||||
entity: this.entry!,
|
||||
updateEntry: async (updates) => {
|
||||
|
||||
@@ -728,7 +728,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
selectable: false,
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
aliases: [],
|
||||
});
|
||||
}
|
||||
if (changed) {
|
||||
|
||||
@@ -305,7 +305,7 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
|
||||
.twoline=${Boolean(boardId)}
|
||||
>
|
||||
${imageURL
|
||||
? html`<img slot="graphic" src=${imageURL} />`
|
||||
? html`<img alt="" slot="graphic" src=${imageURL} />`
|
||||
: ""}
|
||||
<span class="primary-text">
|
||||
${boardName ||
|
||||
|
||||
@@ -48,6 +48,7 @@ class HaDomainIntegrations extends LitElement {
|
||||
hasMeta
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
|
||||
@@ -92,6 +92,7 @@ export class HaIntegrationHeader extends LitElement {
|
||||
<slot name="above-header"></slot>
|
||||
<div class="header">
|
||||
<img
|
||||
alt=""
|
||||
src=${brandsUrl({
|
||||
domain: this.domain,
|
||||
type: "icon",
|
||||
|
||||
@@ -47,6 +47,7 @@ export class HaIntegrationListItem extends ListItemBase {
|
||||
)}"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: this.integration.domain,
|
||||
|
||||
@@ -52,6 +52,7 @@ class HaConfigRepairs extends LitElement {
|
||||
@click=${this._openShowMoreDialog}
|
||||
>
|
||||
<img
|
||||
alt=${domainToName(this.hass.localize, issue.domain)}
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: issue.issue_domain || issue.domain,
|
||||
|
||||
@@ -65,6 +65,7 @@ class IntegrationsStartupTime extends LitElement {
|
||||
href=${docLink}
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: setup.domain,
|
||||
|
||||
@@ -527,15 +527,10 @@ export class HaScriptTrace extends LitElement {
|
||||
:host([narrow]) .graph {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
|
||||
.linkButton {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.trace-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,14 @@ class DialogTagDetail
|
||||
canvas.height / 3
|
||||
);
|
||||
|
||||
this._qrCode = html`<img src=${canvas.toDataURL()}></img>`;
|
||||
this._qrCode = html`<img
|
||||
alt=${this.hass.localize(
|
||||
"ui.panel.config.tag.qr_code_image",
|
||||
"name",
|
||||
this._name
|
||||
)}
|
||||
src=${canvas.toDataURL()}
|
||||
></img>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -101,7 +101,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
),
|
||||
})}
|
||||
>
|
||||
<img src=${this.hass.hassUrl(this._config.image)} />
|
||||
<img
|
||||
alt=${this._config.alt_text}
|
||||
src=${this.hass.hassUrl(this._config.image)}
|
||||
/>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -332,6 +332,7 @@ export interface PictureCardConfig extends LovelaceCardConfig {
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
theme?: string;
|
||||
alt_text?: string;
|
||||
}
|
||||
|
||||
export interface PictureElementsCardConfig extends LovelaceCardConfig {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const handleAction = async (
|
||||
actionConfig.confirmation &&
|
||||
(!actionConfig.confirmation.exemptions ||
|
||||
!actionConfig.confirmation.exemptions.some(
|
||||
(e) => e.user === hass!.user!.id
|
||||
(e) => e.user === hass!.user?.id
|
||||
))
|
||||
) {
|
||||
forwardHaptic("warning");
|
||||
|
||||
@@ -20,6 +20,7 @@ const cardConfigStruct = assign(
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
theme: optional(string()),
|
||||
alt_text: optional(string()),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -53,6 +54,10 @@ export class HuiPictureCardEditor
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
|
||||
get _alt_text(): string {
|
||||
return this._config!.alt_text || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
@@ -72,6 +77,16 @@ export class HuiPictureCardEditor
|
||||
.configValue=${"image"}
|
||||
@input=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.alt_text"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._alt_text}
|
||||
.configValue=${"alt_text"}
|
||||
@input=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<ha-theme-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._theme}
|
||||
|
||||
@@ -68,6 +68,7 @@ export class HuiPictureHeaderFooter
|
||||
|
||||
return html`
|
||||
<img
|
||||
alt=${this._config!.alt_text}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
|
||||
@@ -17,6 +17,7 @@ export const pictureHeaderFooterConfigStruct = object({
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
alt_text: optional(string()),
|
||||
});
|
||||
|
||||
export const buttonsHeaderFooterConfigStruct = object({
|
||||
|
||||
@@ -27,4 +27,5 @@ export interface PictureHeaderFooterConfig extends LovelaceHeaderFooterConfig {
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
alt_text?: string;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ class HUIRoot extends LitElement {
|
||||
((Array.isArray(view.visible) &&
|
||||
!view.visible.some(
|
||||
(e) =>
|
||||
e.user === this.hass!.user!.id
|
||||
e.user === this.hass!.user?.id
|
||||
)) ||
|
||||
view.visible === false))
|
||||
),
|
||||
@@ -470,7 +470,7 @@ class HUIRoot extends LitElement {
|
||||
view.visible !== undefined &&
|
||||
((Array.isArray(view.visible) &&
|
||||
!view.visible.some(
|
||||
(e) => e.user === this.hass!.user!.id
|
||||
(e) => e.user === this.hass!.user?.id
|
||||
)) ||
|
||||
view.visible === false)
|
||||
),
|
||||
|
||||
@@ -85,7 +85,14 @@ export class HaLongLivedAccessTokenDialog extends LitElement {
|
||||
canvas.height / 3
|
||||
);
|
||||
|
||||
this._qrCode = html`<img src=${canvas.toDataURL()}></img>`;
|
||||
this._qrCode = html`<img
|
||||
alt=${this.hass.localize(
|
||||
"ui.panel.profile.long_lived_access_tokens.qr_code_image",
|
||||
"name",
|
||||
this._params!.name
|
||||
)}
|
||||
src=${canvas.toDataURL()}
|
||||
></img>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -644,6 +644,33 @@
|
||||
"monthly": "months",
|
||||
"weekly": "weeks",
|
||||
"daily": "days"
|
||||
},
|
||||
"monthly": {
|
||||
"label": "Repeat Monthly"
|
||||
},
|
||||
"weekly": {
|
||||
"weekday": {
|
||||
"su": "Sun",
|
||||
"mo": "Mon",
|
||||
"tu": "Tue",
|
||||
"we": "Wed",
|
||||
"th": "Thu",
|
||||
"fr": "Fri",
|
||||
"sa": "Sat"
|
||||
}
|
||||
},
|
||||
"end": {
|
||||
"label": "End",
|
||||
"never": "Never",
|
||||
"after": "After",
|
||||
"on": "On"
|
||||
},
|
||||
"end_on": {
|
||||
"label": "End On"
|
||||
},
|
||||
"end_after": {
|
||||
"label": "End After",
|
||||
"ocurrences": "ocurrences"
|
||||
}
|
||||
},
|
||||
"rrule": {
|
||||
@@ -791,7 +818,8 @@
|
||||
"close": "Close"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Crop"
|
||||
"crop": "Crop",
|
||||
"crop_image": "Picture to crop"
|
||||
},
|
||||
"date-picker": {
|
||||
"today": "Today"
|
||||
@@ -975,7 +1003,8 @@
|
||||
"aliases": {
|
||||
"heading": "{name} aliases",
|
||||
"description": "Aliases are alternative names used in voice assistants to refer to this entity.",
|
||||
"remove_alias": "Remove alias",
|
||||
"remove_alias": "Remove alias {number}",
|
||||
"input_label": "Alias {number}",
|
||||
"save": "Save",
|
||||
"add_alias": "Add alias",
|
||||
"no_aliases": "No aliases have been added yet",
|
||||
@@ -1435,6 +1464,7 @@
|
||||
"confirm_remove_title": "Remove tag?",
|
||||
"confirm_remove": "Are you sure you want to remove tag {tag}?",
|
||||
"automation_title": "Tag {name} is scanned",
|
||||
"qr_code_image": "QR code for tag {name}",
|
||||
"headers": {
|
||||
"icon": "Icon",
|
||||
"name": "Name",
|
||||
@@ -2632,7 +2662,7 @@
|
||||
"enable_state_reporting": "Enable State Reporting",
|
||||
"info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Amazon. This allows you to always see the latest states in the Alexa app and use the state changes to create routines.",
|
||||
"state_reporting_error": "Unable to {enable_disable} report state.",
|
||||
"manage_entities": "Manage Entities",
|
||||
"manage_entities": "[%key:ui::panel::config::cloud::account::google::manage_entities%]",
|
||||
"enable": "enable",
|
||||
"disable": "disable",
|
||||
"not_configured_title": "Alexa is not activated",
|
||||
@@ -2683,6 +2713,7 @@
|
||||
"follow_domain": "[%key:ui::panel::config::cloud::google::follow_domain%]",
|
||||
"exposed": "[%key:ui::panel::config::cloud::google::exposed%]",
|
||||
"not_exposed": "[%key:ui::panel::config::cloud::google::not_exposed%]",
|
||||
"manage_aliases": "[%key:ui::panel::config::cloud::google::manage_aliases%]",
|
||||
"expose": "Expose to Alexa",
|
||||
"sync_entities": "Synchronize entities",
|
||||
"sync_entities_error": "Failed to sync entities:"
|
||||
@@ -2708,6 +2739,7 @@
|
||||
"follow_domain": "Follow domain",
|
||||
"exposed": "{selected} exposed",
|
||||
"not_exposed": "{selected} not exposed",
|
||||
"manage_aliases": "Manage aliases",
|
||||
"sync_to_google": "Synchronizing changes to Google.",
|
||||
"sync_entities": "Synchronize entities",
|
||||
"sync_entities_error": "Failed to sync entities:",
|
||||
@@ -4195,6 +4227,7 @@
|
||||
"description": "The Light card allows you to change the brightness of the light."
|
||||
},
|
||||
"generic": {
|
||||
"alt_text": "Alternative Text",
|
||||
"aspect_ratio": "Aspect Ratio",
|
||||
"attribute": "Attribute",
|
||||
"camera_image": "Camera Entity",
|
||||
@@ -4598,7 +4631,8 @@
|
||||
"name": "Name",
|
||||
"prompt_name": "Give the token a name",
|
||||
"prompt_copy_token": "Copy your access token. It will not be shown again.",
|
||||
"empty_state": "You have no long-lived access tokens yet."
|
||||
"empty_state": "You have no long-lived access tokens yet.",
|
||||
"qr_code_image": "QR code for token {name}"
|
||||
}
|
||||
},
|
||||
"shopping_list": {
|
||||
|
||||
Reference in New Issue
Block a user