fix: Confirmations should apply to all actions (#1959)

This also refactors how generic HA actions are handled, and improves
typing of actions.


[skip ci]
This commit is contained in:
Dermot Duffy
2025-03-15 14:32:48 -07:00
committed by GitHub
parent a79ffa6edc
commit 75ae7720d3
90 changed files with 1332 additions and 854 deletions
-90
View File
@@ -1,90 +0,0 @@
import { fireHASSEvent } from './fire-hass-event.js';
import { forwardHaptic } from './haptic.js';
import { navigate } from './navigate.js';
import { toggleEntity } from './toggle-entity.js';
import { ActionConfig, HomeAssistant } from './types.js';
export const handleActionConfig = (
node: HTMLElement,
hass: HomeAssistant,
config: {
entity?: string;
camera_image?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
},
actionConfig: ActionConfig | undefined,
): void => {
if (!actionConfig) {
actionConfig = {
action: 'more-info',
};
}
if (
actionConfig.confirmation &&
(!actionConfig.confirmation.exemptions ||
!actionConfig.confirmation.exemptions.some((e) => e.user === hass!.user!.id))
) {
forwardHaptic('warning');
if (
!confirm(
actionConfig.confirmation.text ||
`Are you sure you want to ${actionConfig.action}?`,
)
) {
return;
}
}
switch (actionConfig.action) {
case 'more-info':
if (config.entity || config.camera_image) {
fireHASSEvent(node, 'hass-more-info', {
entityId: config.entity ? config.entity : config.camera_image!,
});
}
break;
case 'navigate':
if (actionConfig.navigation_path) {
navigate(node, actionConfig.navigation_path);
}
break;
case 'url':
if (actionConfig.url_path) {
window.open(actionConfig.url_path);
}
break;
case 'toggle':
if (config.entity) {
toggleEntity(hass, config.entity!);
forwardHaptic('success');
}
break;
case 'perform-action': {
if (!actionConfig.perform_action) {
forwardHaptic('failure');
return;
}
const [domain, service] = actionConfig.perform_action.split('.', 2);
hass.callService(domain, service, actionConfig.data, actionConfig.target);
forwardHaptic('success');
break;
}
case 'call-service': {
if (!actionConfig.service) {
forwardHaptic('failure');
return;
}
const [domain, service] = actionConfig.service.split('.', 2);
hass.callService(domain, service, actionConfig.data, actionConfig.target);
forwardHaptic('success');
break;
}
case 'fire-dom-event': {
fireHASSEvent(node, 'll-custom', actionConfig);
}
}
};
-5
View File
@@ -1,5 +0,0 @@
import { ActionConfig } from './types.js';
export function hasAction(config?: ActionConfig): boolean {
return config !== undefined && config.action !== 'none';
}
-20
View File
@@ -1,20 +0,0 @@
import { fireHASSEvent } from './fire-hass-event.js';
declare global {
interface HASSDomEvents {
'location-changed': {
replace: boolean;
};
}
}
export const navigate = (_node: unknown, path: string, replace: boolean = false) => {
if (replace) {
history.replaceState(null, '', path);
} else {
history.pushState(null, '', path);
}
fireHASSEvent(window, 'location-changed', {
replace,
});
};
-8
View File
@@ -1,8 +0,0 @@
import { STATES_OFF } from './const.js';
import { turnOnOffEntity } from './turn-on-off-entity.js';
import { HomeAssistant } from './types.js';
export const toggleEntity = (hass: HomeAssistant, entityId: string): Promise<void> => {
const turnOn = STATES_OFF.includes(hass.states[entityId].state);
return turnOnOffEntity(hass, entityId, turnOn);
};
-25
View File
@@ -1,25 +0,0 @@
import { HomeAssistant } from './types.js';
import { computeDomain } from './compute-domain.js';
export const turnOnOffEntity = (
hass: HomeAssistant,
entityId: string,
turnOn = true,
): Promise<void> => {
const stateDomain = computeDomain(entityId);
const serviceDomain = stateDomain === 'group' ? 'homeassistant' : stateDomain;
let service;
switch (stateDomain) {
case 'lock':
service = turnOn ? 'unlock' : 'lock';
break;
case 'cover':
service = turnOn ? 'open_cover' : 'close_cover';
break;
default:
service = turnOn ? 'turn_on' : 'turn_off';
}
return hass.callService(serviceDomain, service, { entity_id: entityId });
};
-81
View File
@@ -7,87 +7,6 @@ import {
HassServiceTarget,
MessageBase,
} from 'home-assistant-js-websocket';
import { HapticType } from './haptic.js';
interface ToggleMenuActionConfig extends BaseActionConfig {
action: 'toggle-menu';
}
export interface ToggleActionConfig extends BaseActionConfig {
action: 'toggle';
}
export interface CallServiceActionConfig extends BaseActionConfig {
action: 'call-service';
service: string;
data?: {
entity_id?: string | [string];
[key: string]: unknown;
};
target?: HassServiceTarget;
repeat?: number;
haptic?: HapticType;
}
export interface PerformActionActionConfig extends BaseActionConfig {
action: 'perform-action';
perform_action: string;
data?: {
entity_id?: string | [string];
[key: string]: unknown;
};
target?: HassServiceTarget;
repeat?: number;
haptic?: HapticType;
}
export interface NavigateActionConfig extends BaseActionConfig {
action: 'navigate';
navigation_path: string;
}
export interface UrlActionConfig extends BaseActionConfig {
action: 'url';
url_path: string;
}
export interface MoreInfoActionConfig extends BaseActionConfig {
action: 'more-info';
entity?: string;
}
export interface NoActionConfig extends BaseActionConfig {
action: 'none';
}
interface CustomActionConfig extends BaseActionConfig {
action: 'fire-dom-event';
}
/**
* `repeat` and `haptic` are specifically for use in custom cards like the Button-Card
*/
interface BaseActionConfig {
confirmation?: ConfirmationRestrictionConfig;
repeat?: number;
haptic?: HapticType;
}
export interface ConfirmationRestrictionConfig {
text?: string;
exemptions?: RestrictionConfig[];
}
interface RestrictionConfig {
user: string;
}
export declare type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| PerformActionActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| NoActionConfig
| CustomActionConfig
| ToggleMenuActionConfig;
declare global {
interface HASSDomEvents {