Skip to content
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

UI 5 #1312

Merged
merged 29 commits into from
Nov 13, 2023
Merged

UI 5 #1312

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d67bb2f
feat(web): scaffold multi-step wizard on main page
ivan-aksamentov Sep 21, 2023
2a83eaa
refactor: split dataset list into subcomponents
ivan-aksamentov Sep 22, 2023
194a2e2
refactor: turn dataset selector input state inside out
ivan-aksamentov Sep 26, 2023
eb566e1
feat(web): cleanup widgets for auto and manual dataset selection flow
ivan-aksamentov Sep 27, 2023
64e5578
feat(web): re-add readme and changelog to the dataset screen
ivan-aksamentov Sep 28, 2023
9b385f2
feat(web): re-add dataset customization
ivan-aksamentov Sep 28, 2023
32fd71f
fix(web): prevent preview alert from adding scroll to the page layout
ivan-aksamentov Sep 28, 2023
200432d
feat(web): scaffold UI-4
ivan-aksamentov Sep 29, 2023
de83b6b
fix: ensure suggestions run and being displayed
ivan-aksamentov Sep 29, 2023
ab22f86
fix: make sure dataset selector is scrollbar
ivan-aksamentov Sep 29, 2023
a2ea9af
feat(web): add example seq dropdown
ivan-aksamentov Oct 2, 2023
9396ee6
Merge branch 'master' into ui-4
ivan-aksamentov Oct 20, 2023
8ace93d
Merge branch 'master' into ui-4
ivan-aksamentov Oct 20, 2023
94761f6
Merge branch 'master' into ui-4
ivan-aksamentov Oct 20, 2023
ba109df
Merge branch 'master' into ui-4
ivan-aksamentov Oct 20, 2023
8be6b9d
Merge branch 'master' into ui-4
ivan-aksamentov Oct 20, 2023
cae1776
feat(web): prototype ui-5
ivan-aksamentov Nov 6, 2023
0fb8442
feat(web): move example sequence picker to near file picker tabs
ivan-aksamentov Nov 6, 2023
4948a77
feat(web): add auto-suggest progress indicator to ui-5
ivan-aksamentov Nov 7, 2023
398f9ca
feat(web): flip panels from left to right
ivan-aksamentov Nov 7, 2023
e9ca64a
feat(web): add list of input files to second page
ivan-aksamentov Nov 7, 2023
f620288
feat(web): add run button at the top
ivan-aksamentov Nov 7, 2023
466d2fb
feat(web): move dataset selection step into a separate web page
ivan-aksamentov Nov 7, 2023
9a279be
feat(web): remove buttons at the bottom
ivan-aksamentov Nov 7, 2023
58afabc
feat(web): add 'Add more' button to input file list
ivan-aksamentov Nov 7, 2023
6e4a918
feat(web): add 'Load example' button for current dataset
ivan-aksamentov Nov 8, 2023
138d304
refactor: lint
ivan-aksamentov Nov 8, 2023
758f65e
refactor: rename file to reduce merge conflicts
ivan-aksamentov Nov 13, 2023
47f0871
Merge branch 'master' into ui-5
ivan-aksamentov Nov 13, 2023
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
1 change: 1 addition & 0 deletions packages_rs/nextclade-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"react-resize-detector": "7.0.0",
"react-select": "5.3.0",
"react-toggle": "4.1.2",
"react-use-wizard": "2.2.3",
"react-virtualized-auto-sizer": "1.0.6",
"react-virtualized-select": "3.1.3",
"react-window": "1.8.7",
Expand Down
7 changes: 7 additions & 0 deletions packages_rs/nextclade-web/src/components/About/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'

import AboutContent from './AboutContent.mdx'

export function About() {
return <AboutContent />
}
43 changes: 43 additions & 0 deletions packages_rs/nextclade-web/src/components/About/AboutContent.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CladeSchema } from 'src/components/Main/CladeSchema.tsx'

## What is Nextclade?

Nextclade is a tool that performs genetic sequence alignment, clade assignment, mutation calling, phylogenetic placement, and quality checks for SARS-CoV-2, Influenza (Flu), Mpox (Monkeypox), Respiratory Syncytial Virus (RSV) and other pathogens.

Nextclade identifies differences between your sequences and a reference sequence, uses these differences to assign your sequences to clades, reports potential sequence quality issues in your data, and shows how the sequences are related to each other by placing them into an existing phylogenetic tree (we call it "phylogenetic placement"). You can use the tool to analyze sequences before you upload them to a database, or if you want to assign Nextstrain clades to a set of sequences.

To analyze your data, drag a fasta file onto the upload box or paste sequences into the text box. These sequences will then be analyzed in your browser - data never leave your computer. Since your computer is doing the work rather than a server, it is advisable to analyze at most a few hundred sequences at a time.

