feat: add WorkflowCanvasControls component for enhanced zoom and layout functionality
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"use client"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
import { Maximize2Icon, MinusIcon, PlusIcon, SparklesIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
|
||||
export function WorkflowCanvasControls({
|
||||
zoomPercent,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onResetZoom,
|
||||
onFitView,
|
||||
onAutoLayout,
|
||||
autoLayoutDisabled = false,
|
||||
}: {
|
||||
zoomPercent?: string
|
||||
onZoomIn?: () => void
|
||||
onZoomOut?: () => void
|
||||
onResetZoom?: () => void
|
||||
onFitView?: () => void
|
||||
onAutoLayout?: () => void
|
||||
autoLayoutDisabled?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className="inline-flex h-9 items-center gap-1 rounded-md bg-background/95 p-1 shadow-sm backdrop-blur">
|
||||
<CanvasControlButton label="缩小" onClick={onZoomOut}>
|
||||
<MinusIcon className="size-3.5" />
|
||||
</CanvasControlButton>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-12 px-1 text-xs tabular-nums"
|
||||
aria-label="重置为 100%"
|
||||
disabled={!onResetZoom}
|
||||
onClick={onResetZoom}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{zoomPercent ?? "100%"}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>重置为 100%</TooltipContent>
|
||||
</Tooltip>
|
||||
<CanvasControlButton label="放大" onClick={onZoomIn}>
|
||||
<PlusIcon className="size-3.5" />
|
||||
</CanvasControlButton>
|
||||
<CanvasControlButton label="适配画布" onClick={onFitView}>
|
||||
<Maximize2Icon className="size-3.5" />
|
||||
</CanvasControlButton>
|
||||
<CanvasControlButton label="自动布局" onClick={onAutoLayout} disabled={autoLayoutDisabled}>
|
||||
<SparklesIcon className="size-3.5" />
|
||||
</CanvasControlButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CanvasControlButton({
|
||||
label,
|
||||
onClick,
|
||||
disabled = false,
|
||||
children,
|
||||
}: {
|
||||
label: string
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="size-7"
|
||||
aria-label={label}
|
||||
disabled={disabled || !onClick}
|
||||
onClick={onClick}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -3,18 +3,13 @@
|
||||
import type { ReactNode } from "react"
|
||||
import {
|
||||
CheckCircle2Icon,
|
||||
Maximize2Icon,
|
||||
MinusIcon,
|
||||
PlusIcon,
|
||||
RotateCcwIcon,
|
||||
SaveIcon,
|
||||
SendIcon,
|
||||
SparklesIcon,
|
||||
Undo2Icon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import type { WorkflowDraftValidation } from "./workflow-utils"
|
||||
@@ -28,13 +23,6 @@ export function WorkflowEditorToolbar({
|
||||
undoDisabled = false,
|
||||
onRedo,
|
||||
redoDisabled = false,
|
||||
onAutoLayout,
|
||||
autoLayoutDisabled = false,
|
||||
zoomPercent,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onResetZoom,
|
||||
onFitView,
|
||||
onRestoreDefault,
|
||||
restoreDefaultDisabled = false,
|
||||
onValidate,
|
||||
@@ -52,13 +40,6 @@ export function WorkflowEditorToolbar({
|
||||
undoDisabled?: boolean
|
||||
onRedo?: () => void
|
||||
redoDisabled?: boolean
|
||||
onAutoLayout?: () => void
|
||||
autoLayoutDisabled?: boolean
|
||||
zoomPercent?: string
|
||||
onZoomIn?: () => void
|
||||
onZoomOut?: () => void
|
||||
onResetZoom?: () => void
|
||||
onFitView?: () => void
|
||||
onRestoreDefault?: () => void
|
||||
restoreDefaultDisabled?: boolean
|
||||
onValidate?: () => void
|
||||
@@ -69,7 +50,7 @@ export function WorkflowEditorToolbar({
|
||||
publishDisabled?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className="inline-flex min-h-10 w-fit max-w-full items-center gap-3 rounded-md border bg-background/95 px-2 py-1 shadow-sm backdrop-blur">
|
||||
<div className="inline-flex min-h-10 w-fit max-w-full items-center gap-3 rounded-md bg-background/95 px-2 py-1 shadow-sm backdrop-blur">
|
||||
<div className="flex min-w-0 shrink items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
@@ -86,35 +67,6 @@ export function WorkflowEditorToolbar({
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center gap-1 overflow-x-auto">
|
||||
{toolbarExtra}
|
||||
<div className="flex h-7 shrink-0 items-center rounded-md border bg-muted/30 p-0.5">
|
||||
<WorkflowToolbarIconButton label="缩小" onClick={onZoomOut}>
|
||||
<MinusIcon className="size-3.5" />
|
||||
</WorkflowToolbarIconButton>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-12 px-1 text-xs tabular-nums"
|
||||
aria-label="重置为 100%"
|
||||
disabled={!onResetZoom}
|
||||
onClick={onResetZoom}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{zoomPercent ?? "100%"}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>重置为 100%</TooltipContent>
|
||||
</Tooltip>
|
||||
<WorkflowToolbarIconButton label="放大" onClick={onZoomIn}>
|
||||
<PlusIcon className="size-3.5" />
|
||||
</WorkflowToolbarIconButton>
|
||||
<WorkflowToolbarIconButton label="适配画布" onClick={onFitView}>
|
||||
<Maximize2Icon className="size-3.5" />
|
||||
</WorkflowToolbarIconButton>
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 shrink-0 px-2 text-xs" disabled={undoDisabled} onClick={onUndo}>
|
||||
<Undo2Icon className="size-3.5" />
|
||||
撤销
|
||||
@@ -122,10 +74,6 @@ export function WorkflowEditorToolbar({
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 shrink-0 px-2 text-xs" disabled={redoDisabled} onClick={onRedo}>
|
||||
重做
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 shrink-0 px-2 text-xs" disabled={autoLayoutDisabled} onClick={onAutoLayout}>
|
||||
<SparklesIcon className="size-3.5" />
|
||||
自动布局
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 shrink-0 px-2 text-xs" disabled={restoreDefaultDisabled} onClick={onRestoreDefault}>
|
||||
<RotateCcwIcon className="size-3.5" />
|
||||
恢复默认
|
||||
@@ -145,34 +93,3 @@ export function WorkflowEditorToolbar({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkflowToolbarIconButton({
|
||||
label,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
label: string
|
||||
onClick?: () => void
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="size-6"
|
||||
aria-label={label}
|
||||
disabled={!onClick}
|
||||
onClick={onClick}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
WorkflowBranchSelectionProvider,
|
||||
type SelectedWorkflowBranch,
|
||||
} from "./workflow-branch-selection"
|
||||
import { WorkflowCanvasControls } from "./workflow-canvas-controls"
|
||||
import { WorkflowConfigPanel } from "./workflow-config-sidebar"
|
||||
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
|
||||
import {
|
||||
@@ -341,13 +342,6 @@ function WorkflowEditorInner({
|
||||
undoDisabled={undoDisabled}
|
||||
onRedo={onRedo}
|
||||
redoDisabled={redoDisabled}
|
||||
onAutoLayout={() => void autoLayout()}
|
||||
autoLayoutDisabled={autoLayouting || definition.nodes.length < 2}
|
||||
zoomPercent={zoomPercent}
|
||||
onZoomIn={() => playgroundTools.zoomin(true)}
|
||||
onZoomOut={() => playgroundTools.zoomout(true)}
|
||||
onResetZoom={resetZoom}
|
||||
onFitView={() => playgroundTools.fitView(true)}
|
||||
onRestoreDefault={onRestoreDefault}
|
||||
restoreDefaultDisabled={restoreDefaultDisabled}
|
||||
onValidate={onValidate}
|
||||
@@ -359,6 +353,18 @@ function WorkflowEditorInner({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-3 left-3 z-50">
|
||||
<WorkflowCanvasControls
|
||||
zoomPercent={zoomPercent}
|
||||
onZoomIn={() => playgroundTools.zoomin(true)}
|
||||
onZoomOut={() => playgroundTools.zoomout(true)}
|
||||
onResetZoom={resetZoom}
|
||||
onFitView={() => playgroundTools.fitView(true)}
|
||||
onAutoLayout={() => void autoLayout()}
|
||||
autoLayoutDisabled={autoLayouting || definition.nodes.length < 2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<WorkflowPortAddProvider onRequestAdd={openNodeMenuFromPort}>
|
||||
<EditorRenderer className="h-full w-full" />
|
||||
</WorkflowPortAddProvider>
|
||||
|
||||
Reference in New Issue
Block a user