Skip to content

Commit

Permalink
feat(web): cleanup widgets for auto and manual dataset selection flow
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-aksamentov committed Sep 27, 2023
1 parent 194a2e2 commit eb566e1
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 101 deletions.
63 changes: 43 additions & 20 deletions packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import { colorHash } from 'src/helpers/colorHash'
import { formatDateIsoUtcSimple } from 'src/helpers/formatDate'
import { firstLetter } from 'src/helpers/string'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import {
autodetectResultsByDatasetAtom,
DATASET_ID_UNDETECTED,
numberAutodetectResultsAtom,
} from 'src/state/autodetect.state'
import { autodetectResultsByDatasetAtom, numberAutodetectResultsAtom } from 'src/state/autodetect.state'
import type { Dataset } from 'src/types'
import styled from 'styled-components'

Expand Down Expand Up @@ -73,9 +69,10 @@ const DatasetInfoBadge = styled(Badge)`

export interface DatasetInfoProps {
dataset: Dataset
showSuggestions?: boolean
}

export function DatasetInfo({ dataset }: DatasetInfoProps) {
export function DatasetInfo({ dataset, showSuggestions }: DatasetInfoProps) {
const { t } = useTranslationSafe()
const { attributes, official, deprecated, enabled, experimental, path, version } = dataset
const { name, reference } = attributes
Expand All @@ -88,18 +85,18 @@ export function DatasetInfo({ dataset }: DatasetInfoProps) {
return updatedAt
}, [version?.tag, version?.updatedAt])

if (!enabled) {
return null
if (path === 'autodetect') {
return <DatasetAutodetectInfo dataset={dataset} />
}

if (path === DATASET_ID_UNDETECTED) {
return <DatasetUndetectedInfo />
if (!enabled) {
return null
}

return (
<Container>
<FlexLeft>
<DatasetInfoAutodetectProgressCircle dataset={dataset} />
<DatasetInfoAutodetectProgressCircle dataset={dataset} showSuggestions={showSuggestions} />
</FlexLeft>

<FlexRight>
Expand Down Expand Up @@ -160,26 +157,52 @@ export function DatasetInfo({ dataset }: DatasetInfoProps) {
)
}

export function DatasetAutodetectInfo({ dataset }: { dataset: Dataset }) {
const { t } = useTranslationSafe()

return (
<ContainerFixed>
<FlexLeft>
<DatasetInfoAutodetectProgressCircle dataset={dataset} />
</FlexLeft>

<FlexRight>
<DatasetName>
<span>{t('Autodetect')}</span>
</DatasetName>
<DatasetInfoLine>{t('Detect pathogen automatically from sequences')}</DatasetInfoLine>
<DatasetInfoLine>{'\u00A0'}</DatasetInfoLine>
<DatasetInfoLine>{'\u00A0'}</DatasetInfoLine>
</FlexRight>
</ContainerFixed>
)
}

export function DatasetUndetectedInfo() {
const { t } = useTranslationSafe()

return (
<Container>
<ContainerFixed>
<DatasetName>
<span>{t('Autodetect')}</span>
<span>{t('Not detected')}</span>
</DatasetName>
<DatasetInfoLine>{t('Detect pathogen automatically from sequences')}</DatasetInfoLine>
<DatasetInfoLine />
<DatasetInfoLine />
</Container>
<DatasetInfoLine>{t('Unable to deduce dataset')}</DatasetInfoLine>
<DatasetInfoLine>{'\u00A0'}</DatasetInfoLine>
<DatasetInfoLine>{'\u00A0'}</DatasetInfoLine>
</ContainerFixed>
)
}

const ContainerFixed = styled(Container)`
height: 127px;
`

export interface DatasetInfoCircleProps {
dataset: Dataset
showSuggestions?: boolean
}

function DatasetInfoAutodetectProgressCircle({ dataset }: DatasetInfoCircleProps) {
function DatasetInfoAutodetectProgressCircle({ dataset, showSuggestions }: DatasetInfoCircleProps) {
const { attributes, path } = dataset
const { name } = attributes

Expand All @@ -188,7 +211,7 @@ function DatasetInfoAutodetectProgressCircle({ dataset }: DatasetInfoCircleProps
const numberAutodetectResults = useRecoilValue(numberAutodetectResultsAtom)

const { circleText, countText, percentage } = useMemo(() => {
if (isNil(records)) {
if (!showSuggestions || isNil(records)) {
return {
circleText: (firstLetter(name.valueFriendly ?? name.value) ?? ' ').toUpperCase(),
percentage: 0,
Expand All @@ -203,7 +226,7 @@ function DatasetInfoAutodetectProgressCircle({ dataset }: DatasetInfoCircleProps
return { circleText, percentage, countText }
}
return { circleText: `0%`, percentage: 0, countText: `0 / ${numberAutodetectResults}` }
}, [records, name.value, name.valueFriendly, numberAutodetectResults])
}, [showSuggestions, records, numberAutodetectResults, name.valueFriendly, name.value])

return (
<>
Expand Down
100 changes: 86 additions & 14 deletions packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { get, isNil, sortBy } from 'lodash'
import React, { useMemo, useState } from 'react'
import { useRecoilValue } from 'recoil'
import React, { useEffect, useMemo, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { Container as ContainerBase } from 'reactstrap'
import { DatasetSelectorListImpl } from 'src/components/Main/DatasetSelectorListImpl'
import { autodetectResultsAtom, groupByDatasets } from 'src/state/autodetect.state'
import { PROJECT_NAME } from 'src/constants'
import {
autodetectResultsAtom,
AutodetectRunState,
autodetectRunStateAtom,
groupByDatasets,
} from 'src/state/autodetect.state'
import styled from 'styled-components'
import type { Dataset } from 'src/types'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { datasetsAtom } from 'src/state/dataset.state'
import { SearchBox } from 'src/components/Common/SearchBox'

// HACK: dataset entry for 'autodetect' option. This is not a real dataset.
const DATASET_AUTODETECT: Dataset = {
export const DATASET_AUTODETECT: Dataset = {
path: 'autodetect',
enabled: true,
official: true,
Expand All @@ -33,11 +39,28 @@ export interface DatasetSelectorProps {
export function DatasetSelector({ datasetHighlighted, onDatasetHighlighted }: DatasetSelectorProps) {
const { datasets } = useRecoilValue(datasetsAtom)

const datasetsActive = useMemo(() => {
return [DATASET_AUTODETECT, ...datasets]
}, [datasets])

return (
<DatasetSelectorImpl
datasetsActive={datasetsActive}
datasetHighlighted={datasetHighlighted}
onDatasetHighlighted={onDatasetHighlighted}
/>
)
}

export function DatasetAutosuggestionResultsList({ datasetHighlighted, onDatasetHighlighted }: DatasetSelectorProps) {
const { datasets } = useRecoilValue(datasetsAtom)

const autodetectResults = useRecoilValue(autodetectResultsAtom)
const [autodetectRunState, setAutodetectRunState] = useRecoilState(autodetectRunStateAtom)

const autodetectResult = useMemo(() => {
if (isNil(autodetectResults) || autodetectResults.length === 0) {
return { itemsStartWith: [], itemsInclude: datasets, itemsNotInclude: [] }
return undefined
}

const recordsByDataset = groupByDatasets(autodetectResults)
Expand All @@ -53,38 +76,86 @@ export function DatasetSelector({ datasetHighlighted, onDatasetHighlighted }: Da
return { itemsStartWith: [], itemsInclude, itemsNotInclude }
}, [autodetectResults, datasets])

const { itemsStartWith, itemsInclude, itemsNotInclude } = autodetectResult

const datasetsActive = useMemo(() => {
return [DATASET_AUTODETECT, ...itemsStartWith, ...itemsInclude]
}, [itemsInclude, itemsStartWith])
if (!autodetectResult) {
return []
}
const { itemsStartWith, itemsInclude } = autodetectResult
return [...itemsStartWith, ...itemsInclude]
}, [autodetectResult])

useEffect(() => {
const topSuggestion = autodetectResult?.itemsInclude[0]
if (autodetectRunState === AutodetectRunState.Done) {
onDatasetHighlighted?.(topSuggestion)
setAutodetectRunState(AutodetectRunState.Idle)
}
}, [autodetectRunState, autodetectResult?.itemsInclude, onDatasetHighlighted, setAutodetectRunState])

const datasetsInactive = useMemo(() => {
return [...itemsNotInclude]
}, [itemsNotInclude])
if (!autodetectResults) {
return <DatasetAutosuggestionInstructions />
}

return (
<DatasetSelectorImpl
datasetsActive={datasetsActive}
datasetsInactive={datasetsInactive}
datasetHighlighted={datasetHighlighted}
onDatasetHighlighted={onDatasetHighlighted}
showSuggestions
/>
)
}

function DatasetAutosuggestionInstructions() {
const { t } = useTranslationSafe()
return (
<div className="d-flex flex-column">
<Heading>{t('Dataset autosuggestion')}</Heading>
<Wrapper>
<div className="flex-1 text-center">
<p className="mx-auto">
{t('{{projectName}} will try to guess dataset from data and will present its suggestions here.', {
projectName: PROJECT_NAME,
})}
</p>
<p className="mx-auto">{t('Please provide sequences to start.')}</p>
</div>
</Wrapper>
</div>
)
}

const Heading = styled.h4`
padding-top: 12px;
margin-bottom: 0;
margin-left: 7px;
width: 100%;
`

const Wrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
padding: 10px;
border: 1px #ccc9 solid;
border-radius: 5px;
margin: 7px;
`

