Skip to content

Commit 1bee111

Browse files
authored
Merge pull request #108 from NeuroJSON/staging
Enhance dataset view with improved readability, fallback summary, and keyword matching updates
2 parents 686dbcb + 7bcbd6b commit 1bee111

File tree

7 files changed

+417
-88
lines changed

7 files changed

+417
-88
lines changed

src/components/DatasetDetailPage/MetaDataPanel.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ParticipantsPreview from "./ParticipantsPreview";
12
import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
23
import {
34
Box,
@@ -190,13 +191,23 @@ const MetaDataPanel: React.FC<Props> = ({
190191
})()}
191192
</Typography>
192193
</Box>
193-
<Box>
194-
<Typography sx={{ color: Colors.darkPurple, fontWeight: "600" }}>
195-
Subjects
196-
</Typography>
197-
<Typography sx={{ color: "text.secondary" }}>
198-
{dbViewInfo?.rows?.[0]?.value?.subj?.length ?? "N/A"}
199-
</Typography>
194+
<Box
195+
sx={{
196+
display: "flex",
197+
flexDirection: "row",
198+
gap: 2,
199+
alignItems: "flex-start",
200+
}}
201+
>
202+
<Box>
203+
<Typography sx={{ color: Colors.darkPurple, fontWeight: "600" }}>
204+
Subjects
205+
</Typography>
206+
<Typography sx={{ color: "text.secondary" }}>
207+
{dbViewInfo?.rows?.[0]?.value?.subj?.length ?? "N/A"}
208+
</Typography>
209+
</Box>
210+
<ParticipantsPreview datasetDocument={datasetDocument} />
200211
</Box>
201212
<Box>
202213
<Typography sx={{ color: Colors.darkPurple, fontWeight: "600" }}>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { makeParticipantsTable } from "../../utils/DatasetDetailPageFunctions/participants";
2+
import {
3+
Box,
4+
Button,
5+
Dialog,
6+
DialogContent,
7+
DialogTitle,
8+
Table,
9+
TableBody,
10+
TableCell,
11+
TableContainer,
12+
TableHead,
13+
TableRow,
14+
Paper,
15+
} from "@mui/material";
16+
import { Colors } from "design/theme";
17+
import React, { useMemo, useState } from "react";
18+
19+
type Props = {
20+
datasetDocument: any;
21+
};
22+
23+
const ParticipantsPreview: React.FC<Props> = ({ datasetDocument }) => {
24+
const [open, setOpen] = useState(false);
25+
26+
const table = useMemo(() => {
27+
const part = datasetDocument?.["participants.tsv"];
28+
return makeParticipantsTable(part);
29+
}, [datasetDocument]);
30+
31+
if (!table) return null; // No participants.tsv found
32+
33+
return (
34+
<>
35+
<Box>
36+
<Button
37+
variant="outlined"
38+
size="small"
39+
onClick={() => setOpen(true)}
40+
sx={{
41+
color: Colors.purple,
42+
borderColor: Colors.purple,
43+
"&:hover": {
44+
color: Colors.secondaryPurple,
45+
transform: "scale(1.01)",
46+
borderColor: Colors.purple,
47+
},
48+
}}
49+
>
50+
Participants Table Preview
51+
</Button>
52+
</Box>
53+
54+
<Dialog
55+
open={open}
56+
onClose={() => setOpen(false)}
57+
maxWidth="md"
58+
fullWidth
59+
>
60+
<DialogTitle>participants.tsv</DialogTitle>
61+
<DialogContent dividers>
62+
<TableContainer component={Paper}>
63+
<Table size="small">
64+
<TableHead>
65+
<TableRow>
66+
{table.columns.map((col) => (
67+
<TableCell
68+
key={col}
69+
sx={{
70+
fontWeight: "bold",
71+
backgroundColor: Colors.darkPurple,
72+
color: Colors.white,
73+
}}
74+
>
75+
{col.replace(/_/g, " ")}
76+
</TableCell>
77+
))}
78+
</TableRow>
79+
</TableHead>
80+
<TableBody>
81+
{table.rows.map((row, rowIdx) => (
82+
<TableRow key={rowIdx}>
83+
{table.columns.map((col) => (
84+
<TableCell key={col}>{row[col]}</TableCell>
85+
))}
86+
</TableRow>
87+
))}
88+
</TableBody>
89+
</Table>
90+
</TableContainer>
91+
</DialogContent>
92+
</Dialog>
93+
</>
94+
);
95+
};
96+
97+
export default ParticipantsPreview;

