From 917af4dd1e0a1ff264fbf75dfac357bb9e0cc2da Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 15:24:18 +0100 Subject: [PATCH 1/7] 984 wip --- .../pageStateSelectors/ApplyFilterButton.tsx | 13 +++- .../CompareSideBySidePageStateSelector.tsx | 43 +++++++----- .../CompareVariantsPageStateSelector.tsx | 42 +++++------ ...CompareVariantsToBaselineStateSelector.tsx | 46 ++++++------ .../SequencingEffortsPageStateSelector.tsx | 39 +++++------ ...eVariantPageStateSelector.browser.spec.tsx | 2 +- .../SingleVariantPageStateSelector.tsx | 39 ++++++----- .../analyzeSingleVariant/CollectionsList.tsx | 70 +++++++++++-------- .../CovidSingleVariantDataDisplay.tsx | 22 +++--- .../CovidSingleVariantReactPage.tsx | 22 +++--- ...GenericAnalyseSingleVariantDataDisplay.tsx | 18 ++--- .../GenericAnalyseSingleVariantReactPage.tsx | 20 +++--- .../analyzeSingleVariant/QuickstartLinks.tsx | 26 ++++--- .../GenericCompareSideBySideDataDisplay.tsx | 22 ++---- .../GenericCompareSideBySideReactPage.tsx | 15 ++-- .../GenericCompareToBaselineDataDisplay.tsx | 22 ++---- .../GenericCompareToBaselineReactPage.tsx | 15 ++-- .../GenericCompareVariantsDataDisplay.tsx | 21 ++---- .../GenericCompareVariantsReactPage.tsx | 20 +++--- .../GenericSequencingEffortsReactPage.tsx | 12 ++-- website/src/views/OrganismConstants.ts | 1 + .../CompareSideBySidePageStateHandler.spec.ts | 1 + .../CompareToBaselinePageStateHandler.spec.ts | 1 + .../CompareVariantsPageStateHandler.spec.ts | 1 + .../SingleVariantPageStateHandler.spec.ts | 1 + 25 files changed, 264 insertions(+), 270 deletions(-) diff --git a/website/src/components/pageStateSelectors/ApplyFilterButton.tsx b/website/src/components/pageStateSelectors/ApplyFilterButton.tsx index d01b61b8f..bb2bf41f7 100644 --- a/website/src/components/pageStateSelectors/ApplyFilterButton.tsx +++ b/website/src/components/pageStateSelectors/ApplyFilterButton.tsx @@ -1,3 +1,5 @@ +import type { Dispatch, SetStateAction } from 'react'; + import type { WithClassName } from '../../types/WithClassName.ts'; import type { PageStateHandler } from '../../views/pageStateHandlers/PageStateHandler.ts'; @@ -8,15 +10,22 @@ const MAX_URL_LENGTH = 2000; export function ApplyFilterButton({ pageStateHandler, newPageState, + setPageState, className = '', }: WithClassName<{ pageStateHandler: PageStateHandler; newPageState: PageState; + setPageState: Dispatch>; }>) { const url = pageStateHandler.toUrl(newPageState); const fullUrl = `${window.location.origin}${url}`; const urlTooLong = fullUrl.length > MAX_URL_LENGTH; + const applyFilters = () => { + window.history.pushState(undefined, '', url); + setPageState(newPageState); + }; + return urlTooLong ? ( <> @@ -30,8 +39,8 @@ export function ApplyFilterButton({ ) : ( - + ); } diff --git a/website/src/components/pageStateSelectors/CompareSideBySidePageStateSelector.tsx b/website/src/components/pageStateSelectors/CompareSideBySidePageStateSelector.tsx index a71cfc05e..f63e329c9 100644 --- a/website/src/components/pageStateSelectors/CompareSideBySidePageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/CompareSideBySidePageStateSelector.tsx @@ -1,37 +1,38 @@ -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react'; import { ApplyFilterButton } from './ApplyFilterButton.tsx'; import { BaselineSelector } from './BaselineSelector.tsx'; import { SelectorHeadline } from './SelectorHeadline.tsx'; import { makeVariantFilterConfig, VariantSelector } from './VariantSelector.tsx'; -import type { OrganismsConfig } from '../../config.ts'; import { Inset } from '../../styles/Inset.tsx'; +import { BaseView } from '../../views/BaseView.ts'; +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; import type { CompareSideBySideData } from '../../views/View.ts'; -import { type OrganismViewKey, Routing } from '../../views/routing.ts'; -import type { compareSideBySideViewKey } from '../../views/viewKeys.ts'; +import { CompareSideBySideStateHandler } from '../../views/pageStateHandlers/CompareSideBySidePageStateHandler.ts'; export function CompareSideBySidePageStateSelector({ + view, filterId, - initialPageState, - organismViewKey, - organismsConfig, + pageState, + setPageState, enableAdvancedQueryFilter, }: { + view: BaseView; filterId: number; - initialPageState: CompareSideBySideData; - organismViewKey: OrganismViewKey & `${string}.${typeof compareSideBySideViewKey}`; - organismsConfig: OrganismsConfig; + pageState: CompareSideBySideData; + setPageState: Dispatch>; enableAdvancedQueryFilter: boolean; }) { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); + const [draftPageState, setDraftPageState] = useState(pageState); + useEffect(() => setDraftPageState(pageState), [pageState]); + const variantFilterConfig = useMemo( () => makeVariantFilterConfig(view.organismConstants), [view.organismConstants], ); - const [pageState, setPageState] = useState(initialPageState); const { filterOfCurrentId, currentLapisFilter } = useMemo(() => { - const filterOfCurrentId = pageState.filters.get(filterId) ?? { + const filterOfCurrentId = draftPageState.filters.get(filterId) ?? { datasetFilter: { locationFilters: {}, dateFilters: {}, @@ -47,7 +48,7 @@ export function CompareSideBySidePageStateSelector({ filterOfCurrentId.variantFilter, ), }; - }, [pageState, filterId, view.pageStateHandler]); + }, [draftPageState, filterId, view.pageStateHandler]); return (
@@ -60,8 +61,8 @@ export function CompareSideBySidePageStateSelector({ lapisFilter={currentLapisFilter} datasetFilter={filterOfCurrentId.datasetFilter} setDatasetFilter={(newDatasetFilter) => { - setPageState((previousState) => { - const updatedFilters = new Map(initialPageState.filters); + setDraftPageState((previousState) => { + const updatedFilters = new Map(pageState.filters); updatedFilters.set(filterId, { ...filterOfCurrentId, datasetFilter: newDatasetFilter, @@ -78,8 +79,8 @@ export function CompareSideBySidePageStateSelector({ { - setPageState((previousState) => { - const updatedFilters = new Map(initialPageState.filters); + setDraftPageState((previousState) => { + const updatedFilters = new Map(pageState.filters); updatedFilters.set(filterId, { ...filterOfCurrentId, variantFilter: newVariantFilter, @@ -96,7 +97,11 @@ export function CompareSideBySidePageStateSelector({
- +
); diff --git a/website/src/components/pageStateSelectors/CompareVariantsPageStateSelector.tsx b/website/src/components/pageStateSelectors/CompareVariantsPageStateSelector.tsx index cbfd106aa..0d03db9c8 100644 --- a/website/src/components/pageStateSelectors/CompareVariantsPageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/CompareVariantsPageStateSelector.tsx @@ -1,39 +1,38 @@ -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react'; import { ApplyFilterButton } from './ApplyFilterButton.tsx'; import { BaselineSelector } from './BaselineSelector.tsx'; import { SelectorHeadline } from './SelectorHeadline.tsx'; import { makeVariantFilterConfig } from './VariantSelector.tsx'; import { VariantsSelector } from './VariantsSelector.tsx'; -import { type OrganismsConfig } from '../../config.ts'; import { Inset } from '../../styles/Inset.tsx'; +import { GenericCompareVariantsView } from '../../views/BaseView.ts'; +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; import type { CompareVariantsData, Id, VariantFilter } from '../../views/View.ts'; -import { type OrganismViewKey, Routing } from '../../views/routing.ts'; -import type { compareVariantsViewKey } from '../../views/viewKeys.ts'; export function CompareVariantsPageStateSelector({ - organismViewKey, - organismsConfig, - initialPageState, + view, + pageState, + setPageState, enableAdvancedQueryFilter, }: { - organismViewKey: OrganismViewKey & `${string}.${typeof compareVariantsViewKey}`; - organismsConfig: OrganismsConfig; - initialPageState: CompareVariantsData; + view: GenericCompareVariantsView; + pageState: CompareVariantsData; + setPageState: Dispatch>; enableAdvancedQueryFilter: boolean; }) { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); - const [pageState, setPageState] = useState(initialPageState); + const [draftPageState, setDraftPageState] = useState(pageState); + useEffect(() => setDraftPageState(pageState), [pageState]); const variantFilterConfigs = useMemo(() => { return new Map( - pageState.variants.entries().map(([id]) => [id, makeVariantFilterConfig(view.organismConstants)]), + draftPageState.variants.entries().map(([id]) => [id, makeVariantFilterConfig(view.organismConstants)]), ); - }, [pageState.variants, view.organismConstants]); + }, [draftPageState.variants, view.organismConstants]); const currentLapisFilter = useMemo(() => { - return view.pageStateHandler.datasetFilterToLapisFilter(pageState.datasetFilter); - }, [pageState, view.pageStateHandler]); + return view.pageStateHandler.datasetFilterToLapisFilter(draftPageState.datasetFilter); + }, [draftPageState, view.pageStateHandler]); return (
@@ -43,9 +42,9 @@ export function CompareVariantsPageStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, datasetFilter: newDatasetFilter, })); @@ -58,10 +57,10 @@ export function CompareVariantsPageStateSelector({ Variant Filters ) => { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, variants: newVariantFilters, })); @@ -75,7 +74,8 @@ export function CompareVariantsPageStateSelector({
diff --git a/website/src/components/pageStateSelectors/CompareVariantsToBaselineStateSelector.tsx b/website/src/components/pageStateSelectors/CompareVariantsToBaselineStateSelector.tsx index 5aa4cb498..d57cb6bc5 100644 --- a/website/src/components/pageStateSelectors/CompareVariantsToBaselineStateSelector.tsx +++ b/website/src/components/pageStateSelectors/CompareVariantsToBaselineStateSelector.tsx @@ -1,29 +1,28 @@ -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react'; import { ApplyFilterButton } from './ApplyFilterButton.tsx'; import { BaselineSelector } from './BaselineSelector.tsx'; import { SelectorHeadline } from './SelectorHeadline.tsx'; import { makeVariantFilterConfig, VariantSelector } from './VariantSelector.tsx'; import { VariantsSelector } from './VariantsSelector.tsx'; -import { type OrganismsConfig } from '../../config.ts'; import { Inset } from '../../styles/Inset.tsx'; +import { GenericCompareToBaselineView } from '../../views/BaseView.ts'; +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; import type { CompareToBaselineData, Id, VariantFilter } from '../../views/View.ts'; -import { type OrganismViewKey, Routing } from '../../views/routing.ts'; -import { type compareToBaselineViewKey } from '../../views/viewKeys.ts'; export function CompareVariantsToBaselineStateSelector({ - organismViewKey, - organismsConfig, - initialPageState, + view, + pageState, + setPageState, enableAdvancedQueryFilter, }: { - organismViewKey: OrganismViewKey & `${string}.${typeof compareToBaselineViewKey}`; - organismsConfig: OrganismsConfig; - initialPageState: CompareToBaselineData; + view: GenericCompareToBaselineView; + pageState: CompareToBaselineData; + setPageState: Dispatch>; enableAdvancedQueryFilter: boolean; }) { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); - const [pageState, setPageState] = useState(initialPageState); + const [draftPageState, setDraftPageState] = useState(pageState); + useEffect(() => setDraftPageState(pageState), [pageState]); const variantFilterConfig = useMemo( () => makeVariantFilterConfig(view.organismConstants), @@ -31,12 +30,12 @@ export function CompareVariantsToBaselineStateSelector({ ); const variantFilterConfigs = useMemo(() => { - return new Map(pageState.variants.entries().map(([id]) => [id, { ...variantFilterConfig }])); - }, [pageState.variants, variantFilterConfig]); + return new Map(draftPageState.variants.entries().map(([id]) => [id, { ...variantFilterConfig }])); + }, [draftPageState.variants, variantFilterConfig]); const currentLapisFilter = useMemo(() => { - return view.pageStateHandler.baselineFilterToLapisFilter(pageState); - }, [pageState, view.pageStateHandler]); + return view.pageStateHandler.baselineFilterToLapisFilter(draftPageState); + }, [draftPageState, view.pageStateHandler]); return (
@@ -47,9 +46,9 @@ export function CompareVariantsToBaselineStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, datasetFilter: newDatasetFilter, })); @@ -64,13 +63,13 @@ export function CompareVariantsToBaselineStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, baselineFilter: newVariantFilter, })); }} variantFilterConfig={variantFilterConfig} - variantFilter={pageState.baselineFilter} + variantFilter={draftPageState.baselineFilter} lapisFilter={currentLapisFilter} enableAdvancedQueryFilter={enableAdvancedQueryFilter} /> @@ -81,10 +80,10 @@ export function CompareVariantsToBaselineStateSelector({ Variant Filters ) => { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, variants: newVariantFilters, })); @@ -98,7 +97,8 @@ export function CompareVariantsToBaselineStateSelector({
diff --git a/website/src/components/pageStateSelectors/SequencingEffortsPageStateSelector.tsx b/website/src/components/pageStateSelectors/SequencingEffortsPageStateSelector.tsx index ee58b8bca..e095837a5 100644 --- a/website/src/components/pageStateSelectors/SequencingEffortsPageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/SequencingEffortsPageStateSelector.tsx @@ -1,33 +1,31 @@ -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react'; import { ApplyFilterButton } from './ApplyFilterButton.tsx'; import { BaselineSelector } from './BaselineSelector.tsx'; import { LineageFilterInput } from './LineageFilterInput.tsx'; import { SelectorHeadline } from './SelectorHeadline.tsx'; -import { type OrganismsConfig } from '../../config.ts'; import { Inset } from '../../styles/Inset.tsx'; +import { GenericSequencingEffortsView } from '../../views/BaseView.ts'; +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; import type { DatasetAndVariantData } from '../../views/View.ts'; -import { type OrganismViewKey, Routing } from '../../views/routing.ts'; -import type { sequencingEffortsViewKey } from '../../views/viewKeys.ts'; export function SequencingEffortsPageStateSelector({ - organismViewKey, - organismsConfig, - initialPageState, + view, + pageState, + setPageState, enableAdvancedQueryFilter, }: { - organismViewKey: OrganismViewKey & `${string}.${typeof sequencingEffortsViewKey}`; - organismsConfig: OrganismsConfig; - initialPageState: DatasetAndVariantData; + view: GenericSequencingEffortsView; + pageState: DatasetAndVariantData; + setPageState: Dispatch>; enableAdvancedQueryFilter: boolean; }) { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); - - const [pageState, setPageState] = useState(initialPageState); + const [draftPageState, setDraftPageState] = useState(pageState); + useEffect(() => setDraftPageState(pageState), [pageState]); const currentLapisFilter = useMemo(() => { - return view.pageStateHandler.toLapisFilter(pageState); - }, [pageState, view.pageStateHandler]); + return view.pageStateHandler.toLapisFilter(draftPageState); + }, [draftPageState, view.pageStateHandler]); return (
@@ -37,9 +35,9 @@ export function SequencingEffortsPageStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, datasetFilter: newDatasetFilter, })); @@ -50,7 +48,7 @@ export function SequencingEffortsPageStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, variantFilter: { lineages: { @@ -62,7 +60,7 @@ export function SequencingEffortsPageStateSelector({ }} key={lineageFilterConfig.lapisField} lapisFilter={currentLapisFilter} - value={pageState.variantFilter.lineages?.[lineageFilterConfig.lapisField]} + value={draftPageState.variantFilter.lineages?.[lineageFilterConfig.lapisField]} /> ))} @@ -71,7 +69,8 @@ export function SequencingEffortsPageStateSelector({
diff --git a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx index b3e4d6b0f..7383bf648 100644 --- a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx +++ b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx @@ -29,7 +29,7 @@ describe('SingleVariantPageStateSelector', () => { , diff --git a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.tsx b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.tsx index d9a75c966..cc40f5e0a 100644 --- a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.tsx @@ -1,38 +1,38 @@ -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react'; import { ApplyFilterButton } from './ApplyFilterButton.tsx'; import { BaselineSelector } from './BaselineSelector.tsx'; import { SelectorHeadline } from './SelectorHeadline.tsx'; import { makeVariantFilterConfig, VariantSelector } from './VariantSelector.tsx'; -import { type OrganismsConfig } from '../../config.ts'; import { Inset } from '../../styles/Inset.tsx'; +import type { GenericSingleVariantView } from '../../views/BaseView.ts'; +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; import { type DatasetAndVariantData } from '../../views/View.ts'; -import { type OrganismViewKey, Routing } from '../../views/routing.ts'; -import type { singleVariantViewKey } from '../../views/viewKeys.ts'; export function SingleVariantPageStateSelector({ - organismViewKey, - organismsConfig, - initialPageState, + view, + pageState, + setPageState, enableAdvancedQueryFilter, }: { - organismViewKey: OrganismViewKey & `${string}.${typeof singleVariantViewKey}`; - organismsConfig: OrganismsConfig; - initialPageState: DatasetAndVariantData; + view: GenericSingleVariantView; + pageState: DatasetAndVariantData; + setPageState: Dispatch>; enableAdvancedQueryFilter: boolean; }) { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); const variantFilterConfig = useMemo( () => makeVariantFilterConfig(view.organismConstants), [view.organismConstants], ); - const [pageState, setPageState] = useState(initialPageState); + + const [draftPageState, setDraftPageState] = useState(pageState); + useEffect(() => setDraftPageState(pageState), [pageState]); const baselineFilterConfigs = view.organismConstants.baselineFilterConfigs; const currentLapisFilter = useMemo(() => { - return view.pageStateHandler.toLapisFilter(pageState); - }, [pageState, view.pageStateHandler]); + return view.pageStateHandler.toLapisFilter(draftPageState); + }, [draftPageState, view.pageStateHandler]); return (
@@ -42,9 +42,9 @@ export function SingleVariantPageStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, datasetFilter: newDatasetFilter, })); @@ -59,13 +59,13 @@ export function SingleVariantPageStateSelector({ { - setPageState((previousState) => ({ + setDraftPageState((previousState) => ({ ...previousState, variantFilter: newVariantFilter, })); }} variantFilterConfig={variantFilterConfig} - variantFilter={pageState.variantFilter} + variantFilter={draftPageState.variantFilter} lapisFilter={currentLapisFilter} enableAdvancedQueryFilter={enableAdvancedQueryFilter} /> @@ -76,7 +76,8 @@ export function SingleVariantPageStateSelector({
diff --git a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx index 0af04924c..84ccb4ea8 100644 --- a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx +++ b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx @@ -1,15 +1,12 @@ import { useQuery } from '@tanstack/react-query'; -import { useMemo, useState } from 'react'; +import { type Dispatch, type SetStateAction, useState } from 'react'; import { z } from 'zod'; import { getClientLogger } from '../../../clientLogger.ts'; -import type { OrganismsConfig } from '../../../config.ts'; -import { type CovidVariantData } from '../../../views/covid.ts'; -import { Routing } from '../../../views/routing.ts'; +import { CovidAnalyzeSingleVariantView, type CovidVariantData } from '../../../views/covid.ts'; import { useErrorToast } from '../../ErrorReportInstruction.tsx'; -import { withQueryProvider } from '../../subscriptions/backendApi/withQueryProvider.tsx'; -export const collectionVariantClassName = 'border bg-white px-4 py-2 hover:bg-cyan border-gray-200'; +export const collectionVariantClassName = 'border bg-white px-4 py-2 hover:bg-cyan border-gray-200 cursor-pointer'; type CollectionVariant = { name: string; @@ -25,12 +22,12 @@ type Collection = { type CollectionsListProps = { initialCollectionId?: number; - organismsConfig: OrganismsConfig; + view: CovidAnalyzeSingleVariantView; + pageState: CovidVariantData; + setPageState: Dispatch>; }; -export const CollectionsList = withQueryProvider(CollectionsListInner); - -function CollectionsListInner({ initialCollectionId, organismsConfig }: CollectionsListProps) { +export function CollectionsList({ initialCollectionId, view, pageState, setPageState }: CollectionsListProps) { const [selectedCollectionId, setSelectedCollectionId] = useState(initialCollectionId ?? 1); const query = useQuery({ @@ -56,7 +53,9 @@ function CollectionsListInner({ initialCollectionId, organismsConfig }: Collecti /> c.id === selectedCollectionId) ?? query.data[0]} - organismsConfig={organismsConfig} + view={view} + pageState={pageState} + setPageState={setPageState} /> ); @@ -96,10 +95,12 @@ const querySchema = z.object({ type CollectionVariantListProps = { collection: Collection; - organismsConfig: OrganismsConfig; + view: CovidAnalyzeSingleVariantView; + pageState: CovidVariantData; + setPageState: Dispatch>; }; -function CollectionVariantList({ collection, organismsConfig }: CollectionVariantListProps) { +function CollectionVariantList({ collection, view, pageState, setPageState }: CollectionVariantListProps) { const variants = collection.variants; return ( @@ -108,8 +109,10 @@ function CollectionVariantList({ collection, organismsConfig }: CollectionVarian ))} @@ -118,32 +121,40 @@ function CollectionVariantList({ collection, organismsConfig }: CollectionVarian type VariantLinkProps = { variant: CollectionVariant; - organismsConfig: OrganismsConfig; + view: CovidAnalyzeSingleVariantView; collectionId: number; + pageState: CovidVariantData; + setPageState: Dispatch>; }; -function VariantLink({ variant, organismsConfig, collectionId }: VariantLinkProps) { - const variantLink = useVariantLink(organismsConfig, collectionId, variant); +function VariantLink({ variant, view, collectionId, pageState, setPageState }: VariantLinkProps) { + const newPageState = useVariantLink(pageState, collectionId, variant); + + const applyFilters = () => { + if (newPageState === undefined) { + return; + } + + window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); + setPageState(newPageState); + }; return ( - + ); } const logger = getClientLogger('CollectionList'); -function useVariantLink(organismsConfig: OrganismsConfig, collectionId: number, variant: CollectionVariant) { - const routing = useMemo(() => new Routing(organismsConfig), [organismsConfig]); - +function useVariantLink( + currentPageState: CovidVariantData, + collectionId: number, + variant: CollectionVariant, +): CovidVariantData | undefined { const { showErrorToast } = useErrorToast(logger); - const currentPageState = routing - .getOrganismView('covid.singleVariantView') - .pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)); - let newPageState: CovidVariantData; - const queryParseResult = querySchema.safeParse(JSON.parse(variant.query)); if (!queryParseResult.success) { @@ -158,7 +169,7 @@ function useVariantLink(organismsConfig: OrganismsConfig, collectionId: number, const query = queryParseResult.data; if (query.variantQuery !== undefined) { - newPageState = { + return { ...currentPageState, collectionId: collectionId, variantFilter: { @@ -168,7 +179,7 @@ function useVariantLink(organismsConfig: OrganismsConfig, collectionId: number, }, }; } else { - newPageState = { + return { ...currentPageState, collectionId: collectionId, variantFilter: { @@ -184,5 +195,4 @@ function useVariantLink(organismsConfig: OrganismsConfig, collectionId: number, }, }; } - return routing.getOrganismView('covid.singleVariantView').pageStateHandler.toUrl(newPageState); } diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx index b07241b50..529c2074a 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx @@ -1,17 +1,13 @@ import { views } from '@genspectrum/dashboard-components/util'; -import { type FC, useMemo } from 'react'; +import { type Dispatch, type FC, type SetStateAction } from 'react'; import { CollectionsList } from './CollectionsList.tsx'; import { SelectVariant } from './SelectVariant.tsx'; -import type { OrganismsConfig } from '../../../config.ts'; -import type { Organisms } from '../../../types/Organism.ts'; import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts'; import { hasOnlyUndefinedValues } from '../../../util/hasOnlyUndefinedValues.ts'; -import type { CovidVariantData } from '../../../views/covid.ts'; +import { CovidAnalyzeSingleVariantView, type CovidVariantData } from '../../../views/covid.ts'; import { getLocationSubdivision } from '../../../views/locationHelpers.ts'; import { locationFieldsToFilterIdentifier } from '../../../views/pageStateHandlers/locationFilterFromToUrl.ts'; -import { Routing } from '../../../views/routing.ts'; -import type { singleVariantViewKey } from '../../../views/viewKeys.ts'; import { ComponentsGrid } from '../../ComponentsGrid.tsx'; import { GsAggregate } from '../../genspectrum/GsAggregate.tsx'; import { GsMutations } from '../../genspectrum/GsMutations.tsx'; @@ -21,18 +17,16 @@ import { GsRelativeGrowthAdvantage } from '../../genspectrum/GsRelativeGrowthAdv import { GsStatistics } from '../../genspectrum/GsStatistics.tsx'; export type CovidSingleVariantDataDisplayProps = { - organismViewKey: `${typeof Organisms.covid}.${typeof singleVariantViewKey}`; - organismsConfig: OrganismsConfig; + view: CovidAnalyzeSingleVariantView; pageState: CovidVariantData; + setPageState: Dispatch>; }; export const CovidSingleVariantDataDisplay: FC = ({ - organismViewKey, - organismsConfig, + view, pageState, + setPageState, }) => { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); - const variantFilter = view.pageStateHandler.toLapisFilter(pageState); const datasetLapisFilter = view.pageStateHandler.toLapisFilterWithoutVariant(pageState); const timeGranularity = chooseGranularityBasedOnDateRange({ @@ -55,7 +49,9 @@ export const CovidSingleVariantDataDisplay: FC diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx index 35b12bad5..7d27e4796 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { CollectionsList } from './CollectionsList.tsx'; import { CovidSingleVariantDataDisplay } from './CovidSingleVariantDataDisplay.tsx'; @@ -20,7 +20,9 @@ export const CovidSingleVariantReactPage: FC = const organismViewKey: OrganismViewKey = 'covid.singleVariantView'; const view = useMemo(() => new Routing(organismsConfig).getOrganismView(organismViewKey), [organismsConfig]); - const pageState = view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)); + const [pageState, setPageState] = useState(() => + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const variantFilter = view.pageStateHandler.toLapisFilter(pageState); @@ -51,9 +53,9 @@ export const CovidSingleVariantReactPage: FC = filters={ <>
@@ -61,17 +63,15 @@ export const CovidSingleVariantReactPage: FC = Collections } dataDisplay={ - + } /> ); diff --git a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantDataDisplay.tsx b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantDataDisplay.tsx index 8dbdf3865..0bd266e27 100644 --- a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantDataDisplay.tsx +++ b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantDataDisplay.tsx @@ -1,16 +1,15 @@ import { views } from '@genspectrum/dashboard-components/util'; -import { type FC, useMemo } from 'react'; +import { type Dispatch, type FC, type SetStateAction } from 'react'; import { QuickstartLinks } from './QuickstartLinks.tsx'; import { SelectVariant } from './SelectVariant.tsx'; -import type { OrganismsConfig } from '../../../config.ts'; import { Organisms } from '../../../types/Organism.ts'; import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts'; import { hasOnlyUndefinedValues } from '../../../util/hasOnlyUndefinedValues.ts'; import type { DatasetAndVariantData } from '../../../views/View.ts'; import { getLocationSubdivision } from '../../../views/locationHelpers.ts'; import { locationFieldsToFilterIdentifier } from '../../../views/pageStateHandlers/locationFilterFromToUrl.ts'; -import { type OrganismViewKey, Routing, type SingleVariantOrganism } from '../../../views/routing.ts'; +import { type SingleVariantOrganism, type ViewsMap } from '../../../views/routing.ts'; import type { singleVariantViewKey } from '../../../views/viewKeys.ts'; import { ComponentsGrid } from '../../ComponentsGrid.tsx'; import { GsAggregate } from '../../genspectrum/GsAggregate.tsx'; @@ -20,19 +19,16 @@ import { GsPrevalenceOverTime } from '../../genspectrum/GsPrevalenceOverTime.tsx import { GsStatistics } from '../../genspectrum/GsStatistics.tsx'; export type GenericAnalyseSingleVariantDataDisplayProps = { - organismViewKey: OrganismViewKey & - `${Exclude}.${typeof singleVariantViewKey}`; - organismsConfig: OrganismsConfig; + view: ViewsMap[Exclude][typeof singleVariantViewKey]; pageState: DatasetAndVariantData; + setPageState: Dispatch>; }; export const GenericAnalyseSingleVariantDataDisplay: FC = ({ - organismViewKey, - organismsConfig, + view, pageState, + setPageState, }) => { - const view = useMemo(() => new Routing(organismsConfig), [organismsConfig]).getOrganismView(organismViewKey); - const variantLapisFilter = view.pageStateHandler.toLapisFilter(pageState); const datasetLapisFilter = view.pageStateHandler.toLapisFilterWithoutVariant(pageState); const timeGranularity = chooseGranularityBasedOnDateRange({ @@ -52,7 +48,7 @@ export const GenericAnalyseSingleVariantDataDisplay: FC {noVariantSelected && ( - + )} diff --git a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx index 3a9c770f8..db13701ee 100644 --- a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { GenericAnalyseSingleVariantDataDisplay } from './GenericAnalyseSingleVariantDataDisplay'; import { type OrganismsConfig } from '../../../config'; @@ -28,7 +28,9 @@ export const GenericAnalyseSingleVariantReactPage: FC + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const variantLapisFilter = view.pageStateHandler.toLapisFilter(pageState); @@ -58,19 +60,13 @@ export const GenericAnalyseSingleVariantReactPage: FC - } - dataDisplay={ - } + dataDisplay={} /> ); }; diff --git a/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx b/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx index 9745d57d8..66a76288f 100644 --- a/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx +++ b/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx @@ -1,8 +1,8 @@ -import type { FC } from 'react'; +import type { Dispatch, FC, SetStateAction } from 'react'; import { collectionVariantClassName } from './CollectionsList'; import { Organisms } from '../../../types/Organism'; -import type { DatasetFilter } from '../../../views/View.ts'; +import type { DatasetAndVariantData, DatasetFilter } from '../../../views/View.ts'; import { toDisplayName } from '../../../views/pageStateHandlers/PageStateHandler'; import type { SingleVariantOrganism, ViewsMap } from '../../../views/routing'; import { singleVariantViewKey } from '../../../views/viewKeys'; @@ -10,9 +10,10 @@ import { singleVariantViewKey } from '../../../views/viewKeys'; export type QuickstartLinksProps = { view: ViewsMap[Exclude][typeof singleVariantViewKey]; datasetFilter: DatasetFilter; + setPageState: Dispatch>; }; -export const QuickstartLinks: FC = ({ view, datasetFilter }) => { +export const QuickstartLinks: FC = ({ view, datasetFilter, setPageState }) => { const variants = view.organismConstants.predefinedVariants; return ( @@ -21,13 +22,20 @@ export const QuickstartLinks: FC = ({ view, datasetFilter
{variants.map((variant) => { - const href = view.pageStateHandler.toUrl({ - datasetFilter: datasetFilter, - variantFilter: variant, - }); + const applyFilters = () => { + const newPageState = { + datasetFilter: datasetFilter, + variantFilter: variant, + }; + // TODO put this into a usePageState hook + window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); + setPageState(newPageState); + }; + + const displayName = toDisplayName(variant); return ( - - {toDisplayName(variant)} + + {displayName} ); })} diff --git a/website/src/components/views/compareSideBySide/GenericCompareSideBySideDataDisplay.tsx b/website/src/components/views/compareSideBySide/GenericCompareSideBySideDataDisplay.tsx index 53921b476..76e5da97c 100644 --- a/website/src/components/views/compareSideBySide/GenericCompareSideBySideDataDisplay.tsx +++ b/website/src/components/views/compareSideBySide/GenericCompareSideBySideDataDisplay.tsx @@ -1,36 +1,28 @@ -import { type FC, useMemo } from 'react'; +import { type FC } from 'react'; -import type { OrganismsConfig } from '../../../config.ts'; import { Organisms } from '../../../types/Organism.ts'; import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts'; -import { ComponentHeight } from '../../../views/OrganismConstants.ts'; -import type { DatasetAndVariantData } from '../../../views/View.ts'; +import { BaseView } from '../../../views/BaseView.ts'; +import { ComponentHeight, type OrganismConstants } from '../../../views/OrganismConstants.ts'; +import type { CompareSideBySideData, DatasetAndVariantData } from '../../../views/View.ts'; +import { CompareSideBySideStateHandler } from '../../../views/pageStateHandlers/CompareSideBySidePageStateHandler.ts'; import { toLapisFilterWithoutVariant } from '../../../views/pageStateHandlers/toLapisFilterWithoutVariant.ts'; -import { type OrganismWithViewKey, Routing } from '../../../views/routing.ts'; -import { compareSideBySideViewKey } from '../../../views/viewKeys.ts'; import { GsAggregate } from '../../genspectrum/GsAggregate.tsx'; import { GsMutations } from '../../genspectrum/GsMutations.tsx'; import { GsPrevalenceOverTime } from '../../genspectrum/GsPrevalenceOverTime.tsx'; import { GsRelativeGrowthAdvantage } from '../../genspectrum/GsRelativeGrowthAdvantage.tsx'; export type GenericCompareSideBySideDataDisplayProps = { - organismViewKey: `${OrganismWithViewKey}.${typeof compareSideBySideViewKey}`; - organismsConfig: OrganismsConfig; + view: BaseView; datasetAndVariantData: DatasetAndVariantData; hideMutationComponents?: boolean; }; export const GenericCompareSideBySideDataDisplay: FC = ({ - organismViewKey, - organismsConfig, + view, datasetAndVariantData, hideMutationComponents, }) => { - const view = useMemo( - () => new Routing(organismsConfig).getOrganismView(organismViewKey), - [organismsConfig, organismViewKey], - ); - const { datasetFilter, variantFilter } = datasetAndVariantData; const datasetLapisFilter = toLapisFilterWithoutVariant(datasetFilter, view.organismConstants.additionalFilters); diff --git a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx index 20e72eede..2c7007ce8 100644 --- a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx +++ b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { GenericCompareSideBySideDataDisplay } from './GenericCompareSideBySideDataDisplay.tsx'; import { toDownloadLink } from './toDownloadLink'; @@ -28,7 +28,9 @@ export const GenericCompareSideBySideReactPage: FC + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const downloadLinks = [...pageState.filters.entries()].map(toDownloadLink(view.pageStateHandler, organism)); @@ -57,17 +59,16 @@ export const GenericCompareSideBySideReactPage: FC )}
diff --git a/website/src/components/views/compareToBaseline/GenericCompareToBaselineDataDisplay.tsx b/website/src/components/views/compareToBaseline/GenericCompareToBaselineDataDisplay.tsx index 39dc1bb01..f2f7163ee 100644 --- a/website/src/components/views/compareToBaseline/GenericCompareToBaselineDataDisplay.tsx +++ b/website/src/components/views/compareToBaseline/GenericCompareToBaselineDataDisplay.tsx @@ -1,31 +1,19 @@ -import { type FC, useMemo } from 'react'; +import { type FC } from 'react'; import { SelectBaseline } from './SelectBaseline.tsx'; -import type { OrganismsConfig } from '../../../config.ts'; import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts'; -import { ComponentHeight } from '../../../views/OrganismConstants.ts'; +import { GenericCompareToBaselineView } from '../../../views/BaseView.ts'; +import { ComponentHeight, type OrganismConstants } from '../../../views/OrganismConstants.ts'; import type { CompareToBaselineData } from '../../../views/View.ts'; -import { type OrganismWithViewKey, Routing } from '../../../views/routing.ts'; -import { compareToBaselineViewKey } from '../../../views/viewKeys.ts'; import { ComponentsGrid } from '../../ComponentsGrid.tsx'; import { GsPrevalenceOverTime } from '../../genspectrum/GsPrevalenceOverTime.tsx'; export type GenericCompareToBaselineDisplayProps = { - organismViewKey: `${OrganismWithViewKey}.${typeof compareToBaselineViewKey}`; - organismsConfig: OrganismsConfig; + view: GenericCompareToBaselineView; pageState: CompareToBaselineData; }; -export const GenericCompareToBaselineDataDisplay: FC = ({ - organismViewKey, - organismsConfig, - pageState, -}) => { - const view = useMemo( - () => new Routing(organismsConfig).getOrganismView(organismViewKey), - [organismsConfig, organismViewKey], - ); - +export const GenericCompareToBaselineDataDisplay: FC = ({ view, pageState }) => { const baselineLapisFilter = view.pageStateHandler.baselineFilterToLapisFilter(pageState); const timeGranularity = chooseGranularityBasedOnDateRange({ earliestDate: new Date(view.organismConstants.earliestDate), diff --git a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx index b1afb1c0b..9a9498610 100644 --- a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx +++ b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { GenericCompareToBaselineDataDisplay } from './GenericCompareToBaselineDataDisplay'; import { type OrganismsConfig } from '../../../config'; @@ -25,7 +25,9 @@ export const GenericCompareToBaselineReactPage: FC + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const numeratorLapisFilters = view.pageStateHandler.variantFiltersToNamedLapisFilters(pageState); const noVariantSelected = pageState.variants.size < 1; @@ -45,16 +47,15 @@ export const GenericCompareToBaselineReactPage: FC } dataDisplay={ } diff --git a/website/src/components/views/compareVariants/GenericCompareVariantsDataDisplay.tsx b/website/src/components/views/compareVariants/GenericCompareVariantsDataDisplay.tsx index 2b57434cd..6610477b3 100644 --- a/website/src/components/views/compareVariants/GenericCompareVariantsDataDisplay.tsx +++ b/website/src/components/views/compareVariants/GenericCompareVariantsDataDisplay.tsx @@ -1,33 +1,22 @@ -import { type FC, useMemo } from 'react'; +import { type FC } from 'react'; import { SelectVariants } from './SelectVariants.tsx'; -import type { OrganismsConfig } from '../../../config.ts'; import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts'; +import { GenericCompareVariantsView } from '../../../views/BaseView.ts'; +import type { OrganismConstants } from '../../../views/OrganismConstants.ts'; import type { CompareVariantsData } from '../../../views/View.ts'; -import { type OrganismWithViewKey, Routing } from '../../../views/routing.ts'; -import { compareVariantsViewKey } from '../../../views/viewKeys.ts'; import { ComponentsGrid } from '../../ComponentsGrid.tsx'; import { GsMutationComparison } from '../../genspectrum/GsMutationComparison.tsx'; import { GsPrevalenceOverTime } from '../../genspectrum/GsPrevalenceOverTime.tsx'; export type GenericCompareVariantsDataDisplayProps = { - organismViewKey: `${OrganismWithViewKey}.${typeof compareVariantsViewKey}`; - organismsConfig: OrganismsConfig; + view: GenericCompareVariantsView; pageState: CompareVariantsData; }; const componentHeight = '540px'; // prevalence over time table with 10 rows -export const GenericCompareVariantsDataDisplay: FC = ({ - organismViewKey, - organismsConfig, - pageState, -}) => { - const view = useMemo( - () => new Routing(organismsConfig).getOrganismView(organismViewKey), - [organismsConfig, organismViewKey], - ); - +export const GenericCompareVariantsDataDisplay: FC = ({ view, pageState }) => { const datasetLapisFilter = view.pageStateHandler.datasetFilterToLapisFilter(pageState.datasetFilter); const timeGranularity = chooseGranularityBasedOnDateRange({ earliestDate: new Date(view.organismConstants.earliestDate), diff --git a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx index d1b970481..5e48741bf 100644 --- a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx +++ b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { GenericCompareVariantsDataDisplay } from './GenericCompareVariantsDataDisplay'; import { type OrganismsConfig } from '../../../config'; @@ -25,7 +25,9 @@ export const GenericCompareVariantsReactPage: FC + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const numeratorLapisFilters = view.pageStateHandler.variantFiltersToNamedLapisFilters(pageState); const notEnoughVariantsSelected = pageState.variants.size < 2; @@ -45,19 +47,13 @@ export const GenericCompareVariantsReactPage: FC - } - dataDisplay={ - } + dataDisplay={} /> ); }; diff --git a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx index 8dea17a2e..979e64205 100644 --- a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx +++ b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo } from 'react'; +import { type FC, useMemo, useState } from 'react'; import { GenericSequencingEffortsDataDisplay } from './GenericSequencingEffortsDataDisplay'; import { type OrganismsConfig } from '../../../config'; @@ -24,7 +24,9 @@ export const GenericSequencingEffortsReactPage: FC + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); const lapisFilter = view.pageStateHandler.toLapisFilter(pageState); @@ -43,9 +45,9 @@ export const GenericSequencingEffortsReactPage: FC } diff --git a/website/src/views/OrganismConstants.ts b/website/src/views/OrganismConstants.ts index e3c32330c..00d1b71ac 100644 --- a/website/src/views/OrganismConstants.ts +++ b/website/src/views/OrganismConstants.ts @@ -24,6 +24,7 @@ type AggregatedVisualizations = { export interface OrganismConstants { readonly organism: Organism; + readonly earliestDate : string; readonly dataOrigins: DataOrigin[]; readonly accessionDownloadFields: string[]; readonly mainDateField: string; diff --git a/website/src/views/pageStateHandlers/CompareSideBySidePageStateHandler.spec.ts b/website/src/views/pageStateHandlers/CompareSideBySidePageStateHandler.spec.ts index a369acc58..99e178fb6 100644 --- a/website/src/views/pageStateHandlers/CompareSideBySidePageStateHandler.spec.ts +++ b/website/src/views/pageStateHandlers/CompareSideBySidePageStateHandler.spec.ts @@ -9,6 +9,7 @@ const mockDateRangeOption = { label: 'Last 7 Days', dateFrom: '2024-11-22', date const mockConstants: OrganismConstants = { organism: Organisms.covid, + earliestDate: 'earliestDate', dataOrigins: [], locationFields: ['country', 'region'], mainDateField: 'date', diff --git a/website/src/views/pageStateHandlers/CompareToBaselinePageStateHandler.spec.ts b/website/src/views/pageStateHandlers/CompareToBaselinePageStateHandler.spec.ts index 174923786..29f131f63 100644 --- a/website/src/views/pageStateHandlers/CompareToBaselinePageStateHandler.spec.ts +++ b/website/src/views/pageStateHandlers/CompareToBaselinePageStateHandler.spec.ts @@ -9,6 +9,7 @@ const mockDateRangeOption = { label: 'Last 7 Days', dateFrom: '2024-11-22', date const mockConstants: OrganismConstants = { organism: Organisms.covid, + earliestDate: 'earliestDate', dataOrigins: [], locationFields: ['country', 'region'], mainDateField: 'date', diff --git a/website/src/views/pageStateHandlers/CompareVariantsPageStateHandler.spec.ts b/website/src/views/pageStateHandlers/CompareVariantsPageStateHandler.spec.ts index 5d05f6de0..fbdce1b4f 100644 --- a/website/src/views/pageStateHandlers/CompareVariantsPageStateHandler.spec.ts +++ b/website/src/views/pageStateHandlers/CompareVariantsPageStateHandler.spec.ts @@ -9,6 +9,7 @@ const mockDateRangeOption = { label: 'Last 7 Days', dateFrom: '2024-11-22', date const mockConstants: OrganismConstants = { organism: Organisms.covid, + earliestDate: 'earliestDate', dataOrigins: [], locationFields: ['country', 'region'], mainDateField: 'date', diff --git a/website/src/views/pageStateHandlers/SingleVariantPageStateHandler.spec.ts b/website/src/views/pageStateHandlers/SingleVariantPageStateHandler.spec.ts index cc9f7ce34..785986390 100644 --- a/website/src/views/pageStateHandlers/SingleVariantPageStateHandler.spec.ts +++ b/website/src/views/pageStateHandlers/SingleVariantPageStateHandler.spec.ts @@ -9,6 +9,7 @@ const mockDateRangeOption = { label: 'Last 7 Days', dateFrom: '2024-11-22', date const mockConstants: OrganismConstants = { organism: Organisms.covid, + earliestDate: 'earliestDate', dataOrigins: [], locationFields: ['country', 'region'], mainDateField: 'date', From ec70c106a179ab032fa93203197a1288a7a17b5a Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 17:37:46 +0100 Subject: [PATCH 2/7] 984 wip --- .../CovidSingleVariantReactPage.tsx | 7 ++-- .../GenericAnalyseSingleVariantReactPage.tsx | 11 ++++--- website/src/components/views/usePageState.ts | 32 +++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 website/src/components/views/usePageState.ts diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx index 7d27e4796..85f3cb2f7 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { CollectionsList } from './CollectionsList.tsx'; import { CovidSingleVariantDataDisplay } from './CovidSingleVariantDataDisplay.tsx'; @@ -10,6 +10,7 @@ import { type OrganismViewKey, Routing } from '../../../views/routing.ts'; import { SelectorHeadline } from '../../pageStateSelectors/SelectorHeadline.tsx'; import { SingleVariantPageStateSelector } from '../../pageStateSelectors/SingleVariantPageStateSelector.tsx'; import { sanitizeForFilename } from '../compareSideBySide/toDownloadLink.ts'; +import { usePageState } from '../usePageState.ts'; export type CovidSingleVariantReactPageProps = { organismsConfig: OrganismsConfig; @@ -20,9 +21,7 @@ export const CovidSingleVariantReactPage: FC = const organismViewKey: OrganismViewKey = 'covid.singleVariantView'; const view = useMemo(() => new Routing(organismsConfig).getOrganismView(organismViewKey), [organismsConfig]); - const [pageState, setPageState] = useState(() => - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const variantFilter = view.pageStateHandler.toLapisFilter(pageState); diff --git a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx index db13701ee..0d187efb5 100644 --- a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { GenericAnalyseSingleVariantDataDisplay } from './GenericAnalyseSingleVariantDataDisplay'; import { type OrganismsConfig } from '../../../config'; @@ -10,6 +10,7 @@ import { type OrganismViewKey, type OrganismWithViewKey, Routing } from '../../. import { singleVariantViewKey } from '../../../views/viewKeys'; import { SingleVariantPageStateSelector } from '../../pageStateSelectors/SingleVariantPageStateSelector'; import { sanitizeForFilename } from '../compareSideBySide/toDownloadLink'; +import { usePageState } from '../usePageState.ts'; export type GenericAnalyseSingleVariantReactPageProps = { organism: Exclude, typeof Organisms.covid>; @@ -28,9 +29,7 @@ export const GenericAnalyseSingleVariantReactPage: FC - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const variantLapisFilter = view.pageStateHandler.toLapisFilter(pageState); @@ -66,7 +65,9 @@ export const GenericAnalyseSingleVariantReactPage: FC } - dataDisplay={} + dataDisplay={ + + } /> ); }; diff --git a/website/src/components/views/usePageState.ts b/website/src/components/views/usePageState.ts new file mode 100644 index 000000000..1be3bddb4 --- /dev/null +++ b/website/src/components/views/usePageState.ts @@ -0,0 +1,32 @@ +import { type Dispatch, type SetStateAction, useCallback, useMemo, useState } from 'react'; + +import type { OrganismConstants } from '../../views/OrganismConstants.ts'; +import type { View } from '../../views/View.ts'; +import type { PageStateHandler } from '../../views/pageStateHandlers/PageStateHandler.ts'; + +export function usePageState< + PageState extends object, + V extends View>, +>(view: V): { pageState: PageState; setPageState: Dispatch> } { + // type PageState = V extends View> ? PS : never; + + const [pageState, setPageStateRaw] = useState(() => + view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + ); + + const setPageState: Dispatch> = useCallback( + (newPageStateOrUpdater) => { + setPageStateRaw((prevState) => { + const newPageState = + typeof newPageStateOrUpdater === 'function' + ? (newPageStateOrUpdater as (prevState: PageState) => PageState)(prevState) + : newPageStateOrUpdater; + window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); + return newPageState; + }); + }, + [view.pageStateHandler], + ); + + return useMemo(() => ({ pageState, setPageState }), [pageState, setPageState]); +} From 32dbbc2fef342ed6f85805362a3dbce1b26a638e Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 17:44:25 +0100 Subject: [PATCH 3/7] 984 wip --- .../pageStateSelectors/ApplyFilterButton.tsx | 1 - .../views/analyzeSingleVariant/CollectionsList.tsx | 13 +++---------- .../CovidSingleVariantDataDisplay.tsx | 1 - .../CovidSingleVariantReactPage.tsx | 1 - .../views/analyzeSingleVariant/QuickstartLinks.tsx | 7 ++----- .../GenericCompareSideBySideReactPage.tsx | 5 ++--- .../GenericCompareToBaselineReactPage.tsx | 5 ++--- .../GenericCompareVariantsReactPage.tsx | 5 ++--- .../GenericSequencingEffortsReactPage.tsx | 5 ++--- website/src/components/views/usePageState.ts | 11 +++++------ 10 files changed, 18 insertions(+), 36 deletions(-) diff --git a/website/src/components/pageStateSelectors/ApplyFilterButton.tsx b/website/src/components/pageStateSelectors/ApplyFilterButton.tsx index bb2bf41f7..d3d7b5736 100644 --- a/website/src/components/pageStateSelectors/ApplyFilterButton.tsx +++ b/website/src/components/pageStateSelectors/ApplyFilterButton.tsx @@ -22,7 +22,6 @@ export function ApplyFilterButton({ const urlTooLong = fullUrl.length > MAX_URL_LENGTH; const applyFilters = () => { - window.history.pushState(undefined, '', url); setPageState(newPageState); }; diff --git a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx index 84ccb4ea8..ab52556a4 100644 --- a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx +++ b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx @@ -22,12 +22,11 @@ type Collection = { type CollectionsListProps = { initialCollectionId?: number; - view: CovidAnalyzeSingleVariantView; pageState: CovidVariantData; setPageState: Dispatch>; }; -export function CollectionsList({ initialCollectionId, view, pageState, setPageState }: CollectionsListProps) { +export function CollectionsList({ initialCollectionId, pageState, setPageState }: CollectionsListProps) { const [selectedCollectionId, setSelectedCollectionId] = useState(initialCollectionId ?? 1); const query = useQuery({ @@ -53,7 +52,6 @@ export function CollectionsList({ initialCollectionId, view, pageState, setPageS /> c.id === selectedCollectionId) ?? query.data[0]} - view={view} pageState={pageState} setPageState={setPageState} /> @@ -95,12 +93,11 @@ const querySchema = z.object({ type CollectionVariantListProps = { collection: Collection; - view: CovidAnalyzeSingleVariantView; pageState: CovidVariantData; setPageState: Dispatch>; }; -function CollectionVariantList({ collection, view, pageState, setPageState }: CollectionVariantListProps) { +function CollectionVariantList({ collection, pageState, setPageState }: CollectionVariantListProps) { const variants = collection.variants; return ( @@ -109,7 +106,6 @@ function CollectionVariantList({ collection, view, pageState, setPageState }: Co >; }; -function VariantLink({ variant, view, collectionId, pageState, setPageState }: VariantLinkProps) { +function VariantLink({ variant, collectionId, pageState, setPageState }: VariantLinkProps) { const newPageState = useVariantLink(pageState, collectionId, variant); const applyFilters = () => { if (newPageState === undefined) { return; } - - window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); setPageState(newPageState); }; diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx index 529c2074a..8ee878318 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantDataDisplay.tsx @@ -49,7 +49,6 @@ export const CovidSingleVariantDataDisplay: FC diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx index 85f3cb2f7..4bd3c1ed9 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx @@ -62,7 +62,6 @@ export const CovidSingleVariantReactPage: FC = Collections diff --git a/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx b/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx index 66a76288f..86eac39e9 100644 --- a/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx +++ b/website/src/components/views/analyzeSingleVariant/QuickstartLinks.tsx @@ -23,13 +23,10 @@ export const QuickstartLinks: FC = ({ view, datasetFilter,
{variants.map((variant) => { const applyFilters = () => { - const newPageState = { + setPageState({ datasetFilter: datasetFilter, variantFilter: variant, - }; - // TODO put this into a usePageState hook - window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); - setPageState(newPageState); + }); }; const displayName = toDisplayName(variant); diff --git a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx index 2c7007ce8..ddf915ac7 100644 --- a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx +++ b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx @@ -7,6 +7,7 @@ import { OrganismViewPageLayout } from '../../../layouts/OrganismPage/OrganismVi import { type OrganismViewKey, type OrganismWithViewKey, Routing } from '../../../views/routing'; import { compareSideBySideViewKey } from '../../../views/viewKeys'; import { CompareSideBySidePageStateSelector } from '../../pageStateSelectors/CompareSideBySidePageStateSelector'; +import { usePageState } from '../usePageState.ts'; export type GenericCompareSideBySideReactPageProps = { organism: OrganismWithViewKey; @@ -28,9 +29,7 @@ export const GenericCompareSideBySideReactPage: FC - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const downloadLinks = [...pageState.filters.entries()].map(toDownloadLink(view.pageStateHandler, organism)); diff --git a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx index 9a9498610..177cbb7a4 100644 --- a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx +++ b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx @@ -7,6 +7,7 @@ import { type OrganismViewKey, type OrganismWithViewKey, Routing } from '../../. import { compareToBaselineViewKey } from '../../../views/viewKeys'; import { CompareVariantsToBaselineStateSelector } from '../../pageStateSelectors/CompareVariantsToBaselineStateSelector'; import { sanitizeForFilename } from '../compareSideBySide/toDownloadLink'; +import { usePageState } from '../usePageState.ts'; export type GenericCompareToBaselineReactPageProps = { organism: OrganismWithViewKey; @@ -25,9 +26,7 @@ export const GenericCompareToBaselineReactPage: FC - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const numeratorLapisFilters = view.pageStateHandler.variantFiltersToNamedLapisFilters(pageState); const noVariantSelected = pageState.variants.size < 1; diff --git a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx index 5e48741bf..2ff07c257 100644 --- a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx +++ b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx @@ -7,6 +7,7 @@ import { type OrganismViewKey, type OrganismWithViewKey, Routing } from '../../. import { compareVariantsViewKey } from '../../../views/viewKeys'; import { CompareVariantsPageStateSelector } from '../../pageStateSelectors/CompareVariantsPageStateSelector'; import { sanitizeForFilename } from '../compareSideBySide/toDownloadLink'; +import { usePageState } from '../usePageState.ts'; export type GenericCompareVariantsReactPageProps = { organism: OrganismWithViewKey; @@ -25,9 +26,7 @@ export const GenericCompareVariantsReactPage: FC - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const numeratorLapisFilters = view.pageStateHandler.variantFiltersToNamedLapisFilters(pageState); const notEnoughVariantsSelected = pageState.variants.size < 2; diff --git a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx index 979e64205..84602f018 100644 --- a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx +++ b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx @@ -6,6 +6,7 @@ import { SingleVariantOrganismPageLayout } from '../../../layouts/OrganismPage/S import { type OrganismViewKey, type OrganismWithViewKey, Routing } from '../../../views/routing'; import { sequencingEffortsViewKey } from '../../../views/viewKeys'; import { SequencingEffortsPageStateSelector } from '../../pageStateSelectors/SequencingEffortsPageStateSelector'; +import { usePageState } from '../usePageState.ts'; export type GenericSequencingEffortsReactPageProps = { organism: OrganismWithViewKey; @@ -24,9 +25,7 @@ export const GenericSequencingEffortsReactPage: FC - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), - ); + const { pageState, setPageState } = usePageState(view); const lapisFilter = view.pageStateHandler.toLapisFilter(pageState); diff --git a/website/src/components/views/usePageState.ts b/website/src/components/views/usePageState.ts index 1be3bddb4..926d681ab 100644 --- a/website/src/components/views/usePageState.ts +++ b/website/src/components/views/usePageState.ts @@ -5,13 +5,12 @@ import type { View } from '../../views/View.ts'; import type { PageStateHandler } from '../../views/pageStateHandlers/PageStateHandler.ts'; export function usePageState< - PageState extends object, - V extends View>, ->(view: V): { pageState: PageState; setPageState: Dispatch> } { - // type PageState = V extends View> ? PS : never; + V extends View>, +>(view: V) { + type PageState = V extends View> ? PS : never; - const [pageState, setPageStateRaw] = useState(() => - view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)), + const [pageState, setPageStateRaw] = useState( + () => view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)) as PageState, ); const setPageState: Dispatch> = useCallback( From 2f7c1cf35ddfa0a5a4c59fca21930bef6d1fab2d Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 17:57:49 +0100 Subject: [PATCH 4/7] 984 wip --- .../wasap/WasapPageStateSelector.tsx | 10 ++++++++-- .../CovidSingleVariantReactPage.tsx | 2 +- .../GenericAnalyseSingleVariantReactPage.tsx | 2 +- .../GenericCompareSideBySideReactPage.tsx | 2 +- .../GenericCompareToBaselineReactPage.tsx | 9 ++------- .../GenericCompareVariantsReactPage.tsx | 2 +- .../GenericSequencingEffortsReactPage.tsx | 2 +- website/src/components/views/usePageState.ts | 16 ++++++---------- website/src/components/views/wasap/Wasap.astro | 4 +--- website/src/components/views/wasap/WasapPage.tsx | 12 ++++++------ 10 files changed, 28 insertions(+), 33 deletions(-) diff --git a/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx b/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx index 811c21333..62d18f6de 100644 --- a/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx +++ b/website/src/components/pageStateSelectors/wasap/WasapPageStateSelector.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { useState } from 'react'; +import { type Dispatch, type SetStateAction, useState } from 'react'; import { ApplyFilterButton } from '../ApplyFilterButton'; import { DynamicDateFilter } from '../DynamicDateFilter'; @@ -34,11 +34,13 @@ export function WasapPageStateSelector({ pageStateHandler, initialBaseFilterState, initialAnalysisFilterState, + setPageState, }: { config: WasapPageConfig; pageStateHandler: PageStateHandler; initialBaseFilterState: WasapBaseFilter; initialAnalysisFilterState: WasapAnalysisFilter; + setPageState: Dispatch>; }) { const [baseFilterState, setBaseFilterState] = useState(initialBaseFilterState); @@ -201,7 +203,11 @@ export function WasapPageStateSelector({ } })()} - +
); } diff --git a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx index 4bd3c1ed9..438ea46b9 100644 --- a/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/CovidSingleVariantReactPage.tsx @@ -21,7 +21,7 @@ export const CovidSingleVariantReactPage: FC = const organismViewKey: OrganismViewKey = 'covid.singleVariantView'; const view = useMemo(() => new Routing(organismsConfig).getOrganismView(organismViewKey), [organismsConfig]); - const { pageState, setPageState } = usePageState(view); + const { pageState, setPageState } = usePageState(view.pageStateHandler); const variantFilter = view.pageStateHandler.toLapisFilter(pageState); diff --git a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx index 0d187efb5..477e9a5a8 100644 --- a/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx +++ b/website/src/components/views/analyzeSingleVariant/GenericAnalyseSingleVariantReactPage.tsx @@ -29,7 +29,7 @@ export const GenericAnalyseSingleVariantReactPage: FC } - dataDisplay={ - - } + dataDisplay={} /> ); }; diff --git a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx index 2ff07c257..7976a1851 100644 --- a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx +++ b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx @@ -26,7 +26,7 @@ export const GenericCompareVariantsReactPage: FC>, ->(view: V) { - type PageState = V extends View> ? PS : never; +export function usePageState>(pageStateHandler: StateHandler) { + type PageState = StateHandler extends PageStateHandler ? PS : never; const [pageState, setPageStateRaw] = useState( - () => view.pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)) as PageState, + () => pageStateHandler.parsePageStateFromUrl(new URL(window.location.href)) as PageState, ); const setPageState: Dispatch> = useCallback( @@ -18,13 +14,13 @@ export function usePageState< setPageStateRaw((prevState) => { const newPageState = typeof newPageStateOrUpdater === 'function' - ? (newPageStateOrUpdater as (prevState: PageState) => PageState)(prevState) + ? newPageStateOrUpdater(prevState) : newPageStateOrUpdater; - window.history.pushState(undefined, '', view.pageStateHandler.toUrl(newPageState)); + window.history.pushState(undefined, '', pageStateHandler.toUrl(newPageState)); return newPageState; }); }, - [view.pageStateHandler], + [pageStateHandler], ); return useMemo(() => ({ pageState, setPageState }), [pageState, setPageState]); diff --git a/website/src/components/views/wasap/Wasap.astro b/website/src/components/views/wasap/Wasap.astro index 4c24593d2..4535ed13f 100644 --- a/website/src/components/views/wasap/Wasap.astro +++ b/website/src/components/views/wasap/Wasap.astro @@ -9,10 +9,8 @@ type Props = { const { wastewaterOrganism } = Astro.props; const { name } = wastewaterOrganismConfigs[wastewaterOrganism]; - -const url = Astro.url; --- - + diff --git a/website/src/components/views/wasap/WasapPage.tsx b/website/src/components/views/wasap/WasapPage.tsx index 03f93c723..b829f9b86 100644 --- a/website/src/components/views/wasap/WasapPage.tsx +++ b/website/src/components/views/wasap/WasapPage.tsx @@ -21,20 +21,19 @@ import { WasapPageStateHandler } from '../../../views/pageStateHandlers/WasapPag import { GsMutationsOverTime } from '../../genspectrum/GsMutationsOverTime'; import { WasapPageStateSelector } from '../../pageStateSelectors/wasap/WasapPageStateSelector'; import { withQueryProvider } from '../../subscriptions/backendApi/withQueryProvider'; +import { usePageState } from '../usePageState.ts'; export type WasapPageProps = { wastewaterOrganism: WastewaterOrganismName; - currentUrl: URL; }; -export const WasapPageInner: FC = ({ wastewaterOrganism, currentUrl }) => { +export const WasapPageInner: FC = ({ wastewaterOrganism }) => { const config = wastewaterOrganismConfigs[wastewaterOrganism]; // initialize page state from the URL const pageStateHandler = useMemo(() => new WasapPageStateHandler(config), [config]); - const { base, analysis } = useMemo( - () => pageStateHandler.parsePageStateFromUrl(currentUrl), - [pageStateHandler, currentUrl], - ); + + const { pageState, setPageState } = usePageState(pageStateHandler); + const { base, analysis } = pageState; // fetch which mutations should be analyzed const { data, isPending, isError } = useWasapPageData(config, analysis); @@ -90,6 +89,7 @@ export const WasapPageInner: FC = ({ wastewaterOrganism, current pageStateHandler={pageStateHandler} initialBaseFilterState={base} initialAnalysisFilterState={analysis} + setPageState={setPageState} /> {isError ? ( From e2d12400defcacabde22d94e64a6c49d7301793b Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 18:17:12 +0100 Subject: [PATCH 5/7] 984 fix test --- ...eVariantPageStateSelector.browser.spec.tsx | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx index 7383bf648..ad9f50fa8 100644 --- a/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx +++ b/website/src/components/pageStateSelectors/SingleVariantPageStateSelector.browser.spec.tsx @@ -1,40 +1,51 @@ +import { act } from '@testing-library/react'; +import { useState } from 'react'; import { describe, expect } from 'vitest'; -import { render } from 'vitest-browser-react'; +import { render, renderHook } from 'vitest-browser-react'; import { SingleVariantPageStateSelector } from './SingleVariantPageStateSelector.tsx'; import { DUMMY_LAPIS_URL, testOrganismsConfig } from '../../../routeMocker.ts'; import { it } from '../../../test-extend'; +import type { CovidVariantData } from '../../views/covid.ts'; +import { Routing } from '../../views/routing.ts'; describe('SingleVariantPageStateSelector', () => { - it('should remember the covid collection id', ({ routeMockers }) => { + it('should remember the covid collection id', async ({ routeMockers }) => { routeMockers.lapis.mockPostAggregated({}, { data: [] }); routeMockers.lapis.mockReferenceGenome({ nucleotideSequences: [{ name: 'main', sequence: 'ATGC' }], genes: [], }); + routeMockers.lapis.mockLineageDefinition('nextcladePangoLineage', {}); - const initialPageState = { - datasetFilter: { - locationFilters: {}, - dateFilters: {}, - textFilters: {}, - numberFilters: {}, - }, - variantFilter: {}, - collectionId: 5, - }; + const { result } = renderHook(() => + useState({ + datasetFilter: { + locationFilters: {}, + dateFilters: {}, + textFilters: {}, + numberFilters: {}, + }, + variantFilter: {}, + collectionId: 5, + }), + ); const { getByRole } = render( , ); - expect(getByRole('button').element()).toHaveAttribute('href', expect.stringContaining('collectionId=5')); + await act(async () => { + await getByRole('button', { name: 'Apply filters' }).click(); + }); + + expect(result.current[0].collectionId).equals(5); }); }); From 7cb19d0c33cfc4466444e4bcf6d57d50f2bdbba4 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 18:34:33 +0100 Subject: [PATCH 6/7] 984 fix test --- .../ApplyFilterButton.browser.spec.tsx | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/website/src/components/pageStateSelectors/ApplyFilterButton.browser.spec.tsx b/website/src/components/pageStateSelectors/ApplyFilterButton.browser.spec.tsx index 128a0927c..4fd115d1c 100644 --- a/website/src/components/pageStateSelectors/ApplyFilterButton.browser.spec.tsx +++ b/website/src/components/pageStateSelectors/ApplyFilterButton.browser.spec.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import { act } from '@testing-library/react'; +import React, { useState } from 'react'; import { describe, expect } from 'vitest'; -import { render } from 'vitest-browser-react'; +import { render, renderHook, type RenderResult } from 'vitest-browser-react'; import { ApplyFilterButton } from './ApplyFilterButton'; import { it } from '../../../test-extend'; @@ -29,69 +30,91 @@ const urlTooLongMessage = /URL is too long/i; describe('ApplyFilterButton', () => { const handler = new DummyPageStateHandler(); - it('should render enabled link for short URL', () => { + it('should render enabled link for short URL', async () => { const shortState: DummyPageState = { data: 'short' }; - const { getByRole, container } = render( - , + const { result: state } = renderHook(() => useState({ data: 'initial' })); + + const { getByRole } = render( + , ); - const link = getByRole('button'); - expect(link.element()).toBeInTheDocument(); - expect(link.element()).toHaveAttribute('href', '/test?data=short'); - expect(container.textContent).not.toMatch(urlTooLongMessage); + await clickApply(getByRole); + + expect(state.current[0].data).toEqual('short'); }); - it('should render disabled span and error message for long URL', () => { + it('should render disabled span and error message for long URL', async () => { const longData = 'x'.repeat(2000); const longState: DummyPageState = { data: longData }; + const { result: state } = renderHook(() => useState({ data: 'initial' })); + const { getByRole, container } = render( - , + , ); - const button = getByRole('button'); - expect(button.element()).not.toHaveAttribute('href'); expect(container.textContent).toMatch(urlTooLongMessage); + expect(getByRole('button', { name: 'Apply filters' }).element()).not.toHaveAttribute('onClick'); + + await clickApply(getByRole); + expect(state.current[0].data).toEqual('initial'); }); - it('should update when state changes from short to long', () => { + it('should update when state changes from short to long', async () => { const shortState: DummyPageState = { data: 'short' }; + const { result: state } = renderHook(() => useState({ data: 'initial' })); + const { getByRole, container, rerender } = render( - , + , ); - const button = getByRole('button'); - expect(button.element()).toHaveAttribute('href', '/test?data=short'); + await clickApply(getByRole); + expect(state.current[0].data).toEqual('short'); expect(container.textContent).not.toMatch(urlTooLongMessage); const longData = 'x'.repeat(2000); const longState: DummyPageState = { data: longData }; - rerender(); + rerender( + , + ); - const updatedButton = getByRole('button'); - expect(updatedButton.element()).not.toHaveAttribute('href'); expect(container.textContent).toMatch(urlTooLongMessage); + expect(getByRole('button', { name: 'Apply filters' }).element()).not.toHaveAttribute('onClick'); + + await clickApply(getByRole); + expect(state.current[0].data).toEqual('short'); }); - it('should update when state changes from long to short', () => { + it('should update when state changes from long to short', async () => { const longData = 'x'.repeat(2000); const longState: DummyPageState = { data: longData }; + const { result: state } = renderHook(() => useState({ data: 'initial' })); + const { getByRole, container, rerender } = render( - , + , ); - const button = getByRole('button'); - expect(button.element()).not.toHaveAttribute('href'); + expect(getByRole('button', { name: 'Apply filters' }).element()).not.toHaveAttribute('onClick'); expect(container.textContent).toMatch(urlTooLongMessage); + await clickApply(getByRole); + expect(state.current[0].data).toEqual('initial'); const shortState: DummyPageState = { data: 'short' }; - rerender(); + rerender( + , + ); - const updatedButton = getByRole('button'); - expect(updatedButton.element()).toHaveAttribute('href', '/test?data=short'); expect(container.textContent).not.toMatch(urlTooLongMessage); + await clickApply(getByRole); + expect(state.current[0].data).toEqual('short'); }); }); + +async function clickApply(getByRole: RenderResult['getByRole']) { + await act(async () => { + await getByRole('button', { name: 'Apply filters' }).click(); + }); +} From 3ff9a1baee6fd511ccfcc30f78ec829357234d93 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 19 Dec 2025 18:36:06 +0100 Subject: [PATCH 7/7] 984 fix lints --- .../components/views/analyzeSingleVariant/CollectionsList.tsx | 4 ++-- .../compareSideBySide/GenericCompareSideBySideReactPage.tsx | 2 +- .../compareToBaseline/GenericCompareToBaselineReactPage.tsx | 2 +- .../views/compareVariants/GenericCompareVariantsReactPage.tsx | 2 +- .../sequencingEfforts/GenericSequencingEffortsReactPage.tsx | 2 +- website/src/views/OrganismConstants.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx index ab52556a4..bd6d4b1e9 100644 --- a/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx +++ b/website/src/components/views/analyzeSingleVariant/CollectionsList.tsx @@ -3,7 +3,7 @@ import { type Dispatch, type SetStateAction, useState } from 'react'; import { z } from 'zod'; import { getClientLogger } from '../../../clientLogger.ts'; -import { CovidAnalyzeSingleVariantView, type CovidVariantData } from '../../../views/covid.ts'; +import { type CovidVariantData } from '../../../views/covid.ts'; import { useErrorToast } from '../../ErrorReportInstruction.tsx'; export const collectionVariantClassName = 'border bg-white px-4 py-2 hover:bg-cyan border-gray-200 cursor-pointer'; @@ -26,7 +26,7 @@ type CollectionsListProps = { setPageState: Dispatch>; }; -export function CollectionsList({ initialCollectionId, pageState, setPageState }: CollectionsListProps) { +export function CollectionsList({ initialCollectionId, pageState, setPageState }: CollectionsListProps) { const [selectedCollectionId, setSelectedCollectionId] = useState(initialCollectionId ?? 1); const query = useQuery({ diff --git a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx index 62cbe4cd7..e4f3b5a2c 100644 --- a/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx +++ b/website/src/components/views/compareSideBySide/GenericCompareSideBySideReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { GenericCompareSideBySideDataDisplay } from './GenericCompareSideBySideDataDisplay.tsx'; import { toDownloadLink } from './toDownloadLink'; diff --git a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx index fa42c856d..b6b71b463 100644 --- a/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx +++ b/website/src/components/views/compareToBaseline/GenericCompareToBaselineReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { GenericCompareToBaselineDataDisplay } from './GenericCompareToBaselineDataDisplay'; import { type OrganismsConfig } from '../../../config'; diff --git a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx index 7976a1851..cffaa97fe 100644 --- a/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx +++ b/website/src/components/views/compareVariants/GenericCompareVariantsReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { GenericCompareVariantsDataDisplay } from './GenericCompareVariantsDataDisplay'; import { type OrganismsConfig } from '../../../config'; diff --git a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx index 1eba69582..865a06470 100644 --- a/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx +++ b/website/src/components/views/sequencingEfforts/GenericSequencingEffortsReactPage.tsx @@ -1,4 +1,4 @@ -import { type FC, useMemo, useState } from 'react'; +import { type FC, useMemo } from 'react'; import { GenericSequencingEffortsDataDisplay } from './GenericSequencingEffortsDataDisplay'; import { type OrganismsConfig } from '../../../config'; diff --git a/website/src/views/OrganismConstants.ts b/website/src/views/OrganismConstants.ts index 00d1b71ac..b6c349517 100644 --- a/website/src/views/OrganismConstants.ts +++ b/website/src/views/OrganismConstants.ts @@ -24,7 +24,7 @@ type AggregatedVisualizations = { export interface OrganismConstants { readonly organism: Organism; - readonly earliestDate : string; + readonly earliestDate: string; readonly dataOrigins: DataOrigin[]; readonly accessionDownloadFields: string[]; readonly mainDateField: string;