|
1 | 1 | import { useState, useEffect, useCallback, useRef } from "react";
|
2 | 2 | import { Button } from "@/components/ui/button";
|
3 | 3 | import { Input } from "@/components/ui/input";
|
4 |
| -import { Label } from "@/components/ui/label"; |
5 | 4 | import JsonEditor from "./JsonEditor";
|
6 |
| -import { updateValueAtPath, JsonObject } from "@/utils/jsonPathUtils"; |
7 |
| -import { generateDefaultValue, formatFieldLabel } from "@/utils/schemaUtils"; |
| 5 | +import { updateValueAtPath } from "@/utils/jsonPathUtils"; |
| 6 | +import { generateDefaultValue } from "@/utils/schemaUtils"; |
8 | 7 |
|
9 | 8 | export type JsonValue =
|
10 | 9 | | string
|
@@ -36,17 +35,25 @@ interface DynamicJsonFormProps {
|
36 | 35 | value: JsonValue;
|
37 | 36 | onChange: (value: JsonValue) => void;
|
38 | 37 | maxDepth?: number;
|
39 |
| - onlyJSON?: boolean; |
40 | 38 | }
|
41 | 39 |
|
| 40 | +const isSimpleObject = (schema: JsonSchemaType): boolean => { |
| 41 | + const supportedTypes = ["string", "number", "integer", "boolean", "null"]; |
| 42 | + if (supportedTypes.includes(schema.type)) return true; |
| 43 | + if (schema.type !== "object") return false; |
| 44 | + return Object.values(schema.properties ?? {}).every((prop) => |
| 45 | + supportedTypes.includes(prop.type), |
| 46 | + ); |
| 47 | +}; |
| 48 | + |
42 | 49 | const DynamicJsonForm = ({
|
43 | 50 | schema,
|
44 | 51 | value,
|
45 | 52 | onChange,
|
46 | 53 | maxDepth = 3,
|
47 |
| - onlyJSON = false, |
48 | 54 | }: DynamicJsonFormProps) => {
|
49 |
| - const [isJsonMode, setIsJsonMode] = useState(onlyJSON); |
| 55 | + const isOnlyJSON = !isSimpleObject(schema); |
| 56 | + const [isJsonMode, setIsJsonMode] = useState(isOnlyJSON); |
50 | 57 | const [jsonError, setJsonError] = useState<string>();
|
51 | 58 | // Store the raw JSON string to allow immediate feedback during typing
|
52 | 59 | // while deferring parsing until the user stops typing
|
@@ -233,111 +240,6 @@ const DynamicJsonForm = ({
|
233 | 240 | required={propSchema.required}
|
234 | 241 | />
|
235 | 242 | );
|
236 |
| - case "object": { |
237 |
| - // Handle case where we have a value but no schema properties |
238 |
| - const objectValue = (currentValue as JsonObject) || {}; |
239 |
| - |
240 |
| - // If we have schema properties, use them to render fields |
241 |
| - if (propSchema.properties) { |
242 |
| - return ( |
243 |
| - <div className="space-y-4 border rounded-md p-4"> |
244 |
| - {Object.entries(propSchema.properties).map(([key, prop]) => ( |
245 |
| - <div key={key} className="space-y-2"> |
246 |
| - <Label>{formatFieldLabel(key)}</Label> |
247 |
| - {renderFormFields( |
248 |
| - prop, |
249 |
| - objectValue[key], |
250 |
| - [...path, key], |
251 |
| - depth + 1, |
252 |
| - )} |
253 |
| - </div> |
254 |
| - ))} |
255 |
| - </div> |
256 |
| - ); |
257 |
| - } |
258 |
| - // If we have a value but no schema properties, render fields based on the value |
259 |
| - else if (Object.keys(objectValue).length > 0) { |
260 |
| - return ( |
261 |
| - <div className="space-y-4 border rounded-md p-4"> |
262 |
| - {Object.entries(objectValue).map(([key, value]) => ( |
263 |
| - <div key={key} className="space-y-2"> |
264 |
| - <Label>{formatFieldLabel(key)}</Label> |
265 |
| - <Input |
266 |
| - type="text" |
267 |
| - value={String(value)} |
268 |
| - onChange={(e) => |
269 |
| - handleFieldChange([...path, key], e.target.value) |
270 |
| - } |
271 |
| - /> |
272 |
| - </div> |
273 |
| - ))} |
274 |
| - </div> |
275 |
| - ); |
276 |
| - } |
277 |
| - // If we have neither schema properties nor value, return null |
278 |
| - return null; |
279 |
| - } |
280 |
| - case "array": { |
281 |
| - const arrayValue = Array.isArray(currentValue) ? currentValue : []; |
282 |
| - if (!propSchema.items) return null; |
283 |
| - return ( |
284 |
| - <div className="space-y-4"> |
285 |
| - {propSchema.description && ( |
286 |
| - <p className="text-sm text-gray-600">{propSchema.description}</p> |
287 |
| - )} |
288 |
| - |
289 |
| - {propSchema.items?.description && ( |
290 |
| - <p className="text-sm text-gray-500"> |
291 |
| - Items: {propSchema.items.description} |
292 |
| - </p> |
293 |
| - )} |
294 |
| - |
295 |
| - <div className="space-y-2"> |
296 |
| - {arrayValue.map((item, index) => ( |
297 |
| - <div key={index} className="flex items-center gap-2"> |
298 |
| - {renderFormFields( |
299 |
| - propSchema.items as JsonSchemaType, |
300 |
| - item, |
301 |
| - [...path, index.toString()], |
302 |
| - depth + 1, |
303 |
| - )} |
304 |
| - <Button |
305 |
| - variant="outline" |
306 |
| - size="sm" |
307 |
| - onClick={() => { |
308 |
| - const newArray = [...arrayValue]; |
309 |
| - newArray.splice(index, 1); |
310 |
| - handleFieldChange(path, newArray); |
311 |
| - }} |
312 |
| - > |
313 |
| - Remove |
314 |
| - </Button> |
315 |
| - </div> |
316 |
| - ))} |
317 |
| - <Button |
318 |
| - variant="outline" |
319 |
| - size="sm" |
320 |
| - onClick={() => { |
321 |
| - const defaultValue = generateDefaultValue( |
322 |
| - propSchema.items as JsonSchemaType, |
323 |
| - ); |
324 |
| - handleFieldChange(path, [ |
325 |
| - ...arrayValue, |
326 |
| - defaultValue ?? null, |
327 |
| - ]); |
328 |
| - }} |
329 |
| - title={ |
330 |
| - propSchema.items?.description |
331 |
| - ? `Add new ${propSchema.items.description}` |
332 |
| - : "Add new item" |
333 |
| - } |
334 |
| - > |
335 |
| - Add Item |
336 |
| - </Button> |
337 |
| - </div> |
338 |
| - </div> |
339 |
| - ); |
340 |
| - } |
341 | 243 | default:
|
342 | 244 | return null;
|
343 | 245 | }
|
@@ -376,7 +278,7 @@ const DynamicJsonForm = ({
|
376 | 278 | Format JSON
|
377 | 279 | </Button>
|
378 | 280 | )}
|
379 |
| - {!onlyJSON && ( |
| 281 | + {!isOnlyJSON && ( |
380 | 282 | <Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
|
381 | 283 | {isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
382 | 284 | </Button>
|
|
0 commit comments