Merge pull request #528 from dermotduffy/action-confirmation
Support confirmation for actions
This commit is contained in:
+15
-5
@@ -5,6 +5,7 @@ import {
|
|||||||
handleActionConfig,
|
handleActionConfig,
|
||||||
hasAction,
|
hasAction,
|
||||||
stateIcon,
|
stateIcon,
|
||||||
|
ActionConfig,
|
||||||
} from 'custom-card-helpers';
|
} from 'custom-card-helpers';
|
||||||
import { StyleInfo } from 'lit/directives/style-map.js';
|
import { StyleInfo } from 'lit/directives/style-map.js';
|
||||||
import { ZodSchema, z } from 'zod';
|
import { ZodSchema, z } from 'zod';
|
||||||
@@ -596,12 +597,19 @@ export const frigateCardHandleActionConfig = (
|
|||||||
action: string,
|
action: string,
|
||||||
actionConfig: ActionType | ActionType[] | undefined,
|
actionConfig: ActionType | ActionType[] | undefined,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
// Only allow a tap action to use a default non-config (the more-info config).
|
||||||
if (actionConfig || action == 'tap') {
|
if (actionConfig || action == 'tap') {
|
||||||
// Only allow a tap action to use a default non-config (the more-info config).
|
// ActionConfig vs ActionType:
|
||||||
|
// There is a slight typing (but not functional) difference between
|
||||||
|
// ActionType in this card and ActionConfig in `custom-card-helpers`. See
|
||||||
|
// `ExtendedConfirmationRestrictionConfig` in `types.ts` for the source and
|
||||||
|
// reason behind this difference.
|
||||||
if (Array.isArray(actionConfig)) {
|
if (Array.isArray(actionConfig)) {
|
||||||
actionConfig.forEach((action) => handleActionConfig(node, hass, config, action));
|
actionConfig.forEach((action) =>
|
||||||
|
handleActionConfig(node, hass, config, action as ActionConfig | undefined),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
handleActionConfig(node, hass, config, actionConfig);
|
handleActionConfig(node, hass, config, actionConfig as ActionConfig | undefined);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -617,10 +625,12 @@ export const frigateCardHandleActionConfig = (
|
|||||||
export const frigateCardHasAction = (
|
export const frigateCardHasAction = (
|
||||||
config?: ActionType | ActionType[] | undefined,
|
config?: ActionType | ActionType[] | undefined,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
// See note above on 'ActionConfig vs ActionType' for why this cast is
|
||||||
|
// necessary and harmless.
|
||||||
if (Array.isArray(config)) {
|
if (Array.isArray(config)) {
|
||||||
return !!config.find((item) => hasAction(item));
|
return !!config.find((item) => hasAction(item as ActionConfig | undefined));
|
||||||
}
|
}
|
||||||
return hasAction(config);
|
return hasAction(config as ActionConfig | undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+62
-22
@@ -1,6 +1,7 @@
|
|||||||
import { StyleInfo } from 'lit/directives/style-map.js';
|
import { StyleInfo } from 'lit/directives/style-map.js';
|
||||||
import {
|
import {
|
||||||
CallServiceActionConfig,
|
CallServiceActionConfig,
|
||||||
|
ConfirmationRestrictionConfig,
|
||||||
CustomActionConfig,
|
CustomActionConfig,
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
@@ -68,7 +69,6 @@ export class FrigateCardError extends Error {}
|
|||||||
/**
|
/**
|
||||||
* Action Types (for "Picture Elements" / Menu)
|
* Action Types (for "Picture Elements" / Menu)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Declare schemas to existing types:
|
// Declare schemas to existing types:
|
||||||
// - https://github.com/colinhacks/zod/issues/372#issuecomment-826380330
|
// - https://github.com/colinhacks/zod/issues/372#issuecomment-826380330
|
||||||
const schemaForType =
|
const schemaForType =
|
||||||
@@ -77,47 +77,87 @@ const schemaForType =
|
|||||||
<S extends z.ZodType<T, any, any>>(arg: S) => {
|
<S extends z.ZodType<T, any, any>>(arg: S) => {
|
||||||
return arg;
|
return arg;
|
||||||
};
|
};
|
||||||
const toggleActionSchema = schemaForType<ToggleActionConfig>()(
|
|
||||||
z.object({
|
// https://www.home-assistant.io/dashboards/actions/#options-for-confirmation
|
||||||
|
const actionBaseSchema = z.object({
|
||||||
|
confirmation: z
|
||||||
|
.boolean()
|
||||||
|
.or(
|
||||||
|
z.object({
|
||||||
|
text: z.string().optional(),
|
||||||
|
exemptions: z
|
||||||
|
.object({
|
||||||
|
user: z.string(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// HA accepts either a boolean or a ConfirmationRestrictionConfig object.
|
||||||
|
// `custom-card-helpers` currently only supports the latter. For maximum
|
||||||
|
// compatibility, this card supports what HA supports.
|
||||||
|
export interface ExtendedConfirmationRestrictionConfig {
|
||||||
|
confirmation?: boolean | ConfirmationRestrictionConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleActionSchema = schemaForType<
|
||||||
|
ToggleActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('toggle'),
|
action: z.literal('toggle'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const callServiceActionSchema = schemaForType<CallServiceActionConfig>()(
|
const callServiceActionSchema = schemaForType<
|
||||||
z.object({
|
CallServiceActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('call-service'),
|
action: z.literal('call-service'),
|
||||||
service: z.string(),
|
service: z.string(),
|
||||||
service_data: z.object({}).passthrough().optional(),
|
service_data: z.object({}).passthrough().optional(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const navigateActionSchema = schemaForType<NavigateActionConfig>()(
|
const navigateActionSchema = schemaForType<
|
||||||
z.object({
|
NavigateActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('navigate'),
|
action: z.literal('navigate'),
|
||||||
navigation_path: z.string(),
|
navigation_path: z.string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const urlActionSchema = schemaForType<UrlActionConfig>()(
|
const urlActionSchema = schemaForType<
|
||||||
z.object({
|
UrlActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('url'),
|
action: z.literal('url'),
|
||||||
url_path: z.string(),
|
url_path: z.string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const moreInfoActionSchema = schemaForType<MoreInfoActionConfig>()(
|
const moreInfoActionSchema = schemaForType<
|
||||||
z.object({
|
MoreInfoActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('more-info'),
|
action: z.literal('more-info'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const customActionSchema = schemaForType<CustomActionConfig>()(
|
const customActionSchema = schemaForType<
|
||||||
z.object({
|
CustomActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('fire-dom-event'),
|
action: z.literal('fire-dom-event'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const noActionSchema = schemaForType<NoActionConfig>()(
|
const noActionSchema = schemaForType<
|
||||||
z.object({
|
NoActionConfig & ExtendedConfirmationRestrictionConfig
|
||||||
|
>()(
|
||||||
|
actionBaseSchema.extend({
|
||||||
action: z.literal('none'),
|
action: z.literal('none'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const frigateCardCustomActionBaseSchema = customActionSchema.extend({
|
const frigateCardCustomactionsBaseSchema = customActionSchema.extend({
|
||||||
// Syntactic sugar to avoid 'fire-dom-event' as part of an external API.
|
// Syntactic sugar to avoid 'fire-dom-event' as part of an external API.
|
||||||
action: z
|
action: z
|
||||||
.literal('custom:frigate-card-action')
|
.literal('custom:frigate-card-action')
|
||||||
@@ -142,10 +182,10 @@ const FRIGATE_CARD_GENERAL_ACTIONS = [
|
|||||||
const FRIGATE_CARD_ACTIONS = [...FRIGATE_CARD_GENERAL_ACTIONS, 'camera_select'] as const;
|
const FRIGATE_CARD_ACTIONS = [...FRIGATE_CARD_GENERAL_ACTIONS, 'camera_select'] as const;
|
||||||
export type FrigateCardAction = typeof FRIGATE_CARD_ACTIONS[number];
|
export type FrigateCardAction = typeof FRIGATE_CARD_ACTIONS[number];
|
||||||
|
|
||||||
const frigateCardGeneralActionSchema = frigateCardCustomActionBaseSchema.extend({
|
const frigateCardGeneralActionSchema = frigateCardCustomactionsBaseSchema.extend({
|
||||||
frigate_card_action: z.enum(FRIGATE_CARD_GENERAL_ACTIONS),
|
frigate_card_action: z.enum(FRIGATE_CARD_GENERAL_ACTIONS),
|
||||||
});
|
});
|
||||||
const frigateCardCameraSelectActionSchema = frigateCardCustomActionBaseSchema.extend({
|
const frigateCardCameraSelectActionSchema = frigateCardCustomactionsBaseSchema.extend({
|
||||||
frigate_card_action: z.literal('camera_select'),
|
frigate_card_action: z.literal('camera_select'),
|
||||||
camera: z.string(),
|
camera: z.string(),
|
||||||
});
|
});
|
||||||
@@ -168,7 +208,7 @@ const actionSchema = z.union([
|
|||||||
]);
|
]);
|
||||||
export type ActionType = z.infer<typeof actionSchema>;
|
export type ActionType = z.infer<typeof actionSchema>;
|
||||||
|
|
||||||
const actionBaseSchema = z
|
const actionsBaseSchema = z
|
||||||
.object({
|
.object({
|
||||||
tap_action: actionSchema.or(actionSchema.array()).optional(),
|
tap_action: actionSchema.or(actionSchema.array()).optional(),
|
||||||
hold_action: actionSchema.or(actionSchema.array()).optional(),
|
hold_action: actionSchema.or(actionSchema.array()).optional(),
|
||||||
@@ -180,17 +220,17 @@ const actionBaseSchema = z
|
|||||||
// card doesn't need these attributes, but handleAction() in
|
// card doesn't need these attributes, but handleAction() in
|
||||||
// custom_card_helpers may depending on how the action is configured.
|
// custom_card_helpers may depending on how the action is configured.
|
||||||
.passthrough();
|
.passthrough();
|
||||||
export type Actions = z.infer<typeof actionBaseSchema>;
|
export type Actions = z.infer<typeof actionsBaseSchema>;
|
||||||
export type ActionsConfig = Actions & {
|
export type ActionsConfig = Actions & {
|
||||||
camera_image?: string;
|
camera_image?: string;
|
||||||
entity?: string;
|
entity?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionsSchema = z.object({
|
const actionsSchema = z.object({
|
||||||
actions: actionBaseSchema.optional(),
|
actions: actionsBaseSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const elementsBaseSchema = actionBaseSchema.extend({
|
const elementsBaseSchema = actionsBaseSchema.extend({
|
||||||
style: z.object({}).passthrough().optional(),
|
style: z.object({}).passthrough().optional(),
|
||||||
title: z.string().nullable().optional(),
|
title: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user