mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Re-do developer tools service (#8410)
This commit is contained in:
parent
627424b8b9
commit
6092af8de6
@ -101,7 +101,7 @@
|
|||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^0.13.2",
|
"hls.js": "^0.13.2",
|
||||||
"home-assistant-js-websocket": "^5.8.1",
|
"home-assistant-js-websocket": "^5.9.0",
|
||||||
"idb-keyval": "^3.2.0",
|
"idb-keyval": "^3.2.0",
|
||||||
"intl-messageformat": "^8.3.9",
|
"intl-messageformat": "^8.3.9",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
|
internalProperty,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
@ -107,8 +108,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
private _opened?: boolean;
|
|
||||||
|
@internalProperty() private _opened?: boolean;
|
||||||
|
|
||||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||||
|
|
||||||
@ -290,6 +292,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
: this.label}
|
: this.label}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
|
.disabled=${this.disabled}
|
||||||
item-value-path="id"
|
item-value-path="id"
|
||||||
item-id-path="id"
|
item-id-path="id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
|
@ -117,6 +117,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
@internalProperty() private _areas?: AreaRegistryEntry[];
|
@internalProperty() private _areas?: AreaRegistryEntry[];
|
||||||
|
|
||||||
@internalProperty() private _devices?: DeviceRegistryEntry[];
|
@internalProperty() private _devices?: DeviceRegistryEntry[];
|
||||||
@ -339,6 +341,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
|
.disabled=${this.disabled}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._areaChanged}
|
@value-changed=${this._areaChanged}
|
||||||
>
|
>
|
||||||
@ -349,6 +352,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
.placeholder=${this.placeholder
|
.placeholder=${this.placeholder
|
||||||
? this._area(this.placeholder)?.name
|
? this._area(this.placeholder)?.name
|
||||||
: undefined}
|
: undefined}
|
||||||
|
.disabled=${this.disabled}
|
||||||
class="input"
|
class="input"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
|
internalProperty,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
query,
|
query,
|
||||||
@ -67,8 +68,9 @@ export class HaComboBox extends LitElement {
|
|||||||
model: { item: any }
|
model: { item: any }
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
private _opened?: boolean;
|
|
||||||
|
@internalProperty() private _opened?: boolean;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||||
|
|
||||||
@ -95,12 +97,14 @@ export class HaComboBox extends LitElement {
|
|||||||
.filteredItems=${this.filteredItems}
|
.filteredItems=${this.filteredItems}
|
||||||
.renderer=${this.renderer || defaultRowRenderer}
|
.renderer=${this.renderer || defaultRowRenderer}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
|
.disabled=${this.disabled}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
<paper-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
|
.disabled=${this.disabled}
|
||||||
class="input"
|
class="input"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
@ -21,8 +21,11 @@ export class HaActionSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-automation-action
|
return html`<ha-automation-action
|
||||||
|
.disabled=${this.disabled}
|
||||||
.actions=${this.value || []}
|
.actions=${this.value || []}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>`;
|
></ha-automation-action>`;
|
||||||
@ -34,6 +37,10 @@ export class HaActionSelector extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) ha-automation-action {
|
||||||
|
opacity: var(--light-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ export class HaAreaSelector extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() public _configEntries?: ConfigEntry[];
|
@internalProperty() public _configEntries?: ConfigEntry[];
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected updated(changedProperties) {
|
protected updated(changedProperties) {
|
||||||
if (changedProperties.has("selector")) {
|
if (changedProperties.has("selector")) {
|
||||||
const oldSelector = changedProperties.get("selector");
|
const oldSelector = changedProperties.get("selector");
|
||||||
@ -50,6 +52,7 @@ export class HaAreaSelector extends LitElement {
|
|||||||
.includeDomains=${this.selector.area.entity?.domain
|
.includeDomains=${this.selector.area.entity?.domain
|
||||||
? [this.selector.area.entity.domain]
|
? [this.selector.area.entity.domain]
|
||||||
: undefined}
|
: undefined}
|
||||||
|
.disabled=${this.disabled}
|
||||||
></ha-area-picker>`;
|
></ha-area-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,11 +19,14 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html` <ha-formfield alignEnd spaceBetween .label=${this.label}>
|
return html` <ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${this.value}
|
.checked=${this.value}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
|
.disabled=${this.disabled}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>`;
|
</ha-formfield>`;
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,12 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() public _configEntries?: ConfigEntry[];
|
@internalProperty() public _configEntries?: ConfigEntry[];
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected updated(changedProperties) {
|
protected updated(changedProperties) {
|
||||||
if (changedProperties.has("selector")) {
|
if (changedProperties.has("selector")) {
|
||||||
const oldSelector = changedProperties.get("selector");
|
const oldSelector = changedProperties.get("selector");
|
||||||
if (oldSelector !== this.selector && this.selector.device.integration) {
|
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
||||||
this._loadConfigEntries();
|
this._loadConfigEntries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,24 +46,25 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
.includeDomains=${this.selector.device.entity?.domain
|
.includeDomains=${this.selector.device.entity?.domain
|
||||||
? [this.selector.device.entity.domain]
|
? [this.selector.device.entity.domain]
|
||||||
: undefined}
|
: undefined}
|
||||||
|
.disabled=${this.disabled}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
></ha-device-picker>`;
|
></ha-device-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
||||||
if (
|
if (
|
||||||
this.selector.device.manufacturer &&
|
this.selector.device?.manufacturer &&
|
||||||
device.manufacturer !== this.selector.device.manufacturer
|
device.manufacturer !== this.selector.device.manufacturer
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.selector.device.model &&
|
this.selector.device?.model &&
|
||||||
device.model !== this.selector.device.model
|
device.model !== this.selector.device.model
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.selector.device.integration) {
|
if (this.selector.device?.integration) {
|
||||||
if (
|
if (
|
||||||
this._configEntries &&
|
this._configEntries &&
|
||||||
!this._configEntries.some((entry) =>
|
!this._configEntries.some((entry) =>
|
||||||
|
@ -25,12 +25,15 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-entity-picker
|
return html`<ha-entity-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.entityFilter=${(entity) => this._filterEntities(entity)}
|
.entityFilter=${(entity) => this._filterEntities(entity)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
></ha-entity-picker>`;
|
></ha-entity-picker>`;
|
||||||
}
|
}
|
||||||
@ -51,12 +54,12 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _filterEntities(entity: HassEntity): boolean {
|
private _filterEntities(entity: HassEntity): boolean {
|
||||||
if (this.selector.entity.domain) {
|
if (this.selector.entity?.domain) {
|
||||||
if (computeStateDomain(entity) !== this.selector.entity.domain) {
|
if (computeStateDomain(entity) !== this.selector.entity.domain) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.selector.entity.device_class) {
|
if (this.selector.entity?.device_class) {
|
||||||
if (
|
if (
|
||||||
!entity.attributes.device_class ||
|
!entity.attributes.device_class ||
|
||||||
entity.attributes.device_class !== this.selector.entity.device_class
|
entity.attributes.device_class !== this.selector.entity.device_class
|
||||||
@ -64,7 +67,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.selector.entity.integration) {
|
if (this.selector.entity?.integration) {
|
||||||
if (
|
if (
|
||||||
!this._entityPlaformLookup ||
|
!this._entityPlaformLookup ||
|
||||||
this._entityPlaformLookup[entity.entity_id] !==
|
this._entityPlaformLookup[entity.entity_id] !==
|
||||||
|
@ -21,8 +21,12 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public value?: number;
|
@property() public value?: number;
|
||||||
|
|
||||||
|
@property() public placeholder?: number;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`${this.label}
|
return html`${this.label}
|
||||||
${this.selector.number.mode === "slider"
|
${this.selector.number.mode === "slider"
|
||||||
@ -31,6 +35,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
.max=${this.selector.number.max}
|
.max=${this.selector.number.max}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.step=${this.selector.number.step}
|
.step=${this.selector.number.step}
|
||||||
|
.disabled=${this.disabled}
|
||||||
pin
|
pin
|
||||||
ignore-bar-touch
|
ignore-bar-touch
|
||||||
@change=${this._handleSliderChange}
|
@change=${this._handleSliderChange}
|
||||||
@ -42,12 +47,14 @@ export class HaNumberSelector extends LitElement {
|
|||||||
.label=${this.selector.number.mode === "slider"
|
.label=${this.selector.number.mode === "slider"
|
||||||
? undefined
|
? undefined
|
||||||
: this.label}
|
: this.label}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
.noLabelFloat=${this.selector.number.mode === "slider"}
|
.noLabelFloat=${this.selector.number.mode === "slider"}
|
||||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||||
.min=${this.selector.number.min}
|
.min=${this.selector.number.min}
|
||||||
.max=${this.selector.number.max}
|
.max=${this.selector.number.max}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.step=${this.selector.number.step}
|
.step=${this.selector.number.step}
|
||||||
|
.disabled=${this.disabled}
|
||||||
type="number"
|
type="number"
|
||||||
auto-validate
|
auto-validate
|
||||||
@value-changed=${this._handleInputChange}
|
@value-changed=${this._handleInputChange}
|
||||||
|
@ -11,8 +11,14 @@ export class HaObjectSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-yaml-editor
|
return html`<ha-yaml-editor
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
.defaultValue=${this.value}
|
.defaultValue=${this.value}
|
||||||
@value-changed=${this._handleChange}
|
@value-changed=${this._handleChange}
|
||||||
></ha-yaml-editor>`;
|
></ha-yaml-editor>`;
|
||||||
|
@ -21,8 +21,13 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-paper-dropdown-menu .label=${this.label}>
|
return html`<ha-paper-dropdown-menu
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.label}
|
||||||
|
>
|
||||||
<paper-listbox
|
<paper-listbox
|
||||||
slot="dropdown-content"
|
slot="dropdown-content"
|
||||||
attr-for-selected="item-value"
|
attr-for-selected="item-value"
|
||||||
@ -41,7 +46,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
if (!ev.detail.value) {
|
if (this.disabled || !ev.detail.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
|
@ -42,6 +42,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@internalProperty() private _configEntries?: ConfigEntry[];
|
@internalProperty() private _configEntries?: ConfigEntry[];
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||||
@ -84,6 +86,7 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
|||||||
.includeDomains=${this.selector.target.entity?.domain
|
.includeDomains=${this.selector.target.entity?.domain
|
||||||
? [this.selector.target.entity.domain]
|
? [this.selector.target.entity.domain]
|
||||||
: undefined}
|
: undefined}
|
||||||
|
.disabled=${this.disabled}
|
||||||
></ha-target-picker>`;
|
></ha-target-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,20 @@ export class HaTextSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property() public selector!: StringSelector;
|
@property() public selector!: StringSelector;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this.selector.text?.multiline) {
|
if (this.selector.text?.multiline) {
|
||||||
return html`<paper-textarea
|
return html`<paper-textarea
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value="${this.value}"
|
.placeholder=${this.placeholder}
|
||||||
@value-changed="${this._handleChange}"
|
.value=${this.value}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._handleChange}
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
@ -29,6 +35,8 @@ export class HaTextSelector extends LitElement {
|
|||||||
return html`<paper-input
|
return html`<paper-input
|
||||||
required
|
required
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._handleChange}
|
@value-changed=${this._handleChange}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
></paper-input>`;
|
></paper-input>`;
|
||||||
|
@ -17,6 +17,8 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const parts = this.value?.split(":") || [];
|
const parts = this.value?.split(":") || [];
|
||||||
const hours = useAMPM ? parts[0] ?? "12" : parts[0] ?? "0";
|
const hours = useAMPM ? parts[0] ?? "12" : parts[0] ?? "0";
|
||||||
@ -29,6 +31,7 @@ export class HaTimeSelector extends LitElement {
|
|||||||
.sec=${parts[2] ?? "00"}
|
.sec=${parts[2] ?? "00"}
|
||||||
.format=${useAMPM ? 12 : 24}
|
.format=${useAMPM ? 12 : 24}
|
||||||
.amPm=${useAMPM && (Number(hours) > 12 ? "PM" : "AM")}
|
.amPm=${useAMPM && (Number(hours) > 12 ? "PM" : "AM")}
|
||||||
|
.disabled=${this.disabled}
|
||||||
@change=${this._timeChanged}
|
@change=${this._timeChanged}
|
||||||
@am-pm-changed=${this._timeChanged}
|
@am-pm-changed=${this._timeChanged}
|
||||||
hide-label
|
hide-label
|
||||||
|
@ -24,6 +24,10 @@ export class HaSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: any;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
const input = this.shadowRoot!.getElementById("selector");
|
const input = this.shadowRoot!.getElementById("selector");
|
||||||
if (!input) {
|
if (!input) {
|
||||||
@ -43,6 +47,8 @@ export class HaSelector extends LitElement {
|
|||||||
selector: this.selector,
|
selector: this.selector,
|
||||||
value: this.value,
|
value: this.value,
|
||||||
label: this.label,
|
label: this.label,
|
||||||
|
placeholder: this.placeholder,
|
||||||
|
disabled: this.disabled,
|
||||||
id: "selector",
|
id: "selector",
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
|
@ -22,6 +22,7 @@ import "./ha-selector/ha-selector";
|
|||||||
import "./ha-service-picker";
|
import "./ha-service-picker";
|
||||||
import "./ha-settings-row";
|
import "./ha-settings-row";
|
||||||
import "./ha-yaml-editor";
|
import "./ha-yaml-editor";
|
||||||
|
import "./ha-checkbox";
|
||||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||||
|
|
||||||
interface ExtHassService extends Omit<HassService, "fields"> {
|
interface ExtHassService extends Omit<HassService, "fields"> {
|
||||||
@ -30,6 +31,7 @@ interface ExtHassService extends Omit<HassService, "fields"> {
|
|||||||
name?: string;
|
name?: string;
|
||||||
description: string;
|
description: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
advanced?: boolean;
|
||||||
default?: any;
|
default?: any;
|
||||||
example?: any;
|
example?: any;
|
||||||
selector?: Selector;
|
selector?: Selector;
|
||||||
@ -48,14 +50,26 @@ export class HaServiceControl extends LitElement {
|
|||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
|
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public showAdvanced?: boolean;
|
||||||
|
|
||||||
@internalProperty() private _serviceData?: ExtHassService;
|
@internalProperty() private _serviceData?: ExtHassService;
|
||||||
|
|
||||||
|
@internalProperty() private _checkedKeys = new Set();
|
||||||
|
|
||||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (!changedProperties.has("value")) {
|
if (!changedProperties.has("value")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const oldValue = changedProperties.get("value") as
|
||||||
|
| undefined
|
||||||
|
| this["value"];
|
||||||
|
|
||||||
|
if (oldValue?.service !== this.value?.service) {
|
||||||
|
this._checkedKeys = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
this._serviceData = this.value?.service
|
this._serviceData = this.value?.service
|
||||||
? this._getServiceInfo(this.value.service)
|
? this._getServiceInfo(this.value.service)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -63,13 +77,33 @@ export class HaServiceControl extends LitElement {
|
|||||||
if (
|
if (
|
||||||
this._serviceData &&
|
this._serviceData &&
|
||||||
"target" in this._serviceData &&
|
"target" in this._serviceData &&
|
||||||
this.value?.data?.entity_id
|
(this.value?.data?.entity_id ||
|
||||||
|
this.value?.data?.area_id ||
|
||||||
|
this.value?.data?.device_id)
|
||||||
) {
|
) {
|
||||||
|
const target = {
|
||||||
|
...this.value.target,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.value.data.entity_id && !this.value.target?.entity_id) {
|
||||||
|
target.entity_id = this.value.data.entity_id;
|
||||||
|
}
|
||||||
|
if (this.value.data.area_id && !this.value.target?.area_id) {
|
||||||
|
target.area_id = this.value.data.area_id;
|
||||||
|
}
|
||||||
|
if (this.value.data.device_id && !this.value.target?.device_id) {
|
||||||
|
target.device_id = this.value.data.device_id;
|
||||||
|
}
|
||||||
|
|
||||||
this.value = {
|
this.value = {
|
||||||
...this.value,
|
...this.value,
|
||||||
target: { ...this.value.target, entity_id: this.value.data.entity_id },
|
target,
|
||||||
|
data: { ...this.value.data },
|
||||||
};
|
};
|
||||||
|
|
||||||
delete this.value.data!.entity_id;
|
delete this.value.data!.entity_id;
|
||||||
|
delete this.value.data!.device_id;
|
||||||
|
delete this.value.data!.area_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value?.data) {
|
if (this.value?.data) {
|
||||||
@ -125,24 +159,46 @@ export class HaServiceControl extends LitElement {
|
|||||||
legacy &&
|
legacy &&
|
||||||
this._serviceData?.fields.find((field) => field.key === "entity_id");
|
this._serviceData?.fields.find((field) => field.key === "entity_id");
|
||||||
|
|
||||||
|
const hasOptional = Boolean(
|
||||||
|
!legacy &&
|
||||||
|
this._serviceData?.fields.some(
|
||||||
|
(field) => field.selector && !field.required
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return html`<ha-service-picker
|
return html`<ha-service-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value?.service}
|
.value=${this.value?.service}
|
||||||
@value-changed=${this._serviceChanged}
|
@value-changed=${this._serviceChanged}
|
||||||
></ha-service-picker>
|
></ha-service-picker>
|
||||||
|
<p>${this._serviceData?.description}</p>
|
||||||
${this._serviceData && "target" in this._serviceData
|
${this._serviceData && "target" in this._serviceData
|
||||||
? html`<ha-selector
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
.hass=${this.hass}
|
${hasOptional
|
||||||
.selector=${this._serviceData.target
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
? { target: this._serviceData.target }
|
: ""}
|
||||||
: {
|
<span slot="heading"
|
||||||
target: {
|
>${this.hass.localize(
|
||||||
entity: { domain: computeDomain(this.value!.service) },
|
"ui.components.service-control.target"
|
||||||
},
|
)}</span
|
||||||
}}
|
>
|
||||||
@value-changed=${this._targetChanged}
|
<span slot="description"
|
||||||
.value=${this.value?.target}
|
>${this.hass.localize(
|
||||||
></ha-selector>`
|
"ui.components.service-control.target_description"
|
||||||
|
)}</span
|
||||||
|
><ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${this._serviceData.target
|
||||||
|
? { target: this._serviceData.target }
|
||||||
|
: {
|
||||||
|
target: {
|
||||||
|
entity: { domain: computeDomain(this.value!.service) },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
@value-changed=${this._targetChanged}
|
||||||
|
.value=${this.value?.target}
|
||||||
|
></ha-selector
|
||||||
|
></ha-settings-row>`
|
||||||
: entityId
|
: entityId
|
||||||
? html`<ha-entity-picker
|
? html`<ha-entity-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -156,38 +212,76 @@ export class HaServiceControl extends LitElement {
|
|||||||
${legacy
|
${legacy
|
||||||
? html`<ha-yaml-editor
|
? html`<ha-yaml-editor
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
"ui.components.service-control.service_data"
|
||||||
)}
|
)}
|
||||||
.name=${"data"}
|
.name=${"data"}
|
||||||
.defaultValue=${this.value?.data}
|
.defaultValue=${this.value?.data}
|
||||||
@value-changed=${this._dataChanged}
|
@value-changed=${this._dataChanged}
|
||||||
></ha-yaml-editor>`
|
></ha-yaml-editor>`
|
||||||
: this._serviceData?.fields.map((dataField) =>
|
: this._serviceData?.fields.map((dataField) =>
|
||||||
dataField.selector
|
dataField.selector && (!dataField.advanced || this.showAdvanced)
|
||||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
|
${dataField.required
|
||||||
|
? hasOptional
|
||||||
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
|
: ""
|
||||||
|
: html`<ha-checkbox
|
||||||
|
.key=${dataField.key}
|
||||||
|
.checked=${this._checkedKeys.has(dataField.key) ||
|
||||||
|
(this.value?.data &&
|
||||||
|
this.value.data[dataField.key] !== undefined)}
|
||||||
|
@change=${this._checkboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`}
|
||||||
<span slot="heading">${dataField.name || dataField.key}</span>
|
<span slot="heading">${dataField.name || dataField.key}</span>
|
||||||
<span slot="description">${dataField?.description}</span
|
<span slot="description">${dataField?.description}</span
|
||||||
><ha-selector
|
><ha-selector
|
||||||
|
.disabled=${!dataField.required &&
|
||||||
|
!this._checkedKeys.has(dataField.key) &&
|
||||||
|
(!this.value?.data ||
|
||||||
|
this.value.data[dataField.key] === undefined)}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${dataField.selector}
|
.selector=${dataField.selector}
|
||||||
.key=${dataField.key}
|
.key=${dataField.key}
|
||||||
@value-changed=${this._serviceDataChanged}
|
@value-changed=${this._serviceDataChanged}
|
||||||
.value=${(this.value?.data &&
|
.value=${this.value?.data &&
|
||||||
this.value.data[dataField.key]) ||
|
this.value.data[dataField.key] !== undefined
|
||||||
dataField.default}
|
? this.value.data[dataField.key]
|
||||||
|
: dataField.default}
|
||||||
></ha-selector
|
></ha-selector
|
||||||
></ha-settings-row>`
|
></ha-settings-row>`
|
||||||
: ""
|
: ""
|
||||||
)} `;
|
)} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _checkboxChanged(ev) {
|
||||||
|
const checked = ev.currentTarget.checked;
|
||||||
|
const key = ev.currentTarget.key;
|
||||||
|
if (checked) {
|
||||||
|
this._checkedKeys.add(key);
|
||||||
|
} else {
|
||||||
|
this._checkedKeys.delete(key);
|
||||||
|
const data = { ...this.value?.data };
|
||||||
|
|
||||||
|
delete data[key];
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.value,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.requestUpdate("_checkedKeys");
|
||||||
|
}
|
||||||
|
|
||||||
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (ev.detail.value === this.value?.service) {
|
if (ev.detail.value === this.value?.service) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { service: ev.detail.value || "", data: {} },
|
value: { service: ev.detail.value || "" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,10 +362,27 @@ export class HaServiceControl extends LitElement {
|
|||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0;
|
padding: var(--service-control-padding, 0 16px);
|
||||||
}
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
--paper-time-input-justify-content: flex-end;
|
--paper-time-input-justify-content: flex-end;
|
||||||
|
border-top: var(
|
||||||
|
--service-control-items-border-top,
|
||||||
|
1px solid var(--divider-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ha-service-picker,
|
||||||
|
ha-entity-picker,
|
||||||
|
ha-yaml-editor {
|
||||||
|
display: block;
|
||||||
|
margin: var(--service-control-padding, 0 16px);
|
||||||
|
}
|
||||||
|
ha-yaml-editor {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: var(--service-control-padding, 0 16px);
|
||||||
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
:host(:not([narrow])) ha-settings-row paper-input {
|
:host(:not([narrow])) ha-settings-row paper-input {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
@ -279,6 +390,12 @@ export class HaServiceControl extends LitElement {
|
|||||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
.checkbox-spacer {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
ha-checkbox {
|
||||||
|
margin-left: -16px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { html, internalProperty, LitElement, property } from "lit-element";
|
import { html, internalProperty, LitElement, property } from "lit-element";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
|
import { domainToName } from "../data/integration";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
|
|
||||||
const rowRenderer = (
|
const rowRenderer = (
|
||||||
root: HTMLElement,
|
root: HTMLElement,
|
||||||
_owner,
|
_owner,
|
||||||
model: { item: { service: string; description: string } }
|
model: { item: { service: string; name: string } }
|
||||||
) => {
|
) => {
|
||||||
if (!root.firstElementChild) {
|
if (!root.firstElementChild) {
|
||||||
root.innerHTML = `
|
root.innerHTML = `
|
||||||
@ -19,15 +21,16 @@ const rowRenderer = (
|
|||||||
</style>
|
</style>
|
||||||
<paper-item>
|
<paper-item>
|
||||||
<paper-item-body two-line="">
|
<paper-item-body two-line="">
|
||||||
<div class='name'>[[item.description]]</div>
|
<div class='name'>[[item.name]]</div>
|
||||||
<div secondary>[[item.service]]</div>
|
<div secondary>[[item.service]]</div>
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
</paper-item>
|
</paper-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.querySelector(".name")!.textContent = model.item.description;
|
root.querySelector(".name")!.textContent = model.item.name;
|
||||||
root.querySelector("[secondary]")!.textContent = model.item.service;
|
root.querySelector("[secondary]")!.textContent =
|
||||||
|
model.item.name === model.item.service ? "" : model.item.service;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HaServicePicker extends LitElement {
|
class HaServicePicker extends LitElement {
|
||||||
@ -43,13 +46,14 @@ class HaServicePicker extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize("ui.components.service-picker.service")}
|
.label=${this.hass.localize("ui.components.service-picker.service")}
|
||||||
.filteredItems=${this._filteredServices(
|
.filteredItems=${this._filteredServices(
|
||||||
|
this.hass.localize,
|
||||||
this.hass.services,
|
this.hass.services,
|
||||||
this._filter
|
this._filter
|
||||||
)}
|
)}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
item-value-path="service"
|
item-value-path="service"
|
||||||
item-label-path="description"
|
item-label-path="name"
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@ -57,38 +61,48 @@ class HaServicePicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _services = memoizeOne((services: HomeAssistant["services"]): {
|
private _services = memoizeOne(
|
||||||
service: string;
|
(
|
||||||
description: string;
|
localize: LocalizeFunc,
|
||||||
}[] => {
|
services: HomeAssistant["services"]
|
||||||
if (!services) {
|
): {
|
||||||
return [];
|
service: string;
|
||||||
}
|
name: string;
|
||||||
const result: { service: string; description: string }[] = [];
|
}[] => {
|
||||||
|
|
||||||
Object.keys(services)
|
|
||||||
.sort()
|
|
||||||
.forEach((domain) => {
|
|
||||||
const services_keys = Object.keys(services[domain]).sort();
|
|
||||||
|
|
||||||
for (const service of services_keys) {
|
|
||||||
result.push({
|
|
||||||
service: `${domain}.${service}`,
|
|
||||||
description:
|
|
||||||
services[domain][service].description || `${domain}.${service}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _filteredServices = memoizeOne(
|
|
||||||
(services: HomeAssistant["services"], filter?: string) => {
|
|
||||||
if (!services) {
|
if (!services) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const processedServices = this._services(services);
|
const result: { service: string; name: string }[] = [];
|
||||||
|
|
||||||
|
Object.keys(services)
|
||||||
|
.sort()
|
||||||
|
.forEach((domain) => {
|
||||||
|
const services_keys = Object.keys(services[domain]).sort();
|
||||||
|
|
||||||
|
for (const service of services_keys) {
|
||||||
|
result.push({
|
||||||
|
service: `${domain}.${service}`,
|
||||||
|
name: `${domainToName(localize, domain)}: ${
|
||||||
|
services[domain][service].name || service
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _filteredServices = memoizeOne(
|
||||||
|
(
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
services: HomeAssistant["services"],
|
||||||
|
filter?: string
|
||||||
|
) => {
|
||||||
|
if (!services) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const processedServices = this._services(localize, services);
|
||||||
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return processedServices;
|
return processedServices;
|
||||||
@ -96,7 +110,7 @@ class HaServicePicker extends LitElement {
|
|||||||
return processedServices.filter(
|
return processedServices.filter(
|
||||||
(service) =>
|
(service) =>
|
||||||
service.service.toLowerCase().includes(filter) ||
|
service.service.toLowerCase().includes(filter) ||
|
||||||
service.description.toLowerCase().includes(filter)
|
service.name?.toLowerCase().includes(filter)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
SVGTemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
@customElement("ha-settings-row")
|
@customElement("ha-settings-row")
|
||||||
@ -16,15 +16,18 @@ export class HaSettingsRow extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "three-line" })
|
@property({ type: Boolean, attribute: "three-line" })
|
||||||
public threeLine = false;
|
public threeLine = false;
|
||||||
|
|
||||||
protected render(): SVGTemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-item-body
|
<div class="prefix-wrap">
|
||||||
?two-line=${!this.threeLine}
|
<slot name="prefix"></slot>
|
||||||
?three-line=${this.threeLine}
|
<paper-item-body
|
||||||
>
|
?two-line=${!this.threeLine}
|
||||||
<slot name="heading"></slot>
|
?three-line=${this.threeLine}
|
||||||
<div secondary><slot name="description"></slot></div>
|
>
|
||||||
</paper-item-body>
|
<slot name="heading"></slot>
|
||||||
|
<div secondary><slot name="description"></slot></div>
|
||||||
|
</paper-item-body>
|
||||||
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -59,6 +62,13 @@ export class HaSettingsRow extends LitElement {
|
|||||||
div[secondary] {
|
div[secondary] {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
.prefix-wrap {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
:host([narrow]) .prefix-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
@internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
||||||
|
|
||||||
@internalProperty() private _devices?: {
|
@internalProperty() private _devices?: {
|
||||||
@ -438,7 +440,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
type: string,
|
type: string,
|
||||||
id: string
|
id: string
|
||||||
): this["value"] {
|
): this["value"] {
|
||||||
const newVal = ensureArray(value![type])!.filter((val) => val !== id);
|
const newVal = ensureArray(value![type])!.filter(
|
||||||
|
(val) => String(val) !== id
|
||||||
|
);
|
||||||
if (newVal.length) {
|
if (newVal.length) {
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
@ -599,6 +603,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
paper-tooltip.expand {
|
paper-tooltip.expand {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) .mdc-chip {
|
||||||
|
opacity: var(--light-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,14 +44,14 @@ export class HaYamlEditor extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _yaml = "";
|
@internalProperty() private _yaml = "";
|
||||||
|
|
||||||
@query("ha-code-editor", true) private _editor?: HaCodeEditor;
|
@query("ha-code-editor") private _editor?: HaCodeEditor;
|
||||||
|
|
||||||
public setValue(value): void {
|
public setValue(value): void {
|
||||||
try {
|
try {
|
||||||
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err, value);
|
||||||
alert(`There was an error converting to YAML: ${err}`);
|
alert(`There was an error converting to YAML: ${err}`);
|
||||||
}
|
}
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
@ -73,7 +73,7 @@ export class HaYamlEditor extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
${this.label ? html` <p>${this.label}</p> ` : ""}
|
${this.label ? html`<p>${this.label}</p>` : ""}
|
||||||
<ha-code-editor
|
<ha-code-editor
|
||||||
.value=${this._yaml}
|
.value=${this._yaml}
|
||||||
mode="yaml"
|
mode="yaml"
|
||||||
@ -85,13 +85,13 @@ export class HaYamlEditor extends LitElement {
|
|||||||
|
|
||||||
private _onChange(ev: CustomEvent): void {
|
private _onChange(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value = ev.detail.value;
|
this._yaml = ev.detail.value;
|
||||||
let parsed;
|
let parsed;
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
if (value) {
|
if (this._yaml) {
|
||||||
try {
|
try {
|
||||||
parsed = safeLoad(value);
|
parsed = safeLoad(this._yaml);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Invalid YAML
|
// Invalid YAML
|
||||||
isValid = false;
|
isValid = false;
|
||||||
@ -107,7 +107,7 @@ export class HaYamlEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get yaml() {
|
get yaml() {
|
||||||
return this._editor?.value;
|
return this._yaml;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
internalProperty,
|
internalProperty,
|
||||||
LitElement,
|
LitElement,
|
||||||
@ -62,6 +64,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._action}
|
.value=${this._action}
|
||||||
|
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
></ha-service-control>
|
></ha-service-control>
|
||||||
`;
|
`;
|
||||||
@ -72,6 +75,15 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-service-control {
|
||||||
|
display: block;
|
||||||
|
margin: 0 -16px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,371 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { safeDump, safeLoad } from "js-yaml";
|
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import "../../../components/buttons/ha-progress-button";
|
|
||||||
import "../../../components/entity/ha-entity-picker";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-code-editor";
|
|
||||||
import "../../../components/ha-service-picker";
|
|
||||||
import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity";
|
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
import "../../../styles/polymer-ha-style";
|
|
||||||
import "../../../util/app-localstorage-document";
|
|
||||||
|
|
||||||
const ERROR_SENTINEL = {};
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style">
|
|
||||||
:host {
|
|
||||||
-ms-user-select: initial;
|
|
||||||
-webkit-user-select: initial;
|
|
||||||
-moz-user-select: initial;
|
|
||||||
display: block;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ha-form {
|
|
||||||
margin-right: 16px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-progress-button {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-card {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 12px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes th {
|
|
||||||
text-align: left;
|
|
||||||
background-color: var(--card-background-color);
|
|
||||||
border-bottom: 1px solid var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .attributes th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes tr {
|
|
||||||
vertical-align: top;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes tr:nth-child(odd) {
|
|
||||||
background-color: var(--table-row-background-color, #eee);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes tr:nth-child(even) {
|
|
||||||
background-color: var(--table-row-alternative-background-color, #eee);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes td:nth-child(3) {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--code-font-family, monospace);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .desc-container {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .desc-container h3 {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<app-localstorage-document
|
|
||||||
key="panel-dev-service-state-domain-service"
|
|
||||||
data="{{domainService}}"
|
|
||||||
>
|
|
||||||
</app-localstorage-document>
|
|
||||||
<app-localstorage-document
|
|
||||||
key="[[_computeServiceDataKey(domainService)]]"
|
|
||||||
data="{{serviceData}}"
|
|
||||||
>
|
|
||||||
</app-localstorage-document>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.description')]]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="ha-form">
|
|
||||||
<ha-service-picker
|
|
||||||
hass="[[hass]]"
|
|
||||||
value="{{domainService}}"
|
|
||||||
></ha-service-picker>
|
|
||||||
<template is="dom-if" if="[[_computeHasEntity(_attributes)]]">
|
|
||||||
<ha-entity-picker
|
|
||||||
hass="[[hass]]"
|
|
||||||
value="[[_computeEntityValue(parsedJSON)]]"
|
|
||||||
on-change="_entityPicked"
|
|
||||||
disabled="[[!validJSON]]"
|
|
||||||
include-domains="[[_computeEntityDomainFilter(_domain)]]"
|
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>
|
|
||||||
</template>
|
|
||||||
<p>[[localize('ui.panel.developer-tools.tabs.services.data')]]</p>
|
|
||||||
<ha-code-editor
|
|
||||||
mode="yaml"
|
|
||||||
value="[[serviceData]]"
|
|
||||||
error="[[!validJSON]]"
|
|
||||||
on-value-changed="_yamlChanged"
|
|
||||||
></ha-code-editor>
|
|
||||||
<ha-progress-button
|
|
||||||
on-click="_callService"
|
|
||||||
raised
|
|
||||||
disabled="[[!validJSON]]"
|
|
||||||
>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.call_service')]]
|
|
||||||
</ha-progress-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-header">
|
|
||||||
<template is="dom-if" if="[[!domainService]]">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.select_service')]]
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[domainService]]">
|
|
||||||
<template is="dom-if" if="[[!_description]]">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.no_description')]]
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_description]]">
|
|
||||||
[[_description]]
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<template is="dom-if" if="[[_description]]">
|
|
||||||
<template is="dom-if" if="[[!_attributes.length]]">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.no_parameters')]]
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_attributes.length]]">
|
|
||||||
<table class="attributes">
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.column_parameter')]]
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.column_description')]]
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.column_example')]]
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<template is="dom-repeat" items="[[_attributes]]" as="attribute">
|
|
||||||
<tr>
|
|
||||||
<td><pre>[[attribute.key]]</pre></td>
|
|
||||||
<td>[[attribute.description]]</td>
|
|
||||||
<td>[[attribute.example]]</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_attributes.length]]">
|
|
||||||
<mwc-button on-click="_fillExampleData">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.services.fill_example_data')]]
|
|
||||||
</mwc-button>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
domainService: {
|
|
||||||
type: String,
|
|
||||||
observer: "_domainServiceChanged",
|
|
||||||
},
|
|
||||||
|
|
||||||
_domain: {
|
|
||||||
type: String,
|
|
||||||
computed: "_computeDomain(domainService)",
|
|
||||||
},
|
|
||||||
|
|
||||||
_service: {
|
|
||||||
type: String,
|
|
||||||
computed: "_computeService(domainService)",
|
|
||||||
},
|
|
||||||
|
|
||||||
serviceData: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
parsedJSON: {
|
|
||||||
type: Object,
|
|
||||||
computed: "_computeParsedServiceData(serviceData)",
|
|
||||||
},
|
|
||||||
|
|
||||||
validJSON: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "_computeValidJSON(parsedJSON)",
|
|
||||||
},
|
|
||||||
|
|
||||||
_attributes: {
|
|
||||||
type: Array,
|
|
||||||
computed: "_computeAttributesArray(hass, _domain, _service)",
|
|
||||||
},
|
|
||||||
|
|
||||||
_description: {
|
|
||||||
type: String,
|
|
||||||
computed: "_computeDescription(hass, _domain, _service)",
|
|
||||||
},
|
|
||||||
|
|
||||||
rtl: {
|
|
||||||
reflectToAttribute: true,
|
|
||||||
computed: "_computeRTL(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_domainServiceChanged() {
|
|
||||||
this.serviceData = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeAttributesArray(hass, domain, service) {
|
|
||||||
const serviceDomains = hass.services;
|
|
||||||
if (!(domain in serviceDomains)) return [];
|
|
||||||
if (!(service in serviceDomains[domain])) return [];
|
|
||||||
|
|
||||||
const fields = serviceDomains[domain][service].fields;
|
|
||||||
return Object.keys(fields).map(function (field) {
|
|
||||||
return { key: field, ...fields[field] };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDescription(hass, domain, service) {
|
|
||||||
const serviceDomains = hass.services;
|
|
||||||
if (!(domain in serviceDomains)) return undefined;
|
|
||||||
if (!(service in serviceDomains[domain])) return undefined;
|
|
||||||
return serviceDomains[domain][service].description;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeServiceDataKey(domainService) {
|
|
||||||
return `panel-dev-service-state-servicedata.${domainService}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDomain(domainService) {
|
|
||||||
return domainService.split(".", 1)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeService(domainService) {
|
|
||||||
return domainService.split(".", 2)[1] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeParsedServiceData(serviceData) {
|
|
||||||
try {
|
|
||||||
return serviceData.trim() ? safeLoad(serviceData) : {};
|
|
||||||
} catch (err) {
|
|
||||||
return ERROR_SENTINEL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeValidJSON(parsedJSON) {
|
|
||||||
return parsedJSON !== ERROR_SENTINEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeHasEntity(attributes) {
|
|
||||||
return attributes.some((attr) => attr.key === "entity_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeEntityValue(parsedJSON) {
|
|
||||||
return parsedJSON === ERROR_SENTINEL ? "" : parsedJSON.entity_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeEntityDomainFilter(domain) {
|
|
||||||
return ENTITY_COMPONENT_DOMAINS.includes(domain) ? [domain] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_callService(ev) {
|
|
||||||
const button = ev.target;
|
|
||||||
if (this.parsedJSON === ERROR_SENTINEL) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.services.alert_parsing_yaml",
|
|
||||||
"data",
|
|
||||||
this.serviceData
|
|
||||||
),
|
|
||||||
});
|
|
||||||
button.actionError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hass
|
|
||||||
.callService(this._domain, this._service, this.parsedJSON)
|
|
||||||
.then(() => {
|
|
||||||
button.actionSuccess();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
button.actionError();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_fillExampleData() {
|
|
||||||
const example = {};
|
|
||||||
this._attributes.forEach((attribute) => {
|
|
||||||
if (attribute.example) {
|
|
||||||
let value = "";
|
|
||||||
try {
|
|
||||||
value = safeLoad(attribute.example);
|
|
||||||
} catch (err) {
|
|
||||||
value = attribute.example;
|
|
||||||
}
|
|
||||||
example[attribute.key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.serviceData = safeDump(example);
|
|
||||||
}
|
|
||||||
|
|
||||||
_entityPicked(ev) {
|
|
||||||
this.serviceData = safeDump({
|
|
||||||
...this.parsedJSON,
|
|
||||||
entity_id: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_yamlChanged(ev) {
|
|
||||||
this.serviceData = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTL(hass) {
|
|
||||||
return computeRTL(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("developer-tools-service", HaPanelDevService);
|
|
350
src/panels/developer-tools/service/developer-tools-service.ts
Normal file
350
src/panels/developer-tools/service/developer-tools-service.ts
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
import { safeLoad } from "js-yaml";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultArray,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
query,
|
||||||
|
} from "lit-element";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { LocalStorage } from "../../../common/decorators/local-storage";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
|
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||||
|
import "../../../components/buttons/ha-progress-button";
|
||||||
|
import "../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-expansion-panel";
|
||||||
|
import "../../../components/ha-service-control";
|
||||||
|
import "../../../components/ha-service-picker";
|
||||||
|
import "../../../components/ha-yaml-editor";
|
||||||
|
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||||
|
import { ServiceAction } from "../../../data/script";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import "../../../styles/polymer-ha-style";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "../../../util/app-localstorage-document";
|
||||||
|
|
||||||
|
class HaPanelDevService extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
|
||||||
|
@LocalStorage("panel-dev-service-state-service-data", true)
|
||||||
|
private _serviceData?: ServiceAction = { service: "", target: {}, data: {} };
|
||||||
|
|
||||||
|
@LocalStorage("panel-dev-service-state-yaml-mode", true)
|
||||||
|
private _yamlMode = false;
|
||||||
|
|
||||||
|
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
|
protected firstUpdated(params) {
|
||||||
|
super.firstUpdated(params);
|
||||||
|
if (!this._serviceData?.service) {
|
||||||
|
const domain = Object.keys(this.hass.services).sort()[0];
|
||||||
|
const service = Object.keys(this.hass.services[domain]).sort()[0];
|
||||||
|
this._serviceData = {
|
||||||
|
service: `${domain}.${service}`,
|
||||||
|
target: {},
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { target, fields } = this._fields(
|
||||||
|
this.hass.services,
|
||||||
|
this._serviceData?.service
|
||||||
|
);
|
||||||
|
|
||||||
|
const isValid = this._isValid(this._serviceData, fields, target);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.description"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
${this._yamlMode
|
||||||
|
? html`<ha-yaml-editor
|
||||||
|
.defaultValue=${this._serviceData}
|
||||||
|
@value-changed=${this._yamlChanged}
|
||||||
|
></ha-yaml-editor>`
|
||||||
|
: html`<ha-card
|
||||||
|
><div>
|
||||||
|
<ha-service-control
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._serviceData}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
showAdvanced
|
||||||
|
@value-changed=${this._serviceChanged}
|
||||||
|
></ha-service-control></div
|
||||||
|
></ha-card>`}
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
|
<div class="buttons">
|
||||||
|
<mwc-button @click=${this._toggleYaml}>
|
||||||
|
${this._yamlMode
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.ui_mode"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.yaml_mode"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button .disabled=${!isValid} raised @click=${this._callService}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.call_service"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${(this._yamlMode ? fields : this._filterSelectorFields(fields)).length
|
||||||
|
? html`<div class="content">
|
||||||
|
<ha-expansion-panel
|
||||||
|
.header=${this._yamlMode
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.all_parameters"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.yaml_parameters"
|
||||||
|
)}
|
||||||
|
outlined
|
||||||
|
.expanded=${this._yamlMode}
|
||||||
|
>
|
||||||
|
${this._yamlMode && target
|
||||||
|
? html`<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.accepts_target"
|
||||||
|
)}
|
||||||
|
</h3>`
|
||||||
|
: ""}
|
||||||
|
<table class="attributes">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.column_parameter"
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.column_description"
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.column_example"
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
${fields.map(
|
||||||
|
(field) => html` <tr>
|
||||||
|
<td><pre>${field.key}</pre></td>
|
||||||
|
<td>${field.description}</td>
|
||||||
|
<td>${field.example}</td>
|
||||||
|
</tr>`
|
||||||
|
)}
|
||||||
|
</table>
|
||||||
|
${this._yamlMode
|
||||||
|
? html`<mwc-button @click=${this._fillExampleData}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.fill_example_data"
|
||||||
|
)}</mwc-button
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
</ha-expansion-panel>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterSelectorFields = memoizeOne((fields) =>
|
||||||
|
fields.filter((field) => !field.selector)
|
||||||
|
);
|
||||||
|
|
||||||
|
private _isValid = memoizeOne((serviceData, fields, target): boolean => {
|
||||||
|
if (!serviceData?.service) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const domain = computeDomain(serviceData.service);
|
||||||
|
const service = computeObjectId(serviceData.service);
|
||||||
|
if (!domain || !service) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
target &&
|
||||||
|
!serviceData.target &&
|
||||||
|
!serviceData.data?.entity_id &&
|
||||||
|
!serviceData.data?.device_id &&
|
||||||
|
!serviceData.data?.area_id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const field of fields) {
|
||||||
|
if (
|
||||||
|
field.required &&
|
||||||
|
(!serviceData.data || serviceData.data[field.key] === undefined)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
private _fields = memoizeOne(
|
||||||
|
(
|
||||||
|
serviceDomains: HomeAssistant["services"],
|
||||||
|
domainService: string | undefined
|
||||||
|
): { target: boolean; fields: any[] } => {
|
||||||
|
if (!domainService) {
|
||||||
|
return { target: false, fields: [] };
|
||||||
|
}
|
||||||
|
const domain = computeDomain(domainService);
|
||||||
|
const service = computeObjectId(domainService);
|
||||||
|
if (!(domain in serviceDomains)) {
|
||||||
|
return { target: false, fields: [] };
|
||||||
|
}
|
||||||
|
if (!(service in serviceDomains[domain])) {
|
||||||
|
return { target: false, fields: [] };
|
||||||
|
}
|
||||||
|
const target = "target" in serviceDomains[domain][service];
|
||||||
|
const fields = serviceDomains[domain][service].fields;
|
||||||
|
const result = Object.keys(fields).map((field) => {
|
||||||
|
return { key: field, ...fields[field] };
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
target,
|
||||||
|
fields: result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _callService() {
|
||||||
|
const domain = computeDomain(this._serviceData!.service);
|
||||||
|
const service = computeObjectId(this._serviceData!.service);
|
||||||
|
if (!domain || !service) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hass.callService(
|
||||||
|
domain,
|
||||||
|
service,
|
||||||
|
this._serviceData!.data,
|
||||||
|
this._serviceData!.target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleYaml() {
|
||||||
|
this._yamlMode = !this._yamlMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _yamlChanged(ev) {
|
||||||
|
if (!ev.detail.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._serviceChanged(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serviceChanged(ev) {
|
||||||
|
this._serviceData = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fillExampleData() {
|
||||||
|
const { fields } = this._fields(
|
||||||
|
this.hass.services,
|
||||||
|
this._serviceData?.service
|
||||||
|
);
|
||||||
|
const example = {};
|
||||||
|
fields.forEach((field) => {
|
||||||
|
if (field.example) {
|
||||||
|
let value = "";
|
||||||
|
try {
|
||||||
|
value = safeLoad(field.example);
|
||||||
|
} catch (err) {
|
||||||
|
value = field.example;
|
||||||
|
}
|
||||||
|
example[field.key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._serviceData = { ...this._serviceData!, data: example };
|
||||||
|
this._yamlEditor?.setValue(this._serviceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding: 16px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.button-row {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
background: var(--card-background-color);
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row .buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes th {
|
||||||
|
text-align: left;
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
border-bottom: 1px solid var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) .attributes th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes tr {
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes tr:nth-child(odd) {
|
||||||
|
background-color: var(--table-row-background-color, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes tr:nth-child(even) {
|
||||||
|
background-color: var(--table-row-alternative-background-color, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes td:nth-child(3) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes td {
|
||||||
|
padding: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("developer-tools-service", HaPanelDevService);
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"developer-tools-service": HaPanelDevService;
|
||||||
|
}
|
||||||
|
}
|
@ -194,6 +194,9 @@ export class HuiActionEditor extends LitElement {
|
|||||||
.dropdown {
|
.dropdown {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
ha-service-control {
|
||||||
|
--service-control-padding: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,11 @@ export const configElementStyle = css`
|
|||||||
}
|
}
|
||||||
.side-by-side > * {
|
.side-by-side > * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-right: 4px;
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
.side-by-side > *:last-child {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
.suffix {
|
.suffix {
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
|
@ -424,6 +424,12 @@
|
|||||||
"service-picker": {
|
"service-picker": {
|
||||||
"service": "Service"
|
"service": "Service"
|
||||||
},
|
},
|
||||||
|
"service-control": {
|
||||||
|
"required": "This field is required",
|
||||||
|
"target": "Target",
|
||||||
|
"target_description": "What should this service call target",
|
||||||
|
"service_data": "Service data"
|
||||||
|
},
|
||||||
"related-items": {
|
"related-items": {
|
||||||
"no_related_found": "No related items found.",
|
"no_related_found": "No related items found.",
|
||||||
"integration": "Integration",
|
"integration": "Integration",
|
||||||
@ -1401,8 +1407,7 @@
|
|||||||
"type_select": "Action type",
|
"type_select": "Action type",
|
||||||
"type": {
|
"type": {
|
||||||
"service": {
|
"service": {
|
||||||
"label": "Call service",
|
"label": "Call service"
|
||||||
"service_data": "Service data"
|
|
||||||
},
|
},
|
||||||
"delay": {
|
"delay": {
|
||||||
"label": "Delay",
|
"label": "Delay",
|
||||||
@ -1425,7 +1430,7 @@
|
|||||||
"event": {
|
"event": {
|
||||||
"label": "Fire event",
|
"label": "Fire event",
|
||||||
"event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]",
|
"event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]",
|
||||||
"service_data": "[%key:ui::panel::config::automation::editor::actions::type::service::service_data%]"
|
"service_data": "[%key:ui::components::service-control::service_data%]"
|
||||||
},
|
},
|
||||||
"device_id": {
|
"device_id": {
|
||||||
"label": "Device",
|
"label": "Device",
|
||||||
@ -2694,7 +2699,6 @@
|
|||||||
"action-editor": {
|
"action-editor": {
|
||||||
"navigation_path": "Navigation Path",
|
"navigation_path": "Navigation Path",
|
||||||
"url_path": "URL Path",
|
"url_path": "URL Path",
|
||||||
"editor_service_data": "Service data can only be entered in the code editor",
|
|
||||||
"actions": {
|
"actions": {
|
||||||
"default_action": "Default Action",
|
"default_action": "Default Action",
|
||||||
"call-service": "Call Service",
|
"call-service": "Call Service",
|
||||||
@ -3273,16 +3277,16 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"title": "Services",
|
"title": "Services",
|
||||||
"description": "The service dev tool allows you to call any available service in Home Assistant.",
|
"description": "The service dev tool allows you to call any available service in Home Assistant.",
|
||||||
"data": "Service Data (YAML, optional)",
|
|
||||||
"call_service": "Call Service",
|
"call_service": "Call Service",
|
||||||
"select_service": "Select a service to see the description",
|
|
||||||
"no_description": "No description is available",
|
|
||||||
"no_parameters": "This service takes no parameters.",
|
|
||||||
"column_parameter": "Parameter",
|
"column_parameter": "Parameter",
|
||||||
"column_description": "Description",
|
"column_description": "Description",
|
||||||
"column_example": "Example",
|
"column_example": "Example",
|
||||||
"fill_example_data": "Fill Example Data",
|
"fill_example_data": "Fill Example Data",
|
||||||
"alert_parsing_yaml": "Error parsing YAML: {data}"
|
"yaml_mode": "Go to YAML mode",
|
||||||
|
"ui_mode": "Go to UI mode",
|
||||||
|
"yaml_parameters": "Parameters only available in YAML mode",
|
||||||
|
"all_parameters": "All available parameters",
|
||||||
|
"accepts_target": "This service accepts a target, for example: `entity_id: light.bed_light`"
|
||||||
},
|
},
|
||||||
"states": {
|
"states": {
|
||||||
"title": "States",
|
"title": "States",
|
||||||
|
@ -8174,10 +8174,10 @@ hmac-drbg@^1.0.0:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
home-assistant-js-websocket@^5.8.1:
|
home-assistant-js-websocket@^5.9.0:
|
||||||
version "5.8.1"
|
version "5.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-5.8.1.tgz#4c5930aa47e7089f5806bb3d190ebe53697d2edc"
|
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-5.9.0.tgz#85f73cc7aa23362e93d7e8208026fbcf25934022"
|
||||||
integrity sha512-2H3q8NK3WrT50iYODv95iz0E2E+nAUOD452V6lhBxhUTQlVFBsuxNMRTTbIZp+6Xab7ad84uF0z+hHFmBMq/Sw==
|
integrity sha512-HSAhX+s2JgsE77sYKKqcNsukiO6Zm4CcCIwugq17MwHcEyLoecChsbQtgtbvg1dHctUAk+IHxuZ0JBx10B1YGQ==
|
||||||
|
|
||||||
homedir-polyfill@^1.0.1:
|
homedir-polyfill@^1.0.1:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user