Skip to content

Commit 40e7499

Browse files
committed
block view
1 parent d787868 commit 40e7499

File tree

6 files changed

+402
-42
lines changed

6 files changed

+402
-42
lines changed

src/App.tsx

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,51 @@ import { AccountDetails } from './pages/AccountDetails'
1212
import { I3StatusBar } from './components/I3StatusBar'
1313
import {ExtrinsicDetails} from "./pages/ExtrinsicDetails.tsx";
1414
import { HelmetProvider } from 'react-helmet-async';
15+
import { BlockDetails } from './pages/BlockDetails.tsx'
16+
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'
17+
18+
const AppContent = () => {
19+
// Initialize keyboard navigation
20+
useKeyboardNavigation();
21+
22+
return (
23+
<>
24+
<ThreeJsTorus />
25+
<div style={{
26+
display: 'flex',
27+
width: '100vw',
28+
height: '100vh',
29+
padding: '10px',
30+
paddingBottom: '40px',
31+
backgroundColor: 'rgba(0,0,0,30%)',
32+
backgroundClip: 'content-box',
33+
position: 'relative',
34+
overflow: 'auto'
35+
}}>
36+
<I3StatusBar />
37+
<Sidebar />
38+
<Routes>
39+
<Route path="/" element={<Home />} />
40+
<Route path="/blocks" element={<Blocks />} />
41+
<Route path="/transfers" element={<Transfers />} />
42+
<Route path="/accounts" element={<Accounts />} />
43+
<Route path="/agents" element={<Agents />} />
44+
<Route path="/extrinsics" element={<Extrinsics />} />
45+
<Route path="/events" element={<Events />} />
46+
<Route path="/account/:address" element={ <AccountDetails />} />
47+
<Route path="/extrinsic/:id" element={ <ExtrinsicDetails />} />
48+
<Route path="/block/:height" element={ <BlockDetails />} />
49+
</Routes>
50+
</div>
51+
</>
52+
);
53+
};
1554

1655
const App = () => {
1756
return (
1857
<HelmetProvider>
1958
<Router>
20-
<ThreeJsTorus />
21-
<div style={{
22-
display: 'flex',
23-
width: '100vw',
24-
height: '100vh',
25-
padding: '10px',
26-
paddingBottom: '40px',
27-
backgroundColor: 'rgba(0,0,0,30%)',
28-
backgroundClip: 'content-box',
29-
position: 'relative',
30-
overflow: 'auto'
31-
}}>
32-
<I3StatusBar />
33-
<Sidebar />
34-
<Routes>
35-
<Route path="/" element={<Home />} />
36-
<Route path="/blocks" element={<Blocks />} />
37-
<Route path="/transfers" element={<Transfers />} />
38-
<Route path="/accounts" element={<Accounts />} />
39-
<Route path="/agents" element={<Agents />} />
40-
<Route path="/extrinsics" element={<Extrinsics />} />
41-
<Route path="/events" element={<Events />} />
42-
<Route path="/account/:address" element={ <AccountDetails />} />
43-
<Route path="/extrinsic/:id" element={ <ExtrinsicDetails />} />
44-
</Routes>
45-
</div>
59+
<AppContent />
4660
</Router>
4761
</HelmetProvider>
4862
)

src/components/ResponsiveAddress.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ export const ResponsiveAddress = ({ address }: ResponsiveAddressProps) => {
4848
}, [address, displayName]);
4949

5050
return (
51-
<div
52-
style={{width: '100%'}}
51+
<div
52+
style={{background: 'inherit'}}
5353
onMouseEnter={() => setIsHovered(true)}
5454
onMouseLeave={() => setIsHovered(false)}
5555
>
5656
<span ref={containerRef}>
5757
{!isHovered ? formattedAddress : (displayName || formattedAddress)}
5858
</span>
59-
</div>
59+
</div>
6060
);
6161
};

