From 355d5a4e93ebf547b0f487b6f6a30064cd347377 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 28 Oct 2024 17:41:38 +0000 Subject: [PATCH 01/11] the very basics --- .../js/client/records/Sequences.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index 3589617aef..ac8187df57 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -17,6 +17,7 @@ import { PfamDomainArchitecture } from 'ortho-client/components/pfam-domains/Pfa import { extractPfamDomain } from 'ortho-client/records/utils'; import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; import { RowCounter } from '@veupathdb/coreui/lib/components/Mesa'; +import PopoverButton from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; import { PfamDomain } from 'ortho-client/components/pfam-domains/PfamDomain'; import { FloatingButton, @@ -303,12 +304,7 @@ export function RecordTable_Sequences( numSequences <= MAX_SEQUENCES_FOR_TREE && (tree == null || treeResponse == null)) ) { - return ( - <> -
Loading...
- - - ); // The loading spinner does not show :-( + return ; } if ( @@ -457,6 +453,21 @@ export function RecordTable_Sequences( /> ) : null; + const proteinFilter = ( + {}}> +
+ {highlightedNodes.length === 0 ? ( +

Select some proteins using the checkboxes in the table below

+ ) : ( +

+ You have {highlightedNodes.length.toLocaleString()} proteins + selected +

+ )} +
+
+ ); + const resetButton = ( Filters: + {proteinFilter} {pfamFilter} {corePeripheralFilter} {taxonFilter} From f5ee686cf841d802cdd88ba9abf39d899d0b51ae Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 28 Oct 2024 18:43:40 +0000 Subject: [PATCH 02/11] straw man ready --- .../js/client/records/Sequences.tsx | 91 +++++++++++++++++-- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index ac8187df57..026c3f1cc5 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -20,7 +20,9 @@ import { RowCounter } from '@veupathdb/coreui/lib/components/Mesa'; import PopoverButton from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; import { PfamDomain } from 'ortho-client/components/pfam-domains/PfamDomain'; import { + FilledButton, FloatingButton, + OutlinedButton, SelectList, Undo, useDeferredState, @@ -28,6 +30,7 @@ import { import { RecordTable_TaxonCounts_Filter } from './RecordTable_TaxonCounts_Filter'; import { formatAttributeValue } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; import { RecordFilter } from '@veupathdb/wdk-client/lib/Views/Records/RecordTable/RecordFilter'; +import { Button } from '@material-ui/core'; type RowType = Record; @@ -53,6 +56,9 @@ export function RecordTable_Sequences( const [resetCounter, setResetCounter] = useState(0); // used for forcing re-render of filter buttons + const [proteinFilterIds, setProteinFilterIds, volatileProteinFilterIds] = + useDeferredState([]); + const [selectedSpecies, setSelectedSpecies, volatileSelectedSpecies] = useDeferredState([]); @@ -218,9 +224,16 @@ export function RecordTable_Sequences( const speciesMatch = selectedSpecies.length === 0 || selectedSpecies.some((specie) => row.taxon_abbrev === specie); + const proteinMatch = + proteinFilterIds.length === 0 || + proteinFilterIds.some((proteinId) => rowFullId === proteinId); return ( - searchMatch && corePeripheralMatch && pfamIdMatch && speciesMatch + searchMatch && + corePeripheralMatch && + pfamIdMatch && + speciesMatch && + proteinMatch ); }); } @@ -234,6 +247,7 @@ export function RecordTable_Sequences( accessionToPfamIds, pfamFilterIds, selectedSpecies, + proteinFilterIds, ]); // now filter the tree if needed - takes a couple of seconds for large trees @@ -454,15 +468,76 @@ export function RecordTable_Sequences( ) : null; const proteinFilter = ( - {}}> -
+ 0 + ? ` (${volatileProteinFilterIds.length})` + : '' + }`} + key={volatileProteinFilterIds.join(':')} + > +
{highlightedNodes.length === 0 ? ( -

Select some proteins using the checkboxes in the table below

+ volatileProteinFilterIds.length === 0 ? ( +
+ Select some proteins using the checkboxes in the table below. +
+ ) : ( + <> +
+ You are filtering on{' '} + {volatileProteinFilterIds.length.toLocaleString()} proteins. +
+ { + setProteinFilterIds([]); + }} + /> + + ) + ) : volatileProteinFilterIds.length === 0 ? ( + <> +
+ You have checked {highlightedNodes.length.toLocaleString()}{' '} + proteins in the table. +
+ { + setProteinFilterIds(highlightedNodes); + setHighlightedNodes([]); + }} + /> + ) : ( -

- You have {highlightedNodes.length.toLocaleString()} proteins - selected -

+ <> +
+ You have checked {highlightedNodes.length.toLocaleString()}{' '} + proteins in the table and are already filtering on{' '} + {volatileProteinFilterIds.length.toLocaleString()} proteins. +
+ { + setProteinFilterIds(highlightedNodes); + setHighlightedNodes([]); + }} + /> + { + setProteinFilterIds([]); + }} + /> + )}
From fccec4c353ab017e9fdd5ae37c1c1ec4ac513dd5 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 28 Oct 2024 18:53:03 +0000 Subject: [PATCH 03/11] forgot the reset button --- .../webapp/wdkCustomization/js/client/records/Sequences.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index 026c3f1cc5..ee2a1f5978 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -551,13 +551,15 @@ export function RecordTable_Sequences( disabled={ pfamFilterIds.length + corePeripheralFilterValue.length + - selectedSpecies.length === + selectedSpecies.length + + proteinFilterIds.length === 0 } icon={Undo} size={'medium'} themeRole={'primary'} onPress={() => { + setProteinFilterIds([]); setPfamFilterIds([]); setCorePeripheralFilterValue([]); setSelectedSpecies([]); From ca400d50691dfe15287323ce0b5170caa2940c42 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 28 Oct 2024 19:01:54 +0000 Subject: [PATCH 04/11] DRYed up a bit --- .../js/client/records/Sequences.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index ee2a1f5978..a22da062d3 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -467,6 +467,20 @@ export function RecordTable_Sequences( /> ) : null; + const resetProteinFilterButton = ( + { + setProteinFilterIds([]); + }} + /> + ); + + const updateProteinFilterIds = () => { + setProteinFilterIds(highlightedNodes); + setHighlightedNodes([]); + }; + const proteinFilter = ( - { - setProteinFilterIds([]); - }} - /> + {resetProteinFilterButton} ) ) : volatileProteinFilterIds.length === 0 ? ( @@ -511,10 +520,7 @@ export function RecordTable_Sequences(
{ - setProteinFilterIds(highlightedNodes); - setHighlightedNodes([]); - }} + onPress={updateProteinFilterIds} /> ) : ( @@ -526,17 +532,9 @@ export function RecordTable_Sequences( { - setProteinFilterIds(highlightedNodes); - setHighlightedNodes([]); - }} - /> - { - setProteinFilterIds([]); - }} + onPress={updateProteinFilterIds} /> + {resetProteinFilterButton} )} From ab88a2ab0643f65d5d3106e5084f91d2a259687c Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 29 Oct 2024 15:40:25 +0000 Subject: [PATCH 05/11] fixed the edge case as described in PR, improved search box responsiveness --- .../js/client/records/Sequences.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index a22da062d3..a6e5b1dc0a 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -1,4 +1,10 @@ -import React, { CSSProperties, useCallback, useMemo, useState } from 'react'; +import React, { + CSSProperties, + useCallback, + useDeferredValue, + useMemo, + useState, +} from 'react'; import TreeTable from '@veupathdb/components/lib/components/tidytree/TreeTable'; import { RecordTableProps, WrappedComponentProps } from './Types'; import { useOrthoService } from 'ortho-client/hooks/orthoService'; @@ -30,7 +36,6 @@ import { import { RecordTable_TaxonCounts_Filter } from './RecordTable_TaxonCounts_Filter'; import { formatAttributeValue } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; import { RecordFilter } from '@veupathdb/wdk-client/lib/Views/Records/RecordTable/RecordFilter'; -import { Button } from '@material-ui/core'; type RowType = Record; @@ -49,9 +54,8 @@ export function RecordTable_Sequences( props: WrappedComponentProps ) { const [searchQuery, setSearchQuery] = useState(''); - const safeSearchRegexp = useMemo( - () => createSafeSearchRegExp(searchQuery), - [searchQuery] + const safeSearchRegexp = useDeferredValue( + useMemo(() => createSafeSearchRegExp(searchQuery), [searchQuery]) ); const [resetCounter, setResetCounter] = useState(0); // used for forcing re-render of filter buttons @@ -198,7 +202,7 @@ export function RecordTable_Sequences( const filteredRows = useMemo(() => { if ( - searchQuery !== '' || + safeSearchRegexp != null || corePeripheralFilterValue != null || pfamFilterIds.length > 0 || selectedSpecies.length > 0 @@ -211,7 +215,7 @@ export function RecordTable_Sequences( const rowPfamIdsSet = accessionToPfamIds.get(rowFullId); const searchMatch = - searchQuery === '' || + safeSearchRegexp == null || rowMatch(row, safeSearchRegexp, selectedColumnFilters); const corePeripheralMatch = corePeripheralFilterValue.length === 0 || @@ -239,7 +243,6 @@ export function RecordTable_Sequences( } return undefined; }, [ - searchQuery, selectedColumnFilters, safeSearchRegexp, sortedRows, @@ -496,6 +499,7 @@ export function RecordTable_Sequences( display: 'flex', flexDirection: 'column', gap: '1em', + maxWidth: '300px', }} > {highlightedNodes.length === 0 ? ( @@ -523,7 +527,7 @@ export function RecordTable_Sequences( onPress={updateProteinFilterIds} /> - ) : ( + ) : highlightedNodes.length < volatileProteinFilterIds.length ? ( <>
You have checked {highlightedNodes.length.toLocaleString()}{' '} @@ -536,6 +540,15 @@ export function RecordTable_Sequences( /> {resetProteinFilterButton} + ) : ( + <> +
+ You have checked all the proteins that are currently being + filtered on. Either uncheck one or more proteins or reset the + filter entirely using the button below. +
+ {resetProteinFilterButton} + )}
@@ -726,7 +739,8 @@ function rowMatch(row: RowType, query: RegExp, keys?: string[]): boolean { ); } -function createSafeSearchRegExp(input: string): RegExp { +function createSafeSearchRegExp(input: string): RegExp | undefined { + if (input === '') return undefined; try { // Attempt to create a RegExp from the user input directly return new RegExp(input, 'i'); From 2f60de757ec1f2c702f378167676dbc8c2c99626 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 29 Oct 2024 16:22:26 +0000 Subject: [PATCH 06/11] text search field selectors now more responsive --- .../wdkCustomization/js/client/records/Sequences.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index a6e5b1dc0a..33e3f3e995 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -196,9 +196,11 @@ export function RecordTable_Sequences( // 2. core-peripheral radio button // 3. checked boxes in the Pfam legend - const [selectedColumnFilters, setSelectedColumnFilters] = useState( - [] - ); + const [ + selectedColumnFilters, + setSelectedColumnFilters, + volatileSelectedColumnFilters, + ] = useDeferredState([]); const filteredRows = useMemo(() => { if ( @@ -626,7 +628,7 @@ export function RecordTable_Sequences( onSearchTermChange={setSearchQuery} recordDisplayName="Proteins" filterAttributes={filterAttributes} - selectedColumnFilters={selectedColumnFilters} + selectedColumnFilters={volatileSelectedColumnFilters} onColumnFilterChange={(keys) => setSelectedColumnFilters(keys)} />
From 72e4740374a5294b083a9e843a60f1b69befb1a9 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 31 Oct 2024 13:16:52 +0000 Subject: [PATCH 07/11] added asterisk and improved filter positioning --- .../wdkCustomization/js/client/records/Sequences.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index 33e3f3e995..ee26e8c24c 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -492,7 +492,7 @@ export function RecordTable_Sequences( volatileProteinFilterIds.length > 0 ? ` (${volatileProteinFilterIds.length})` : '' - }`} + }${highlightedNodes.length > 0 ? '*' : ''}`} key={volatileProteinFilterIds.join(':')} >
- You have checked {highlightedNodes.length.toLocaleString()}{' '} + * You have checked {highlightedNodes.length.toLocaleString()}{' '} proteins in the table.
- You have checked {highlightedNodes.length.toLocaleString()}{' '} + * You have checked {highlightedNodes.length.toLocaleString()}{' '} proteins in the table and are already filtering on{' '} {volatileProteinFilterIds.length.toLocaleString()} proteins.
@@ -648,7 +648,7 @@ export function RecordTable_Sequences( flexDirection: 'row', gap: '1em', alignItems: 'center', - justifyContent: 'flex-end', + marginLeft: 'auto', }} > Filters: From c22b905c57bb80d8cc845f25b8c0d8a4e57cebe7 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 31 Oct 2024 15:41:04 +0000 Subject: [PATCH 08/11] fixed broken logic with corePeripheral filter --- .../js/client/records/Sequences.tsx | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index ee26e8c24c..eb6c5f83ed 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -205,7 +205,7 @@ export function RecordTable_Sequences( const filteredRows = useMemo(() => { if ( safeSearchRegexp != null || - corePeripheralFilterValue != null || + corePeripheralFilterValue.length > 0 || pfamFilterIds.length > 0 || selectedSpecies.length > 0 ) { @@ -243,7 +243,7 @@ export function RecordTable_Sequences( ); }); } - return undefined; + return sortedRows; }, [ selectedColumnFilters, safeSearchRegexp, @@ -257,9 +257,15 @@ export function RecordTable_Sequences( // now filter the tree if needed - takes a couple of seconds for large trees const filteredTree = useMemo(() => { - if (leaves == null || tree == null || filteredRows?.length === 0) return; - - if (filteredRows != null && filteredRows.length < leaves.length) { + if ( + leaves == null || + tree == null || + filteredRows == null || + filteredRows.length === 0 + ) + return; + + if (filteredRows.length < leaves.length) { const filteredRowIds = new Set( filteredRows.map(({ full_id }) => full_id as string) ); @@ -286,16 +292,17 @@ export function RecordTable_Sequences( // make a newick string from the filtered tree if needed const finalNewick = useMemo(() => { - if (filteredTree === tree && treeResponse != null) { - return treeResponse; // no filtering so return what we read from the back end - } else if ( - filteredTree != null && - filteredRows != null && - filteredRows.length > 0 - ) { - return filteredTree.toNewick(); // make new newick data from the filtered tree - } else return; - }, [filteredTree, treeResponse, tree, filteredRows]); + if (treeResponse != null) { + if (filteredTree != null) { + if (filteredTree === tree) { + return treeResponse; // no filtering so return what we read from the back end + } else { + return filteredTree.toNewick(); // make new newick data from the filtered tree + } + } + } + return; + }, [filteredTree, treeResponse, tree]); // list of column keys and display names to show in the checkbox dropdown in the table text search box (RecordFilter) const filterAttributes = useMemo( From f6f91535250b9942a71d936aabf2ab99aebc98a7 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 31 Oct 2024 17:55:35 +0000 Subject: [PATCH 09/11] memoized rowsByAccession and mesaState and fixed some logic errors --- .../js/client/records/Sequences.tsx | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index eb6c5f83ed..4678e9289d 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -102,7 +102,10 @@ export function RecordTable_Sequences( // deal with Pfam domain architectures const proteinPfams = props.record.tables['ProteinPFams']; - const rowsByAccession = groupBy(proteinPfams, 'full_id'); + const rowsByAccession = useMemo( + () => groupBy(proteinPfams, 'full_id'), + [proteinPfams] + ); const accessionToPfamIds = useMemo( () => @@ -207,7 +210,8 @@ export function RecordTable_Sequences( safeSearchRegexp != null || corePeripheralFilterValue.length > 0 || pfamFilterIds.length > 0 || - selectedSpecies.length > 0 + selectedSpecies.length > 0 || + proteinFilterIds.length > 0 ) { return sortedRows?.filter((row) => { const rowCorePeripheral = ( @@ -324,7 +328,53 @@ export function RecordTable_Sequences( [setSelectedSpecies, setTablePageNumber] ); + const firstRowIndex = (tablePageNumber - 1) * MAX_SEQUENCES_FOR_TREE; + + const mesaState: MesaStateProps | undefined = useMemo(() => { + if (sortedRows == null) return; + return { + options: { + isRowSelected: (row: RowType) => + highlightedNodes.includes(row.full_id as string), + useStickyHeader: true, + tableBodyMaxHeight: 'calc(100vh - 200px)', // 200px accounts for header/footer + }, + uiState: { + pagination: { + currentPage: tablePageNumber, + rowsPerPage: MAX_SEQUENCES_FOR_TREE, + totalRows: filteredRows?.length ?? 0, + }, + }, + rows: sortedRows, + filteredRows: filteredRows?.slice( + firstRowIndex, + firstRowIndex + MAX_SEQUENCES_FOR_TREE + ), + columns: mesaColumns, + eventHandlers: { + onRowSelect: (row: RowType) => + setHighlightedNodes((prev) => [...prev, row.full_id as string]), + onRowDeselect: (row: RowType) => + setHighlightedNodes((prev) => + prev.filter((id) => id !== row.full_id) + ), + onPageChange: (page: number) => setTablePageNumber(page), + }, + }; + }, [ + sortedRows, + filteredRows, + highlightedNodes, + tablePageNumber, + firstRowIndex, + mesaColumns, + setHighlightedNodes, + setTablePageNumber, + ]); + if ( + !mesaState || !sortedRows || (numSequences >= MIN_SEQUENCES_FOR_TREE && numSequences <= MAX_SEQUENCES_FOR_TREE && @@ -358,45 +408,6 @@ export function RecordTable_Sequences( ); } - const firstRowIndex = (tablePageNumber - 1) * MAX_SEQUENCES_FOR_TREE; - - const mesaState: MesaStateProps = { - options: { - isRowSelected: (row: RowType) => - highlightedNodes.includes(row.full_id as string), - useStickyHeader: true, - tableBodyMaxHeight: 'calc(100vh - 200px)', // 200px accounts for header/footer - }, - uiState: { - pagination: { - currentPage: tablePageNumber, - rowsPerPage: MAX_SEQUENCES_FOR_TREE, - totalRows: filteredRows?.length ?? 0, - }, - }, - rows: sortedRows, - filteredRows: filteredRows?.slice( - firstRowIndex, - firstRowIndex + MAX_SEQUENCES_FOR_TREE - ), - columns: mesaColumns, - eventHandlers: { - onRowSelect: (row: RowType) => - setHighlightedNodes((prev) => [...prev, row.full_id as string]), - onRowDeselect: (row: RowType) => - setHighlightedNodes((prev) => prev.filter((id) => id !== row.full_id)), - onPageChange: (page: number) => setTablePageNumber(page), - }, - }; - - const treeProps = { - data: finalNewick, - width: treeWidth, - highlightMode: 'monophyletic' as const, - highlightColor, - highlightedNodeIds: highlightedNodes, - }; - const rowHeight = 45; const clustalDisabled = highlightedNodes == null || highlightedNodes.length < 2; @@ -540,7 +551,7 @@ export function RecordTable_Sequences( <>
* You have checked {highlightedNodes.length.toLocaleString()}{' '} - proteins in the table and are already filtering on{' '} + proteins in the table that is already filtered on{' '} {volatileProteinFilterIds.length.toLocaleString()} proteins.
MAX_SEQUENCES_FOR_TREE || From 337348e63a539aa3395e33cc763667555bc09874 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 31 Oct 2024 17:59:29 +0000 Subject: [PATCH 10/11] memoize treeProps just for tidiness --- .../js/client/records/Sequences.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index 4678e9289d..c76e0e8fac 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -373,6 +373,17 @@ export function RecordTable_Sequences( setTablePageNumber, ]); + const treeProps = useMemo( + () => ({ + data: finalNewick, + width: treeWidth, + highlightMode: 'monophyletic' as const, + highlightColor, + highlightedNodeIds: highlightedNodes, + }), + [finalNewick, treeWidth, highlightColor, highlightedNodes] + ); + if ( !mesaState || !sortedRows || @@ -687,13 +698,7 @@ export function RecordTable_Sequences( <> MAX_SEQUENCES_FOR_TREE || From 2520735de3c13a1d26678ae68e7c3f090527234c Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 31 Oct 2024 21:15:23 +0000 Subject: [PATCH 11/11] replaced the key hack with ref approach --- .../buttons/PopoverButton/PopoverButton.tsx | 190 ++++++++++-------- .../js/client/records/Sequences.tsx | 13 +- 2 files changed, 117 insertions(+), 86 deletions(-) diff --git a/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx b/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx index 3dfe53f36c..63cb4dbf04 100644 --- a/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx +++ b/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx @@ -1,4 +1,11 @@ -import { ReactNode, useState, useEffect, useMemo } from 'react'; +import { + ReactNode, + useState, + useEffect, + useMemo, + forwardRef, + useImperativeHandle, +} from 'react'; import { Popover } from '@material-ui/core'; import SwissArmyButton from '../SwissArmyButton'; import { gray } from '../../../definitions/colors'; @@ -66,6 +73,11 @@ const defaultStyle: ButtonStyleSpec = { }, }; +export interface PopoverButtonHandle { + /** Closes the popover */ + close: () => void; +} + export interface PopoverButtonProps { /** Contents of the menu when opened */ children: ReactNode; @@ -87,86 +99,96 @@ export interface PopoverButtonProps { /** * Renders a button that display `children` in a popover widget. */ -export default function PopoverButton(props: PopoverButtonProps) { - const { - children, - buttonDisplayContent, - onClose, - setIsPopoverOpen, - isDisabled = false, - styleOverrides = {}, - } = props; - const [anchorEl, setAnchorEl] = useState(null); - - const finalStyle = useMemo( - () => merge({}, defaultStyle, styleOverrides), - [styleOverrides] - ); - - const onCloseHandler = () => { - setAnchorEl(null); - onClose && onClose(); - }; - - useEffect(() => { - if (!setIsPopoverOpen) return; - if (anchorEl) { - setIsPopoverOpen(true); - } else { - setIsPopoverOpen(false); - } - }, [anchorEl, setIsPopoverOpen]); - - const menu = ( - - {children} - - ); - - const button = ( - setAnchorEl(event.currentTarget)} - disabled={isDisabled} - styleSpec={finalStyle} - icon={ArrowDown} - iconPosition="right" - additionalAriaProperties={{ - 'aria-controls': 'dropdown', - 'aria-haspopup': 'true', - }} - /> - ); - - return ( -
{ - // prevent click event from propagating to ancestor nodes - event.stopPropagation(); - }} - > - {button} - {menu} -
- ); -} + +const PopoverButton = forwardRef( + function PopoverButton(props, ref) { + const { + children, + buttonDisplayContent, + onClose, + setIsPopoverOpen, + isDisabled = false, + styleOverrides = {}, + } = props; + const [anchorEl, setAnchorEl] = useState(null); + + const finalStyle = useMemo( + () => merge({}, defaultStyle, styleOverrides), + [styleOverrides] + ); + + const onCloseHandler = () => { + setAnchorEl(null); + onClose && onClose(); + }; + + // Expose the `close()` method to external components via ref + useImperativeHandle(ref, () => ({ + close: onCloseHandler, + })); + + useEffect(() => { + if (!setIsPopoverOpen) return; + if (anchorEl) { + setIsPopoverOpen(true); + } else { + setIsPopoverOpen(false); + } + }, [anchorEl, setIsPopoverOpen]); + + const menu = ( + + {children} + + ); + + const button = ( + setAnchorEl(event.currentTarget)} + disabled={isDisabled} + styleSpec={finalStyle} + icon={ArrowDown} + iconPosition="right" + additionalAriaProperties={{ + 'aria-controls': 'dropdown', + 'aria-haspopup': 'true', + }} + /> + ); + + return ( +
{ + // prevent click event from propagating to ancestor nodes + event.stopPropagation(); + }} + > + {button} + {menu} +
+ ); + } +); + +export default PopoverButton; diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index c76e0e8fac..b584b3a12a 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useDeferredValue, useMemo, + useRef, useState, } from 'react'; import TreeTable from '@veupathdb/components/lib/components/tidytree/TreeTable'; @@ -23,7 +24,9 @@ import { PfamDomainArchitecture } from 'ortho-client/components/pfam-domains/Pfa import { extractPfamDomain } from 'ortho-client/records/utils'; import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; import { RowCounter } from '@veupathdb/coreui/lib/components/Mesa'; -import PopoverButton from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; +import PopoverButton, { + PopoverButtonHandle, +} from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; import { PfamDomain } from 'ortho-client/components/pfam-domains/PfamDomain'; import { FilledButton, @@ -384,6 +387,10 @@ export function RecordTable_Sequences( [finalNewick, treeWidth, highlightColor, highlightedNodes] ); + const proteinFilterButtonRef = useRef(null); + + // None shall pass! (hooks, at least) + if ( !mesaState || !sortedRows || @@ -505,24 +512,26 @@ export function RecordTable_Sequences( { + proteinFilterButtonRef.current?.close(); setProteinFilterIds([]); }} /> ); const updateProteinFilterIds = () => { + proteinFilterButtonRef.current?.close(); setProteinFilterIds(highlightedNodes); setHighlightedNodes([]); }; const proteinFilter = ( 0 ? ` (${volatileProteinFilterIds.length})` : '' }${highlightedNodes.length > 0 ? '*' : ''}`} - key={volatileProteinFilterIds.join(':')} >