refactor: Break apart monolithic configuration schema (#1977)
[skip ci]
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { z } from 'zod';
|
||||
import { cardIDRegex } from '../common/const';
|
||||
|
||||
// https://www.home-assistant.io/dashboards/actions/#options-for-confirmation
|
||||
export const actionBaseSchema = z.object({
|
||||
confirmation: z
|
||||
.boolean()
|
||||
.or(
|
||||
z.object({
|
||||
text: z.string().optional(),
|
||||
exemptions: z
|
||||
.object({
|
||||
user: z.string(),
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
card_id: z
|
||||
.string()
|
||||
.regex(cardIDRegex, 'card_id parameter can only contain [a-z][A-Z][0-9_]-')
|
||||
.optional(),
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const advancedCameraCardCustomActionsBaseSchema = actionBaseSchema.extend({
|
||||
action: z
|
||||
.literal('fire-dom-event')
|
||||
.or(
|
||||
z
|
||||
.literal('custom:advanced-camera-card-action')
|
||||
.transform((): 'fire-dom-event' => 'fire-dom-event'),
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const cameraSelectActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('camera_select'),
|
||||
camera: z.string().optional(),
|
||||
triggered: z.boolean().optional(),
|
||||
});
|
||||
export type CameraSelectActionConfig = z.infer<typeof cameraSelectActionConfigSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { viewDisplayModeSchema } from '../../common/display';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const viewDisplayModeActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('display_mode_select'),
|
||||
display_mode: viewDisplayModeSchema,
|
||||
});
|
||||
export type DisplayModeActionConfig = z.infer<typeof viewDisplayModeActionConfigSchema>;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
const GENERAL_ACTIONS = [
|
||||
'camera_ui',
|
||||
'default',
|
||||
'download',
|
||||
'expand',
|
||||
'fullscreen',
|
||||
'live_substream_off',
|
||||
'live_substream_on',
|
||||
'menu_toggle',
|
||||
'microphone_connect',
|
||||
'microphone_disconnect',
|
||||
'microphone_mute',
|
||||
'microphone_unmute',
|
||||
'mute',
|
||||
'pause',
|
||||
'play',
|
||||
'screenshot',
|
||||
'unmute',
|
||||
] as const;
|
||||
export type AdvancedCameraCardGeneralAction = (typeof GENERAL_ACTIONS)[number];
|
||||
|
||||
export const generalActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.enum(GENERAL_ACTIONS),
|
||||
});
|
||||
export type GeneralActionConfig = z.infer<typeof generalActionConfigSchema>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
// An action that can be used internally to call a callback (it is not possible
|
||||
// for the user to pass this through via the configuration).
|
||||
export const INTERNAL_CALLBACK_ACTION = '__INTERNAL_CALLBACK_ACTION__';
|
||||
export const internalCallbackActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal(INTERNAL_CALLBACK_ACTION),
|
||||
|
||||
// The callback is expected to be called with a CardController API object.
|
||||
callback: z.function().args(z.any()).returns(z.promise(z.void())),
|
||||
});
|
||||
export type InternalCallbackActionConfig = z.infer<
|
||||
typeof internalCallbackActionConfigSchema
|
||||
>;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
const LOG_ACTIONS_LEVELS = ['debug', 'info', 'warn', 'error'] as const;
|
||||
export type LogActionLevel = (typeof LOG_ACTIONS_LEVELS)[number];
|
||||
|
||||
export const logActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('log'),
|
||||
message: z.string(),
|
||||
level: z.enum(LOG_ACTIONS_LEVELS).default('info'),
|
||||
});
|
||||
export type LogActionConfig = z.infer<typeof logActionConfigSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const mediaPlayerActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('media_player'),
|
||||
media_player: z.string(),
|
||||
media_player_action: z.enum(['play', 'stop']),
|
||||
});
|
||||
export type MediaPlayerActionConfig = z.infer<typeof mediaPlayerActionConfigSchema>;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const ptzControlsActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('ptz_controls'),
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
export type PTZControlsActionConfig = z.infer<typeof ptzControlsActionConfigSchema>;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import { panSchema } from '../../common/pan';
|
||||
import { zoomSchema } from '../../common/zoom';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
import { PTZ_ACTION_PHASES, PTZ_ACTIONS } from './ptz';
|
||||
|
||||
export const ptzDigitalActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('ptz_digital'),
|
||||
target_id: z.string().optional(),
|
||||
absolute: z
|
||||
.object({
|
||||
zoom: zoomSchema.optional(),
|
||||
pan: panSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
ptz_action: z.enum(PTZ_ACTIONS).optional(),
|
||||
ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(),
|
||||
});
|
||||
export type PTZDigitialActionConfig = z.infer<typeof ptzDigitalActionConfigSchema>;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
import { PTZ_ACTION_PHASES, PTZ_ACTIONS } from './ptz';
|
||||
|
||||
export const ptzMultiActionSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('ptz_multi'),
|
||||
target_id: z.string().optional(),
|
||||
|
||||
ptz_action: z.enum(PTZ_ACTIONS).optional(),
|
||||
ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(),
|
||||
ptz_preset: z.string().optional(),
|
||||
});
|
||||
export type PTZMultiActionConfig = z.infer<typeof ptzMultiActionSchema>;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
const PTZ_PAN_TILT_ACTIONS = ['left', 'right', 'up', 'down'] as const;
|
||||
const PTZ_ZOOM_ACTIONS = ['zoom_in', 'zoom_out'] as const;
|
||||
const PTZ_BASE_ACTIONS = [...PTZ_PAN_TILT_ACTIONS, ...PTZ_ZOOM_ACTIONS] as const;
|
||||
export type PTZBaseAction = (typeof PTZ_BASE_ACTIONS)[number];
|
||||
|
||||
// PTZ actions as used by the PTZ control (includes a 'home' button).
|
||||
const PTZ_CONTROL_ACTIONS = [...PTZ_BASE_ACTIONS, 'home'] as const;
|
||||
export type PTZControlAction = (typeof PTZ_CONTROL_ACTIONS)[number];
|
||||
|
||||
// PTZ actions as used by the camera manager (includes generic presets).
|
||||
export const PTZ_ACTIONS = [...PTZ_BASE_ACTIONS, 'preset'] as const;
|
||||
export type PTZAction = (typeof PTZ_ACTIONS)[number];
|
||||
|
||||
export const PTZ_ACTION_PHASES = ['start', 'stop'] as const;
|
||||
export type PTZActionPhase = (typeof PTZ_ACTION_PHASES)[number];
|
||||
|
||||
export const ptzActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('ptz'),
|
||||
camera: z.string().optional(),
|
||||
ptz_action: z.enum(PTZ_ACTIONS).optional(),
|
||||
ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(),
|
||||
ptz_preset: z.string().optional(),
|
||||
});
|
||||
export type PTZActionConfig = z.infer<typeof ptzActionConfigSchema>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
const timeDeltaSchema = z.object({
|
||||
ms: z.number().optional(),
|
||||
s: z.number().optional(),
|
||||
m: z.number().optional(),
|
||||
h: z.number().optional(),
|
||||
});
|
||||
export type TimeDelta = z.infer<typeof timeDeltaSchema>;
|
||||
|
||||
export const sleepActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('sleep'),
|
||||
duration: timeDeltaSchema.optional().default({ s: 1 }),
|
||||
});
|
||||
export type SleepActionConfig = z.infer<typeof sleepActionConfigSchema>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const substreamSelectActionConfigSchema =
|
||||
advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('live_substream_select'),
|
||||
camera: z.string(),
|
||||
});
|
||||
export type SubstreamSelectActionConfig = z.infer<
|
||||
typeof substreamSelectActionConfigSchema
|
||||
>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { VIEWS_USER_SPECIFIED } from '../../common/const';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './base';
|
||||
|
||||
export const viewActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.enum(VIEWS_USER_SPECIFIED),
|
||||
});
|
||||
export type ViewActionConfig = z.infer<typeof viewActionConfigSchema>;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
import { targetSchema } from './target';
|
||||
|
||||
// Note: call-service is deprecated and will eventually go away. Please use
|
||||
// perform-action instead.
|
||||
// See: https://www.home-assistant.io/blog/2024/08/07/release-20248/#goodbye-service-calls-hello-actions-
|
||||
|
||||
export const callServiceActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('call-service'),
|
||||
service: z.string(),
|
||||
data: z.object({}).passthrough().optional(),
|
||||
target: targetSchema.optional(),
|
||||
});
|
||||
export type CallServiceActionConfig = z.infer<typeof callServiceActionSchema>;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const customActionSchema = actionBaseSchema
|
||||
.extend({
|
||||
action: z.literal('fire-dom-event'),
|
||||
})
|
||||
.passthrough();
|
||||
export type CustomActionConfig = z.infer<typeof customActionSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const moreInfoActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('more-info'),
|
||||
entity: z.string().optional(),
|
||||
});
|
||||
export type MoreInfoActionConfig = z.infer<typeof moreInfoActionSchema>;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const navigateActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('navigate'),
|
||||
navigation_path: z.string(),
|
||||
navigation_replace: z.boolean().optional(),
|
||||
});
|
||||
export type NavigateActionConfig = z.infer<typeof navigateActionSchema>;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const noneActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('none'),
|
||||
});
|
||||
export type NoneActionConfig = z.infer<typeof noneActionSchema>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
import { targetSchema } from './target';
|
||||
|
||||
export const performActionActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('perform-action'),
|
||||
perform_action: z.string(),
|
||||
data: z.object({}).passthrough().optional(),
|
||||
target: targetSchema.optional(),
|
||||
});
|
||||
export type PerformActionActionConfig = z.infer<typeof performActionActionSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { HassServiceTarget } from 'home-assistant-js-websocket';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const targetSchema: z.ZodSchema<HassServiceTarget, z.ZodTypeDef> = z.object({
|
||||
entity_id: z.string().or(z.string().array()).optional(),
|
||||
device_id: z.string().or(z.string().array()).optional(),
|
||||
area_id: z.string().or(z.string().array()).optional(),
|
||||
floor_id: z.string().or(z.string().array()).optional(),
|
||||
label_id: z.string().or(z.string().array()).optional(),
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const toggleActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('toggle'),
|
||||
});
|
||||
export type ToggleActionConfig = z.infer<typeof toggleActionSchema>;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import { callServiceActionSchema } from './call-service';
|
||||
import { customActionSchema } from './custom';
|
||||
import { moreInfoActionSchema } from './more-info';
|
||||
import { navigateActionSchema } from './navigate';
|
||||
import { noneActionSchema } from './none';
|
||||
import { performActionActionSchema } from './perform-action';
|
||||
import { toggleActionSchema } from './toggle';
|
||||
import { urlActionSchema } from './url';
|
||||
|
||||
export const stockActionSchema = z.union([
|
||||
callServiceActionSchema,
|
||||
customActionSchema,
|
||||
moreInfoActionSchema,
|
||||
navigateActionSchema,
|
||||
noneActionSchema,
|
||||
performActionActionSchema,
|
||||
toggleActionSchema,
|
||||
urlActionSchema,
|
||||
]);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
import { actionBaseSchema } from '../base';
|
||||
|
||||
export const urlActionSchema = actionBaseSchema.extend({
|
||||
action: z.literal('url'),
|
||||
url_path: z.string(),
|
||||
});
|
||||
export type URLActionConfig = z.infer<typeof urlActionSchema>;
|
||||
@@ -0,0 +1,133 @@
|
||||
import { z } from 'zod';
|
||||
import { statusBarItemBaseSchema } from '../common/status-bar';
|
||||
import { advancedCameraCardCustomActionsBaseSchema } from './custom/base';
|
||||
import { cameraSelectActionConfigSchema } from './custom/camera-select';
|
||||
import { viewDisplayModeActionConfigSchema } from './custom/display-mode';
|
||||
import { generalActionConfigSchema } from './custom/general';
|
||||
import { internalCallbackActionConfigSchema } from './custom/internal';
|
||||
import { logActionConfigSchema } from './custom/log';
|
||||
import { mediaPlayerActionConfigSchema } from './custom/media-player';
|
||||
import { ptzActionConfigSchema } from './custom/ptz';
|
||||
import { ptzControlsActionConfigSchema } from './custom/ptz-controls';
|
||||
import { ptzDigitalActionConfigSchema } from './custom/ptz-digital';
|
||||
import { ptzMultiActionSchema } from './custom/ptz-multi';
|
||||
import { sleepActionConfigSchema } from './custom/sleep';
|
||||
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.
|
||||
// See: https://zod.dev/?id=recursive-types
|
||||
export type StatusBarActionConfig = z.infer<
|
||||
typeof advancedCameraCardCustomActionsBaseSchema
|
||||
> & {
|
||||
advanced_camera_card_action: 'status_bar';
|
||||
status_bar_action: 'add' | 'remove' | 'reset';
|
||||
items?: StatusBarItem[];
|
||||
};
|
||||
export const statusBarActionConfigSchema: z.ZodSchema<
|
||||
StatusBarActionConfig,
|
||||
z.ZodTypeDef,
|
||||
unknown
|
||||
> = advancedCameraCardCustomActionsBaseSchema.extend({
|
||||
advanced_camera_card_action: z.literal('status_bar'),
|
||||
status_bar_action: z.enum(['add', 'remove', 'reset']),
|
||||
items: z
|
||||
.lazy(() => statusBarItemSchema)
|
||||
.array()
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const advancedCameraCardCustomActionSchema = z.union([
|
||||
cameraSelectActionConfigSchema,
|
||||
generalActionConfigSchema,
|
||||
internalCallbackActionConfigSchema,
|
||||
logActionConfigSchema,
|
||||
mediaPlayerActionConfigSchema,
|
||||
ptzActionConfigSchema,
|
||||
ptzControlsActionConfigSchema,
|
||||
ptzDigitalActionConfigSchema,
|
||||
ptzMultiActionSchema,
|
||||
sleepActionConfigSchema,
|
||||
statusBarActionConfigSchema,
|
||||
substreamSelectActionConfigSchema,
|
||||
viewActionConfigSchema,
|
||||
viewDisplayModeActionConfigSchema,
|
||||
]);
|
||||
export type AdvancedCameraCardCustomActionConfig = z.infer<
|
||||
typeof advancedCameraCardCustomActionSchema
|
||||
>;
|
||||
|
||||
export const actionConfigSchema = z.union([
|
||||
stockActionSchema,
|
||||
advancedCameraCardCustomActionSchema,
|
||||
]);
|
||||
export type ActionConfig = z.infer<typeof actionConfigSchema>;
|
||||
|
||||
export const actionsBaseSchema = z
|
||||
.object({
|
||||
tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
hold_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
double_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
start_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
end_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(),
|
||||
})
|
||||
// Passthrough to allow (at least) entity/camera_image to go through. This
|
||||
// card doesn't need these attributes, but handleAction() in
|
||||
// custom_card_helpers may depending on how the action is configured.
|
||||
.passthrough();
|
||||
export type Actions = z.infer<typeof actionsBaseSchema>;
|
||||
|
||||
export interface AuxillaryActionConfig {
|
||||
entity?: string;
|
||||
}
|
||||
|
||||
export type ActionsConfig = Actions & AuxillaryActionConfig;
|
||||
|
||||
export const actionsSchema = z.object({
|
||||
actions: actionsBaseSchema.optional(),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Status Bar Elements
|
||||
//
|
||||
// Note: Status Bar action & elements are included in this file, since this is
|
||||
// the only action that may include content that refers to *other* actions (e.g.
|
||||
// a status bar action, compromises of status bar items, which themselves may
|
||||
// have actions). This circular relationship means the components must be
|
||||
// together in a file, or they will generate typescript circular dependency
|
||||
// errors.
|
||||
// ============================================================================
|
||||
|
||||
const statusBarItemElementsBaseSchema = statusBarItemBaseSchema.extend({
|
||||
sufficient: z.boolean().default(false).optional(),
|
||||
exclusive: z.boolean().default(false).optional(),
|
||||
expand: z.boolean().default(false).optional(),
|
||||
actions: actionsBaseSchema.optional(),
|
||||
});
|
||||
|
||||
export const statusBarIconItemSchema = statusBarItemElementsBaseSchema.extend({
|
||||
type: z.literal('custom:advanced-camera-card-status-bar-icon'),
|
||||
icon: z.string(),
|
||||
});
|
||||
export type StatusBarIcon = z.infer<typeof statusBarIconItemSchema>;
|
||||
|
||||
export const statusBarImageItemSchema = statusBarItemElementsBaseSchema.extend({
|
||||
type: z.literal('custom:advanced-camera-card-status-bar-image'),
|
||||
image: z.string(),
|
||||
});
|
||||
export type StatusBarImage = z.infer<typeof statusBarImageItemSchema>;
|
||||
|
||||
export const statusBarStringItemSchema = statusBarItemElementsBaseSchema.extend({
|
||||
type: z.literal('custom:advanced-camera-card-status-bar-string'),
|
||||
string: z.string(),
|
||||
});
|
||||
export type StatusBarString = z.infer<typeof statusBarStringItemSchema>;
|
||||
|
||||
const statusBarItemSchema = z.union([
|
||||
statusBarIconItemSchema,
|
||||
statusBarImageItemSchema,
|
||||
statusBarStringItemSchema,
|
||||
]);
|
||||
export type StatusBarItem = z.infer<typeof statusBarItemSchema>;
|
||||
Reference in New Issue
Block a user