Add in-canvas module delete actions
This commit is contained in:
parent
3876a0c403
commit
20d0529365
@ -10,6 +10,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Trash2,
|
||||
Save,
|
||||
Undo2,
|
||||
Redo2,
|
||||
@ -941,6 +942,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
const clearCanvas = useCanvasStore((state) => state.clearCanvas);
|
||||
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
|
||||
const addNode = useCanvasStore((state) => state.addNode);
|
||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||
const addEdge = useCanvasStore((state) => state.addEdge);
|
||||
const selectNodes = useCanvasStore((state) => state.selectNodes);
|
||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||
@ -1187,6 +1189,11 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
: 296
|
||||
: 0;
|
||||
const selectedWorkflowPanelLeft = isVideoMode ? videoModuleSidebarWidth + 28 : 16;
|
||||
const handleDeleteSelectedWorkflowNode = useCallback(() => {
|
||||
if (!selectedVideoWorkflowNode) return;
|
||||
deleteNode(selectedVideoWorkflowNode.id);
|
||||
toast.success(`已删除模块:${selectedVideoWorkflowDefinition?.label || "当前模块"}`);
|
||||
}, [deleteNode, selectedVideoWorkflowDefinition?.label, selectedVideoWorkflowNode]);
|
||||
const workflowGraph = useMemo(
|
||||
() => createWorkflowGraphContext(nodes, edges),
|
||||
[edges, nodes]
|
||||
@ -7941,6 +7948,17 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={handleDeleteSelectedWorkflowNode}
|
||||
disabled={isAnyWorkflowExecutionRunning}
|
||||
className="w-full rounded-2xl shadow-sm"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除当前模块
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { useCanvasStore } from "@/store/canvas";
|
||||
import type { CanvasNode, TextNodeData } from "@/types/canvas";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -17,6 +18,7 @@ export function TextNode({ node, isSelected }: TextNodeProps) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||
|
||||
// 进入编辑模式时聚焦
|
||||
useEffect(() => {
|
||||
@ -119,12 +121,33 @@ export function TextNode({ node, isSelected }: TextNodeProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 编辑提示 */}
|
||||
{!isEditing && isSelected && (
|
||||
<div className="absolute bottom-1 right-1 text-[10px] text-muted-foreground">
|
||||
双击编辑
|
||||
</div>
|
||||
)}
|
||||
{!isEditing && isSelected ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteNode(node.id);
|
||||
}}
|
||||
className={cn(
|
||||
"absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full border text-muted-foreground transition-colors hover:text-destructive",
|
||||
data.transparent
|
||||
? "border-slate-200 bg-white/95 shadow-sm hover:border-destructive/30 hover:bg-red-50"
|
||||
: "border-slate-200 bg-white hover:border-destructive/30 hover:bg-red-50"
|
||||
)}
|
||||
aria-label="删除文本模块"
|
||||
title="删除文本模块"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div className="absolute bottom-1 right-2 text-[10px] text-muted-foreground">
|
||||
双击编辑
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,8 +8,10 @@ import {
|
||||
LayoutPanelTop,
|
||||
ScanSearch,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useCanvasStore } from "@/store/canvas";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getVideoWorkflowNodeDefinition,
|
||||
@ -78,6 +80,7 @@ const STATUS_LABELS = {
|
||||
} as const;
|
||||
|
||||
export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||
const data = node.data as WorkflowNodeData;
|
||||
const definition = getVideoWorkflowNodeDefinition(data.templateType);
|
||||
const metadata = (data.metadata || {}) as Record<string, any>;
|
||||
@ -183,9 +186,27 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className={badgeClassName}>
|
||||
{STATUS_LABELS[data.status] || STATUS_LABELS.pending}
|
||||
</Badge>
|
||||
<div className="flex items-start gap-2">
|
||||
{isSelected ? (
|
||||
<button
|
||||
type="button"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
deleteNode(node.id);
|
||||
}}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-muted-foreground transition-colors hover:border-destructive/30 hover:bg-red-50 hover:text-destructive dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-destructive/40 dark:hover:bg-red-950/30"
|
||||
aria-label="删除模块"
|
||||
title="删除模块"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
<Badge variant="outline" className={badgeClassName}>
|
||||
{STATUS_LABELS[data.status] || STATUS_LABELS.pending}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={sectionClassName}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user