@openneuro/app 4.36.2 → 4.37.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/src/scripts/components/accordion/accordion.scss +1 -1
- package/src/scripts/components/activity-slider/ActivitySlider.tsx +4 -22
- package/src/scripts/components/activity-slider/slider.scss +1 -82
- package/src/scripts/components/button/button.scss +1 -1
- package/src/scripts/components/count-toggle/count-toggle.scss +1 -1
- package/src/scripts/components/dropdown/dropdown.scss +1 -1
- package/src/scripts/components/facets/facet.scss +5 -4
- package/src/scripts/components/footer/footer.scss +1 -1
- package/src/scripts/components/front-page/front-page.scss +1 -1
- package/src/scripts/components/header/header.scss +1 -1
- package/src/scripts/components/input/input.scss +1 -1
- package/src/scripts/components/input/term-search.scss +1 -1
- package/src/scripts/components/loading/loading.scss +1 -1
- package/src/scripts/components/modal/modal.scss +1 -1
- package/src/scripts/components/modality-cube/ModalityHexagon.tsx +29 -0
- package/src/scripts/components/modality-cube/modality-cube.scss +1 -1
- package/src/scripts/components/modality-cube/modality-hexagon.scss +93 -0
- package/src/scripts/components/page/page.scss +1 -1
- package/src/scripts/components/progress-bar/progress-bar.scss +1 -1
- package/src/scripts/components/radio/radio.scss +2 -2
- package/src/scripts/components/range/TwoHandleRange.scss +3 -3
- package/src/scripts/components/read-more/read-more.scss +1 -1
- package/src/scripts/components/scss/upload-modal.scss +1 -1
- package/src/scripts/components/tooltip/tooltip.scss +1 -1
- package/src/scripts/dataset/__tests__/__snapshots__/snapshot-container.spec.tsx.snap +132 -128
- package/src/scripts/dataset/__tests__/draft-container.spec.tsx +136 -0
- package/src/scripts/dataset/common/follow-toggles.tsx +1 -1
- package/src/scripts/dataset/components/DatasetHeader.tsx +13 -16
- package/src/scripts/dataset/components/DatasetToolButton.tsx +6 -7
- package/src/scripts/dataset/components/DatasetTools.tsx +6 -2
- package/src/scripts/dataset/draft-container.tsx +30 -24
- package/src/scripts/dataset/files/{file-display.jsx → file-display.tsx} +32 -19
- package/src/scripts/dataset/routes/tab-routes-draft.tsx +6 -1
- package/src/scripts/dataset/routes/tab-routes-snapshot.tsx +5 -1
- package/src/scripts/dataset/snapshot-container.tsx +37 -43
- package/src/scripts/scss/dataset/dataset-page.scss +44 -120
- package/src/scripts/scss/variables.scss +65 -13
- package/src/scripts/{components/search-page → search}/__tests__/NuerobagelSearch.spec.tsx +1 -1
- package/src/scripts/search/components/DatasetsRadioTabs.tsx +103 -0
- package/src/scripts/{components/search-page → search/components}/FilterListItem.tsx +1 -1
- package/src/scripts/{components/search-page → search/components}/FiltersBlock.tsx +5 -8
- package/src/scripts/search/components/MetaListItemList.tsx +31 -0
- package/src/scripts/{components/search-page → search/components}/SearchPage.tsx +15 -8
- package/src/scripts/search/components/SearchResultDetails.tsx +167 -0
- package/src/scripts/{components/search-page → search/components}/SearchResultItem.tsx +57 -173
- package/src/scripts/search/components/SearchResultsList.tsx +45 -0
- package/src/scripts/{components/search-page → search/components}/SearchSort.tsx +2 -2
- package/src/scripts/search/filters-block-container.tsx +1 -1
- package/src/scripts/search/inputs/index.ts +0 -4
- package/src/scripts/search/inputs/sliding-radio-group.tsx +127 -0
- package/src/scripts/search/inputs/sort-by-select.tsx +1 -1
- package/src/scripts/{components/search-page → search/scss}/filters-block.scss +1 -1
- package/src/scripts/{components/search-page → search/scss}/search-page.scss +123 -92
- package/src/scripts/search/scss/search-result-details.scss +70 -0
- package/src/scripts/{components/search-page → search/scss}/search-result.scss +29 -56
- package/src/scripts/{components/search-page → search/scss}/search-sort.scss +1 -1
- package/src/scripts/search/scss/sliding-radio-group.scss +115 -0
- package/src/scripts/search/search-container.tsx +90 -24
- package/src/scripts/search/use-search-results.tsx +3 -0
- package/src/scripts/users/github-auth-button.tsx +1 -1
- package/src/scripts/components/scss/_variables.scss +0 -245
- package/src/scripts/components/search-page/SearchResultsList.tsx +0 -29
- package/src/scripts/dataset/files/index.tsx +0 -6
- package/src/scripts/search/inputs/admin-allDatasets-toggle.tsx +0 -47
- package/src/scripts/search/inputs/show-datasets-radios.tsx +0 -74
- /package/src/scripts/{components/search-page → search/components}/CommunityHeader.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/FacetBlockContainerExample.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/FilterDateItem.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/ModalityHeader.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/NeurobagelSearch.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/SearchSortContainerExample.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/TermListItem.tsx +0 -0
- /package/src/scripts/{components/search-page → search/components}/neurobagel_logo.svg +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { useCallback, useContext } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
import { SearchParamsCtx } from "../search-params-ctx"
|
|
4
|
+
import { useCookies } from "react-cookie"
|
|
5
|
+
import { getUnexpiredProfile } from "../../authentication/profile"
|
|
6
|
+
import { useUser } from "../../queries/user"
|
|
7
|
+
import SlidingRadioGroup from "../inputs/sliding-radio-group"
|
|
8
|
+
|
|
9
|
+
export const DatasetsRadioTabs: FC = () => {
|
|
10
|
+
const [cookies] = useCookies()
|
|
11
|
+
const loggedOut = !getUnexpiredProfile(cookies)
|
|
12
|
+
const { user } = useUser()
|
|
13
|
+
const isAdmin = user?.admin
|
|
14
|
+
const { searchParams, setSearchParams } = useContext(SearchParamsCtx)
|
|
15
|
+
const {
|
|
16
|
+
datasetType_available,
|
|
17
|
+
datasetType_selected,
|
|
18
|
+
datasetStatus_available,
|
|
19
|
+
datasetStatus_selected,
|
|
20
|
+
searchAllDatasets,
|
|
21
|
+
} = searchParams
|
|
22
|
+
|
|
23
|
+
const updatedDatasetTypeAvailable = [...datasetType_available]
|
|
24
|
+
if (isAdmin) {
|
|
25
|
+
const adminDatasetValue = "Admin: All Datasets"
|
|
26
|
+
const adminDatasetLabel = "Admin" // Define the new label
|
|
27
|
+
|
|
28
|
+
// Check if an item with the 'Admin: All Datasets' value already exists
|
|
29
|
+
const alreadyHasAdminDataset = updatedDatasetTypeAvailable.some((item) =>
|
|
30
|
+
(typeof item === "object" ? item.value : item) === adminDatasetValue
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if (!alreadyHasAdminDataset) {
|
|
34
|
+
// Push it as an object with both value and the desired label
|
|
35
|
+
updatedDatasetTypeAvailable.push({
|
|
36
|
+
value: adminDatasetValue,
|
|
37
|
+
label: adminDatasetLabel,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const setShowSelected = useCallback(
|
|
43
|
+
(newDatasetTypeSelected: string) => {
|
|
44
|
+
setSearchParams((prevState) => {
|
|
45
|
+
const newSearchAllDatasets =
|
|
46
|
+
newDatasetTypeSelected === "Admin: All Datasets"
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...prevState,
|
|
50
|
+
datasetType_selected: newDatasetTypeSelected,
|
|
51
|
+
searchAllDatasets: newSearchAllDatasets,
|
|
52
|
+
|
|
53
|
+
datasetStatus_selected: newSearchAllDatasets
|
|
54
|
+
? undefined
|
|
55
|
+
: newDatasetTypeSelected === "My Datasets"
|
|
56
|
+
? prevState.datasetStatus_selected
|
|
57
|
+
: undefined,
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
[setSearchParams],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const setShowMyUploadsSelected = useCallback(
|
|
65
|
+
(newDatasetStatusSelected: string) => {
|
|
66
|
+
setSearchParams((prevState) => ({
|
|
67
|
+
...prevState,
|
|
68
|
+
datasetStatus_selected: newDatasetStatusSelected,
|
|
69
|
+
}))
|
|
70
|
+
},
|
|
71
|
+
[setSearchParams],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if (loggedOut) {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<SlidingRadioGroup
|
|
81
|
+
items={updatedDatasetTypeAvailable}
|
|
82
|
+
selected={datasetType_selected}
|
|
83
|
+
setSelected={setShowSelected}
|
|
84
|
+
groupName="show-datasets"
|
|
85
|
+
className={datasetType_selected === "Admin: All Datasets"
|
|
86
|
+
? "AdminAllDatasets"
|
|
87
|
+
: datasetType_selected.replace(/\s/g, "")}
|
|
88
|
+
initialSelectedValueOverride={searchAllDatasets
|
|
89
|
+
? "Admin: All Datasets"
|
|
90
|
+
: undefined}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
{datasetType_selected === "My Datasets" && !searchAllDatasets && (
|
|
94
|
+
<SlidingRadioGroup
|
|
95
|
+
items={datasetStatus_available}
|
|
96
|
+
selected={datasetStatus_selected}
|
|
97
|
+
setSelected={setShowMyUploadsSelected}
|
|
98
|
+
groupName="dataset-status"
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -3,7 +3,8 @@ import { Button } from "../../components/button/Button"
|
|
|
3
3
|
import { FilterListItem } from "./FilterListItem"
|
|
4
4
|
import { TermListItem } from "./TermListItem"
|
|
5
5
|
import type { FacetSelectValueType } from "../../components/facets/FacetSelect"
|
|
6
|
-
import "
|
|
6
|
+
import "../scss/filters-block.scss"
|
|
7
|
+
import { modalityShortMapping } from "../../components/formatting/modality-label"
|
|
7
8
|
|
|
8
9
|
export interface FiltersBlockProps {
|
|
9
10
|
keywords: string[]
|
|
@@ -69,17 +70,13 @@ export const FiltersBlock = ({
|
|
|
69
70
|
const subjectCountRangeIsNull =
|
|
70
71
|
JSON.stringify(subjectCountRange) === JSON.stringify([null, null])
|
|
71
72
|
|
|
73
|
+
const labelText = modalityShortMapping(modality_selected)
|
|
74
|
+
|
|
72
75
|
return (
|
|
73
76
|
<div className="filters-block">
|
|
74
77
|
<h2>
|
|
75
78
|
{noFilters
|
|
76
|
-
?
|
|
77
|
-
<b>
|
|
78
|
-
Showing all available {modality_selected ? modality_selected : ""}
|
|
79
|
-
{" "}
|
|
80
|
-
datasets
|
|
81
|
-
</b>
|
|
82
|
-
)
|
|
79
|
+
? <b>Showing all available {labelText ? labelText : ""} datasets</b>
|
|
83
80
|
: (
|
|
84
81
|
<>
|
|
85
82
|
{loading
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { FC, ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
interface MetaListItemListProps {
|
|
5
|
+
typeLabel: ReactNode
|
|
6
|
+
items: (string | ReactNode)[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A reusable component for rendering a list of meta items in the search results details.
|
|
11
|
+
*/
|
|
12
|
+
export const MetaListItemList: FC<MetaListItemListProps> = (
|
|
13
|
+
{ typeLabel, items },
|
|
14
|
+
) => {
|
|
15
|
+
if (!items || items.length === 0) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="result-summary-meta">
|
|
21
|
+
<label>{typeLabel}:</label>
|
|
22
|
+
<div>
|
|
23
|
+
{items.map((item, index) => (
|
|
24
|
+
<span className="list-item" key={index}>
|
|
25
|
+
{item}
|
|
26
|
+
</span>
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { ModalityHeader } from "./ModalityHeader"
|
|
3
3
|
import { CommunityHeader } from "./CommunityHeader"
|
|
4
|
-
import "
|
|
4
|
+
import "../scss/search-page.scss"
|
|
5
5
|
|
|
6
6
|
export interface PortalContent {
|
|
7
7
|
className?: string
|
|
@@ -15,6 +15,7 @@ export interface PortalContent {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface SearchPageProps {
|
|
18
|
+
hasDetailsOpen?: boolean
|
|
18
19
|
portalContent?: PortalContent
|
|
19
20
|
renderSearchFacets: () => React.ReactNode
|
|
20
21
|
renderSearchResultsList: () => React.ReactNode
|
|
@@ -23,15 +24,18 @@ export interface SearchPageProps {
|
|
|
23
24
|
renderSearchHeader: () => React.ReactNode
|
|
24
25
|
renderLoading: () => React.ReactNode
|
|
25
26
|
renderAggregateCounts: () => React.ReactNode
|
|
27
|
+
renderItemDetails: () => React.ReactNode
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export const SearchPage = ({
|
|
31
|
+
hasDetailsOpen,
|
|
29
32
|
portalContent,
|
|
30
33
|
renderSearchFacets,
|
|
31
34
|
renderSearchResultsList,
|
|
32
35
|
renderSortBy,
|
|
33
36
|
renderFilterBlock,
|
|
34
37
|
renderSearchHeader,
|
|
38
|
+
renderItemDetails,
|
|
35
39
|
renderLoading,
|
|
36
40
|
renderAggregateCounts,
|
|
37
41
|
}: SearchPageProps) => {
|
|
@@ -69,18 +73,14 @@ export const SearchPage = ({
|
|
|
69
73
|
</>
|
|
70
74
|
)
|
|
71
75
|
: null}
|
|
72
|
-
<div className="container">
|
|
76
|
+
<div className="container-full">
|
|
73
77
|
<div className="grid grid-nogutter">
|
|
74
|
-
<div className="col col-12 search-heading">
|
|
75
|
-
<h1>{renderSearchHeader()}</h1>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
78
|
<div className="col col-12 search-wrapper">
|
|
79
79
|
<button
|
|
80
80
|
className="show-filters-btn"
|
|
81
81
|
onClick={() => setOpen(!isOpen)}
|
|
82
82
|
>
|
|
83
|
-
Show Filters
|
|
83
|
+
Show Additional Filters
|
|
84
84
|
</button>
|
|
85
85
|
<div
|
|
86
86
|
className={isOpen
|
|
@@ -95,16 +95,23 @@ export const SearchPage = ({
|
|
|
95
95
|
</button>
|
|
96
96
|
{renderSearchFacets()}
|
|
97
97
|
</div>
|
|
98
|
-
<div
|
|
98
|
+
<div
|
|
99
|
+
className={`search-content ${
|
|
100
|
+
hasDetailsOpen ? " details-opened" : ""
|
|
101
|
+
}`}
|
|
102
|
+
>
|
|
103
|
+
<div className="search-heading">{renderSearchHeader()}</div>
|
|
99
104
|
{renderLoading()}
|
|
100
105
|
<div className="grid grid-nogutter">
|
|
101
106
|
<div className="col col-12">{renderFilterBlock()}</div>
|
|
102
107
|
<div className="col col-12">
|
|
103
108
|
<div className="grid grid-nogutter">{renderSortBy()}</div>
|
|
104
109
|
</div>
|
|
110
|
+
|
|
105
111
|
{renderSearchResultsList()}
|
|
106
112
|
</div>
|
|
107
113
|
</div>
|
|
114
|
+
{renderItemDetails()}
|
|
108
115
|
</div>
|
|
109
116
|
</div>
|
|
110
117
|
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react"
|
|
2
|
+
import type { FC, ReactNode } from "react"
|
|
3
|
+
import bytes from "bytes"
|
|
4
|
+
import parseISO from "date-fns/parseISO"
|
|
5
|
+
import formatDistanceToNow from "date-fns/formatDistanceToNow"
|
|
6
|
+
import { Link } from "react-router-dom"
|
|
7
|
+
import type { SearchResultItemProps } from "./SearchResultItem"
|
|
8
|
+
import { ModalityLabel } from "../../components/formatting/modality-label"
|
|
9
|
+
import { MetaListItemList } from "./MetaListItemList"
|
|
10
|
+
import "../scss/search-result-details.scss"
|
|
11
|
+
|
|
12
|
+
interface SearchResultDetailsProps {
|
|
13
|
+
itemData: SearchResultItemProps["node"] | null
|
|
14
|
+
onClose: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SearchResultDetails: FC<SearchResultDetailsProps> = (
|
|
18
|
+
{ itemData, onClose },
|
|
19
|
+
) => {
|
|
20
|
+
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (itemData && closeButtonRef.current) {
|
|
24
|
+
closeButtonRef.current.focus()
|
|
25
|
+
}
|
|
26
|
+
}, [itemData])
|
|
27
|
+
|
|
28
|
+
if (!itemData) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const formatDate = (dateString: string): string => {
|
|
33
|
+
if (!dateString) return "N/A"
|
|
34
|
+
const date = new Date(dateString)
|
|
35
|
+
return date.toISOString().split("T")[0]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const summary = itemData.latestSnapshot?.summary
|
|
39
|
+
const numSessions = summary?.sessions?.length > 0
|
|
40
|
+
? summary.sessions.length
|
|
41
|
+
: 1
|
|
42
|
+
const numSubjects = summary?.subjects?.length > 0
|
|
43
|
+
? summary.subjects.length
|
|
44
|
+
: 1
|
|
45
|
+
|
|
46
|
+
// Header for more details
|
|
47
|
+
const moreDetailsHeader = (
|
|
48
|
+
<h4>
|
|
49
|
+
<Link to={"/datasets/" + itemData?.id}>
|
|
50
|
+
{itemData.latestSnapshot?.description?.Name || itemData.id}
|
|
51
|
+
</Link>
|
|
52
|
+
</h4>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// Lists
|
|
56
|
+
const modalityList = summary?.modalities?.length
|
|
57
|
+
? (
|
|
58
|
+
<div className="modality-list">
|
|
59
|
+
<MetaListItemList
|
|
60
|
+
typeLabel={
|
|
61
|
+
<>{summary?.modalities.length === 1 ? "Modality" : "Modalities"}</>
|
|
62
|
+
}
|
|
63
|
+
items={summary?.modalities.map((modality) => (
|
|
64
|
+
<ModalityLabel key={modality} modality={modality} />
|
|
65
|
+
))}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
: null
|
|
70
|
+
|
|
71
|
+
const taskList = summary?.tasks?.length
|
|
72
|
+
? (
|
|
73
|
+
<div className="task-list">
|
|
74
|
+
<MetaListItemList typeLabel={<>Tasks</>} items={summary?.tasks} />
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
: null
|
|
78
|
+
|
|
79
|
+
const tracers = summary?.pet?.TracerName?.length
|
|
80
|
+
? (
|
|
81
|
+
<div className="tracers-list">
|
|
82
|
+
<MetaListItemList
|
|
83
|
+
typeLabel={
|
|
84
|
+
<>
|
|
85
|
+
{summary?.pet?.TracerName.length === 1
|
|
86
|
+
? "Radiotracer"
|
|
87
|
+
: "Radiotracers"}
|
|
88
|
+
</>
|
|
89
|
+
}
|
|
90
|
+
items={summary?.pet?.TracerName}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
: null
|
|
95
|
+
|
|
96
|
+
// function for consistent meta item rendering
|
|
97
|
+
const renderMetaItem = (
|
|
98
|
+
label: string | ReactNode,
|
|
99
|
+
content: ReactNode,
|
|
100
|
+
): JSX.Element => (
|
|
101
|
+
<div className="result-summary-meta">
|
|
102
|
+
<label>{label}: </label>
|
|
103
|
+
{content}
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const sessions = renderMetaItem("Sessions", numSessions.toLocaleString())
|
|
108
|
+
const subjects = renderMetaItem("Participants", numSubjects.toLocaleString())
|
|
109
|
+
const size = renderMetaItem(
|
|
110
|
+
"Size",
|
|
111
|
+
bytes(itemData?.latestSnapshot?.size) || "unknown",
|
|
112
|
+
)
|
|
113
|
+
const files = renderMetaItem("Files", summary?.totalFiles?.toLocaleString())
|
|
114
|
+
const lastUpdatedDisplay = renderMetaItem(
|
|
115
|
+
"Last Updated",
|
|
116
|
+
<div>
|
|
117
|
+
{formatDate(
|
|
118
|
+
itemData.snapshots?.[itemData.snapshots.length - 1]?.created ||
|
|
119
|
+
itemData.created,
|
|
120
|
+
)}
|
|
121
|
+
</div>,
|
|
122
|
+
)
|
|
123
|
+
const accessionNumberDisplay = renderMetaItem(
|
|
124
|
+
"Openneuro Accession Number",
|
|
125
|
+
<Link to={"/datasets/" + itemData?.id}>
|
|
126
|
+
{itemData?.id}
|
|
127
|
+
</Link>,
|
|
128
|
+
)
|
|
129
|
+
const authors = renderMetaItem(
|
|
130
|
+
"Authors",
|
|
131
|
+
<div>{itemData.latestSnapshot?.description?.Authors}</div>,
|
|
132
|
+
)
|
|
133
|
+
const uploaderDisplay = renderMetaItem(
|
|
134
|
+
"Uploader by",
|
|
135
|
+
<div>
|
|
136
|
+
{itemData.uploader?.name} on {formatDate(itemData?.created)} -{" "}
|
|
137
|
+
{formatDistanceToNow(parseISO(itemData?.created))} ago
|
|
138
|
+
</div>,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="search-details">
|
|
143
|
+
<div className="search-details-scroll">
|
|
144
|
+
<button
|
|
145
|
+
className="close-details-button"
|
|
146
|
+
onClick={onClose}
|
|
147
|
+
aria-label="Close details"
|
|
148
|
+
ref={closeButtonRef}
|
|
149
|
+
>
|
|
150
|
+
×
|
|
151
|
+
</button>
|
|
152
|
+
{moreDetailsHeader}
|
|
153
|
+
{authors}
|
|
154
|
+
{modalityList}
|
|
155
|
+
{taskList}
|
|
156
|
+
{accessionNumberDisplay}
|
|
157
|
+
{tracers}
|
|
158
|
+
{sessions}
|
|
159
|
+
{subjects}
|
|
160
|
+
{size}
|
|
161
|
+
{files}
|
|
162
|
+
{uploaderDisplay}
|
|
163
|
+
{lastUpdatedDisplay}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|