refactor: move undo/redo to toolbar, simplify left panel

Reorganized the UI to improve clarity and reduce duplication:
- Reintroduced Toolbar component below document tabs
- Moved undo/redo controls from left panel to toolbar
- Simplified toolbar to show only undo/redo (removed actor/relation controls)
- Removed History section from left panel
- Left panel now focuses on content management (actors, relations, etc.)

The toolbar now provides a consistent, always-visible location for history
controls across the top of the editor, while the left panel is dedicated to
adding and managing graph content.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik Bruhn 2025-10-10 22:16:05 +02:00
parent 5aeb187efe
commit ae334efd34
3 changed files with 53 additions and 189 deletions

View file

@ -4,6 +4,7 @@ import GraphEditor from "./components/Editor/GraphEditor";
import LeftPanel from "./components/Panels/LeftPanel";
import RightPanel from "./components/Panels/RightPanel";
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";
@ -137,6 +138,9 @@ function AppContent() {
{/* Document Tabs */}
<DocumentTabs />
{/* Toolbar */}
{activeDocumentId && <Toolbar />}
{/* Main content area with side panels */}
<main className="flex-1 overflow-hidden flex">
{/* Left Panel */}

View file

@ -2,14 +2,11 @@ import { useCallback } from 'react';
import { IconButton, Tooltip } from '@mui/material';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import UndoIcon from '@mui/icons-material/Undo';
import RedoIcon from '@mui/icons-material/Redo';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { usePanelStore } from '../../stores/panelStore';
import { useGraphWithHistory } from '../../hooks/useGraphWithHistory';
import { useEditorStore } from '../../stores/editorStore';
import { useDocumentHistory } from '../../hooks/useDocumentHistory';
import { createNode } from '../../utils/nodeUtils';
/**
@ -41,7 +38,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
const { nodeTypes, edgeTypes, addNode } = useGraphWithHistory();
const { selectedRelationType, setSelectedRelationType } = useEditorStore();
const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useDocumentHistory();
const handleAddNode = useCallback(
(nodeTypeId: string) => {
@ -78,17 +74,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
<ChevronRightIcon fontSize="small" />
</IconButton>
</Tooltip>
{/* Icon indicators for quick reference */}
<div className="flex-1 flex flex-col items-center space-y-4 pt-4">
<Tooltip title={`Undo: ${undoDescription || 'Nothing to undo'}`} placement="right">
<span>
<IconButton size="small" onClick={undo} disabled={!canUndo}>
<UndoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</div>
</div>
);
}
@ -111,58 +96,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto overflow-x-hidden">
{/* History Section */}
<div className="border-b border-gray-200">
<button
onClick={() => toggleLeftPanelSection('history')}
className="w-full px-3 py-2 flex items-center justify-between bg-gray-50 hover:bg-gray-100 transition-colors"
>
<span className="text-xs font-semibold text-gray-700">History</span>
{leftPanelSections.history ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
</button>
{leftPanelSections.history && (
<div className="px-3 py-3 space-y-2">
<div className="flex items-center space-x-1">
<Tooltip
title={undoDescription ? `Undo: ${undoDescription}` : 'Undo (Ctrl+Z)'}
arrow
>
<span className="flex-1">
<button
onClick={undo}
disabled={!canUndo}
className="w-full flex items-center space-x-2 px-2 py-1.5 text-xs font-medium text-gray-700 bg-gray-50 rounded hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
<UndoIcon fontSize="small" />
<span>Undo</span>
</button>
</span>
</Tooltip>
<Tooltip
title={redoDescription ? `Redo: ${redoDescription}` : 'Redo (Ctrl+Y)'}
arrow
>
<span className="flex-1">
<button
onClick={redo}
disabled={!canRedo}
className="w-full flex items-center space-x-2 px-2 py-1.5 text-xs font-medium text-gray-700 bg-gray-50 rounded hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
<RedoIcon fontSize="small" />
<span>Redo</span>
</button>
</span>
</Tooltip>
</div>
{undoDescription && (
<p className="text-xs text-gray-500 italic">
Next: {undoDescription}
</p>
)}
</div>
)}
</div>
{/* Add Actors Section */}
<div className="border-b border-gray-200">
<button

View file

@ -1,143 +1,70 @@
import { useCallback, useEffect } from 'react';
import { useGraphWithHistory } from '../../hooks/useGraphWithHistory';
import { useEditorStore } from '../../stores/editorStore';
import { useDocumentHistory } from '../../hooks/useDocumentHistory';
import { createNode } from '../../utils/nodeUtils';
import UndoIcon from '@mui/icons-material/Undo';
import RedoIcon from '@mui/icons-material/Redo';
import { Tooltip, IconButton } from '@mui/material';
/**
* Toolbar - Graph editing tools
* Toolbar - Undo/Redo controls
*
* Features:
* - Undo/Redo buttons with keyboard shortcuts
* - Add Actor buttons (by type)
* - Relation type selector
* - Visual node type palette
* - Shows operation descriptions
*
* Usage: Placed below tabs, provides quick access to graph editing tools
* Usage: Placed below tabs, provides quick access to history controls
*/
const Toolbar = () => {
const { nodeTypes, edgeTypes, addNode } = useGraphWithHistory();
const { selectedRelationType, setSelectedRelationType } = useEditorStore();
const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useDocumentHistory();
// Set default relation type on mount or when edge types change
useEffect(() => {
if (!selectedRelationType && edgeTypes.length > 0) {
setSelectedRelationType(edgeTypes[0].id);
}
}, [edgeTypes, selectedRelationType, setSelectedRelationType]);
const handleAddNode = useCallback(
(nodeTypeId: string) => {
// Create node at center of viewport (approximate)
const position = {
x: Math.random() * 400 + 100,
y: Math.random() * 300 + 100,
};
const nodeTypeConfig = nodeTypes.find((nt) => nt.id === nodeTypeId);
const newNode = createNode(nodeTypeId, position, nodeTypeConfig);
addNode(newNode);
},
[addNode, nodeTypes]
);
const selectedEdgeTypeConfig = edgeTypes.find(et => et.id === selectedRelationType);
return (
<div className="bg-white border-b border-gray-200 shadow-sm">
<div className="px-4 py-3">
<div className="flex items-center space-x-6">
<div className="px-4 py-2">
<div className="flex items-center space-x-2">
{/* Undo/Redo */}
<div className="flex items-center space-x-1">
<Tooltip
title={undoDescription ? `Undo: ${undoDescription} (Ctrl+Z)` : 'Undo (Ctrl+Z)'}
arrow
>
<span>
<IconButton
onClick={undo}
disabled={!canUndo}
size="small"
sx={{
'&:disabled': {
opacity: 0.4,
}
}}
>
<UndoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip
title={redoDescription ? `Redo: ${redoDescription} (Ctrl+Y)` : 'Redo (Ctrl+Y)'}
arrow
>
<span>
<IconButton
onClick={redo}
disabled={!canRedo}
size="small"
sx={{
'&:disabled': {
opacity: 0.4,
}
}}
>
<RedoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</div>
<Tooltip
title={undoDescription ? `Undo: ${undoDescription} (Ctrl+Z)` : 'Undo (Ctrl+Z)'}
arrow
>
<span>
<IconButton
onClick={undo}
disabled={!canUndo}
size="small"
sx={{
'&:disabled': {
opacity: 0.4,
}
}}
>
<UndoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip
title={redoDescription ? `Redo: ${redoDescription} (Ctrl+Y)` : 'Redo (Ctrl+Y)'}
arrow
>
<span>
<IconButton
onClick={redo}
disabled={!canRedo}
size="small"
sx={{
'&:disabled': {
opacity: 0.4,
}
}}
>
<RedoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
{/* Add Actor */}
<div className="flex items-center space-x-2 border-l border-gray-300 pl-6">
<h2 className="text-sm font-semibold text-gray-700">Add Actor:</h2>
<div className="flex space-x-2">
{nodeTypes.map((nodeType) => (
<button
key={nodeType.id}
onClick={() => handleAddNode(nodeType.id)}
className="px-3 py-2 text-sm font-medium text-white rounded-md hover:opacity-90 transition-opacity focus:outline-none focus:ring-2 focus:ring-offset-2"
style={{ backgroundColor: nodeType.color }}
title={nodeType.description}
>
{nodeType.label}
</button>
))}
</div>
</div>
{/* Relation Type Selector */}
<div className="flex items-center space-x-2 border-l border-gray-300 pl-6">
<h2 className="text-sm font-semibold text-gray-700">Relation Type:</h2>
<select
value={selectedRelationType || ''}
onChange={(e) => setSelectedRelationType(e.target.value)}
className="px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
style={{
borderLeftWidth: '4px',
borderLeftColor: selectedEdgeTypeConfig?.color || '#6b7280'
}}
>
{edgeTypes.map((edgeType) => (
<option key={edgeType.id} value={edgeType.id}>
{edgeType.label}
</option>
))}
</select>
</div>
</div>
{/* Instructions */}
<div className="mt-3 text-xs text-gray-500">
<p>
Click a button to add an actor. Drag actors to position them. Select a relation type above, then click
and drag from a handle to create a relation between actors.
</p>
{/* Description text */}
{undoDescription && (
<span className="text-xs text-gray-500 ml-2">
Next: {undoDescription}
</span>
)}
</div>
</div>
</div>