Rework the dashboards onto the flow canvas
One shell for both editors. Flows and dashboards each get a searchable
overview under the padded shell, their editors move to the full-bleed
canvas, and the floating chrome is shared: a title bar that only says
what you are looking at, and a bottom dock carrying everything else —
the flow bar's status, settings and Publish moved down there, the
add-flow button moved to the overview.
Dashboards gain the rest of M4's visualization work:
- widgets are picked by clicking them, with the header as the drag
handle so a slider still slides and a switch still flips while
editing; settings moved into the flows' SidePanel
- react-grid-layout for drag and edge-resize, so the stored x/y finally
mean something; a dashboard nobody arranged is shelf-packed once
- a per-dashboard grid size, so a panel can be matched to its screen
- the chart widget, drawn with uPlot: several messages on one axis, fed
from the stored history plus the live socket tail, coloured from the
new --chart-1..5 ramp
- /view/{name}: the URL a wall panel is pointed at — no sidebar, no
footer, no editing, and no editor code, since routes are split
- a widget wired to a payload type it cannot carry, or wired to nothing
at all, carries the same red dot a failing node does; the picker
records the type it bound and WidgetDef refuses a mismatch on save
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { useReactFlow } from "@xyflow/react"
|
||||
import {
|
||||
AlertCircle,
|
||||
Check,
|
||||
Loader2,
|
||||
Maximize2,
|
||||
Pause,
|
||||
Pencil,
|
||||
Play,
|
||||
PlayCircle,
|
||||
Plus,
|
||||
WifiOff,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
} from "lucide-react"
|
||||
@@ -28,6 +31,7 @@ import {
|
||||
import { slideUp, transitions } from "@/lib/motion"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { type LogsFilter, LogsPanel } from "./LogsPanel"
|
||||
import { useLiveConnection } from "./liveStore"
|
||||
|
||||
/**
|
||||
* What "fit" means on this canvas: the view a flow opens with, and the one the
|
||||
@@ -40,6 +44,10 @@ 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.
|
||||
*
|
||||
* Everything the flow bar used to carry is here too — whether the work is
|
||||
* saved, the flow's own settings, and putting it live — so the top of the
|
||||
* canvas is left to say which flow this is.
|
||||
*/
|
||||
export function FlowDock({
|
||||
flow,
|
||||
@@ -47,25 +55,36 @@ export function FlowDock({
|
||||
running,
|
||||
enabled,
|
||||
paused,
|
||||
saving,
|
||||
hasDraft,
|
||||
publishing,
|
||||
logs,
|
||||
onAddNode,
|
||||
onRun,
|
||||
onTogglePause,
|
||||
onFocusNode,
|
||||
onEditFlow,
|
||||
onPublish,
|
||||
}: {
|
||||
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
|
||||
onFocusNode: (nodeId: string) => void
|
||||
onEditFlow: () => void
|
||||
onPublish: () => void
|
||||
}) {
|
||||
const { zoomIn, zoomOut, fitView } = useReactFlow()
|
||||
const connected = useLiveConnection()
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -219,6 +238,60 @@ export function FlowDock({
|
||||
{enabled ? "Run every node once" : "Start the flow to run it"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Separator orientation="vertical" className="mx-0.5 !h-5" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 text-muted-foreground md:size-8"
|
||||
onClick={onEditFlow}
|
||||
aria-label="Flow settings"
|
||||
data-testid="edit-flow"
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Flow settings</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex size-8 shrink-0 items-center justify-center text-muted-foreground">
|
||||
{!connected ? (
|
||||
<WifiOff className="size-3.5" />
|
||||
) : saving ? (
|
||||
<Loader2 className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
<Check className="size-3.5" />
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{!connected
|
||||
? "Reconnecting to the engine"
|
||||
: saving
|
||||
? "Saving"
|
||||
: hasDraft
|
||||
? "Saved — publish to put it live"
|
||||
: "All changes saved"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{hasDraft ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-11 shrink-0 rounded-full md:h-8"
|
||||
onClick={onPublish}
|
||||
disabled={publishing}
|
||||
data-testid="publish-flow"
|
||||
>
|
||||
{publishing ? "Publishing…" : "Publish"}
|
||||
</Button>
|
||||
) : null}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user