Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/panels/lovelace/common/confirmation-default-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { domainToName } from "../../../data/integration";
import type { ActionConfig } from "../../../data/lovelace/config/action";
import type { HomeAssistant } from "../../../types";

export const getConfirmationDefaultText = async (
hass: HomeAssistant,
actionConfig: Pick<ActionConfig, "action"> & {
perform_action?: string;
service?: string;
}
): Promise<string> => {
let actionLabel: string | undefined;

if (
actionConfig.action === "call-service" ||
actionConfig.action === "perform-action"
) {
const performAction = actionConfig.perform_action || actionConfig.service;
if (performAction) {
const [domain, service] = performAction.split(".", 2);
const serviceDomains = hass.services;
if (domain in serviceDomains && service in serviceDomains[domain]) {
await hass.loadBackendTranslation("title");
const localize = await hass.loadBackendTranslation("services");
actionLabel = `${domainToName(localize, domain)}: ${
localize(
`component.${domain}.services.${service}.name`,
hass.services[domain][service].description_placeholders
) ||
serviceDomains[domain][service].name ||
service
}`;
}
}
}

return hass.localize("ui.panel.lovelace.cards.actions.action_confirmation", {
action:
actionLabel ||
hass.localize(
`ui.panel.lovelace.editor.action-editor.actions.${actionConfig.action}` as any
) ||
actionConfig.action,
});
};
33 changes: 2 additions & 31 deletions src/panels/lovelace/common/handle-action.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { forwardHaptic } from "../../../data/haptics";
import { domainToName } from "../../../data/integration";
import type { ActionConfig } from "../../../data/lovelace/config/action";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { showVoiceCommandDialog } from "../../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import type { HomeAssistant } from "../../../types";
import { showToast } from "../../../util/toast";
import { getConfirmationDefaultText } from "./confirmation-default-text";
import { toggleEntity } from "./entity/toggle-entity";

