Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@
"numjs": "^0.16.1",
"pako": "1.0.11",
"path-browserify": "^1.0.1",
"pako": "^2.1.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-json-view": "^1.21.3",
"react-redux": "^8.1.2",
"react-router-dom": "^6.15.0",
"react-scripts": "^5.0.1",
"react-syntax-highlighter": "^15.6.1",
"sharp": "^0.33.5",
"stats-js": "^1.0.1",
"stats.js": "0.17.0",
Expand Down Expand Up @@ -94,7 +94,6 @@
"postcss": "^8.4.31",
"nth-check": "^2.0.1",
"@babel/runtime": "7.26.10",
"@babel/helpers": "7.26.10",
"3d-force-graph": "1.74.6"
}
}
133 changes: 110 additions & 23 deletions src/components/DatasetDetailPage/LoadDatasetTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@ import { Tabs, Tab, Box, Typography, IconButton, Tooltip } from "@mui/material";
import { Colors } from "design/theme";
import React from "react";
import { useState } from "react";
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash";
import cpp from "react-syntax-highlighter/dist/esm/languages/hljs/cpp";
import javascript from "react-syntax-highlighter/dist/esm/languages/hljs/javascript";
import matlab from "react-syntax-highlighter/dist/esm/languages/hljs/matlab";
import python from "react-syntax-highlighter/dist/esm/languages/hljs/python";
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";

// import { Color } from "three";

// Register language theme
SyntaxHighlighter.registerLanguage("python", python);
SyntaxHighlighter.registerLanguage("bash", bash);
SyntaxHighlighter.registerLanguage("cpp", cpp);
SyntaxHighlighter.registerLanguage("matlab", matlab);
SyntaxHighlighter.registerLanguage("javascript", javascript);
interface LoadDatasetTabsProps {
pagename: string;
docname: string;
Expand Down Expand Up @@ -58,9 +73,9 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabIndex(newValue);
};
console.log("datasetDocument", datasetDocument);
// console.log("datasetDocument", datasetDocument);
const datasetDesc = datasetDocument?.["dataset_description.json"];
console.log("datasetDesc", datasetDesc);
// console.log("datasetDesc", datasetDesc);
const datasetName = datasetDesc?.Name?.includes(" - ")
? datasetDesc.Name.split(" - ")[1]
: datasetDesc?.Name || datasetDocument?._id || docname;
Expand All @@ -84,22 +99,58 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
);
};

const CopyableCodeBlock = ({ code }: { code: string }) => {
const handleCopy = () => {
navigator.clipboard.writeText(code);
const CopyableCodeBlock = ({
code,
language = "python",
}: {
code: string;
language?: string;
}) => {
// const handleCopy = () => {
// navigator.clipboard.writeText(code);
// };
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 3000); // reset after 3s
} catch (err) {
console.error("Failed to copy:", err);
}
};

