diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.test.tsx b/frontend/packages/data-portal/app/components/AuthorList/AuthorList.test.tsx similarity index 86% rename from frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.test.tsx rename to frontend/packages/data-portal/app/components/AuthorList/AuthorList.test.tsx index 8e5b019b2..56e9ce1b1 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.test.tsx +++ b/frontend/packages/data-portal/app/components/AuthorList/AuthorList.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react' import { AuthorInfo, MockAuthorLink } from 'app/components/AuthorLink' -import { DatasetAuthors } from './DatasetAuthors' +import { AuthorList } from './AuthorList' const DEFAULT_AUTHORS: AuthorInfo[] = [ { name: 'Foo', corresponding_author_status: true }, @@ -19,7 +19,7 @@ const AUTHOR_MAP = Object.fromEntries( ) it('should render authors', () => { - render() + render() DEFAULT_AUTHORS.forEach((author) => expect(screen.getByText(author.name)).toBeInTheDocument(), @@ -27,7 +27,7 @@ it('should render authors', () => { }) it('should sort primary authors', () => { - render() + render() const authorNode = screen.getByRole('paragraph') const authors = (authorNode.textContent ?? '').split(', ') @@ -36,7 +36,7 @@ it('should sort primary authors', () => { }) it('should sort other authors', () => { - render() + render() const authorNode = screen.getByRole('paragraph') const authors = (authorNode.textContent ?? '').split(', ') const otherAuthors = authors.slice(2, -2) @@ -48,7 +48,7 @@ it('should sort other authors', () => { }) it('should sort corresponding authors', () => { - render() + render() const authorNode = screen.getByRole('paragraph') const authors = (authorNode.textContent ?? '').split(', ') @@ -66,9 +66,7 @@ it('should render author links', () => { orcid: `0000-0000-0000-000${idx}`, })) - render( - , - ) + render() authors.forEach((author) => expect( @@ -84,7 +82,7 @@ it('should not render author links when compact', () => { })) render( - { }) it('should not render other authors when compact', () => { - render() + render() const authorNode = screen.getByRole('paragraph') const authors = (authorNode.textContent ?? '').split(', ') const otherAuthors = authors.slice(2, -2) @@ -106,13 +104,13 @@ it('should not render other authors when compact', () => { }) it('should render comma if compact and has corresponding authors', () => { - render() + render() expect(screen.getByText((text) => text.includes('... ,'))).toBeInTheDocument() }) it('should not render comma for others if compact and no corresponding authors', () => { render( - !author.corresponding_author_status, )} diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.tsx b/frontend/packages/data-portal/app/components/AuthorList/AuthorList.tsx similarity index 98% rename from frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.tsx rename to frontend/packages/data-portal/app/components/AuthorList/AuthorList.tsx index 9eb2e29f2..67dc88cbe 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetAuthors.tsx +++ b/frontend/packages/data-portal/app/components/AuthorList/AuthorList.tsx @@ -13,7 +13,7 @@ function getAuthorIds(authors: AuthorInfo[]) { return authors.map((author) => author.name + author.email + author.orcid) } -export function DatasetAuthors({ +export function AuthorList({ AuthorLinkComponent = AuthorLink, authors, className, diff --git a/frontend/packages/data-portal/app/components/AuthorList/index.ts b/frontend/packages/data-portal/app/components/AuthorList/index.ts new file mode 100644 index 000000000..f5ab0146a --- /dev/null +++ b/frontend/packages/data-portal/app/components/AuthorList/index.ts @@ -0,0 +1 @@ +export * from './AuthorList' diff --git a/frontend/packages/data-portal/app/components/Breadcrumbs.tsx b/frontend/packages/data-portal/app/components/Breadcrumbs.tsx index 96f9b58e3..530bef698 100644 --- a/frontend/packages/data-portal/app/components/Breadcrumbs.tsx +++ b/frontend/packages/data-portal/app/components/Breadcrumbs.tsx @@ -40,10 +40,10 @@ function Breadcrumb({ export function Breadcrumbs({ variant, - dataset, + data, }: { - variant: 'dataset' | 'run' - dataset: { id: number; title: string } + variant: 'dataset' | 'deposition' | 'run' + data: { id: number; title: string } }) { const { t } = useI18n() @@ -51,26 +51,28 @@ export function Breadcrumbs({ const { singleDatasetHistory } = useSingleDatasetFilterHistory() const browseAllLink = useMemo(() => { - const url = '/browse-data/datasets' - const encodedParams = encodeParams( - Array.from(browseDatasetHistory?.entries() ?? []), - ) + const url = + variant === 'deposition' + ? '/browse-data/depositions' + : '/browse-data/datasets' + const history = variant === 'deposition' ? undefined : browseDatasetHistory + const encodedParams = encodeParams(Array.from(history?.entries() ?? [])) return `${url}?${encodedParams}` - }, [browseDatasetHistory]) + }, [browseDatasetHistory, variant]) const singleDatasetLink = useMemo(() => { if (variant === 'dataset') { return undefined } - const url = `/datasets/${dataset.id}` + const url = `/datasets/${data.id}` const encodedParams = encodeParams( Array.from(singleDatasetHistory?.entries() ?? []), ) return `${url}?${encodedParams}` - }, [singleDatasetHistory, variant, dataset]) + }, [singleDatasetHistory, variant, data]) const chevronIcon = ( @@ -79,20 +81,24 @@ export function Breadcrumbs({ return (
{chevronIcon} - + {variant === 'deposition' ? ( + + ) : ( + + )} {variant === 'run' && ( <> {chevronIcon} diff --git a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx index c9722c24f..d4a0da942 100644 --- a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx +++ b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx @@ -8,7 +8,7 @@ import { range } from 'lodash-es' import { useEffect, useMemo, useState } from 'react' import { AnnotatedObjectsList } from 'app/components/AnnotatedObjectsList' -import { DatasetAuthors } from 'app/components/Dataset/DatasetAuthors' +import { AuthorList } from 'app/components/AuthorList' import { I18n } from 'app/components/I18n' import { KeyPhoto } from 'app/components/KeyPhoto' import { Link } from 'app/components/Link' @@ -170,7 +170,7 @@ export function DatasetTable() { /> ) : ( - + )}

diff --git a/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx b/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx index c686c6b85..278d0ab60 100644 --- a/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx +++ b/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx @@ -8,7 +8,7 @@ import { range, sum } from 'lodash-es' import { useMemo } from 'react' import { AnnotatedObjectsList } from 'app/components/AnnotatedObjectsList' -import { DatasetAuthors } from 'app/components/Dataset/DatasetAuthors' +import { AuthorList } from 'app/components/AuthorList' import { KeyPhoto } from 'app/components/KeyPhoto' import { Link } from 'app/components/Link' import { CellHeader, PageTable, TableCell } from 'app/components/Table' @@ -147,7 +147,7 @@ export function DepositionTable() { /> ) : ( - + )}

