mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-27 07:43:41 +00:00
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:
parent
5aeb187efe
commit
ae334efd34
3 changed files with 53 additions and 189 deletions
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue