refactor: Break apart monolithic configuration schema (#1977)
[skip ci]
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import { actionsBaseSchema } from '../actions/types';
|
||||
|
||||
export const elementsBaseSchema = actionsBaseSchema.extend({
|
||||
style: z.record(z.string().nullable().or(z.undefined()).or(z.number())).optional(),
|
||||
title: z.string().nullable().optional(),
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import { MENU_PRIORITY_DEFAULT, MENU_PRIORITY_MAX } from '../../../common/const';
|
||||
|
||||
export const menuBaseSchema = z.object({
|
||||
enabled: z.boolean().default(true).optional(),
|
||||
priority: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(MENU_PRIORITY_MAX)
|
||||
.default(MENU_PRIORITY_DEFAULT)
|
||||
.optional(),
|
||||
alignment: z.enum(['matching', 'opposing']).default('matching').optional(),
|
||||
icon: z.string().optional(),
|
||||
permanent: z.boolean().default(false).optional(),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { iconSchema } from '../../stock/icon';
|
||||
import { menuBaseSchema } from './base';
|
||||
|
||||
export const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({
|
||||
type: z.literal('custom:advanced-camera-card-menu-icon'),
|
||||
});
|
||||
export type MenuIcon = z.infer<typeof menuIconSchema>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { stateIconSchema } from '../../stock/state-icon';
|
||||
import { menuBaseSchema } from './base';
|
||||
|
||||
export const menuStateIconSchema = menuBaseSchema
|
||||
.merge(stateIconSchema)
|
||||
.extend({
|
||||
type: z.literal('custom:advanced-camera-card-menu-state-icon'),
|
||||
})
|
||||
.merge(menuBaseSchema);
|
||||
export type MenuStateIcon = z.infer<typeof menuStateIconSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { stateIconSchema } from '../../stock/state-icon';
|
||||
import { menuBaseSchema } from './base';
|
||||
import { menuSubmenuItemSchema } from './submenu';
|
||||
|
||||
export const menuSubmenuSelectSchema = menuBaseSchema.merge(stateIconSchema).extend({
|
||||
type: z.literal('custom:advanced-camera-card-menu-submenu-select'),
|
||||
options: z.record(menuSubmenuItemSchema.deepPartial()).optional(),
|
||||
});
|
||||
export type MenuSubmenuSelect = z.infer<typeof menuSubmenuSelectSchema>;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../../base';
|
||||
import { iconSchema } from '../../stock/icon';
|
||||
import { menuBaseSchema } from './base';
|
||||
|
||||
export const menuSubmenuItemSchema = elementsBaseSchema.extend({
|
||||
entity: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
state_color: z.boolean().default(true),
|
||||
selected: z.boolean().default(false),
|
||||
subtitle: z.string().optional(),
|
||||
enabled: z.boolean().default(true),
|
||||
});
|
||||
export type MenuSubmenuItem = z.infer<typeof menuSubmenuItemSchema>;
|
||||
|
||||
export const menuSubmenuSchema = menuBaseSchema.merge(iconSchema).extend({
|
||||
type: z.literal('custom:advanced-camera-card-menu-submenu'),
|
||||
items: menuSubmenuItemSchema.array(),
|
||||
});
|
||||
export type MenuSubmenu = z.infer<typeof menuSubmenuSchema>;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { MenuIcon } from './icon';
|
||||
import { MenuStateIcon } from './state-icon';
|
||||
import { MenuSubmenu } from './submenu';
|
||||
import { MenuSubmenuSelect } from './submenu-select';
|
||||
|
||||
export type MenuItem = MenuIcon | MenuStateIcon | MenuSubmenu | MenuSubmenuSelect;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#custom-elements
|
||||
export const customSchema = z
|
||||
.object({
|
||||
// Insist that Advanced Camera Card custom elements are handled by other schemas.
|
||||
type: z.string().superRefine((val, ctx) => {
|
||||
if (!val.match(/^custom:(?!advanced-camera-card).+/)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'advanced-camera-card custom elements must match specific schemas',
|
||||
fatal: true,
|
||||
});
|
||||
}
|
||||
}),
|
||||
})
|
||||
.passthrough();
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#icon-element
|
||||
export const iconSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('icon'),
|
||||
icon: z.string(),
|
||||
entity: z.string().optional(),
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#image-element
|
||||
export const imageSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('image'),
|
||||
entity: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
camera_image: z.string().optional(),
|
||||
camera_view: z.string().optional(),
|
||||
state_image: z.object({}).passthrough().optional(),
|
||||
filter: z.string().optional(),
|
||||
state_filter: z.object({}).passthrough().optional(),
|
||||
aspect_ratio: z.string().optional(),
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#service-call-button
|
||||
export const serviceCallButtonSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('service-button'),
|
||||
|
||||
// Title is required for service button.
|
||||
title: z.string(),
|
||||
service: z.string(),
|
||||
service_data: z.object({}).passthrough().optional(),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#state-badge
|
||||
export const stateBadgeIconSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('state-badge'),
|
||||
entity: z.string(),
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#state-icon
|
||||
export const stateIconSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('state-icon'),
|
||||
entity: z.string(),
|
||||
icon: z.string().optional(),
|
||||
state_color: z.boolean().default(true),
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { elementsBaseSchema } from '../base';
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#state-label
|
||||
export const stateLabelSchema = elementsBaseSchema.extend({
|
||||
type: z.literal('state-label'),
|
||||
entity: z.string(),
|
||||
attribute: z.string().optional(),
|
||||
prefix: z.string().optional(),
|
||||
suffix: z.string().optional(),
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
statusBarIconItemSchema,
|
||||
statusBarImageItemSchema,
|
||||
statusBarStringItemSchema,
|
||||
} from '../actions/types';
|
||||
import { StockCondition, stockConditionSchema } from '../conditions/stock/types';
|
||||
import {
|
||||
AdvancedCameraCardCondition,
|
||||
advancedCameraCardConditionSchema,
|
||||
} from '../conditions/types';
|
||||
import { menuIconSchema } from './custom/menu/icon';
|
||||
import { menuStateIconSchema } from './custom/menu/state-icon';
|
||||
import { menuSubmenuSchema } from './custom/menu/submenu';
|
||||
import { menuSubmenuSelectSchema } from './custom/menu/submenu-select';
|
||||
import { customSchema } from './stock/custom';
|
||||
import { iconSchema } from './stock/icon';
|
||||
import { imageSchema } from './stock/image';
|
||||
import { serviceCallButtonSchema } from './stock/service-call';
|
||||
import { stateBadgeIconSchema } from './stock/state-badge';
|
||||
import { stateIconSchema } from './stock/state-icon';
|
||||
import { stateLabelSchema } from './stock/state-label';
|
||||
|
||||
// Condition elements are included in this upper file as they recursively
|
||||
// include other elements. Putting these elements elsewhere would cause
|
||||
// typescript circular dependency errors as the types need to be both included
|
||||
// in the master pictureElementSchema, but also refer to it internally.
|
||||
//
|
||||
// Provide a manual type definition to avoid the `any` that would be created by
|
||||
// the lazy() evaluation below.
|
||||
// See: https://zod.dev/?id=recursive-types
|
||||
|
||||
// https://www.home-assistant.io/lovelace/picture-elements/#image-element
|
||||
type Conditional = {
|
||||
type: 'conditional';
|
||||
conditions: StockCondition[];
|
||||
elements?: PictureElements;
|
||||
};
|
||||
export const conditionalSchema: z.ZodSchema<Conditional, z.ZodTypeDef> = z.object({
|
||||
type: z.literal('conditional'),
|
||||
conditions: stockConditionSchema.array(),
|
||||
elements: z.lazy(() => pictureElementsSchema),
|
||||
});
|
||||
|
||||
export type AdvancedCameraCardConditional = {
|
||||
type: 'custom:advanced-camera-card-conditional';
|
||||
conditions: AdvancedCameraCardCondition[];
|
||||
elements?: PictureElements;
|
||||
};
|
||||
const advancedCameraCardConditionalSchema: z.ZodSchema<
|
||||
AdvancedCameraCardConditional,
|
||||
z.ZodTypeDef
|
||||
> = z.object({
|
||||
type: z.literal('custom:advanced-camera-card-conditional'),
|
||||
conditions: advancedCameraCardConditionSchema.array(),
|
||||
elements: z.lazy(() => pictureElementsSchema),
|
||||
});
|
||||
|
||||
// Cannot use discriminatedUnion since customSchema uses a superRefine, which
|
||||
// causes false rejections.
|
||||
const pictureElementSchema = z.union([
|
||||
conditionalSchema,
|
||||
customSchema,
|
||||
advancedCameraCardConditionalSchema,
|
||||
iconSchema,
|
||||
imageSchema,
|
||||
menuIconSchema,
|
||||
menuStateIconSchema,
|
||||
menuSubmenuSchema,
|
||||
menuSubmenuSelectSchema,
|
||||
serviceCallButtonSchema,
|
||||
stateBadgeIconSchema,
|
||||
stateIconSchema,
|
||||
stateLabelSchema,
|
||||
statusBarIconItemSchema,
|
||||
statusBarImageItemSchema,
|
||||
statusBarStringItemSchema,
|
||||
]);
|
||||
|
||||
export const pictureElementsSchema = pictureElementSchema.array().optional();
|
||||
export type PictureElements = z.infer<typeof pictureElementsSchema>;
|
||||
Reference in New Issue
Block a user