diff --git a/src/components/DatasetDetailPage/MetaDataPanel.tsx b/src/components/DatasetDetailPage/MetaDataPanel.tsx new file mode 100644 index 0000000..ad63381 --- /dev/null +++ b/src/components/DatasetDetailPage/MetaDataPanel.tsx @@ -0,0 +1,258 @@ +import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight"; +import { + Box, + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + Chip, + IconButton, + Tooltip, +} from "@mui/material"; +import { Colors } from "design/theme"; +import React, { useMemo, useState } from "react"; + +type Props = { + dbViewInfo: any; + datasetDocument: any; + dbName: string | undefined; + docId: string | undefined; +}; + +type RevInfo = { rev: string }; + +const MetaDataPanel: React.FC = ({ + dbViewInfo, + datasetDocument, + dbName, + docId, +}) => { + const revs: RevInfo[] = useMemo( + () => + Array.isArray(datasetDocument?.["_revs_info"]) + ? (datasetDocument!["_revs_info"] as RevInfo[]) + : [], + [datasetDocument] + ); + const [revIdx, setRevIdx] = useState(0); + const selected = revs[revIdx]; + + return ( + + + + + Modalities + + + {dbViewInfo?.rows?.[0]?.value?.modality?.join(", ") ?? "N/A"} + + + + + DOI + + + {(() => { + const doi = + datasetDocument?.["dataset_description.json"]?.DatasetDOI || + datasetDocument?.["dataset_description.json"]?.ReferenceDOI; + + if (!doi) return "N/A"; + + // Normalize into a clickable URL + let url = doi; + if (/^10\./.test(doi)) { + url = `https://doi.org/${doi}`; + } else if (/^doi:/.test(doi)) { + url = `https://doi.org/${doi.replace(/^doi:/, "")}`; + } + + return ( + + {url} + + ); + })()} + + + + + Subjects + + + {dbViewInfo?.rows?.[0]?.value?.subj?.length ?? "N/A"} + + + + + License + + + {datasetDocument?.["dataset_description.json"]?.License ?? "N/A"} + + + + + BIDS Version + + + {datasetDocument?.["dataset_description.json"]?.BIDSVersion ?? + "N/A"} + + + + + References and Links + + + {Array.isArray( + datasetDocument?.["dataset_description.json"]?.ReferencesAndLinks + ) + ? datasetDocument["dataset_description.json"].ReferencesAndLinks + .length > 0 + ? datasetDocument[ + "dataset_description.json" + ].ReferencesAndLinks.join(", ") + : "N/A" + : datasetDocument?.["dataset_description.json"] + ?.ReferencesAndLinks ?? "N/A"} + + + + {revs.length > 0 && ( + + + Revisions + + + + Select revision + + + + {selected && ( + + + + Selected rev: + + + {selected.rev} + + + + + window.open( + `https://neurojson.io:7777/${dbName}/${docId}?rev=${selected.rev}`, + "_blank" + ) + } + > + + + + + )} + + )} + + + ); +}; + +export default MetaDataPanel; diff --git a/src/components/DatasetPageCard.tsx b/src/components/DatasetPageCard.tsx index c3d9d17..e7aff0b 100644 --- a/src/components/DatasetPageCard.tsx +++ b/src/components/DatasetPageCard.tsx @@ -15,6 +15,24 @@ import { useNavigate } from "react-router-dom"; import { Row } from "redux/neurojson/types/neurojson.interface"; import RoutesEnum from "types/routes.enum"; +const formatSize = (sizeInBytes: number): string => { + if (sizeInBytes < 1024) { + return `${sizeInBytes} Bytes`; + } else if (sizeInBytes < 1024 * 1024) { + return `${(sizeInBytes / 1024).toFixed(1)} KB`; + } else if (sizeInBytes < 1024 * 1024 * 1024) { + return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`; + } else if (sizeInBytes < 1024 * 1024 * 1024 * 1024) { + return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; + } else { + return `${(sizeInBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`; + } +}; + +// for showing the size +const jsonBytes = (obj: unknown) => + obj ? new TextEncoder().encode(JSON.stringify(obj)).length : 0; + interface DatasetPageCardProps { doc: Row; index: number; @@ -32,6 +50,11 @@ const DatasetPageCard: React.FC = ({ }) => { const navigate = useNavigate(); const datasetIndex = (page - 1) * pageSize + index + 1; + const sizeInBytes = React.useMemo(() => { + const len = (doc as any)?.value?.length; // bytes from length key + if (typeof len === "number" && Number.isFinite(len)) return len; + return jsonBytes(doc.value); // fallback: summary object size + }, [doc.value]); return ( = ({ Size:{" "} - {doc.value.length + {/* {doc.value.length ? `${(doc.value.length / 1024 / 1024).toFixed(2)} MB` - : "Unknown"} + : "Unknown"} */} + {formatSize(sizeInBytes)} {doc.value.info?.DatasetDOI && ( diff --git a/src/components/SearchPage/DatabaseCard.tsx b/src/components/SearchPage/DatabaseCard.tsx new file mode 100644 index 0000000..0a76afb --- /dev/null +++ b/src/components/SearchPage/DatabaseCard.tsx @@ -0,0 +1,211 @@ +import { + Box, + Typography, + Chip, + Card, + CardContent, + Stack, + Avatar, +} from "@mui/material"; +import { Colors } from "design/theme"; +import React from "react"; +import { Link } from "react-router-dom"; +import RoutesEnum from "types/routes.enum"; +import { modalityValueToEnumLabel } from "utils/SearchPageFunctions/modalityLabels"; + +type Props = { + dbId?: string; + fullName?: string; + datasets?: number; + modalities?: string[]; + logo?: string; + keyword?: string; + onChipClick: (key: string, value: string) => void; +}; + +const DatabaseCard: React.FC = ({ + dbId, + fullName, + datasets, + modalities, + logo, + keyword, + onChipClick, +}) => { + const databaseLink = `${RoutesEnum.DATABASES}/${dbId}`; + // 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() ? ( + + {part} + + ) : ( + {part} + ) + )} + + ); + }; + + // for datatype rendering + const isClickableModality = (raw: string) => + !!modalityValueToEnumLabel[raw.toLowerCase()]; + + const renderDatatype = (raw: string, idx: number) => { + const key = raw.toLowerCase(); + const label = modalityValueToEnumLabel[key]; + + if (label) { + // Clickable modality → drives the "modality" filter + return ( + onChipClick("modality", key)} // pass normalized key + sx={{ + "& .MuiChip-label": { px: "6px", fontSize: "0.8rem" }, + height: 24, + color: Colors.darkPurple, + border: `1px solid ${Colors.darkPurple}`, + fontWeight: "bold", + transition: "all 0.2s ease", + "&:hover": { + backgroundColor: `${Colors.purple} !important`, + color: "white", + borderColor: Colors.purple, + }, + }} + /> + ); + } + + // Not a modality → render as plain text (or a disabled/outlined chip if you prefer) + return ( + + {raw} + + ); + }; + + return ( + + + + {/* Logo as Avatar */} + + {logo && ( + + )} + + {/* database card */} + + + Database:{" "} + {highlightKeyword(fullName || "Untitled Database", keyword)} + + + + + Data Type: + + + {Array.isArray(modalities) && modalities.length > 0 ? ( + modalities.map(renderDatatype) + ) : ( + // ( + // modalities.map((mod, idx) => ( + // onChipClick("modality", mod)} + // sx={{ + // "& .MuiChip-label": { + // paddingX: "6px", + // fontSize: "0.8rem", + // }, + // height: "24px", + // color: Colors.darkPurple, + // border: `1px solid ${Colors.darkPurple}`, + // fontWeight: "bold", + // transition: "all 0.2s ease", + // "&:hover": { + // backgroundColor: `${Colors.purple} !important`, + // color: "white", + // borderColor: Colors.purple, + // }, + // }} + // /> + // )) + // ) + + N/A + + )} + + + + + Datasets: {datasets ?? "N/A"} + + + + + + + + ); +}; + +export default DatabaseCard; diff --git a/src/pages/DatasetPage.tsx b/src/pages/DatasetPage.tsx index 978eb6d..21cd777 100644 --- a/src/pages/DatasetPage.tsx +++ b/src/pages/DatasetPage.tsx @@ -39,6 +39,20 @@ const DatasetPage: React.FC = () => { const totalPages = Math.ceil(limit / pageSize); const [searchQuery, setSearchQuery] = useState(""); + const formatSize = (sizeInBytes: number): string => { + if (sizeInBytes < 1024) { + return `${sizeInBytes} Bytes`; + } else if (sizeInBytes < 1024 * 1024) { + return `${(sizeInBytes / 1024).toFixed(1)} KB`; + } else if (sizeInBytes < 1024 * 1024 * 1024) { + return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`; + } else if (sizeInBytes < 1024 * 1024 * 1024 * 1024) { + return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; + } else { + return `${(sizeInBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`; + } + }; + useEffect(() => { if (dbName) { dispatch(fetchDbInfo(dbName.toLowerCase())); @@ -326,11 +340,12 @@ const DatasetPage: React.FC = () => { {filteredData.map((doc: any, index: number) => { const datasetIndex = (currentPage - 1) * pageSize + index + 1; + return ( { + if (!keyword) return false; + const needle = keyword.toLowerCase(); + return ( + item.name?.toLowerCase().includes(needle) || + item.fullname?.toLowerCase().includes(needle) || + item.datatype?.some((dt) => dt.toLowerCase().includes(needle)) + ); +}; + const SearchPage: React.FC = () => { const dispatch = useAppDispatch(); const [hasSearched, setHasSearched] = useState(false); @@ -54,6 +74,37 @@ const SearchPage: React.FC = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + // for database card + const keywordInput = String(formData?.keyword ?? "").trim(); + const selectedDbId = String(formData?.database ?? "").trim(); + console.log("keyword", keywordInput); + + // const registryMatches: RegistryItem[] = React.useMemo(() => { + // if (!Array.isArray(registry) || !keywordInput) return []; + // return (registry as RegistryItem[]).filter((r) => + // matchesKeyword(r, keywordInput) + // ); + // }, [registry, keywordInput]); + + const registryMatches: RegistryItem[] = React.useMemo(() => { + if (!Array.isArray(registry)) return []; + const list = registry as RegistryItem[]; + + const fromId = + selectedDbId && selectedDbId !== "any" + ? list.filter((r) => r.id === selectedDbId) + : []; + + const fromKeyword = keywordInput + ? list.filter((r) => matchesKeyword(r, keywordInput)) + : []; + + // merge the db results of selectedDB and keywordInput --> de duplicates + const map = new Map(); + [...fromId, ...fromKeyword].forEach((r) => map.set(r.id, r)); + return Array.from(map.values()); // return matched registry + }, [registry, selectedDbId, keywordInput]); + // to show the applied chips on the top of results const activeFilters = Object.entries(appliedFilters).filter( ([key, value]) => @@ -296,15 +347,30 @@ const SearchPage: React.FC = () => { ); + // check if has database/dataset matches + // const hasDbMatches = !!keywordInput && registryMatches.length > 0; + const hasDbMatches = registryMatches.length > 0; + const hasDatasetMatches = Array.isArray(results) && results.length > 0; + // when backend find nothing + const backendEmpty = + !Array.isArray(results) && (results as any)?.msg === "empty output"; + + // show red message only if nothing matched at all + const showNoResults = + hasSearched && + !loading && + !hasDbMatches && + (!hasDatasetMatches || backendEmpty); return ( { p: 3, borderRadius: 2, boxShadow: 1, - minWidth: "35%", + minWidth: "25%", }} > {renderFilterForm()} @@ -382,8 +448,8 @@ const SearchPage: React.FC = () => { color: Colors.darkPurple, }} > - Use the filters to search for datasets or subjects based on - metadata. + Use the filters and click submit to search for datasets or + subjects based on metadata. )} @@ -465,8 +531,190 @@ const SearchPage: React.FC = () => { )} - {/* results */} - {hasSearched && ( + + {/* matching databases */} + {/* {keywordInput && registryMatches.length > 0 && ( */} + {registryMatches.length > 0 && ( + + + Matching Databases + + {registryMatches.map((db) => ( + + ))} + + )} + + {/* results */} + {hasSearched && ( + + {loading ? ( + + + + Loading search results... + + + ) : ( + <> + {/* Only show header when there are dataset hits */} + {hasDatasetMatches && ( + + {`Showing ${results.length} ${ + isDataset ? "Datasets" : "Subjects" + }`} + + )} + + {/* pagination + cards (unchanged, but guard with hasDatasetMatches) */} + {hasDatasetMatches && ( + <> + {results.length >= 50 && ( + + + + )} + + + + + + {results.length > 0 && + paginatedResults.length > 0 && + paginatedResults.map((item, idx) => { + try { + const parsedJson = JSON.parse(item.json); + const globalIndex = + (page - 1) * itemsPerPage + idx; + + const isDataset = + parsedJson?.value?.subj && + Array.isArray(parsedJson.value.subj); + + return isDataset ? ( + + ) : ( + + ); + } catch (e) { + console.error( + `Failed to parse JSON for item #${idx}`, + e + ); + return null; + } + })} + + )} + + {/* Single place to show the red message */} + {showNoResults && ( + + No results found based on your criteria. Please adjust + the filters and try again. + + )} + + {hasSearched && + !loading && + !Array.isArray(results) && + results?.msg !== "empty output" && ( + + Something went wrong. Please try again later. + + )} + + )} + + )} + + + {/* {hasSearched && ( {loading ? ( @@ -584,7 +832,7 @@ const SearchPage: React.FC = () => { )} - )} + )} */} {/* mobile version filters */} diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx index c7b2af3..0e946e7 100644 --- a/src/pages/UpdatedDatasetDetailPage.tsx +++ b/src/pages/UpdatedDatasetDetailPage.tsx @@ -20,12 +20,13 @@ import { makeLinkMap, } from "components/DatasetDetailPage/FileTree/utils"; import LoadDatasetTabs from "components/DatasetDetailPage/LoadDatasetTabs"; +import MetaDataPanel from "components/DatasetDetailPage/MetaDataPanel"; import ReadMoreText from "design/ReadMoreText"; import { Colors } from "design/theme"; import { useAppDispatch } from "hooks/useAppDispatch"; import { useAppSelector } from "hooks/useAppSelector"; import React, { useEffect, useMemo, useState } from "react"; -import ReactJson from "react-json-view"; +// import ReactJson from "react-json-view"; import { useParams, useNavigate } from "react-router-dom"; import { fetchDocumentDetails, @@ -73,11 +74,16 @@ const UpdatedDatasetDetailPage: React.FC = () => { const [previewIndex, setPreviewIndex] = useState(0); const aiSummary = datasetDocument?.[".datainfo"]?.AISummary ?? ""; - // 1) detect subjects at the top level, return true or false - const hasTopLevelSubjects = useMemo( - () => Object.keys(datasetDocument || {}).some((k) => /^sub-/i.test(k)), - [datasetDocument] - ); + // useEffect(() => { + // if (!datasetDocument) { + // setJsonSize(0); + // return; + // } + // const bytes = new TextEncoder().encode( + // JSON.stringify(datasetDocument) + // ).length; + // setJsonSize(bytes); + // }, [datasetDocument]); const linkMap = useMemo(() => makeLinkMap(externalLinks), [externalLinks]); @@ -86,26 +92,6 @@ const UpdatedDatasetDetailPage: React.FC = () => { [datasetDocument, linkMap] ); - // “rest” JSON only when we actually have subjects - const rest = useMemo(() => { - if (!datasetDocument || !hasTopLevelSubjects) return {}; - const r: any = {}; - Object.keys(datasetDocument).forEach((k) => { - if (!/^sub-/i.test(k)) r[k] = (datasetDocument as any)[k]; - }); - return r; - }, [datasetDocument, hasTopLevelSubjects]); - - // JSON panel should always render: - // - if we have subjects -> JSON show "rest" (everything except sub-*) - // - if we don't have subjects -> JSON show the whole document - const jsonPanelData = useMemo( - () => (hasTopLevelSubjects ? rest : datasetDocument || {}), - [hasTopLevelSubjects, rest, datasetDocument] - ); - - // 5) header title + counts also fall back - // const treeTitle = hasTopLevelSubjects ? "Subjects" : "Files"; const treeTitle = "Files"; const filesCount = externalLinks.length; const totalBytes = useMemo(() => { @@ -256,15 +242,24 @@ const UpdatedDatasetDetailPage: React.FC = () => { return internalLinks; }; + // useEffect(() => { + // const fetchData = async () => { + // if (dbName && docId) { + // await dispatch(fetchDocumentDetails({ dbName, docId })); + // await dispatch(fetchDbInfoByDatasetId({ dbName, docId })); + // } + // }; + + // fetchData(); + // }, [dbName, docId, dispatch]); + useEffect(() => { - const fetchData = async () => { - if (dbName && docId) { - await dispatch(fetchDocumentDetails({ dbName, docId })); - await dispatch(fetchDbInfoByDatasetId({ dbName, docId })); - } - }; + if (!dbName || !docId) return; - fetchData(); + (async () => { + await dispatch(fetchDocumentDetails({ dbName, docId })); // render tree ASAP + dispatch(fetchDbInfoByDatasetId({ dbName, docId })); // don't await + })(); }, [dbName, docId, dispatch]); useEffect(() => { @@ -278,6 +273,15 @@ const UpdatedDatasetDetailPage: React.FC = () => { }) ); + const bytes = new Blob([JSON.stringify(datasetDocument)], { + type: "application/json", + }); + setJsonSize(bytes.size); + + // const bytes = new TextEncoder().encode( + // JSON.stringify(datasetDocument) + // ).length; + // setJsonSize(bytes); // Extract Internal Data & Assign `index` const internalData = extractInternalData(datasetDocument).map( (data, index) => ({ @@ -317,10 +321,10 @@ const UpdatedDatasetDetailPage: React.FC = () => { } }); - const blob = new Blob([JSON.stringify(datasetDocument, null, 2)], { - type: "application/json", - }); - setJsonSize(blob.size); + // const blob = new Blob([JSON.stringify(datasetDocument, null, 2)], { + // type: "application/json", + // }); + // setJsonSize(blob.size); // Construct download script dynamically let script = `curl -L --create-dirs "https://neurojson.io:7777/${dbName}/${docId}" -o "${docId}.json"\n`; @@ -402,18 +406,7 @@ const UpdatedDatasetDetailPage: React.FC = () => { if (typeof window !== "undefined" && (window as any).__previewType) { return (window as any).__previewType === "2d"; } - // if (window.__previewType) { - // console.log("work~~~~~~~"); - // return window.__previewType === "2d"; - // } - // console.log("is 2d preview candidate !== 2d"); - // console.log("obj", obj); - // if (typeof obj === "string" && obj.includes("db=optics-at-martinos")) { - // return false; - // } - // if (typeof obj === "string" && obj.endsWith(".jdb")) { - // return true; - // } + if (!obj || typeof obj !== "object") { return false; } @@ -422,8 +415,6 @@ const UpdatedDatasetDetailPage: React.FC = () => { return false; } const dim = obj._ArraySize_; - // console.log("array.isarray(dim)", Array.isArray(dim)); - // console.log("dim.length", dim.length === 1 || dim.length === 2); return ( Array.isArray(dim) && @@ -500,18 +491,6 @@ const UpdatedDatasetDetailPage: React.FC = () => { typeof dataOrUrl === "string" ? extractFileName(dataOrUrl) : ""; if (isPreviewableFile(fileName)) { (window as any).previewdataurl(dataOrUrl, idx); - // const is2D = is2DPreviewCandidate(dataOrUrl); - // const panel = document.getElementById("chartpanel"); - // console.log("is2D", is2D); - // console.log("panel", panel); - - // if (is2D) { - // // console.log("📊 2D data → rendering inline with dopreview()"); - // if (panel) panel.style.display = "block"; // Show it! - // setPreviewOpen(false); // Don't open modal - // } else { - // if (panel) panel.style.display = "none"; // Hide chart panel on 3D external - // } } else { console.warn("⚠️ Unsupported file format for preview:", dataOrUrl); } @@ -856,137 +835,12 @@ const UpdatedDatasetDetailPage: React.FC = () => { flexDirection: "column", }} > - - - - - Modalities - - - {dbViewInfo?.rows?.[0]?.value?.modality?.join(", ") ?? - "N/A"} - - - - - - DOI - - - {(() => { - const doi = - datasetDocument?.["dataset_description.json"] - ?.DatasetDOI || - datasetDocument?.["dataset_description.json"] - ?.ReferenceDOI; - - if (!doi) return "N/A"; - - // Normalize into a clickable URL - let url = doi; - if (/^10\./.test(doi)) { - url = `https://doi.org/${doi}`; - } else if (/^doi:/.test(doi)) { - url = `https://doi.org/${doi.replace(/^doi:/, "")}`; - } - - return ( - - {url} - - ); - })()} - - - - - - Subjects - - - {datasetDocument?.["participants.tsv"]?.["participant_id"] - ?.length ?? "N/A"} - - - - - License - - - {datasetDocument?.["dataset_description.json"]?.License ?? - "N/A"} - - - - - BIDS Version - - - {datasetDocument?.["dataset_description.json"] - ?.BIDSVersion ?? "N/A"} - - - - - References and Links - - - {Array.isArray( - datasetDocument?.["dataset_description.json"] - ?.ReferencesAndLinks - ) - ? datasetDocument["dataset_description.json"] - .ReferencesAndLinks.length > 0 - ? datasetDocument[ - "dataset_description.json" - ].ReferencesAndLinks.join(", ") - : "N/A" - : datasetDocument?.["dataset_description.json"] - ?.ReferencesAndLinks ?? "N/A"} - - - - + @@ -1089,12 +943,15 @@ const UpdatedDatasetDetailPage: React.FC = () => { variant="contained" size="small" sx={{ - backgroundColor: "#1976d2", + backgroundColor: Colors.purple, flexShrink: 0, minWidth: "70px", fontSize: "0.7rem", padding: "2px 6px", lineHeight: 1, + "&:hover": { + backgroundColor: Colors.secondaryPurple, + }, }} onClick={() => handlePreview(link.data, link.index, true) @@ -1114,7 +971,7 @@ const UpdatedDatasetDetailPage: React.FC = () => { { variant="contained" size="small" sx={{ - backgroundColor: "#1976d2", + backgroundColor: Colors.purple, minWidth: "70px", fontSize: "0.7rem", padding: "2px 6px", lineHeight: 1, + "&:hover": { + backgroundColor: Colors.secondaryPurple, + }, }} onClick={() => window.open(link.url, "_blank")} > @@ -1214,10 +1074,16 @@ const UpdatedDatasetDetailPage: React.FC = () => { variant="outlined" size="small" sx={{ + color: Colors.purple, + borderColor: Colors.purple, minWidth: "65px", fontSize: "0.7rem", padding: "2px 6px", lineHeight: 1, + "&:hover": { + color: Colors.secondaryPurple, + borderColor: Colors.secondaryPurple, + }, }} onClick={() => handlePreview(link.url, link.index, false) diff --git a/src/services/neurojson.service.ts b/src/services/neurojson.service.ts index 02aa590..8e5b706 100644 --- a/src/services/neurojson.service.ts +++ b/src/services/neurojson.service.ts @@ -30,7 +30,9 @@ export const NeurojsonService = { }, getDocumentById: async (dbName: string, documentId: string): Promise => { try { - const response = await api.get(`${baseURL}/${dbName}/${documentId}`); + const response = await api.get( + `${baseURL}/${dbName}/${documentId}?revs_info=true` + ); return response.data; } catch (error) { console.error( diff --git a/src/utils/SearchPageFunctions/modalityLabels.ts b/src/utils/SearchPageFunctions/modalityLabels.ts index b4c6f9a..532c3be 100644 --- a/src/utils/SearchPageFunctions/modalityLabels.ts +++ b/src/utils/SearchPageFunctions/modalityLabels.ts @@ -1,6 +1,8 @@ export const modalityValueToEnumLabel: Record = { anat: "Structural MRI (anat)", + mri: "Structural MRI (anat)", func: "fMRI (func)", + fmri: "fMRI (func)", dwi: "DWI (dwi)", fmap: "Field maps (fmap)", perf: "Perfusion (perf)", @@ -11,5 +13,6 @@ export const modalityValueToEnumLabel: Record = { pet: "PET (pet)", micr: "microscopy (micr)", nirs: "fNIRS (nirs)", + fnirs: "fNIRS (nirs)", motion: "motion (motion)", };