Skip to content

Commit 959f927

Browse files
authored
Feature: Slide-In Panel for Home Page (#7)
* slide-in node info panel component structure completed * create a database info card in slide-in panel * add database size, database disk size, and links into the slide-in panel
1 parent 772c3cf commit 959f927

File tree

7 files changed

+319
-8
lines changed

7 files changed

+319
-8
lines changed

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
]
6666
},
6767
"resolutions": {
68-
"postcss": "^8.4.31",
69-
"nth-check": "^2.0.1"
70-
}
71-
68+
"postcss": "^8.4.31",
69+
"nth-check": "^2.0.1"
70+
}
7271
}

src/components/NodeInfoPanel.tsx

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import React, { useEffect } from "react";
2+
import { Box, Typography, IconButton, Drawer, Grid, Card, CardContent, CardActions, Button } from "@mui/material";
3+
import CloseIcon from "@mui/icons-material/Close";
4+
import { NodeObject } from "modules/universe/NeuroJsonGraph";
5+
import { Colors} from "design/theme";
6+
import { useNavigate } from "react-router-dom";
7+
import { useAppDispatch } from "hooks/useAppDispatch";
8+
import { useAppSelector } from "hooks/useAppSelector";
9+
import { RootState } from "redux/store";
10+
import { fetchDbInfo } from "redux/neurojson/neurojson.action";
11+
12+
13+
interface NodeInfoPanelProps {
14+
open: boolean;
15+
onClose: () => void;
16+
nodeData: NodeObject | null;
17+
}
18+
19+
// helper function to covert the database size format
20+
const formatSize = (bytes?: number): string =>{
21+
if (bytes === undefined) return "N/A";
22+
if (bytes >= 1_073_741_824) {
23+
return `${Math.floor(bytes / 1_073_741_824)} Gb`;
24+
} else if (bytes >= 1_048_576) {
25+
return `${Math.floor(bytes / 1_048_576)} Mb`;
26+
} else if (bytes >= 1024) {
27+
return `${Math.floor(bytes / 1024)} Kb`;
28+
} else {
29+
return `${bytes} Bytes`;
30+
}
31+
};
32+
33+
// 1 Kilobyte (KB) = 1,024 Bytes
34+
// 1 Megabyte (MB) = 1,024 KB = 1,048,576 Bytes (1024*1024)
35+
// 1 Gigabyte (GB) = 1,024 MB = 1,073,741,824 Bytes (1024*1024*1024)
36+
37+
const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({ open, onClose, nodeData }) => {
38+
const navigate = useNavigate();
39+
const dispatch = useAppDispatch();
40+
const dbInfo = useAppSelector((state: RootState) => state.neurojson.dbInfo);
41+
const loading = useAppSelector((state: RootState) => state.neurojson.loading);
42+
console.log("dbInfo", dbInfo);
43+
44+
useEffect(() => {
45+
if (nodeData?.id) {
46+
dispatch(fetchDbInfo(nodeData.id.toLowerCase()));
47+
}
48+
}, [nodeData, dispatch])
49+
50+
return (
51+
<Drawer
52+
anchor="right"
53+
open={open}
54+
onClose={onClose}
55+
container={document.body}
56+
sx={{
57+
"& .MuiDrawer-paper": {
58+
width: "30%",
59+
padding: "1rem",
60+
backgroundColor: Colors.lightGray,
61+
boxShadow: "-2px 0 8px rgba(0, 0, 0, 0.2)",
62+
},
63+
}}
64+
>
65+
<Box>
66+
{nodeData? (
67+
<>
68+
{/* Close Button */}
69+
<Box display="flex" justifyContent="space-between" alignItems="center">
70+
<Typography variant="h6" sx={{color: Colors.primary.dark}}>{nodeData.name}</Typography>
71+
<IconButton onClick={onClose} sx={{color: Colors.primary.main}}>
72+
<CloseIcon />
73+
</IconButton>
74+
</Box>
75+
{/* Node Metadata */}
76+
<Grid container spacing={2}>
77+
<Grid item xs={12}>
78+
<Typography>Website</Typography>
79+
<Typography>
80+
<a href={nodeData.url} target="_blank">{nodeData.url}</a>
81+
</Typography>
82+
</Grid>
83+
84+
<Grid item xs={12}>
85+
<Typography>Number of Datasets</Typography>
86+
<Typography>{nodeData.datasets}</Typography>
87+
</Grid>
88+
89+
<Grid item xs={12}>
90+
<Typography>Data Types</Typography>
91+
<Typography>{nodeData.datatype.join(", ")}</Typography>
92+
</Grid>
93+
94+
<Grid item xs={12}>
95+
<Typography>Data Standards</Typography>
96+
<Typography>{nodeData.standard.join(", ")}</Typography>
97+
</Grid>
98+
99+
<Grid item xs={12}>
100+
<Typography>Upstream Contact</Typography>
101+
<a href={`mailto:${nodeData.support}`} style={{ textDecoration: "none", color: "blue"}}>
102+
<Typography>{nodeData.support}</Typography>
103+
</a>
104+
</Grid>
105+
106+
<Grid item xs={12}>
107+
<Typography>NeuroJSON-Cuated Datasets</Typography>
108+
{dbInfo?(<Typography>{dbInfo.doc_count - 1}</Typography>) : "Coming soon "}
109+
</Grid>
110+
</Grid>
111+
{/*database info card*/}
112+
{dbInfo? (
113+
<Card sx={{ mt:2, backgroundColor: Colors.white }}>
114+
<CardContent>
115+
<Grid container spacing={1}>
116+
<Grid item xs={12}>
117+
<Typography>NeuroJSON.io Database Name</Typography>
118+
<Typography>{dbInfo.db_name}</Typography>
119+
</Grid>
120+
<Grid item xs={12}>
121+
<Typography>REST-API URL</Typography>
122+
<a href={`https://neurojson.io:7777/${dbInfo.db_name}`} target="_blank" rel="noopener noreferrer">
123+
{`https://neurojson.io:7777/${dbInfo.db_name}`}
124+
</a>
125+
</Grid>
126+
<Grid item xs={12}>
127+
<Typography>Database Creation Time</Typography>
128+
<Typography></Typography>
129+
</Grid>
130+
<Grid item xs={12}>
131+
<Typography>Searchable Database Size</Typography>
132+
<Typography>{formatSize(dbInfo.sizes?.external)}</Typography>
133+
</Grid>
134+
<Grid item xs={12}>
135+
<Typography>DatabaseDisk Size (compressed)</Typography>
136+
<Typography>{formatSize(dbInfo.sizes?.file)}</Typography>
137+
</Grid>
138+
</Grid>
139+
</CardContent>
140+
<CardActions>
141+
<Grid container direction="column" spacing={1}>
142+
<Grid item xs={12}>
143+
<Button
144+
variant="contained"
145+
fullWidth
146+
sx={{
147+
backgroundColor: Colors.primary.main,
148+
color: Colors.white,
149+
"&:hover": {
150+
backgroundColor: Colors.primary.dark,
151+
},
152+
}}
153+
onClick={() => navigate(`/databases/${nodeData.id}`)}
154+
>
155+
Browse Database
156+
</Button>
157+
</Grid>
158+
<Grid item xs={12}>
159+
<Button
160+
variant="contained"
161+
fullWidth
162+
sx={{
163+
backgroundColor: Colors.primary.main,
164+
color: Colors.white,
165+
"&:hover": {
166+
backgroundColor: Colors.primary.dark,
167+
},
168+
}}
169+
>
170+
Search Subjects
171+
</Button>
172+
</Grid>
173+
<Grid item xs={12}>
174+
<Button
175+
variant="contained"
176+
fullWidth
177+
sx={{
178+
backgroundColor: Colors.primary.main,
179+
color: Colors.white,
180+
"&:hover": {
181+
backgroundColor: Colors.primary.dark,
182+
},
183+
}}
184+
>
185+
Advanced Search
186+
</Button>
187+
</Grid>
188+
<Grid item xs={12}>
189+
<Button
190+
variant="contained"
191+
fullWidth
192+
sx={{
193+
backgroundColor: Colors.primary.main,
194+
color: Colors.white,
195+
"&:hover": {
196+
backgroundColor: Colors.primary.dark,
197+
},
198+
}}
199+
onClick={() => window.open(`https://github.com/NeuroJSON-io/${nodeData.id}`, "_blank", "noopener noreferrer")}
200+
>
201+
DownLoad Database
202+
</Button>
203+
</Grid>
204+
</Grid>
205+
</CardActions>
206+
207+
</Card>
208+
) : (
209+
<Typography>Select a node to see database info.</Typography>
210+
)}
211+
212+
</>
213+
) : (
214+
<Typography>Select a node to see database info.</Typography>
215+
)}
216+
</Box>
217+
</Drawer>
218+
)
219+
};
220+
221+
export default NodeInfoPanel;