export interface DatasetSelectorImplProps {
datasetsActive: Dataset[]
datasetsInactive: Dataset[]
datasetsInactive?: Dataset[]
datasetHighlighted?: Dataset
onDatasetHighlighted?(dataset?: Dataset): void
showSuggestions?: boolean
}

export function DatasetSelectorImpl({
datasetsActive,
datasetsInactive,
datasetHighlighted,
onDatasetHighlighted,
showSuggestions,
}: DatasetSelectorImplProps) {
const { t } = useTranslationSafe()
const [searchTerm, setSearchTerm] = useState('')
Expand All @@ -103,6 +174,7 @@ export function DatasetSelectorImpl({
datasetHighlighted={datasetHighlighted}
onDatasetHighlighted={onDatasetHighlighted}
searchTerm={searchTerm}
showSuggestions={showSuggestions}
/>
</Main>
</Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ import { DatasetInfo } from 'src/components/Main/DatasetInfo'

export interface DatasetSelectorListImplProps {
datasetsActive: Dataset[]
datasetsInactive: Dataset[]
datasetsInactive?: Dataset[]
datasetHighlighted?: Dataset
onDatasetHighlighted?(dataset?: Dataset): void
searchTerm: string
showSuggestions?: boolean
}

export function DatasetSelectorListImpl({
datasetsActive,
datasetsInactive,
datasetsInactive = [],
datasetHighlighted,
onDatasetHighlighted,
searchTerm,
showSuggestions,
}: DatasetSelectorListImplProps) {
const onItemClick = useCallback((dataset: Dataset) => () => onDatasetHighlighted?.(dataset), [onDatasetHighlighted])

Expand Down Expand Up @@ -52,6 +54,7 @@ export function DatasetSelectorListImpl({
dataset={dataset}
onClick={onItemClick(dataset)}
isCurrent={areDatasetsEqual(dataset, datasetHighlighted)}
showSuggestions={showSuggestions}
/>
))}

Expand All @@ -62,12 +65,13 @@ export function DatasetSelectorListImpl({
dataset={dataset}
onClick={onItemClick(dataset)}
isCurrent={areDatasetsEqual(dataset, datasetHighlighted)}
showSuggestions={showSuggestions}
isDimmed
/>
))}
</Ul>
),
[datasetHighlighted, itemsInclude, itemsNotInclude, itemsStartWith, listItemsRef, onItemClick],
[datasetHighlighted, itemsInclude, itemsNotInclude, itemsStartWith, listItemsRef, onItemClick, showSuggestions],
)
}

