mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-20 15:56:35 +00:00
Add helper UI (#4940)
* Add helper UI * Oops * Update * Update * Update * Lint * Add all input forms * Return extended entity registry entry from update * Comments Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
52ded635ff
commit
b229071248
@ -452,7 +452,7 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
.disabled=${!this.addon.available || this._installing}
|
||||
.progress=${this._installing}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
|
@ -4,7 +4,7 @@ export const dynamicElement = directive(
|
||||
(tag: string, properties?: { [key: string]: any }) => (part: Part): void => {
|
||||
if (!(part instanceof NodePart)) {
|
||||
throw new Error(
|
||||
"dynamicContentDirective can only be used in content bindings"
|
||||
"dynamicElementDirective can only be used in content bindings"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ const fixedIcons = {
|
||||
homeassistant: "hass:home-assistant",
|
||||
homekit: "hass:home-automation",
|
||||
image_processing: "hass:image-filter-frames",
|
||||
input_boolean: "hass:drawing",
|
||||
input_boolean: "hass:toggle-switch-outline",
|
||||
input_datetime: "hass:calendar-clock",
|
||||
input_number: "hass:ray-vertex",
|
||||
input_select: "hass:format-list-bulleted",
|
||||
|
65
src/components/ha-icon-input.ts
Normal file
65
src/components/ha-icon-input.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "./ha-icon";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-icon-input")
|
||||
export class HaIconInput extends LitElement {
|
||||
@property() public value?: string;
|
||||
@property() public label?: string;
|
||||
@property() public placeholder?: string;
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
auto-validate
|
||||
.errorMessage=${this.errorMessage}
|
||||
pattern="^\\S+:\\S+$"
|
||||
>
|
||||
${this.value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this.value || this.placeholder} slot="suffix">
|
||||
</ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this.value = ev.detail.value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value: ev.detail.value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon {
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -70,9 +70,7 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
if (Object.keys(this._related).length === 0) {
|
||||
return html`
|
||||
<p>
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
</p>
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
|
@ -6,14 +6,23 @@ import { debounce } from "../common/util/debounce";
|
||||
export interface EntityRegistryEntry {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
platform: string;
|
||||
config_entry_id?: string;
|
||||
device_id?: string;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
unique_id: string;
|
||||
capabilities: object;
|
||||
original_name?: string;
|
||||
original_icon?: string;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
disabled_by?: string | null;
|
||||
new_entity_id?: string;
|
||||
}
|
||||
@ -29,12 +38,21 @@ export const computeEntityRegistryName = (
|
||||
return state ? computeStateName(state) : null;
|
||||
};
|
||||
|
||||
export const getExtendedEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
||||
export const updateEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
): Promise<EntityRegistryEntry> =>
|
||||
hass.callWS<EntityRegistryEntry>({
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/update",
|
||||
entity_id: entityId,
|
||||
...updates,
|
||||
|
@ -59,3 +59,12 @@ export const getOptimisticFrontendUserDataCollection = <
|
||||
`_frontendUserData-${userDataKey}`,
|
||||
() => fetchFrontendUserData(conn, userDataKey)
|
||||
);
|
||||
|
||||
export const subscribeFrontendUserData = <UserDataKey extends ValidUserDataKey>(
|
||||
conn: Connection,
|
||||
userDataKey: UserDataKey,
|
||||
onChange: (state: FrontendUserData[UserDataKey] | null) => void
|
||||
) =>
|
||||
getOptimisticFrontendUserDataCollection(conn, userDataKey).subscribe(
|
||||
onChange
|
||||
);
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
43
src/data/input_boolean.ts
Normal file
43
src/data/input_boolean.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputBoolean {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: boolean;
|
||||
}
|
||||
|
||||
export interface InputBooleanMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: boolean;
|
||||
}
|
||||
|
||||
export const fetchInputBoolean = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputBoolean[]>({ type: "input_boolean/list" });
|
||||
|
||||
export const createInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
values: InputBooleanMutableParams
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputBooleanMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/update",
|
||||
input_boolean_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputBoolean = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_boolean/delete",
|
||||
input_boolean_id: id,
|
||||
});
|
@ -1,5 +1,22 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputDateTime {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export interface InputDateTimeMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export const setInputDateTimeValue = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@ -9,3 +26,32 @@ export const setInputDateTimeValue = (
|
||||
const param = { entity_id: entityId, time, date };
|
||||
hass.callService(entityId.split(".", 1)[0], "set_datetime", param);
|
||||
};
|
||||
|
||||
export const fetchInputDateTime = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputDateTime[]>({ type: "input_datetime/list" });
|
||||
|
||||
export const createInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
values: InputDateTimeMutableParams
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputDateTimeMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/update",
|
||||
input_datetime_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputDateTime = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_datetime/delete",
|
||||
input_datetime_id: id,
|
||||
});
|
||||
|
53
src/data/input_number.ts
Normal file
53
src/data/input_number.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputNumber {
|
||||
id: string;
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
icon?: string;
|
||||
initial?: number;
|
||||
step?: number;
|
||||
mode?: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export interface InputNumberMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
mode: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export const fetchInputNumber = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputNumber[]>({ type: "input_number/list" });
|
||||
|
||||
export const createInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
values: InputNumberMutableParams
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputNumberMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/update",
|
||||
input_number_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputNumber = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_number/delete",
|
||||
input_number_id: id,
|
||||
});
|
55
src/data/input_select.ts
Normal file
55
src/data/input_select.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputSelect {
|
||||
id: string;
|
||||
name: string;
|
||||
options: string[];
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
}
|
||||
|
||||
export interface InputSelectMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputSelect = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputSelect[]>({ type: "input_select/list" });
|
||||
|
||||
export const createInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
values: InputSelectMutableParams
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputSelectMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/update",
|
||||
input_select_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputSelect = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_select/delete",
|
||||
input_select_id: id,
|
||||
});
|
@ -1,7 +1,57 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputText {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
pattern?: string;
|
||||
mode?: "text" | "password";
|
||||
}
|
||||
|
||||
export interface InputTextMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
min: number;
|
||||
max: number;
|
||||
pattern: string;
|
||||
mode: "text" | "password";
|
||||
}
|
||||
|
||||
export const setValue = (hass: HomeAssistant, entity: string, value: string) =>
|
||||
hass.callService(entity.split(".", 1)[0], "set_value", {
|
||||
value,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputText = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputText[]>({ type: "input_text/list" });
|
||||
|
||||
export const createInputText = (
|
||||
hass: HomeAssistant,
|
||||
values: InputTextMutableParams
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputText = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputTextMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/update",
|
||||
input_text_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputText = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_text/delete",
|
||||
input_text_id: id,
|
||||
});
|
||||
|
@ -129,6 +129,10 @@ class DialogBox extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
ha-paper-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
|
@ -8,7 +8,6 @@ import "../resources/ha-style";
|
||||
import "./more-info/more-info-controls";
|
||||
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
|
||||
import DialogMixin from "../mixins/dialog-mixin";
|
||||
|
||||
@ -81,7 +80,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
hass="[[hass]]"
|
||||
state-obj="[[stateObj]]"
|
||||
dialog-element="[[_dialogElement()]]"
|
||||
registry-entry="[[_registryInfo]]"
|
||||
large="{{large}}"
|
||||
></more-info-controls>
|
||||
`;
|
||||
@ -102,8 +100,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
observer: "_largeChanged",
|
||||
},
|
||||
|
||||
_registryInfo: Object,
|
||||
|
||||
dataDomain: {
|
||||
computed: "_computeDomain(stateObj)",
|
||||
reflectToAttribute: true,
|
||||
@ -127,11 +123,10 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
return hass.states[hass.moreInfoEntityId] || null;
|
||||
}
|
||||
|
||||
async _stateObjChanged(newVal, oldVal) {
|
||||
async _stateObjChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this.setProperties({
|
||||
opened: false,
|
||||
_registryInfo: null,
|
||||
large: false,
|
||||
});
|
||||
return;
|
||||
@ -144,25 +139,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
this.opened = true;
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
!isComponentLoaded(this.hass, "config") ||
|
||||
(oldVal && oldVal.entity_id === newVal.entity_id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hass.user.is_admin) {
|
||||
try {
|
||||
const info = await this.hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: newVal.entity_id,
|
||||
});
|
||||
this._registryInfo = info;
|
||||
} catch (err) {
|
||||
this._registryInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dialogOpenChanged(newVal) {
|
||||
|
@ -39,7 +39,8 @@ class MoreInfoPerson extends LitElement {
|
||||
></ha-map>
|
||||
`
|
||||
: ""}
|
||||
${this.hass.user?.is_admin &&
|
||||
${!__DEMO__ &&
|
||||
this.hass.user?.is_admin &&
|
||||
this.stateObj.state === "not_home" &&
|
||||
this.stateObj.attributes.latitude &&
|
||||
this.stateObj.attributes.longitude
|
||||
|
@ -22,7 +22,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showEntityRegistryDetailDialog } from "../../panels/config/entities/show-dialog-entity-registry-detail";
|
||||
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
@ -88,7 +88,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[registryEntry]]">
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<paper-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
@ -221,6 +221,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
|
||||
_computeConfig(hass) {
|
||||
return hass.user.is_admin && isComponentLoaded(hass, "config");
|
||||
}
|
||||
|
||||
_computeEdit(hass, stateObj) {
|
||||
const domain = this._computeDomain(stateObj);
|
||||
return (
|
||||
@ -260,7 +264,9 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
_gotoSettings() {
|
||||
showEntityRegistryDetailDialog(this, { entry: this.registryEntry });
|
||||
showEntityEditorDialog(this, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
this.fire("hass-more-info", { entityId: null });
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import {
|
||||
fetchConfig,
|
||||
fetchResources,
|
||||
@ -92,6 +93,7 @@ window.hassConnection.then(({ conn }) => {
|
||||
subscribePanels(conn, noop);
|
||||
subscribeThemes(conn, noop);
|
||||
subscribeUser(conn, noop);
|
||||
subscribeFrontendUserData(conn, "core", noop);
|
||||
|
||||
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
|
||||
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(
|
||||
|
@ -20,7 +20,7 @@ import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon";
|
||||
import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
|
||||
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||
@ -150,7 +150,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
private _openEditEntry(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
const entry = (ev.currentTarget! as any).entry;
|
||||
showEntityRegistryDetailDialog(this, {
|
||||
showEntityEditorDialog(this, {
|
||||
entry,
|
||||
entity_id: entry.entity_id,
|
||||
});
|
||||
|
@ -6,8 +6,6 @@ import {
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import {
|
||||
@ -253,17 +251,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
8
src/panels/config/entities/const.ts
Normal file
8
src/panels/config/entities/const.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/** Platforms that have a settings tab. */
|
||||
export const PLATFORMS_WITH_SETTINGS_TAB = {
|
||||
input_number: "entity-settings-helper-tab",
|
||||
input_select: "entity-settings-helper-tab",
|
||||
input_text: "entity-settings-helper-tab",
|
||||
input_boolean: "entity-settings-helper-tab",
|
||||
input_datetime: "entity-settings-helper-tab",
|
||||
};
|
@ -13,25 +13,45 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
import { PLATFORMS_WITH_SETTINGS_TAB } from "./const";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
|
||||
import "../../../components/ha-related-items";
|
||||
import "../../../dialogs/more-info/controls/more-info-content";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import "../../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./entity-registry-settings";
|
||||
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
|
||||
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-editor";
|
||||
|
||||
@customElement("dialog-entity-registry-detail")
|
||||
export class DialogEntityRegistryDetail extends LitElement {
|
||||
interface Tabs {
|
||||
[key: string]: Tab;
|
||||
}
|
||||
|
||||
interface Tab {
|
||||
component: string;
|
||||
translationKey: string;
|
||||
}
|
||||
|
||||
@customElement("dialog-entity-editor")
|
||||
export class DialogEntityEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _params?: EntityRegistryDetailDialogParams;
|
||||
@property() private _entry?:
|
||||
| EntityRegistryEntry
|
||||
| ExtEntityRegistryEntry
|
||||
| null;
|
||||
@property() private _curTab?: string;
|
||||
@property() private _extraTabs: Tabs = {};
|
||||
@property() private _settingsElementTag?: string;
|
||||
@query("ha-paper-dialog") private _dialog!: HaPaperDialog;
|
||||
private _curTabIndex = 0;
|
||||
|
||||
@ -39,6 +59,10 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
params: EntityRegistryDetailDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._entry = undefined;
|
||||
this._settingsElementTag = undefined;
|
||||
this._extraTabs = {};
|
||||
this._getEntityReg();
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@ -47,11 +71,11 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._params || this._entry === undefined) {
|
||||
return html``;
|
||||
}
|
||||
const entry = this._params.entry;
|
||||
const entityId = this._params.entity_id;
|
||||
const entry = this._entry;
|
||||
const stateObj: HassEntity | undefined = this.hass.states[entityId];
|
||||
|
||||
return html`
|
||||
@ -59,6 +83,7 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed=${this._openedChanged}
|
||||
@close-dialog=${this.closeDialog}
|
||||
>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
@ -92,6 +117,13 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
<paper-tab id="tab-settings">
|
||||
${this.hass.localize("ui.dialogs.entity_registry.settings")}
|
||||
</paper-tab>
|
||||
${Object.entries(this._extraTabs).map(
|
||||
([key, tab]) => html`
|
||||
<paper-tab id=${key}>
|
||||
${this.hass.localize(tab.translationKey) || key}
|
||||
</paper-tab>
|
||||
`
|
||||
)}
|
||||
<paper-tab id="tab-related">
|
||||
${this.hass.localize("ui.dialogs.entity_registry.related")}
|
||||
</paper-tab>
|
||||
@ -99,14 +131,16 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
${cache(
|
||||
this._curTab === "tab-settings"
|
||||
? entry
|
||||
? html`
|
||||
<entity-registry-settings
|
||||
.hass=${this.hass}
|
||||
.entry=${entry}
|
||||
.dialogElement=${this._dialog}
|
||||
@close-dialog=${this._closeDialog}
|
||||
></entity-registry-settings>
|
||||
`
|
||||
? this._settingsElementTag
|
||||
? html`
|
||||
${dynamicElement(this._settingsElementTag, {
|
||||
hass: this.hass,
|
||||
entry,
|
||||
entityId,
|
||||
dialogElement: this._dialog,
|
||||
})}
|
||||
`
|
||||
: ""
|
||||
: html`
|
||||
<paper-dialog-scrollable>
|
||||
${this.hass.localize(
|
||||
@ -121,7 +155,6 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
itemType="entity"
|
||||
@close-dialog=${this._closeDialog}
|
||||
></ha-related-items>
|
||||
</paper-dialog-scrollable>
|
||||
`
|
||||
@ -131,6 +164,18 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getEntityReg() {
|
||||
try {
|
||||
this._entry = await getExtendedEntityRegistryEntry(
|
||||
this.hass,
|
||||
this._params!.entity_id
|
||||
);
|
||||
this._loadPlatformSettingTabs();
|
||||
} catch {
|
||||
this._entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleTabSelected(ev: CustomEvent): void {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
@ -144,15 +189,26 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
fireEvent(this._dialog as HTMLElement, "iron-resize");
|
||||
}
|
||||
|
||||
private async _loadPlatformSettingTabs(): Promise<void> {
|
||||
if (!this._entry) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
|
||||
) {
|
||||
this._settingsElementTag = "entity-registry-settings";
|
||||
return;
|
||||
}
|
||||
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
|
||||
await import(`./editor-tabs/settings/${tag}`);
|
||||
this._settingsElementTag = tag;
|
||||
}
|
||||
|
||||
private _openMoreInfo(): void {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: this._params!.entity_id,
|
||||
});
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
@ -250,6 +306,6 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-entity-registry-detail": DialogEntityRegistryDetail;
|
||||
"dialog-entity-editor": DialogEntityEditor;
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { HaPaperDialog } from "../../../../../components/dialog/ha-paper-dialog";
|
||||
import { ExtEntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import {
|
||||
deleteInputBoolean,
|
||||
fetchInputBoolean,
|
||||
updateInputBoolean,
|
||||
} from "../../../../../data/input_boolean";
|
||||
import {
|
||||
deleteInputDateTime,
|
||||
fetchInputDateTime,
|
||||
updateInputDateTime,
|
||||
} from "../../../../../data/input_datetime";
|
||||
import {
|
||||
deleteInputNumber,
|
||||
fetchInputNumber,
|
||||
updateInputNumber,
|
||||
} from "../../../../../data/input_number";
|
||||
import {
|
||||
deleteInputSelect,
|
||||
fetchInputSelect,
|
||||
updateInputSelect,
|
||||
} from "../../../../../data/input_select";
|
||||
import {
|
||||
deleteInputText,
|
||||
fetchInputText,
|
||||
updateInputText,
|
||||
} from "../../../../../data/input_text";
|
||||
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../helpers/forms/ha-input_boolean-form";
|
||||
import "../../../helpers/forms/ha-input_text-form";
|
||||
import "../../../helpers/forms/ha-input_datetime-form";
|
||||
import "../../../helpers/forms/ha-input_select-form";
|
||||
import "../../../helpers/forms/ha-input_number-form";
|
||||
import { Helper } from "../../../helpers/const";
|
||||
import "../../entity-registry-basic-editor";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaEntityRegistryBasicEditor } from "../../entity-registry-basic-editor";
|
||||
|
||||
const HELPERS = {
|
||||
input_boolean: {
|
||||
fetch: fetchInputBoolean,
|
||||
update: updateInputBoolean,
|
||||
delete: deleteInputBoolean,
|
||||
},
|
||||
input_text: {
|
||||
fetch: fetchInputText,
|
||||
update: updateInputText,
|
||||
delete: deleteInputText,
|
||||
},
|
||||
input_number: {
|
||||
fetch: fetchInputNumber,
|
||||
update: updateInputNumber,
|
||||
delete: deleteInputNumber,
|
||||
},
|
||||
input_datetime: {
|
||||
fetch: fetchInputDateTime,
|
||||
update: updateInputDateTime,
|
||||
delete: deleteInputDateTime,
|
||||
},
|
||||
input_select: {
|
||||
fetch: fetchInputSelect,
|
||||
update: updateInputSelect,
|
||||
delete: deleteInputSelect,
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("entity-settings-helper-tab")
|
||||
export class EntityRegistrySettingsHelper extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() public dialogElement!: HaPaperDialog;
|
||||
@property() private _error?: string;
|
||||
@property() private _item?: Helper | null;
|
||||
@property() private _submitting?: boolean;
|
||||
@property() private _componentLoaded?: boolean;
|
||||
@query("ha-registry-basic-editor")
|
||||
private _registryEditor?: HaEntityRegistryBasicEditor;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._componentLoaded = isComponentLoaded(this.hass, this.entry.platform);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entry")) {
|
||||
this._error = undefined;
|
||||
this._item = undefined;
|
||||
this._getItem();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._item === undefined) {
|
||||
return html``;
|
||||
}
|
||||
if (!this._componentLoaded) {
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
The ${this.entry.platform} component is not loaded, please add it your
|
||||
configuration. Either by adding 'default_config:' or
|
||||
'${this.entry.platform}:'.
|
||||
</paper-dialog-scrollable>
|
||||
`;
|
||||
}
|
||||
if (this._item === null) {
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
This entity can not be edited from the UI. Only entities setup from
|
||||
the UI are editable.
|
||||
</paper-dialog-scrollable>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="form">
|
||||
<div @value-changed=${this._valueChanged}>
|
||||
${dynamicElement(`ha-${this.entry.platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
entry: this.entry,
|
||||
})}
|
||||
</div>
|
||||
<ha-registry-basic-editor
|
||||
.hass=${this.hass}
|
||||
.entry=${this.entry}
|
||||
></ha-registry-basic-editor>
|
||||
</div>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="buttons">
|
||||
<mwc-button
|
||||
class="warning"
|
||||
@click=${this._confirmDeleteItem}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._updateItem}
|
||||
.disabled=${this._submitting || !this._item.name}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
this._error = undefined;
|
||||
this._item = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _getItem() {
|
||||
const items = await HELPERS[this.entry.platform].fetch(this.hass!);
|
||||
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
|
||||
await this.updateComplete;
|
||||
fireEvent(this.dialogElement as HTMLElement, "iron-resize");
|
||||
}
|
||||
|
||||
private async _updateItem(): Promise<void> {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
try {
|
||||
await HELPERS[this.entry.platform].update(
|
||||
this.hass!,
|
||||
this._item.id,
|
||||
this._item
|
||||
);
|
||||
await this._registryEditor?.updateEntry();
|
||||
fireEvent(this, "close-dialog");
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _confirmDeleteItem(): Promise<void> {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.confirm_delete"
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
await HELPERS[this.entry.platform].delete(this.hass!, this._item.id);
|
||||
fireEvent(this, "close-dialog");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"entity-platform-helper-tab": EntityRegistrySettingsHelper;
|
||||
}
|
||||
}
|
138
src/panels/config/entities/entity-registry-basic-editor.ts
Normal file
138
src/panels/config/entities/entity-registry-basic-editor.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../components/ha-switch";
|
||||
import {
|
||||
ExtEntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaSwitch } from "../../../components/ha-switch";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
@customElement("ha-registry-basic-editor")
|
||||
export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() private _origEntityId!: string;
|
||||
@property() private _entityId!: string;
|
||||
@property() private _disabledBy!: string | null;
|
||||
@property() private _submitting?: boolean;
|
||||
|
||||
public async updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
new_entity_id: this._entityId.trim(),
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
params.disabled_by = this._disabledBy;
|
||||
}
|
||||
try {
|
||||
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("entry")) {
|
||||
return;
|
||||
}
|
||||
if (this.entry) {
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.hass ||
|
||||
!this.entry ||
|
||||
this.entry.entity_id !== this._origEntityId
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
const invalidDomainUpdate =
|
||||
computeDomain(this._entityId.trim()) !==
|
||||
computeDomain(this.entry.entity_id);
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.entity_id"
|
||||
)}
|
||||
error-message="Domain needs to stay the same"
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${!this._disabledBy}
|
||||
@change=${this._disabledByChanged}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_label"
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this._disabledBy && this._disabledBy !== "user"
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`config_entry.disabled_by.${this._disabledBy}`
|
||||
)
|
||||
)
|
||||
: ""}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_description"
|
||||
)}
|
||||
<br />${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.note"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-switch>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _disabledByChanged(ev: Event): void {
|
||||
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -12,26 +12,28 @@ import {
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-icon-input";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaSwitch } from "../../../components/ha-switch";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
removeEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
export class EntityRegistrySettings extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: EntityRegistryEntry;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() public dialogElement!: HTMLElement;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _entityId!: string;
|
||||
@property() private _disabledBy!: string | null;
|
||||
@property() private _error?: string;
|
||||
@ -43,6 +45,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
if (changedProperties.has("entry")) {
|
||||
this._error = undefined;
|
||||
this._name = this.entry.name || "";
|
||||
this._icon = this.entry.icon || "";
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
@ -59,7 +62,6 @@ export class EntityRegistrySettings extends LitElement {
|
||||
const invalidDomainUpdate =
|
||||
computeDomain(this._entityId.trim()) !==
|
||||
computeDomain(this.entry.entity_id);
|
||||
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
${!stateObj
|
||||
@ -83,9 +85,21 @@ export class EntityRegistrySettings extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.name"
|
||||
)}
|
||||
.placeholder=${stateObj ? computeStateName(stateObj) : ""}
|
||||
.placeholder=${this.entry.original_name}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.icon"
|
||||
)}
|
||||
.placeholder=${this.entry.original_icon}
|
||||
.disabled=${this._submitting}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.icon_error"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<paper-input
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
@ -153,6 +167,11 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._name = ev.detail.value;
|
||||
}
|
||||
|
||||
private _iconChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._error = undefined;
|
||||
this._entityId = ev.detail.value;
|
||||
@ -162,6 +181,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
name: this._name.trim() || null,
|
||||
icon: this._icon.trim() || null,
|
||||
new_entity_id: this._entityId.trim(),
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
@ -192,7 +212,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
|
||||
try {
|
||||
await removeEntityRegistryEntry(this.hass!, this._origEntityId);
|
||||
fireEvent(this as HTMLElement, "close-dialog");
|
||||
fireEvent(this, "close-dialog");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
@ -202,36 +222,39 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
margin-right: auto;
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
margin-right: auto;
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||
import {
|
||||
loadEntityRegistryDetailDialog,
|
||||
showEntityRegistryDetailDialog,
|
||||
} from "./show-dialog-entity-registry-detail";
|
||||
loadEntityEditorDialog,
|
||||
showEntityEditorDialog,
|
||||
} from "./show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
@ -75,7 +75,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@property() private _selectedEntities: string[] = [];
|
||||
@query("hass-tabs-subpage-data-table")
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
private getDialog?: () => DialogEntityRegistryDetail | undefined;
|
||||
private getDialog?: () => DialogEntityEditor | undefined;
|
||||
|
||||
private _columns = memoize(
|
||||
(narrow, _language): DataTableColumnContainer => {
|
||||
@ -387,33 +387,35 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.integrations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._filteredEntities(
|
||||
this._entities,
|
||||
this.hass.states,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly
|
||||
)}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
.data=${this._filteredEntities(
|
||||
this._entities,
|
||||
this.hass.states,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly
|
||||
)}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
>
|
||||
<div class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})} slot="header">
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</ha-data-table>
|
||||
<div
|
||||
class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})}
|
||||
slot="header"
|
||||
>
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
loadEntityRegistryDetailDialog();
|
||||
loadEntityEditorDialog();
|
||||
}
|
||||
|
||||
private _showDisabledChanged() {
|
||||
@ -524,7 +526,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
const entry = this._entities!.find(
|
||||
(entity) => entity.entity_id === entityId
|
||||
);
|
||||
this.getDialog = showEntityRegistryDetailDialog(this, {
|
||||
this.getDialog = showEntityEditorDialog(this, {
|
||||
entry,
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
@ -1,32 +1,33 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||
|
||||
export interface EntityRegistryDetailDialogParams {
|
||||
entry?: EntityRegistryEntry;
|
||||
entity_id: string;
|
||||
tab?: string;
|
||||
}
|
||||
|
||||
export const loadEntityRegistryDetailDialog = () =>
|
||||
export const loadEntityEditorDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "entity-registry-detail-dialog" */ "./dialog-entity-registry-detail"
|
||||
/* webpackChunkName: "entity-editor-dialog" */ "./dialog-entity-editor"
|
||||
);
|
||||
|
||||
const getDialog = () => {
|
||||
return document
|
||||
.querySelector("home-assistant")!
|
||||
.shadowRoot!.querySelector("dialog-entity-registry-detail") as
|
||||
| DialogEntityRegistryDetail
|
||||
.shadowRoot!.querySelector("dialog-entity-editor") as
|
||||
| DialogEntityEditor
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export const showEntityRegistryDetailDialog = (
|
||||
export const showEntityEditorDialog = (
|
||||
element: HTMLElement,
|
||||
entityDetailParams: EntityRegistryDetailDialogParams
|
||||
): (() => DialogEntityRegistryDetail | undefined) => {
|
||||
): (() => DialogEntityEditor | undefined) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-entity-registry-detail",
|
||||
dialogImport: loadEntityRegistryDetailDialog,
|
||||
dialogTag: "dialog-entity-editor",
|
||||
dialogImport: loadEntityEditorDialog,
|
||||
dialogParams: entityDetailParams,
|
||||
});
|
||||
return getDialog;
|
@ -6,10 +6,6 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { HomeAssistant, Route } from "../../types";
|
||||
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||
import {
|
||||
getOptimisticFrontendUserDataCollection,
|
||||
CoreFrontendUserData,
|
||||
} from "../../data/frontend";
|
||||
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
@ -71,6 +67,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
translationKey: "ui.panel.config.script.caption",
|
||||
icon: "hass:script-text",
|
||||
},
|
||||
{
|
||||
component: "helpers",
|
||||
path: "/config/helpers",
|
||||
translationKey: "ui.panel.config.helpers.caption",
|
||||
icon: "hass:tools",
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
persons: [
|
||||
{
|
||||
@ -235,6 +238,13 @@ class HaPanelConfig extends HassRouterPage {
|
||||
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
|
||||
),
|
||||
},
|
||||
helpers: {
|
||||
tag: "ha-config-helpers",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-helpers" */ "./helpers/ha-config-helpers"
|
||||
),
|
||||
},
|
||||
users: {
|
||||
tag: "ha-config-users",
|
||||
load: () =>
|
||||
@ -268,8 +278,6 @@ class HaPanelConfig extends HassRouterPage {
|
||||
|
||||
@property() private _wideSidebar: boolean = false;
|
||||
@property() private _wide: boolean = false;
|
||||
@property() private _coreUserData?: CoreFrontendUserData;
|
||||
@property() private _showAdvanced = false;
|
||||
@property() private _cloudStatus?: CloudStatus;
|
||||
|
||||
private _listeners: Array<() => void> = [];
|
||||
@ -286,17 +294,6 @@ class HaPanelConfig extends HassRouterPage {
|
||||
this._wideSidebar = matches;
|
||||
})
|
||||
);
|
||||
this._listeners.push(
|
||||
getOptimisticFrontendUserDataCollection(
|
||||
this.hass.connection,
|
||||
"core"
|
||||
).subscribe((coreUserData) => {
|
||||
this._coreUserData = coreUserData || {};
|
||||
this._showAdvanced = !!(
|
||||
this._coreUserData && this._coreUserData.showAdvanced
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
@ -337,7 +334,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
(el as PolymerElement).setProperties({
|
||||
route: this.routeTail,
|
||||
hass: this.hass,
|
||||
showAdvanced: this._showAdvanced,
|
||||
showAdvanced: Boolean(this.hass.userData?.showAdvanced),
|
||||
isWide,
|
||||
narrow: this.narrow,
|
||||
cloudStatus: this._cloudStatus,
|
||||
@ -345,7 +342,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
} else {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.showAdvanced = this._showAdvanced;
|
||||
el.showAdvanced = Boolean(this.hass.userData?.showAdvanced);
|
||||
el.isWide = isWide;
|
||||
el.narrow = this.narrow;
|
||||
el.cloudStatus = this._cloudStatus;
|
||||
|
24
src/panels/config/helpers/const.ts
Normal file
24
src/panels/config/helpers/const.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { InputBoolean } from "../../../data/input_boolean";
|
||||
|
||||
import { InputText } from "../../../data/input_text";
|
||||
|
||||
import { InputNumber } from "../../../data/input_number";
|
||||
|
||||
import { InputSelect } from "../../../data/input_select";
|
||||
|
||||
import { InputDateTime } from "../../../data/input_datetime";
|
||||
|
||||
export const HELPER_DOMAINS = [
|
||||
"input_boolean",
|
||||
"input_text",
|
||||
"input_number",
|
||||
"input_datetime",
|
||||
"input_select",
|
||||
];
|
||||
|
||||
export type Helper =
|
||||
| InputBoolean
|
||||
| InputText
|
||||
| InputNumber
|
||||
| InputSelect
|
||||
| InputDateTime;
|
197
src/panels/config/helpers/dialog-helper-detail.ts
Normal file
197
src/panels/config/helpers/dialog-helper-detail.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../components/ha-dialog";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import { createInputBoolean } from "../../../data/input_boolean";
|
||||
import { createInputText } from "../../../data/input_text";
|
||||
import { createInputNumber } from "../../../data/input_number";
|
||||
import { createInputDateTime } from "../../../data/input_datetime";
|
||||
import { createInputSelect } from "../../../data/input_select";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { Helper } from "./const";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "./forms/ha-input_boolean-form";
|
||||
import "./forms/ha-input_text-form";
|
||||
import "./forms/ha-input_datetime-form";
|
||||
import "./forms/ha-input_select-form";
|
||||
import "./forms/ha-input_number-form";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
const HELPERS = {
|
||||
input_boolean: createInputBoolean,
|
||||
input_text: createInputText,
|
||||
input_number: createInputNumber,
|
||||
input_datetime: createInputDateTime,
|
||||
input_select: createInputSelect,
|
||||
};
|
||||
|
||||
@customElement("dialog-helper-detail")
|
||||
export class DialogHelperDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _item?: Helper;
|
||||
@property() private _opened = false;
|
||||
@property() private _platform?: string;
|
||||
@property() private _error?: string;
|
||||
@property() private _submitting = false;
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this._platform = undefined;
|
||||
this._item = undefined;
|
||||
this._opened = true;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._opened = false;
|
||||
this._error = "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closing=${this.closeDialog}
|
||||
class=${classMap({ "button-left": !this._platform })}
|
||||
.heading=${this._platform
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.helpers.dialog.add_platform",
|
||||
"platform",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${this._platform}`
|
||||
) || this._platform
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.helpers.dialog.add_helper")}
|
||||
>
|
||||
${this._platform
|
||||
? html`
|
||||
<div class="form" @value-changed=${this._valueChanged}>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
${dynamicElement(`ha-${this._platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
new: true,
|
||||
})}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click="${this._createItem}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.helpers.dialog.create")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click="${this._goBack}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
Back
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
${Object.keys(HELPERS).map((platform: string) => {
|
||||
return html`
|
||||
<paper-icon-item
|
||||
.disabled=${!isComponentLoaded(this.hass, platform)}
|
||||
@click="${this._platformPicked}"
|
||||
.platform="${platform}"
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${domainIcon(platform)}
|
||||
></ha-icon>
|
||||
<span class="item-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${platform}`
|
||||
) || platform}
|
||||
</span>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
})}
|
||||
<mwc-button slot="primaryAction" @click="${this.closeDialog}">
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
this._item = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _createItem(): Promise<void> {
|
||||
if (!this._platform || !this._item) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
this._error = "";
|
||||
try {
|
||||
await HELPERS[this._platform](this.hass, this._item);
|
||||
this.closeDialog();
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _platformPicked(ev: Event): void {
|
||||
this._platform = (ev.currentTarget! as any).platform;
|
||||
}
|
||||
|
||||
private _goBack() {
|
||||
this._platform = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
}
|
||||
ha-dialog.button-left {
|
||||
--justify-action-buttons: flex-start;
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* make dialog fullscreen on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 100vw;
|
||||
--mdc-dialog-max-height: 100vh;
|
||||
--mdc-dialog-shape-radius: 0px;
|
||||
--vertial-align-dialog: flex-end;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-helper-detail": DialogHelperDetail;
|
||||
}
|
||||
}
|
137
src/panels/config/helpers/forms/ha-input_boolean-form.ts
Normal file
137
src/panels/config/helpers/forms/ha-input_boolean-form.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { InputBoolean } from "../../../../data/input_boolean";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
@customElement("ha-input_boolean-form")
|
||||
class HaInputBooleanForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputBoolean;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: boolean;
|
||||
|
||||
set item(item: InputBoolean) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._initial}
|
||||
@change=${this._initialChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _initialChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, initial: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_boolean-form": HaInputBooleanForm;
|
||||
}
|
||||
}
|
165
src/panels/config/helpers/forms/ha-input_datetime-form.ts
Normal file
165
src/panels/config/helpers/forms/ha-input_datetime-form.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputDateTime } from "../../../../data/input_datetime";
|
||||
|
||||
@customElement("ha-input_datetime-form")
|
||||
class HaInputDateTimeForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputDateTime;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: string;
|
||||
@property() private _hasTime?: boolean;
|
||||
@property() private _hasDate?: boolean;
|
||||
|
||||
set item(item: InputDateTime) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
this._hasTime = item.has_time;
|
||||
this._hasDate = item.has_date;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.has_time"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._hasTime}
|
||||
@change=${this._hasTimeChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.has_date"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._hasDate}
|
||||
@change=${this._hasDateChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _hasTimeChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, has_time: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _hasDateChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, has_date: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_datetime-form": HaInputDateTimeForm;
|
||||
}
|
||||
}
|
214
src/panels/config/helpers/forms/ha-input_number-form.ts
Normal file
214
src/panels/config/helpers/forms/ha-input_number-form.ts
Normal file
@ -0,0 +1,214 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputNumber } from "../../../../data/input_number";
|
||||
|
||||
@customElement("ha-input_number-form")
|
||||
class HaInputNumberForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: Partial<InputNumber>;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: number;
|
||||
@property() private _max?: number;
|
||||
@property() private _min?: number;
|
||||
@property() private _mode?: string;
|
||||
@property() private _step?: number;
|
||||
// tslint:disable-next-line: variable-name
|
||||
@property() private _unit_of_measurement?: string;
|
||||
|
||||
set item(item: InputNumber) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._max = item.max ?? 100;
|
||||
this._min = item.min ?? 0;
|
||||
this._initial = item.initial;
|
||||
this._mode = item.mode || "slider";
|
||||
this._step = item.step || 1;
|
||||
this._unit_of_measurement = item.unit_of_measurement;
|
||||
} else {
|
||||
this._item = {
|
||||
min: 0,
|
||||
max: 0,
|
||||
};
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._max = 100;
|
||||
this._min = 0;
|
||||
this._mode = "slider";
|
||||
this._step = 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<paper-input
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.max"
|
||||
)}
|
||||
></paper-input>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="layout horizontal center justified">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.mode"
|
||||
)}
|
||||
<paper-radio-group
|
||||
.selected=${this._mode}
|
||||
@selected-changed=${this._modeChanged}
|
||||
>
|
||||
<paper-radio-button name="slider">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.slider"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="box">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.box"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
</div>
|
||||
${this._mode === "slider"
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._step}
|
||||
.configValue=${"step"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.step"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
<paper-input
|
||||
.value=${this._unit_of_measurement}
|
||||
.configValue=${"unit_of_measurement"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.unit_of_measurement"
|
||||
)}
|
||||
></paper-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, mode: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (value === undefined || value === "") {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_number-form": HaInputNumberForm;
|
||||
}
|
||||
}
|
240
src/panels/config/helpers/forms/ha-input_select-form.ts
Normal file
240
src/panels/config/helpers/forms/ha-input_select-form.ts
Normal file
@ -0,0 +1,240 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
query,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputSelect } from "../../../../data/input_select";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("ha-input_select-form")
|
||||
class HaInputSelectForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputSelect;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _options: string[] = [];
|
||||
@property() private _initial?: string;
|
||||
@query("#option_input") private _optionInput?: PaperInputElement;
|
||||
|
||||
set item(item: InputSelect) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
this._options = item.options || [];
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._options = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}:
|
||||
${this._options.length
|
||||
? this._options.map((option, index) => {
|
||||
return html`
|
||||
<paper-item class="option">
|
||||
<paper-item-body> ${option} </paper-item-body>
|
||||
<paper-icon-button
|
||||
.index=${index}
|
||||
.title=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
)}
|
||||
@click=${this._removeOption}
|
||||
icon="hass:delete"
|
||||
></paper-icon-button>
|
||||
</paper-item>
|
||||
`;
|
||||
})
|
||||
: html`
|
||||
<paper-item>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.no_options"
|
||||
)}
|
||||
</paper-item>
|
||||
`}
|
||||
<div class="layout horizontal bottom">
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="option_input"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add_option"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addOption}
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<ha-paper-dropdown-menu
|
||||
label-float
|
||||
dynamic-align
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-initial"
|
||||
.selected=${this._initial}
|
||||
@selected-changed=${this._initialChanged}
|
||||
>
|
||||
${this._options.map(
|
||||
(option) => html`
|
||||
<paper-item item-initial=${option}>${option}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _initialChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, initial: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _handleKeyAdd(ev: KeyboardEvent) {
|
||||
ev.stopPropagation();
|
||||
if (ev.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
this._addOption();
|
||||
}
|
||||
|
||||
private _addOption() {
|
||||
const input = this._optionInput;
|
||||
if (!input || !input.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, options: [...this._options, input.value] },
|
||||
});
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
private async _removeOption(ev: Event) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Delete this item?",
|
||||
text: "Are you sure you want to delete this item?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const index = (ev.target as any).index;
|
||||
const options = [...this._options];
|
||||
options.splice(index, 1);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, options },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.option {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_select-form": HaInputSelectForm;
|
||||
}
|
||||
}
|
199
src/panels/config/helpers/forms/ha-input_text-form.ts
Normal file
199
src/panels/config/helpers/forms/ha-input_text-form.ts
Normal file
@ -0,0 +1,199 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { InputText } from "../../../../data/input_text";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
@customElement("ha-input_text-form")
|
||||
class HaInputTextForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputText;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: string;
|
||||
@property() private _max?: number;
|
||||
@property() private _min?: number;
|
||||
@property() private _mode?: string;
|
||||
@property() private _pattern?: string;
|
||||
|
||||
set item(item: InputText) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._max = item.max || 100;
|
||||
this._min = item.min || 0;
|
||||
this._initial = item.initial;
|
||||
this._mode = item.mode || "text";
|
||||
this._pattern = item.pattern;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._max = 100;
|
||||
this._min = 0;
|
||||
this._mode = "text";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
min="0"
|
||||
max="255"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
min="0"
|
||||
max="255"
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.max"
|
||||
)}
|
||||
></paper-input>
|
||||
<div class="layout horizontal center justified">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.mode"
|
||||
)}
|
||||
<paper-radio-group
|
||||
.selected=${this._mode}
|
||||
@selected-changed=${this._modeChanged}
|
||||
>
|
||||
<paper-radio-button name="text">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.text"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="password">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.password"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
</div>
|
||||
<paper-input
|
||||
.value=${this._pattern}
|
||||
.configValue=${"pattern"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern"
|
||||
)}
|
||||
></paper-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, mode: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_text-form": HaInputTextForm;
|
||||
}
|
||||
}
|
184
src/panels/config/helpers/ha-config-helpers.ts
Normal file
184
src/panels/config/helpers/ha-config-helpers.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import memoize from "memoize-one";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import "../../../common/search/search-input";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import { HELPER_DOMAINS } from "./const";
|
||||
|
||||
@customElement("ha-config-helpers")
|
||||
export class HaConfigHelpers extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public route!: Route;
|
||||
@property() private _stateItems: HassEntity[] = [];
|
||||
|
||||
private _columns = memoize(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
icon: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (icon) => html`
|
||||
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.headers.name"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (name, item: any) =>
|
||||
html`
|
||||
${name}
|
||||
<div style="color: var(--secondary-text-color)">
|
||||
${item.entity_id}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
type: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.headers.type"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (type) =>
|
||||
html`
|
||||
${this.hass.localize(`ui.panel.config.helpers.types.${type}`) ||
|
||||
type}
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
private _getItems = memoize((stateItems: HassEntity[]) => {
|
||||
return stateItems.map((state) => {
|
||||
return {
|
||||
id: state.entity_id,
|
||||
icon: state.attributes.icon,
|
||||
name: state.attributes.friendly_name || "",
|
||||
entity_id: state.entity_id,
|
||||
editable: state.attributes.editable,
|
||||
type: computeStateDomain(state),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || this._stateItems === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automation}
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.data=${this._getItems(this._stateItems)}
|
||||
@row-click=${this._openEditDialog}
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.add_helper"
|
||||
)}"
|
||||
@click=${this._createHelpler}
|
||||
></ha-fab>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._getStates();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (oldHass && this._stateItems) {
|
||||
this._getStates(oldHass);
|
||||
}
|
||||
}
|
||||
|
||||
private _getStates(oldHass?: HomeAssistant) {
|
||||
let changed = false;
|
||||
const tempStates = Object.values(this.hass!.states).filter((entity) => {
|
||||
if (!HELPER_DOMAINS.includes(computeStateDomain(entity))) {
|
||||
return false;
|
||||
}
|
||||
if (oldHass?.states[entity.entity_id] !== entity) {
|
||||
changed = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (changed || this._stateItems.length !== tempStates.length) {
|
||||
this._stateItems = tempStates;
|
||||
}
|
||||
}
|
||||
|
||||
private async _openEditDialog(ev: CustomEvent): Promise<void> {
|
||||
const entityId = (ev.detail as RowClickedEvent).id;
|
||||
showEntityEditorDialog(this, {
|
||||
entity_id: entityId,
|
||||
});
|
||||
}
|
||||
|
||||
private _createHelpler() {
|
||||
showHelperDetailDialog(this);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
14
src/panels/config/helpers/show-dialog-helper-detail.ts
Normal file
14
src/panels/config/helpers/show-dialog-helper-detail.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export const loadHelperDetailDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "helper-detail-dialog" */ "./dialog-helper-detail"
|
||||
);
|
||||
|
||||
export const showHelperDetailDialog = (element: HTMLElement) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-helper-detail",
|
||||
dialogImport: loadHelperDetailDialog,
|
||||
dialogParams: {},
|
||||
});
|
||||
};
|
@ -148,6 +148,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
${this._config.show_icon
|
||||
? html`
|
||||
<ha-icon
|
||||
tabindex="-1"
|
||||
data-domain=${ifDefined(
|
||||
this._config.state_color && stateObj
|
||||
? computeStateDomain(stateObj)
|
||||
@ -170,7 +171,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
: ""}
|
||||
${this._config.show_name
|
||||
? html`
|
||||
<span>
|
||||
<span tabindex="-1">
|
||||
${this._config.name ||
|
||||
(stateObj ? computeStateName(stateObj) : "")}
|
||||
</span>
|
||||
@ -224,6 +225,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
}
|
||||
|
||||
ha-icon,
|
||||
span {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
${iconColorCSS}
|
||||
`;
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../components/hui-theme-select-editor";
|
||||
|
||||
import "../../../../components/ha-icon-input";
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
@ -17,6 +19,7 @@ import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { LightCardConfig } from "../../cards/types";
|
||||
import { stateIcon } from "../../../../common/entity/state_icon";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
@ -34,8 +37,7 @@ export class HuiLightCardEditor extends LitElement
|
||||
@property() private _config?: LightCardConfig;
|
||||
|
||||
public setConfig(config: LightCardConfig): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
this._config = cardConfigStruct(config);
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
@ -86,16 +88,18 @@ export class HuiLightCardEditor extends LitElement
|
||||
.configValue="${"name"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
<ha-icon-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.icon"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value="${this._icon}"
|
||||
.placeholder=${this._icon ||
|
||||
stateIcon(this.hass.states[this._entity])}
|
||||
.configValue="${"icon"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
></ha-icon-input>
|
||||
</div>
|
||||
|
||||
<hui-theme-select-editor
|
||||
|
@ -20,7 +20,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
import { HomeAssistant, InputSelectEntity } from "../../../types";
|
||||
import { LovelaceRow } from "./types";
|
||||
import { setInputSelectOption } from "../../../data/input-select";
|
||||
import { setInputSelectOption } from "../../../data/input_select";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
|
@ -118,6 +118,9 @@ export const haStyle = css`
|
||||
.layout.center-center {
|
||||
align-items: center;
|
||||
}
|
||||
.layout.bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.layout.center-justified,
|
||||
.layout.center-center {
|
||||
justify-content: center;
|
||||
|
@ -18,7 +18,7 @@ import "../components/entity/state-badge";
|
||||
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { HomeAssistant, InputSelectEntity } from "../types";
|
||||
import { setInputSelectOption } from "../data/input-select";
|
||||
import { setInputSelectOption } from "../data/input_select";
|
||||
import { PolymerIronSelectEvent } from "../polymer-types";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { Constructor, ServiceCallResponse } from "../types";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { broadcastConnectionStatus } from "../data/connection-status";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
|
||||
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
superClass: T
|
||||
@ -141,6 +142,9 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
subscribeConfig(conn, (config) => this._updateHass({ config }));
|
||||
subscribeServices(conn, (services) => this._updateHass({ services }));
|
||||
subscribePanels(conn, (panels) => this._updateHass({ panels }));
|
||||
subscribeFrontendUserData(conn, "core", (userData) =>
|
||||
this._updateHass({ userData })
|
||||
);
|
||||
}
|
||||
|
||||
protected hassReconnected() {
|
||||
|
@ -644,6 +644,8 @@
|
||||
"no_unique_id": "This entity does not have a unique ID, therefore it's settings can not be managed from the UI.",
|
||||
"editor": {
|
||||
"name": "Name Override",
|
||||
"icon": "Icon Override",
|
||||
"icon_error": "Icons should be in the format 'prefix:iconname', e.g. 'mdi:home'",
|
||||
"entity_id": "Entity ID",
|
||||
"unavailable": "This entity is not currently available.",
|
||||
"enabled_label": "Enable entity",
|
||||
@ -655,6 +657,44 @@
|
||||
"note": "Note: this might not work yet with all integrations."
|
||||
}
|
||||
},
|
||||
"helper_settings": {
|
||||
"not_editable": "Not editable",
|
||||
"not_editable_text": "This entity can't be changed from the UI because it is defined in configuration.yaml.",
|
||||
"required_error_msg": "This field is required",
|
||||
"generic": {
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"initial_value": "Initial value at start",
|
||||
"initial_value_explain": "The value the element will have when Home Assistant starts. When left empty, the value will be restored to it's previous value."
|
||||
},
|
||||
"input_datetime": {
|
||||
"has_time": "Time",
|
||||
"has_date": "Date"
|
||||
},
|
||||
"input_text": {
|
||||
"min": "Minimum lenght",
|
||||
"max": "Maximum lenght",
|
||||
"mode": "Display mode",
|
||||
"text": "Text",
|
||||
"password": "Password",
|
||||
"pattern": "Regex pattern for client-side validation"
|
||||
},
|
||||
"input_number": {
|
||||
"min": "Minimum value",
|
||||
"max": "Maximum value",
|
||||
"mode": "Display mode",
|
||||
"box": "Input field",
|
||||
"slider": "Slider",
|
||||
"step": "Step size of the slider",
|
||||
"unit_of_measurement": "Unit of measurement"
|
||||
},
|
||||
"input_select": {
|
||||
"options": "Options",
|
||||
"add_option": "Add option",
|
||||
"no_options": "There are no options yet.",
|
||||
"add": "Add"
|
||||
}
|
||||
},
|
||||
"options_flow": {
|
||||
"form": {
|
||||
"header": "Options"
|
||||
@ -757,6 +797,30 @@
|
||||
"create": "CREATE"
|
||||
}
|
||||
},
|
||||
"helpers": {
|
||||
"caption": "Helpers",
|
||||
"description": "Elements that can help build automations.",
|
||||
"types": {
|
||||
"input_text": "Text",
|
||||
"input_number": "Number",
|
||||
"input_select": "Dropdown",
|
||||
"input_boolean": "Toggle",
|
||||
"input_datetime": "Date and/or time"
|
||||
},
|
||||
"picker": {
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"type": "Type",
|
||||
"editable": "Editable"
|
||||
},
|
||||
"add_helper": "Add helper"
|
||||
},
|
||||
"dialog": {
|
||||
"create": "Create",
|
||||
"add_helper": "Add helper",
|
||||
"add_platform": "Add {platform}"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"caption": "General",
|
||||
"description": "Change your general Home Assistant configuration",
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { LocalizeFunc } from "./common/translations/localize";
|
||||
import { ExternalMessaging } from "./external_app/external_messaging";
|
||||
import { CoreFrontendUserData } from "./data/frontend";
|
||||
|
||||
declare global {
|
||||
var __DEV__: boolean;
|
||||
@ -155,6 +156,7 @@ export interface HomeAssistant {
|
||||
dockedSidebar: "docked" | "always_hidden" | "auto";
|
||||
moreInfoEntityId: string | null;
|
||||
user?: CurrentUser;
|
||||
userData?: CoreFrontendUserData | null;
|
||||
hassUrl(path?): string;
|
||||
callService(
|
||||
domain: string,
|
||||
|
Loading…
x
Reference in New Issue
Block a user