src/modules/universe/NeuroJsonGraph.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export interface NodeObject {
1818
support: string;
1919
url: string;
2020
datasets: number;
21+
standard: string[]; // define type of standard property
2122
}
2223

23-
const NeuroJsonGraph: React.FC<{ registry: Database[] }> = ({ registry }) => {
24+
const NeuroJsonGraph: React.FC<{ registry: Database[], onNodeClick?: (node: NodeObject) => void }> = ({ registry, onNodeClick }) => {
2425
const navigate = useNavigate();
2526
const graphRef = useRef<HTMLDivElement>(null);
2627

@@ -57,6 +58,7 @@ const NeuroJsonGraph: React.FC<{ registry: Database[] }> = ({ registry }) => {
5758
url: db.url,
5859
datasets: db.datasets,
5960
size: size,
61+
standard: db.standard || [],// add standard property
6062
};
6163
}),
6264
links: registry.flatMap((db, index) => {
@@ -87,7 +89,10 @@ const NeuroJsonGraph: React.FC<{ registry: Database[] }> = ({ registry }) => {
8789
})
8890
.onNodeClick((node) => {
8991
const castNode = node as NodeObject;
90-
navigate(`/databases/${castNode.id}`);
92+
if (onNodeClick) {
93+
onNodeClick(castNode);
94+
}
95+
// navigate(`/databases/${castNode.id}`);
9196
})
9297
.nodeThreeObject((node) => {
9398
const castNode = node as NodeObject;

src/pages/Home.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,32 @@ import { Colors } from "design/theme";
1010
import { useAppDispatch } from "hooks/useAppDispatch";
1111
import { useAppSelector } from "hooks/useAppSelector";
1212
import NeuroJsonGraph from "modules/universe/NeuroJsonGraph";
13-
import React, { useEffect } from "react";
13+
import React, { useEffect, useState } from "react";
1414
import { useNavigate } from "react-router-dom";
1515
import { fetchRegistry } from "redux/neurojson/neurojson.action";
1616
import { NeurojsonSelector } from "redux/neurojson/neurojson.selector";
17+
import NodeInfoPanel from "components/NodeInfoPanel";
18+
import { NodeObject } from "modules/universe/NeuroJsonGraph";
1719

1820
const Home: React.FC = () => {
1921
const navigate = useNavigate();
2022
const dispatch = useAppDispatch();
2123
const { registry, loading } = useAppSelector(NeurojsonSelector);
2224

25+
// State for selected node and panel visibility
26+
const [selectedNode, setSelectedNode] = useState<NodeObject | null>(null);
27+
const [panelOpen, setPanelOpen] = useState(false);
28+
2329
useEffect(() => {
2430
dispatch(fetchRegistry());
2531
}, [dispatch]);
2632

33+
// Handle node click: Set selected node and open panel
34+
const handleNodeClick = (node: NodeObject) => {
35+
setSelectedNode(node);
36+
setPanelOpen(true);
37+
};
38+
2739
return (
2840
<Container
2941
style={{
@@ -47,7 +59,7 @@ const Home: React.FC = () => {
4759
<CircularProgress sx={{ color: Colors.primary.main }} />
4860
</Box>
4961
) : registry && registry.length > 0 ? (
50-
<NeuroJsonGraph registry={registry} />
62+
<NeuroJsonGraph registry={registry} onNodeClick={handleNodeClick} />
5163
) : (
5264
<Box sx={{ textAlign: "center", mt: 4 }}>
5365
<Typography variant="h6" color={Colors.textSecondary}>
@@ -101,6 +113,8 @@ const Home: React.FC = () => {
101113
</Button>
102114
</Box>
103115
</Box>
116+
117+
<NodeInfoPanel open={panelOpen} onClose={() => setPanelOpen(false)} nodeData={selectedNode} />
104118
</Container>
105119
);
106120
};

src/redux/neurojson/neurojson.slice.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const initialState: INeuroJsonState = {
1717
limit: 100,
1818
hasMore: true,
1919
registry: null,
20+
dbInfo: null, // add dbInfo in neurojson.interface.ts
2021
};
2122

2223
const neurojsonSlice = createSlice({
@@ -124,6 +125,7 @@ const neurojsonSlice = createSlice({
124125
})
125126
.addCase(fetchDbInfo.fulfilled, (state, action: PayloadAction<any>) => {
126127
state.loading = false;
128+
state.dbInfo = action.payload; // store database info in Redux
127129
})
128130
.addCase(fetchDbInfo.rejected, (state, action) => {
129131
state.loading = false;

src/redux/neurojson/types/neurojson.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface INeuroJsonState {
99
offset: number;
1010
limit: number;
1111
hasMore: boolean;
12+
dbInfo: DBParticulars | null; // add dbInfo type
1213
}
1314

1415
export interface DBParticulars {

0 commit comments

Comments
 (0)