832 lines
24 KiB
TypeScript
832 lines
24 KiB
TypeScript
import { mdiHelpCircle } from "@mdi/js";
|
|
import {
|
|
HassService,
|
|
HassServices,
|
|
HassServiceTarget,
|
|
} from "home-assistant-js-websocket";
|
|
import {
|
|
css,
|
|
CSSResultGroup,
|
|
html,
|
|
LitElement,
|
|
nothing,
|
|
PropertyValues,
|
|
} from "lit";
|
|
import { customElement, property, query, state } from "lit/decorators";
|
|
import memoizeOne from "memoize-one";
|
|
import { ensureArray } from "../common/array/ensure-array";
|
|
import { fireEvent } from "../common/dom/fire_event";
|
|
import { computeDomain } from "../common/entity/compute_domain";
|
|
import { computeObjectId } from "../common/entity/compute_object_id";
|
|
import { supportsFeature } from "../common/entity/supports-feature";
|
|
import { nestedArrayMove } from "../common/util/array-move";
|
|
import {
|
|
fetchIntegrationManifest,
|
|
IntegrationManifest,
|
|
} from "../data/integration";
|
|
import {
|
|
areaMeetsTargetSelector,
|
|
deviceMeetsTargetSelector,
|
|
entityMeetsTargetSelector,
|
|
expandAreaTarget,
|
|
expandDeviceTarget,
|
|
expandFloorTarget,
|
|
Selector,
|
|
} from "../data/selector";
|
|
import { HomeAssistant, ValueChangedEvent } from "../types";
|
|
import { documentationUrl } from "../util/documentation-url";
|
|
import "./ha-checkbox";
|
|
import "./ha-icon-button";
|
|
import "./ha-selector/ha-selector";
|
|
import "./ha-service-picker";
|
|
import "./ha-settings-row";
|
|
import "./ha-yaml-editor";
|
|
import type { HaYamlEditor } from "./ha-yaml-editor";
|
|
|
|
const attributeFilter = (values: any[], attribute: any) => {
|
|
if (typeof attribute === "object") {
|
|
if (Array.isArray(attribute)) {
|
|
return attribute.some((item) => values.includes(item));
|
|
}
|
|
return false;
|
|
}
|
|
return values.includes(attribute);
|
|
};
|
|
|
|
const showOptionalToggle = (field) =>
|
|
field.selector &&
|
|
!field.required &&
|
|
!("boolean" in field.selector && field.default);
|
|
|
|
interface ExtHassService extends Omit<HassService, "fields"> {
|
|
fields: Array<
|
|
Omit<HassService["fields"][string], "selector"> & {
|
|
key: string;
|
|
selector?: Selector;
|
|
}
|
|
>;
|
|
hasSelector: string[];
|
|
}
|
|
|
|
@customElement("ha-service-control")
|
|
export class HaServiceControl extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public value?: {
|
|
service: string;
|
|
target?: HassServiceTarget;
|
|
data?: Record<string, any>;
|
|
};
|
|
|
|
@property({ type: Boolean }) public disabled = false;
|
|
|
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
|
|
|
@property({ type: Boolean }) public showAdvanced = false;
|
|
|
|
@property({ type: Boolean, reflect: true }) public hidePicker = false;
|
|
|
|
@property({ type: Boolean }) public hideDescription = false;
|
|
|
|
@state() private _value!: this["value"];
|
|
|
|
@state() private _checkedKeys = new Set();
|
|
|
|
@state() private _manifest?: IntegrationManifest;
|
|
|
|
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
|
|
|
protected willUpdate(changedProperties: PropertyValues<this>) {
|
|
if (!this.hasUpdated) {
|
|
this.hass.loadBackendTranslation("services");
|
|
this.hass.loadBackendTranslation("selector");
|
|
}
|
|
if (!changedProperties.has("value")) {
|
|
return;
|
|
}
|
|
const oldValue = changedProperties.get("value") as
|
|
| undefined
|
|
| this["value"];
|
|
|
|
if (oldValue?.service !== this.value?.service) {
|
|
this._checkedKeys = new Set();
|
|
}
|
|
|
|
const serviceData = this._getServiceInfo(
|
|
this.value?.service,
|
|
this.hass.services
|
|
);
|
|
|
|
// Fetch the manifest if we have a service selected and the service domain changed.
|
|
// If no service is selected, clear the manifest.
|
|
if (this.value?.service) {
|
|
if (
|
|
!oldValue?.service ||
|
|
computeDomain(this.value.service) !== computeDomain(oldValue.service)
|
|
) {
|
|
this._fetchManifest(computeDomain(this.value?.service));
|
|
}
|
|
} else {
|
|
this._manifest = undefined;
|
|
}
|
|
|
|
if (
|
|
serviceData &&
|
|
"target" in serviceData &&
|
|
(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,
|
|
target,
|
|
data: { ...this.value.data },
|
|
};
|
|
|
|
delete this._value.data!.entity_id;
|
|
delete this._value.data!.device_id;
|
|
delete this._value.data!.area_id;
|
|
} else {
|
|
this._value = this.value;
|
|
}
|
|
|
|
if (oldValue?.service !== this.value?.service) {
|
|
let updatedDefaultValue = false;
|
|
if (this._value && serviceData) {
|
|
const loadDefaults = this.value && !("data" in this.value);
|
|
// Set mandatory bools without a default value to false
|
|
if (!this._value.data) {
|
|
this._value.data = {};
|
|
}
|
|
serviceData.fields.forEach((field) => {
|
|
if (
|
|
field.selector &&
|
|
field.required &&
|
|
field.default === undefined &&
|
|
"boolean" in field.selector &&
|
|
this._value!.data![field.key] === undefined
|
|
) {
|
|
updatedDefaultValue = true;
|
|
this._value!.data![field.key] = false;
|
|
}
|
|
if (
|
|
loadDefaults &&
|
|
field.selector &&
|
|
field.default !== undefined &&
|
|
this._value!.data![field.key] === undefined
|
|
) {
|
|
updatedDefaultValue = true;
|
|
this._value!.data![field.key] = field.default;
|
|
}
|
|
});
|
|
}
|
|
if (updatedDefaultValue) {
|
|
fireEvent(this, "value-changed", {
|
|
value: {
|
|
...this._value,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
if (this._value?.data) {
|
|
const yamlEditor = this._yamlEditor;
|
|
if (yamlEditor && yamlEditor.value !== this._value.data) {
|
|
yamlEditor.setValue(this._value.data);
|
|
}
|
|
}
|
|
}
|
|
|
|
private _getServiceInfo = memoizeOne(
|
|
(
|
|
service?: string,
|
|
serviceDomains?: HassServices
|
|
): ExtHassService | undefined => {
|
|
if (!service || !serviceDomains) {
|
|
return undefined;
|
|
}
|
|
const domain = computeDomain(service);
|
|
const serviceName = computeObjectId(service);
|
|
if (!(domain in serviceDomains)) {
|
|
return undefined;
|
|
}
|
|
if (!(serviceName in serviceDomains[domain])) {
|
|
return undefined;
|
|
}
|
|
|
|
const fields = Object.entries(
|
|
serviceDomains[domain][serviceName].fields
|
|
).map(([key, value]) => ({
|
|
key,
|
|
...value,
|
|
selector: value.selector as Selector | undefined,
|
|
}));
|
|
return {
|
|
...serviceDomains[domain][serviceName],
|
|
fields,
|
|
hasSelector: fields.length
|
|
? fields.filter((field) => field.selector).map((field) => field.key)
|
|
: [],
|
|
};
|
|
}
|
|
);
|
|
|
|
private _filterFields = memoizeOne(
|
|
(serviceData: ExtHassService | undefined, value: this["value"]) =>
|
|
serviceData?.fields?.filter(
|
|
(field) =>
|
|
!field.filter ||
|
|
this._filterField(serviceData.target, field.filter, value)
|
|
)
|
|
);
|
|
|
|
private _filterField(
|
|
target: ExtHassService["target"],
|
|
filter: ExtHassService["fields"][number]["filter"],
|
|
value: this["value"]
|
|
) {
|
|
const targetSelector = target ? { target } : { target: {} };
|
|
const targetEntities =
|
|
ensureArray(
|
|
value?.target?.entity_id || value?.data?.entity_id
|
|
)?.slice() || [];
|
|
const targetDevices =
|
|
ensureArray(
|
|
value?.target?.device_id || value?.data?.device_id
|
|
)?.slice() || [];
|
|
const targetAreas =
|
|
ensureArray(value?.target?.area_id || value?.data?.area_id)?.slice() ||
|
|
[];
|
|
const targetFloors = ensureArray(
|
|
value?.target?.floor_id || value?.data?.floor_id
|
|
)?.slice();
|
|
if (targetFloors) {
|
|
targetFloors.forEach((floorId) => {
|
|
const expanded = expandFloorTarget(
|
|
this.hass,
|
|
floorId,
|
|
this.hass.areas,
|
|
targetSelector
|
|
);
|
|
targetAreas.push(...expanded.areas);
|
|
});
|
|
}
|
|
if (targetAreas.length) {
|
|
targetAreas.forEach((areaId) => {
|
|
const expanded = expandAreaTarget(
|
|
this.hass,
|
|
areaId,
|
|
this.hass.devices,
|
|
this.hass.entities,
|
|
targetSelector
|
|
);
|
|
targetEntities.push(...expanded.entities);
|
|
targetDevices.push(...expanded.devices);
|
|
});
|
|
}
|
|
if (targetDevices.length) {
|
|
targetDevices.forEach((deviceId) => {
|
|
targetEntities.push(
|
|
...expandDeviceTarget(
|
|
this.hass,
|
|
deviceId,
|
|
this.hass.entities,
|
|
targetSelector
|
|
).entities
|
|
);
|
|
});
|
|
}
|
|
if (!targetEntities.length) {
|
|
return false;
|
|
}
|
|
if (
|
|
targetEntities.some((entityId) => {
|
|
const entityState = this.hass.states[entityId];
|
|
if (!entityState) {
|
|
return false;
|
|
}
|
|
if (
|
|
filter!.supported_features?.some((feature) =>
|
|
supportsFeature(entityState, feature)
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
if (
|
|
filter!.attribute &&
|
|
Object.entries(filter!.attribute).some(
|
|
([attribute, values]) =>
|
|
attribute in entityState.attributes &&
|
|
attributeFilter(values, entityState.attributes[attribute])
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
})
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected render() {
|
|
const serviceData = this._getServiceInfo(
|
|
this._value?.service,
|
|
this.hass.services
|
|
);
|
|
|
|
const shouldRenderServiceDataYaml =
|
|
(serviceData?.fields.length && !serviceData.hasSelector.length) ||
|
|
(serviceData &&
|
|
Object.keys(this._value?.data || {}).some(
|
|
(key) => !serviceData!.hasSelector.includes(key)
|
|
));
|
|
|
|
const entityId =
|
|
shouldRenderServiceDataYaml &&
|
|
serviceData?.fields.find((field) => field.key === "entity_id");
|
|
|
|
const hasOptional = Boolean(
|
|
!shouldRenderServiceDataYaml &&
|
|
serviceData?.fields.some((field) => showOptionalToggle(field))
|
|
);
|
|
|
|
const filteredFields = this._filterFields(serviceData, this._value);
|
|
|
|
const domain = this._value?.service
|
|
? computeDomain(this._value.service)
|
|
: undefined;
|
|
const serviceName = this._value?.service
|
|
? computeObjectId(this._value.service)
|
|
: undefined;
|
|
|
|
const description =
|
|
(serviceName &&
|
|
this.hass.localize(
|
|
`component.${domain}.services.${serviceName}.description`
|
|
)) ||
|
|
serviceData?.description;
|
|
|
|
return html`
|
|
${this.hidePicker
|
|
? nothing
|
|
: html`<ha-service-picker
|
|
.hass=${this.hass}
|
|
.value=${this._value?.service}
|
|
.disabled=${this.disabled}
|
|
@value-changed=${this._serviceChanged}
|
|
></ha-service-picker>`}
|
|
${this.hideDescription
|
|
? nothing
|
|
: html`
|
|
<div class="description">
|
|
${description ? html`<p>${description}</p>` : ""}
|
|
${this._manifest
|
|
? html` <a
|
|
href=${this._manifest.is_built_in
|
|
? documentationUrl(
|
|
this.hass,
|
|
`/integrations/${this._manifest.domain}`
|
|
)
|
|
: this._manifest.documentation}
|
|
title=${this.hass.localize(
|
|
"ui.components.service-control.integration_doc"
|
|
)}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<ha-icon-button
|
|
.path=${mdiHelpCircle}
|
|
class="help-icon"
|
|
></ha-icon-button>
|
|
</a>`
|
|
: nothing}
|
|
</div>
|
|
`}
|
|
${serviceData && "target" in serviceData
|
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
|
${hasOptional
|
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
|
: ""}
|
|
<span slot="heading"
|
|
>${this.hass.localize(
|
|
"ui.components.service-control.target"
|
|
)}</span
|
|
>
|
|
<span slot="description"
|
|
>${this.hass.localize(
|
|
"ui.components.service-control.target_description"
|
|
)}</span
|
|
><ha-selector
|
|
.hass=${this.hass}
|
|
.selector=${serviceData.target
|
|
? { target: serviceData.target }
|
|
: { target: {} }}
|
|
.disabled=${this.disabled}
|
|
@value-changed=${this._targetChanged}
|
|
.value=${this._value?.target}
|
|
></ha-selector
|
|
></ha-settings-row>`
|
|
: entityId
|
|
? html`<ha-entity-picker
|
|
.hass=${this.hass}
|
|
.disabled=${this.disabled}
|
|
.value=${this._value?.data?.entity_id}
|
|
.label=${this.hass.localize(
|
|
`component.${domain}.services.${serviceName}.fields.entity_id.description`
|
|
) || entityId.description}
|
|
@value-changed=${this._entityPicked}
|
|
allow-custom-entity
|
|
></ha-entity-picker>`
|
|
: ""}
|
|
${shouldRenderServiceDataYaml
|
|
? html`<ha-yaml-editor
|
|
.hass=${this.hass}
|
|
.label=${this.hass.localize("ui.components.service-control.data")}
|
|
.name=${"data"}
|
|
.readOnly=${this.disabled}
|
|
.defaultValue=${this._value?.data}
|
|
@value-changed=${this._dataChanged}
|
|
></ha-yaml-editor>`
|
|
: filteredFields?.map((dataField) => {
|
|
const selector = dataField?.selector ?? { text: undefined };
|
|
const type = Object.keys(selector)[0];
|
|
const enhancedSelector = [
|
|
"action",
|
|
"condition",
|
|
"trigger",
|
|
].includes(type)
|
|
? {
|
|
[type]: {
|
|
...selector[type],
|
|
path: [dataField.key],
|
|
},
|
|
}
|
|
: selector;
|
|
|
|
const showOptional = showOptionalToggle(dataField);
|
|
|
|
return dataField.selector &&
|
|
(!dataField.advanced ||
|
|
this.showAdvanced ||
|
|
(this._value?.data &&
|
|
this._value.data[dataField.key] !== undefined))
|
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
|
${!showOptional
|
|
? 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)}
|
|
.disabled=${this.disabled}
|
|
@change=${this._checkboxChanged}
|
|
slot="prefix"
|
|
></ha-checkbox>`}
|
|
<span slot="heading"
|
|
>${this.hass.localize(
|
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
|
) ||
|
|
dataField.name ||
|
|
dataField.key}</span
|
|
>
|
|
<span slot="description"
|
|
>${this.hass.localize(
|
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
|
) || dataField?.description}</span
|
|
>
|
|
<ha-selector
|
|
.disabled=${this.disabled ||
|
|
(showOptional &&
|
|
!this._checkedKeys.has(dataField.key) &&
|
|
(!this._value?.data ||
|
|
this._value.data[dataField.key] === undefined))}
|
|
.hass=${this.hass}
|
|
.selector=${enhancedSelector}
|
|
.key=${dataField.key}
|
|
@value-changed=${this._serviceDataChanged}
|
|
.value=${this._value?.data
|
|
? this._value.data[dataField.key]
|
|
: undefined}
|
|
.placeholder=${dataField.default}
|
|
.localizeValue=${this._localizeValueCallback}
|
|
@item-moved=${this._itemMoved}
|
|
></ha-selector>
|
|
</ha-settings-row>`
|
|
: "";
|
|
})}
|
|
`;
|
|
}
|
|
|
|
private _localizeValueCallback = (key: string) => {
|
|
if (!this._value?.service) {
|
|
return "";
|
|
}
|
|
return this.hass.localize(
|
|
`component.${computeDomain(this._value.service)}.selector.${key}`
|
|
);
|
|
};
|
|
|
|
private _checkboxChanged(ev) {
|
|
const checked = ev.currentTarget.checked;
|
|
const key = ev.currentTarget.key;
|
|
let data;
|
|
|
|
if (checked) {
|
|
this._checkedKeys.add(key);
|
|
const field = this._getServiceInfo(
|
|
this._value?.service,
|
|
this.hass.services
|
|
)?.fields.find((_field) => _field.key === key);
|
|
|
|
let defaultValue = field?.default;
|
|
|
|
if (
|
|
defaultValue == null &&
|
|
field?.selector &&
|
|
"constant" in field.selector
|
|
) {
|
|
defaultValue = field.selector.constant?.value;
|
|
}
|
|
|
|
if (
|
|
defaultValue == null &&
|
|
field?.selector &&
|
|
"boolean" in field.selector
|
|
) {
|
|
defaultValue = false;
|
|
}
|
|
|
|
if (defaultValue != null) {
|
|
data = {
|
|
...this._value?.data,
|
|
[key]: defaultValue,
|
|
};
|
|
}
|
|
} else {
|
|
this._checkedKeys.delete(key);
|
|
data = { ...this._value?.data };
|
|
delete data[key];
|
|
}
|
|
if (data) {
|
|
fireEvent(this, "value-changed", {
|
|
value: {
|
|
...this._value,
|
|
data,
|
|
},
|
|
});
|
|
}
|
|
this.requestUpdate("_checkedKeys");
|
|
}
|
|
|
|
private _serviceChanged(ev: ValueChangedEvent<string>) {
|
|
ev.stopPropagation();
|
|
if (ev.detail.value === this._value?.service) {
|
|
return;
|
|
}
|
|
|
|
const newService = ev.detail.value || "";
|
|
let target: HassServiceTarget | undefined;
|
|
|
|
if (newService) {
|
|
const serviceData = this._getServiceInfo(newService, this.hass.services);
|
|
const currentTarget = this._value?.target;
|
|
if (currentTarget && serviceData?.target) {
|
|
const targetSelector = { target: { ...serviceData.target } };
|
|
let targetEntities =
|
|
ensureArray(
|
|
currentTarget.entity_id || this._value!.data?.entity_id
|
|
)?.slice() || [];
|
|
let targetDevices =
|
|
ensureArray(
|
|
currentTarget.device_id || this._value!.data?.device_id
|
|
)?.slice() || [];
|
|
let targetAreas =
|
|
ensureArray(
|
|
currentTarget.area_id || this._value!.data?.area_id
|
|
)?.slice() || [];
|
|
if (targetAreas.length) {
|
|
targetAreas = targetAreas.filter((area) =>
|
|
areaMeetsTargetSelector(
|
|
this.hass,
|
|
this.hass.entities,
|
|
this.hass.devices,
|
|
area,
|
|
targetSelector
|
|
)
|
|
);
|
|
}
|
|
if (targetDevices.length) {
|
|
targetDevices = targetDevices.filter((device) =>
|
|
deviceMeetsTargetSelector(
|
|
this.hass,
|
|
Object.values(this.hass.entities),
|
|
this.hass.devices[device],
|
|
targetSelector
|
|
)
|
|
);
|
|
}
|
|
if (targetEntities.length) {
|
|
targetEntities = targetEntities.filter((entity) =>
|
|
entityMeetsTargetSelector(this.hass.states[entity], targetSelector)
|
|
);
|
|
}
|
|
target = {
|
|
...(targetEntities.length ? { entity_id: targetEntities } : {}),
|
|
...(targetDevices.length ? { device_id: targetDevices } : {}),
|
|
...(targetAreas.length ? { area_id: targetAreas } : {}),
|
|
};
|
|
}
|
|
}
|
|
|
|
const value = {
|
|
service: newService,
|
|
target,
|
|
};
|
|
|
|
fireEvent(this, "value-changed", {
|
|
value,
|
|
});
|
|
}
|
|
|
|
private _entityPicked(ev: CustomEvent) {
|
|
ev.stopPropagation();
|
|
const newValue = ev.detail.value;
|
|
if (this._value?.data?.entity_id === newValue) {
|
|
return;
|
|
}
|
|
let value;
|
|
if (!newValue && this._value?.data) {
|
|
value = { ...this._value };
|
|
delete value.data.entity_id;
|
|
} else {
|
|
value = {
|
|
...this._value,
|
|
data: { ...this._value?.data, entity_id: ev.detail.value },
|
|
};
|
|
}
|
|
fireEvent(this, "value-changed", {
|
|
value,
|
|
});
|
|
}
|
|
|
|
private _targetChanged(ev: CustomEvent) {
|
|
ev.stopPropagation();
|
|
const newValue = ev.detail.value;
|
|
if (this._value?.target === newValue) {
|
|
return;
|
|
}
|
|
let value;
|
|
if (!newValue) {
|
|
value = { ...this._value };
|
|
delete value.target;
|
|
} else {
|
|
value = { ...this._value, target: ev.detail.value };
|
|
}
|
|
fireEvent(this, "value-changed", {
|
|
value,
|
|
});
|
|
}
|
|
|
|
private _serviceDataChanged(ev: CustomEvent) {
|
|
ev.stopPropagation();
|
|
const key = (ev.currentTarget as any).key;
|
|
const value = ev.detail.value;
|
|
if (
|
|
this._value?.data?.[key] === value ||
|
|
(!this._value?.data?.[key] && (value === "" || value === undefined))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const data = { ...this._value?.data, [key]: value };
|
|
|
|
if (value === "" || value === undefined) {
|
|
delete data[key];
|
|
}
|
|
|
|
fireEvent(this, "value-changed", {
|
|
value: {
|
|
...this._value,
|
|
data,
|
|
},
|
|
});
|
|
}
|
|
|
|
private _itemMoved(ev) {
|
|
ev.stopPropagation();
|
|
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
|
|
|
const data = this.value?.data ?? {};
|
|
|
|
const newData = nestedArrayMove(data, oldIndex, newIndex, oldPath, newPath);
|
|
|
|
fireEvent(this, "value-changed", {
|
|
value: {
|
|
...this.value,
|
|
data: newData,
|
|
},
|
|
});
|
|
}
|
|
|
|
private _dataChanged(ev: CustomEvent) {
|
|
ev.stopPropagation();
|
|
if (!ev.detail.isValid) {
|
|
return;
|
|
}
|
|
fireEvent(this, "value-changed", {
|
|
value: {
|
|
...this._value,
|
|
data: ev.detail.value,
|
|
},
|
|
});
|
|
}
|
|
|
|
private async _fetchManifest(integration: string) {
|
|
this._manifest = undefined;
|
|
try {
|
|
this._manifest = await fetchIntegrationManifest(this.hass, integration);
|
|
} catch (err: any) {
|
|
// Ignore if loading manifest fails. Probably bad JSON in manifest
|
|
}
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return css`
|
|
ha-settings-row {
|
|
padding: var(--service-control-padding, 0 16px);
|
|
}
|
|
ha-settings-row {
|
|
--paper-time-input-justify-content: flex-end;
|
|
--settings-row-content-width: 100%;
|
|
--settings-row-prefix-display: contents;
|
|
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([hidePicker]) p {
|
|
padding-top: 0;
|
|
}
|
|
.checkbox-spacer {
|
|
width: 32px;
|
|
}
|
|
ha-checkbox {
|
|
margin-left: -16px;
|
|
margin-inline-start: -16px;
|
|
margin-inline-end: initial;
|
|
}
|
|
.help-icon {
|
|
color: var(--secondary-text-color);
|
|
}
|
|
.description {
|
|
justify-content: space-between;
|
|
display: flex;
|
|
align-items: center;
|
|
padding-right: 2px;
|
|
padding-inline-end: 2px;
|
|
padding-inline-start: initial;
|
|
}
|
|
.description p {
|
|
direction: ltr;
|
|
}
|
|
`;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-service-control": HaServiceControl;
|
|
}
|
|
}
|