"use client"; import { useState, useEffect, useRef } from "react"; import { Save, Undo2, Redo2, ZoomIn, ZoomOut, Maximize2, Download, Trash2, Plus, } from "lucide-react"; import { Input } from "@/components/ui/input"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { useReactFlow } from "reactflow"; import { SaveConfirmDialog } from "./dialogs/SaveConfirmDialog"; import { validateFlow, summarizeValidations } from "@/lib/utils/flowValidation"; import type { FlowValidation } from "@/lib/utils/flowValidation"; import { useToast } from "@/hooks/use-toast"; interface FlowToolbarProps { validations?: FlowValidation[]; onSaveComplete?: (flowId: number, flowName: string) => void; onOpenCommandPalette?: () => void; } export function FlowToolbar({ validations = [], onSaveComplete, onOpenCommandPalette, }: FlowToolbarProps) { const { toast } = useToast(); const { zoomIn, zoomOut, fitView } = useReactFlow(); const { flowName, setFlowName, nodes, edges, saveFlow, exportFlow, isSaving, selectedNodes, removeNodes, undo, redo, canUndo, canRedo, } = useFlowEditorStore(); const [showSaveDialog, setShowSaveDialog] = useState(false); const handleSaveRef = useRef<() => void>(); useEffect(() => { handleSaveRef.current = handleSave; }); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); if (!isSaving) handleSaveRef.current?.(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isSaving]); const handleSave = async () => { const currentValidations = validations.length > 0 ? validations : validateFlow(nodes, edges); if (currentValidations.length > 0) { setShowSaveDialog(true); return; } await performSave(); }; const performSave = async () => { const result = await saveFlow(); if (result.success) { toast({ title: "저장했어요", description: `플로우가 안전하게 저장됐어요`, variant: "default", }); if (onSaveComplete && result.flowId) { onSaveComplete(result.flowId, flowName); } if (window.opener && result.flowId) { window.opener.postMessage( { type: "FLOW_SAVED", flowId: result.flowId, flowName }, "*", ); } } else { toast({ title: "저장에 실패했어요", description: result.message, variant: "destructive", }); } setShowSaveDialog(false); }; const handleExport = () => { const json = exportFlow(); const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${flowName || "flow"}.json`; a.click(); URL.revokeObjectURL(url); toast({ title: "내보내기 완료", description: "JSON 파일로 저장했어요", variant: "default", }); }; const handleDelete = () => { if (selectedNodes.length === 0) return; removeNodes(selectedNodes); toast({ title: "노드를 삭제했어요", description: `${selectedNodes.length}개 노드가 삭제됐어요`, variant: "default", }); }; const ToolBtn = ({ onClick, disabled, title, danger, children, }: { onClick: () => void; disabled?: boolean; title: string; danger?: boolean; children: React.ReactNode; }) => ( ); return ( <>
{/* 노드 추가 */} {onOpenCommandPalette && ( <>
)} {/* 플로우 이름 */} setFlowName(e.target.value)} onKeyDown={(e) => e.stopPropagation()} className="h-7 w-[160px] border-none bg-transparent px-2 text-xs font-medium text-zinc-200 placeholder:text-zinc-600 focus-visible:ring-0 focus-visible:ring-offset-0" placeholder="플로우 이름을 입력해요" />
{/* Undo / Redo */} {/* 삭제 */} {selectedNodes.length > 0 && ( <>
)}
{/* 줌 */} zoomIn()} title="확대"> zoomOut()} title="축소"> fitView()} title="전체 보기">
{/* 저장 */} {/* JSON 내보내기 */}
0 ? validations : validateFlow(nodes, edges)} onConfirm={performSave} onCancel={() => setShowSaveDialog(false)} /> ); }