Skip to content

Commit f24e270

Browse files
authored
Merge pull request #63 from NeuroJSON/dev-fan
Enhance Dataset Detail Page and Search Result UI: Clipboard Support, Syntax Highlighting, Layout Improvements, and Keyword Highlighting
2 parents af74f29 + f56807b commit f24e270

File tree

10 files changed

+1762
-1344
lines changed

10 files changed

+1762
-1344
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@
3838
"numjs": "^0.16.1",
3939
"pako": "1.0.11",
4040
"path-browserify": "^1.0.1",
41-
"pako": "^2.1.0",
4241
"query-string": "^8.1.0",
4342
"react": "^18.2.0",
4443
"react-dom": "^18.2.0",
4544
"react-json-view": "^1.21.3",
4645
"react-redux": "^8.1.2",
4746
"react-router-dom": "^6.15.0",
4847
"react-scripts": "^5.0.1",
48+
"react-syntax-highlighter": "^15.6.1",
4949
"sharp": "^0.33.5",
5050
"stats-js": "^1.0.1",
5151
"stats.js": "0.17.0",
@@ -94,7 +94,6 @@
9494
"postcss": "^8.4.31",
9595
"nth-check": "^2.0.1",
9696
"@babel/runtime": "7.26.10",
97-
"@babel/helpers": "7.26.10",
9897
"3d-force-graph": "1.74.6"
9998
}
10099
}

src/components/DatasetDetailPage/LoadDatasetTabs.tsx

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,22 @@ import { Tabs, Tab, Box, Typography, IconButton, Tooltip } from "@mui/material";
33
import { Colors } from "design/theme";
44
import React from "react";
55
import { useState } from "react";
6+
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
7+
import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash";
8+
import cpp from "react-syntax-highlighter/dist/esm/languages/hljs/cpp";
9+
import javascript from "react-syntax-highlighter/dist/esm/languages/hljs/javascript";
10+
import matlab from "react-syntax-highlighter/dist/esm/languages/hljs/matlab";
11+
import python from "react-syntax-highlighter/dist/esm/languages/hljs/python";
12+
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
613

14+
// import { Color } from "three";
15+
16+
// Register language theme
17+
SyntaxHighlighter.registerLanguage("python", python);
18+
SyntaxHighlighter.registerLanguage("bash", bash);
19+
SyntaxHighlighter.registerLanguage("cpp", cpp);
20+
SyntaxHighlighter.registerLanguage("matlab", matlab);
21+
SyntaxHighlighter.registerLanguage("javascript", javascript);
722
interface LoadDatasetTabsProps {
823
pagename: string;
924
docname: string;
@@ -58,9 +73,9 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
5873
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
5974
setTabIndex(newValue);
6075
};
61-
console.log("datasetDocument", datasetDocument);
76+
// console.log("datasetDocument", datasetDocument);
6277
const datasetDesc = datasetDocument?.["dataset_description.json"];
63-
console.log("datasetDesc", datasetDesc);
78+
// console.log("datasetDesc", datasetDesc);
6479
const datasetName = datasetDesc?.Name?.includes(" - ")
6580
? datasetDesc.Name.split(" - ")[1]
6681
: datasetDesc?.Name || datasetDocument?._id || docname;
@@ -84,22 +99,58 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
8499
);
85100
};
86101

