The node, flow, widget and dashboard panels explained themselves in paragraphs under every control. They now carry a caption and an info tooltip, so a panel reads as a list of settings. InfoTip, PanelSection and FieldLabel live beside PANEL_SECTION in SidePanel.tsx, which the panels already share. InfoTip holds its own open state because a Radix tooltip ignores a touch pointer, and below md the whole panel is a full-screen sheet — hover-only help would leave a phone with the caption and nothing else. Dashboard/panels.tsx drops from 30 helper paragraphs to 5; the ones left are empty states and status, not explanation. Em-dashes are out of the user-facing strings, except where one separates the halves of a select item. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
import { useState } from "react"
|
|
|
|
import type { FlowDef_Input } from "@/client"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Switch } from "@/components/ui/switch"
|
|
import { BoundarySections } from "./FlowBoundary"
|
|
import { PanelSection, PanelTitle, SidePanel } from "./SidePanel"
|
|
|
|
/**
|
|
* The flow's own settings, in the same panel its nodes use — down to where the
|
|
* name sits and how confirming a new one works.
|
|
*/
|
|
export function FlowPanel({
|
|
open,
|
|
definition,
|
|
nodeCount,
|
|
onChange,
|
|
onDelete,
|
|
hasDraft,
|
|
enabled,
|
|
toggling,
|
|
onToggleEnabled,
|
|
onClose,
|
|
}: {
|
|
open: boolean
|
|
definition: FlowDef_Input
|
|
nodeCount: number
|
|
onChange: (next: FlowDef_Input) => void
|
|
onDelete: () => void
|
|
hasDraft: boolean
|
|
enabled: boolean
|
|
toggling: boolean
|
|
onToggleEnabled: (next: boolean) => void
|
|
onClose: () => void
|
|
}) {
|
|
const [confirmOpen, setConfirmOpen] = useState(false)
|
|
|
|
return (
|
|
<>
|
|
<SidePanel
|
|
open={open}
|
|
label="Flow settings"
|
|
testId="flow-panel"
|
|
bodyKey={definition.name}
|
|
onClose={onClose}
|
|
header={
|
|
<PanelTitle
|
|
value={definition.title ?? ""}
|
|
placeholder={definition.name}
|
|
label="Flow title"
|
|
onConfirm={(title) => onChange({ ...definition, title })}
|
|
/>
|
|
}
|
|
footer={
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-destructive hover:text-destructive"
|
|
onClick={() => setConfirmOpen(true)}
|
|
data-testid="delete-flow"
|
|
>
|
|
Delete flow
|
|
</Button>
|
|
}
|
|
>
|
|
<div className="grid gap-6 p-4">
|
|
<PanelSection
|
|
title="Running"
|
|
className="gap-3"
|
|
help="While this is on, the engine keeps the flow's subscriptions, schedules and webhooks live. Off, none of it is subscribed, scheduled or reachable."
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<p className="text-sm text-muted-foreground">
|
|
{enabled ? "Live" : "Stopped"}
|
|
</p>
|
|
<Switch
|
|
checked={enabled}
|
|
disabled={toggling}
|
|
onCheckedChange={onToggleEnabled}
|
|
aria-label="Run this flow"
|
|
data-testid="flow-enabled"
|
|
/>
|
|
</div>
|
|
</PanelSection>
|
|
|
|
<BoundarySections
|
|
flow={definition.name}
|
|
definition={definition}
|
|
running={enabled}
|
|
onChange={onChange}
|
|
/>
|
|
|
|
<PanelSection
|
|
title="Contents"
|
|
className="gap-3"
|
|
help={
|
|
<>
|
|
Every message in this flow is named under{" "}
|
|
<span className="font-mono">{definition.name}</span>, so two
|
|
flows can use the same short name without colliding.
|
|
</>
|
|
}
|
|
>
|
|
<p className="text-sm text-muted-foreground">
|
|
{nodeCount === 0
|
|
? "No nodes yet."
|
|
: `${nodeCount} node${nodeCount === 1 ? "" : "s"}`}
|
|
</p>
|
|
</PanelSection>
|
|
|
|
{hasDraft ? (
|
|
<PanelSection
|
|
title="Unpublished changes"
|
|
className="gap-3"
|
|
help="Edits are kept as a draft until you publish them. The bar below publishes this one, or discards it."
|
|
>
|
|
<p className="text-sm text-muted-foreground">
|
|
The engine is still running the last published version.
|
|
</p>
|
|
</PanelSection>
|
|
) : null}
|
|
</div>
|
|
</SidePanel>
|
|
|
|
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Delete {definition.title || definition.name}?
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
This removes the flow and the code of its{" "}
|
|
{nodeCount === 1 ? "node" : `${nodeCount} nodes`}. Its history
|
|
stays in the flow store's git repository.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setConfirmOpen(false)}>
|
|
Keep it
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={() => {
|
|
setConfirmOpen(false)
|
|
onDelete()
|
|
}}
|
|
data-testid="confirm-delete-flow"
|
|
>
|
|
Delete flow
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|