import { useReactFlow } from "@xyflow/react" import { AlertCircle, Loader2, Maximize2, Pause, Play, PlayCircle, Plus, ZoomIn, ZoomOut, } from "lucide-react" import { motion } from "motion/react" import type { ValidationIssue } from "@/client" import { Button } from "@/components/ui/button" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Separator } from "@/components/ui/separator" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { slideUp, transitions } from "@/lib/motion" import { cn } from "@/lib/utils" import { type LogsFilter, LogsPanel } from "./LogsPanel" /** * What "fit" means on this canvas: the view a flow opens with, and the one the * fit button returns to. The generous padding keeps the graph clear of the * chrome floating over it, and the cap stops a two-node flow from being blown * up past legibility. */ export const FIT_VIEW = { padding: 0.25, maxZoom: 1.2 } /** * The action bar, floating bottom-centre. Run is the one brand-secondary * affordance on this view; everything else stays quiet. */ export function FlowDock({ flow, issues, running, enabled, paused, logs, onAddNode, onRun, onTogglePause, onFocusNode, }: { flow: string issues: ValidationIssue[] running: boolean enabled: boolean paused: boolean /** Owned by the editor, because a failing node can open it too. */ logs: LogsFilter onAddNode: () => void onRun: () => void onTogglePause: () => void onFocusNode: (nodeId: string) => void }) { const { zoomIn, zoomOut, fitView } = useReactFlow() return ( Add a node (⌘K) Fit to screen {issues.length > 0 ? ( <>

Needs attention

    {issues.map((issue) => (
  • ))}
) : null} {!enabled ? "This flow is stopped" : paused ? "Let the flow carry on" : "Hold the nodes; values still arrive"} {enabled ? "Run every node once" : "Start the flow to run it"}
) }