From 7bb7838b30703e41b8aae9fa39f9961ccb216bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=BCnzel?= <simonk@fsmpi.rwth-aachen.de> Date: Thu, 17 Apr 2025 01:40:32 +0200 Subject: [PATCH] Add JsonEditor --- src/videoag/api/types.ts | 2 +- src/videoag/form/TypeEditor.tsx | 80 +++++++++++++++++++ .../object_management/OMFieldEditor.tsx | 11 ++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/videoag/api/types.ts b/src/videoag/api/types.ts index 80568bd..ac052d6 100644 --- a/src/videoag/api/types.ts +++ b/src/videoag/api/types.ts @@ -166,7 +166,7 @@ export interface field_description { has_default_value: boolean; default_value?: any; - // type: object, datetime + // type: object, datetime, media_process may_be_null?: boolean; // type: enum enums?: string[]; diff --git a/src/videoag/form/TypeEditor.tsx b/src/videoag/form/TypeEditor.tsx index 60861d3..40ab15c 100644 --- a/src/videoag/form/TypeEditor.tsx +++ b/src/videoag/form/TypeEditor.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import type { int } from "@/videoag/api/types"; import { useLanguage } from "@/videoag/localization/LanguageProvider"; +import { deepEquals } from "@/videoag/miscellaneous/Util"; export type EditorArgs = { value?: any; @@ -348,3 +349,82 @@ export function ChooserEditor({ </select> ); } + +export function JsonEditor({ + value, + updateValue, + disabled, + autoFocus, + hideErrors, + allowUndefined, + undefinedAsNull, +}: EditorArgs & { + allowUndefined?: boolean; + undefinedAsNull?: boolean; +}) { + // We need to store the default value locally to enable smooth editing. If the input is partially invalid, we just + // get nothing and setting that back as the value will cause the whole input to reset + const knownValue = useRef<any>(undefined); + const [stringValue, setStringValue] = useState<string>(""); + + useEffect(() => { + if (deepEquals(value, knownValue.current)) return; + //Value was changed not by us, update internal state + knownValue.current = value; + + let normValue = value; + if (undefinedAsNull && normValue === null) { + normValue = undefined; + } + if (allowUndefined && normValue === undefined) { + setStringValue(""); + } else { + setStringValue(JSON.stringify(normValue, null, " ")); + } + }, [knownValue, value, allowUndefined, undefinedAsNull]); + + const getJsonParseResults = (stringValue: string) => { + let json; + let isValid; + let errorMsg = undefined; + if (stringValue === "" && allowUndefined === true) { + isValid = true; + json = undefinedAsNull ? null : undefined; + } else { + try { + json = JSON.parse(stringValue); + isValid = true; + } catch (e: any) { + json = undefined; + isValid = false; + errorMsg = e.toString(); + } + } + return [json, isValid, errorMsg]; + }; + const update = (val: any, affectNow: boolean) => { + setStringValue(val); + const [json, isValid, errorMsg] = getJsonParseResults(val); + knownValue.current = json; + updateValue(json, isValid, affectNow); + }; + const [json, isValid, errorMsg] = getJsonParseResults(stringValue); + + return ( + <div className="d-inline-grid w-100"> + <textarea + className={ + "form-control w-100 " + (isValid || hideErrors === true ? "" : "is-invalid") + } + style={{ minHeight: "6em" }} + value={stringValue ?? ""} + onChange={(e) => update(e.target.value, false)} + disabled={disabled} + autoFocus={autoFocus} + /> + {errorMsg && ( + <span className="d-inline invalid-feedback">Invalid JSON: {errorMsg}</span> + )} + </div> + ); +} diff --git a/src/videoag/object_management/OMFieldEditor.tsx b/src/videoag/object_management/OMFieldEditor.tsx index 96ce052..0b45fec 100644 --- a/src/videoag/object_management/OMFieldEditor.tsx +++ b/src/videoag/object_management/OMFieldEditor.tsx @@ -8,6 +8,7 @@ import { IntEditor, DatetimeEditor, ChooserEditor, + JsonEditor, } from "@/videoag/form/TypeEditor"; import { UserIdListEditor } from "./ObjectEditor"; @@ -73,7 +74,15 @@ export function OMFieldEditor({ </span> ); - // TODO media_process + case "media_process": + return ( + <JsonEditor + {...args} + allowUndefined={description.may_be_null} + undefinedAsNull={true} + /> + ); + // TODO object default: -- GitLab