From ac20c18ac54e57d3707de3e12f66c7b4a23be9cb Mon Sep 17 00:00:00 2001 From: Kira Evans Date: Thu, 27 Jun 2024 11:31:35 -0700 Subject: [PATCH] feat: add skeleton for browse depositions page (#822) #773 --- .../components/BrowseData/BrowseDataTabs.tsx | 42 ++- .../components/BrowseData/DatasetTable.tsx | 4 +- .../components/BrowseData/DepositionTable.tsx | 332 ++++++++++++++++++ .../app/components/Dataset/RunsTable.tsx | 4 +- .../data-portal/app/components/KeyPhoto.tsx | 13 +- .../app/components/Layout/TopNavigation.tsx | 4 +- .../data-portal/app/components/Tabs.tsx | 56 +-- .../app/constants/objectShapeTypes.ts | 6 + .../data-portal/app/constants/table.ts | 9 + .../graphql/getBrowseDepositions.server.ts | 194 ++++++++++ .../data-portal/app/hooks/useDepositions.ts | 33 ++ .../app/routes/browse-data.depositions.tsx | 73 ++++ .../data-portal/app/utils/featureFlags.ts | 4 +- .../public/locales/en/translation.json | 11 + 14 files changed, 732 insertions(+), 53 deletions(-) create mode 100644 frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx create mode 100644 frontend/packages/data-portal/app/constants/objectShapeTypes.ts create mode 100644 frontend/packages/data-portal/app/graphql/getBrowseDepositions.server.ts create mode 100644 frontend/packages/data-portal/app/hooks/useDepositions.ts create mode 100644 frontend/packages/data-portal/app/routes/browse-data.depositions.tsx diff --git a/frontend/packages/data-portal/app/components/BrowseData/BrowseDataTabs.tsx b/frontend/packages/data-portal/app/components/BrowseData/BrowseDataTabs.tsx index b17386691..9a7ed659a 100644 --- a/frontend/packages/data-portal/app/components/BrowseData/BrowseDataTabs.tsx +++ b/frontend/packages/data-portal/app/components/BrowseData/BrowseDataTabs.tsx @@ -1,49 +1,65 @@ -import { useLocation } from '@remix-run/react' +import { useLocation, useNavigate } from '@remix-run/react' import { useMemo } from 'react' import { useTypedLoaderData } from 'remix-typedjson' import { GetToolbarDataQuery } from 'app/__generated__/graphql' import { TabData, Tabs } from 'app/components/Tabs' -import { i18n } from 'app/i18n' +import { useI18n } from 'app/hooks/useI18n' +import { useFeatureFlag } from 'app/utils/featureFlags' export enum BrowseDataTab { Datasets = 'datasets', + Depositions = 'depositions', Runs = 'runs', } // TODO: uncomment features when implemented export function BrowseDataTabs() { - // const navigate = useNavigate() + const { t } = useI18n() + const navigate = useNavigate() const { pathname } = useLocation() - const tab = pathname.includes('/browse-data/datasets') - ? BrowseDataTab.Datasets - : BrowseDataTab.Runs + const showDepositions = useFeatureFlag('depositions') + + let tab = BrowseDataTab.Datasets + if (pathname.includes('/browse-data/depositions')) { + tab = BrowseDataTab.Depositions + } else if (pathname.includes('/browse-data/runs')) { + tab = BrowseDataTab.Runs + } const data = useTypedLoaderData() const datasetCount = data.datasets_aggregate.aggregate?.count ?? 0 + // TODO: hook up to backend when available + // const depositionsCount = data.depositions_aggregate?.count ?? 0 // const runCount = data.runs_aggregate.aggregate?.count ?? 0 const tabOptions = useMemo[]>( () => [ { - label: i18n.datasetsTab(datasetCount), + label: t('datasetsTab', { count: datasetCount }), value: BrowseDataTab.Datasets, }, // { - // label: i18n.runsTab(runCount), + // label: t('runsTab', { count: runCount }), // value: BrowseDataTab.Runs, // }, + { + label: t('depositionsTab', { count: datasetCount }), + value: BrowseDataTab.Depositions, + }, ], - [datasetCount], - // [datasetCount, runCount], + [datasetCount, t], + // [datasetCount, depositionsCount, runCount, t], ) return ( navigate(`/browse-data/${nextTab}`)} - onChange={() => null} + onChange={(nextTab) => navigate(`/browse-data/${nextTab}`)} value={tab} - tabs={tabOptions} + tabs={tabOptions.filter( + (option) => + showDepositions || option.value !== BrowseDataTab.Depositions, + )} /> ) } diff --git a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx index 95e0ea6b1..c9722c24f 100644 --- a/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx +++ b/frontend/packages/data-portal/app/components/BrowseData/DatasetTable.tsx @@ -90,7 +90,9 @@ export function DatasetTable() { title={dataset.title} src={dataset.key_photo_thumbnail_url ?? undefined} loading={isLoadingDebounced} - overlayOnGroupHover={!isClickingOnEmpiarId} + textOnGroupHover={ + isClickingOnEmpiarId ? undefined : 'openDataset' + } /> diff --git a/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx b/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx new file mode 100644 index 000000000..c686c6b85 --- /dev/null +++ b/frontend/packages/data-portal/app/components/BrowseData/DepositionTable.tsx @@ -0,0 +1,332 @@ +/* eslint-disable react/no-unstable-nested-components */ + +import { CellHeaderDirection } from '@czi-sds/components' +import Skeleton from '@mui/material/Skeleton' +import { useNavigate, 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 { DatasetAuthors } from 'app/components/Dataset/DatasetAuthors' +import { KeyPhoto } from 'app/components/KeyPhoto' +import { Link } from 'app/components/Link' +import { CellHeader, PageTable, TableCell } from 'app/components/Table' +import { shapeTypeToI18nKey } from 'app/constants/objectShapeTypes' +import { ANNOTATED_OBJECTS_MAX, MAX_PER_PAGE } from 'app/constants/pagination' +import { DepositionTableWidths } from 'app/constants/table' +import { Deposition, useDepositions } from 'app/hooks/useDepositions' +import { useI18n } from 'app/hooks/useI18n' +import { useIsLoading } from 'app/hooks/useIsLoading' +import { I18nKeys } from 'app/types/i18n' +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_DEPOSITIONS = range(0, MAX_PER_PAGE).map( + (value) => + ({ + authors: [], + id: value, + title: `loading-deposition-${value}`, + deposition_date: '', + annotations_count: [], + datasets_count: {}, + runs: [], + }) as Deposition, +) + +export function DepositionTable() { + const { t } = useI18n() + const { depositions } = useDepositions() + + const [searchParams, setSearchParams] = useSearchParams() + const depositionSort = (searchParams.get('sort') ?? undefined) as + | CellHeaderDirection + | undefined + + const { isLoadingDebounced } = useIsLoading() + const navigate = useNavigate() + + const columns = useMemo(() => { + const columnHelper = createColumnHelper() + + try { + return [ + columnHelper.accessor('key_photo_thumbnail_url', { + header: () =>

, + + cell({ row: { original: deposition } }) { + const depositionUrl = `/depositions/${deposition.id}` + + return ( + + + + + + ) + }, + }), + + columnHelper.accessor('id', { + header: () => ( + { + event.stopPropagation() + event.preventDefault() + const nextParams = new URLSearchParams(searchParams) + + if (depositionSort === undefined) { + nextParams.set('sort', 'asc') + } else if (depositionSort === 'asc') { + nextParams.set('sort', 'desc') + } else { + nextParams.delete('sort') + } + + setSearchParams(nextParams) + }} + width={DepositionTableWidths.id} + > + {t('depositionName')} + + ), + + cell({ row: { original: deposition } }) { + const depositionUrl = `/depositions/${deposition.id}` + + return ( + +

+

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

+ +

+ {isLoadingDebounced ? ( + + ) : ( + `${t('depositionId')}: ${deposition.id}` + )} +

+ +

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

+
+ + ) + }, + }), + + columnHelper.accessor('deposition_date', { + header: () => ( + + {t('depositionDate')} + + ), + + cell({ row: { original: deposition } }) { + return {deposition.deposition_date} + }, + }), + + columnHelper.accessor('annotations_count', { + header: () => ( + + {t('annotations')} + + ), + + cell({ row: { original: deposition } }) { + // TODO: hook up to real data when backend ready + const annotationsCount = sum( + deposition.annotations_count.flatMap((run) => + run.tomogram_voxel_spacings.flatMap( + (tomo) => tomo.annotations_aggregate.aggregate?.count ?? 0, + ), + ), + ) + const runsCount = deposition.datasets_count.aggregate?.count ?? 0 + + return ( + +

+ {isLoadingDebounced ? ( + + ) : ( + annotationsCount.toLocaleString() + )} +

+ +

+ {isLoadingDebounced ? ( + + ) : ( + t('acrossDatasets', { count: runsCount }) + )} +

+
+ ) + }, + }), + + columnHelper.accessor((deposition) => deposition.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 ? ( + '--' + ) : ( + + )} +
+ ) + }, + }), + + columnHelper.accessor((deposition) => deposition.runs, { + id: 'shapeTypes', + header: () => ( + + {t('objectShapeType')} + + ), + + cell({ getValue }) { + const runs = getValue() + const shapeTypes = Array.from( + new Set( + runs.flatMap((run) => + run.tomogram_voxel_spacings.flatMap((voxelSpacing) => + voxelSpacing.shape_types.flatMap((annotation) => + annotation.files.flatMap((file) => file.shape_type), + ), + ), + ), + ), + ) + + return ( + ( +
+ {range(0, 2).map((val) => ( + + ))} +
+ )} + > + {shapeTypes.length === 0 ? ( + '--' + ) : ( +
    + {Object.entries(shapeTypeToI18nKey) + .filter(([key]) => shapeTypes.includes(key)) + .map((entry) => ( +
  • + {t(entry[1] as I18nKeys)} +
  • + ))} +
+ )} +
+ ) + }, + }), + ] as ColumnDef[] + } catch (err) { + sendLogs({ + level: LogLevel.Error, + messages: [ + { + type: 'browser', + message: 'Error creating columns for deposition table', + error: getErrorMessage(err), + }, + ], + }) + + throw err + } + }, [depositionSort, isLoadingDebounced, searchParams, setSearchParams, t]) + + return ( + navigate(`/depositions/${row.original.id}`)} + hoverType="group" + /> + ) +} diff --git a/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx b/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx index fe2137aba..c1a39521c 100644 --- a/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx +++ b/frontend/packages/data-portal/app/components/Dataset/RunsTable.tsx @@ -86,7 +86,9 @@ export function RunsTable() { ?.key_photo_thumbnail_url ?? undefined } loading={isLoadingDebounced} - overlayOnGroupHover={!isHoveringOverInteractable} + textOnGroupHover={ + isHoveringOverInteractable ? undefined : 'openRun' + } /> ), diff --git a/frontend/packages/data-portal/app/components/KeyPhoto.tsx b/frontend/packages/data-portal/app/components/KeyPhoto.tsx index cdf0fa931..91de3e2dd 100644 --- a/frontend/packages/data-portal/app/components/KeyPhoto.tsx +++ b/frontend/packages/data-portal/app/components/KeyPhoto.tsx @@ -1,9 +1,9 @@ import Skeleton from '@mui/material/Skeleton' -import { useLocation } from '@remix-run/react' import { match, P } from 'ts-pattern' import { KeyPhotoFallbackIcon } from 'app/components/icons' import { useI18n } from 'app/hooks/useI18n' +import { I18nKeys } from 'app/types/i18n' import { cns } from 'app/utils/cns' export function KeyPhoto({ @@ -11,16 +11,15 @@ export function KeyPhoto({ loading = false, src, title, - overlayOnGroupHover, + textOnGroupHover, }: { className?: string loading?: boolean src?: string title: string - overlayOnGroupHover?: boolean + textOnGroupHover?: I18nKeys }) { const { t } = useI18n() - const { pathname } = useLocation() return (
{match([src, loading]) .with([P._, true], () => ) diff --git a/frontend/packages/data-portal/app/components/Layout/TopNavigation.tsx b/frontend/packages/data-portal/app/components/Layout/TopNavigation.tsx index c9b036d76..8c00fb849 100644 --- a/frontend/packages/data-portal/app/components/Layout/TopNavigation.tsx +++ b/frontend/packages/data-portal/app/components/Layout/TopNavigation.tsx @@ -18,7 +18,9 @@ interface TopNavLink { const TOP_NAV_LINKS: TopNavLink[] = [ { isActive: (pathname) => - pathname.includes('/datasets') || pathname.includes('/runs'), + pathname.includes('/datasets') || + pathname.includes('/runs') || + pathname.includes('/depositions'), label: 'browseData', link: '/browse-data/datasets', }, diff --git a/frontend/packages/data-portal/app/components/Tabs.tsx b/frontend/packages/data-portal/app/components/Tabs.tsx index 9971540ab..5e61442b4 100644 --- a/frontend/packages/data-portal/app/components/Tabs.tsx +++ b/frontend/packages/data-portal/app/components/Tabs.tsx @@ -20,32 +20,34 @@ export function Tabs({ return ( // Use Material UI tabs because SDS tabs have bug where styles aren't // rendered correctly within a modal. - onChange(nextTab)} - classes={{ - // Translate to overlap with bottom gray border used in different places - // in the UI. - root: 'translate-y-[2px]', - indicator: 'bg-sds-primary-500', - flexContainer: 'gap-sds-xl', - }} - > - {tabs.map((tab) => ( - - ))} - +
+ onChange(nextTab)} + classes={{ + // Translate to overlap with bottom gray border used in different places + // in the UI. + root: 'translate-y-[2px] !min-h-0', + indicator: 'bg-sds-primary-500', + flexContainer: 'gap-sds-xl !pb-sds-xxs', + }} + > + {tabs.map((tab) => ( + + ))} + +
) } diff --git a/frontend/packages/data-portal/app/constants/objectShapeTypes.ts b/frontend/packages/data-portal/app/constants/objectShapeTypes.ts new file mode 100644 index 000000000..aa29d97a8 --- /dev/null +++ b/frontend/packages/data-portal/app/constants/objectShapeTypes.ts @@ -0,0 +1,6 @@ +export const shapeTypeToI18nKey = { + InstanceSegmentation: 'instanceSegmentation', + OrientedPoint: 'orientedPoint', + Point: 'point', + SegmentationMask: 'segmentationMask', +} diff --git a/frontend/packages/data-portal/app/constants/table.ts b/frontend/packages/data-portal/app/constants/table.ts index c6e8f2b57..36356f284 100644 --- a/frontend/packages/data-portal/app/constants/table.ts +++ b/frontend/packages/data-portal/app/constants/table.ts @@ -38,3 +38,12 @@ export const RunTableWidths = { annotatedObjects: { min: 250, max: 400 }, actions: { min: 150, max: 200 }, } + +export const DepositionTableWidths = { + photo: PHOTO_COLUMN_WIDTH, + id: { min: 450, max: 800 }, + depositionDate: { min: 110, max: 160 }, + annotations: { min: 100, max: 200 }, + annotatedObjects: { min: 120, max: 400 }, + objectShapeTypes: { min: 120, max: 200 }, +} diff --git a/frontend/packages/data-portal/app/graphql/getBrowseDepositions.server.ts b/frontend/packages/data-portal/app/graphql/getBrowseDepositions.server.ts new file mode 100644 index 000000000..8fc84d82d --- /dev/null +++ b/frontend/packages/data-portal/app/graphql/getBrowseDepositions.server.ts @@ -0,0 +1,194 @@ +import type { ApolloClient, NormalizedCacheObject } from '@apollo/client' +import { performance } from 'perf_hooks' + +import { gql } from 'app/__generated__' +import { Datasets_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_DEPOSITIONS_DATA_QUERY = gql(` + query GetDepositionsData( + $limit: Int, + $offset: Int, + $order_by_deposition: order_by, + $filter: datasets_bool_exp, + ) { + depositions: datasets( + limit: $limit, + offset: $offset, + order_by: { title: $order_by_deposition }, + where: $filter + ) { + id + title + key_photo_thumbnail_url + deposition_date + + authors( + order_by: { + author_list_order: asc, + }, + ) { + name + primary_author_status + corresponding_author_status + } + + annotations_count: runs { + tomogram_voxel_spacings { + annotations_aggregate { + aggregate { + count + } + } + } + } + + datasets_count: runs_aggregate { + aggregate { + count + } + } + + runs { + tomogram_voxel_spacings { + annotations(distinct_on: object_name) { + object_name + } + + shape_types: annotations { + files(distinct_on: shape_type) { + shape_type + } + } + } + } + } + + depositions_aggregate: datasets_aggregate { + aggregate { + count + } + } + + filtered_depositions_aggregate: datasets_aggregate(where: $filter) { + aggregate { + count + } + } + + object_names: annotations(distinct_on: object_name) { + object_name + } + + object_shape_types: annotation_files(distinct_on: shape_type) { + shape_type + } + } +`) + +function getFilter(filterState: FilterState, query: string) { + // TODO: refactor to use real data when available + const filters: Datasets_Bool_Exp[] = [] + + // Text search by dataset title + if (query) { + filters.push({ + title: { + _ilike: `%${query}%`, + }, + }) + } + + // Author filters + // Author name filter + if (filterState.author.name) { + filters.push({ + authors: { + name: { + _ilike: `%${filterState.author.name}%`, + }, + }, + }) + } + + // Author Orcid filter + if (filterState.author.orcid) { + filters.push({ + authors: { + orcid: { + _ilike: `%${filterState.author.orcid}%`, + }, + }, + }) + } + + // Annotation filters + const { objectNames, objectShapeTypes } = filterState.annotation + + // Object names filter + if (objectNames.length > 0) { + filters.push({ + runs: { + tomogram_voxel_spacings: { + annotations: { + object_name: { + _in: objectNames, + }, + }, + }, + }, + }) + } + + // Object shape type filter + if (objectShapeTypes.length > 0) { + filters.push({ + runs: { + tomogram_voxel_spacings: { + annotations: { + files: { + shape_type: { + _in: objectShapeTypes, + }, + }, + }, + }, + }, + }) + } + + return { _and: filters } as Datasets_Bool_Exp +} + +export async function getBrowseDepositions({ + client, + orderBy, + page = 1, + params = new URLSearchParams(), + query = '', +}: { + client: ApolloClient + orderBy?: Order_By | null + page?: number + params?: URLSearchParams + query?: string +}) { + const start = performance.now() + + const results = await client.query({ + query: GET_DEPOSITIONS_DATA_QUERY, + variables: { + filter: getFilter(getFilterState(params), query), + limit: MAX_PER_PAGE, + offset: (page - 1) * MAX_PER_PAGE, + order_by_deposition: orderBy, + }, + }) + + const end = performance.now() + // eslint-disable-next-line no-console + console.log(`getBrowseDepositions query perf: ${end - start}ms`) + + return results +} diff --git a/frontend/packages/data-portal/app/hooks/useDepositions.ts b/frontend/packages/data-portal/app/hooks/useDepositions.ts new file mode 100644 index 000000000..f7e3ded79 --- /dev/null +++ b/frontend/packages/data-portal/app/hooks/useDepositions.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react' +import { useTypedLoaderData } from 'remix-typedjson' + +import { GetDepositionsDataQuery } from 'app/__generated__/graphql' + +export type Deposition = GetDepositionsDataQuery['depositions'][number] + +export function useDepositions() { + const data = useTypedLoaderData() + + return useMemo( + () => ({ + depositions: data.depositions, + depositionCount: data.depositions_aggregate.aggregate?.count ?? 0, + + filteredDepositionCount: + data.filtered_depositions_aggregate.aggregate?.count ?? 0, + + objectNames: data.object_names.map((value) => value.object_name), + + objectShapeTypes: data.object_shape_types.map( + (value) => value.shape_type, + ), + }), + [ + data.depositions, + data.depositions_aggregate.aggregate?.count, + data.filtered_depositions_aggregate.aggregate?.count, + data.object_names, + data.object_shape_types, + ], + ) +} diff --git a/frontend/packages/data-portal/app/routes/browse-data.depositions.tsx b/frontend/packages/data-portal/app/routes/browse-data.depositions.tsx new file mode 100644 index 000000000..53950351e --- /dev/null +++ b/frontend/packages/data-portal/app/routes/browse-data.depositions.tsx @@ -0,0 +1,73 @@ +import { Button, CellHeaderDirection } from '@czi-sds/components' +import { json, LoaderFunctionArgs, redirect } from '@remix-run/node' + +import { Order_By } from 'app/__generated__/graphql' +import { apolloClient } from 'app/apollo.server' +import { DepositionTable } from 'app/components/BrowseData/DepositionTable' +import { NoResults } from 'app/components/NoResults' +import { TablePageLayout } from 'app/components/TablePageLayout' +import { getBrowseDepositions } from 'app/graphql/getBrowseDepositions.server' +import { useDepositions } from 'app/hooks/useDepositions' +import { useFilter } from 'app/hooks/useFilter' +import { useI18n } from 'app/hooks/useI18n' +import { getFeatureFlag } from 'app/utils/featureFlags' + +export async function loader({ 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 page = +(url.searchParams.get('page') ?? '1') + const sort = (url.searchParams.get('sort') ?? undefined) as + | CellHeaderDirection + | undefined + const query = url.searchParams.get('search') ?? '' + + let orderBy: Order_By | null = null + + if (sort) { + orderBy = sort === 'asc' ? Order_By.Asc : Order_By.Desc + } + + const { data } = await getBrowseDepositions({ + orderBy, + page, + query, + client: apolloClient, + params: url.searchParams, + }) + + return json(data) +} + +export default function BrowseDepositionsPage() { + // TODO: hook up to backend when available + const { depositionCount, filteredDepositionCount } = useDepositions() + const { reset } = useFilter() + const { t } = useI18n() + + return ( + } + totalCount={depositionCount} + noResults={ + {t('clearFilters')}} + /> + } + /> + ) +} diff --git a/frontend/packages/data-portal/app/utils/featureFlags.ts b/frontend/packages/data-portal/app/utils/featureFlags.ts index 11a4f094e..4da714c80 100644 --- a/frontend/packages/data-portal/app/utils/featureFlags.ts +++ b/frontend/packages/data-portal/app/utils/featureFlags.ts @@ -4,10 +4,10 @@ import { useEnvironment } from 'app/context/Environment.context' export type FeatureFlagEnvironment = typeof process.env.ENV -export type FeatureFlagKey = 'stub' +export type FeatureFlagKey = 'depositions' export const FEATURE_FLAGS: Record = { - stub: ['local', 'dev'], + depositions: ['local', 'dev'], } const ENABLE_FEATURE_PARAM = 'enable-feature' diff --git a/frontend/packages/data-portal/public/locales/en/translation.json b/frontend/packages/data-portal/public/locales/en/translation.json index ad4313dbc..2e153254c 100644 --- a/frontend/packages/data-portal/public/locales/en/translation.json +++ b/frontend/packages/data-portal/public/locales/en/translation.json @@ -8,6 +8,8 @@ "aboutTheCompetitionContent3": "Our machine learning competition focuses on enhancing particle picking for a range of particle sizes through the development of machine learning models to generate semantic segmentation masks within 3D CryoET tomograms. The primary aim is to refine the template matching process by identifying areas of interest where template matching should be concentrated. This method narrows down the search space, thereby boosting the accuracy and efficiency of the CryoET computational workflow. Participants are tasked with crafting models that produce masks highlighting the precise locations for template matching, facilitating the identification of specific particles across various tomograms.", "aboutTheOrganizers": "About the Organizers", "accelerationVoltage": "Acceleration Voltage", + "acrossDatasets_one": "Across {{count}} Dataset", + "acrossDatasets_other": "Across {{count}} Datasets", "additionalMicroscopeOpticalSetup": "Additional microscope optical setup", "affiliationName": "Affiliation Name", "affiliationNames": "Affiliation Names", @@ -96,6 +98,10 @@ "datasets": "Datasets", "datasetsTab": "Datasets {{count}}", "depositionDate": "Deposition Date", + "depositionId": "Deposition ID", + "depositionName": "Deposition Name", + "depositions": "Depositions", + "depositionsTab": "Depositions {{count}}", "description": "Description", "developAMLModel": "Develop a ML model for annotating subcellular structures and proteins in CryoET data", "directDownload": "Direct Download", @@ -161,6 +167,7 @@ "imageCorrector": "Image Corrector", "includedContents": "Included Contents", "info": "Info", + "instanceSegmentation": "Instance Segmentation", "keyPhoto": "key photo", "landingHeaderImageAttribution": "Top Image: The inner workings of an algal cell as depicted with cryo-electron tomography, which aggregates multiple snapshots of a single piece of material. Visible are the Golgi apparatus (green and magenta), and vesicles (multi-colored circles). | Photo credit: Y. S. Bykkov et al./eLIFE (CC BY 4.0)", "landingHeaderTitle": "Open access to annotated cryoET tomograms", @@ -208,6 +215,7 @@ "objectShapeType": "Object Shape Type", "objectState": "Object State", "openDataset": "Open Dataset", + "openDeposition": "Open Deposition", "openRun": "Open Run", "optional": "Optional", "orExploreViaApi": "or explore via API", @@ -215,12 +223,14 @@ "organism": "Organism", "organismName": "Organism Name", "organizers": "Organizers", + "orientedPoint": "Oriented Point", "otherSetup": "Other Setup", "participateInOurCompetition": "Participate in our ML competition", "participateInOurCompetitionCTA": "Develop a ML model for annotating subcellular structures and proteins in CryoET data.", "phasePlate": "Phase Plate", "pixelSpacing": "Pixel Spacing", "plusMore": "+{{count}} More", + "point": "Point", "poor": "Poor", "precision": "Precision", "precisionTooltip": "Precision: Percentage of true positives among the total number of positive predictions. A value of 100% means everything found is actually the object of interest. Learn More", @@ -259,6 +269,7 @@ "samplePreparation": "Sample Preparation", "sampleType": "Sample Type", "search": "Search", + "segmentationMask": "Segmentation Mask", "selectASpecificTomogram": "Select a specific tomogram from this run to download.", "selectDownload": "Select Download", "selectDownloadMethod": "Select Download Method",