refactor: Break apart monolithic configuration schema (#1977)

[skip ci]
This commit is contained in:
Dermot Duffy
2025-03-22 15:06:11 -07:00
committed by GitHub
parent 92fc8dd844
commit d6225fefda
266 changed files with 2789 additions and 2562 deletions
+12
View File
@@ -0,0 +1,12 @@
import { z } from 'zod';
export const aspectRatioSchema = z
.number()
.array()
.length(2)
.or(
z
.string()
.regex(/^\s*\d+\.?\d*\s*[:/]\s*\d+\.?\d*\s*$/)
.transform((input) => input.split(/[:\/]/).map((d) => Number(d))),
);
+35
View File
@@ -0,0 +1,35 @@
export const cardIDRegex = /^[-\w]+$/;
export const MENU_PRIORITY_DEFAULT = 50;
export const MENU_PRIORITY_MAX = 100;
export const STATUS_BAR_PRIORITY_DEFAULT = 50;
export const STATUS_BAR_PRIORITY_MAX = 100;
export const BUTTON_SIZE_MIN = 20;
// The default view (may not be supported on all cameras).
export const VIEW_DEFAULT = 'live' as const;
export const VIEWS_USER_SPECIFIED = [
'diagnostics',
'live',
'clip',
'clips',
'snapshot',
'snapshots',
'recording',
'recordings',
'image',
'timeline',
] as const;
export type AdvancedCameraCardUserSpecifiedView = (typeof VIEWS_USER_SPECIFIED)[number];
const VIEWS = [
...VIEWS_USER_SPECIFIED,
'diagnostics',
// Media: A generic piece of media (could be clip, snapshot, recording).
'media',
] as const;
export type AdvancedCameraCardView = (typeof VIEWS)[number];
@@ -0,0 +1,8 @@
import { z } from 'zod';
import { BUTTON_SIZE_MIN } from '../const';
export const nextPreviousControlConfigSchema = z.object({
style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']),
size: z.number().min(BUTTON_SIZE_MIN),
});
export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>;
+27
View File
@@ -0,0 +1,27 @@
import { z } from 'zod';
export const ptzControlsDefaults = {
orientation: 'horizontal' as const,
mode: 'auto' as const,
hide_pan_tilt: false,
hide_zoom: false,
hide_home: false,
position: 'bottom-right' as const,
};
export const ptzControlsConfigSchema = z.object({
mode: z.enum(['off', 'auto', 'on']).default(ptzControlsDefaults.mode),
position: z
.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right'])
.default(ptzControlsDefaults.position),
orientation: z
.enum(['vertical', 'horizontal'])
.default(ptzControlsDefaults.orientation),
hide_pan_tilt: z.boolean().default(ptzControlsDefaults.hide_pan_tilt),
hide_zoom: z.boolean().default(ptzControlsDefaults.hide_zoom),
hide_home: z.boolean().default(ptzControlsDefaults.hide_home),
style: z.object({}).passthrough().optional(),
});
export type PTZControlsConfig = z.infer<typeof ptzControlsConfigSchema>;
@@ -0,0 +1,45 @@
import { z } from 'zod';
// The min/max width thumbnail.
export const THUMBNAIL_WIDTH_MIN = 75;
export const THUMBNAIL_WIDTH_MAX = 300;
const thumbnailControlsBaseDefaults = {
size: 100,
show_details: true,
show_favorite_control: true,
show_timeline_control: true,
show_download_control: true,
};
// Configuration for the actual rendered thumbnail.
const thumbnailsControlBaseSchema = z.object({
size: z
.number()
.min(THUMBNAIL_WIDTH_MIN)
.max(THUMBNAIL_WIDTH_MAX)
.default(thumbnailControlsBaseDefaults.size),
show_details: z.boolean().default(thumbnailControlsBaseDefaults.show_details),
show_favorite_control: z
.boolean()
.default(thumbnailControlsBaseDefaults.show_favorite_control),
show_timeline_control: z
.boolean()
.default(thumbnailControlsBaseDefaults.show_timeline_control),
show_download_control: z
.boolean()
.default(thumbnailControlsBaseDefaults.show_download_control),
});
export type ThumbnailsControlBaseConfig = z.infer<typeof thumbnailsControlBaseSchema>;
export const thumbnailControlsDefaults = {
...thumbnailControlsBaseDefaults,
mode: 'right' as const,
};
export const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({
mode: z
.enum(['none', 'above', 'below', 'left', 'right'])
.default(thumbnailControlsDefaults.mode),
});
export type ThumbnailsControlConfig = z.infer<typeof thumbnailsControlSchema>;
@@ -0,0 +1,67 @@
import { z } from 'zod';
import { eventsMediaTypeSchema } from '../events-media';
import { thumbnailControlsDefaults } from './thumbnails';
const timelineCoreConfigDefault = {
clustering_threshold: 3,
events_media_type: 'all' as const,
window_seconds: 60 * 60,
show_recordings: true,
style: 'stack' as const,
pan_mode: 'pan' as const,
format: {
'24h': true,
},
};
const timelinePanModeSchema = z.enum(['pan', 'seek', 'seek-in-media', 'seek-in-camera']);
export type TimelinePanMode = z.infer<typeof timelinePanModeSchema>;
const timelineFormatSchema = z.object({
'24h': z.boolean().optional().default(timelineCoreConfigDefault.format['24h']),
});
export const timelineCoreConfigSchema = z.object({
clustering_threshold: z
.number()
.optional()
.default(timelineCoreConfigDefault.clustering_threshold),
events_media_type: eventsMediaTypeSchema
.optional()
.default(timelineCoreConfigDefault.events_media_type),
window_seconds: z
.number()
.min(1 * 60)
.max(24 * 60 * 60)
.optional()
.default(timelineCoreConfigDefault.window_seconds),
show_recordings: z
.boolean()
.optional()
.default(timelineCoreConfigDefault.show_recordings),
style: z.enum(['stack', 'ribbon']).optional().default(timelineCoreConfigDefault.style),
pan_mode: timelinePanModeSchema.optional().default(timelineCoreConfigDefault.pan_mode),
format: timelineFormatSchema.optional().default(timelineCoreConfigDefault.format),
});
export type TimelineCoreConfig = z.infer<typeof timelineCoreConfigSchema>;
export const miniTimelineConfigDefault = {
...timelineCoreConfigDefault,
mode: 'none' as const,
// Mini-timeline defaults to ribbon style.
style: 'ribbon' as const,
};
export const miniTimelineConfigSchema = timelineCoreConfigSchema.extend({
mode: z.enum(['none', 'above', 'below']).default(miniTimelineConfigDefault.mode),
style: timelineCoreConfigSchema.shape.style.default(miniTimelineConfigDefault.style),
});
export type MiniTimelineControlConfig = z.infer<typeof miniTimelineConfigSchema>;
export const timelineConfigDefault = {
...timelineCoreConfigDefault,
controls: {
thumbnails: thumbnailControlsDefaults,
},
};
+14
View File
@@ -0,0 +1,14 @@
import { z } from 'zod';
export const viewDisplayModeSchema = z.enum(['single', 'grid']);
export type ViewDisplayMode = z.infer<typeof viewDisplayModeSchema>;
export const viewDisplaySchema = z
.object({
mode: viewDisplayModeSchema.optional(),
grid_selected_width_factor: z.number().min(0).optional(),
grid_max_columns: z.number().min(0).optional(),
grid_columns: z.number().min(0).optional(),
})
.optional();
export type ViewDisplayConfig = z.infer<typeof viewDisplaySchema>;
+3
View File
@@ -0,0 +1,3 @@
import { z } from 'zod';
export const eventsMediaTypeSchema = z.enum(['all', 'clips', 'snapshots']);
+20
View File
@@ -0,0 +1,20 @@
import { z } from 'zod';
export const imageConfigDefault = {
mode: 'auto' as const,
refresh_seconds: 1,
zoomable: true,
};
const IMAGE_MODES = ['auto', 'camera', 'entity', 'screensaver', 'url'] as const;
export type ImageMode = (typeof IMAGE_MODES)[number];
export const imageBaseConfigSchema = z.object({
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds),
url: z.string().optional(),
entity: z.string().optional(),
entity_parameters: z.string().optional(),
});
+19
View File
@@ -0,0 +1,19 @@
export const MEDIA_ACTION_POSITIVE_CONDITIONS = ['selected', 'visible'] as const;
export const MEDIA_ACTION_NEGATIVE_CONDITIONS = ['unselected', 'hidden'] as const;
export const MEDIA_MUTE_CONDITIONS = [
...MEDIA_ACTION_NEGATIVE_CONDITIONS,
'microphone',
] as const;
export const MEDIA_UNMUTE_CONDITIONS = [
...MEDIA_ACTION_POSITIVE_CONDITIONS,
'microphone',
] as const;
export type AutoPlayCondition = (typeof MEDIA_ACTION_POSITIVE_CONDITIONS)[number];
export type AutoPauseCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number];
export type AutoMuteCondition = (typeof MEDIA_MUTE_CONDITIONS)[number];
export type AutoUnmuteCondition = (typeof MEDIA_UNMUTE_CONDITIONS)[number];
export type LazyUnloadCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number];
+6
View File
@@ -0,0 +1,6 @@
import { z } from 'zod';
export const panSchema = z.object({
x: z.number().min(0).max(100).optional(),
y: z.number().min(0).max(100).optional(),
});
+12
View File
@@ -0,0 +1,12 @@
import { z } from 'zod';
import { STATUS_BAR_PRIORITY_DEFAULT, STATUS_BAR_PRIORITY_MAX } from './const';
export const statusBarItemBaseSchema = z.object({
enabled: z.boolean().default(true).optional(),
priority: z
.number()
.min(0)
.max(STATUS_BAR_PRIORITY_MAX)
.default(STATUS_BAR_PRIORITY_DEFAULT)
.optional(),
});
@@ -0,0 +1,4 @@
import { z } from 'zod';
export const transitionEffectConfigSchema = z.enum(['none', 'slide']);
export type TransitionEffect = z.infer<typeof transitionEffectConfigSchema>;
+6
View File
@@ -0,0 +1,6 @@
import { z } from 'zod';
export const ZOOM_MIN = 1;
export const ZOOM_MAX = 10;
export const zoomSchema = z.number().min(ZOOM_MIN).max(ZOOM_MAX);