src/components/SearchPage/DatabaseCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const DatabaseCard: React.FC<Props> = ({
3939
}) => {
4040
const dispatch = useAppDispatch();
4141
const dbInfo = useAppSelector((state: RootState) => state.neurojson.dbInfo);
42-
console.log("dbInfo", dbInfo);
42+
// console.log("dbInfo", dbInfo);
4343
useEffect(() => {
4444
if (dbId) {
4545
dispatch(fetchDbInfo(dbId.toLowerCase()));

src/components/SearchPage/DatasetCard.tsx

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Typography, Card, CardContent, Stack, Chip } from "@mui/material";
22
import { Colors } from "design/theme";
33
import React from "react";
4+
import { useMemo } from "react";
45
import { Link } from "react-router-dom";
56
import RoutesEnum from "types/routes.enum";
67

@@ -17,14 +18,73 @@ interface DatasetCardProps {
1718
info?: {
1819
Authors?: string[];
1920
DatasetDOI?: string;
21+
[k: string]: any;
2022
};
23+
[k: string]: any;
2124
};
2225
};
2326
index: number;
2427
onChipClick: (key: string, value: string) => void;
2528
keyword?: string; // for keyword highlight
2629
}
2730

31+
/** ---------- utility helpers ---------- **/
32+
const normalize = (s: string) =>
33+
s
34+
?.replace(/[\u2018\u2019\u2032]/g, "'") // curly → straight
35+
?.replace(/[\u201C\u201D\u2033]/g, '"') ?? // curly → straight
36+
"";
37+
38+
const containsKeyword = (text?: string, kw?: string) => {
39+
if (!text || !kw) return false;
40+
const t = normalize(text).toLowerCase();
41+
const k = normalize(kw).toLowerCase();
42+
return t.includes(k);
43+
};
44+
45+
/** Find a short snippet in secondary fields if not already visible */
46+
function findMatchSnippet(
47+
v: any,
48+
kw?: string
49+
): { label: string; html: string } | null {
50+
if (!kw) return null;
51+
52+
// Which fields to scan (can add/remove fields here)
53+
const CANDIDATE_FIELDS: Array<[string, (v: any) => string | undefined]> = [
54+
["Acknowledgements", (v) => v?.info?.Acknowledgements],
55+
[
56+
"Funding",
57+
(v) =>
58+
Array.isArray(v?.info?.Funding)
59+
? v.info.Funding.join(" ")
60+
: v?.info?.Funding,
61+
],
62+
["ReferencesAndLinks", (v) => v?.info?.ReferencesAndLinks],
63+
];
64+
65+
const k = normalize(kw).toLowerCase();
66+
67+
for (const [label, getter] of CANDIDATE_FIELDS) {
68+
const raw = getter(v); // v = parsedJson.value
69+
if (!raw) continue;
70+
const text = normalize(String(raw));
71+
const i = text.toLowerCase().indexOf(k); // k is the lowercase version of keyword
72+
if (i >= 0) {
73+
const start = Math.max(0, i - 40);
74+
const end = Math.min(text.length, i + k.length + 40);
75+
const before = text.slice(start, i);
76+
const hit = text.slice(i, i + k.length);
77+
const after = text.slice(i + k.length, end);
78+
const html = `${
79+
start > 0 ? "…" : ""
80+
}${before}<mark>${hit}</mark>${after}${end < text.length ? "…" : ""}`;
81+
return { label, html };
82+
}
83+
}
84+
return null;
85+
}
86+
/** ---------- end of helpers ---------- **/
87+
2888
const DatasetCard: React.FC<DatasetCardProps> = ({
2989
dbname,
3090
dsname,
@@ -40,7 +100,29 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
40100
const rawDOI = info?.DatasetDOI?.replace(/^doi:/, "");
41101
const doiLink = rawDOI ? `https://doi.org/${rawDOI}` : null;
42102

43-
// keyword hightlight functional component
103+
// precompute what’s visible & whether it already contains the keyword
104+
const authorsJoined = Array.isArray(info?.Authors)
105+
? info!.Authors.join(", ")
106+
: typeof info?.Authors === "string"
107+
? info!.Authors
108+
: "";
109+
110+
const visibleHasKeyword = useMemo(
111+
() =>
112+
containsKeyword(name, keyword) ||
113+
containsKeyword(readme, keyword) ||
114+
containsKeyword(authorsJoined, keyword),
115+
[name, readme, authorsJoined, keyword]
116+
);
117+
118+
// If not visible, produce a one-line snippet from other fields (for non-visible fields)
119+
const snippet = useMemo(
120+
() =>
121+
!visibleHasKeyword ? findMatchSnippet(parsedJson.value, keyword) : null,
122+
[parsedJson.value, keyword, visibleHasKeyword]
123+
);
124+
125+
// keyword hightlight functional component (only for visible fields)
44126
const highlightKeyword = (text: string, keyword?: string) => {
45127
if (!keyword || !text?.toLowerCase().includes(keyword.toLowerCase())) {
46128
return text;
@@ -99,7 +181,10 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
99181
{highlightKeyword(name || "Untitled Dataset", keyword)}
100182
</Typography>
101183
<Typography>
102-
Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname}
184+
{/* Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname} */}
185+
<strong>Database:</strong> {highlightKeyword(dbname, keyword)}
186+
{" "}&nbsp;&nbsp;|&nbsp;&nbsp;{" "}
187+
<strong>Dataset:</strong> {highlightKeyword(dsname, keyword)}
103188
</Typography>
104189

105190
<Stack spacing={2} margin={1}>
@@ -168,20 +253,35 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
168253
{info?.Authors && (
169254
<Typography variant="body2" mt={1}>
170255
<strong>Authors:</strong>{" "}
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-
)}
256+
{highlightKeyword(authorsJoined || "N/A", keyword)}
179257
</Typography>
180258
)}
181259
</Typography>
182260
)}
183261
</Stack>
184262

