feat: refactor workflow editor authoring surfaces
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import {
|
||||
BadgeInfo,
|
||||
GitBranch,
|
||||
LayoutGrid,
|
||||
Loader2,
|
||||
MousePointer2,
|
||||
Play,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Save,
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
|
||||
type WorkflowExecutionModeOption = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface WorkflowCanvasToolbarProps {
|
||||
workflowName: string
|
||||
blueprintLabel?: string | null
|
||||
blueprintDescription?: string | null
|
||||
graphFamilyLabel: string
|
||||
graphFamilyClassName: string
|
||||
executionMode: string
|
||||
executionModeLabel: string
|
||||
executionModeClassName: string
|
||||
executionModeHint: string
|
||||
dispatchContextId: string
|
||||
executionModes: WorkflowExecutionModeOption[]
|
||||
selectedEdgeCount: number
|
||||
canAutoLayout: boolean
|
||||
hasValidationErrors: boolean
|
||||
isPreflightPending: boolean
|
||||
isDispatchPending: boolean
|
||||
isSaving: boolean
|
||||
onDispatchContextIdChange: (value: string) => void
|
||||
onExecutionModeChange: (value: string) => void
|
||||
onOpenNodeMenu: () => void
|
||||
onAutoLayout: () => void
|
||||
onDeleteSelectedEdges: () => void
|
||||
onPreflight: () => void
|
||||
onDispatch: () => void
|
||||
onSave: () => void
|
||||
}
|
||||
|
||||
export function WorkflowCanvasToolbar({
|
||||
workflowName,
|
||||
blueprintLabel,
|
||||
blueprintDescription,
|
||||
graphFamilyLabel,
|
||||
graphFamilyClassName,
|
||||
executionMode,
|
||||
executionModeLabel,
|
||||
executionModeClassName,
|
||||
executionModeHint,
|
||||
dispatchContextId,
|
||||
executionModes,
|
||||
selectedEdgeCount,
|
||||
canAutoLayout,
|
||||
hasValidationErrors,
|
||||
isPreflightPending,
|
||||
isDispatchPending,
|
||||
isSaving,
|
||||
onDispatchContextIdChange,
|
||||
onExecutionModeChange,
|
||||
onOpenNodeMenu,
|
||||
onAutoLayout,
|
||||
onDeleteSelectedEdges,
|
||||
onPreflight,
|
||||
onDispatch,
|
||||
onSave,
|
||||
}: WorkflowCanvasToolbarProps) {
|
||||
return (
|
||||
<div className="border-b border-border-default bg-surface px-3 py-2">
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<div className="flex items-center gap-2 rounded-full border border-border-default bg-surface-hover/60 px-2 py-0.5 text-[11px] font-medium text-content-secondary">
|
||||
<GitBranch size={13} />
|
||||
Workflow Canvas
|
||||
</div>
|
||||
<h1 className="truncate text-sm font-semibold text-content">{workflowName}</h1>
|
||||
<span className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${graphFamilyClassName}`}>
|
||||
{graphFamilyLabel}
|
||||
</span>
|
||||
<span className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${executionModeClassName}`}>
|
||||
{executionModeLabel}
|
||||
</span>
|
||||
{blueprintLabel && (
|
||||
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700 dark:bg-slate-900/40 dark:text-slate-300">
|
||||
{blueprintLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 text-[11px] text-content-muted">
|
||||
{(blueprintDescription || executionModeHint) && (
|
||||
<span className="inline-flex max-w-3xl items-center gap-1 rounded-full border border-border-default bg-surface px-2 py-0.5">
|
||||
<BadgeInfo size={11} />
|
||||
{blueprintDescription ?? executionModeHint}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-full border border-border-default bg-surface px-2 py-0.5"
|
||||
title="Right-click anywhere on the canvas to open the searchable node picker."
|
||||
>
|
||||
<MousePointer2 size={11} />
|
||||
Right-click to add
|
||||
</span>
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-full border border-border-default bg-surface px-2 py-0.5"
|
||||
title="Select an edge and press Delete, or use right-click / double-click to remove it."
|
||||
>
|
||||
<Trash2 size={11} />
|
||||
Delete removes connections
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenNodeMenu}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border-default px-2.5 py-1.5 text-sm text-content hover:bg-surface-hover"
|
||||
title="Open searchable node picker"
|
||||
>
|
||||
<Plus size={14} />
|
||||
Node
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAutoLayout}
|
||||
disabled={!canAutoLayout}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border-default px-2.5 py-1.5 text-sm text-content hover:bg-surface-hover disabled:opacity-50"
|
||||
title="Automatically align nodes into a readable graph layout"
|
||||
>
|
||||
<LayoutGrid size={14} />
|
||||
Align
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDeleteSelectedEdges}
|
||||
disabled={selectedEdgeCount === 0}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border-default px-2.5 py-1.5 text-sm text-content hover:bg-surface-hover disabled:opacity-50"
|
||||
title="Delete the currently selected connection(s)"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
{selectedEdgeCount > 1 ? `Delete (${selectedEdgeCount})` : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex flex-wrap items-center justify-between gap-2 border-t border-border-default/70 pt-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<label className="flex items-center gap-2 rounded-lg border border-border-default bg-surface px-2 py-1.5 text-xs text-content-secondary">
|
||||
<span className="whitespace-nowrap">Context</span>
|
||||
<input
|
||||
value={dispatchContextId}
|
||||
onChange={event => onDispatchContextIdChange(event.target.value)}
|
||||
placeholder="context id"
|
||||
className="w-40 bg-transparent text-sm text-content focus:outline-none lg:w-52"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 rounded-lg border border-border-default bg-surface px-2 py-1.5 text-xs text-content-secondary">
|
||||
<span className="whitespace-nowrap">Mode</span>
|
||||
<select
|
||||
value={executionMode}
|
||||
onChange={event => onExecutionModeChange(event.target.value)}
|
||||
className="bg-transparent text-sm text-content focus:outline-none"
|
||||
>
|
||||
{executionModes.map(mode => (
|
||||
<option key={mode.value} value={mode.value}>
|
||||
{mode.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
onClick={onPreflight}
|
||||
disabled={isPreflightPending || hasValidationErrors}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border-default px-2.5 py-1.5 text-sm text-content hover:bg-surface-hover disabled:opacity-50"
|
||||
title="Validate graph runtime readiness without dispatching tasks"
|
||||
>
|
||||
{isPreflightPending ? <Loader2 size={14} className="animate-spin" /> : <RefreshCw size={14} />}
|
||||
{isPreflightPending ? 'Checking…' : 'Dry Run'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onDispatch}
|
||||
disabled={isDispatchPending || hasValidationErrors}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border-default px-2.5 py-1.5 text-sm text-content hover:bg-surface-hover disabled:opacity-50"
|
||||
title="Manual graph runtime dispatch for workflow debugging"
|
||||
>
|
||||
{isDispatchPending ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />}
|
||||
{isDispatchPending ? 'Dispatching…' : 'Run'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onSave}
|
||||
disabled={isSaving || hasValidationErrors}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-accent px-2.5 py-1.5 text-sm text-white hover:bg-accent-hover disabled:opacity-50"
|
||||
>
|
||||
<Save size={14} />
|
||||
{isSaving ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[11px] text-content-muted">
|
||||
{executionModeHint}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user