import { useMemo, useState } from "react"; import { parse as parseYaml } from "yaml"; import { useHttpAuths, useHttpPages, useScripts, useWorkflows } from "../../api/hooks.js"; import { dataFromInputMeta, firstInputMeta } from "../../lib/script.js"; import { workflowToFlowchart } from "../../lib/workflow-mermaid.js"; import { parseWorkflowYaml, stringifyWorkflowDoc } from "../../lib/workflow-doc.js"; import { ScriptsTab } from "./ScriptsTab.jsx"; import { TriggersTab } from "./TriggersTab.jsx"; import { YamlTab } from "./YamlTab.jsx"; import { WorkflowTestPanel } from "./WorkflowTestPanel.jsx"; export function useYamlPreview(content) { return useMemo(() => { try { const parsedYaml = parseYaml(content); return { parsed: parsedYaml, parseError: null, mermaid: workflowToFlowchart(parsedYaml), }; } catch (err) { return { parsed: null, parseError: err instanceof Error ? err.message : String(err), mermaid: { chart: "", scriptIds: {} }, }; } }, [content]); } export function WorkflowVisualEditor({ yaml, onYamlChange, owner, file, extraChrome, showTest, savedYaml, testOpen, onTestClose, }) { const [tab, setTab] = useState("scripts"); const [lastEdited, setLastEdited] = useState("yaml"); const [doc, setDoc] = useState(() => parseWorkflowYaml(yaml).doc); const { parsed, parseError, mermaid } = useYamlPreview(yaml); const parsedDoc = useMemo(() => parseWorkflowYaml(yaml), [yaml]); const displayDoc = lastEdited === "visual" && doc ? doc : parsedDoc.doc; const visualDisabled = !displayDoc; const { data: scripts = [] } = useScripts(); const { data: workflows = [] } = useWorkflows(owner || undefined); const { data: auths = [] } = useHttpAuths(); const { data: allPages = [] } = useHttpPages(); const pages = allPages.filter((p) => p.kind !== "template"); const scriptCount = displayDoc?.scripts?.length ?? 0; const triggerCount = displayDoc?.triggers?.length ?? 0; const unsaved = savedYaml != null && yaml !== savedYaml; function patchDoc(mutator) { const base = lastEdited === "visual" && doc ? doc : parsedDoc.doc; if (!base) return; const next = structuredClone(base); mutator(next); setDoc(next); onYamlChange(stringifyWorkflowDoc(next)); setLastEdited("visual"); } function onYamlTabChange(value) { setLastEdited("yaml"); onYamlChange(value); } function selectTab(next) { if (next !== "yaml" && lastEdited === "yaml") { const { doc: d } = parseWorkflowYaml(yaml); if (d) setDoc(d); } setTab(next); } const scriptsByName = useMemo(() => { const map = new Map(); for (const s of scripts) { const name = typeof s === "string" ? s : s.name; if (name) map.set(name, s); } return map; }, [scripts]); const inputMeta = useMemo( () => firstInputMeta(displayDoc?.scripts, scriptsByName), [displayDoc?.scripts, scriptsByName], ); const defaultData = useMemo(() => { const yamlData = displayDoc?.extra?.data; return dataFromInputMeta(inputMeta, yamlData); }, [displayDoc?.extra?.data, inputMeta]); const inputFields = inputMeta?.input; let testDisabledReason = null; if (visualDisabled) testDisabledReason = "Fix YAML before running."; else if (unsaved) testDisabledReason = "Save the workflow before running. Test uses the saved YAML."; return ( <>
{extraChrome} patchDoc((d) => { d.name = e.target.value; }) } />