Skip to content

Commit 4ad1579

Browse files
authored
Merge pull request #27 from NeuroJSON/dev-fan
Feature: Filter on landing page to interact with nodes (Close #17)
2 parents bad854b + 601db39 commit 4ad1579

File tree

7 files changed

+459
-24
lines changed

7 files changed

+459
-24
lines changed

.github/workflows/build-deploy-zodiac.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- dev-fan
88
- dev_mario
99
- dev_fang
10+
- staging
1011

1112
jobs:
1213
deploy:

src/components/NodeInfoPanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
8181
anchor="right"
8282
open={open}
8383
onClose={onClose}
84-
container={document.body}
84+
ModalProps={{ keepMounted: true }} // Keeps Drawer in DOM even when closed
85+
disableEnforceFocus // prevents MUI from trapping focus inside the drawer
8586
sx={{
8687
"& .MuiDrawer-paper": {
8788
width: "30%",
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import KeywordFilter from "./KeywordFilter";
2+
import ModalitiesFilter from "./ModalitiesFilter";
3+
import FilterListIcon from "@mui/icons-material/FilterList";
4+
import {
5+
IconButton,
6+
Menu,
7+
MenuItem,
8+
Box,
9+
Typography,
10+
Divider,
11+
} from "@mui/material";
12+
import { Colors } from "design/theme";
13+
import React, { useState, useEffect } from "react";
14+
15+
interface FilterMenuProps {
16+
onKeywordFilter: (query: string) => void;
17+
onModalitiesFilter: (selectedModalities: string[]) => void;
18+
filterKeyword: string; // receive from parent
19+
homeSelectedModalities: string[]; // receive from parent
20+
}
21+
22+
const FilterMenu: React.FC<FilterMenuProps> = ({
23+
onKeywordFilter,
24+
onModalitiesFilter,
25+
filterKeyword, //receive from home parent
26+
homeSelectedModalities, // receive from parent
27+
}) => {
28+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
29+
// const [filterType, setFilterType] = useState<string | null>(null);
30+
const [menuKey, setMenuKey] = useState(0); // Forces re-render
31+
32+
useEffect(() => {
33+
const handleResize = () => {
34+
setMenuKey((prevKey) => prevKey + 1);
35+
};
36+
37+
window.addEventListener("resize", handleResize);
38+
return () => window.removeEventListener("resize", handleResize);
39+
}, []);
40+
41+
// Handle menu open and close
42+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
43+
setAnchorEl(event.currentTarget);
44+
};
45+
46+
const handleClose = () => {
47+
setAnchorEl(null);
48+
// setFilterType(null); //reset menu state when closing
49+
};
50+
51+
return (
52+
<Box>
53+
{/* Filter Icon Button */}
54+
<IconButton
55+
onClick={handleClick}
56+
sx={{
57+
color: Colors.lightGray,
58+
"&:hover": {
59+
color: Colors.green,
60+
backgroundColor: Colors.darkPurple,
61+
boxShadow: `0px 0px 15px ${Colors.darkGreen}`,
62+
padding: "10px",
63+
},
64+
}}
65+
>
66+
<FilterListIcon />
67+
{/* <Typography
68+
sx={{
69+
color: Colors.lightGray,
70+
fontWeight: "bold",
71+
}}
72+
>
73+
Databases Filter
74+
</Typography> */}
75+
</IconButton>
76+
77+
{/* Dropdown Menu */}
78+
<Menu
79+
anchorEl={anchorEl}
80+
open={Boolean(anchorEl)}
81+
onClose={handleClose}
82+
disablePortal // Ensures positioning inside the DOM
83+
sx={{
84+
transition: "transform 0.3s ease, opacity 0.3s ease",
85+
transformOrigin: "top right",
86+
"& .MuiPaper-root": {
87+
// backgroundColor: "rgba(40, 44, 86, 0.1)", // Override Paper's default background
88+
backgroundColor: Colors.lightGray,
89+
// boxShadow: `0px 0px 5px ${Colors.lightGray}`,
90+
backdropFilter: "blur(15px)",
91+
},
92+
}}
93+
>
94+
{/* unified panel */}
95+
<Box
96+
sx={{
97+
// backgroundColor: Colors.darkPurple,
98+
// backdropFilter: "blur(15px)",
99+
minWidth: 300,
100+
padding: "10px",
101+
}}
102+
>
103+
<Typography
104+
sx={{
105+
fontSize: "x-large",
106+
fontWeight: "bold",
107+
color: Colors.orange,
108+
}}
109+
>
110+
Databases Filter
111+
</Typography>
112+
113+
<Divider
114+
sx={{
115+
marginY: 2,
116+
// borderColor: Colors.darkPurple,
117+
}}
118+
/>
119+
120+
{/* Keyword Filter */}
121+
<Box
122+
sx={{
123+
display: "flex",
124+
flexDirection: "column",
125+
gap: "2px",
126+
}}
127+
>
128+
<Typography
129+
variant="subtitle1"
130+
sx={{ color: Colors.darkGreen, fontWeight: "bold" }}
131+
>
132+
Filter by Keyword
133+
</Typography>
134+
<KeywordFilter
135+
onFilter={onKeywordFilter}
136+
filterKeyword={filterKeyword}
137+
/>
138+
</Box>
139+
140+
<Divider
141+
sx={{
142+
marginY: 2,
143+
// borderColor: Colors.lightGray
144+
}}
145+
/>
146+
147+
{/* Modalities Filter */}
148+
<Box
149+
sx={{
150+
display: "flex",
151+
flexDirection: "column",
152+
gap: "2px",
153+
}}
154+
>
155+
<Typography
156+
variant="subtitle1"
157+
sx={{ color: Colors.darkGreen, fontWeight: "bold" }}
158+
>
159+
Filter by Modalities
160+
</Typography>
161+
<ModalitiesFilter
162+
onFilter={onModalitiesFilter}
163+
homeSeletedModalities={homeSelectedModalities}
164+
/>
165+
</Box>
166+
</Box>
167+
</Menu>
168+
</Box>
169+
);
170+
};
171+
172+
export default FilterMenu;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Autocomplete, TextField } from "@mui/material";
2+
import React, { useEffect, useState } from "react";
3+
4+
interface FilterSearchProps {
5+
onFilter: (query: string) => void;
6+
filterKeyword: string; // add prop to receive parent state
7+
}
8+
9+
const KeywordFilter: React.FC<FilterSearchProps> = ({
10+
onFilter,
11+
filterKeyword,
12+
}) => {
13+
const [inputValue, setInputValue] = useState<string>(filterKeyword);
14+
15+
useEffect(() => {
16+
setInputValue(filterKeyword);
17+
}, [filterKeyword]);
18+
19+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
20+
const value = e.target.value;
21+
setInputValue(value);
22+
onFilter(value); // Pass value to parent component
23+
};
24+
25+
return (
26+
<TextField
27+
label="Browse databases by keyword"
28+
variant="outlined"
29+
size="small"
30+
value={inputValue}
31+
onChange={handleChange}
32+
sx={{
33+
width: 250,
34+
background: "white",
35+
borderRadius: 1,
36+
}}
37+
/>
38+
);
39+
};
40+
41+
export default KeywordFilter;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
Box,
3+
FormControlLabel,
4+
Checkbox,
5+
Typography,
6+
Button,
7+
} from "@mui/material";
8+
import { Colors } from "design/theme";
9+
import { DATA_TYPE_COLORS } from "modules/universe/NeuroJsonGraph";
10+
import React, { useEffect, useState } from "react";
11+
12+
interface ModalitiesFilterProps {
13+
onFilter: (selectedModalities: string[]) => void;
14+
homeSeletedModalities: string[]; // add prop to receive parent state
15+
}
16+
17+
const modalitiesList = ["mri", "pet", "meg", "eeg", "ieeg", "dwi", "fnirs"];
18+
19+
const ModalitiesFilter: React.FC<ModalitiesFilterProps> = ({
20+
onFilter,
21+
homeSeletedModalities,
22+
}) => {
23+
const [selectedModalities, setSelectedModalities] = useState<string[]>(
24+
homeSeletedModalities
25+
);
26+
27+
useEffect(() => {
28+
setSelectedModalities(homeSeletedModalities);
29+
}, [homeSeletedModalities]);
30+
31+
const handleModalityChange = (modality: string) => {
32+
const updatedModalities = selectedModalities.includes(modality)
33+
? selectedModalities.filter((m) => m !== modality)
34+
: [...selectedModalities, modality];
35+
setSelectedModalities(updatedModalities);
36+
onFilter(updatedModalities);
37+
};
38+
39+
// reset function to clear all selected checkedboxes
40+
const handleReset = () => {
41+
setSelectedModalities([]); // clear the local state
42+
onFilter([]); // notify parent that selection is reset
43+
};
44+
45+
return (
46+
<Box>
47+
{modalitiesList.map((modality) => {
48+
const bgColor = DATA_TYPE_COLORS[modality]
49+
? `rgb(${DATA_TYPE_COLORS[modality].join(",")})`
50+
: "transparent";
51+
return (
52+
<Box
53+
key={modality}
54+
sx={{
55+
display: "flex",
56+
flexDirection: "column",
57+
}}
58+
>
59+
<FormControlLabel
60+
// key={modality}
61+
control={
62+
<Checkbox
63+
sx={
64+
{
65+
// color: Colors.lightGray,
66+
// "&.Mui-checked": {
67+
// color: "black", // Change checkmark color
68+
// },
69+
// "&.Mui-checked.MuiCheckbox-root": {
70+
// backgroundColor: "white", // Ensures white background when checked
71+
// borderRadius: "3px",
72+
// },
73+
// "&.MuiCheckbox-root:hover": {
74+
// backgroundColor: "white",
75+
// },
76+
}
77+
}
78+
checked={selectedModalities.includes(modality)}
79+
onChange={() => handleModalityChange(modality)}
80+
/>
81+
}
82+
label={
83+
<Typography
84+
sx={{
85+
color: Colors.lightGray,
86+
backgroundColor: bgColor,
87+
borderRadius: "5px",
88+
padding: "5px",
89+
marginBottom: "5px",
90+
fontWeight: "bold",
91+
minWidth: "45px",
92+
textAlign: "center",
93+
}}
94+
>
95+
{modality}
96+
</Typography>
97+
}
98+
/>
99+
</Box>
100+
);
101+
})}
102+
{/* Reset button */}
103+
<Button
104+
variant="contained"
105+
sx={{
106+
marginTop: "10px",
107+
backgroundColor: Colors.white,
108+
color: Colors.purple,
109+
fontWeight: "bold",
110+
"&:hover": {
111+
backgroundColor: Colors.darkPurple,
112+
color: Colors.green,
113+
boxShadow: `0px 0px 15px ${Colors.darkGreen}`,
114+
},
115+
}}
116+
onClick={handleReset}
117+
>
118+
Clear
119+
</Button>
120+
</Box>
121+
);
122+
};
123+
124+
export default ModalitiesFilter;

0 commit comments

Comments
 (0)