import { closestCenter, DndContext, type DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { TOOLS } from "@snapotter/shared"; import { FileImage, GripVertical, X } from "lucide-react"; import { ICON_MAP } from "@/lib/icon-map"; import { cn } from "@/lib/utils"; import type { PipelineStep } from "@/stores/pipeline-store"; import { PipelineStepSettings } from "./pipeline-step-settings"; import { getSettingsSummary } from "./pipeline-step-summary"; interface PipelineBuilderProps { steps: PipelineStep[]; expandedStepId: string | null; onRemoveStep: (id: string) => void; onReorderSteps: (activeId: string, overId: string) => void; onUpdateSettings: (id: string, settings: Record) => void; onToggleStep: (id: string | null) => void; } /* ------------------------------------------------------------------ */ /* SortableStep */ /* ------------------------------------------------------------------ */ interface SortableStepProps { step: PipelineStep; index: number; isExpanded: boolean; onToggle: () => void; onRemove: () => void; onUpdateSettings: (settings: Record) => void; } function SortableStep({ step, index, isExpanded, onToggle, onRemove, onUpdateSettings, }: SortableStepProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: step.id, }); const style = { transform: CSS.Transform.toString(transform), transition, }; const tool = TOOLS.find((t) => t.id === step.toolId); if (!tool) return null; const Icon = (ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage; const summary = getSettingsSummary(step.toolId, step.settings); return (
{/* Header row - click to expand/collapse */} { // biome-ignore lint/a11y/useKeyWithClickEvents: keyboard handled by dnd-kit attributes // biome-ignore lint/a11y/useSemanticElements: div with role=button avoids invalid nested
}
); } /* ------------------------------------------------------------------ */ /* PipelineBuilder */ /* ------------------------------------------------------------------ */ export function PipelineBuilder({ steps, expandedStepId, onRemoveStep, onReorderSteps, onUpdateSettings, onToggleStep, }: PipelineBuilderProps) { const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), ); function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (over && active.id !== over.id) { onReorderSteps(String(active.id), String(over.id)); } } if (steps.length === 0) { return (

No steps yet

Click tools from the palette to build your pipeline

); } return ( s.id)} strategy={verticalListSortingStrategy}>
{steps.map((step, idx) => ( onToggleStep(expandedStepId === step.id ? null : step.id)} onRemove={() => onRemoveStep(step.id)} onUpdateSettings={(s) => onUpdateSettings(step.id, s)} /> ))}
); }