mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 02:38:53 +00:00
Button feature change variable handling script (#26786)
* Update types.ts Added extra parameter button_action * Update hui-button-card-feature-editor.ts Added second field * Update hui-button-card-feature.ts * Update types.ts * Update hui-button-card-feature-editor.ts Fix issue with field naming * Update hui-button-card-feature-editor.ts * Update hui-button-card-feature-editor.ts * Update hui-button-card-feature-editor.ts * . * Strategy update * Update types.ts * Update hui-button-card-feature-editor.ts * Fix linting issues * Add data field to editor * localize error * Update hui-button-card-feature.ts Added suggestions * Use UI to set script variables in button feature * Update src/panels/lovelace/card-features/hui-button-card-feature.ts Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com> --------- Co-authored-by: Wendelin <w@pe8.at> Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
@@ -5,17 +5,20 @@ import type {
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { Describe } from "superstruct";
|
||||
import {
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
union,
|
||||
array,
|
||||
assign,
|
||||
boolean,
|
||||
object,
|
||||
optional,
|
||||
refine,
|
||||
string,
|
||||
union,
|
||||
} from "superstruct";
|
||||
import { arrayLiteralIncludes } from "../common/array/literal-includes";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { hasTemplate } from "../common/string/has-template";
|
||||
import { createSearchParam } from "../common/url/search-params";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type {
|
||||
Condition,
|
||||
@@ -26,9 +29,6 @@ import type {
|
||||
} from "./automation";
|
||||
import { migrateAutomationTrigger } from "./automation";
|
||||
import type { BlueprintInput } from "./blueprint";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { createSearchParam } from "../common/url/search-params";
|
||||
import { hasTemplate } from "../common/string/has-template";
|
||||
|
||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||
export const MODES_MAX = ["queued", "parallel"] as const;
|
||||
@@ -395,6 +395,37 @@ export const hasScriptFields = (
|
||||
return fields !== undefined && Object.keys(fields).length > 0;
|
||||
};
|
||||
|
||||
export const hasRequiredScriptFields = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): boolean => {
|
||||
const fields = hass.services.script[computeObjectId(entityId)]?.fields;
|
||||
return (
|
||||
fields !== undefined &&
|
||||
Object.values(fields).some((field) => field.required)
|
||||
);
|
||||
};
|
||||
|
||||
export const requiredScriptFieldsFilled = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
data?: Record<string, any>
|
||||
): boolean => {
|
||||
const fields = hass.services.script[computeObjectId(entityId)]?.fields;
|
||||
if (fields === undefined || Object.keys(fields).length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (data === undefined) {
|
||||
return false;
|
||||
}
|
||||
return Object.entries(fields).every(([key, field]) => {
|
||||
if (field.required) {
|
||||
return data[key] !== undefined;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
export const migrateAutomationAction = (
|
||||
action: Action | Action[]
|
||||
): Action | Action[] => {
|
||||
|
||||
@@ -3,20 +3,24 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-service-control";
|
||||
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import "../../../components/entity/state-info";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/entity/state-info";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { ScriptEntity } from "../../../data/script";
|
||||
import { canRun } from "../../../data/script";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||
import "../components/ha-more-info-state-header";
|
||||
import type { ExtEntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-service-control";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import type { ExtEntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import type { ScriptEntity } from "../../../data/script";
|
||||
import {
|
||||
canRun,
|
||||
hasRequiredScriptFields,
|
||||
requiredScriptFieldsFilled,
|
||||
} from "../../../data/script";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-state-header";
|
||||
|
||||
@customElement("more-info-script")
|
||||
class MoreInfoScript extends LitElement {
|
||||
@@ -26,6 +30,8 @@ class MoreInfoScript extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entry?: ExtEntityRegistryEntry;
|
||||
|
||||
@property({ attribute: false }) public data?: Record<string, any>;
|
||||
|
||||
@state() private _scriptData: Record<string, any> = {};
|
||||
|
||||
@state() private narrow = false;
|
||||
@@ -110,7 +116,10 @@ class MoreInfoScript extends LitElement {
|
||||
hide-picker
|
||||
hide-description
|
||||
.hass=${this.hass}
|
||||
.value=${this._scriptData}
|
||||
.value=${{
|
||||
...(this.data ? { data: this.data } : {}),
|
||||
...this._scriptData,
|
||||
}}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
.narrow=${this.narrow}
|
||||
@value-changed=${this._scriptDataChanged}
|
||||
@@ -198,7 +207,13 @@ class MoreInfoScript extends LitElement {
|
||||
|
||||
private _canRun() {
|
||||
if (
|
||||
canRun(this.stateObj!) ||
|
||||
!hasRequiredScriptFields(this.hass, this.stateObj!.entity_id) ||
|
||||
(requiredScriptFieldsFilled(
|
||||
this.hass,
|
||||
this.stateObj!.entity_id,
|
||||
this._scriptData.data
|
||||
) &&
|
||||
canRun(this.stateObj!)) ||
|
||||
// Restart can also always runs. Just cancels other run.
|
||||
this.stateObj!.attributes.mode === "restart"
|
||||
) {
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface MoreInfoDialogParams {
|
||||
view?: View;
|
||||
/** @deprecated Use `view` instead */
|
||||
tab?: View;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
type View = "info" | "history" | "settings" | "related";
|
||||
@@ -96,6 +97,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@state() private _entityId?: string | null;
|
||||
|
||||
@state() private _data?: Record<string, any>;
|
||||
|
||||
@state() private _currView: View = DEFAULT_VIEW;
|
||||
|
||||
@state() private _initialView: View = DEFAULT_VIEW;
|
||||
@@ -116,6 +119,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
this.closeDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
this._data = params.data;
|
||||
this._currView = params.view || DEFAULT_VIEW;
|
||||
this._initialView = params.view || DEFAULT_VIEW;
|
||||
this._childView = undefined;
|
||||
@@ -570,6 +575,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
.editMode=${this._infoEditMode}
|
||||
.data=${this._data}
|
||||
></ha-more-info-info>
|
||||
`
|
||||
: this._currView === "history"
|
||||
|
||||
@@ -27,6 +27,8 @@ export class MoreInfoInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public editMode?: boolean;
|
||||
|
||||
@property({ attribute: false }) public data?: Record<string, any>;
|
||||
|
||||
@state() private _sensorNumericDeviceClasses?: string[] = [];
|
||||
|
||||
private async _loadNumericDeviceClasses() {
|
||||
@@ -105,6 +107,7 @@ export class MoreInfoInfo extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entry=${this.entry}
|
||||
.editMode=${this.editMode}
|
||||
.data=${this.data}
|
||||
></more-info-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,14 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import "../../components/ha-badge";
|
||||
import type { ExtEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { supportsCoverPositionCardFeature } from "../../panels/lovelace/card-features/hui-cover-position-card-feature";
|
||||
import { supportsLightBrightnessCardFeature } from "../../panels/lovelace/card-features/hui-light-brightness-card-feature";
|
||||
import type { LovelaceCardFeatureConfig } from "../../panels/lovelace/card-features/types";
|
||||
import type { TileCardConfig } from "../../panels/lovelace/cards/types";
|
||||
import { importMoreInfoControl } from "../../panels/lovelace/custom-card-helpers";
|
||||
import "../../panels/lovelace/sections/hui-section";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { stateMoreInfoType } from "./state_more_info_control";
|
||||
import type { LovelaceCardFeatureConfig } from "../../panels/lovelace/card-features/types";
|
||||
import { supportsLightBrightnessCardFeature } from "../../panels/lovelace/card-features/hui-light-brightness-card-feature";
|
||||
import { supportsCoverPositionCardFeature } from "../../panels/lovelace/card-features/hui-cover-position-card-feature";
|
||||
|
||||
@customElement("more-info-content")
|
||||
class MoreInfoContent extends LitElement {
|
||||
@@ -25,6 +25,8 @@ class MoreInfoContent extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public editMode?: boolean;
|
||||
|
||||
@property({ attribute: false }) public data?: Record<string, any>;
|
||||
|
||||
protected render() {
|
||||
let moreInfoType: string | undefined;
|
||||
|
||||
@@ -48,6 +50,7 @@ class MoreInfoContent extends LitElement {
|
||||
stateObj: this.stateObj,
|
||||
entry: this.entry,
|
||||
editMode: this.editMode,
|
||||
data: this.data,
|
||||
})}
|
||||
${this._showEntityMembers(this.stateObj)
|
||||
? html`
|
||||
|
||||
@@ -4,7 +4,10 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import { hasScriptFields } from "../../../data/script";
|
||||
import {
|
||||
hasRequiredScriptFields,
|
||||
requiredScriptFieldsFilled,
|
||||
} from "../../../data/script";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
@@ -50,15 +53,28 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
|
||||
|
||||
if (domain === "script") {
|
||||
const entityId = this._stateObj.entity_id;
|
||||
if (hasScriptFields(this.hass!, entityId)) {
|
||||
showMoreInfoDialog(this, { entityId: entityId });
|
||||
if (
|
||||
hasRequiredScriptFields(this.hass!, entityId) &&
|
||||
!requiredScriptFieldsFilled(this.hass!, entityId, this._config?.data)
|
||||
) {
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: entityId,
|
||||
data: this._config?.data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.hass.callService(domain, service, {
|
||||
const serviceData = {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
...(this._config?.data
|
||||
? {
|
||||
variables: this._config.data,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
this.hass.callService(domain, service, serviceData);
|
||||
}
|
||||
|
||||
static getStubConfig(): ButtonCardFeatureConfig {
|
||||
|
||||
@@ -2,9 +2,12 @@ import type { AlarmMode } from "../../../data/alarm_control_panel";
|
||||
import type { HvacMode } from "../../../data/climate";
|
||||
import type { OperationMode } from "../../../data/water_heater";
|
||||
|
||||
export type ButtonCardData = Record<string, any>;
|
||||
|
||||
export interface ButtonCardFeatureConfig {
|
||||
type: "button";
|
||||
action_name?: string;
|
||||
data?: ButtonCardData;
|
||||
}
|
||||
|
||||
export interface CoverOpenCloseCardFeatureConfig {
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { mdiApplicationVariableOutline } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import "../../../../components/ha-service-control";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { hasScriptFields } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { ButtonCardFeatureConfig } from "../../card-features/types";
|
||||
import type {
|
||||
ButtonCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
@customElement("hui-button-card-feature-editor")
|
||||
@@ -14,6 +25,8 @@ export class HuiButtonCardFeatureEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: ButtonCardFeatureConfig;
|
||||
|
||||
public setConfig(config: ButtonCardFeatureConfig): void {
|
||||
@@ -35,6 +48,27 @@ export class HuiButtonCardFeatureEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
let scriptData:
|
||||
| {
|
||||
action: string;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (this.context?.entity_id) {
|
||||
const domain = computeDomain(this.context.entity_id);
|
||||
|
||||
if (
|
||||
domain === "script" &&
|
||||
hasScriptFields(this.hass, this.context.entity_id)
|
||||
) {
|
||||
scriptData = {
|
||||
action: this.context.entity_id,
|
||||
data: this._config.data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
@@ -43,19 +77,68 @@ export class HuiButtonCardFeatureEditor
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
${scriptData
|
||||
? html`<ha-expansion-panel
|
||||
outlined
|
||||
expanded
|
||||
.header=${this.hass.localize(
|
||||
"ui.components.service-control.script_variables"
|
||||
)}
|
||||
.secondary=${this.hass.localize("ui.common.optional")}
|
||||
no-collapse
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="leading-icon"
|
||||
.path=${mdiApplicationVariableOutline}
|
||||
></ha-svg-icon>
|
||||
<ha-service-control
|
||||
hide-picker
|
||||
hide-description
|
||||
.hass=${this.hass}
|
||||
.value=${scriptData}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
.narrow=${false}
|
||||
@value-changed=${this._scriptFieldVariablesChanged}
|
||||
></ha-service-control
|
||||
></ha-expansion-panel>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeLabel = () => this.hass.localize("ui.common.name");
|
||||
private _computeLabel = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "action_name":
|
||||
return this.hass!.localize("ui.common.name");
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private _scriptFieldVariablesChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", {
|
||||
config: {
|
||||
...(this._config || {}),
|
||||
data: ev.detail.value.data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("config-changed", {
|
||||
detail: { config: ev.detail.value },
|
||||
})
|
||||
);
|
||||
fireEvent(this, "config-changed", {
|
||||
config: { ...(this._config || {}), ...ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-expansion-panel {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -30,6 +30,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
{
|
||||
entityId: ev.detail.entityId,
|
||||
view: ev.detail.view || ev.detail.tab,
|
||||
data: ev.detail.data,
|
||||
},
|
||||
() => import("../dialogs/more-info/ha-more-info-dialog")
|
||||
);
|
||||
|
||||
@@ -955,7 +955,8 @@
|
||||
"target": "Targets",
|
||||
"target_secondary": "What should this action use as targeted areas, devices or entities.",
|
||||
"action_data": "Action data",
|
||||
"integration_doc": "Integration documentation"
|
||||
"integration_doc": "Integration documentation",
|
||||
"script_variables": "Script variables"
|
||||
},
|
||||
"related-items": {
|
||||
"no_related_found": "No related items found.",
|
||||
|
||||
Reference in New Issue
Block a user