"use client"; import React, { useState, useRef, useEffect } from 'react'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import * as Dialog from '@radix-ui/react-dialog'; import { Book, MoreVertical, AlertTriangle } from 'lucide-react'; import { useFlowStoreActions } from '@/stores/flow-store'; import { useTemplateStore } from '@/stores/template-store'; import { useDesignStore } from '@/stores/design-store'; import { useSidebarStore } from '@/stores/sidebar-store'; import useFlowStore from '@/stores/flow-store'; import AboutStudioModal from '@/modals/about-studio'; import PromptModal from '@/modals/prompt-modal'; import SaveTemplateModal from '@/modals/save-template-modal'; import { generateAIPrompt, generateMermaidDiagram } from '@/utils/prompt-generator'; const NavigationBar = () => { const [isEditingName, setIsEditingName] = useState(false); const [tempName, setTempName] = useState(''); const [showResetWarning, setShowResetWarning] = useState(false); const [showAboutModal, setShowAboutModal] = useState(false); const [showPromptModal, setShowPromptModal] = useState(false); const [showSaveTemplateModal, setShowSaveTemplateModal] = useState(false); const [generatedPrompt, setGeneratedPrompt] = useState(''); const inputRef = useRef(null); const { reset, exportFlow, importFlow, autoLayout, undo, redo, canUndo, canRedo, onNodesChange } = useFlowStoreActions(); const { setShowTemplateSelector, hasTemplates } = useTemplateStore(); const { currentDesign, updateDesignName, createNewDesign, setCurrentDesign } = useDesignStore(); const { showMinimap, toggleMinimap, showComments, toggleComments, toggleFeedbackPanel } = useSidebarStore(); const { nodes } = useFlowStore(); const handleResetClick = () => { setShowResetWarning(true); }; const handleConfirmReset = () => { reset(); setShowResetWarning(false); }; const handleCancelReset = () => { setShowResetWarning(false); }; const handleSelectAll = () => { // Select all nodes const nodeUpdates = nodes.map(node => ({ id: node.id, type: 'select' as const, selected: true })); onNodesChange(nodeUpdates); }; const startEditing = () => { setTempName(currentDesign?.name || ''); setIsEditingName(true); }; const saveNameChange = () => { if (currentDesign && tempName.trim() && tempName !== currentDesign.name) { updateDesignName(currentDesign.id, tempName.trim()); } setIsEditingName(false); }; const cancelEditing = () => { setTempName(''); setIsEditingName(false); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { saveNameChange(); } else if (e.key === 'Escape') { cancelEditing(); } }; // Focus input when editing starts useEffect(() => { if (isEditingName && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [isEditingName]); const saveToFile = () => { const data = exportFlow(); // Check if the browser supports the File System Access API if ('showSaveFilePicker' in window) { // Modern browsers with File System Access API const saveFileWithPicker = async () => { try { const fileHandle = await (window as any).showSaveFilePicker({ suggestedName: `${currentDesign?.name || 'design'}.ecstudio`, types: [{ description: 'EventCatalog Studio files', accept: { 'application/json': ['.ecstudio'] } }] }); const writable = await fileHandle.createWritable(); await writable.write(JSON.stringify(data, null, 2)); await writable.close(); } catch (error) { // User cancelled or other error if (error instanceof Error && error.name !== 'AbortError') { console.error('Error saving file:', error); } } }; saveFileWithPicker(); } else { // Fallback for browsers without File System Access API const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${currentDesign?.name || 'design'}.ecstudio`; a.click(); URL.revokeObjectURL(url); } } const openFromFile = () => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.ecstudio'; fileInput.onchange = (e) => { const target = e.target as HTMLInputElement; const file = target?.files?.[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { try { const data = JSON.parse(event.target?.result as string); importFlow(data); // Update the current design name based on metadata (root level or nested) or filename const designName = data.name || data.metadata?.name || file.name.replace('.ecstudio', ''); if (currentDesign?.id) { updateDesignName(currentDesign.id, designName); } } catch (error) { console.error('Error parsing file:', error); alert('Error: Invalid file format. Please select a valid .ecstudio file.'); } } reader.readAsText(file); } } fileInput.click(); } const handleGeneratePrompt = () => { const flowData = exportFlow(); const designName = currentDesign?.name || 'Untitled Design'; if (!flowData) { throw new Error('Unable to export flow data'); } return generateAIPrompt({ flowData, designName }); } const exportToPrompt = () => { try { const prompt = handleGeneratePrompt(); if (prompt) { setGeneratedPrompt(prompt); setShowPromptModal(true); } } catch (error) { console.error('Error generating prompt:', error); alert(error instanceof Error ? error.message : 'Error generating prompt. Please try again.'); } } const saveAsTemplate = (templateData: { title: string; description: string; tags: string[]; }) => { const flowData = exportFlow(); if (!flowData) { alert('Unable to export flow data. Please try again.'); return; } const mermaidDiagram = generateMermaidDiagram(flowData); const template = { title: templateData.title, description: templateData.description, type: 'custom', data: flowData, usecase: templateData.description, mermaid: mermaidDiagram, tags: templateData.tags }; const blob = new Blob([JSON.stringify(template, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${templateData.title.toLowerCase().replace(/[^a-z0-9]/g, '-')}.ectemplate`; a.click(); URL.revokeObjectURL(url); } const handleNewDesign = () => { if (hasTemplates()) { // If templates are available, show the template selector setShowTemplateSelector(true); } else { // If no templates, create a blank design directly const newDesign = { id: Math.random().toString(36).substring(2, 15), type: 'design', name: 'Untitled Design', description: '', data: { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } }, createdAt: new Date(), updatedAt: new Date(), lastModified: 'now' }; createNewDesign(newDesign); setCurrentDesign(newDesign); } } return (
{/* Left: Book Icon + Three Dots Menu */}
{/* File operations - most common actions first */} New Design ^ N openFromFile()} className="flex items-center justify-between px-3 py-2 text-sm rounded hover:bg-gray-100 cursor-default outline-none" > Open ⌘ O saveToFile()} className="flex items-center justify-between px-3 py-2 text-sm rounded hover:bg-gray-100 cursor-default outline-none" > Save ⌘ S {/* Export submenu */} Export › as LLM prompt setShowSaveTemplateModal(true)} className="flex items-center px-3 py-2 text-sm rounded hover:bg-gray-100 cursor-default outline-none whitespace-nowrap" > as Template {/* Edit actions */} Undo ⌘Z Redo ⇧⌘Z Select All ⌘A {/* View actions */} Show Minimap {showMinimap && ✓} Show Comments {showComments && ✓} Auto Layout setShowAboutModal(true)} className="flex items-center px-3 py-2 text-sm rounded hover:bg-gray-100 cursor-default outline-none" > About Studio Reset Canvas
{/* Center: Design Name */}
{isEditingName ? ( setTempName(e.target.value)} onBlur={saveNameChange} onKeyDown={handleKeyDown} className="font-medium text-gray-800 bg-transparent border-0 text-center focus:outline-none focus:ring-0 text-sm max-w-xs" /> ) : ( )}
{/* Right: Sign In Button */}
setShowAboutModal(false)} /> setShowPromptModal(false)} prompt={generatedPrompt} title={`Architecture Prompt: ${currentDesign?.name || 'Untitled Design'}`} /> setShowSaveTemplateModal(false)} onSave={saveAsTemplate} /> {/* Reset Canvas Warning Dialog */}
Reset Canvas This will remove all nodes and connections from the canvas. This action cannot be undone.
); }; export default NavigationBar;