feat: Allow user generated notifications (#2401)
This commit is contained in:
@@ -19,9 +19,26 @@ import { substreamSelectActionConfigSchema } from './custom/substream-select';
|
||||
import { viewActionConfigSchema } from './custom/view';
|
||||
import { stockActionSchema } from './stock/types';
|
||||
|
||||
// Provide a manual type definition to avoid the `any` that would be created by
|
||||
// the lazy() evaluation below.
|
||||
// ============================================================================
|
||||
// Notification and Status Bar action schemas are co-located here because their
|
||||
// content schemas reference actionConfigSchema (creating a circular dep).
|
||||
// Each uses z.lazy + a manual type annotation to break the cycle and preserve
|
||||
// correct type inference.
|
||||
// See: https://zod.dev/?id=recursive-types
|
||||
// ============================================================================
|
||||
|
||||
export type NotificationActionConfig = z.infer<
|
||||
typeof advancedCameraCardCustomActionsBaseSchema
|
||||
> & {
|
||||
advanced_camera_card_action: 'notification';
|
||||
notification: Notification;
|
||||
};
|
||||
export const notificationActionConfigSchema: z.ZodSchema<NotificationActionConfig> =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('notification'),
|
||||
notification: z.lazy(() => notificationSchema),
|
||||
});
|
||||
|
||||
export type StatusBarActionConfig = z.infer<
|
||||
typeof advancedCameraCardCustomActionsBaseSchema
|
||||
> & {
|
||||
@@ -46,6 +63,7 @@ const advancedCameraCardCustomActionSchema = z.union([
|
||||
internalCallbackActionConfigSchema,
|
||||
logActionConfigSchema,
|
||||
mediaPlayerActionConfigSchema,
|
||||
notificationActionConfigSchema,
|
||||
ptzActionConfigSchema,
|
||||
ptzControlsActionConfigSchema,
|
||||
ptzDigitalActionConfigSchema,
|
||||
@@ -66,6 +84,7 @@ export const actionConfigSchema = z.union([
|
||||
advancedCameraCardCustomActionSchema,
|
||||
]);
|
||||
export type ActionConfig = z.infer<typeof actionConfigSchema>;
|
||||
|
||||
export const actionsBaseSchema = z
|
||||
.object({
|
||||
tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
@@ -90,6 +109,38 @@ export const actionsSchema = z.object({
|
||||
actions: actionsBaseSchema.optional(),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Notification Elements
|
||||
//
|
||||
// Note: Notification schemas are defined here (after actionsBaseSchema) so
|
||||
// controls can directly reference actionsBaseSchema without z.lazy.
|
||||
// ============================================================================
|
||||
|
||||
const notificationBaseSchema = z.object({
|
||||
icon: z.string().optional(),
|
||||
tooltip: z.string().optional(),
|
||||
severity: severitySchema.optional(),
|
||||
});
|
||||
|
||||
export const notificationDetailSchema = notificationBaseSchema.extend({
|
||||
text: z.string(),
|
||||
});
|
||||
export type NotificationDetail = z.infer<typeof notificationDetailSchema>;
|
||||
|
||||
export const notificationControlSchema = notificationBaseSchema.extend({
|
||||
actions: actionsBaseSchema.optional(),
|
||||
dismiss: z.boolean().default(true),
|
||||
});
|
||||
export type NotificationControl = z.infer<typeof notificationControlSchema>;
|
||||
|
||||
export const notificationSchema = z.object({
|
||||
heading: notificationDetailSchema.optional(),
|
||||
controls: notificationControlSchema.array().optional(),
|
||||
details: notificationDetailSchema.array().optional(),
|
||||
text: z.string().optional(),
|
||||
});
|
||||
export type Notification = z.infer<typeof notificationSchema>;
|
||||
|
||||
// ============================================================================
|
||||
// Status Bar Elements
|
||||
//
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const iconSchema = z.object({
|
||||
// MDI icon name (e.g. 'mdi:star').
|
||||
icon: z.string().optional(),
|
||||
|
||||
// HA entity whose icon will be used when `icon` is not set.
|
||||
entity: z.string().optional(),
|
||||
|
||||
// Whether to tint the icon color based on the entity's state.
|
||||
stateColor: z.boolean().optional(),
|
||||
});
|
||||
export type Icon = z.infer<typeof iconSchema>;
|
||||
|
||||
// Extended internally to include a fallback icon that is not user-configurable.
|
||||
export interface InternalIcon extends Icon {
|
||||
fallback?: string;
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export const statusBarConfigDefault = {
|
||||
severity: statusBarItemDefault,
|
||||
technology: statusBarItemDefault,
|
||||
title: statusBarItemDefault,
|
||||
upgrade: statusBarItemDefault,
|
||||
},
|
||||
position: 'bottom' as const,
|
||||
style: 'popup' as const,
|
||||
@@ -53,6 +54,7 @@ export const statusBarConfigSchema = z
|
||||
),
|
||||
severity: statusBarItemBaseSchema.default(statusBarConfigDefault.items.severity),
|
||||
title: statusBarItemBaseSchema.default(statusBarConfigDefault.items.title),
|
||||
upgrade: statusBarItemBaseSchema.default(statusBarConfigDefault.items.upgrade),
|
||||
})
|
||||
.default(statusBarConfigDefault.items),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user