diff --git a/src/features/canvas/CanvasOverlays.tsx b/src/features/canvas/CanvasOverlays.tsx index 9f239bd..7a6f031 100644 --- a/src/features/canvas/CanvasOverlays.tsx +++ b/src/features/canvas/CanvasOverlays.tsx @@ -44,6 +44,7 @@ interface CanvasOverlaysProps { onUpdateNodes: (nodeIds: string[], patch: Partial) => void; onResizeNodes: (nodeIds: string[], width?: number, height?: number) => void; onUpdateEdge: (edgeId: string, patch: Partial) => void; + onSaveSelectionAsTemplate?: () => void; }; contextMenu: { state: CanvasContextMenuState | null; @@ -134,6 +135,7 @@ export default function CanvasOverlays({ onUpdateNodes={properties.onUpdateNodes} onResizeNodes={properties.onResizeNodes} onUpdateEdge={properties.onUpdateEdge} + onSaveSelectionAsTemplate={properties.onSaveSelectionAsTemplate} /> )} diff --git a/src/features/canvas/FlowCanvas.tsx b/src/features/canvas/FlowCanvas.tsx index db49035..57d04da 100644 --- a/src/features/canvas/FlowCanvas.tsx +++ b/src/features/canvas/FlowCanvas.tsx @@ -385,6 +385,7 @@ export default function FlowCanvas({ onUpdateNodes: nodeCommands.updateNodesFromPanel, onResizeNodes: nodeCommands.resizeNodesFromPanel, onUpdateEdge: edgeCommands.updateEdgeFromPanel, + onSaveSelectionAsTemplate: templates.saveSelectionAsTemplate, }} contextMenu={{ state: contextMenu.contextMenu, diff --git a/src/features/canvas/components/CanvasPropertiesPanel.tsx b/src/features/canvas/components/CanvasPropertiesPanel.tsx index 516be7a..5b03cb3 100644 --- a/src/features/canvas/components/CanvasPropertiesPanel.tsx +++ b/src/features/canvas/components/CanvasPropertiesPanel.tsx @@ -1,5 +1,5 @@ import { Edge, MarkerType } from '@xyflow/react'; -import { AlignCenter, AlignLeft, AlignRight, CornerDownRight, Link2, MousePointer2, Route, SquareDashedMousePointer, Upload } from 'lucide-react'; +import { AlignCenter, AlignLeft, AlignRight, Boxes, CornerDownRight, Link2, MousePointer2, Route, SquareDashedMousePointer, Upload } from 'lucide-react'; import { useRef, useState } from 'react'; import type { ChangeEvent, ReactNode } from 'react'; import { TableCellSelection, TableNodeDataValue, TableTextAlign, WorkspaceNode } from '../../../types'; @@ -11,6 +11,7 @@ interface CanvasPropertiesPanelProps { onUpdateNodes: (nodeIds: string[], patch: Partial) => void; onResizeNodes: (nodeIds: string[], width?: number, height?: number) => void; onUpdateEdge: (edgeId: string, patch: Partial) => void; + onSaveSelectionAsTemplate?: () => void; } const STATUS_OPTIONS = ['', '草稿', '待补充', '已确认', '重点', '废弃']; @@ -41,6 +42,7 @@ export default function CanvasPropertiesPanel({ onUpdateNodes, onResizeNodes, onUpdateEdge, + onSaveSelectionAsTemplate, }: CanvasPropertiesPanelProps) { if (selectedNodes.length === 0 && !selectedEdge) return null; @@ -71,6 +73,13 @@ export default function CanvasPropertiesPanel({ onUpdateNodes={onUpdateNodes} /> + {!isSingle && onSaveSelectionAsTemplate && ( + + )} + {isSingle && firstNode.type !== 'timeline' && ( @@ -80,6 +89,29 @@ export default function CanvasPropertiesPanel({ ); } +function BatchTemplateAction({ + selectedCount, + onSaveSelectionAsTemplate, +}: { + selectedCount: number; + onSaveSelectionAsTemplate: () => void; +}) { + return ( +
+ +
+ ); +} + function EdgePropertiesPanel({ selectedEdge, onUpdateEdge,