Skip to content

feat: single deposition table updates #1852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMemo } from 'react'

import { Link } from 'app/components/Link'
import { useI18n } from 'app/hooks/useI18n'
import type { I18nKeys } from 'app/types/i18n'
import { DASHED_UNDERLINED_CLASSES } from 'app/utils/classNames'

interface DepositItem {
label: I18nKeys
url: string
value?: string
}

export function DepositedInTableCell({
datasetId,
datasetTitle,
runId,
runName,
}: {
datasetId?: number
datasetTitle?: string
runId?: number
runName?: string
}) {
const depositItems: DepositItem[] = useMemo(
() => [
...(datasetId && datasetTitle
? [
{
label: 'dataset' as I18nKeys,
value: datasetTitle,
url: `/datasets/${datasetId}`,
},
]
: []),

...(runId && runName
? [
{
label: 'run' as I18nKeys,
value: runName,
url: `/runs/${runId}`,
},
]
: []),
],
[datasetId, datasetTitle, runId, runName],
)

const { t } = useI18n()

if (depositItems.length === 0) {
return '--'
}

return (
<div className="flex flex-col gap-sds-s">
{depositItems.map(({ label, value, url }) => (
<Link
className="text-sds-body-xxs-400-wide tracking-sds-body-xxs-400-wide leading-sds-body-xxs"
key={label}
to={url}
>
<span className="font-semibold">{t(label)}: </span>
<span className={DASHED_UNDERLINED_CLASSES}>{value ?? '--'}</span>
</Link>
))}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'

import {
Annotation_File_Shape_Type_Enum,
Annotation_Method_Type_Enum,
type GetDepositionAnnotationsQuery,
} from 'app/__generated_v2__/graphql'
import { useAnnotationNameColumn } from 'app/components/AnnotationTable/useAnnotationNameColumn'
import { useShapeTypeColumn } from 'app/components/AnnotationTable/useShapeTypeColumn'
import { PageTable } from 'app/components/Table'
import { DepositionAnnotationTableWidths } from 'app/constants/table'
import { useDepositionById } from 'app/hooks/useDepositionById'
import { useIsLoading } from 'app/hooks/useIsLoading'

import { useMethodTypeColumn } from '../AnnotationTable/useMethodTypeColumn'
import { useDepositedInColumn } from './useDepositedInColumn'

export type DepositionAnnotationTableData =
GetDepositionAnnotationsQuery['annotationShapes'][number]

const LOADING_ANNOTATIONS = Array.from(
{ length: 10 },
(_, index) =>
({
id: index,
shapeType: Annotation_File_Shape_Type_Enum.Point,

annotation: {
id: index,
objectName: 'object name',
groundTruthStatus: false,
methodType: Annotation_Method_Type_Enum.Automated,
},

annotationFiles: {
edges: [
{
node: {
s3Path: 's3://example.com/loading',
},
},
],
},
}) as DepositionAnnotationTableData,
)

export function DepositionAnnotationTable() {
const { annotations } = useDepositionById()

const { isLoadingDebounced } = useIsLoading()

const annotationNameColumn = useAnnotationNameColumn({
width: DepositionAnnotationTableWidths.name,
})

const shapeTypeColumn = useShapeTypeColumn(
DepositionAnnotationTableWidths.objectShapeType,
)

const methodTypeColumn = useMethodTypeColumn({
width: DepositionAnnotationTableWidths.methodType,
})

const depositedInColumn = useDepositedInColumn<DepositionAnnotationTableData>(
{
width: DepositionAnnotationTableWidths.depositedIn,

getDepositedInData: ({ annotation }) => ({
datasetId: annotation?.run?.dataset?.id,
datasetTitle: annotation?.run?.dataset?.title,
runId: annotation?.run?.id,
runName: annotation?.run?.name,
}),
},
)

const columns = useMemo(
() =>
[
annotationNameColumn,
shapeTypeColumn,
methodTypeColumn,
depositedInColumn,
] as ColumnDef<DepositionAnnotationTableData>[],
[
annotationNameColumn,
depositedInColumn,
methodTypeColumn,
shapeTypeColumn,
],
)

return (
<PageTable
data={
isLoadingDebounced
? LOADING_ANNOTATIONS
: annotations?.annotationShapes ?? []
}
columns={columns}
hoverType="none"
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useMemo } from 'react'

import { useDepositionById } from 'app/hooks/useDepositionById'
import { useI18n } from 'app/hooks/useI18n'
import { cns } from 'app/utils/cns'
import { getDataContents } from 'app/utils/deposition'

import { DepositionTabs } from './DepositionTabs'

export function DepositionFilters() {
const { allRuns } = useDepositionById()
const dataContents = getDataContents(allRuns)
const dataContentItems = useMemo(
() =>
[
{
label: 'tiltSeries',
isAvailable: dataContents.tiltSeriesAvailable,
},
{
label: 'frames',
isAvailable: dataContents.framesAvailable,
},
{
label: 'ctf',
isAvailable: dataContents.ctfAvailable,
},
{
label: 'alignment',
isAvailable: dataContents.alignmentAvailable,
},
] as const,
[dataContents],
)

const { t } = useI18n()

return (
<div className="flex flex-col pt-sds-xl">
<h4
className={cns(
'text-sds-header-m-600-wide tracking-sds-header-m-600-wide',
'font-semibold leading-sds-header-m px-sds-xl mb-sds-s',
)}
>
{t('dataContents')}
</h4>

<DepositionTabs />

<div className="flex flex-col justify-between gap-sds-s mt-sds-l">
{dataContentItems.map(({ label, isAvailable }) => (
<div
key={label}
className={cns(
'flex items-center justify-between px-sds-xl',
'text-sds-body-xs-400-wide tracking-sds-body-xs-400-wide leading-sds-body-xs',
'text-light-sds-color-semantic-base-text-secondary',
)}
>
<span>{t(label)}</span>

<span>{t(isAvailable ? 'available' : 'na')}</span>
</div>
))}
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need some padding/margin bottom here when there is no data/loading

)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DepositionTab, useDepositionTab } from 'app/hooks/useDepositionTab'

import { DepositionAnnotationTable } from './DepositionAnnotationTable'
import { DepositionTomogramTable } from './DepositionTomogramTable'

export function DepositionTableRenderer() {
const [tab] = useDepositionTab()

switch (tab) {
case DepositionTab.Tomograms:
return <DepositionTomogramTable />

default:
return <DepositionAnnotationTable />
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Icon } from '@czi-sds/components'
import { useMemo } from 'react'

import { type TabData, Tabs } from 'app/components/Tabs'
import { useDepositionById } from 'app/hooks/useDepositionById'
import { DepositionTab, useDepositionTab } from 'app/hooks/useDepositionTab'
import { useI18n } from 'app/hooks/useI18n'
import { cns } from 'app/utils/cns'

export function DepositionTabs() {
const [tab, setTab] = useDepositionTab()
const tabs = useTabs(tab)

return <Tabs tabs={tabs} value={tab} onChange={setTab} vertical />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future Nice-to-have suggestion: It would be good if changing tabs didn't bring you to the top of the page and also it might be nice if the headers loaded as well as the data - or maybe they just change to the next tabs head (like if you click on Tomograms then the headers change immediately even while we wait for the data to load

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a way to do that in remix: https://remix.run/docs/en/main/components/scroll-restoration#preventing-scroll-reset

also it might be nice if the headers loaded as well as the data - or maybe they just change to the next tabs head (like if you click on Tomograms then the headers change immediately even while we wait for the data to load

I agree, I think this is limitation to how we implemented the frontend in the beginning because data fetching has to complete before it can show the next page. I think this was before Suspense and Await became more widespread, so we never adopted this pattern in the codebase 😢

I think overall we should look into deferring the data fetches and using Suspense to make the application feel snappier. There's a Remix example of using it here:
https://remix.run/docs/en/main/discussion/pending-ui#deferred-data-loading

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened an issue to track this: #1861

}

function useTabs(activeTab: DepositionTab) {
const { t } = useI18n()
const { annotationsCount, tomogramsCount } = useDepositionById()

return useMemo<TabData<DepositionTab>[]>(() => {
const tabData = [
{
tab: DepositionTab.Annotations,
label: 'annotations',
icon: 'Cube',
count: annotationsCount,
},
{
tab: DepositionTab.Tomograms,
label: 'tomograms',
icon: 'FlagOutline',
count: tomogramsCount,
},
] as const

return tabData.map(({ tab, label, icon, count }) => ({
value: tab,
label: (
<span
className={cns(
'font-semibold text-sds-body-s-600-wide',
'tracking-sds-body-s-600-wide leading-sds-body-s',
'w-full flex items-center justify-between',
)}
>
<span className="flex items-center gap-sds-xs">
<Icon
className={
activeTab === tab
? '!text-black'
: '!text-light-sds-color-primitive-gray-600'
}
sdsIcon={icon}
sdsSize="xs"
/>

<span>{t(label)}</span>
</span>

<span>{count.toLocaleString()}</span>
</span>
),
}))
}, [activeTab, annotationsCount, t, tomogramsCount])
}
Loading
Loading