From 1c5fdc259f078e7f19fab43defb89dfc5ce65143 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 18 Sep 2023 09:10:26 +0200 Subject: [PATCH 01/11] feat(web): swap file picker and dataset selector widgets (left to right) --- .../nextclade-web/src/components/Main/MainInputForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx index 94b4b9e84..b802fb94d 100644 --- a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx +++ b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx @@ -27,12 +27,12 @@ export function MainInputForm() { return ( - + - + - + From 1b3631fb1fda04e731b829d83032cb5bd754c2ed Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 18 Sep 2023 09:25:01 +0200 Subject: [PATCH 02/11] feat(web): alternative main page layout --- .../components/Main/DatasetSelectorList.tsx | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 2bb5be734..936672290 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -1,17 +1,23 @@ import { get, isNil, sortBy } from 'lodash' import { lighten } from 'polished' import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react' -import { ListGroup } from 'reactstrap' -import { useRecoilState, useRecoilValue } from 'recoil' +import { Button, ListGroup } from 'reactstrap' +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { ListGenericCss } from 'src/components/Common/List' import { DatasetInfo } from 'src/components/Main/DatasetInfo' import { search } from 'src/helpers/search' +import { useTranslationSafe } from 'src/helpers/useTranslationSafe' +import { useRunAnalysis } from 'src/hooks/useRunAnalysis' import { autodetectResultsAtom, AutodetectRunState, autodetectRunStateAtom, groupByDatasets, } 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' import type { Dataset } from 'src/types' import { areDatasetsEqual } from 'src/types' import styled from 'styled-components' @@ -30,7 +36,12 @@ export function DatasetSelectorList({ datasetHighlighted, onDatasetHighlighted, }: DatasetSelectorListProps) { - const onItemClick = useCallback((dataset: Dataset) => () => onDatasetHighlighted(dataset), [onDatasetHighlighted]) + const onItemClick = useCallback( + (_dataset: Dataset) => () => { + /* onDatasetHighlighted(dataset) */ + }, + [], + ) const autodetectResults = useRecoilValue(autodetectResultsAtom) const [autodetectRunState, setAutodetectRunState] = useRecoilState(autodetectRunStateAtom) @@ -147,6 +158,8 @@ export const Ul = styled(ListGroup)` ` export const Li = styled.li<{ $active?: boolean; $isDimmed?: boolean }>` + position: relative; + cursor: pointer; opacity: ${(props) => props.$isDimmed && 0.4}; background-color: transparent; @@ -173,10 +186,54 @@ interface DatasetSelectorListItemProps { const DatasetSelectorListItem = forwardRef( function DatasetSelectorListItemWithRef({ dataset, isCurrent, isDimmed, onClick }, ref) { + const { t } = useTranslationSafe() + + const setDatasetCurrent = useSetRecoilState(datasetCurrentAtom) + + const canRun = useRecoilValue(canRunAtom) + const hasRequiredInputs = useRecoilValue(hasRequiredInputsAtom) + const hasInputErrors = useRecoilValue(hasInputErrorsAtom) + + const runAnalysis = useRunAnalysis() + const run = useCallback(() => { + setDatasetCurrent(dataset) + runAnalysis() + }, [dataset, runAnalysis, setDatasetCurrent]) + + const { isRunButtonDisabled, runButtonColor, runButtonTooltip } = useMemo(() => { + const isRunButtonDisabled = !(canRun && hasRequiredInputs) || hasInputErrors + return { + isRunButtonDisabled, + runButtonColor: isRunButtonDisabled ? 'secondary' : 'success', + runButtonTooltip: isRunButtonDisabled + ? t('Please provide sequence data for the algorithm') + : t('Launch the algorithm!'), + } + }, [canRun, hasInputErrors, hasRequiredInputs, t]) + return (
  • + + {t('Run')} +
  • ) }, ) + +const ButtonRunStyled = styled(Button)` + position: absolute; + bottom: 10px; + right: 10px; + min-width: 120px; + min-height: 30px; +` + +export const FlexRight = styled.div` + position: absolute; +` + +export const FlexLeft = styled.div` + margin-right: auto; +` From 139e836903ece2d4a4b1bb67b741eead0e2222af Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 18 Sep 2023 09:41:05 +0200 Subject: [PATCH 03/11] feat(web): add "load example" button to each dataset list item --- .../components/Main/DatasetSelectorList.tsx | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 936672290..13d9154ed 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -8,6 +8,8 @@ import { DatasetInfo } from 'src/components/Main/DatasetInfo' import { search } from 'src/helpers/search' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { useRunAnalysis } from 'src/hooks/useRunAnalysis' +import { useRecoilToggle } from 'src/hooks/useToggle' +import { AlgorithmInputDefault } from 'src/io/AlgorithmInput' import { autodetectResultsAtom, AutodetectRunState, @@ -16,8 +18,9 @@ import { } 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 { hasRequiredInputsAtom, useQuerySeqInputs } from 'src/state/inputs.state' import { canRunAtom } from 'src/state/results.state' +import { shouldRunAutomaticallyAtom, shouldSuggestDatasetsAtom } from 'src/state/settings.state' import type { Dataset } from 'src/types' import { areDatasetsEqual } from 'src/types' import styled from 'styled-components' @@ -189,7 +192,8 @@ const DatasetSelectorListItem = forwardRef { + addQryInputs([new AlgorithmInputDefault(dataset)]) + if (shouldRunAutomatically) { + runAnalysis() + } + }, [addQryInputs, dataset, runAnalysis, shouldRunAutomatically]) + return (
  • + + + {t('Load example')} + + {t('Run')} @@ -230,6 +246,14 @@ const ButtonRunStyled = styled(Button)` min-height: 30px; ` +const ButtonLoadExample = styled(Button)` + position: absolute; + bottom: 45px; + right: 2px; + min-width: 120px; + min-height: 30px; +` + export const FlexRight = styled.div` position: absolute; ` From d94c496fda2e5ccb02fc668adb992eb0264368e9 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 18 Sep 2023 09:49:50 +0200 Subject: [PATCH 04/11] refactor: lint --- .../nextclade-web/src/components/Main/DatasetSelectorList.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 13d9154ed..d1dbbadc1 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -8,7 +8,6 @@ import { DatasetInfo } from 'src/components/Main/DatasetInfo' import { search } from 'src/helpers/search' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { useRunAnalysis } from 'src/hooks/useRunAnalysis' -import { useRecoilToggle } from 'src/hooks/useToggle' import { AlgorithmInputDefault } from 'src/io/AlgorithmInput' import { autodetectResultsAtom, @@ -20,7 +19,7 @@ import { datasetCurrentAtom } from 'src/state/dataset.state' import { hasInputErrorsAtom } from 'src/state/error.state' import { hasRequiredInputsAtom, useQuerySeqInputs } from 'src/state/inputs.state' import { canRunAtom } from 'src/state/results.state' -import { shouldRunAutomaticallyAtom, shouldSuggestDatasetsAtom } from 'src/state/settings.state' +import { shouldRunAutomaticallyAtom } from 'src/state/settings.state' import type { Dataset } from 'src/types' import { areDatasetsEqual } from 'src/types' import styled from 'styled-components' From 3ef6366d035d665705aa1cfb3998801c8c4ae8df Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 13:15:21 +0200 Subject: [PATCH 05/11] feat: remove dataset highlighting, global run button and autorun toggle --- .../src/components/Main/DatasetSelectorList.tsx | 17 ++--------------- .../components/Main/QuerySequenceFilePicker.tsx | 10 ---------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index d1dbbadc1..1b00d571b 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -86,19 +86,6 @@ export function DatasetSelectorList({ const itemsRef = useRef>(new Map()) - function scrollToId(itemId: string) { - const node = itemsRef.current.get(itemId) - node?.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'center', - }) - } - - if (datasetHighlighted) { - scrollToId(datasetHighlighted.path) - } - useEffect(() => { const topSuggestion = autodetectResult.itemsInclude[0] if (autodetectRunState === AutodetectRunState.Done) { @@ -187,7 +174,7 @@ interface DatasetSelectorListItemProps { } const DatasetSelectorListItem = forwardRef( - function DatasetSelectorListItemWithRef({ dataset, isCurrent, isDimmed, onClick }, ref) { + function DatasetSelectorListItemWithRef({ dataset, isDimmed, onClick }, ref) { const { t } = useTranslationSafe() const setDatasetCurrent = useSetRecoilState(datasetCurrentAtom) @@ -222,7 +209,7 @@ const DatasetSelectorListItem = forwardRef +
  • diff --git a/packages_rs/nextclade-web/src/components/Main/QuerySequenceFilePicker.tsx b/packages_rs/nextclade-web/src/components/Main/QuerySequenceFilePicker.tsx index 0c69f6f48..c2e9c28e4 100644 --- a/packages_rs/nextclade-web/src/components/Main/QuerySequenceFilePicker.tsx +++ b/packages_rs/nextclade-web/src/components/Main/QuerySequenceFilePicker.tsx @@ -3,7 +3,6 @@ import { useRecoilValue } from 'recoil' import styled from 'styled-components' import type { AlgorithmInput } from 'src/types' import { QuerySequenceList } from 'src/components/Main/QuerySequenceList' -import { RunPanel } from 'src/components/Main/RunPanel' import { useRunAnalysis } from 'src/hooks/useRunAnalysis' import { useRunSeqAutodetect } from 'src/hooks/useRunSeqAutodetect' import { useRecoilToggle } from 'src/hooks/useToggle' @@ -67,10 +66,6 @@ export function QuerySequenceFilePicker() {
    - -
    - -
    ) } @@ -97,8 +92,3 @@ const Main = styled.div` flex-direction: column; overflow: hidden; ` - -const Footer = styled.div` - display: flex; - flex: 0; -` From 28ca96e29834ad733c2042ea33e05b5a6ce212c6 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 13:38:14 +0200 Subject: [PATCH 06/11] feat: run suggestions when examples are added --- .../src/components/Main/DatasetSelectorList.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 1b00d571b..df303b712 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -8,6 +8,7 @@ import { DatasetInfo } from 'src/components/Main/DatasetInfo' import { search } from 'src/helpers/search' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { useRunAnalysis } from 'src/hooks/useRunAnalysis' +import { useRunSeqAutodetect } from 'src/hooks/useRunSeqAutodetect' import { AlgorithmInputDefault } from 'src/io/AlgorithmInput' import { autodetectResultsAtom, @@ -19,7 +20,7 @@ import { datasetCurrentAtom } from 'src/state/dataset.state' import { hasInputErrorsAtom } from 'src/state/error.state' import { hasRequiredInputsAtom, useQuerySeqInputs } from 'src/state/inputs.state' import { canRunAtom } from 'src/state/results.state' -import { shouldRunAutomaticallyAtom } from 'src/state/settings.state' +import { shouldRunAutomaticallyAtom, shouldSuggestDatasetsAtom } from 'src/state/settings.state' import type { Dataset } from 'src/types' import { areDatasetsEqual } from 'src/types' import styled from 'styled-components' @@ -179,12 +180,14 @@ const DatasetSelectorListItem = forwardRef { setDatasetCurrent(dataset) runAnalysis() @@ -203,10 +206,13 @@ const DatasetSelectorListItem = forwardRef { addQryInputs([new AlgorithmInputDefault(dataset)]) + if (shouldSuggestDatasets) { + runSuggestions() + } if (shouldRunAutomatically) { runAnalysis() } - }, [addQryInputs, dataset, runAnalysis, shouldRunAutomatically]) + }, [addQryInputs, dataset, runAnalysis, runSuggestions, shouldRunAutomatically, shouldSuggestDatasets]) return (
  • From 721e77760b5a638f4f6b1846e162d1fdf952f0b4 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 18:35:28 +0200 Subject: [PATCH 07/11] feat: add suggestion run progress indication --- .../src/components/Main/DatasetSelector.tsx | 14 +- .../components/Main/DatasetSelectorList.tsx | 2 - .../src/components/Main/SuggestionPanel.tsx | 228 +++++++++++++++--- .../nextclade-web/src/helpers/unreachable.ts | 5 + .../src/hooks/useResetSuggestions.ts | 12 + .../src/hooks/useRunSeqAutodetect.ts | 4 +- .../src/state/autodetect.state.ts | 14 +- .../nextclade-web/src/state/inputs.state.ts | 8 +- 8 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 packages_rs/nextclade-web/src/helpers/unreachable.ts create mode 100644 packages_rs/nextclade-web/src/hooks/useResetSuggestions.ts diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx index ca7f290b4..950900815 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx @@ -20,9 +20,11 @@ export function DatasetSelector() {
    {t('Select dataset')} -
    +
    + +
    {!isBusy && ( @@ -42,10 +44,6 @@ export function DatasetSelector() { )}
    - -
    - -
    ) } @@ -62,7 +60,6 @@ const Container = styled.div` const Header = styled.div` display: flex; flex: 0; - padding-left: 10px; margin-top: 10px; margin-bottom: 3px; ` @@ -74,11 +71,6 @@ const Main = styled.div` overflow: hidden; ` -const Footer = styled.div` - display: flex; - flex: 0; -` - const Title = styled.h4` flex: 1; margin: auto 0; diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index df303b712..030c7fd10 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -91,7 +91,6 @@ export function DatasetSelectorList({ const topSuggestion = autodetectResult.itemsInclude[0] if (autodetectRunState === AutodetectRunState.Done) { onDatasetHighlighted(topSuggestion) - setAutodetectRunState(AutodetectRunState.Idle) } }, [autodetectRunState, autodetectResult.itemsInclude, onDatasetHighlighted, setAutodetectRunState]) @@ -143,7 +142,6 @@ export const Ul = styled(ListGroup)` ${ListGenericCss}; flex: 1; overflow: auto; - padding: 5px 5px; border-radius: 0 !important; ` diff --git a/packages_rs/nextclade-web/src/components/Main/SuggestionPanel.tsx b/packages_rs/nextclade-web/src/components/Main/SuggestionPanel.tsx index 8d9800c17..b45fa4c41 100644 --- a/packages_rs/nextclade-web/src/components/Main/SuggestionPanel.tsx +++ b/packages_rs/nextclade-web/src/components/Main/SuggestionPanel.tsx @@ -1,54 +1,187 @@ import { isNil } from 'lodash' import React, { useMemo } from 'react' -import { useRunSeqAutodetect } from 'src/hooks/useRunSeqAutodetect' -import { hasRequiredInputsAtom } from 'src/state/inputs.state' +import { Button, Form as FormBase, FormGroup as FormGroupBase, Spinner, UncontrolledAlert } from 'reactstrap' +import { useRecoilValue } from 'recoil' import styled from 'styled-components' -import { Button, Form as FormBase, FormGroup } from 'reactstrap' -import { useRecoilValue, useResetRecoilState } from 'recoil' import { Toggle } from 'src/components/Common/Toggle' -import { FlexLeft, FlexRight } from 'src/components/FilePicker/FilePickerStyles' +import { unreachable } from 'src/helpers/unreachable' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' +import { useRunSeqAutodetect } from 'src/hooks/useRunSeqAutodetect' +import { useResetSuggestions } from 'src/hooks/useResetSuggestions' import { useRecoilToggle } from 'src/hooks/useToggle' -import { autodetectResultsAtom, hasAutodetectResultsAtom } from 'src/state/autodetect.state' -import { minimizerIndexVersionAtom } from 'src/state/dataset.state' +import { + autodetectResultsAtom, + AutodetectRunState, + autodetectRunStateAtom, + groupByDatasets, + hasAutodetectResultsAtom, + numberAutodetectResultsAtom, +} from 'src/state/autodetect.state' +import { datasetsAtom, minimizerIndexVersionAtom } from 'src/state/dataset.state' +import { hasRequiredInputsAtom } from 'src/state/inputs.state' import { shouldSuggestDatasetsAtom } from 'src/state/settings.state' export function SuggestionPanel() { - const { t } = useTranslationSafe() const minimizerIndexVersion = useRecoilValue(minimizerIndexVersionAtom) - const resetAutodetectResults = useResetRecoilState(autodetectResultsAtom) - const hasAutodetectResults = useRecoilValue(hasAutodetectResultsAtom) + const autodetectRunState = useRecoilValue(autodetectRunStateAtom) + + if (isNil(minimizerIndexVersion)) { + return null + } + + switch (autodetectRunState) { + case AutodetectRunState.Idle: + return + case AutodetectRunState.Started: + return + case AutodetectRunState.Done: + return + case AutodetectRunState.Failed: + return + default: { + return unreachable(autodetectRunState) + } + } +} + +export function ButtonSuggest() { + const { t } = useTranslationSafe() const hasRequiredInputs = useRecoilValue(hasRequiredInputsAtom) const runSuggest = useRunSeqAutodetect() + const hasAutodetectResults = useRecoilValue(hasAutodetectResultsAtom) - const { canRun, runButtonColor, runButtonTooltip } = useMemo(() => { + const { text, canRun, color, title } = useMemo(() => { const canRun = hasRequiredInputs return { + text: hasAutodetectResults ? t('Re-suggest') : t('Suggest'), canRun, - runButtonColor: !canRun ? 'secondary' : 'success', - runButtonTooltip: !canRun ? t('Please provide sequence data for the algorithm') : t('Launch suggestions engine!'), + color: !canRun ? 'secondary' : 'primary', + title: !canRun + ? t('Please provide sequence data for the algorithm') + : hasAutodetectResults + ? t('Re-launch suggestions engine!') + : t('Launch suggestions engine!'), } - }, [hasRequiredInputs, t]) + }, [hasAutodetectResults, hasRequiredInputs, t]) - if (isNil(minimizerIndexVersion)) { - return null - } + return ( + + {text} + + ) +} + +export function ButtonSuggestionsReset() { + const { t } = useTranslationSafe() + const resetAutodetectResults = useResetSuggestions() + const hasAutodetectResults = useRecoilValue(hasAutodetectResultsAtom) + return ( + + {t('Reset')} + + ) +} + +export function SuggestionPanelIdle() { return (
    - + +
    +

    + +

    +

    {'\u00A0'}

    +
    +
    + + + +
    +
    + ) +} +export function SuggestionPanelStarted() { + const { t } = useTranslationSafe() + const numberAutodetectResults = useRecoilValue(numberAutodetectResultsAtom) + + return ( + +
    + + +
    +

    {t('Searching matching datasets')}

    +

    {t(`${numberAutodetectResults} sequences`)}

    +
    +
    +
    +
    + ) +} + +export function SuggestionPanelDone() { + const { t } = useTranslationSafe() + const { datasets } = useRecoilValue(datasetsAtom) + const autodetectResults = useRecoilValue(autodetectResultsAtom) + const numSuggestedDatasets = useMemo(() => { + if (!autodetectResults) { + return 0 + } + const recordsByDataset = groupByDatasets(autodetectResults) + return datasets.filter((candidate) => + Object.entries(recordsByDataset).some(([dataset, _]) => dataset === candidate.path), + ).length + }, [autodetectResults, datasets]) + + const text = useMemo(() => { + if (numSuggestedDatasets === 0) { + return ( + +

    {t('No matching datasets found.')}

    +

    {t('Consider contributing a new dataset.')}

    +
    + ) + } + return ( + +

    {t(`${numSuggestedDatasets} dataset(s) appear to match your data.`)}

    +

    {t('Select the one to use.')}

    +
    + ) + }, [numSuggestedDatasets, t]) + + return ( + +
    + {text} - + + + +
    +
    + ) +} - - {t('Suggest')} - +export function SuggestionPanelFailed() { + const { t } = useTranslationSafe() + return ( + +
    + + +

    {t('Suggestion engine failed.')}

    +

    {t('Please report this issue.')}

    +
    +
    + + +
    @@ -57,32 +190,57 @@ export function SuggestionPanel() { const Container = styled.div` flex: 1; - margin-top: auto; - margin-bottom: 7px; - padding: 7px 0; - padding-left: 5px; ` const Form = styled(FormBase)` display: flex; width: 100%; height: 100%; - margin-top: auto; + min-height: 45px; padding: 10px; border: 1px #ccc9 solid; border-radius: 5px; ` +export const FlexLeft = styled.div` + display: flex; + flex: 1; + margin-right: auto; + vertical-align: middle; +` + +export const FlexRight = styled.div` + margin-left: auto; +` + +const Alert = styled(UncontrolledAlert)` + margin: 0; + width: 100%; + padding: 0.5rem 1rem; +` + const ButtonRunStyled = styled(Button)` - min-width: 150px; - min-height: 45px; + width: 120px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +` + +const ButtonResetStyled = styled(Button)` + margin: 0 1rem; + max-width: 100px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; ` -function AutosuggestionToggle() { +function AutosuggestionToggle({ ...restProps }) { const { t } = useTranslationSafe() const { state: shouldSuggestDatasets, toggle: toggleSuggestDatasets } = useRecoilToggle(shouldSuggestDatasetsAtom) return ( - + ) } + +const FormGroup = styled(FormGroupBase)` + margin: auto 0; +` diff --git a/packages_rs/nextclade-web/src/helpers/unreachable.ts b/packages_rs/nextclade-web/src/helpers/unreachable.ts new file mode 100644 index 000000000..61ab958f2 --- /dev/null +++ b/packages_rs/nextclade-web/src/helpers/unreachable.ts @@ -0,0 +1,5 @@ +import { ErrorInternal } from 'src/helpers/ErrorInternal' + +export function unreachable(impossible: never): never { + throw new ErrorInternal(`Reached impossible state: '${impossible}'`) +} diff --git a/packages_rs/nextclade-web/src/hooks/useResetSuggestions.ts b/packages_rs/nextclade-web/src/hooks/useResetSuggestions.ts new file mode 100644 index 000000000..8124dd0ff --- /dev/null +++ b/packages_rs/nextclade-web/src/hooks/useResetSuggestions.ts @@ -0,0 +1,12 @@ +import { useCallback } from 'react' +import { useResetRecoilState } from 'recoil' +import { autodetectResultsAtom, autodetectRunStateAtom } from 'src/state/autodetect.state' + +export function useResetSuggestions() { + const resetAutodetectResultsAtom = useResetRecoilState(autodetectResultsAtom) + const resetAutodetectRunStateAtom = useResetRecoilState(autodetectRunStateAtom) + return useCallback(() => { + resetAutodetectResultsAtom() + resetAutodetectRunStateAtom() + }, [resetAutodetectResultsAtom, resetAutodetectRunStateAtom]) +} diff --git a/packages_rs/nextclade-web/src/hooks/useRunSeqAutodetect.ts b/packages_rs/nextclade-web/src/hooks/useRunSeqAutodetect.ts index fb1ce5837..0b80d38b2 100644 --- a/packages_rs/nextclade-web/src/hooks/useRunSeqAutodetect.ts +++ b/packages_rs/nextclade-web/src/hooks/useRunSeqAutodetect.ts @@ -23,8 +23,6 @@ export function useRunSeqAutodetect() { () => { const { getPromise } = snapshot - set(autodetectRunStateAtom, AutodetectRunState.Started) - reset(minimizerIndexAtom) reset(autodetectResultsAtom) reset(autodetectRunStateAtom) @@ -44,6 +42,8 @@ export function useRunSeqAutodetect() { set(autodetectRunStateAtom, AutodetectRunState.Done) } + set(autodetectRunStateAtom, AutodetectRunState.Started) + Promise.all([getPromise(qrySeqInputsStorageAtom), getPromise(minimizerIndexVersionAtom)]) .then(async ([qrySeqInputs, minimizerIndexVersion]) => { if (!minimizerIndexVersion) { diff --git a/packages_rs/nextclade-web/src/state/autodetect.state.ts b/packages_rs/nextclade-web/src/state/autodetect.state.ts index 5697f4875..5018211a3 100644 --- a/packages_rs/nextclade-web/src/state/autodetect.state.ts +++ b/packages_rs/nextclade-web/src/state/autodetect.state.ts @@ -113,13 +113,6 @@ export const numberAutodetectResultsAtom = selector({ }, }) -export const hasAutodetectResultsAtom = selector({ - key: 'hasAutodetectResultsAtom', - get({ get }) { - return get(numberAutodetectResultsAtom) > 0 - }, -}) - export enum AutodetectRunState { Idle = 'Idle', Started = 'Started', @@ -131,3 +124,10 @@ export const autodetectRunStateAtom = atom({ key: 'autodetectRunStateAtom', default: AutodetectRunState.Idle, }) + +export const hasAutodetectResultsAtom = selector({ + key: 'hasAutodetectResultsAtom', + get({ get }) { + return get(autodetectRunStateAtom) === AutodetectRunState.Done && get(numberAutodetectResultsAtom) > 0 + }, +}) diff --git a/packages_rs/nextclade-web/src/state/inputs.state.ts b/packages_rs/nextclade-web/src/state/inputs.state.ts index 2ddff2b13..6ae91a339 100644 --- a/packages_rs/nextclade-web/src/state/inputs.state.ts +++ b/packages_rs/nextclade-web/src/state/inputs.state.ts @@ -1,9 +1,9 @@ import { isEmpty } from 'lodash' import { useCallback } from 'react' import { atom, selector, useRecoilState, useResetRecoilState } from 'recoil' -import { autodetectResultsAtom } from 'src/state/autodetect.state' import { AlgorithmInput } from 'src/types' import { notUndefinedOrNull } from 'src/helpers/notUndefined' +import { useResetSuggestions } from 'src/hooks/useResetSuggestions' export const qrySeqInputsStorageAtom = atom({ key: 'qrySeqInputsStorage', @@ -13,7 +13,7 @@ export const qrySeqInputsStorageAtom = atom({ export function useQuerySeqInputs() { const [qryInputs, setQryInputs] = useRecoilState(qrySeqInputsStorageAtom) const resetSeqInputsStorage = useResetRecoilState(qrySeqInputsStorageAtom) - const resetAutodetectResults = useResetRecoilState(autodetectResultsAtom) + const resetSuggestions = useResetSuggestions() const addQryInputs = useCallback( (newInputs: AlgorithmInput[]) => { @@ -30,9 +30,9 @@ export function useQuerySeqInputs() { ) const clearQryInputs = useCallback(() => { - resetAutodetectResults() + resetSuggestions() resetSeqInputsStorage() - }, [resetAutodetectResults, resetSeqInputsStorage]) + }, [resetSeqInputsStorage, resetSuggestions]) return { qryInputs, addQryInputs, removeQryInput, clearQryInputs } } From 4551781417f4c1f3955cc65f3ad011efff68ed02 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 18:44:26 +0200 Subject: [PATCH 08/11] fix: reset suggestions when last input is removed --- packages_rs/nextclade-web/src/state/inputs.state.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages_rs/nextclade-web/src/state/inputs.state.ts b/packages_rs/nextclade-web/src/state/inputs.state.ts index 6ae91a339..74ec627aa 100644 --- a/packages_rs/nextclade-web/src/state/inputs.state.ts +++ b/packages_rs/nextclade-web/src/state/inputs.state.ts @@ -1,5 +1,5 @@ import { isEmpty } from 'lodash' -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import { atom, selector, useRecoilState, useResetRecoilState } from 'recoil' import { AlgorithmInput } from 'src/types' import { notUndefinedOrNull } from 'src/helpers/notUndefined' @@ -34,6 +34,12 @@ export function useQuerySeqInputs() { resetSeqInputsStorage() }, [resetSeqInputsStorage, resetSuggestions]) + useEffect(() => { + if (qryInputs.length === 0) { + resetSuggestions() + } + }, [qryInputs, resetSuggestions]) + return { qryInputs, addQryInputs, removeQryInput, clearQryInputs } } From b2106e7591d78aebc764fe0039be22b30e351607 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 19:20:51 +0200 Subject: [PATCH 09/11] fix: remove pointer cursor from dataset list elements --- .../nextclade-web/src/components/Main/DatasetSelectorList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx index 030c7fd10..bc7c75378 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelectorList.tsx @@ -148,7 +148,6 @@ export const Ul = styled(ListGroup)` export const Li = styled.li<{ $active?: boolean; $isDimmed?: boolean }>` position: relative; - cursor: pointer; opacity: ${(props) => props.$isDimmed && 0.4}; background-color: transparent; From 2646513bd2115c64e8f3355e5763263aeb00e214 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Mon, 16 Oct 2023 19:21:09 +0200 Subject: [PATCH 10/11] feat: restrict max width of main page --- packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx | 2 ++ packages_rs/nextclade-web/src/theme.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx index b802fb94d..fc62eb146 100644 --- a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx +++ b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx @@ -6,6 +6,8 @@ import { useUpdatedDatasetIndex } from 'src/io/fetchDatasets' import { DatasetSelector } from 'src/components/Main/DatasetSelector' const Container = styled.div` + max-width: ${(props) => props.theme.containerMaxWidths.twoxl}; + margin: 0 auto; height: 100%; overflow: hidden; margin-top: 10px; diff --git a/packages_rs/nextclade-web/src/theme.ts b/packages_rs/nextclade-web/src/theme.ts index 4d13bc8a0..e97b90704 100644 --- a/packages_rs/nextclade-web/src/theme.ts +++ b/packages_rs/nextclade-web/src/theme.ts @@ -16,6 +16,7 @@ const containerMaxWidths = { md: '720px', lg: '960px', xl: '1140px', + twoxl: '1500px', xxl: '1950px', } From 936b4eba7fa2e5b35ebf6768c97c40a2b9604495 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Fri, 20 Oct 2023 09:09:17 +0200 Subject: [PATCH 11/11] Merge branch 'master' into ui-2 --- .../src/components/Layout/Layout.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages_rs/nextclade-web/src/components/Layout/Layout.tsx b/packages_rs/nextclade-web/src/components/Layout/Layout.tsx index ef0d56bb6..b0b833808 100644 --- a/packages_rs/nextclade-web/src/components/Layout/Layout.tsx +++ b/packages_rs/nextclade-web/src/components/Layout/Layout.tsx @@ -18,8 +18,19 @@ const HeaderWrapper = styled.header` height: 45px; ` -const MainWrapper = styled.main` +const MainInner = styled.main` + display: flex; + flex: 1; + overflow: hidden; + height: 100%; + width: 100%; + padding: 0; + margin: 0; +` + +const MainOuter = styled.main` flex: auto; + flex-direction: column; overflow: hidden; height: 100%; width: 100%; @@ -36,10 +47,10 @@ export function Layout({ children }: PropsWithChildren - + - {children} - + {children} +