87-
const CopyableCodeBlock = ({ code }: { code: string }) => {
88-
const handleCopy = () => {
89-
navigator.clipboard.writeText(code);
102+
const CopyableCodeBlock = ({
103+
code,
104+
language = "python",
105+
}: {
106+
code: string;
107+
language?: string;
108+
}) => {
109+
// const handleCopy = () => {
110+
// navigator.clipboard.writeText(code);
111+
// };
112+
const [copied, setCopied] = useState(false);
113+
const handleCopy = async () => {
114+
try {
115+
await navigator.clipboard.writeText(code);
116+
setCopied(true);
117+
setTimeout(() => setCopied(false), 3000); // reset after 3s
118+
} catch (err) {
119+
console.error("Failed to copy:", err);
120+
}
90121
};
122+
91123
return (
92124
<Box sx={{ position: "relative" }}>
93-
<IconButton
94-
onClick={handleCopy}
95-
size="small"
96-
sx={{ position: "absolute", top: 5, right: 5 }}
125+
<Tooltip title={copied ? "Copied!" : "Copy to clipboard"}>
126+
<IconButton
127+
onClick={handleCopy}
128+
size="small"
129+
sx={{ position: "absolute", top: 5, right: 5 }}
130+
>
131+
<ContentCopyIcon fontSize="small" sx={{ color: Colors.green }} />
132+
</IconButton>
133+
</Tooltip>
134+
<Box
135+
sx={{
136+
padding: { xs: "25px 20px 16px 16px", sm: "25px 20px 16px 16px" },
137+
borderRadius: "5px",
138+
fontSize: "16px",
139+
backgroundColor: Colors.black,
140+
overflowX: "auto",
141+
}}
97142
>
98-
<Tooltip title="Copy to clipboard">
99-
<ContentCopyIcon fontSize="small" />
100-
</Tooltip>
101-
</IconButton>
102-
<code style={flashcardStyles.codeBlock}>{code}</code>
143+
<SyntaxHighlighter
144+
language={language}
145+
style={atomOneDark}
146+
customStyle={{
147+
background: "transparent",
148+
margin: 0,
149+
}}
150+
>
151+
{code}
152+
</SyntaxHighlighter>
153+
</Box>
103154
</Box>
104155
);
105156
};
@@ -111,6 +162,7 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
111162
onChange={handleTabChange}
112163
variant="scrollable"
113164
scrollButtons="auto"
165+
allowScrollButtonsMobile
114166
sx={{
115167
"& .MuiTab-root": {
116168
color: Colors.lightGray, // default color
@@ -124,6 +176,12 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
124176
"& .MuiTabs-indicator": {
125177
backgroundColor: Colors.green,
126178
},
179+
"& .MuiTabs-scrollButtons": {
180+
color: Colors.lightGray, // scroll buttons color
181+
},
182+
"& .MuiTabs-scrollButtons.Mui-disabled": {
183+
opacity: 0.3, // darker when button disabled
184+
},
127185
}}
128186
>
129187
<Tab label="Python (REST API)" />
@@ -141,7 +199,10 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
141199
Load by URL with REST-API in Python
142200
</Typography>
143201
<Typography>Install:</Typography>
144-
<CopyableCodeBlock code={`pip install jdata bjdata numpy`} />
202+
<CopyableCodeBlock
203+
code={`pip install jdata bjdata numpy`}
204+
language="bash"
205+
/>
145206
<Typography>Load from URL:</Typography>
146207
<CopyableCodeBlock
147208
code={`import jdata as jd
@@ -152,6 +213,7 @@ links = jd.jsonpath(data, '$.._DataLink_')
152213
153214
# Download & cache anatomical nii.gz data for sub-01/sub-02
154215
jd.jdlink(links, {'regex': 'anat/sub-0[12]_.*\\.nii'})`}
216+
language="python"
155217
/>
156218
</Box>
157219
</TabPanel>
@@ -163,7 +225,10 @@ jd.jdlink(links, {'regex': 'anat/sub-0[12]_.*\\.nii'})`}
163225
Load by URL with REST-API in MATLAB
164226
</Typography>
165227
<Typography>Install:</Typography>
166-
<CopyableCodeBlock code={`Download and addpath to JSONLab`} />
228+
<CopyableCodeBlock
229+
code={`Download and addpath to JSONLab`}
230+
language="text"
231+
/>
167232
<Typography>Load from URL:</Typography>
168233
<CopyableCodeBlock
169234
code={`data = loadjson('${datasetUrl}');
@@ -176,6 +241,7 @@ links = jsonpath(data, '$.._DataLink_');
176241
177242
% Download & cache anatomical nii.gz data for sub-01/sub-02
178243
niidata = jdlink(links, 'regex', 'anat/sub-0[12]_.*\\.nii');`}
244+
language="matlab"
179245
/>
180246
</Box>
181247
</TabPanel>
@@ -187,9 +253,15 @@ niidata = jdlink(links, 'regex', 'anat/sub-0[12]_.*\\.nii');`}
187253
Use in MATLAB/Octave
188254
</Typography>
189255
<Typography>Load:</Typography>
190-
<CopyableCodeBlock code={`data = loadjd('${docname}.json');`} />
256+
<CopyableCodeBlock
257+
code={`data = loadjd('${docname}.json');`}
258+
language="matlab"
259+
/>
191260
<Typography>Read value:</Typography>
192-
<CopyableCodeBlock code={`data.(encodevarname('${onekey}'))`} />
261+
<CopyableCodeBlock
262+
code={`data.(encodevarname('${onekey}'))`}
263+
language="matlab"
264+
/>
193265
</Box>
194266
</TabPanel>
195267

@@ -203,9 +275,10 @@ niidata = jdlink(links, 'regex', 'anat/sub-0[12]_.*\\.nii');`}
203275
<CopyableCodeBlock
204276
code={`import jdata as jd
205277
data = jd.load('${docname}.json')`}
278+
language="python"
206279
/>
207280
<Typography>Read value:</Typography>
208-
<CopyableCodeBlock code={`data["${onekey}"]`} />
281+
<CopyableCodeBlock code={`data["${onekey}"]`} language="python" />
209282
</Box>
210283
</TabPanel>
211284

@@ -216,17 +289,24 @@ data = jd.load('${docname}.json')`}
216289
Use in C++
217290
</Typography>
218291
<Typography>Install:</Typography>
219-
<CopyableCodeBlock code={`Download JSON for Modern C++ json.hpp`} />
292+
<CopyableCodeBlock
293+
code={`Download JSON for Modern C++ json.hpp`}
294+
language="text"
295+
/>
220296
<Typography>Load:</Typography>
221297
<CopyableCodeBlock
222298
code={`#include "json.hpp"
223299
using json=nlohmann::ordered_json;
224300
225301
std::ifstream datafile("${docname}.json");
226302
json data(datafile);`}
303+
language="cpp"
227304
/>
228305
<Typography>Read value:</Typography>
229-
<CopyableCodeBlock code={`std::cout << data["${onekey}"];`} />
306+
<CopyableCodeBlock
307+
code={`std::cout << data["${onekey}"];`}
308+
language="cpp"
309+
/>
230310
</Box>
231311
</TabPanel>
232312

@@ -237,7 +317,10 @@ json data(datafile);`}
237317
Use in JS/Node.js
238318
</Typography>
239319
<Typography>Install:</Typography>
240-
<CopyableCodeBlock code={`npm install jda numjs pako atob`} />
320+
<CopyableCodeBlock
321+
code={`npm install jda numjs pako atob`}
322+
language="bash"
323+
/>
241324
<Typography>Load:</Typography>
242325
<CopyableCodeBlock
243326
code={`const fs = require("fs");
@@ -248,9 +331,13 @@ const fn = "${docname}.json";
248331
var jstr = fs.readFileSync(fn).toString().replace(/\\n/g, "");
249332
var data = new jd(JSON.parse(jstr));
250333
data = data.decode();`}
334+
language="javascript"
251335
/>
252336
<Typography>Read value:</Typography>
253-
<CopyableCodeBlock code={`console.log(data.data["${onekey}"]);`} />
337+
<CopyableCodeBlock
338+
code={`console.log(data.data["${onekey}"]);`}
339+
language="javascript"
340+
/>
254341
</Box>
255342
</TabPanel>
256343
</>

src/components/NavBar/NavItems.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ const NavItems: React.FC = () => {
5454
variant="h1"
5555
sx={{
5656
color: Colors.yellow,
57+
fontSize: {
58+
xs: "2.2rem", // font size on mobile
59+
sm: "2.5rem",
60+
},
5761
}}
5862
>
5963
NeuroJSON.io
@@ -62,6 +66,10 @@ const NavItems: React.FC = () => {
6266
variant="h2"
6367
sx={{
6468
color: Colors.lightGray,
69+
fontSize: {
70+
xs: "1rem", // font size on mobile
71+
sm: "1.2rem",
72+
},
6573
}}
6674
>
6775
Free Data Worth Sharing

src/components/SearchPage/DatasetCard.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface DatasetCardProps {
2222
};
2323
index: number;
2424
onChipClick: (key: string, value: string) => void;
25+
keyword?: string; // for keyword highlight
2526
}
2627

2728
const DatasetCard: React.FC<DatasetCardProps> = ({
@@ -30,6 +31,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
3031
parsedJson,
3132
index,
3233
onChipClick,
34+
keyword,
3335
}) => {
3436
const { name, readme, modality, subj, info } = parsedJson.value;
3537
const datasetLink = `${RoutesEnum.DATABASES}/${dbname}/${dsname}`;
@@ -38,6 +40,33 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
3840
const rawDOI = info?.DatasetDOI?.replace(/^doi:/, "");
3941
const doiLink = rawDOI ? `https://doi.org/${rawDOI}` : null;
4042

43+
// keyword hightlight functional component
44+
const highlightKeyword = (text: string, keyword?: string) => {
45+
if (!keyword || !text?.toLowerCase().includes(keyword.toLowerCase())) {
46+
return text;
47+
}
48+
49+
const regex = new RegExp(`(${keyword})`, "gi"); // for case-insensitive and global
50+
const parts = text.split(regex);
51+
52+
return (
53+
<>
54+
{parts.map((part, i) =>
55+
part.toLowerCase() === keyword.toLowerCase() ? (
56+
<mark
57+
key={i}
58+
style={{ backgroundColor: "yellow", fontWeight: 600 }}
59+
>
60+
{part}
61+
</mark>
62+
) : (
63+
<React.Fragment key={i}>{part}</React.Fragment>
64+
)
65+
)}
66+
</>
67+
);
68+
};
69+
4170
return (
4271
<Card sx={{ mb: 3, position: "relative" }}>
4372
<CardContent>
@@ -67,7 +96,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
6796
to={datasetLink}
6897
target="_blank"
6998
>
70-
{name || "Untitled Dataset"}
99+
{highlightKeyword(name || "Untitled Dataset", keyword)}
71100
</Typography>
72101
<Typography>
73102
Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname}
@@ -91,7 +120,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
91120
key={idx}
92121
label={mod}
93122
variant="outlined"
94-
onClick={() => onChipClick("modality", mod)} //
123+
onClick={() => onChipClick("modality", mod)}
95124
sx={{
96125
"& .MuiChip-label": {
97126
paddingX: "6px",
@@ -129,7 +158,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
129158
paragraph
130159
sx={{ textOverflow: "ellipsis" }}
131160
>
132-
<strong>Summary:</strong> {readme}
161+
<strong>Summary:</strong> {highlightKeyword(readme, keyword)}
133162
</Typography>
134163
)}
135164
</Stack>
@@ -139,11 +168,14 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
139168
{info?.Authors && (
140169
<Typography variant="body2" mt={1}>
141170
<strong>Authors:</strong>{" "}
142-
{Array.isArray(info.Authors)
143-
? info.Authors.join(", ")
144-
: typeof info.Authors === "string"
145-
? info.Authors
146-
: "N/A"}
171+
{highlightKeyword(
172+
Array.isArray(info.Authors)
173+
? info.Authors.join(", ")
174+
: typeof info.Authors === "string"
175+
? info.Authors
176+
: "N/A",
177+
keyword
178+
)}
147179
</Typography>
148180
)}
149181
</Typography>

0 commit comments

Comments
 (0)