263+
{/* show why it matched if not visible in main fields */}
264+
{snippet && (
265+
<Stack direction="row" spacing={1} flexWrap="wrap" gap={1}>
266+
<Chip
267+
label={`Matched in ${snippet.label}`}
268+
size="small"
269+
sx={{
270+
height: 22,
271+
backgroundColor: "#f9f9ff",
272+
color: Colors.darkPurple,
273+
border: `1px solid ${Colors.lightGray}`,
274+
}}
275+
/>
276+
<Typography
277+
variant="body2"
278+
sx={{ mt: 0.5 }}
279+
// safe: snippet is derived from our own strings with <mark> only
280+
dangerouslySetInnerHTML={{ __html: snippet.html }}
281+
/>
282+
</Stack>
283+
)}
284+
185285
<Stack direction="row" spacing={1} flexWrap="wrap" gap={1}>
186286
{doiLink && (
187287
<Stack mt={1}>

src/pages/DatasetDetailPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ const DatasetDetailPage: React.FC = () => {
305305
useEffect(() => {
306306
if (datasetDocument) {
307307
// ✅ Extract External Data & Assign `index`
308-
console.log("datasetDocument", datasetDocument);
308+
// console.log("datasetDocument", datasetDocument);
309309
const links = extractDataLinks(datasetDocument, "").map(
310310
(link, index) => ({
311311
...link,

0 commit comments

Comments
 (0)