return (
<Box sx={{ position: "relative" }}>
<IconButton
onClick={handleCopy}
size="small"
sx={{ position: "absolute", top: 5, right: 5 }}
<Tooltip title={copied ? "Copied!" : "Copy to clipboard"}>
<IconButton
onClick={handleCopy}
size="small"
sx={{ position: "absolute", top: 5, right: 5 }}
>
<ContentCopyIcon fontSize="small" sx={{ color: Colors.green }} />
</IconButton>
</Tooltip>
<Box
sx={{
padding: { xs: "25px 20px 16px 16px", sm: "25px 20px 16px 16px" },
borderRadius: "5px",
fontSize: "16px",
backgroundColor: Colors.black,
overflowX: "auto",
}}
>
<Tooltip title="Copy to clipboard">
<ContentCopyIcon fontSize="small" />
</Tooltip>
</IconButton>
<code style={flashcardStyles.codeBlock}>{code}</code>
<SyntaxHighlighter
language={language}
style={atomOneDark}
customStyle={{
background: "transparent",
margin: 0,
}}
>
{code}
</SyntaxHighlighter>
</Box>
</Box>
);
};
Expand All @@ -111,6 +162,7 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
sx={{
"& .MuiTab-root": {
color: Colors.lightGray, // default color
Expand All @@ -124,6 +176,12 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
"& .MuiTabs-indicator": {
backgroundColor: Colors.green,
},
"& .MuiTabs-scrollButtons": {
color: Colors.lightGray, // scroll buttons color
},
"& .MuiTabs-scrollButtons.Mui-disabled": {
opacity: 0.3, // darker when button disabled
},
}}
>
<Tab label="Python (REST API)" />
Expand All @@ -141,7 +199,10 @@ const LoadDatasetTabs: React.FC<LoadDatasetTabsProps> = ({
Load by URL with REST-API in Python
</Typography>
<Typography>Install:</Typography>
<CopyableCodeBlock code={`pip install jdata bjdata numpy`} />
<CopyableCodeBlock
code={`pip install jdata bjdata numpy`}
language="bash"
/>
<Typography>Load from URL:</Typography>
<CopyableCodeBlock
code={`import jdata as jd
Expand All @@ -152,6 +213,7 @@ links = jd.jsonpath(data, '$.._DataLink_')

# Download & cache anatomical nii.gz data for sub-01/sub-02
jd.jdlink(links, {'regex': 'anat/sub-0[12]_.*\\.nii'})`}
language="python"
/>
</Box>
</TabPanel>
Expand All @@ -163,7 +225,10 @@ jd.jdlink(links, {'regex': 'anat/sub-0[12]_.*\\.nii'})`}
Load by URL with REST-API in MATLAB
</Typography>
<Typography>Install:</Typography>
<CopyableCodeBlock code={`Download and addpath to JSONLab`} />
<CopyableCodeBlock
code={`Download and addpath to JSONLab`}
language="text"
/>
<Typography>Load from URL:</Typography>
<CopyableCodeBlock
code={`data = loadjson('${datasetUrl}');
Expand All @@ -176,6 +241,7 @@ links = jsonpath(data, '$.._DataLink_');

% Download & cache anatomical nii.gz data for sub-01/sub-02
niidata = jdlink(links, 'regex', 'anat/sub-0[12]_.*\\.nii');`}
language="matlab"
/>
</Box>
</TabPanel>
Expand All @@ -187,9 +253,15 @@ niidata = jdlink(links, 'regex', 'anat/sub-0[12]_.*\\.nii');`}
Use in MATLAB/Octave
</Typography>
<Typography>Load:</Typography>
<CopyableCodeBlock code={`data = loadjd('${docname}.json');`} />
<CopyableCodeBlock
code={`data = loadjd('${docname}.json');`}
language="matlab"
/>
<Typography>Read value:</Typography>
<CopyableCodeBlock code={`data.(encodevarname('${onekey}'))`} />
<CopyableCodeBlock
code={`data.(encodevarname('${onekey}'))`}
language="matlab"
/>
</Box>
</TabPanel>

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

Expand All @@ -216,17 +289,24 @@ data = jd.load('${docname}.json')`}
Use in C++
</Typography>
<Typography>Install:</Typography>
<CopyableCodeBlock code={`Download JSON for Modern C++ json.hpp`} />
<CopyableCodeBlock
code={`Download JSON for Modern C++ json.hpp`}
language="text"
/>
<Typography>Load:</Typography>
<CopyableCodeBlock
code={`#include "json.hpp"
using json=nlohmann::ordered_json;

std::ifstream datafile("${docname}.json");
json data(datafile);`}
language="cpp"
/>
<Typography>Read value:</Typography>
<CopyableCodeBlock code={`std::cout << data["${onekey}"];`} />
<CopyableCodeBlock
code={`std::cout << data["${onekey}"];`}
language="cpp"
/>
</Box>
</TabPanel>

Expand All @@ -237,7 +317,10 @@ json data(datafile);`}
Use in JS/Node.js
</Typography>
<Typography>Install:</Typography>
<CopyableCodeBlock code={`npm install jda numjs pako atob`} />
<CopyableCodeBlock
code={`npm install jda numjs pako atob`}
language="bash"
/>
<Typography>Load:</Typography>
<CopyableCodeBlock
code={`const fs = require("fs");
Expand All @@ -248,9 +331,13 @@ const fn = "${docname}.json";
var jstr = fs.readFileSync(fn).toString().replace(/\\n/g, "");
var data = new jd(JSON.parse(jstr));
data = data.decode();`}
language="javascript"
/>
<Typography>Read value:</Typography>
<CopyableCodeBlock code={`console.log(data.data["${onekey}"]);`} />
<CopyableCodeBlock
code={`console.log(data.data["${onekey}"]);`}
language="javascript"
/>
</Box>
</TabPanel>
</>
Expand Down
8 changes: 8 additions & 0 deletions src/components/NavBar/NavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ const NavItems: React.FC = () => {
variant="h1"
sx={{
color: Colors.yellow,
fontSize: {
xs: "2.2rem", // font size on mobile
sm: "2.5rem",
},
}}
>
NeuroJSON.io
Expand All @@ -62,6 +66,10 @@ const NavItems: React.FC = () => {
variant="h2"
sx={{
color: Colors.lightGray,
fontSize: {
xs: "1rem", // font size on mobile
sm: "1.2rem",
},
}}
>
Free Data Worth Sharing
Expand Down
48 changes: 40 additions & 8 deletions src/components/SearchPage/DatasetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface DatasetCardProps {
};
index: number;
onChipClick: (key: string, value: string) => void;
keyword?: string; // for keyword highlight
}

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

// keyword hightlight functional component
const highlightKeyword = (text: string, keyword?: string) => {
if (!keyword || !text?.toLowerCase().includes(keyword.toLowerCase())) {
return text;
}

const regex = new RegExp(`(${keyword})`, "gi"); // for case-insensitive and global
const parts = text.split(regex);

return (
<>
{parts.map((part, i) =>
part.toLowerCase() === keyword.toLowerCase() ? (
<mark
key={i}
style={{ backgroundColor: "yellow", fontWeight: 600 }}
>
{part}
</mark>
) : (
<React.Fragment key={i}>{part}</React.Fragment>
)
)}
</>
);
};

return (
<Card sx={{ mb: 3, position: "relative" }}>
<CardContent>
Expand Down Expand Up @@ -67,7 +96,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
to={datasetLink}
target="_blank"
>
{name || "Untitled Dataset"}
{highlightKeyword(name || "Untitled Dataset", keyword)}
</Typography>
<Typography>
Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname}
Expand All @@ -91,7 +120,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
key={idx}
label={mod}
variant="outlined"
onClick={() => onChipClick("modality", mod)} //
onClick={() => onChipClick("modality", mod)}
sx={{
"& .MuiChip-label": {
paddingX: "6px",
Expand Down Expand Up @@ -129,7 +158,7 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
paragraph
sx={{ textOverflow: "ellipsis" }}
>
<strong>Summary:</strong> {readme}
<strong>Summary:</strong> {highlightKeyword(readme, keyword)}
</Typography>
)}
</Stack>
Expand All @@ -139,11 +168,14 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
{info?.Authors && (
<Typography variant="body2" mt={1}>
<strong>Authors:</strong>{" "}
{Array.isArray(info.Authors)
? info.Authors.join(", ")
: typeof info.Authors === "string"
? info.Authors
: "N/A"}
{highlightKeyword(
Array.isArray(info.Authors)
? info.Authors.join(", ")
: typeof info.Authors === "string"
? info.Authors
: "N/A",
keyword
)}
</Typography>
)}
</Typography>
Expand Down
Loading