Expand Down Expand Up @@ -97,14 +101,6 @@ function useScrollListToDataset(datasetHighlighted?: Dataset) {
scrollToId(datasetHighlighted.path)
}

// useEffect(() => {
// const topSuggestion = autodetectResult.itemsInclude[0]
// if (autodetectRunState === AutodetectRunState.Done) {
// onDatasetHighlighted?.(topSuggestion)
// setAutodetectRunState(AutodetectRunState.Idle)
// }
// }, [autodetectRunState, autodetectResult.itemsInclude, onDatasetHighlighted, setAutodetectRunState])

return itemsRef
}

Expand Down Expand Up @@ -138,14 +134,15 @@ interface DatasetSelectorListItemProps {
dataset: Dataset
isCurrent?: boolean
isDimmed?: boolean
showSuggestions?: boolean
onClick?: () => void
}

const DatasetSelectorListItem = forwardRef<HTMLLIElement, DatasetSelectorListItemProps>(
function DatasetSelectorListItemWithRef({ dataset, isCurrent, isDimmed, onClick }, ref) {
function DatasetSelectorListItemWithRef({ dataset, isCurrent, isDimmed, onClick, showSuggestions }, ref) {
return (
<Li ref={ref} $isDimmed={isDimmed} aria-current={isCurrent} $active={isCurrent} onClick={onClick}>
<DatasetInfo dataset={dataset} />
<DatasetInfo dataset={dataset} showSuggestions={showSuggestions} />
</Li>
)
},
Expand Down
Loading

0 comments on commit eb566e1

Please sign in to comment.