The Nextclade app and algorithms are opensource. The code is available on [GitHub](https://github.com/nextstrain/nextclade). The user manual is available at [docs.nextstrain.org/projects/nextclade](https://docs.nextstrain.org/projects/nextclade).


### What are the SARS-CoV-2 clades?

Nextclade was originally developed during COVID-19 pandemic, primarily focused on SARS-CoV-2. This section describes clades with application to SARS-CoV-2, but Nextclade can analyse other pathogens too.

<CladeSchema />

Since its emergence in late 2019, SARS-CoV-2 has diversified into several different co-circulating variants. To facilitate discussion of these variants, we have grouped them into __clades__ which are defined by specific signature mutations.

We currently define more than 30 clades (see [this blog post](https://nextstrain.org/blog/2021-01-06-updated-SARS-CoV-2-clade-naming) for details):

- 19A and 19B emerged in Wuhan and have dominated the early outbreak
- 20A emerged from 19A out of dominated the European outbreak in March and has since spread globally
- 20B and 20C are large genetically distinct subclades 20A emerged in early 2020
- 20D to 20J have emerged over the summer of 2020 and include three "Variants of Concern" (VoC).
- 21A to 21F include the VoC __delta__ and several Variants of Interest (VoI).
- 21K onwards are different clades within the diverse VoC __omicron__.

Within Nextstrain, we define each clade by its combination of signature mutations. You can find the exact clade definition in [github.com/nextstrain/ncov/defaults/clades.tsv](https://github.com/nextstrain/ncov/blob/master/defaults/clades.tsv). When available, we will include [WHO labels for VoCs and VoIs](https://www.who.int/en/activities/tracking-SARS-CoV-2-variants/).

Learn more about how Nextclade assigns clades in the [documentation](https://docs.nextstrain.org/projects/nextclade/en/stable/user/algorithm/).

### Other pathogens

Besides SARS-CoV-2, we provide Nextclade datasets to analyze the following other pathogens:

* Seasonal Influenza viruses (HA and NA for A/H3N2, A/H1N1pdm, B/Vic, and B/Yam)
* Mpox virus (the overall clade structure, as well as fine-grained lineages within the recent sustained human-to-human transmission)
* Respiratory Syncytial Virus (RSV) (subtypes A and B)

You can also put together your own dataset to analyse other pathogens.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function Dropdown({
value={value}
isMulti={false}
onChange={handleChange}
menuPortalTarget={document.body}
{...restProps}
/>
)
Expand Down
8 changes: 2 additions & 6 deletions packages_rs/nextclade-web/src/components/Common/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,21 @@ import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'
import { LinkExternal } from 'src/components/Link/LinkExternal'
import { useAxiosQuery } from 'src/helpers/useAxiosQuery'
import { LOADING } from 'src/components/Loading/Loading'
import { mdxComponents } from 'src/mdx-components'

const REMARK_PLUGINS = [remarkGfm]

const REHYPE_PLUGINS = [rehypeRaw, rehypeSanitize]

const MD_COMPONENTS = {
a: LinkExternal,
}

export interface MarkdownProps {
content: string
}

export function Markdown({ content }: MarkdownProps) {
return (
<ReactMarkdown rehypePlugins={REHYPE_PLUGINS} remarkPlugins={REMARK_PLUGINS} components={MD_COMPONENTS}>
<ReactMarkdown rehypePlugins={REHYPE_PLUGINS} remarkPlugins={REMARK_PLUGINS} components={mdxComponents}>
{content}
</ReactMarkdown>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export function SearchBox({ searchTitle, searchTerm, onSearchTermChange, ...rest
<IconSearch size={25} />
</IconSearchWrapper>
<Input
autoFocus
type="text"
title={searchTitle}
placeholder={searchTitle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TabPanelUrl } from './TabPanelUrl'
import { TabPanelPaste } from './TabPanelPaste'
import { UploadedFileInfo } from './UploadedFileInfo'
import { UploadedFileInfoCompact } from './UploadedFileInfoCompact'
import { ExampleSequencePicker } from '../Main/ExampleSequencePicker'

export const FilePickerContainer = styled.div`
flex: 1;
Expand All @@ -37,6 +38,10 @@ export const FilePickerTitle = styled.h4`
flex: 1;
padding-top: 0.75rem;
margin: auto 0;

white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`

export const TabsPanelStyled = styled(TabsPanel)`
Expand Down Expand Up @@ -173,8 +178,9 @@ export function FilePicker({
return (
<FilePickerContainer {...props}>
<FilePickerHeader>
<FilePickerTitle>{title}</FilePickerTitle>
<FilePickerTitle title={title}>{title}</FilePickerTitle>
<TabsPanelStyled tabs={tabs} activeTab={activeTab} onChange={setActiveTab} disabled={!!input || isInProgress} />
<ExampleSequencePicker />
</FilePickerHeader>
<FilePickerBody $compact={compact}>{FileUploadOrFileInfo}</FilePickerBody>
</FilePickerContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function FilePickerAdvanced() {
<Row noGutters>
<Col>
<FilePicker
className="my-3"
className="mb-2"
compact
icon={iconJson}
title={t('Reference tree')}
Expand Down
3 changes: 2 additions & 1 deletion packages_rs/nextclade-web/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ const MainInner = styled.main`
`

const MainOuter = styled.main`
flex: auto;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
height: 100%;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export function NavigationBar() {
const linksLeft = useMemo(() => {
return [
{ url: '/', content: t('Start'), title: t('Show start page') },
{ url: '/dataset', content: t('Dataset'), title: t('Show current dataset details') },
{
url: hasRan ? '/results' : undefined,
content: t('Results'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { isNil } from 'lodash'
import React, { useMemo } from 'react'
import { Button, ButtonProps } from 'reactstrap'
import styled from 'styled-components'
import { useRecoilValue } from 'recoil'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { datasetCurrentAtom } from 'src/state/dataset.state'

export interface DatasetNoneSectionProps {
toDatasetSelection(): void
}

export function DatasetNoneSection({ toDatasetSelection }: DatasetNoneSectionProps) {
return (
<Container>
<ButtonChangeDataset onClick={toDatasetSelection} />
</Container>
)
}

const Container = styled.div`
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;

padding: 12px;
border: 1px #ccc9 solid;
border-radius: 5px;

min-height: 110px;
`

export interface ChangeDatasetButtonProps extends ButtonProps {
onClick(): void
}

export function ButtonChangeDataset({ onClick, ...restProps }: ChangeDatasetButtonProps) {
const { t } = useTranslationSafe()
const dataset = useRecoilValue(datasetCurrentAtom)

const { color, text, tooltip } = useMemo(() => {
const hasDataset = !isNil(dataset)
const text = hasDataset ? t('Change dataset') : t('Select dataset')
return {
color: hasDataset ? 'secondary' : 'primary',
text,
tooltip: text,
}
}, [dataset, t])

return (
<Button className="m-auto" color={color} title={tooltip} onClick={onClick} {...restProps}>
{text}
</Button>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useCallback } from 'react'
import { Button } from 'reactstrap'
import { useRecoilValue } from 'recoil'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { AlgorithmInputDefault } from 'src/io/AlgorithmInput'
import { datasetCurrentAtom } from 'src/state/dataset.state'
import { hasInputErrorsAtom } from 'src/state/error.state'
import { useQuerySeqInputs } from 'src/state/inputs.state'

export function ButtonLoadExample({ ...rest }) {
const { t } = useTranslationSafe()

const datasetCurrent = useRecoilValue(datasetCurrentAtom)
const { addQryInputs } = useQuerySeqInputs()
const hasInputErrors = useRecoilValue(hasInputErrorsAtom)
// const shouldRunAutomatically = useRecoilValue(shouldRunAutomaticallyAtom)
// const shouldSuggestDatasets = useRecoilValue(shouldSuggestDatasetsAtom)
// const runAnalysis = useRunAnalysis()
// const runAutodetect = useRunSeqAutodetect()

const setExampleSequences = useCallback(() => {
if (datasetCurrent) {
addQryInputs([new AlgorithmInputDefault(datasetCurrent)])
// if (shouldSuggestDatasets) {
// runAutodetect()
// }
// if (shouldRunAutomatically) {
// runAnalysis()
// }
}
}, [addQryInputs, datasetCurrent])

return (
<Button {...rest} color="link" onClick={setExampleSequences} disabled={hasInputErrors || !datasetCurrent}>
{t('Load example')}
</Button>
)
}
44 changes: 44 additions & 0 deletions packages_rs/nextclade-web/src/components/Main/ButtonRun.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { isNil } from 'lodash'
import React, { useMemo } from 'react'
import { Button, ButtonProps } from 'reactstrap'
import { useRecoilValue } from 'recoil'
import styled from 'styled-components'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { isAutodetectRunningAtom } from 'src/state/autodetect.state'
import { datasetCurrentAtom } from 'src/state/dataset.state'
import { hasInputErrorsAtom } from 'src/state/error.state'
import { hasRequiredInputsAtom } from 'src/state/inputs.state'
import { canRunAtom } from 'src/state/results.state'

export interface ButtonRunProps extends ButtonProps {
onClick(): void
}

export function ButtonRun({ onClick, ...restProps }: ButtonRunProps) {
const canRun = useRecoilValue(canRunAtom)
const hasRequiredInputs = useRecoilValue(hasRequiredInputsAtom)
const hasInputErrors = useRecoilValue(hasInputErrorsAtom)
const isAutodetectRunning = useRecoilValue(isAutodetectRunningAtom)
const dataset = useRecoilValue(datasetCurrentAtom)

const { t } = useTranslationSafe()
const { isDisabled, color, tooltip } = useMemo(() => {
const isDisabled = !(canRun && hasRequiredInputs && !isAutodetectRunning) || hasInputErrors || isNil(dataset)
return {
isDisabled,
color: isDisabled ? 'secondary' : 'success',
tooltip: isDisabled ? t('Please provide sequence data first') : t('Launch the algorithm!'),
}
}, [canRun, dataset, hasInputErrors, hasRequiredInputs, isAutodetectRunning, t])

return (
<ButtonStyled disabled={isDisabled} color={color} onClick={onClick} title={tooltip} {...restProps}>
{t('Run')}
</ButtonStyled>
)
}

const ButtonStyled = styled(Button)`
min-width: 150px;
min-height: 40px;
`
Loading