diff --git a/src/App.tsx b/src/App.tsx index 79e55df..48d05d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,6 @@ import LeftPanel, { type LeftPanelRef } from "./components/Panels/LeftPanel"; import RightPanel from "./components/Panels/RightPanel"; import BottomPanel from "./components/Timeline/BottomPanel"; import DocumentTabs from "./components/Workspace/DocumentTabs"; -import Toolbar from "./components/Toolbar/Toolbar"; import MenuBar from "./components/Menu/MenuBar"; import DocumentManager from "./components/Workspace/DocumentManager"; import KeyboardShortcutsHelp from "./components/Common/KeyboardShortcutsHelp"; @@ -24,9 +23,8 @@ import type { ExportOptions } from "./utils/graphExport"; * * Layout: * - Header with title - * - Menu bar (File, Edit, View) + * - Menu bar (File, Edit, View) with undo/redo controls * - Document tabs for multi-file support - * - Toolbar for graph editing controls * - Main graph editor canvas * * Features: @@ -141,9 +139,6 @@ function AppContent() { {/* Document Tabs */} - {/* Toolbar */} - {activeDocumentId && } - {/* Main content area with side panels and bottom panel */}
{/* Top section: Left panel, graph editor, right panel */} diff --git a/src/components/Editor/GraphEditor.tsx b/src/components/Editor/GraphEditor.tsx index a8cb91b..09a2593 100644 --- a/src/components/Editor/GraphEditor.tsx +++ b/src/components/Editor/GraphEditor.tsx @@ -223,6 +223,20 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq getCurrentViewport, ]); + // Listen for custom event to close all menus (including context menus) + useEffect(() => { + const handleCloseAllMenus = (event: Event) => { + const customEvent = event as CustomEvent; + // Don't close if the event came from context menu itself (source: 'contextmenu') + if (customEvent.detail?.source !== 'contextmenu') { + setContextMenu(null); + } + }; + + window.addEventListener('closeAllMenus', handleCloseAllMenus); + return () => window.removeEventListener('closeAllMenus', handleCloseAllMenus); + }, []); + // Save viewport periodically (debounced) const handleViewportChange = useCallback( (_event: MouseEvent | TouchEvent | null, viewport: Viewport) => { @@ -455,6 +469,10 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq y: event.clientY, type: "pane", }); + // Close other menus when opening context menu (after state update) + setTimeout(() => { + window.dispatchEvent(new CustomEvent('closeAllMenus', { detail: { source: 'contextmenu' } })); + }, 0); }, []); // Handle right-click on node @@ -467,6 +485,10 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq type: "node", target: node, }); + // Close other menus when opening context menu (after state update) + setTimeout(() => { + window.dispatchEvent(new CustomEvent('closeAllMenus', { detail: { source: 'contextmenu' } })); + }, 0); }, [], ); @@ -481,6 +503,10 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq type: "edge", target: edge, }); + // Close other menus when opening context menu (after state update) + setTimeout(() => { + window.dispatchEvent(new CustomEvent('closeAllMenus', { detail: { source: 'contextmenu' } })); + }, 0); }, [], ); @@ -490,6 +516,8 @@ const GraphEditor = ({ onNodeSelect, onEdgeSelect, onAddNodeRequest, onExportReq if (contextMenu) { setContextMenu(null); } + // Close all menus (menu bar dropdowns and context menus) when clicking on the graph canvas + window.dispatchEvent(new Event('closeAllMenus')); }, [contextMenu]); // Shared node creation logic (used by context menu and left panel) diff --git a/src/components/Menu/MenuBar.tsx b/src/components/Menu/MenuBar.tsx index bc41400..2861367 100644 --- a/src/components/Menu/MenuBar.tsx +++ b/src/components/Menu/MenuBar.tsx @@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useWorkspaceStore } from '../../stores/workspaceStore'; import { useGraphWithHistory } from '../../hooks/useGraphWithHistory'; +import { useDocumentHistory } from '../../hooks/useDocumentHistory'; import DocumentManager from '../Workspace/DocumentManager'; import NodeTypeConfigModal from '../Config/NodeTypeConfig'; import EdgeTypeConfigModal from '../Config/EdgeTypeConfig'; @@ -51,6 +52,21 @@ const MenuBar: React.FC = ({ onOpenHelp, onFitView, onSelectAll, o } = useWorkspaceStore(); const { clearGraph } = useGraphWithHistory(); + const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useDocumentHistory(); + + // Listen for custom event to close all menus (e.g., from graph canvas clicks, context menu opens) + useEffect(() => { + const handleCloseMenuEvent = (event: Event) => { + const customEvent = event as CustomEvent; + // Don't close if the event came from MenuBar itself (source: 'menubar') + if (customEvent.detail?.source !== 'menubar') { + setActiveMenu(null); + } + }; + + window.addEventListener('closeAllMenus', handleCloseMenuEvent); + return () => window.removeEventListener('closeAllMenus', handleCloseMenuEvent); + }, []); // Close menu when clicking outside useEffect(() => { @@ -67,7 +83,17 @@ const MenuBar: React.FC = ({ onOpenHelp, onFitView, onSelectAll, o }, [activeMenu]); const toggleMenu = useCallback((menuName: string) => { - setActiveMenu((current) => (current === menuName ? null : menuName)); + setActiveMenu((current) => { + const newMenu = current === menuName ? null : menuName; + // When opening a menu (not closing), dispatch event to close context menus after state updates + if (newMenu !== null && current !== menuName) { + // Use setTimeout to dispatch after the render phase completes + setTimeout(() => { + window.dispatchEvent(new CustomEvent('closeAllMenus', { detail: { source: 'menubar' } })); + }, 0); + } + return newMenu; + }); }, []); const closeMenu = useCallback(() => { @@ -148,6 +174,16 @@ const MenuBar: React.FC = ({ onOpenHelp, onFitView, onSelectAll, o closeMenu(); }, [closeMenu]); + const handleUndo = useCallback(() => { + undo(); + closeMenu(); + }, [undo, closeMenu]); + + const handleRedo = useCallback(() => { + redo(); + closeMenu(); + }, [redo, closeMenu]); + const handleClearGraph = useCallback(async () => { const confirmed = await confirm({ title: 'Clear Current Graph', @@ -301,6 +337,31 @@ const MenuBar: React.FC = ({ onOpenHelp, onFitView, onSelectAll, o {activeMenu === 'edit' && (
+ + + +
+