diff --git a/frontend/packages/data-portal/app/components/CollapsibleList.tsx b/frontend/packages/data-portal/app/components/CollapsibleList.tsx new file mode 100644 index 000000000..37470dfde --- /dev/null +++ b/frontend/packages/data-portal/app/components/CollapsibleList.tsx @@ -0,0 +1,89 @@ +import { Icon } from '@czi-sds/components' +import { ReactNode, useState } from 'react' + +import { useI18n } from 'app/hooks/useI18n' +import { cns } from 'app/utils/cns' + +interface ListEntry { + key: string + entry: ReactNode +} + +export function CollapsibleList({ + entries, + collapseAfter, + tableVariant = false, +}: { + entries?: ListEntry[] + collapseAfter?: number + tableVariant?: boolean +}) { + const collapsible = + collapseAfter !== undefined && + collapseAfter >= 0 && + entries !== undefined && + entries.length > collapseAfter + 1 + + const { t } = useI18n() + const [collapsed, setCollapsed] = useState(true) + + return entries ? ( +
    + {entries.map( + ({ key, entry }, i) => + !(collapsible && collapsed && i + 1 > collapseAfter) && ( +
  • {entry}
  • + ), + )} + {collapsible && ( +
    + +
    + )} +
+ ) : ( +

+ {t('notSubmitted')} +

+ ) +} diff --git a/frontend/packages/data-portal/app/components/DatabaseList.tsx b/frontend/packages/data-portal/app/components/DatabaseList.tsx new file mode 100644 index 000000000..1491e19da --- /dev/null +++ b/frontend/packages/data-portal/app/components/DatabaseList.tsx @@ -0,0 +1,20 @@ +import { CollapsibleList } from 'app/components/CollapsibleList' +import { DatabaseEntry } from 'app/components/DatabaseEntry' + +export function DatabaseList({ + entries, + collapseAfter, +}: { + entries?: string[] + collapseAfter?: number +}) { + return ( + ({ + key: e, + entry: , + }))} + collapseAfter={collapseAfter} + /> + ) +} diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetDescription.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetDescription.tsx deleted file mode 100644 index baa627abe..000000000 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetDescription.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Button } from '@czi-sds/components' -import { useState } from 'react' - -import { AuthorLegend } from 'app/components/AuthorLegend' -import { DatabaseEntry } from 'app/components/DatabaseEntry' -import { DOI_ID } from 'app/constants/external-dbs' -import { useDatasetById } from 'app/hooks/useDatasetById' -import { i18n } from 'app/i18n' -import { cns, cnsNoMerge } from 'app/utils/cns' - -import { DatasetAuthors } from './DatasetAuthors' - -// use clsx here instead of cns since it erroneously merges text-sds-gray-500 and text-sds-caps-xxxs -const sectionHeaderStyles = cnsNoMerge( - 'font-semibold uppercase', - 'text-sds-gray-black', - 'text-sds-caps-xxxs leading-sds-caps-xxxs tracking-sds-caps', -) - -interface DatabaseListProps { - title: string - entries?: string[] - className?: string - collapseAfter?: number -} - -function DatabaseList(props: DatabaseListProps) { - const { title, entries, className, collapseAfter } = props - const collapsible = - collapseAfter !== undefined && - collapseAfter >= 0 && - entries !== undefined && - entries.length > collapseAfter - const [collapsed, setCollapsed] = useState(true) - - return ( -
-

{title}

