diff --git a/packages/open-workflow-diagram-editor/src/core/taskDetails.ts b/packages/open-workflow-diagram-editor/src/core/taskDetails.ts index 1e849281..93f5c554 100644 --- a/packages/open-workflow-diagram-editor/src/core/taskDetails.ts +++ b/packages/open-workflow-diagram-editor/src/core/taskDetails.ts @@ -24,7 +24,11 @@ const MAX_DEPTH = 4; /* Flattened task row - kind: how the view should render it */ export type DetailField = - | { path: string; kind: "text"; display: string } + | { path: string; kind: "scalar"; value: string | number | boolean } + | { path: string; kind: "enum"; value: string; options: string[] } + | { path: string; kind: "runtime-expression"; value: string } + | { path: string; kind: "duration"; value: string } + | { path: string; kind: "long-string"; value: string } | { path: string; kind: "array"; count: number } | { path: string; kind: "object" }; @@ -32,6 +36,26 @@ function isPlainObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } +function isLongStringField(path: string): boolean { + return path === "run.shell.command" || path === "run.script.code"; +} + +function isRuntimeExpressionField(path: string): boolean { + return path === "if"; +} + +function isDurationField(path: string): boolean { + return path === "timeout" || path === "timeout.after"; +} + +const ENUM_FIELDS: Record = { + "with.output": ["raw", "content", "response"], +}; + +function getEnumOptions(path: string): string[] | undefined { + return ENUM_FIELDS[path]; +} + function flattenFields( value: unknown, path: string = "", @@ -57,9 +81,58 @@ function flattenFields( for (const [key, val] of Object.entries(value)) { flattenFields(val, path ? `${path}.${key}` : key, depth + 1, outputFields); } + + return; + } + + if (typeof value === "string" && isLongStringField(path)) { + outputFields.push({ + path, + kind: "long-string", + value, + }); return; } - outputFields.push({ path, kind: "text", display: String(value) }); + + if (typeof value === "string" && isRuntimeExpressionField(path)) { + outputFields.push({ + path, + kind: "runtime-expression", + value, + }); + return; + } + + if (typeof value === "string" && isDurationField(path)) { + outputFields.push({ + path, + kind: "duration", + value, + }); + return; + } + + if (typeof value === "string") { + const options = getEnumOptions(path); + + if (options) { + outputFields.push({ + path, + kind: "enum", + value, + options, + }); + return; + } + } + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + outputFields.push({ + path, + kind: "scalar", + value, + }); + } } /* Builds the flattened detail rows for a task: task-specific fields first, inherited base fields last */ diff --git a/packages/open-workflow-diagram-editor/src/side-panel/Fields.tsx b/packages/open-workflow-diagram-editor/src/side-panel/Fields.tsx index debb04a8..d5419248 100644 --- a/packages/open-workflow-diagram-editor/src/side-panel/Fields.tsx +++ b/packages/open-workflow-diagram-editor/src/side-panel/Fields.tsx @@ -14,6 +14,24 @@ * limitations under the License. */ +import { useEffect, useRef } from "react"; +import type { ReactNode } from "react"; +import type { DetailField } from "@/core/taskDetails"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Switch } from "@/components/ui/switch"; +import { + Combobox, + ComboboxContent, + ComboboxItem, + ComboboxTrigger, + ComboboxList, + ComboboxValue, +} from "@/components/ui/combobox"; + +const ISO_8601_DURATION_REGEX = + /^P(?=\d|T)(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?=\d)(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/; + export function SectionHeader({ label }: { label: string }) { return (
@@ -32,11 +50,88 @@ export function InlineField({ label, value }: { label: string; value: string }) ); } -export function PropertyField({ label, value }: { label: string; value: string }) { +function AutoGrowTextarea({ value, disabled }: { value: string; disabled: boolean }) { + const textareaRef = useRef(null); + + useEffect(() => { + const textarea = textareaRef.current; + + if (!textarea) { + return; + } + + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + }, [value]); + + return