Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from 'react'; | |
| import { useGridStore } from './store/gridStore'; | |
| import { Grid } from './components/Grid/Grid'; | |
| import { GridGenerator } from './components/Controls/GridGenerator'; | |
| import { AlgorithmSelector } from './components/Controls/AlgorithmSelector'; | |
| import { PlaybackControls } from './components/Controls/PlaybackControls'; | |
| import { MetricsPanel } from './components/Stats/MetricsPanel'; | |
| import { ComparisonDashboard } from './components/Stats/ComparisonDashboard'; | |
| import { GroupInfo } from './components/Info/GroupInfo'; | |
| import { PlanResultsModal } from './components/Stats/PlanResultsModal'; | |
| import { Button } from './components/ui/button'; | |
| import { | |
| ChevronLeft, | |
| ChevronRight, | |
| Play, | |
| FileText, | |
| Eye, | |
| BarChart3, | |
| Truck, | |
| AlertCircle, | |
| Users, | |
| Circle, | |
| Square, | |
| X, | |
| } from 'lucide-react'; | |
| type Tab = 'visualize' | 'compare' | 'info'; | |
| function App() { | |
| const [activeTab, setActiveTab] = useState<Tab>('visualize'); | |
| const [sidebarCollapsed, setSidebarCollapsed] = useState(false); | |
| const [showPlanModal, setShowPlanModal] = useState(false); | |
| const { | |
| grid, | |
| planResult, | |
| runPlan, | |
| runSearch, | |
| isLoading, | |
| error, | |
| } = useGridStore(); | |
| // Open modal when planResult is updated | |
| useEffect(() => { | |
| if (planResult) { | |
| setShowPlanModal(true); | |
| } | |
| }, [planResult]); | |
| const handleRunSearch = () => { | |
| if (!grid || grid.stores.length === 0 || grid.destinations.length === 0) return; | |
| const start = grid.stores[0].position; | |
| const goal = grid.destinations[0].position; | |
| runSearch(start, goal); | |
| }; | |
| const handleRunPlan = async () => { | |
| await runPlan(); | |
| }; | |
| return ( | |
| <div className="h-screen w-screen bg-zinc-950 text-zinc-100 flex flex-col overflow-hidden"> | |
| {/* Header */} | |
| <header className="bg-zinc-900/80 backdrop-blur-sm border-b border-zinc-800 px-6 py-3 flex items-center justify-between shrink-0"> | |
| <div className="flex items-center gap-4"> | |
| <div className="flex items-center gap-3"> | |
| <div className="p-2 bg-zinc-800 rounded-lg"> | |
| <Truck className="w-5 h-5 text-zinc-300" /> | |
| </div> | |
| <div> | |
| <h1 className="text-lg font-semibold text-zinc-100">Pathfinding Visualizer</h1> | |
| <p className="text-xs text-zinc-500">Search Algorithm Analysis</p> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Tab navigation */} | |
| <div className="flex gap-1 p-1 bg-zinc-800/50 rounded-lg"> | |
| <Button | |
| onClick={() => setActiveTab('visualize')} | |
| variant={activeTab === 'visualize' ? 'secondary' : 'ghost'} | |
| size="sm" | |
| className="gap-2" | |
| > | |
| <Eye className="w-4 h-4" /> | |
| Visualize | |
| </Button> | |
| <Button | |
| onClick={() => setActiveTab('compare')} | |
| variant={activeTab === 'compare' ? 'secondary' : 'ghost'} | |
| size="sm" | |
| className="gap-2" | |
| > | |
| <BarChart3 className="w-4 h-4" /> | |
| Compare | |
| </Button> | |
| <Button | |
| onClick={() => setActiveTab('info')} | |
| variant={activeTab === 'info' ? 'secondary' : 'ghost'} | |
| size="sm" | |
| className="gap-2" | |
| > | |
| <Users className="w-4 h-4" /> | |
| Group Info | |
| </Button> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {grid && ( | |
| <> | |
| <Button | |
| onClick={handleRunSearch} | |
| disabled={isLoading} | |
| variant="primary" | |
| size="sm" | |
| className="gap-2" | |
| > | |
| <Play className="w-4 h-4" /> | |
| {isLoading ? 'Running...' : 'Run Search for S1'} | |
| </Button> | |
| <Button | |
| onClick={handleRunPlan} | |
| disabled={isLoading} | |
| variant="outline" | |
| size="sm" | |
| className="gap-2" | |
| > | |
| <FileText className="w-4 h-4" /> | |
| Full Plan | |
| </Button> | |
| </> | |
| )} | |
| </div> | |
| </header> | |
| {/* Main content */} | |
| <div className="flex-1 flex overflow-hidden"> | |
| {/* Sidebar */} | |
| <aside className={`bg-zinc-900/50 border-r border-zinc-800 flex flex-col transition-all duration-300 ${sidebarCollapsed ? 'w-12' : 'w-80'}`}> | |
| <button | |
| onClick={() => setSidebarCollapsed(!sidebarCollapsed)} | |
| className="p-3 text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800/50 border-b border-zinc-800 transition-colors" | |
| > | |
| {sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />} | |
| </button> | |
| {!sidebarCollapsed && ( | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4"> | |
| <GridGenerator /> | |
| <AlgorithmSelector /> | |
| <PlaybackControls /> | |
| <MetricsPanel /> | |
| </div> | |
| )} | |
| </aside> | |
| {/* Main area */} | |
| <main className="flex-1 flex flex-col overflow-hidden" style={{ backgroundColor: '#0a0a0b' }}> | |
| {/* Error display */} | |
| {error && ( | |
| <div className="m-4 mb-0 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-400 flex items-center gap-3 text-sm"> | |
| <AlertCircle className="w-4 h-4 shrink-0" /> | |
| <span>{error}</span> | |
| </div> | |
| )} | |
| {/* Content */} | |
| <div className="flex-1 overflow-hidden relative"> | |
| {activeTab === 'visualize' ? ( | |
| <> | |
| <Grid /> | |
| {/* Legend overlay */} | |
| <div className="absolute bottom-4 left-4 bg-zinc-900/95 backdrop-blur-sm border border-zinc-800 rounded-lg p-3 shadow-xl"> | |
| <p className="text-[10px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Legend</p> | |
| <div className="flex gap-4 text-xs"> | |
| <div className="flex flex-col gap-1.5"> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-amber-500 text-amber-500" /> | |
| <span className="text-zinc-400">Current</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-emerald-500 text-emerald-500" /> | |
| <span className="text-zinc-400">Path</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-sky-500 text-sky-500" /> | |
| <span className="text-zinc-400">Frontier</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-zinc-500 text-zinc-500" /> | |
| <span className="text-zinc-400">Explored</span> | |
| </div> | |
| </div> | |
| <div className="border-l border-zinc-800 pl-4 flex flex-col gap-1.5"> | |
| <div className="flex items-center gap-2"> | |
| <Square className="w-2.5 h-2.5 fill-blue-500 text-blue-500 rounded-sm" /> | |
| <span className="text-zinc-400">Store</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-emerald-500 text-emerald-500" /> | |
| <span className="text-zinc-400">Destination</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2.5 h-2.5 fill-purple-500 text-purple-500" /> | |
| <span className="text-zinc-400">Tunnel</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <X className="w-2.5 h-2.5 text-red-500" /> | |
| <span className="text-zinc-400">Blocked</span> | |
| </div> | |
| </div> | |
| <div className="border-l border-zinc-800 pl-4"> | |
| <p className="text-zinc-600 text-[10px] mb-1">Edge Costs</p> | |
| <div className="flex gap-2 text-xs font-mono"> | |
| <span className="text-zinc-300">1</span> | |
| <span className="text-zinc-400">2</span> | |
| <span className="text-zinc-500">3</span> | |
| <span className="text-zinc-600">4</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ) : activeTab === 'compare' ? ( | |
| <div className="h-full p-4"> | |
| <ComparisonDashboard /> | |
| </div> | |
| ) : ( | |
| <GroupInfo /> | |
| )} | |
| </div> | |
| </main> | |
| </div> | |
| {/* Status bar */} | |
| <footer className="bg-zinc-900/50 border-t border-zinc-800 px-6 py-2 flex items-center justify-between text-xs text-zinc-600 shrink-0"> | |
| <div className="flex items-center gap-4 font-mono"> | |
| {grid && ( | |
| <> | |
| <span>Grid {grid.width}x{grid.height}</span> | |
| <span className="text-zinc-700">|</span> | |
| <span>S:{grid.stores.length}</span> | |
| <span>D:{grid.destinations.length}</span> | |
| <span>T:{grid.tunnels.length}</span> | |
| </> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> | |
| <span>Connected</span> | |
| </div> | |
| </footer> | |
| {/* Plan Results Modal */} | |
| <PlanResultsModal | |
| isOpen={showPlanModal} | |
| onClose={() => setShowPlanModal(false)} | |
| /> | |
| </div> | |
| ); | |
| } | |
| export default App; | |