src/components/Sidebar.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const NavItem = styled(NavLink)<{ $isExpanded: boolean }>`
3939
4040
&.hovered {
4141
translate: -8px;
42-
4342
}
4443
4544
&.hovered:after {
@@ -137,17 +136,17 @@ export const Sidebar = () => {
137136
document.addEventListener('keydown', handleGlobalKeyDown);
138137
return () => document.removeEventListener('keydown', handleGlobalKeyDown);
139138
}, [navItems, isMobile]);
140-
141-
const handleKeyDown = (e: React.KeyboardEvent) => {
142-
if (e.key === 'ArrowDown') {
143-
setFocusedIndex(prev => (prev + 1) % navItems.length);
144-
} else if (e.key === 'ArrowUp') {
145-
setFocusedIndex(prev => (prev - 1 + navItems.length) % navItems.length);
146-
} else if (e.key === 'Enter') {
147-
const link = document.getElementById(`nav-item-${focusedIndex}`);
148-
link?.click();
149-
}
150-
};
139+
//
140+
// const handleKeyDown = (e: React.KeyboardEvent) => {
141+
// if (e.key === 'ArrowDown') {
142+
// setFocusedIndex(prev => (prev + 1) % navItems.length);
143+
// } else if (e.key === 'ArrowUp') {
144+
// setFocusedIndex(prev => (prev - 1 + navItems.length) % navItems.length);
145+
// } else if (e.key === 'Enter') {
146+
// const link = document.getElementById(`nav-item-${focusedIndex}`);
147+
// link?.click();
148+
// }
149+
// };
151150

152151
const renderLabel = (label: string, shortcutKey: string) => {
153152
const index = label.toLowerCase().indexOf(shortcutKey);
@@ -164,7 +163,6 @@ export const Sidebar = () => {
164163

165164
return (
166165
<SidebarContainer
167-
onKeyDown={handleKeyDown}
168166
tabIndex={0}
169167
$isExpanded={isExpanded || !isMobile}
170168
onClick={() => isMobile && setIsExpanded(!isExpanded)}

src/hooks/useKeyboardNavigation.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { useEffect, useState } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
4+
export const useKeyboardNavigation = () => {
5+
const [currentIndex, setCurrentIndex] = useState<number>(-1);
6+
const [links, setLinks] = useState<HTMLAnchorElement[]>([]);
7+
const location = useLocation();
8+
9+
// Reset navigation when route changes
10+
useEffect(() => {
11+
setCurrentIndex(-1);
12+
// Small delay to let the new page render
13+
setTimeout(() => {
14+
const newLinks = getAllVisibleLinks();
15+
setLinks(newLinks);
16+
}, 100);
17+
}, [location]);
18+
19+
const getAllVisibleLinks = () => {
20+
// Get all <a> elements
21+
const allLinks = Array.from(document.getElementsByTagName('a'));//Array.from([...Array.from(document.getElementsByTagName('a')), ...Array.from(document.getElementsByTagName('button'))]);
22+
23+
// Filter for actually clickable links
24+
return allLinks.filter(link => {
25+
// Get computed style
26+
const style = window.getComputedStyle(link);
27+
const rect = link.getBoundingClientRect();
28+
29+
// Check if the link is visible and clickable
30+
const isVisible = style.display !== 'none' &&
31+
style.visibility !== 'hidden' &&
32+
style.opacity !== '0' &&
33+
rect.width > 0 &&
34+
rect.height > 0;
35+
36+
// Check if it's an actual clickable link
37+
const isClickable = link.hasAttribute('href') &&
38+
!link.getAttribute('href')?.startsWith('#') &&
39+
!link.getAttribute('aria-hidden');
40+
41+
// Check if any parent is hidden
42+
let parent = link.parentElement;
43+
while (parent) {
44+
const parentStyle = window.getComputedStyle(parent);
45+
if (parentStyle.display === 'none' ||
46+
parentStyle.visibility === 'hidden' ||
47+
parentStyle.opacity === '0') {
48+
return false;
49+
}
50+
parent = parent.parentElement;
51+
}
52+
53+
// Check if this link is the actual target (not a parent of another link)
54+
const hasNestedLinks = link.getElementsByTagName('a').length > 0;
55+
56+
return isVisible && isClickable && !hasNestedLinks;
57+
});
58+
};
59+
60+
useEffect(() => {
61+
// Update links when DOM changes
62+
const updateLinks = () => {
63+
const newLinks = getAllVisibleLinks();
64+
if (JSON.stringify(newLinks.map(l => l.href)) !== JSON.stringify(links.map(l => l.href))) {
65+
setLinks(newLinks);
66+
// Reset index if current index is invalid
67+
if (currentIndex >= newLinks.length) {
68+
setCurrentIndex(-1);
69+
}
70+
}
71+
};
72+
73+
// Initial links collection
74+
updateLinks();
75+
76+
// Set up mutation observer to watch for DOM changes
77+
const observer = new MutationObserver(() => {
78+
requestAnimationFrame(updateLinks);
79+
});
80+
81+
observer.observe(document.body, {
82+
childList: true,
83+
subtree: true,
84+
attributes: true,
85+
attributeFilter: ['style', 'class', 'href']
86+
});
87+
88+
const handleKeyDown = (e: KeyboardEvent) => {
89+
// Don't handle if user is typing in an input or textarea
90+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
91+
return;
92+
}
93+
94+
// Get fresh list of links
95+
const currentLinks = getAllVisibleLinks();
96+
97+
switch (e.key) {
98+
case 'ArrowDown':
99+
case 'ArrowRight': {
100+
if (currentLinks.length === 0) return;
101+
102+
e.preventDefault();
103+
setCurrentIndex(prev => {
104+
const next = prev + 1;
105+
return next >= currentLinks.length ? 0 : next;
106+
});
107+
setLinks(currentLinks);
108+
break;
109+
}
110+
case 'ArrowUp':
111+
case 'ArrowLeft': {
112+
if (currentLinks.length === 0) return;
113+
114+
e.preventDefault();
115+
setCurrentIndex(prev => {
116+
const next = prev - 1;
117+
return next < 0 ? currentLinks.length - 1 : next;
118+
});
119+
setLinks(currentLinks);
120+
break;
121+
}
122+
case 'Enter':
123+
if (currentIndex >= 0 && currentIndex < currentLinks.length) {
124+
currentLinks[currentIndex].click();
125+
}
126+
break;
127+
}
128+
};
129+
130+
window.addEventListener('keydown', handleKeyDown);
131+
132+
return () => {
133+
window.removeEventListener('keydown', handleKeyDown);
134+
observer.disconnect();
135+
};
136+
}, [currentIndex, links]);
137+
138+
// Effect to handle focus and styling
139+
useEffect(() => {
140+
// Remove previous highlights
141+
document.querySelectorAll('a').forEach(link => {
142+
link.style.background = '';
143+
});
144+
145+
if (currentIndex >= 0 && currentIndex < links.length) {
146+
const currentLink = links[currentIndex];
147+
currentLink.style.background = 'rgba(0,255,0,0.7)';
148+
currentLink.scrollIntoView({ behavior: 'instant', block: 'nearest' });
149+
}
150+
}, [currentIndex, links]);
151+
152+
return { currentIndex };
153+
};

0 commit comments

Comments
 (0)