declare global {
Expand Down Expand Up @@ -55,40 +55,11 @@ export const handleAction = async (
) {
forwardHaptic(node, "warning");

let serviceName;
if (
actionConfig.action === "call-service" ||
actionConfig.action === "perform-action"
) {
const [domain, service] = (actionConfig.perform_action ||
actionConfig.service)!.split(".", 2);
const serviceDomains = hass.services;
if (domain in serviceDomains && service in serviceDomains[domain]) {
await hass.loadBackendTranslation("title");
const localize = await hass.loadBackendTranslation("services");
serviceName = `${domainToName(localize, domain)}: ${
localize(
`component.${domain}.services.${service}.name`,
hass.services[domain][service].description_placeholders
) ||
serviceDomains[domain][service].name ||
service
}`;
}
}

if (
!(await showConfirmationDialog(node, {
text:
actionConfig.confirmation.text ||
hass.localize("ui.panel.lovelace.cards.actions.action_confirmation", {
action:
serviceName ||
hass.localize(
`ui.panel.lovelace.editor.action-editor.actions.${actionConfig.action}`
) ||
actionConfig.action,
}),
(await getConfirmationDefaultText(hass, actionConfig)),
title: actionConfig.confirmation.title,
dismissText: actionConfig.confirmation.dismiss_text,
confirmText: actionConfig.confirmation.confirm_text,
Expand Down
12 changes: 11 additions & 1 deletion src/panels/lovelace/components/hui-action-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
NavigateActionConfig,
UrlActionConfig,
} from "../../../data/lovelace/config/action";
import "../editor/confirmation-editor/hui-action-confirmation-toggle";
import type { ServiceAction } from "../../../data/script";
import type { HomeAssistant } from "../../../types";

Expand Down Expand Up @@ -234,6 +235,11 @@ export class HuiActionEditor extends LitElement {
</ha-form>
`
: nothing}
<hui-action-confirmation-toggle
.hass=${this.hass}
.config=${this.config}
.label=${this.label}
></hui-action-confirmation-toggle>
`;
}

Expand Down Expand Up @@ -274,8 +280,12 @@ export class HuiActionEditor extends LitElement {
}
}

const preservedConfirmation = this.config?.confirmation
? { confirmation: this.config.confirmation }
: {};

fireEvent(this, "value-changed", {
value: { action: value, ...data },
value: { action: value, ...preservedConfirmation, ...data },
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { css, html } from "lit";
import { customElement } from "lit/decorators";
import "../../../../components/ha-alert";
import type { HaFormSchema } from "../../../../components/ha-form/types";
import "../../../../components/user/ha-users-picker";
import type {
ActionConfig,
ConfirmationRestrictionConfig,
} from "../../../../data/lovelace/config/action";
import { getConfirmationDefaultText } from "../../common/confirmation-default-text";
import type { LovelaceConfigForm } from "../../types";
import { HuiElementEditor } from "../hui-element-editor";

interface ConfirmationEditorContext {
label?: string;
actionConfig?: ActionConfig;
}

@customElement("hui-action-confirmation-editor")
export class HuiActionConfirmationEditor extends HuiElementEditor<
ConfirmationRestrictionConfig,
ConfirmationEditorContext
> {
protected async getConfigForm(): Promise<LovelaceConfigForm | undefined> {
const localize = this.hass.localize.bind(this.hass);
const actionConfig = this.context?.actionConfig;

const defaultText = actionConfig
? await getConfirmationDefaultText(this.hass, actionConfig)
: undefined;

const defaultTitle = localize(
"ui.dialogs.generic.default_confirmation_title"
);

const helpers: Record<string, string | undefined> = {
title: defaultTitle,
text: defaultText,
confirm_text: localize("ui.common.ok"),
dismiss_text: localize("ui.common.cancel"),
};

return {
schema: [
{ name: "title", selector: { text: {} } },
{ name: "text", selector: { text: {} } },
{ name: "confirm_text", selector: { text: {} } },
{ name: "dismiss_text", selector: { text: {} } },
] as HaFormSchema[],
computeLabel: (schema) =>
localize(
`ui.panel.lovelace.editor.action-editor.confirmation.${schema.name}` as any
),
computeHelper: (schema) => {
const value = helpers[schema.name];
if (!value) return undefined;
return localize(
"ui.panel.lovelace.editor.action-editor.confirmation.default_value" as any,
{ value }
);
},
};
}

protected override renderConfigElement() {
const exemptionIds = this.value?.exemptions?.map((e) => e.user) ?? [];

return html`
${super.renderConfigElement()}
<div class="exemptions">
<ha-users-picker
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.action-editor.confirmation.exemptions"
)}
.value=${exemptionIds}
@value-changed=${this._exemptionsChanged}
></ha-users-picker>
</div>
`;
}

private _exemptionsChanged(ev: CustomEvent): void {
ev.stopPropagation();
const ids: string[] = ev.detail.value;
const exemptions = ids.length ? ids.map((id) => ({ user: id })) : undefined;
this.value = { ...this.value, exemptions } as ConfirmationRestrictionConfig;
}

static override styles = [
HuiElementEditor.styles,
css`
.exemptions {
margin-top: var(--ha-space-4);
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
"hui-action-confirmation-editor": HuiActionConfirmationEditor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { mdiPencil } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-selector/ha-selector-boolean";
import type {
ActionConfig,
ConfirmationRestrictionConfig,
} from "../../../../data/lovelace/config/action";
import type { HomeAssistant } from "../../../../types";

@customElement("hui-action-confirmation-toggle")
export class HuiActionConfirmationToggle extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property({ attribute: false }) public config?: ActionConfig;

@property() public label?: string;

protected render() {
if (!this.hass || !this.config?.action || this.config.action === "none") {
return nothing;
}

return html`
<div class="confirmation-row">
<ha-selector-boolean
.label=${this.hass.localize(
"ui.panel.lovelace.editor.action-editor.confirmation.enable"
)}
.value=${!!this.config.confirmation}
@value-changed=${this._toggleConfirmation}
></ha-selector-boolean>
<ha-icon-button
.path=${mdiPencil}
.disabled=${!this.config.confirmation}
.label=${this.hass.localize(
"ui.panel.lovelace.editor.action-editor.confirmation.edit"
)}
@click=${this._editConfirmation}
></ha-icon-button>
</div>
`;
}

private _toggleConfirmation(ev: CustomEvent): void {
ev.stopPropagation();
const enabled = ev.detail.value as boolean;
if (enabled) {
const existing = this.config!.confirmation;
fireEvent(this, "value-changed", {
value: {
...this.config!,
confirmation:
existing && typeof existing === "object" ? existing : {},
},
});
} else {
const { confirmation: _removed, ...rest } = this
.config as ActionConfig & {
confirmation?: ConfirmationRestrictionConfig | boolean;
};
fireEvent(this, "value-changed", { value: rest });
}
}

private _editConfirmation(): void {
if (!this.config?.confirmation) {
return;
}
const confirmation =
typeof this.config.confirmation === "object"
? this.config.confirmation
: {};
fireEvent(this, "edit-sub-element", {
type: "confirmation",
config: confirmation,
context: { label: this.label, actionConfig: this.config },
saveConfig: (newConfirmation: ConfirmationRestrictionConfig) => {
fireEvent(this, "value-changed", {
value: { ...this.config!, confirmation: newConfirmation },
});
},
});
}

static styles = css`
.confirmation-row {
display: flex;
align-items: center;
margin-top: 8px;
}
ha-selector-boolean {
flex-grow: 1;
}
ha-icon-button {
--ha-icon-button-size: var(--ha-space-9);
color: var(--secondary-text-color);
}
`;
}

declare global {
interface HTMLElementTagNameMap {
"hui-action-confirmation-toggle": HuiActionConfirmationToggle;
}
}
23 changes: 20 additions & 3 deletions src/panels/lovelace/editor/hui-sub-element-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-button-prev";
import type { HomeAssistant } from "../../../types";
import "./confirmation-editor/hui-action-confirmation-editor";
import "./entity-row-editor/hui-row-element-editor";
import "./feature-editor/hui-card-feature-element-editor";
import "./header-footer-editor/hui-header-footer-element-editor";
Expand Down Expand Up @@ -61,9 +62,14 @@ export class HuiSubElementEditor extends LitElement {
) || elementType,
}
)
: this.hass.localize(
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
)}
: this.config?.type === "confirmation" &&
this.config.context?.label
? `${this.config.context.label} - ${this.hass.localize(
"ui.panel.lovelace.editor.sub-element-editor.types.confirmation"
)}`
: this.hass.localize(
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
)}
</span>
</div>
<ha-icon-button
Expand Down Expand Up @@ -154,6 +160,17 @@ export class HuiSubElementEditor extends LitElement {
@GUImode-changed=${this._handleGUIModeChanged}
></hui-heading-badge-element-editor>
`;
case "confirmation":
return html`
<hui-action-confirmation-editor
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
.context=${this.config.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-action-confirmation-editor>
`;
default:
return nothing;
}
Expand Down
Loading
Loading