blob: f288a0d65e84fe10326680ff37fa37838e3f0fce (
plain) (
tree)
|
|
import { useMemo } from "react";
import type { DirectiveStep } from "../../lib/api";
import { StepNode } from "./StepNode";
interface DirectiveDAGProps {
steps: DirectiveStep[];
onComplete?: (stepId: string) => void;
onFail?: (stepId: string) => void;
onSkip?: (stepId: string) => void;
}
interface Layer {
steps: DirectiveStep[];
}
function topoSort(steps: DirectiveStep[]): Layer[] {
if (steps.length === 0) return [];
const stepMap = new Map(steps.map((s) => [s.id, s]));
const assigned = new Set<string>();
const layers: Layer[] = [];
// Iteratively find steps whose dependencies are all assigned
let remaining = [...steps];
while (remaining.length > 0) {
const layer: DirectiveStep[] = [];
for (const step of remaining) {
const depsResolved = step.dependsOn.every(
(depId) => assigned.has(depId) || !stepMap.has(depId)
);
if (depsResolved) {
layer.push(step);
}
}
if (layer.length === 0) {
// Cycle detected or orphaned — push all remaining
layers.push({ steps: remaining });
break;
}
for (const s of layer) {
assigned.add(s.id);
}
layers.push({ steps: layer.sort((a, b) => a.orderIndex - b.orderIndex) });
remaining = remaining.filter((s) => !assigned.has(s.id));
}
return layers;
}
export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAGProps) {
const layers = useMemo(() => topoSort(steps), [steps]);
if (steps.length === 0) {
return (
<div className="text-center py-8 text-[#7788aa] font-mono text-sm">
No steps yet. Add steps to build the DAG.
</div>
);
}
return (
<div className="flex flex-col gap-4 items-center py-4">
{layers.map((layer, layerIdx) => (
<div key={layerIdx}>
{layerIdx > 0 && (
<div className="flex justify-center py-1">
<div className="w-px h-4 bg-[#2a3a5a]" />
</div>
)}
<div className="flex flex-wrap gap-3 justify-center">
{layer.steps.map((step) => (
<StepNode
key={step.id}
step={step}
onComplete={onComplete ? () => onComplete(step.id) : undefined}
onFail={onFail ? () => onFail(step.id) : undefined}
onSkip={onSkip ? () => onSkip(step.id) : undefined}
/>
))}
</div>
</div>
))}
</div>
);
}
|