import { useState, useEffect, useCallback } from 'react' import './App.css' import TraceViewer from './components/TraceViewer' import PhaseViewer from './components/PhaseViewer' import HTTPRequestViewer from './components/httprequestviewer/HTTPRequestViewer' import RequestDebugger from './components/RequestDebugger' import TraceUpload from './components/TraceUpload' import TraceSelector from './components/TraceSelector' import { traceDatabase } from './utils/traceDatabase' import { useDatabaseTraceData } from './hooks/useDatabaseTraceData' type AppView = 'trace' | 'phases' | 'http' | 'debug' type AppMode = 'selector' | 'upload' | 'analysis' type ThreeDView = 'network' | 'timeline' | null // URL path utilities for /[traceid]/[view]/[3dview] routing const updateUrlWithTraceId = (traceId: string | null, view: AppView = 'http', threeDView: ThreeDView = null) => { if (!traceId) { window.history.pushState({}, '', '/') return } const path = threeDView ? `/${traceId}/${view}/${threeDView}` : `/${traceId}/${view}` window.history.pushState({}, '', path) } const getUrlParams = () => { const path = window.location.pathname const segments = path.split('/').filter(Boolean) // Remove empty segments if (segments.length >= 3) { const traceId = segments[0] const view = segments[1] as AppView const threeDView = segments[2] as ThreeDView // Validate view and 3D view values const validViews: AppView[] = ['trace', 'phases', 'http', 'debug'] const validThreeDViews: (ThreeDView)[] = ['network', 'timeline'] const validatedView = validViews.includes(view) ? view : 'http' const validatedThreeDView = validThreeDViews.includes(threeDView) ? threeDView : null return { traceId, view: validatedView, threeDView: validatedThreeDView } } else if (segments.length >= 2) { const traceId = segments[0] const view = segments[1] as AppView // Validate view is one of the allowed values const validViews: AppView[] = ['trace', 'phases', 'http', 'debug'] const validatedView = validViews.includes(view) ? view : 'http' return { traceId, view: validatedView, threeDView: null } } else if (segments.length === 1) { // Only trace ID provided, default to http view return { traceId: segments[0], view: 'http' as AppView, threeDView: null } } // Root path or invalid format return { traceId: null, view: 'http' as AppView, threeDView: null } } // Export utility functions for use in other components export { getUrlParams, updateUrlWithTraceId, type ThreeDView } function App() { const [mode, setMode] = useState('selector') const [currentView, setCurrentView] = useState('http') const [selectedTraceId, setSelectedTraceId] = useState(null) const [, setHasTraces] = useState(false) const [dbInitialized, setDbInitialized] = useState(false) // Always call hooks at the top level const { traceData } = useDatabaseTraceData(selectedTraceId) // Handle browser back/forward navigation const handlePopState = useCallback(() => { const { traceId, view } = getUrlParams() if (traceId && traceId !== selectedTraceId) { setSelectedTraceId(traceId) setCurrentView(view) setMode('analysis') // Note: threeDView will be handled by HTTPRequestViewer component } else if (!traceId && selectedTraceId) { setSelectedTraceId(null) setMode('selector') } }, [selectedTraceId]) useEffect(() => { initializeApp() // Listen for browser navigation (back/forward) window.addEventListener('popstate', handlePopState) return () => window.removeEventListener('popstate', handlePopState) }, [handlePopState]) const initializeApp = async () => { try { // Initialize the database await traceDatabase.init() setDbInitialized(true) // Check if we have any traces const traces = await traceDatabase.getAllTraces() setHasTraces(traces.length > 0) // Check URL for existing trace parameter const { traceId, view } = getUrlParams() if (traceId && traces.some(t => t.id === traceId)) { // Valid trace ID in URL, load it setSelectedTraceId(traceId) setCurrentView(view) setMode('analysis') // Note: threeDView will be handled by HTTPRequestViewer component } else if (traceId) { // Invalid trace ID in URL, clear it and show selector window.history.replaceState({}, '', '/') setMode(traces.length > 0 ? 'selector' : 'upload') } else if (traces.length === 0) { // No traces, show upload screen setMode('upload') } else { // Has traces, show selector setMode('selector') } } catch (error) { console.error('Failed to initialize database:', error) setMode('upload') // Fallback to upload mode setDbInitialized(true) } } const handleTraceSelect = (traceId: string) => { setSelectedTraceId(traceId) setCurrentView('http') // Default to HTTP view setMode('analysis') updateUrlWithTraceId(traceId, 'http', null) } const handleUploadSuccess = (traceId: string) => { setSelectedTraceId(traceId) setCurrentView('http') setMode('analysis') setHasTraces(true) updateUrlWithTraceId(traceId, 'http', null) } const handleBackToSelector = () => { setSelectedTraceId(null) setMode('selector') window.history.pushState({}, '', '/') } const handleUploadNew = () => { setMode('upload') } if (!dbInitialized) { return (
Initializing database...
) } if (mode === 'upload') { return } if (mode === 'selector') { return ( ) } // Analysis mode - show the main interface return ( <>

Perf Viz

{currentView === 'trace' && ( )} {currentView === 'phases' && ( )} {currentView === 'http' && ( )} {currentView === 'debug' && traceData && ( )}
) } export default App