- {entries ? ( -
    - {entries.map( - (e, i) => - !(collapsible && collapsed && i + 1 > collapseAfter) && ( -
  • - -
  • - ), - )} - {collapsible && ( -
    - -
    - )} -
- ) : ( -

- {i18n.notSubmitted} -

- )} -
- ) -} - -export function DatasetDescription() { - const { dataset } = useDatasetById() - - // clean up entries into lists - const publicationEntries = dataset.dataset_publications - ?.split(',') - .map((e) => e.trim()) - .filter((e) => DOI_ID.exec(e)) // only show DOI links - - const relatedDatabaseEntries = dataset.related_database_entries - ?.split(',') - .map((e) => e.trim()) - - return ( -
-

- {dataset.description} -

-
-
-

{i18n.authors}

- -
- -
-
- - - {/* extra div to turn it into 3 columns */} -
-
-
- ) -} diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx index 07163e952..1efe1a638 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/DatasetHeader.tsx @@ -1,10 +1,9 @@ import { Button, Icon } from '@czi-sds/components' import { Breadcrumbs } from 'app/components/Breadcrumbs' -import { DatasetDescription } from 'app/components/Dataset/DatasetDescription' +import { DatasetOverview } from 'app/components/Dataset/DatasetOverview' import { KeyPhoto } from 'app/components/KeyPhoto' import { PageHeader } from 'app/components/PageHeader' -import { PageHeaderSubtitle } from 'app/components/PageHeaderSubtitle' import { useDatasetById } from 'app/hooks/useDatasetById' import { useDownloadModalQueryParamState } from 'app/hooks/useDownloadModalQueryParamState' import { useI18n } from 'app/hooks/useI18n' @@ -31,7 +30,7 @@ export function DatasetHeader() { {t('downloadDataset')} } - breadcrumbs={} + breadcrumbs={} lastModifiedDate={dataset.last_modified_date ?? dataset.deposition_date} metadata={[ { @@ -52,9 +51,7 @@ export function DatasetHeader() {
- {t('datasetOverview')} - - + {moreInfo}
diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx index 13c4ea4a2..0c3d16f91 100644 --- a/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/DatasetMetadataTable.tsx @@ -4,12 +4,12 @@ import { isString } from 'lodash-es' import { AccordionMetadataTable } from 'app/components/AccordionMetadataTable' import { AuthorLegend } from 'app/components/AuthorLegend' import { AuthorInfo } from 'app/components/AuthorLink' +import { AuthorList } from 'app/components/AuthorList' import { DatabaseEntry } from 'app/components/DatabaseEntry' import { Link } from 'app/components/Link' import { useI18n } from 'app/hooks/useI18n' import { getTableData } from 'app/utils/table' -import { DatasetAuthors } from './DatasetAuthors' import { DatasetType } from './type' function DatabaseEntryList({ entries }: { entries: string }) { @@ -95,9 +95,7 @@ export function DatasetMetadataTable({ : t('authors'), labelExtra: , renderValue: () => { - return ( - - ) + return }, values: [], className: 'leading-sds-body-s', diff --git a/frontend/packages/data-portal/app/components/Dataset/DatasetOverview.tsx b/frontend/packages/data-portal/app/components/Dataset/DatasetOverview.tsx new file mode 100644 index 000000000..5173ccf89 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Dataset/DatasetOverview.tsx @@ -0,0 +1,72 @@ +import { AuthorLegend } from 'app/components/AuthorLegend' +import { AuthorList } from 'app/components/AuthorList' +import { DatabaseList } from 'app/components/DatabaseList' +import { DOI_ID } from 'app/constants/external-dbs' +import { useDatasetById } from 'app/hooks/useDatasetById' +import { useI18n } from 'app/hooks/useI18n' +import { cnsNoMerge } from 'app/utils/cns' + +import { PageHeaderSubtitle } from '../PageHeaderSubtitle' + +// use clsx here instead of cns since it erroneously merges text-sds-gray-500 and text-sds-caps-xxxs +const sectionHeaderStyles = cnsNoMerge( + 'font-semibold uppercase', + 'text-sds-gray-black', + 'text-sds-caps-xxxs leading-sds-caps-xxxs tracking-sds-caps', + 'mb-sds-xs', +) + +export function DatasetOverview() { + const { dataset } = useDatasetById() + const { t } = useI18n() + + // clean up entries into lists + const publicationEntries = dataset.dataset_publications + ?.split(',') + .map((e) => e.trim()) + .filter((e) => DOI_ID.exec(e)) // only show DOI links + + const relatedDatabaseEntries = dataset.related_database_entries + ?.split(',') + .map((e) => e.trim()) + + return ( +
+ + {t('datasetOverview')} + +

+ {dataset.description} +

+
+
+

{t('authors')}

+ +
+ +
+
+
+

{t('publications')}

+ +
+
+

{t('relatedDatabases')}

+ + +
+ {/* extra div to turn it into 3 columns */} +
+
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/AnnotationMethodsSummary.tsx b/frontend/packages/data-portal/app/components/Deposition/AnnotationMethodsSummary.tsx new file mode 100644 index 000000000..e5e532f49 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/AnnotationMethodsSummary.tsx @@ -0,0 +1,238 @@ +import { Icon } from '@czi-sds/components' +import { ReactNode } from 'react' + +import { CollapsibleList } from 'app/components/CollapsibleList' +import { I18n } from 'app/components/I18n' +import { SourceCodeIcon, WeightsIcon } from 'app/components/icons' +import { Link } from 'app/components/Link' +import { PageHeaderSubtitle } from 'app/components/PageHeaderSubtitle' +import { Tooltip } from 'app/components/Tooltip' +import { + methodLabels, + methodTooltipLabels, + MethodType, +} from 'app/constants/methodTypes' +import { useI18n } from 'app/hooks/useI18n' +import { I18nKeys } from 'app/types/i18n' + +interface MethodLink { + i18nLabel: I18nKeys + url: string + icon: ReactNode + title?: string +} + +const iconMap = { + sourceCode: ( + + ), + modelWeights: ( + + ), + website: ( + + ), + documentation: ( + + ), + other: ( + + ), +} as const + +interface MethodLinkVariantProps { + variant: keyof typeof iconMap + url: string + title?: string +} + +const variantOrder: (keyof typeof iconMap)[] = [ + 'sourceCode', + 'modelWeights', + 'website', + 'documentation', + 'other', +] + +function methodLinkFromVariant({ + variant, + url, + title, +}: MethodLinkVariantProps): MethodLink { + return { + i18nLabel: variant, + url, + title, + icon: iconMap[variant], + } +} + +function generateMethodLinks(links: MethodLinkVariantProps[]): MethodLink[] { + return links + .toSorted( + (a, b) => + variantOrder.indexOf(a.variant) - variantOrder.indexOf(b.variant), + ) + .map((props) => methodLinkFromVariant(props)) +} + +function MethodTypeSection({ + methodType, + links, +}: { + methodType: MethodType + links?: MethodLink[] +}) { + const { t } = useI18n() + + return ( +
+

+ {t('methodType')} +

+

+ {t(methodLabels[methodType])} + } + > + + +

+

+ {t('methodLinks')} +

+ ({ + key: `${link.url}_${link.i18nLabel}_${link.title}`, + entry: ( + + + {link.icon} + + {t(link.i18nLabel)}: + + + + {link.title ?? link.url} + + + ), + }))} + collapseAfter={1} + /> +
+ ) +} + +export function AnnotationMethodsSummary() { + const { t } = useI18n() + + const separator =
+ + const hybridMethodLinks: MethodLinkVariantProps[] = [ + { + variant: 'sourceCode', + url: 'https://www.example.com', + }, + { + variant: 'website', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + ] + + const automatedMethodLinks: MethodLinkVariantProps[] = [ + { + variant: 'sourceCode', + url: 'https://www.example.com', + }, + { + variant: 'website', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + { + variant: 'website', + url: 'https://www.example.com', + }, + { + variant: 'sourceCode', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + { + variant: 'other', + url: 'https://www.example.com', + }, + { + variant: 'documentation', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + { + variant: 'modelWeights', + url: 'https://www.example.com', + }, + { + variant: 'other', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + { + variant: 'documentation', + url: 'https://www.example.com', + }, + { + variant: 'modelWeights', + url: 'https://www.example.com', + title: 'Optional Custom Link Name', + }, + ] + + return ( +
+ + {t('annotationMethodsSummary')} + +
+ + {separator} + + {separator} + +
+
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DatasetsTable.tsx b/frontend/packages/data-portal/app/components/Deposition/DatasetsTable.tsx new file mode 100644 index 000000000..bba62b91f --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DatasetsTable.tsx @@ -0,0 +1,307 @@ +/* eslint-disable react/no-unstable-nested-components */ + +import { CellHeaderDirection } from '@czi-sds/components' +import Skeleton from '@mui/material/Skeleton' +import { useSearchParams } from '@remix-run/react' +import { ColumnDef, createColumnHelper } from '@tanstack/react-table' +import { range, sum } from 'lodash-es' +import { useMemo } from 'react' + +import { AnnotatedObjectsList } from 'app/components/AnnotatedObjectsList' +import { AuthorList } from 'app/components/AuthorList' +import { I18n } from 'app/components/I18n' +import { KeyPhoto } from 'app/components/KeyPhoto' +import { Link } from 'app/components/Link' +import { CellHeader, PageTable, TableCell } from 'app/components/Table' +import { ANNOTATED_OBJECTS_MAX, MAX_PER_PAGE } from 'app/constants/pagination' +import { DepositionPageDatasetTableWidths } from 'app/constants/table' +import { Dataset, useDepositionById } from 'app/hooks/useDepositionById' +import { useI18n } from 'app/hooks/useI18n' +import { useIsLoading } from 'app/hooks/useIsLoading' +import { LogLevel } from 'app/types/logging' +import { cnsNoMerge } from 'app/utils/cns' +import { sendLogs } from 'app/utils/logging' +import { getErrorMessage } from 'app/utils/string' + +const LOADING_DATASETS = range(0, MAX_PER_PAGE).map( + (value) => + ({ + authors: [], + id: value, + title: `loading-dataset-${value}`, + runs: [], + runs_aggregate: {}, + }) as Dataset, +) + +export function DatasetsTable() { + const { t } = useI18n() + const { deposition } = useDepositionById() + + const [searchParams, setSearchParams] = useSearchParams() + const datasetSort = (searchParams.get('sort') ?? undefined) as + | CellHeaderDirection + | undefined + + const { isLoadingDebounced } = useIsLoading() + + const columns = useMemo(() => { + const columnHelper = createColumnHelper() + + try { + return [ + columnHelper.accessor('key_photo_thumbnail_url', { + header: () =>

, + + cell({ row: { original: dataset } }) { + const datasetUrl = `/datasets/${dataset.id}` + + return ( + + + + + + ) + }, + }), + + columnHelper.accessor('id', { + header: () => ( + { + event.stopPropagation() + event.preventDefault() + const nextParams = new URLSearchParams(searchParams) + + if (datasetSort === undefined) { + nextParams.set('sort', 'asc') + } else if (datasetSort === 'asc') { + nextParams.set('sort', 'desc') + } else { + nextParams.delete('sort') + } + + setSearchParams(nextParams) + }} + width={DepositionPageDatasetTableWidths.id} + > + {t('datasetName')} + + ), + + cell({ row: { original: dataset } }) { + const datasetUrl = `/datasets/${dataset.id}` + + return ( + +

+

+ {isLoadingDebounced ? ( + + ) : ( + {dataset.title} + )} +

+ +

+ {isLoadingDebounced ? ( + + ) : ( + `${t('datasetId')}: ${dataset.id}` + )} +

+ +

+ {isLoadingDebounced ? ( + <> + + + + + ) : ( + + )} +

+
+ + ) + }, + }), + + columnHelper.accessor('organism_name', { + header: () => ( + + {t('organism')} + + ), + + cell: ({ getValue }) => ( + + ), + }), + + columnHelper.accessor( + (dataset) => dataset.runs_aggregate.aggregate?.count, + { + id: 'runs', + + header: () => ( + +

+ + {t('symbolPeriod')} +

+

+ +

+
+ } + arrowPadding={{ right: 270 }} + width={DepositionPageDatasetTableWidths.runs} + subHeader={t('withDepositionData')} + > + {t('runs')} + + ), + + cell: ({ getValue }) => ( + + ), + }, + ), + + columnHelper.accessor((dataset) => dataset.runs, { + id: 'annotations', + + header: () => ( + + {t('annotations')} + + ), + + cell({ getValue }) { + const runs = getValue() + const annotationCount = sum( + runs.flatMap((run) => + run.tomogram_voxel_spacings.flatMap( + (voxelSpacing) => + voxelSpacing.annotations_aggregate.aggregate?.count ?? 0, + ), + ), + ) + + return ( + + {annotationCount.toLocaleString()} + + ) + }, + }), + + columnHelper.accessor((dataset) => dataset.runs, { + id: 'annotatedObjects', + + header: () => ( + + {t('annotatedObjects')} + + ), + + cell({ getValue }) { + const runs = getValue() + const annotatedObjects = Array.from( + new Set( + runs.flatMap((run) => + run.tomogram_voxel_spacings.flatMap((voxelSpacing) => + voxelSpacing.annotations.flatMap( + (annotation) => annotation.object_name, + ), + ), + ), + ), + ) + + return ( + ( +
+ {range(0, ANNOTATED_OBJECTS_MAX).map((val) => ( + + ))} +
+ )} + > + {annotatedObjects.length === 0 ? ( + '--' + ) : ( + + )} +
+ ) + }, + }), + ] as ColumnDef[] + } catch (err) { + sendLogs({ + level: LogLevel.Error, + messages: [ + { + type: 'browser', + message: 'Error creating columns for dataset table', + error: getErrorMessage(err), + }, + ], + }) + + throw err + } + }, [datasetSort, isLoadingDebounced, searchParams, setSearchParams, t]) + + return ( + + ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionHeader.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionHeader.tsx new file mode 100644 index 000000000..f3935444d --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionHeader.tsx @@ -0,0 +1,51 @@ +import { Breadcrumbs } from 'app/components/Breadcrumbs' +import { KeyPhoto } from 'app/components/KeyPhoto' +import { PageHeader } from 'app/components/PageHeader' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useI18n } from 'app/hooks/useI18n' +import { + MetadataDrawerId, + useMetadataDrawer, +} from 'app/hooks/useMetadataDrawer' + +import { DepositionOverview } from './DepositionOverview' + +export function DepositionHeader() { + const { deposition } = useDepositionById() + const { toggleDrawer } = useMetadataDrawer() + const { t } = useI18n() + + return ( + } + lastModifiedDate={ + deposition.last_modified_date ?? deposition.deposition_date + } + metadata={[ + { + key: t('depositionId'), + value: String(deposition.id), + }, + ]} + onMoreInfoClick={() => toggleDrawer(MetadataDrawerId.Deposition)} + releaseDate={deposition.release_date} + title={deposition.title} + renderHeader={({ moreInfo }) => ( +
+
+ +
+ +
+ + + {moreInfo} +
+
+ )} + /> + ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/DepositionOverview.tsx b/frontend/packages/data-portal/app/components/Deposition/DepositionOverview.tsx new file mode 100644 index 000000000..ffc762f64 --- /dev/null +++ b/frontend/packages/data-portal/app/components/Deposition/DepositionOverview.tsx @@ -0,0 +1,102 @@ +import { sum } from 'lodash-es' +import { useMemo } from 'react' + +import { AuthorLegend } from 'app/components/AuthorLegend' +import { AuthorList } from 'app/components/AuthorList' +import { DatabaseList } from 'app/components/DatabaseList' +import { PageHeaderSubtitle } from 'app/components/PageHeaderSubtitle' +import { DOI_ID } from 'app/constants/external-dbs' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useI18n } from 'app/hooks/useI18n' +import { cnsNoMerge } from 'app/utils/cns' + +import { AnnotationMethodsSummary } from './AnnotationMethodsSummary' + +// use clsx here instead of cns since it erroneously merges text-sds-gray-500 and text-sds-caps-xxxs +const sectionHeaderStyles = cnsNoMerge( + 'font-semibold uppercase', + 'text-sds-gray-black', + 'text-sds-caps-xxxs leading-sds-caps-xxxs tracking-sds-caps', + 'mb-sds-xs', +) + +export function DepositionOverview() { + const { deposition } = useDepositionById() + + const { t } = useI18n() + + const annotationsCount = useMemo( + () => + sum( + deposition.datasets.flatMap((dataset) => + dataset.runs.flatMap( + (run) => + run.tomogram_voxel_spacings.at(0)?.annotations_aggregate.aggregate + ?.count, + ), + ), + ), + [deposition], + ) + + // clean up entries into lists + const publicationEntries = deposition.deposition_publications + ?.split(',') + .map((e) => e.trim()) + .filter((e) => DOI_ID.exec(e)) // only show DOI links + + const relatedDatabaseEntries = deposition.related_database_entries + ?.split(',') + .map((e) => e.trim()) + + return ( +
+
+ + {t('depositionOverview')} + +

+ {deposition.description} +

+
+
+
+

{t('authors')}

+ +
+ +
+
+
+

{t('depositionData')}

+

+ + {t('annotations')}: + + {annotationsCount.toLocaleString()} +

+
+ +
+

{t('publications')}

+ +
+ +
+

{t('relatedDatabases')}

+ +
+
+ +
+ ) +} diff --git a/frontend/packages/data-portal/app/components/Deposition/index.ts b/frontend/packages/data-portal/app/components/Deposition/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/packages/data-portal/app/components/I18n.tsx b/frontend/packages/data-portal/app/components/I18n.tsx index aa056e32f..b10fbf3eb 100644 --- a/frontend/packages/data-portal/app/components/I18n.tsx +++ b/frontend/packages/data-portal/app/components/I18n.tsx @@ -1,14 +1,13 @@ -import { LinkProps } from '@remix-run/react' import { Trans, TransProps } from 'react-i18next' import { Required } from 'utility-types' import type { I18nKeys } from 'app/types/i18n' -import { Link } from './Link' +import { Link, VariantLinkProps } from './Link' interface Props extends Omit, 'ns' | 'i18nKey'> { i18nKey: I18nKeys - linkProps?: Partial + linkProps?: Partial } /** @@ -28,10 +27,17 @@ export function I18n({ i18nKey, components, linkProps, ...props }: Props) { semibold: , code: , + urlNoColor: ( + , 'to'>)}> + {/* This will get replaced by I18next */} + tmp + + ), + url: ( , 'to'>)} + {...(linkProps as Required, 'to'>)} > {/* This will get replaced by I18next */} tmp diff --git a/frontend/packages/data-portal/app/components/Link/Link.tsx b/frontend/packages/data-portal/app/components/Link/Link.tsx index a8776db71..6075fc685 100644 --- a/frontend/packages/data-portal/app/components/Link/Link.tsx +++ b/frontend/packages/data-portal/app/components/Link/Link.tsx @@ -10,13 +10,12 @@ export const DASHED_BORDERED_CLASSES = export const DASHED_UNDERLINED_CLASSES = 'underline underline-offset-[3px] decoration-dashed hover:decoration-solid' +export type VariantLinkProps = LinkProps & { + variant?: 'dashed-bordered' | 'dashed-underlined' +} + function BaseLink( - { - to, - variant, - className, - ...props - }: LinkProps & { variant?: 'dashed-bordered' | 'dashed-underlined' }, + { to, variant, className, ...props }: VariantLinkProps, ref: ForwardedRef, ) { let newTabProps: Partial = {} diff --git a/frontend/packages/data-portal/app/components/PageHeaderSubtitle.tsx b/frontend/packages/data-portal/app/components/PageHeaderSubtitle.tsx index c870b9e02..9cf1e1d17 100644 --- a/frontend/packages/data-portal/app/components/PageHeaderSubtitle.tsx +++ b/frontend/packages/data-portal/app/components/PageHeaderSubtitle.tsx @@ -1,8 +1,21 @@ import { ReactNode } from 'react' -export function PageHeaderSubtitle({ children }: { children: ReactNode }) { +import { cns } from 'app/utils/cns' + +export function PageHeaderSubtitle({ + className, + children, +}: { + className: string + children: ReactNode +}) { return ( -

+

{children}

) diff --git a/frontend/packages/data-portal/app/components/Run/AnnotationOveriewTable.tsx b/frontend/packages/data-portal/app/components/Run/AnnotationOveriewTable.tsx index 8fab56c7e..6d23c9af5 100644 --- a/frontend/packages/data-portal/app/components/Run/AnnotationOveriewTable.tsx +++ b/frontend/packages/data-portal/app/components/Run/AnnotationOveriewTable.tsx @@ -1,6 +1,6 @@ import { AccordionMetadataTable } from 'app/components/AccordionMetadataTable' import { AuthorLegend } from 'app/components/AuthorLegend' -import { DatasetAuthors } from 'app/components/Dataset/DatasetAuthors' +import { AuthorList } from 'app/components/AuthorList' import { useI18n } from 'app/hooks/useI18n' import { useAnnotation } from 'app/state/annotation' @@ -28,7 +28,7 @@ export function AnnotationOverviewTable() { : t('annotationAuthors'), labelExtra: , renderValue: () => { - return + return }, values: [''], className: 'leading-sds-body-s', diff --git a/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx b/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx index 2b59b5c57..40adf0e2a 100644 --- a/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx +++ b/frontend/packages/data-portal/app/components/Run/AnnotationTable.tsx @@ -5,11 +5,16 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table' import { range } from 'lodash-es' import { ComponentProps, useCallback, useMemo } from 'react' -import { DatasetAuthors } from 'app/components/Dataset/DatasetAuthors' +import { AuthorList } from 'app/components/AuthorList' import { I18n } from 'app/components/I18n' import { DASHED_BORDERED_CLASSES } from 'app/components/Link' import { CellHeader, PageTable, TableCell } from 'app/components/Table' import { Tooltip } from 'app/components/Tooltip' +import { + methodLabels, + methodTooltipLabels, + MethodType, +} from 'app/constants/methodTypes' import { MAX_PER_PAGE } from 'app/constants/pagination' import { AnnotationTableWidths } from 'app/constants/table' import { TestIds } from 'app/constants/testIds' @@ -58,24 +63,6 @@ function ConfidenceValue({ value }: { value: number }) { ) } -type MethodTypeLabels = { - automated: I18nKeys - hybrid: I18nKeys - manual: I18nKeys -} - -const methodLabels: MethodTypeLabels = { - automated: 'automated', - hybrid: 'hybrid', - manual: 'manual', -} - -const methodTooltipLabels: MethodTypeLabels = { - automated: 'methodTypeAutomated', - hybrid: 'methodTypeHybrid', - manual: 'methodTypeManual', -} - export function AnnotationTable() { const { isLoadingDebounced } = useIsLoading() const { run } = useRunById() @@ -184,7 +171,7 @@ export function AnnotationTable() {
- +
), @@ -257,10 +244,7 @@ export function AnnotationTable() { ) } - const methodType = annotation.method_type as - | 'automated' - | 'manual' - | 'hybrid' + const methodType = annotation.method_type as MethodType return ( } + breadcrumbs={} metadata={[{ key: t('runId'), value: `${run.id}` }]} onMoreInfoClick={() => toggleDrawer(MetadataDrawerId.Run)} title={run.name} @@ -123,7 +123,9 @@ export function RunHeader() {
- {t('runOverview')} + + {t('runOverview')} + {multipleTomogramsEnabled ? ( - {children} +

{children}

+ {subHeader && ( +

+ {subHeader} +

+ )} ) } diff --git a/frontend/packages/data-portal/app/components/icons/SourceCodeIcon.tsx b/frontend/packages/data-portal/app/components/icons/SourceCodeIcon.tsx new file mode 100644 index 000000000..02ebdce4d --- /dev/null +++ b/frontend/packages/data-portal/app/components/icons/SourceCodeIcon.tsx @@ -0,0 +1,31 @@ +import { IconProps } from './icon.types' + +export function SourceCodeIcon(props: IconProps) { + return ( + + + + + + ) +} diff --git a/frontend/packages/data-portal/app/components/icons/WeightsIcon.tsx b/frontend/packages/data-portal/app/components/icons/WeightsIcon.tsx new file mode 100644 index 000000000..2f6fc355a --- /dev/null +++ b/frontend/packages/data-portal/app/components/icons/WeightsIcon.tsx @@ -0,0 +1,21 @@ +import { IconProps } from './icon.types' + +export function WeightsIcon(props: IconProps) { + return ( + + + + ) +} diff --git a/frontend/packages/data-portal/app/components/icons/index.ts b/frontend/packages/data-portal/app/components/icons/index.ts index bc95aae2d..3c032aaf7 100644 --- a/frontend/packages/data-portal/app/components/icons/index.ts +++ b/frontend/packages/data-portal/app/components/icons/index.ts @@ -7,4 +7,6 @@ export * from './ImageInstituteIcon' export * from './KeyPhotoFallbackIcon' export * from './ORCIDIcon' export * from './SmallChevronRightIcon' +export * from './SourceCodeIcon' export * from './SpeechBubbleIcon' +export * from './WeightsIcon' diff --git a/frontend/packages/data-portal/app/constants/methodTypes.ts b/frontend/packages/data-portal/app/constants/methodTypes.ts new file mode 100644 index 000000000..0b3bc12ab --- /dev/null +++ b/frontend/packages/data-portal/app/constants/methodTypes.ts @@ -0,0 +1,21 @@ +import { I18nKeys } from 'app/types/i18n' + +type MethodTypeLabels = { + automated: I18nKeys + hybrid: I18nKeys + manual: I18nKeys +} + +export type MethodType = keyof MethodTypeLabels + +export const methodLabels: MethodTypeLabels = { + automated: 'automated', + hybrid: 'hybrid', + manual: 'manual', +} + +export const methodTooltipLabels: MethodTypeLabels = { + automated: 'methodTypeAutomated', + hybrid: 'methodTypeHybrid', + manual: 'methodTypeManual', +} diff --git a/frontend/packages/data-portal/app/constants/table.ts b/frontend/packages/data-portal/app/constants/table.ts index 67ced8b4d..c63b7289c 100644 --- a/frontend/packages/data-portal/app/constants/table.ts +++ b/frontend/packages/data-portal/app/constants/table.ts @@ -47,3 +47,12 @@ export const DepositionTableWidths = { annotatedObjects: { min: 120, max: 400 }, objectShapeTypes: { min: 120, max: 200 }, } + +export const DepositionPageDatasetTableWidths = { + photo: PHOTO_COLUMN_WIDTH, + id: { min: 300, max: 800 }, + organism: { min: 100, max: 400 }, + runs: { min: 120, max: 200 }, + annotations: { min: 120, max: 200 }, + annotatedObjects: { min: 120, max: 400 }, +} diff --git a/frontend/packages/data-portal/app/graphql/getDepositionById.server.ts b/frontend/packages/data-portal/app/graphql/getDepositionById.server.ts new file mode 100644 index 000000000..044002f98 --- /dev/null +++ b/frontend/packages/data-portal/app/graphql/getDepositionById.server.ts @@ -0,0 +1,167 @@ +import type { ApolloClient, NormalizedCacheObject } from '@apollo/client' + +import { gql } from 'app/__generated__' +import { + Datasets_Bool_Exp as Depositions_Bool_Exp, + Order_By, +} from 'app/__generated__/graphql' +import { MAX_PER_PAGE } from 'app/constants/pagination' +import { FilterState, getFilterState } from 'app/hooks/useFilter' + +const GET_DEPOSITION_BY_ID = gql(` + query GetDepositionById( + $id: Int!, + $dataset_limit: Int, + $dataset_offset: Int, + $dataset_order_by: order_by, + $filter: datasets_bool_exp, + ) { + deposition: datasets_by_pk(id: $id) { + s3_prefix + + # key photo + key_photo_url + + # dates + last_modified_date + release_date + deposition_date + + # metadata + id + title + description + + funding_sources { + funding_agency_name + grant_id + } + + related_database_entries + deposition_citations: dataset_citations + + authors( + order_by: { + author_list_order: asc, + }, + ) { + corresponding_author_status + email + name + orcid + primary_author_status + } + + # publication info + related_database_entries + deposition_publications: dataset_publications + + # runs + run_stats: runs { + tomogram_voxel_spacings { + annotations { + object_name + + files(distinct_on: shape_type) { + shape_type + } + } + + annotations_aggregate { + aggregate { + count + } + } + } + } + } + + datasets( + limit: $dataset_limit, + offset: $dataset_offset, + order_by: { title: $dataset_order_by }, + where: $filter + ) { + id + title + organism_name + key_photo_thumbnail_url + + authors( + order_by: { + author_list_order: asc, + }, + ) { + name + primary_author_status + corresponding_author_status + } + + runs_aggregate { + aggregate { + count + } + } + + runs { + tomogram_voxel_spacings { + annotations(distinct_on: object_name) { + object_name + } + + annotations_aggregate { + aggregate { + count + } + } + } + } + } + + datasets_aggregate { + aggregate { + count + } + } + + filtered_datasets_aggregate: datasets_aggregate(where: $filter) { + aggregate { + count + } + } + } +`) + +function getFilter(filterState: FilterState) { + const filters: Depositions_Bool_Exp[] = [] + // TODO: implement filters + // eslint-disable-next-line no-console + console.log(filterState) + + return { _and: filters } as Depositions_Bool_Exp +} + +export async function getDepositionById({ + client, + id, + orderBy, + page = 1, + params = new URLSearchParams(), +}: { + client: ApolloClient + orderBy?: Order_By | null + id: number + page?: number + params?: URLSearchParams +}) { + return client.query({ + query: GET_DEPOSITION_BY_ID, + variables: { + id, + dataset_limit: MAX_PER_PAGE, + dataset_offset: (page - 1) * MAX_PER_PAGE, + dataset_order_by: orderBy, + filter: getFilter(getFilterState(params)), + }, + }) +} diff --git a/frontend/packages/data-portal/app/hooks/useDepositionById.ts b/frontend/packages/data-portal/app/hooks/useDepositionById.ts new file mode 100644 index 000000000..5ac30a603 --- /dev/null +++ b/frontend/packages/data-portal/app/hooks/useDepositionById.ts @@ -0,0 +1,60 @@ +import { useMemo } from 'react' +import { useTypedLoaderData } from 'remix-typedjson' + +import { GetDepositionByIdQuery } from 'app/__generated__/graphql' +import { NonUndefined } from 'app/types/utils' + +export type Dataset = GetDepositionByIdQuery['datasets'][number] + +export type Deposition = NonUndefined & { + datasets: GetDepositionByIdQuery['datasets'] + datasets_aggregate: GetDepositionByIdQuery['datasets_aggregate'] + filtered_datasets_aggregate: GetDepositionByIdQuery['filtered_datasets_aggregate'] +} + +export function useDepositionById() { + const data = useTypedLoaderData() + + const objectNames = useMemo( + () => + Array.from( + new Set( + data.deposition?.run_stats.flatMap((run) => + run.tomogram_voxel_spacings.flatMap((voxelSpacing) => + voxelSpacing.annotations.flatMap( + (annotation) => annotation.object_name, + ), + ), + ), + ), + ), + [data.deposition?.run_stats], + ) + + const objectShapeTypes = useMemo( + () => + Array.from( + new Set( + data.deposition?.run_stats.flatMap((run) => + run.tomogram_voxel_spacings.flatMap((voxelSpacing) => + voxelSpacing.annotations.flatMap((annotation) => + annotation.files.flatMap((file) => file.shape_type), + ), + ), + ), + ), + ), + [data.deposition?.run_stats], + ) + + return { + deposition: { + ...data.deposition, + datasets: data.datasets, + datasets_aggregate: data.datasets_aggregate, + filtered_datasets_aggregate: data.filtered_datasets_aggregate, + } as Deposition, + objectNames, + objectShapeTypes, + } +} diff --git a/frontend/packages/data-portal/app/hooks/useMetadataDrawer.ts b/frontend/packages/data-portal/app/hooks/useMetadataDrawer.ts index 31656475c..2cc34bca5 100644 --- a/frontend/packages/data-portal/app/hooks/useMetadataDrawer.ts +++ b/frontend/packages/data-portal/app/hooks/useMetadataDrawer.ts @@ -7,6 +7,7 @@ import { stringParam, useQueryParams } from './useQueryParam' export enum MetadataDrawerId { Annotation = 'annotation', Dataset = 'dataset', + Deposition = 'deposition', Run = 'run', } diff --git a/frontend/packages/data-portal/app/routes/depositions.$id.tsx b/frontend/packages/data-portal/app/routes/depositions.$id.tsx new file mode 100644 index 000000000..54ffd0b96 --- /dev/null +++ b/frontend/packages/data-portal/app/routes/depositions.$id.tsx @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-throw-literal */ + +import { CellHeaderDirection } from '@czi-sds/components' +import { json, LoaderFunctionArgs, redirect } from '@remix-run/server-runtime' + +import { Order_By } from 'app/__generated__/graphql' +import { apolloClient } from 'app/apollo.server' +import { DatasetsTable } from 'app/components/Deposition/DatasetsTable' +import { DepositionHeader } from 'app/components/Deposition/DepositionHeader' +import { TablePageLayout } from 'app/components/TablePageLayout' +import { QueryParams } from 'app/constants/query' +import { getDepositionById } from 'app/graphql/getDepositionById.server' +import { useDepositionById } from 'app/hooks/useDepositionById' +import { useI18n } from 'app/hooks/useI18n' +import { getFeatureFlag } from 'app/utils/featureFlags' + +export async function loader({ params, request }: LoaderFunctionArgs) { + const url = new URL(request.url) + + const showDepositions = getFeatureFlag({ + env: process.env.ENV, + key: 'depositions', + params: url.searchParams, + }) + + if (!showDepositions) { + return redirect('/404') + } + + const id = params.id ? +params.id : NaN + const page = +(url.searchParams.get(QueryParams.Page) ?? '1') + const sort = (url.searchParams.get('sort') ?? undefined) as + | CellHeaderDirection + | undefined + + if (Number.isNaN(+id)) { + throw new Response(null, { + status: 400, + statusText: 'ID is not defined', + }) + } + + let orderBy: Order_By | null = null + + if (sort) { + orderBy = sort === 'asc' ? Order_By.Asc : Order_By.Desc + } + + const { data } = await getDepositionById({ + id, + orderBy, + page, + client: apolloClient, + params: url.searchParams, + }) + + if (data.deposition === null) { + throw new Response(null, { + status: 404, + statusText: `Deposition with ID ${id} not found`, + }) + } + + return json(data) +} + +export default function DatasetByIdPage() { + const { deposition } = useDepositionById() + const { t } = useI18n() + + return ( + } + table={} + totalCount={deposition.datasets_aggregate.aggregate?.count ?? 0} + /> + ) +} diff --git a/frontend/packages/data-portal/app/types/utils.ts b/frontend/packages/data-portal/app/types/utils.ts new file mode 100644 index 000000000..aabab7d8c --- /dev/null +++ b/frontend/packages/data-portal/app/types/utils.ts @@ -0,0 +1 @@ +export type NonUndefined = T extends undefined ? never : T diff --git a/frontend/packages/data-portal/public/locales/en/translation.json b/frontend/packages/data-portal/public/locales/en/translation.json index efe2ce607..6cb85582e 100644 --- a/frontend/packages/data-portal/public/locales/en/translation.json +++ b/frontend/packages/data-portal/public/locales/en/translation.json @@ -18,6 +18,7 @@ "alignmentFile": "Alignment File", "all": "All", "allDatasets": "All Datasets", + "allDepositions": "All Depositions", "annotatedObjects": "Annotated Objects", "annotationAuthor": "Annotation Author", "annotationAuthors": "Annotation Authors", @@ -26,6 +27,7 @@ "annotationId": "Annotation ID", "annotationMetadata": "Annotation Metadata", "annotationMethod": "Annotation Method", + "annotationMethodsSummary": "Annotation Methods Summary", "annotationObject": "Annotation Object", "annotationOverview": "Annotation Overview", "annotationSoftware": "Annotation Software", @@ -98,9 +100,13 @@ "datasets": "Datasets", "datasetsTab": "Datasets {{count}}", "dataSummary": "Data Summary", + "deposition": "Deposition", + "depositionData": "Deposition Data", "depositionDate": "Deposition Date", "depositionId": "Deposition ID", "depositionName": "Deposition Name", + "depositionOnly": "Deposition only", + "depositionOverview": "Deposition Overview", "depositions": "Depositions", "depositionsTab": "Depositions {{count}}", "description": "Description", @@ -187,6 +193,7 @@ "manual": "Manual", "meetsAll": "Meets all", "metadata": "Metadata", + "methodLinks": "Method Links", "methodType": "Method Type", "methodTypeAutomated": "Automated: Annotations were generated using automated tools or algorithms without supervision.", "methodTypeHybrid": "Hybrid: Annotations were generated using a combination of automated and manual methods.", @@ -194,6 +201,7 @@ "methodTypeManual": "Manual: Annotations were created by hand.", "microscopeManufacturer": "Microscope Manufacturer", "microscopeModel": "Microscope model", + "modelWeights": "Model Weights", "moderate": "Moderate", "moreInfo": "More Info", "moreInfoComingFall2024": "More info coming in Fall 2024", @@ -225,6 +233,7 @@ "organismName": "Organism Name", "organizers": "Organizers", "orientedPoint": "Oriented Point", + "other": "Other", "otherSetup": "Other Setup", "participateInOurCompetition": "Participate in our ML competition", "participateInOurCompetitionCTA": "Develop a ML model for annotating subcellular structures and proteins in CryoET data.", @@ -265,7 +274,8 @@ "runOverview": "Run Overview", "runs": "Runs", "runsTab": "Runs {{count}}", - "runsTooltip": "Run: A tomography run is a collection of all data and annotations related to one physical location in a biological specimen. Learn More", + "runsTooltip": "Run: A tomography run is a collection of all data and annotations related to one physical location in a biological specimen. Learn More", + "runsTooltipDepositionSubtext": "Count only shows the number of runs containing deposition data. Learn more about depositions in the portal.", "sampleAndExperimentConditions": "Sample and Experiment Conditions", "samplePreparation": "Sample Preparation", "sampleType": "Sample Type", @@ -276,10 +286,12 @@ "selectDownloadMethod": "Select Download Method", "selectSaveDestination": "Select Save Destination", "seriesIsAligned": "Series is Aligned", - "showLess": "Show Less", + "showLess": "Show less", + "showMore": "Show {{count}} more", "size": "Size", "smallestAvailableVoxelSpacing": "Smallest Available Voxel Spacing", "somethingWentWrong": "Something went wrong", + "sourceCode": "Source Code", "species": "Species", "sphericalAberrationConstant": "Spherical Aberration Constant", "sponsoredBy": "Sponsored By", @@ -288,6 +300,7 @@ "submitFeedback": "Submit Feedback", "surveyBanner": "Share first impressions, or sign up for invites to future feedback activities in this short form.", "surveyLink": "https://airtable.com/apppmytRJXoXYTO9w/shrjmV9knAC7E7VVM?prefill_Event=Banner&hide_Event=true", + "symbolPeriod": ".", "tellUsMore": "Tell us More", "terms": "Terms", "termsOfUse": "Terms of Use", @@ -303,7 +316,7 @@ "tiltSeriesAlignment": "Tilt Series Alignment", "tiltSeriesMetadata": "Tilt Series Metadata", "tiltSeriesQualityScore": "Tilt Series Quality Score", - "tiltSeriesTooltip": "Tilt Series Quality: Dataset author’s assessment of tilt series quality. Score ranges 1-5, with 5 being best. Learn More", + "tiltSeriesTooltip": "Tilt Series Quality: Dataset author's assessment of tilt series quality. Score ranges 1-5, with 5 being best. Learn More", "tiltStep": "Tilt Step", "tiltingScheme": "Tilting Scheme", "tissueName": "Tissue Name", @@ -338,6 +351,8 @@ "viewDatasetsCta": "Find and visualize cryoET datasets in the portal and download to use for your work.", "viewTomogram": "View Tomogram", "voxelSpacingId": "Voxel Spacing ID", + "website": "Website", + "withDepositionData": "With deposition data", "yes": "Yes", "youMustHaveCliInstalled": "You must have AWS CLI installed. How to Install AWS CLI.", "youMustHaveCurlInstalled": "You must have cURL installed. How to Install cURL",