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 LeftPanel from "./components/Panels/LeftPanel";
|
||||||
import RightPanel from "./components/Panels/RightPanel";
|
import RightPanel from "./components/Panels/RightPanel";
|
||||||
import DocumentTabs from "./components/Workspace/DocumentTabs";
|
import DocumentTabs from "./components/Workspace/DocumentTabs";
|
||||||
|
import Toolbar from "./components/Toolbar/Toolbar";
|
||||||
import MenuBar from "./components/Menu/MenuBar";
|
import MenuBar from "./components/Menu/MenuBar";
|
||||||
import DocumentManager from "./components/Workspace/DocumentManager";
|
import DocumentManager from "./components/Workspace/DocumentManager";
|
||||||
import KeyboardShortcutsHelp from "./components/Common/KeyboardShortcutsHelp";
|
import KeyboardShortcutsHelp from "./components/Common/KeyboardShortcutsHelp";
|
||||||
|
|
@ -137,6 +138,9 @@ function AppContent() {
|
||||||
{/* Document Tabs */}
|
{/* Document Tabs */}
|
||||||
<DocumentTabs />
|
<DocumentTabs />
|
||||||
|
|
||||||
|
{/* Toolbar */}
|
||||||
|
{activeDocumentId && <Toolbar />}
|
||||||
|
|
||||||
{/* Main content area with side panels */}
|
{/* Main content area with side panels */}
|
||||||
<main className="flex-1 overflow-hidden flex">
|
<main className="flex-1 overflow-hidden flex">
|
||||||
{/* Left Panel */}
|
{/* Left Panel */}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ import { useCallback } from 'react';
|
||||||
import { IconButton, Tooltip } from '@mui/material';
|
import { IconButton, Tooltip } from '@mui/material';
|
||||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
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 ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
import { usePanelStore } from '../../stores/panelStore';
|
import { usePanelStore } from '../../stores/panelStore';
|
||||||
import { useGraphWithHistory } from '../../hooks/useGraphWithHistory';
|
import { useGraphWithHistory } from '../../hooks/useGraphWithHistory';
|
||||||
import { useEditorStore } from '../../stores/editorStore';
|
import { useEditorStore } from '../../stores/editorStore';
|
||||||
import { useDocumentHistory } from '../../hooks/useDocumentHistory';
|
|
||||||
import { createNode } from '../../utils/nodeUtils';
|
import { createNode } from '../../utils/nodeUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,7 +38,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
|
||||||
|
|
||||||
const { nodeTypes, edgeTypes, addNode } = useGraphWithHistory();
|
const { nodeTypes, edgeTypes, addNode } = useGraphWithHistory();
|
||||||
const { selectedRelationType, setSelectedRelationType } = useEditorStore();
|
const { selectedRelationType, setSelectedRelationType } = useEditorStore();
|
||||||
const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useDocumentHistory();
|
|
||||||
|
|
||||||
const handleAddNode = useCallback(
|
const handleAddNode = useCallback(
|
||||||
(nodeTypeId: string) => {
|
(nodeTypeId: string) => {
|
||||||
|
|
@ -78,17 +74,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
|
||||||
<ChevronRightIcon fontSize="small" />
|
<ChevronRightIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -111,58 +96,6 @@ const LeftPanel = ({ onDeselectAll, onAddNode }: LeftPanelProps) => {
|
||||||
|
|
||||||
{/* Scrollable content */}
|
{/* Scrollable content */}
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
<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 */}
|
{/* Add Actors Section */}
|
||||||
<div className="border-b border-gray-200">
|
<div className="border-b border-gray-200">
|
||||||
<button
|
<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 { useDocumentHistory } from '../../hooks/useDocumentHistory';
|
||||||
import { createNode } from '../../utils/nodeUtils';
|
|
||||||
import UndoIcon from '@mui/icons-material/Undo';
|
import UndoIcon from '@mui/icons-material/Undo';
|
||||||
import RedoIcon from '@mui/icons-material/Redo';
|
import RedoIcon from '@mui/icons-material/Redo';
|
||||||
import { Tooltip, IconButton } from '@mui/material';
|
import { Tooltip, IconButton } from '@mui/material';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toolbar - Graph editing tools
|
* Toolbar - Undo/Redo controls
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
* - Undo/Redo buttons with keyboard shortcuts
|
* - Undo/Redo buttons with keyboard shortcuts
|
||||||
* - Add Actor buttons (by type)
|
* - Shows operation descriptions
|
||||||
* - Relation type selector
|
|
||||||
* - Visual node type palette
|
|
||||||
*
|
*
|
||||||
* Usage: Placed below tabs, provides quick access to graph editing tools
|
* Usage: Placed below tabs, provides quick access to history controls
|
||||||
*/
|
*/
|
||||||
const Toolbar = () => {
|
const Toolbar = () => {
|
||||||
const { nodeTypes, edgeTypes, addNode } = useGraphWithHistory();
|
|
||||||
const { selectedRelationType, setSelectedRelationType } = useEditorStore();
|
|
||||||
const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useDocumentHistory();
|
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 (
|
return (
|
||||||
<div className="bg-white border-b border-gray-200 shadow-sm">
|
<div className="bg-white border-b border-gray-200 shadow-sm">
|
||||||
<div className="px-4 py-3">
|
<div className="px-4 py-2">
|
||||||
<div className="flex items-center space-x-6">
|
<div className="flex items-center space-x-2">
|
||||||
{/* Undo/Redo */}
|
{/* Undo/Redo */}
|
||||||
<div className="flex items-center space-x-1">
|
<Tooltip
|
||||||
<Tooltip
|
title={undoDescription ? `Undo: ${undoDescription} (Ctrl+Z)` : 'Undo (Ctrl+Z)'}
|
||||||
title={undoDescription ? `Undo: ${undoDescription} (Ctrl+Z)` : 'Undo (Ctrl+Z)'}
|
arrow
|
||||||
arrow
|
>
|
||||||
>
|
<span>
|
||||||
<span>
|
<IconButton
|
||||||
<IconButton
|
onClick={undo}
|
||||||
onClick={undo}
|
disabled={!canUndo}
|
||||||
disabled={!canUndo}
|
size="small"
|
||||||
size="small"
|
sx={{
|
||||||
sx={{
|
'&:disabled': {
|
||||||
'&:disabled': {
|
opacity: 0.4,
|
||||||
opacity: 0.4,
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<UndoIcon fontSize="small" />
|
||||||
<UndoIcon fontSize="small" />
|
</IconButton>
|
||||||
</IconButton>
|
</span>
|
||||||
</span>
|
</Tooltip>
|
||||||
</Tooltip>
|
<Tooltip
|
||||||
<Tooltip
|
title={redoDescription ? `Redo: ${redoDescription} (Ctrl+Y)` : 'Redo (Ctrl+Y)'}
|
||||||
title={redoDescription ? `Redo: ${redoDescription} (Ctrl+Y)` : 'Redo (Ctrl+Y)'}
|
arrow
|
||||||
arrow
|
>
|
||||||
>
|
<span>
|
||||||
<span>
|
<IconButton
|
||||||
<IconButton
|
onClick={redo}
|
||||||
onClick={redo}
|
disabled={!canRedo}
|
||||||
disabled={!canRedo}
|
size="small"
|
||||||
size="small"
|
sx={{
|
||||||
sx={{
|
'&:disabled': {
|
||||||
'&:disabled': {
|
opacity: 0.4,
|
||||||
opacity: 0.4,
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<RedoIcon fontSize="small" />
|
||||||
<RedoIcon fontSize="small" />
|
</IconButton>
|
||||||
</IconButton>
|
</span>
|
||||||
</span>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add Actor */}
|
{/* Description text */}
|
||||||
<div className="flex items-center space-x-2 border-l border-gray-300 pl-6">
|
{undoDescription && (
|
||||||
<h2 className="text-sm font-semibold text-gray-700">Add Actor:</h2>
|
<span className="text-xs text-gray-500 ml-2">
|
||||||
<div className="flex space-x-2">
|
Next: {undoDescription}
|
||||||
{nodeTypes.map((nodeType) => (
|
</span>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue