From e8cc7a379fff352a94f20f271fbbe19219985ec4 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Tue, 5 Sep 2023 02:32:25 +0200 Subject: [PATCH] feat(web): add sequence sorting prototype --- .../src/cli/nextclade_seq_sort.rs | 8 +- packages_rs/nextclade-web/src/build.rs | 5 ++ .../components/Autodetect/AutodetectPage.tsx | 89 +++++++++++++++++++ .../src/components/Main/DatasetCurrent.tsx | 44 ++++----- .../src/components/Main/DatasetInfo.tsx | 24 +++++ .../components/Main/DatasetSelectorList.tsx | 22 +++++ .../Main/MainInputFormSequenceFilePicker.tsx | 16 +++- .../src/hooks/useRunSeqAutodetect.ts | 58 ++++++++++++ .../nextclade-web/src/io/fetchDatasets.ts | 24 +++-- .../src/io/fetchDatasetsIndex.ts | 28 +++++- packages_rs/nextclade-web/src/lib.rs | 8 ++ packages_rs/nextclade-web/src/pages/_app.tsx | 18 +++- .../nextclade-web/src/pages/autodetect.tsx | 1 + .../src/state/autodetect.state.ts | 68 ++++++++++++++ .../nextclade-web/src/state/dataset.state.ts | 12 ++- packages_rs/nextclade-web/src/wasm/jserr.rs | 8 ++ packages_rs/nextclade-web/src/wasm/main.rs | 12 +-- packages_rs/nextclade-web/src/wasm/mod.rs | 2 + .../nextclade-web/src/wasm/seq_autodetect.rs | 59 ++++++++++++ .../src/workers/launchAnalysis.ts | 2 +- .../src/workers/nextcladeAutodetect.worker.ts | 60 +++++++++++++ .../src/workers/nextcladeWasm.worker.ts | 4 +- .../nextclade/src/sort/minimizer_search.rs | 7 ++ 23 files changed, 518 insertions(+), 61 deletions(-) create mode 100644 packages_rs/nextclade-web/src/components/Autodetect/AutodetectPage.tsx create mode 100644 packages_rs/nextclade-web/src/hooks/useRunSeqAutodetect.ts create mode 100644 packages_rs/nextclade-web/src/pages/autodetect.tsx create mode 100644 packages_rs/nextclade-web/src/state/autodetect.state.ts create mode 100644 packages_rs/nextclade-web/src/wasm/jserr.rs create mode 100644 packages_rs/nextclade-web/src/wasm/seq_autodetect.rs create mode 100644 packages_rs/nextclade-web/src/workers/nextcladeAutodetect.worker.ts diff --git a/packages_rs/nextclade-cli/src/cli/nextclade_seq_sort.rs b/packages_rs/nextclade-cli/src/cli/nextclade_seq_sort.rs index 8cdbe6a6ae..670537bf51 100644 --- a/packages_rs/nextclade-cli/src/cli/nextclade_seq_sort.rs +++ b/packages_rs/nextclade-cli/src/cli/nextclade_seq_sort.rs @@ -7,7 +7,7 @@ use log::{info, trace, LevelFilter}; use nextclade::io::fasta::{FastaReader, FastaRecord, FastaWriter}; use nextclade::make_error; use nextclade::sort::minimizer_index::{MinimizerIndexJson, MINIMIZER_INDEX_ALGO_VERSION}; -use nextclade::sort::minimizer_search::{run_minimizer_search, MinimizerSearchResult}; +use nextclade::sort::minimizer_search::{run_minimizer_search, MinimizerSearchRecord}; use nextclade::utils::string::truncate; use serde::Serialize; use std::collections::BTreeMap; @@ -15,12 +15,6 @@ use std::path::PathBuf; use std::str::FromStr; use tinytemplate::TinyTemplate; -#[derive(Debug, Clone)] -struct MinimizerSearchRecord { - pub fasta_record: FastaRecord, - pub result: MinimizerSearchResult, -} - pub fn nextclade_seq_sort(args: &NextcladeSeqSortArgs) -> Result<(), Report> { check_args(args)?; diff --git a/packages_rs/nextclade-web/src/build.rs b/packages_rs/nextclade-web/src/build.rs index 9d6b85f9e4..6dcd7284b4 100644 --- a/packages_rs/nextclade-web/src/build.rs +++ b/packages_rs/nextclade-web/src/build.rs @@ -16,6 +16,8 @@ use nextclade::qc::qc_run::QcResult; use nextclade::run::nextclade_wasm::{ AnalysisInitialData, AnalysisInput, NextcladeParams, NextcladeParamsRaw, NextcladeResult, OutputTrees, }; +use nextclade::sort::minimizer_index::MinimizerIndexJson; +use nextclade::sort::minimizer_search::{MinimizerSearchRecord, MinimizerSearchResult}; use nextclade::translate::translate_genes::Translation; use nextclade::tree::tree::{AuspiceTree, CladeNodeAttrKeyDesc}; use nextclade::types::outputs::{NextcladeErrorOutputs, NextcladeOutputs}; @@ -73,4 +75,7 @@ struct _SchemaRoot<'a> { _27: DatasetAttributeValue, _28: DatasetAttributes, _29: DatasetCollectionUrl, + _30: MinimizerIndexJson, + _31: MinimizerSearchResult, + _32: MinimizerSearchRecord, } diff --git a/packages_rs/nextclade-web/src/components/Autodetect/AutodetectPage.tsx b/packages_rs/nextclade-web/src/components/Autodetect/AutodetectPage.tsx new file mode 100644 index 0000000000..122620cf85 --- /dev/null +++ b/packages_rs/nextclade-web/src/components/Autodetect/AutodetectPage.tsx @@ -0,0 +1,89 @@ +import React from 'react' +import { Col as ColBase, Row as RowBase } from 'reactstrap' +import { useRecoilValue } from 'recoil' +import { TableSlimWithBorders } from 'src/components/Common/TableSlim' +import { Layout } from 'src/components/Layout/Layout' +import { useTranslationSafe } from 'src/helpers/useTranslationSafe' +import { autodetectResultsAtom } from 'src/state/autodetect.state' +import styled from 'styled-components' + +const Container = styled.div` + margin-top: 1rem; + height: 100%; + overflow: hidden; +` + +const Row = styled(RowBase)` + overflow: hidden; + height: 100%; +` + +const Col = styled(ColBase)` + overflow: hidden; + height: 100%; +` + +const Table = styled(TableSlimWithBorders)` + padding-top: 50px; + + & thead { + height: 51px; + position: sticky; + top: -2px; + background-color: ${(props) => props.theme.gray700}; + color: ${(props) => props.theme.gray100}; + } + + & thead th { + margin: auto; + text-align: center; + vertical-align: middle; + } +` + +const TableWrapper = styled.div` + height: 100%; + overflow-y: auto; +` + +export function AutodetectPage() { + const { t } = useTranslationSafe() + // const minimizerIndex = useRecoilValue(minimizerIndexAtom) + const autodetectResults = useRecoilValue(autodetectResultsAtom) + + return ( + + + + + + + + + + + + + + + + + + {autodetectResults.map((res) => ( + + + + + + + + ))} + +
{'#'}{t('Seq. name')}{t('dataset')}{t('total hits')}{t('max hit')}
{res.fastaRecord.index}{res.fastaRecord.seqName}{res.result.dataset ?? ''}{res.result.totalHits}{res.result.maxNormalizedHit.toFixed(3)}
+
+ +
+
+
+ ) +} diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetCurrent.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetCurrent.tsx index af08072012..0c4d5167e4 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetCurrent.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetCurrent.tsx @@ -1,5 +1,5 @@ import { isNil } from 'lodash' -import React, { useCallback, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { Button, Col, Collapse, Row, UncontrolledAlert } from 'reactstrap' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import styled from 'styled-components' @@ -82,6 +82,28 @@ export function DatasetCurrent() { const onCustomizeClicked = useCallback(() => setAdvancedOpen((advancedOpen) => !advancedOpen), []) + const customize = useMemo(() => { + if (datasetCurrent?.path === 'autodetect') { + return null + } + + return ( + + + + + + + + + + + + + + ) + }, [advancedOpen, datasetCurrent?.path, onCustomizeClicked]) + if (!datasetCurrent) { return null } @@ -105,29 +127,11 @@ export function DatasetCurrent() { {t('Change')} - - {t('Recent dataset updates')} - - - - - - - - - - - - - - + {customize} ) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx index 0ea36038c6..adb5af26c8 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetInfo.tsx @@ -21,6 +21,11 @@ export const DatasetInfoLine = styled.p` font-size: 0.9rem; padding: 0; margin: 0; + + &:after { + content: ' '; + white-space: pre; + } ` const DatasetInfoBadge = styled(Badge)` @@ -50,6 +55,10 @@ export function DatasetInfo({ dataset }: DatasetInfoProps) { return null } + if (path === 'autodetect') { + return + } + return ( @@ -107,3 +116,18 @@ export function DatasetInfo({ dataset }: DatasetInfoProps) { ) } + +export function DatasetAutodetectInfo() { + const { t } = useTranslationSafe() + + return ( + + + {t('Autodetect')} + + {t('Detect pathogen automatically from sequences')} + + + + ) +} diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 6ccdafb799..d2f148b684 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -18,6 +18,20 @@ import { DatasetInfo } from 'src/components/Main/DatasetInfo' // border-radius: 5px; // ` +const DATASET_AUTODETECT: Dataset = { + path: 'autodetect', + enabled: true, + official: true, + attributes: { + name: { value: 'autodetect', valueFriendly: 'Autodetect' }, + reference: { value: 'autodetect', valueFriendly: 'Autodetect' }, + }, + files: { + reference: '', + pathogenJson: '', + }, +} + export const DatasetSelectorUl = styled(ListGroup)` flex: 1; overflow-y: scroll; @@ -79,6 +93,14 @@ export function DatasetSelectorList({ return ( // + { + + } + {[itemsStartWith, itemsInclude].map((datasets) => datasets.map((dataset) => ( , []) - const run = useRunAnalysis() + const runAnalysis = useRunAnalysis() + const runAutodetect = useRunSeqAutodetect() + + const run = useCallback(() => { + if (datasetCurrent?.path === 'autodetect') { + runAutodetect() + } else { + runAnalysis() + } + }, [datasetCurrent?.path, runAnalysis, runAutodetect]) const setSequences = useCallback( (inputs: AlgorithmInput[]) => { addQryInputs(inputs) - if (shouldRunAutomatically) { run() } @@ -62,7 +71,6 @@ export function MainInputFormSequenceFilePicker() { const setExampleSequences = useCallback(() => { if (datasetCurrent) { addQryInputs([new AlgorithmInputDefault(datasetCurrent)]) - if (shouldRunAutomatically) { run() } @@ -81,7 +89,7 @@ export function MainInputFormSequenceFilePicker() { }, [canRun, hasInputErrors, hasRequiredInputs, t]) const LoadExampleLink = useMemo(() => { - const cannotLoadExample = hasInputErrors || !datasetCurrent + const cannotLoadExample = hasInputErrors || !datasetCurrent || datasetCurrent.path === 'autodetect' return (