mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-22 08:08:22 +03:00
fix validation for schema objects that can be null
This commit is contained in:
parent
55c6c50c97
commit
f7cc87e8ce
@ -16,6 +16,10 @@ export function TextWidget(props: WidgetProps) {
|
|||||||
options,
|
options,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const isNullable = Array.isArray(schema.type)
|
||||||
|
? schema.type.includes("null")
|
||||||
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
@ -24,7 +28,13 @@ export function TextWidget(props: WidgetProps) {
|
|||||||
disabled={disabled || readonly}
|
disabled={disabled || readonly}
|
||||||
placeholder={placeholder || (options.placeholder as string) || ""}
|
placeholder={placeholder || (options.placeholder as string) || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(e.target.value === "" ? undefined : e.target.value)
|
onChange(
|
||||||
|
e.target.value === ""
|
||||||
|
? isNullable
|
||||||
|
? null
|
||||||
|
: undefined
|
||||||
|
: e.target.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onBlur={(e) => onBlur(id, e.target.value)}
|
onBlur={(e) => onBlur(id, e.target.value)}
|
||||||
onFocus={(e) => onFocus(id, e.target.value)}
|
onFocus={(e) => onFocus(id, e.target.value)}
|
||||||
|
|||||||
@ -16,6 +16,10 @@ export function TextareaWidget(props: WidgetProps) {
|
|||||||
options,
|
options,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const isNullable = Array.isArray(schema.type)
|
||||||
|
? schema.type.includes("null")
|
||||||
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Textarea
|
<Textarea
|
||||||
id={id}
|
id={id}
|
||||||
@ -24,7 +28,13 @@ export function TextareaWidget(props: WidgetProps) {
|
|||||||
placeholder={placeholder || (options.placeholder as string) || ""}
|
placeholder={placeholder || (options.placeholder as string) || ""}
|
||||||
rows={(options.rows as number) || 3}
|
rows={(options.rows as number) || 3}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(e.target.value === "" ? undefined : e.target.value)
|
onChange(
|
||||||
|
e.target.value === ""
|
||||||
|
? isNullable
|
||||||
|
? null
|
||||||
|
: undefined
|
||||||
|
: e.target.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onBlur={(e) => onBlur(id, e.target.value)}
|
onBlur={(e) => onBlur(id, e.target.value)}
|
||||||
onFocus={(e) => onFocus(id, e.target.value)}
|
onFocus={(e) => onFocus(id, e.target.value)}
|
||||||
|
|||||||
@ -30,6 +30,14 @@ function isSchemaObject(
|
|||||||
return typeof schema === "object" && schema !== null;
|
return typeof schema === "object" && schema !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function schemaHasType(schema: Record<string, unknown>, type: string): boolean {
|
||||||
|
const schemaType = schema.type;
|
||||||
|
if (Array.isArray(schemaType)) {
|
||||||
|
return schemaType.includes(type);
|
||||||
|
}
|
||||||
|
return schemaType === type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes nullable schemas by unwrapping anyOf/oneOf [Type, null] patterns.
|
* Normalizes nullable schemas by unwrapping anyOf/oneOf [Type, null] patterns.
|
||||||
*
|
*
|
||||||
@ -57,6 +65,15 @@ function normalizeNullableSchema(schema: RJSFSchema): RJSFSchema {
|
|||||||
|
|
||||||
const schemaObj = schema as Record<string, unknown>;
|
const schemaObj = schema as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (
|
||||||
|
schemaObj.default === null &&
|
||||||
|
schemaObj.type &&
|
||||||
|
!Array.isArray(schemaObj.type) &&
|
||||||
|
schemaObj.type !== "null"
|
||||||
|
) {
|
||||||
|
schemaObj.type = [schemaObj.type, "null"];
|
||||||
|
}
|
||||||
|
|
||||||
const anyOf = schemaObj.anyOf;
|
const anyOf = schemaObj.anyOf;
|
||||||
if (Array.isArray(anyOf)) {
|
if (Array.isArray(anyOf)) {
|
||||||
const hasNull = anyOf.some(
|
const hasNull = anyOf.some(
|
||||||
@ -71,8 +88,20 @@ function normalizeNullableSchema(schema: RJSFSchema): RJSFSchema {
|
|||||||
) as RJSFSchema | undefined;
|
) as RJSFSchema | undefined;
|
||||||
|
|
||||||
if (hasNull && nonNull && anyOf.length === 2) {
|
if (hasNull && nonNull && anyOf.length === 2) {
|
||||||
|
const normalizedNonNull = normalizeNullableSchema(nonNull as RJSFSchema);
|
||||||
|
const normalizedNonNullObj = normalizedNonNull as Record<string, unknown>;
|
||||||
|
const nonNullType = normalizedNonNullObj.type;
|
||||||
|
const mergedType = Array.isArray(nonNullType)
|
||||||
|
? Array.from(new Set([...nonNullType, "null"]))
|
||||||
|
: nonNullType
|
||||||
|
? [nonNullType, "null"]
|
||||||
|
: ["null"];
|
||||||
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schemaObj;
|
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schemaObj;
|
||||||
return normalizeNullableSchema({ ...nonNull, ...rest } as RJSFSchema);
|
return {
|
||||||
|
...rest,
|
||||||
|
...normalizedNonNullObj,
|
||||||
|
type: mergedType,
|
||||||
|
} as RJSFSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -97,8 +126,20 @@ function normalizeNullableSchema(schema: RJSFSchema): RJSFSchema {
|
|||||||
) as RJSFSchema | undefined;
|
) as RJSFSchema | undefined;
|
||||||
|
|
||||||
if (hasNull && nonNull && oneOf.length === 2) {
|
if (hasNull && nonNull && oneOf.length === 2) {
|
||||||
|
const normalizedNonNull = normalizeNullableSchema(nonNull as RJSFSchema);
|
||||||
|
const normalizedNonNullObj = normalizedNonNull as Record<string, unknown>;
|
||||||
|
const nonNullType = normalizedNonNullObj.type;
|
||||||
|
const mergedType = Array.isArray(nonNullType)
|
||||||
|
? Array.from(new Set([...nonNullType, "null"]))
|
||||||
|
: nonNullType
|
||||||
|
? [nonNullType, "null"]
|
||||||
|
: ["null"];
|
||||||
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schemaObj;
|
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schemaObj;
|
||||||
return normalizeNullableSchema({ ...nonNull, ...rest } as RJSFSchema);
|
return {
|
||||||
|
...rest,
|
||||||
|
...normalizedNonNullObj,
|
||||||
|
type: mergedType,
|
||||||
|
} as RJSFSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -324,7 +365,7 @@ function getWidgetForField(
|
|||||||
// Color fields
|
// Color fields
|
||||||
if (
|
if (
|
||||||
fieldName.toLowerCase().includes("color") &&
|
fieldName.toLowerCase().includes("color") &&
|
||||||
schemaObj.type === "object"
|
schemaHasType(schemaObj, "object")
|
||||||
) {
|
) {
|
||||||
return "color";
|
return "color";
|
||||||
}
|
}
|
||||||
@ -335,13 +376,14 @@ function getWidgetForField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Boolean fields get switch widget
|
// Boolean fields get switch widget
|
||||||
if (schemaObj.type === "boolean") {
|
if (schemaHasType(schemaObj, "boolean")) {
|
||||||
return "switch";
|
return "switch";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number with range gets slider
|
// Number with range gets slider
|
||||||
if (
|
if (
|
||||||
(schemaObj.type === "number" || schemaObj.type === "integer") &&
|
(schemaHasType(schemaObj, "number") ||
|
||||||
|
schemaHasType(schemaObj, "integer")) &&
|
||||||
schemaObj.minimum !== undefined &&
|
schemaObj.minimum !== undefined &&
|
||||||
schemaObj.maximum !== undefined
|
schemaObj.maximum !== undefined
|
||||||
) {
|
) {
|
||||||
@ -350,7 +392,7 @@ function getWidgetForField(
|
|||||||
|
|
||||||
// Array of strings gets tags widget
|
// Array of strings gets tags widget
|
||||||
if (
|
if (
|
||||||
schemaObj.type === "array" &&
|
schemaHasType(schemaObj, "array") &&
|
||||||
isSchemaObject(schemaObj.items) &&
|
isSchemaObject(schemaObj.items) &&
|
||||||
(schemaObj.items as Record<string, unknown>).type === "string"
|
(schemaObj.items as Record<string, unknown>).type === "string"
|
||||||
) {
|
) {
|
||||||
@ -426,7 +468,10 @@ function generateUiSchema(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle nested objects recursively
|
// Handle nested objects recursively
|
||||||
if (fSchema.type === "object" && isSchemaObject(fSchema.properties)) {
|
if (
|
||||||
|
schemaHasType(fSchema, "object") &&
|
||||||
|
isSchemaObject(fSchema.properties)
|
||||||
|
) {
|
||||||
const nestedOptions: UiSchemaOptions = {
|
const nestedOptions: UiSchemaOptions = {
|
||||||
hiddenFields: hiddenFields
|
hiddenFields: hiddenFields
|
||||||
.filter((f) => f.startsWith(`${fieldName}.`))
|
.filter((f) => f.startsWith(`${fieldName}.`))
|
||||||
@ -561,7 +606,7 @@ export function applySchemaDefaults(
|
|||||||
) {
|
) {
|
||||||
result[key] = propSchema.default;
|
result[key] = propSchema.default;
|
||||||
} else if (
|
} else if (
|
||||||
propSchema.type === "object" &&
|
schemaHasType(propSchema, "object") &&
|
||||||
isSchemaObject(propSchema.properties) &&
|
isSchemaObject(propSchema.properties) &&
|
||||||
result[key] !== undefined
|
result[key] !== undefined
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Global Configuration View
|
// Global Configuration View
|
||||||
// Main view for configuring global Frigate settings
|
// Main view for configuring global Frigate settings
|
||||||
|
|
||||||
import { useMemo, useCallback, useState, useEffect, memo, useRef } from "react";
|
import { useMemo, useCallback, useState, useEffect, useRef } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -386,7 +386,7 @@ interface GlobalConfigSectionProps {
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalConfigSection = memo(function GlobalConfigSection({
|
function GlobalConfigSection({
|
||||||
sectionKey,
|
sectionKey,
|
||||||
schema,
|
schema,
|
||||||
config,
|
config,
|
||||||
@ -546,7 +546,7 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
export default function GlobalConfigView() {
|
export default function GlobalConfigView() {
|
||||||
const { t } = useTranslation([
|
const { t } = useTranslation([
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user