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 type { ReactNode } from "react"
|
||||||
import {
|
import {
|
||||||
CheckCircle2Icon,
|
CheckCircle2Icon,
|
||||||
Maximize2Icon,
|
|
||||||
MinusIcon,
|
|
||||||
PlusIcon,
|
|
||||||
RotateCcwIcon,
|
RotateCcwIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
SparklesIcon,
|
|
||||||
Undo2Icon,
|
Undo2Icon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
import type { WorkflowDraftValidation } from "./workflow-utils"
|
import type { WorkflowDraftValidation } from "./workflow-utils"
|
||||||
@@ -28,13 +23,6 @@ export function WorkflowEditorToolbar({
|
|||||||
undoDisabled = false,
|
undoDisabled = false,
|
||||||
onRedo,
|
onRedo,
|
||||||
redoDisabled = false,
|
redoDisabled = false,
|
||||||
onAutoLayout,
|
|
||||||
autoLayoutDisabled = false,
|
|
||||||
zoomPercent,
|
|
||||||
onZoomIn,
|
|
||||||
onZoomOut,
|
|
||||||
onResetZoom,
|
|
||||||
onFitView,
|
|
||||||
onRestoreDefault,
|
onRestoreDefault,
|
||||||
restoreDefaultDisabled = false,
|
restoreDefaultDisabled = false,
|
||||||
onValidate,
|
onValidate,
|
||||||
@@ -52,13 +40,6 @@ export function WorkflowEditorToolbar({
|
|||||||
undoDisabled?: boolean
|
undoDisabled?: boolean
|
||||||
onRedo?: () => void
|
onRedo?: () => void
|
||||||
redoDisabled?: boolean
|
redoDisabled?: boolean
|
||||||
onAutoLayout?: () => void
|
|
||||||
autoLayoutDisabled?: boolean
|
|
||||||
zoomPercent?: string
|
|
||||||
onZoomIn?: () => void
|
|
||||||
onZoomOut?: () => void
|
|
||||||
onResetZoom?: () => void
|
|
||||||
onFitView?: () => void
|
|
||||||
onRestoreDefault?: () => void
|
onRestoreDefault?: () => void
|
||||||
restoreDefaultDisabled?: boolean
|
restoreDefaultDisabled?: boolean
|
||||||
onValidate?: () => void
|
onValidate?: () => void
|
||||||
@@ -69,7 +50,7 @@ export function WorkflowEditorToolbar({
|
|||||||
publishDisabled?: boolean
|
publishDisabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
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">
|
<div className="flex min-w-0 shrink items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -86,35 +67,6 @@ export function WorkflowEditorToolbar({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 items-center gap-1 overflow-x-auto">
|
<div className="flex min-w-0 items-center gap-1 overflow-x-auto">
|
||||||
{toolbarExtra}
|
{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}>
|
<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" />
|
<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 type="button" variant="ghost" size="sm" className="h-7 shrink-0 px-2 text-xs" disabled={redoDisabled} onClick={onRedo}>
|
||||||
重做
|
重做
|
||||||
</Button>
|
</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}>
|
<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" />
|
<RotateCcwIcon className="size-3.5" />
|
||||||
恢复默认
|
恢复默认
|
||||||
@@ -145,34 +93,3 @@ export function WorkflowEditorToolbar({
|
|||||||
</div>
|
</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,
|
WorkflowBranchSelectionProvider,
|
||||||
type SelectedWorkflowBranch,
|
type SelectedWorkflowBranch,
|
||||||
} from "./workflow-branch-selection"
|
} from "./workflow-branch-selection"
|
||||||
|
import { WorkflowCanvasControls } from "./workflow-canvas-controls"
|
||||||
import { WorkflowConfigPanel } from "./workflow-config-sidebar"
|
import { WorkflowConfigPanel } from "./workflow-config-sidebar"
|
||||||
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
|
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
|
||||||
import {
|
import {
|
||||||
@@ -341,13 +342,6 @@ function WorkflowEditorInner({
|
|||||||
undoDisabled={undoDisabled}
|
undoDisabled={undoDisabled}
|
||||||
onRedo={onRedo}
|
onRedo={onRedo}
|
||||||
redoDisabled={redoDisabled}
|
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}
|
onRestoreDefault={onRestoreDefault}
|
||||||
restoreDefaultDisabled={restoreDefaultDisabled}
|
restoreDefaultDisabled={restoreDefaultDisabled}
|
||||||
onValidate={onValidate}
|
onValidate={onValidate}
|
||||||
@@ -359,6 +353,18 @@ function WorkflowEditorInner({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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}>
|
<WorkflowPortAddProvider onRequestAdd={openNodeMenuFromPort}>
|
||||||
<EditorRenderer className="h-full w-full" />
|
<EditorRenderer className="h-full w-full" />
|
||||||
</WorkflowPortAddProvider>
|
</WorkflowPortAddProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user