Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 375 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@
"dependencies": {
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@tailwindcss/vite": "^4.1.14",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"lucide-react": "^0.544.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
Expand Down
137 changes: 118 additions & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,67 @@
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useShallow } from 'zustand/shallow';
import { ToastContainer, toast } from 'react-toastify';
import { Menu, X } from 'lucide-react';
import 'react-toastify/dist/ReactToastify.css';
import { Button } from '@/components/ui/button';
import { EntityTreeSidebar } from '@/components/EntityTreeSidebar';
import { EntityDetailPanel } from '@/components/EntityDetailPanel';
import { ServerConnectionDialog } from '@/components/ServerConnectionDialog';
import { SearchCommand } from '@/components/SearchCommand';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { useSearchShortcut } from '@/hooks/useSearchShortcut';
import { useAppStore } from '@/lib/store';

type ViewMode = 'entity' | 'faults-dashboard';

function App() {
const { isConnected, serverUrl, baseEndpoint, connect } = useAppStore(
const { isConnected, serverUrl, baseEndpoint, connect, clearSelection, selectedPath } = useAppStore(
useShallow((state) => ({
isConnected: state.isConnected,
serverUrl: state.serverUrl,
baseEndpoint: state.baseEndpoint,
connect: state.connect,
clearSelection: state.clearSelection,
selectedPath: state.selectedPath,
}))
);

const [showConnectionDialog, setShowConnectionDialog] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>('entity');
const [sidebarOpen, setSidebarOpen] = useState(true);
const autoConnectAttempted = useRef(false);

// Keyboard shortcut: Ctrl+K / Cmd+K to open search
const openSearch = useCallback(() => setShowSearch(true), []);
useSearchShortcut(openSearch);

// Handle faults dashboard navigation
const handleFaultsDashboardClick = useCallback(() => {
clearSelection();
setViewMode('faults-dashboard');
// Close sidebar on mobile when navigating
if (window.innerWidth < 768) {
setSidebarOpen(false);
}
}, [clearSelection]);

// When entity is selected, switch back to entity view
const handleEntitySelect = useCallback(() => {
setViewMode('entity');
// Close sidebar on mobile when selecting entity
if (window.innerWidth < 768) {
setSidebarOpen(false);
}
}, []);

// Close sidebar on mobile when entity is selected from search
useEffect(() => {
if (selectedPath && window.innerWidth < 768) {
setSidebarOpen(false);
}
}, [selectedPath]);

// Auto-connect on mount if we have a stored URL
useEffect(() => {
if (serverUrl && !isConnected && !autoConnectAttempted.current) {
Expand All @@ -35,23 +77,80 @@ function App() {
}, []);

return (
<div className="flex h-screen bg-background">
<EntityTreeSidebar onSettingsClick={() => setShowConnectionDialog(true)} />
<EntityDetailPanel onConnectClick={() => setShowConnectionDialog(true)} />
<ServerConnectionDialog open={showConnectionDialog} onOpenChange={setShowConnectionDialog} />
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="dark"
/>
</div>
<ErrorBoundary
onError={(error) => {
toast.error(`Application error: ${error.message}`);
}}
>
<div className="flex h-screen bg-background relative">
{/* Mobile menu toggle */}
<Button
variant="ghost"
size="icon"
className="fixed top-3 left-3 z-50 md:hidden"
onClick={() => setSidebarOpen(!sidebarOpen)}
aria-label={sidebarOpen ? 'Close menu' : 'Open menu'}
>
{sidebarOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</Button>

{/* Sidebar with responsive behavior */}
<div
className={`
fixed inset-y-0 left-0 z-40 w-80 transform transition-transform duration-200 ease-in-out
md:relative md:translate-x-0
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
<EntityTreeSidebar
onSettingsClick={() => setShowConnectionDialog(true)}
onFaultsDashboardClick={handleFaultsDashboardClick}
/>
</div>

{/* Overlay for mobile when sidebar is open */}
{sidebarOpen && (
<div
className="fixed inset-0 z-30 bg-black/50 md:hidden"
onClick={() => setSidebarOpen(false)}
onKeyDown={(event) => {
if (event.key === 'Escape') {
setSidebarOpen(false);
}
}}
role="button"
tabIndex={0}
aria-label="Close sidebar"
/>
)}

{/* Main content */}
<div className="flex-1 md:ml-0">
<ErrorBoundary>
<EntityDetailPanel
onConnectClick={() => setShowConnectionDialog(true)}
viewMode={viewMode}
onEntitySelect={handleEntitySelect}
/>
</ErrorBoundary>
</div>

<ServerConnectionDialog open={showConnectionDialog} onOpenChange={setShowConnectionDialog} />
<SearchCommand open={showSearch} onOpenChange={setShowSearch} />
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="dark"
/>
</div>
</ErrorBoundary>
);
}

Expand Down
Loading