import { useReactFlow } from "@xyflow/react" import { AlertCircle, Check, Maximize2, Pause, Pencil, Play, PlayCircle, Plus, StepForward, WifiOff, X, } from "lucide-react" import { AnimatePresence, motion } from "motion/react" import { useEffect, useState } from "react" import type { ValidationIssue } from "@/client" import { Button } from "@/components/ui/button" import { FluksioLoader } from "@/components/ui/fluksio-loader" 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, LogsTrigger, PANEL } from "./LogsPanel" import { useLiveConnection } from "./liveStore" /** * 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 same fit with the settings panel's lane held clear, so an opening panel * never lands on the node you are looking at. 432px is the lane the chrome is * pushed out of by `md:right-[27rem]`; xyflow's padding parser takes px and %, * so it cannot be spelled in rem. 10% per side is what `padding: 0.25` above * resolves to, so the two fits are equally generous. */ export const FIT_VIEW_PANEL = { maxZoom: FIT_VIEW.maxZoom, // `as const` because xyflow types each side as `${number}px | ${number}%`, // which a widened `string` does not satisfy. padding: { top: "10%", bottom: "10%", left: "10%", right: "432px" } as const, } /** * The action bar, floating bottom-centre. Run is the one brand-secondary * affordance on this view; everything else stays quiet. * * Everything the flow bar used to carry is here too — whether the work is * saved, the flow's own settings, and putting it live or throwing it away — * so the top of the canvas is left to say which flow this is. * * It wraps rather than overflows, and drops the fit control on a phone; see * DESIGN-GUIDELINES.md → Responsive. */ export function FlowDock({ flow, issues, running, enabled, paused, saving, hasDraft, publishing, logs, onAddNode, onRun, onTogglePause, onStep, stepping, onFocusNode, onEditFlow, onPublish, onDiscard, className, }: { flow: string issues: ValidationIssue[] running: boolean enabled: boolean paused: boolean saving: boolean hasDraft: boolean publishing: boolean /** Owned by the editor, because a failing node can open it too. */ logs: LogsFilter onAddNode: () => void onRun: () => void onTogglePause: () => void onStep: () => void stepping: boolean onFocusNode: (nodeId: string) => void onEditFlow: () => void onPublish: () => void onDiscard: () => void className?: string }) { const { fitView } = useReactFlow() const connected = useLiveConnection() const [issuesOpen, setIssuesOpen] = useState(false) // The engine marks the issues it does not count against a flow. They are // still worth saying, so they stay in the list — but in the muted tone, and // without turning the summary red, since nothing here is actually broken. const faults = issues.filter((issue) => !issue.advisory) return ( // The panels and the bar are siblings in one column, so neither nests its // half-pixel centring translate or its backdrop filter inside the other — // which is what Firefox was drawing the console's text through. It also // gives the two panels a row to share, instead of one landing on the other.
Needs attention