|
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