feat: Allow user generated notifications (#2401)

This commit is contained in:
Dermot Duffy
2026-03-07 20:47:24 -08:00
committed by GitHub
parent 1d81e03b04
commit 24c6b98948
53 changed files with 939 additions and 471 deletions
+53 -2
View File
@@ -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
//
+18
View File
@@ -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;
}
+2
View File
@@ -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),
})