From 402b2df6b55c860e9657e7fb606b51e116c48cf7 Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Tue, 19 Nov 2024 12:12:42 -0500 Subject: [PATCH] fix(protocol-designer): fix labware tools filtering and expand/collapse behavior This PR addresses several functional issues with our LabwareTools component, produced when adding labware to the starting deck state. Here, I add filtering to populated expanded categories, allow independent expand/collapse toggling for multiple categories simultaneously, and auto expand/collapse all categories based on the state of the current search term. Closes RQA-3590 --- .../ListButtonAccordionContainer.tsx | 2 +- .../localization/en/starting_deck_state.json | 12 +- .../pages/Designer/DeckSetup/LabwareTools.tsx | 314 ++++++++++-------- 3 files changed, 179 insertions(+), 149 deletions(-) diff --git a/components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordionContainer.tsx b/components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordionContainer.tsx index 99fde7dd81f..85f76f901b2 100644 --- a/components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordionContainer.tsx +++ b/components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordionContainer.tsx @@ -16,7 +16,7 @@ export function ListButtonAccordionContainer( const { id, children } = props return ( - + {children} ) diff --git a/protocol-designer/src/assets/localization/en/starting_deck_state.json b/protocol-designer/src/assets/localization/en/starting_deck_state.json index 31be366c9d7..fcf88c2866e 100644 --- a/protocol-designer/src/assets/localization/en/starting_deck_state.json +++ b/protocol-designer/src/assets/localization/en/starting_deck_state.json @@ -1,6 +1,6 @@ { "adapter_compatible_lab": "Adapter compatible labware", - "adapter": "Adapter", + "adapter": "Adapters", "add_fixture": "Add a fixture", "add_hardware_labware": "Add hardware/labware", "add_hw_lw": "Add hardware/labware", @@ -9,7 +9,7 @@ "add_module": "Add a module", "add_rest": "Add labware and liquids to complete deck setup", "alter_pause": "You may also need to alter the time you pause while your magnet is engaged.", - "aluminumBlock": "Aluminum block", + "aluminumBlock": "Aluminum blocks", "clear_labware": "Clear labware", "clear_slot": "Clear slot", "clear": "Clear", @@ -47,16 +47,16 @@ "protocol_starting_deck": "Protocol starting deck", "read_more_gen1_gen2": "Read more about the differences between GEN1 and GEN2 Magnetic Modules", "rename_lab": "Rename labware", - "reservoir": "Reservoir", + "reservoir": "Reservoirs", "shift_click_to_select_all": "Shift + Click to select all", "starting_deck_state": "Starting deck state", "tc_slots_occupied_flex": "The Thermocycler needs slots A1 and B1. Slot A1 is occupied", "tc_slots_occupied_ot2": "The Thermocycler needs slots 7, 8, 10, and 11. One or more of those slots is occupied", - "tipRack": "Tip rack", + "tipRack": "Tip racks", "trash_required": "A trash bin or waste chute is required", - "tubeRack": "Tube rack", + "tubeRack": "Tube racks", "untitled_protocol": "Untitled protocol", "upload_custom_labware": "Upload custom labware", "we_added_hardware": "We've added your deck hardware!", - "wellPlate": "Well plate" + "wellPlate": "Well plates" } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx index dec0d114f83..79b6d5abd04 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx @@ -87,11 +87,30 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { selectedModuleModel, selectedNestedLabwareDefUri, } = zoomedInSlotInfo - const [selectedCategory, setSelectedCategory] = React.useState( - null - ) + const allOrderedCategories = [CUSTOM_CATEGORY, ...ORDERED_CATEGORIES] + const allCategoriesExpanded = allOrderedCategories.reduce< + Record + >((acc, category) => { + return { ...acc, [category]: true } + }, {}) + const allCategoriesCollapsed = allOrderedCategories.reduce< + Record + >((acc, category) => { + return { ...acc, [category]: false } + }, {}) + const [areCategoriesExpanded, setAreCategoriesExpanded] = React.useState< + Record + >(allCategoriesCollapsed) const [searchTerm, setSearchTerm] = React.useState('') + React.useEffect(() => { + if (searchTerm !== '') { + setAreCategoriesExpanded(allCategoriesExpanded) + } else { + setAreCategoriesExpanded(allCategoriesCollapsed) + } + }, [searchTerm]) + const searchFilter = (termToCheck: string): boolean => termToCheck.toLowerCase().includes(searchTerm.toLowerCase()) @@ -186,13 +205,22 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { const populatedCategories: { [category: string]: boolean } = React.useMemo( () => - ORDERED_CATEGORIES.reduce((acc, category) => { + allOrderedCategories.reduce((acc, category) => { const isDeckLocationCategory = slot === 'offDeck' ? category !== 'adapter' : true + if (category === 'custom') { + return { + ...acc, + [category]: Object.values(customLabwareDefs).some(def => + searchFilter(def.metadata.displayName) + ), + } + } return category in labwareByCategory && isDeckLocationCategory && - labwareByCategory[category].some(lw => - searchFilter(lw.metadata.displayName) + labwareByCategory[category].some( + lw => + searchFilter(lw.metadata.displayName) && !getIsLabwareFiltered(lw) ) ? { ...acc, @@ -204,8 +232,13 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { }, {}), [labwareByCategory, getIsLabwareFiltered, searchTerm] ) - const handleCategoryClick = (category: string): void => { - setSelectedCategory(selectedCategory === category ? null : category) + + const handleCategoryClick = (category: string, expand?: boolean): void => { + const updatedExpandState = { + ...areCategoriesExpanded, + [category]: expand ?? !areCategoriesExpanded[category], + } + setAreCategoriesExpanded(updatedExpandState) } return ( @@ -225,6 +258,7 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { showDeleteIcon onDelete={() => { setSearchTerm('') + setAreCategoriesExpanded(allCategoriesCollapsed) }} /> {moduleType != null || @@ -253,7 +287,7 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { gridGap={SPACING.spacing4} paddingTop={SPACING.spacing8} > - {customLabwareURIs.length === 0 ? null : ( + {populatedCategories[CUSTOM_CATEGORY] ? ( - {customLabwareURIs.map((labwareURI, index) => ( - { - setHoveredLabware(null) - }} - setHovered={() => { - setHoveredLabware(labwareURI) - }} - buttonValue={labwareURI} - onChange={e => { - e.stopPropagation() - dispatch(selectLabware({ labwareDefUri: labwareURI })) - }} - isSelected={labwareURI === selectedLabwareDefUri} - /> - ))} + {customLabwareURIs.map((labwareURI, index) => + searchFilter( + customLabwareDefs[labwareURI].metadata.displayName + ) ? ( + { + setHoveredLabware(null) + }} + setHovered={() => { + setHoveredLabware(labwareURI) + }} + buttonValue={labwareURI} + onChange={e => { + e.stopPropagation() + dispatch(selectLabware({ labwareDefUri: labwareURI })) + }} + isSelected={labwareURI === selectedLabwareDefUri} + /> + ) : null + )} - )} + ) : null} {ORDERED_CATEGORIES.map(category => { const isPopulated = populatedCategories[category] if (isPopulated) { @@ -305,144 +343,136 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { {labwareByCategory[category]?.map((labwareDef, index) => { - const isFiltered = getIsLabwareFiltered(labwareDef) const labwareURI = getLabwareDefURI(labwareDef) const loadName = labwareDef.parameters.loadName - const isMatch = searchFilter( - labwareDef.metadata.displayName - ) - if (!isFiltered && isMatch) { - return ( - - { - setHoveredLabware(null) - }} - setHovered={() => { - setHoveredLabware(labwareURI) - }} - id={`${index}_${category}_${loadName}`} - buttonText={labwareDef.metadata.displayName} - buttonValue={labwareURI} - onChange={e => { - e.stopPropagation() - dispatch( - selectLabware({ - labwareDefUri: - labwareURI === selectedLabwareDefUri - ? null - : labwareURI, - }) - ) - // reset the nested labware def uri in case it is not compatible - dispatch( - selectNestedLabware({ - nestedLabwareDefUri: null, - }) - ) - }} - isSelected={labwareURI === selectedLabwareDefUri} - /> - {labwareURI === selectedLabwareDefUri && - getLabwareCompatibleWithAdapter(loadName) - ?.length > 0 && ( - - - {has96Channel && - loadName === ADAPTER_96_CHANNEL - ? permittedTipracks.map( - (tiprackDefUri, index) => { - const nestedDef = - defs[tiprackDefUri] - return ( - { - setHoveredLabware(null) - }} - setHovered={() => { - setHoveredLabware( - tiprackDefUri - ) - }} - key={`${index}_${category}_${loadName}_${tiprackDefUri}`} - id={`${index}_${category}_${loadName}_${tiprackDefUri}`} - buttonText={ - nestedDef?.metadata - .displayName ?? '' - } - buttonValue={tiprackDefUri} - onChange={e => { - e.stopPropagation() - dispatch( - selectNestedLabware({ - nestedLabwareDefUri: tiprackDefUri, - }) - ) - }} - isSelected={ - tiprackDefUri === - selectedNestedLabwareDefUri - } - /> - ) - } - ) - : getLabwareCompatibleWithAdapter( - loadName - ).map(nestedDefUri => { - const nestedDef = defs[nestedDefUri] + return searchFilter(labwareDef.metadata.displayName) ? ( + + { + setHoveredLabware(null) + }} + setHovered={() => { + setHoveredLabware(labwareURI) + }} + id={`${index}_${category}_${loadName}`} + buttonText={labwareDef.metadata.displayName} + buttonValue={labwareURI} + onChange={e => { + e.stopPropagation() + dispatch( + selectLabware({ + labwareDefUri: + labwareURI === selectedLabwareDefUri + ? null + : labwareURI, + }) + ) + // reset the nested labware def uri in case it is not compatible + dispatch( + selectNestedLabware({ + nestedLabwareDefUri: null, + }) + ) + }} + isSelected={labwareURI === selectedLabwareDefUri} + /> + {labwareURI === selectedLabwareDefUri && + getLabwareCompatibleWithAdapter(loadName)?.length > + 0 && ( + + + {has96Channel && + loadName === ADAPTER_96_CHANNEL + ? permittedTipracks.map( + (tiprackDefUri, index) => { + const nestedDef = defs[tiprackDefUri] return ( { setHoveredLabware(null) }} setHovered={() => { - setHoveredLabware(nestedDefUri) + setHoveredLabware(tiprackDefUri) }} - key={`${index}_${category}_${loadName}_${nestedDefUri}`} - id={`${index}_${category}_${loadName}_${nestedDefUri}`} + key={`${index}_${category}_${loadName}_${tiprackDefUri}`} + id={`${index}_${category}_${loadName}_${tiprackDefUri}`} buttonText={ nestedDef?.metadata .displayName ?? '' } - buttonValue={nestedDefUri} + buttonValue={tiprackDefUri} onChange={e => { e.stopPropagation() dispatch( selectNestedLabware({ - nestedLabwareDefUri: nestedDefUri, + nestedLabwareDefUri: tiprackDefUri, }) ) }} isSelected={ - nestedDefUri === + tiprackDefUri === selectedNestedLabwareDefUri } /> ) - })} - - - )} - - ) - } + } + ) + : getLabwareCompatibleWithAdapter( + loadName + ).map(nestedDefUri => { + const nestedDef = defs[nestedDefUri] + + return ( + { + setHoveredLabware(null) + }} + setHovered={() => { + setHoveredLabware(nestedDefUri) + }} + key={`${index}_${category}_${loadName}_${nestedDefUri}`} + id={`${index}_${category}_${loadName}_${nestedDefUri}`} + buttonText={ + nestedDef?.metadata.displayName ?? + '' + } + buttonValue={nestedDefUri} + onChange={e => { + e.stopPropagation() + dispatch( + selectNestedLabware({ + nestedLabwareDefUri: nestedDefUri, + }) + ) + }} + isSelected={ + nestedDefUri === + selectedNestedLabwareDefUri + } + /> + ) + })} + + + )} + + ) : null })} @@ -464,8 +494,8 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { data-testid="customLabwareInput" type="file" onChange={e => { - setSelectedCategory(CUSTOM_CATEGORY) dispatch(createCustomLabwareDef(e)) + handleCategoryClick(CUSTOM_CATEGORY, true) }} />