From 494674819e3ca59f9e595b22088c01c4e8c8de09 Mon Sep 17 00:00:00 2001 From: Jethary Date: Fri, 19 Jan 2024 12:25:17 -0500 Subject: [PATCH] fix more connect fns --- .../LabwareOverlays/EditLabwareOffDeck.tsx | 81 +++++-------- .../LabwareOverlays/NameThisLabware.tsx | 38 +++---- .../LabwareDetailsCard/LabwareDetailsCard.tsx | 57 ++++++++-- .../LabwareDetailsCard/index.ts | 76 ------------- .../src/components/IngredientsList/index.tsx | 56 +++++---- .../LabwareSelectionModal.tsx | 86 +++++++++++--- .../components/LabwareSelectionModal/index.ts | 106 ------------------ .../src/components/LiquidsSidebar/index.tsx | 65 ++++------- .../src/components/ProtocolEditor.tsx | 12 +- .../FeatureFlagCard/FeatureFlagCard.tsx | 32 ++++-- .../SettingsPage/FeatureFlagCard/index.ts | 30 ----- .../components/SettingsPage/SettingsApp.tsx | 69 +++--------- .../fields/DisposalVolumeField.tsx | 86 ++++++-------- .../fields/TipPositionField/index.tsx | 82 ++++++-------- .../components/labware/BrowseLabwareModal.tsx | 69 +++--------- .../FileUploadMessageModal.tsx | 27 ++--- .../modals/FileUploadMessageModal/index.ts | 35 ------ .../LabwareUploadMessageModal.tsx | 36 +++++- .../modals/LabwareUploadMessageModal/index.ts | 62 ---------- .../StartingDeckStateTerminalItem.tsx | 27 +---- .../src/components/steplist/StepList.tsx | 77 +++++++------ .../src/containers/ConnectedMainPanel.tsx | 35 ++---- .../src/containers/ConnectedSidebar.tsx | 43 ++----- .../src/containers/ConnectedStepList.ts | 37 ------ .../src/containers/IngredientsList.ts | 56 --------- 25 files changed, 465 insertions(+), 915 deletions(-) delete mode 100644 protocol-designer/src/components/IngredientsList/LabwareDetailsCard/index.ts delete mode 100644 protocol-designer/src/components/LabwareSelectionModal/index.ts delete mode 100644 protocol-designer/src/components/SettingsPage/FeatureFlagCard/index.ts delete mode 100644 protocol-designer/src/components/modals/FileUploadMessageModal/index.ts delete mode 100644 protocol-designer/src/components/modals/LabwareUploadMessageModal/index.ts delete mode 100644 protocol-designer/src/containers/ConnectedStepList.ts delete mode 100644 protocol-designer/src/containers/IngredientsList.ts diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx index c923df5af15..44f49a3ba01 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { connect } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { @@ -23,7 +23,7 @@ import { NameThisLabware } from './NameThisLabware' import styles from './LabwareOverlays.css' import type { LabwareEntity } from '@opentrons/step-generation' -import type { BaseState, ThunkDispatch } from '../../../types' +import type { ThunkDispatch } from '../../../types' const NAME_LABWARE_OVERLAY_STYLE = css` z-index: 1; @@ -58,30 +58,24 @@ const REGULAR_OVERLAY_STYLE = css` } ` -interface OP { +interface Props { labwareEntity: LabwareEntity } -interface SP { - isYetUnnamed: boolean -} -interface DP { - editLiquids: () => void - duplicateLabware: () => void - deleteLabware: () => void -} - -type Props = OP & SP & DP -const EditLabwareOffDeckComponent = (props: Props): JSX.Element => { - const { - labwareEntity, - isYetUnnamed, - editLiquids, - deleteLabware, - duplicateLabware, - } = props +export const EditLabwareOffDeck = (props: Props): JSX.Element => { + const { labwareEntity } = props const { t } = useTranslation('deck') + const dispatch: ThunkDispatch = useDispatch() + const allSavedLabware = useSelector(labwareIngredSelectors.getSavedLabware) + const hasName = allSavedLabware[labwareEntity.id] const { isTiprack } = labwareEntity.def.parameters + + const isYetUnnamed = isTiprack && !hasName + + const editLiquids = (): void => { + dispatch(openIngredientSelector(labwareEntity.id)) + } + if (isYetUnnamed && !isTiprack) { return (
@@ -102,11 +96,23 @@ const EditLabwareOffDeckComponent = (props: Props): JSX.Element => { ) : (
)} - + dispatch(duplicateLabware(labwareEntity.id))} + > {t('overlay.edit.duplicate')} - + { + window.confirm( + t('warning.cancelForSure', { + adapterName: getLabwareDisplayName(labwareEntity.def), + }) + ) && dispatch(deleteContainer({ labwareId: labwareEntity.id })) + }} + > {t('overlay.edit.delete')} @@ -114,32 +120,3 @@ const EditLabwareOffDeckComponent = (props: Props): JSX.Element => { ) } } - -const mapStateToProps = (state: BaseState, ownProps: OP): SP => { - const { id } = ownProps.labwareEntity - const hasName = labwareIngredSelectors.getSavedLabware(state)[id] - return { - isYetUnnamed: !ownProps.labwareEntity.def.parameters.isTiprack && !hasName, - } -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, - ownProps: OP -): DP => ({ - editLiquids: () => - dispatch(openIngredientSelector(ownProps.labwareEntity.id)), - duplicateLabware: () => dispatch(duplicateLabware(ownProps.labwareEntity.id)), - deleteLabware: () => { - window.confirm( - `Are you sure you want to permanently delete this ${getLabwareDisplayName( - ownProps.labwareEntity.def - )}?` - ) && dispatch(deleteContainer({ labwareId: ownProps.labwareEntity.id })) - }, -}) - -export const EditLabwareOffDeck = connect( - mapStateToProps, - mapDispatchToProps -)(EditLabwareOffDeckComponent) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx index 90c9a623244..8582e690397 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx @@ -1,31 +1,32 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { connect } from 'react-redux' +import { useDispatch } from 'react-redux' import cx from 'classnames' import { Icon, useOnClickOutside } from '@opentrons/components' import { renameLabware } from '../../../labware-ingred/actions' import styles from './LabwareOverlays.css' + import type { LabwareEntity } from '@opentrons/step-generation' import type { ThunkDispatch } from '../../../types' import type { LabwareOnDeck } from '../../../step-forms' -interface OP { +interface Props { labwareOnDeck: LabwareOnDeck | LabwareEntity - editLiquids: () => unknown -} - -interface DP { - setLabwareName: (name: string | null | undefined) => unknown + editLiquids: () => void } -type Props = OP & DP - -const NameThisLabwareComponent = (props: Props): JSX.Element => { - const [inputValue, setInputValue] = React.useState('') +export const NameThisLabware = (props: Props): JSX.Element => { + const { labwareOnDeck } = props + const dispatch: ThunkDispatch = useDispatch() + const [inputValue, setInputValue] = React.useState('') const { t } = useTranslation('deck') + const setLabwareName = (name: string | null | undefined): void => { + dispatch(renameLabware({ labwareId: labwareOnDeck.id, name })) + } + const saveNickname = (): void => { - props.setLabwareName(inputValue || null) + setLabwareName() } const wrapperRef: React.RefObject = useOnClickOutside({ onClickOutside: saveNickname, @@ -67,16 +68,3 @@ const NameThisLabwareComponent = (props: Props): JSX.Element => {
) } - -const mapDispatchToProps = (dispatch: ThunkDispatch, ownProps: OP): DP => { - const { id } = ownProps.labwareOnDeck - return { - setLabwareName: (name: string | null | undefined) => - dispatch(renameLabware({ labwareId: id, name })), - } -} - -export const NameThisLabware = connect( - null, - mapDispatchToProps -)(NameThisLabwareComponent) diff --git a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx index f71634702c8..dc30c6c35a6 100644 --- a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx +++ b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx @@ -1,18 +1,53 @@ import * as React from 'react' +import assert from 'assert' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import cx from 'classnames' +import { getLabwareDisplayName } from '@opentrons/shared-data' +import { selectors as stepFormSelectors } from '../../../step-forms' +import { selectors as uiLabwareSelectors } from '../../../ui/labware' +import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' +import * as labwareIngredActions from '../../../labware-ingred/actions' import { PDTitledList, PDListItem } from '../../lists' import { EditableTextField } from '../../EditableTextField' -import styles from './labwareDetailsCard.css' +import type { ThunkDispatch } from '../../../types' -export interface Props { - labwareDefDisplayName: string - nickname: string - renameLabware: (name: string) => unknown -} +import styles from './labwareDetailsCard.css' -export function LabwareDetailsCard(props: Props): JSX.Element { +export function LabwareDetailsCard(): JSX.Element { const { t } = useTranslation('form') + const dispatch: ThunkDispatch = useDispatch() + const labwareNicknamesById = useSelector( + uiLabwareSelectors.getLabwareNicknamesById + ) + const labwareId = useSelector(labwareIngredSelectors.getSelectedLabwareId) + const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) + const labwareDefDisplayName = + labwareId != null + ? getLabwareDisplayName(labwareEntities[labwareId].def) + : null + + assert( + labwareId, + 'Expected labware id to exist in connected labware details card' + ) + + const renameLabware = (name: string): void => { + assert( + labwareId, + 'renameLabware in LabwareDetailsCard expected a labwareId' + ) + + if (labwareId) { + dispatch( + labwareIngredActions.renameLabware({ + labwareId: labwareId, + name, + }) + ) + } + } + return ( @@ -20,9 +55,7 @@ export function LabwareDetailsCard(props: Props): JSX.Element { {t('generic.labware_type')} - - {props.labwareDefDisplayName} - + {labwareDefDisplayName}
@@ -31,8 +64,8 @@ export function LabwareDetailsCard(props: Props): JSX.Element { {t('generic.nickname')} diff --git a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/index.ts b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/index.ts deleted file mode 100644 index a230372e7aa..00000000000 --- a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/index.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { connect } from 'react-redux' -import assert from 'assert' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { - LabwareDetailsCard as LabwareDetailsCardComponent, - Props as LabwareDetailsCardProps, -} from './LabwareDetailsCard' -import { selectors as stepFormSelectors } from '../../../step-forms' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import * as labwareIngredActions from '../../../labware-ingred/actions' -import { BaseState, ThunkDispatch } from '../../../types' -type SP = Omit & { - _labwareId?: string -} - -function mapStateToProps(state: BaseState): SP { - const labwareNicknamesById = uiLabwareSelectors.getLabwareNicknamesById(state) - const labwareId = labwareIngredSelectors.getSelectedLabwareId(state) - const labwareDefDisplayName = - labwareId && - getLabwareDisplayName( - stepFormSelectors.getLabwareEntities(state)[labwareId].def - ) - assert( - labwareId, - 'Expected labware id to exist in connected labware details card' - ) - - if (!labwareId || !labwareDefDisplayName) { - return { - labwareDefDisplayName: '?', - nickname: '?', - } - } - - return { - labwareDefDisplayName, - nickname: labwareNicknamesById[labwareId] || 'Unnamed Labware', - _labwareId: labwareId, - } -} - -function mergeProps( - stateProps: SP, - dispatchProps: { - dispatch: ThunkDispatch - } -): LabwareDetailsCardProps { - const dispatch = dispatchProps.dispatch - const { _labwareId, ...passThruProps } = stateProps - - const renameLabware = (name: string): void => { - assert( - _labwareId, - 'renameLabware in LabwareDetailsCard expected a labwareId' - ) - - if (_labwareId) { - dispatch( - labwareIngredActions.renameLabware({ - labwareId: _labwareId, - name, - }) - ) - } - } - - return { ...passThruProps, renameLabware } -} - -export const LabwareDetailsCard = connect( - mapStateToProps, - null, - mergeProps -)(LabwareDetailsCardComponent) diff --git a/protocol-designer/src/components/IngredientsList/index.tsx b/protocol-designer/src/components/IngredientsList/index.tsx index 721dfd36b78..d501ec4081d 100644 --- a/protocol-designer/src/components/IngredientsList/index.tsx +++ b/protocol-designer/src/components/IngredientsList/index.tsx @@ -1,18 +1,23 @@ // TODO: Ian 2018-10-09 figure out what belongs in LiquidsSidebar vs IngredientsList after #2427 import * as React from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { SingleLabwareLiquidState } from '@opentrons/step-generation' import { IconButton, SidePanel, truncateString } from '@opentrons/components' import { sortWells } from '@opentrons/shared-data' -import { selectors } from '../../labware-ingred/selectors' +import * as wellSelectionSelectors from '../../top-selectors/well-contents' +import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' import { PDTitledList, PDListItem } from '../lists' import { TitledListNotes } from '../TitledListNotes' import { swatchColors } from '../swatchColors' -import { LabwareDetailsCard } from './LabwareDetailsCard' -import { LiquidGroupsById, LiquidGroup } from '../../labware-ingred/types' +import { LabwareDetailsCard } from './LabwareDetailsCard/LabwareDetailsCard' + import styles from './IngredientsList.css' +import type { SelectedContainerId } from '../../labware-ingred/reducers' +import type { LiquidGroup } from '../../labware-ingred/types' +import type { ThunkDispatch } from '../../types' + type RemoveWellsContents = (args: { liquidGroupId: string wells: string[] @@ -48,7 +53,9 @@ const LiquidGroupCard = (props: LiquidGroupCardProps): JSX.Element | null => { const wellsWithIngred = Object.keys(labwareWellContents) .sort(sortWells) .filter(well => labwareWellContents[well][groupId]) - const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) + const liquidDisplayColors = useSelector( + labwareIngredSelectors.getLiquidDisplayColors + ) if (wellsWithIngred.length < 1) { // do not show liquid card if it has no instances for this labware @@ -147,20 +154,31 @@ function IngredIndividual(props: IndividProps): JSX.Element { ) } -type Props = CommonProps & { - liquidGroupsById: LiquidGroupsById - labwareWellContents: SingleLabwareLiquidState - selectedIngredientGroupId?: string | null -} - -export function IngredientsList(props: Props): JSX.Element { - const { - labwareWellContents, - liquidGroupsById, - removeWellsContents, - selectedIngredientGroupId, - } = props +export function IngredientsList(): JSX.Element { + const selectedLabwareId = useSelector( + labwareIngredSelectors.getSelectedLabwareId + ) + const allLabwareWellContents = useSelector( + labwareIngredSelectors.getLiquidsByLabwareId + ) + const labwareWellContents = + selectedLabwareId != null ? allLabwareWellContents[selectedLabwareId] : {} + const liquidGroupsById = useSelector( + labwareIngredSelectors.getLiquidGroupsById + ) + const selectedIngredientGroupId = useSelector( + wellSelectionSelectors.getSelectedWellsCommonIngredId + ) const { t } = useTranslation('nav') + const dispatch: ThunkDispatch = useDispatch() + + const removeWellsContents = ( + labwareId?: SelectedContainerId | null + ): void => { + if (labwareId != null) { + dispatch(removeWellsContents(selectedLabwareId)) + } + } return ( @@ -168,7 +186,7 @@ export function IngredientsList(props: Props): JSX.Element { {Object.keys(liquidGroupsById).map(groupIdForCard => ( removeWellsContents(selectedLabwareId)} labwareWellContents={labwareWellContents} ingredGroup={liquidGroupsById[groupIdForCard]} groupId={groupIdForCard} diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx index 2491108d6e5..c5166d592f3 100644 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx +++ b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import startCase from 'lodash/startCase' import reduce from 'lodash/reduce' @@ -23,13 +24,26 @@ import { ModuleModel, getModuleType, THERMOCYCLER_MODULE_V2, + getAreSlotsHorizontallyAdjacent, } from '@opentrons/shared-data' +import { + closeLabwareSelector, + createContainer, +} from '../../labware-ingred/actions' +import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' +import { + actions as labwareDefActions, + selectors as labwareDefSelectors, +} from '../../labware-defs' +import { selectors as stepFormSelectors, ModuleOnDeck } from '../../step-forms' import { SPAN7_8_10_11_SLOT } from '../../constants' import { getLabwareIsCompatible as _getLabwareIsCompatible, getLabwareCompatibleWithAdapter, ADAPTER_96_CHANNEL, } from '../../utils/labwareModuleCompatibility' +import { getPipetteEntities } from '../../step-forms/selectors' +import { getHas96Channel } from '../../utils' import { getOnlyLatestDefs } from '../../labware-defs/utils' import { Portal } from '../portals/TopPortal' import { PDTitledList } from '../lists' @@ -39,7 +53,7 @@ import { LabwareItem } from './LabwareItem' import { LabwarePreview } from './LabwarePreview' import styles from './styles.css' -import type { DeckSlot } from '../../types' +import type { DeckSlot, ThunkDispatch } from '../../types' import type { LabwareDefByDefURI } from '../../labware-defs' export interface Props { @@ -127,21 +141,61 @@ export const getLabwareIsRecommended = ( : false } } -export const LabwareSelectionModal = (props: Props): JSX.Element | null => { - const { - customLabwareDefs, - permittedTipracks, - onClose, - onUploadLabware, - slot, - parentSlot, - moduleModel, - selectLabware, - isNextToHeaterShaker, - adapterLoadName, - has96Channel, - } = props +export const LabwareSelectionModal = (): JSX.Element | null => { const { t } = useTranslation(['modules', 'modal', 'button', 'alert']) + const dispatch: ThunkDispatch = useDispatch() + const selectedLabwareSlot = useSelector( + labwareIngredSelectors.selectedAddLabwareSlot + ) + const slot = selectedLabwareSlot === false ? null : selectedLabwareSlot + const pipetteEntities = useSelector(getPipetteEntities) + const permittedTipracks = useSelector(stepFormSelectors.getPermittedTipracks) + const has96Channel = getHas96Channel(pipetteEntities) + const customLabwareDefs = useSelector( + labwareDefSelectors.getCustomLabwareDefsByURI + ) + const deckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) + const modulesById = deckSetup.modules + const labwareById = deckSetup.labware + + const onClose = (): void => { + dispatch(closeLabwareSelector()) + } + const selectLabware = (labwareDefURI: string): void => { + if (slot) { + dispatch( + createContainer({ + slot: slot, + labwareDefURI, + }) + ) + } + } + + const onUploadLabware = ( + fileChangeEvent: React.ChangeEvent + ): void => { + dispatch(labwareDefActions.createCustomLabwareDef(fileChangeEvent)) + } + + const initialModules: ModuleOnDeck[] = Object.keys(modulesById).map( + moduleId => modulesById[moduleId] + ) + const parentModule = + (slot != null && + initialModules.find(moduleOnDeck => moduleOnDeck.id === slot)) || + null + const parentSlot = parentModule != null ? parentModule.slot : null + const moduleModel = parentModule != null ? parentModule.model : null + const isNextToHeaterShaker = initialModules.some( + hardwareModule => + hardwareModule.type === HEATERSHAKER_MODULE_TYPE && + getAreSlotsHorizontallyAdjacent(hardwareModule.slot, parentSlot ?? slot) + ) + const adapterLoadName = Object.values(labwareById) + .filter(labwareOnDeck => slot === labwareOnDeck.id) + .map(labwareOnDeck => labwareOnDeck.def.parameters.loadName)[0] + const defs = getOnlyLatestDefs() const moduleType = moduleModel != null ? getModuleType(moduleModel) : null const URIs = Object.keys(defs) @@ -154,10 +208,12 @@ export const LabwareSelectionModal = (props: Props): JSX.Element | null => { const [filterRecommended, setFilterRecommended] = React.useState( false ) + const [filterHeight, setFilterHeight] = React.useState(false) const [enqueuedLabwareType, setEnqueuedLabwareType] = React.useState< string | null >(null) + const blockingCustomLabwareHint = useBlockingHint({ enabled: enqueuedLabwareType !== null, hintKey: 'custom_labware_with_modules', diff --git a/protocol-designer/src/components/LabwareSelectionModal/index.ts b/protocol-designer/src/components/LabwareSelectionModal/index.ts deleted file mode 100644 index f60c2bf95d7..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { connect } from 'react-redux' -import { - getAreSlotsHorizontallyAdjacent, - HEATERSHAKER_MODULE_TYPE, -} from '@opentrons/shared-data' -import { - LabwareSelectionModal as LabwareSelectionModalComponent, - Props as LabwareSelectionModalProps, -} from './LabwareSelectionModal' -import { - closeLabwareSelector, - createContainer, -} from '../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { - actions as labwareDefActions, - selectors as labwareDefSelectors, -} from '../../labware-defs' -import { selectors as stepFormSelectors, ModuleOnDeck } from '../../step-forms' -import { getHas96Channel } from '../../utils' -import { getPipetteEntities } from '../../step-forms/selectors' -import { adapter96ChannelDefUri } from '../modals/CreateFileWizard' -import type { BaseState, ThunkDispatch } from '../../types' -interface SP { - customLabwareDefs: LabwareSelectionModalProps['customLabwareDefs'] - slot: LabwareSelectionModalProps['slot'] - parentSlot: LabwareSelectionModalProps['parentSlot'] - moduleModel: LabwareSelectionModalProps['moduleModel'] - permittedTipracks: LabwareSelectionModalProps['permittedTipracks'] - isNextToHeaterShaker: boolean - has96Channel: boolean - adapterDefUri?: string - adapterLoadName?: string -} - -function mapStateToProps(state: BaseState): SP { - const slot = labwareIngredSelectors.selectedAddLabwareSlot(state) || null - const pipettes = getPipetteEntities(state) - const has96Channel = getHas96Channel(pipettes) - - // TODO: Ian 2019-10-29 needs revisit to support multiple manualIntervention steps - const modulesById = stepFormSelectors.getInitialDeckSetup(state).modules - const initialModules: ModuleOnDeck[] = Object.keys(modulesById).map( - moduleId => modulesById[moduleId] - ) - const labwareById = stepFormSelectors.getInitialDeckSetup(state).labware - const parentModule = - (slot != null && - initialModules.find(moduleOnDeck => moduleOnDeck.id === slot)) || - null - const parentSlot = parentModule != null ? parentModule.slot : null - const moduleModel = parentModule != null ? parentModule.model : null - const isNextToHeaterShaker = initialModules.some( - hardwareModule => - hardwareModule.type === HEATERSHAKER_MODULE_TYPE && - getAreSlotsHorizontallyAdjacent(hardwareModule.slot, parentSlot ?? slot) - ) - const adapterLoadNameOnDeck = Object.values(labwareById) - .filter(labwareOnDeck => slot === labwareOnDeck.id) - .map(labwareOnDeck => labwareOnDeck.def.parameters.loadName)[0] - - return { - customLabwareDefs: labwareDefSelectors.getCustomLabwareDefsByURI(state), - slot, - parentSlot, - moduleModel, - isNextToHeaterShaker, - has96Channel, - adapterDefUri: has96Channel ? adapter96ChannelDefUri : undefined, - permittedTipracks: stepFormSelectors.getPermittedTipracks(state), - adapterLoadName: adapterLoadNameOnDeck, - } -} - -function mergeProps( - stateProps: SP, - dispatchProps: { - dispatch: ThunkDispatch - } -): LabwareSelectionModalProps { - const dispatch = dispatchProps.dispatch - return { - ...stateProps, - onClose: () => { - dispatch(closeLabwareSelector()) - }, - onUploadLabware: fileChangeEvent => - dispatch(labwareDefActions.createCustomLabwareDef(fileChangeEvent)), - selectLabware: labwareDefURI => { - if (stateProps.slot) { - dispatch( - createContainer({ - slot: stateProps.slot, - labwareDefURI, - }) - ) - } - }, - } -} - -export const LabwareSelectionModal = connect( - mapStateToProps, - null, - mergeProps -)(LabwareSelectionModalComponent) diff --git a/protocol-designer/src/components/LiquidsSidebar/index.tsx b/protocol-designer/src/components/LiquidsSidebar/index.tsx index e36022ba3ba..d8b17b0452a 100644 --- a/protocol-designer/src/components/LiquidsSidebar/index.tsx +++ b/protocol-designer/src/components/LiquidsSidebar/index.tsx @@ -1,36 +1,34 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { connect } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { DeprecatedPrimaryButton, SidePanel, truncateString, } from '@opentrons/components' +import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' +import * as labwareIngredActions from '../../labware-ingred/actions' import { PDTitledList } from '../lists' import { swatchColors } from '../swatchColors' -import listButtonStyles from '../listButtons.css' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { OrderedLiquids } from '../../labware-ingred/types' -import * as labwareIngredActions from '../../labware-ingred/actions' -import { BaseState, ThunkDispatch } from '../../types' +import type { ThunkDispatch } from '../../types' import styles from './styles.css' -interface SP { - liquids: OrderedLiquids - selectedLiquid?: string | null -} - -interface DP { - createNewLiquid: () => unknown - selectLiquid: (liquidId: string) => unknown -} - -type Props = SP & DP +import listButtonStyles from '../listButtons.css' -function LiquidsSidebarComponent(props: Props): JSX.Element { - const { liquids, selectedLiquid, createNewLiquid, selectLiquid } = props +export function LiquidsSidebar(): JSX.Element { const { t } = useTranslation('button') + const selectedLiquidGroup = useSelector( + labwareIngredSelectors.getSelectedLiquidGroupState + ) + const liquids = useSelector(labwareIngredSelectors.allIngredientNamesIds) + const dispatch: ThunkDispatch = useDispatch() + + const selectLiquid = (liquidGroupId: string): void => { + dispatch(labwareIngredActions.selectLiquidGroup(liquidGroupId)) + } + const selectedLiquid = + selectedLiquidGroup && selectedLiquidGroup.liquidGroupId return ( {liquids.map(({ ingredientId, name, displayColor }) => ( @@ -54,34 +52,13 @@ function LiquidsSidebarComponent(props: Props): JSX.Element { /> ))}
- + dispatch(labwareIngredActions.createNewLiquidGroup())} + > {t('new_liquid')}
) } - -function mapStateToProps(state: BaseState): SP { - const selectedLiquidGroup = labwareIngredSelectors.getSelectedLiquidGroupState( - state - ) - return { - liquids: labwareIngredSelectors.allIngredientNamesIds(state), - selectedLiquid: selectedLiquidGroup && selectedLiquidGroup.liquidGroupId, - } -} - -function mapDispatchToProps(dispatch: ThunkDispatch): DP { - return { - selectLiquid: liquidGroupId => - dispatch(labwareIngredActions.selectLiquidGroup(liquidGroupId)), - createNewLiquid: () => - dispatch(labwareIngredActions.createNewLiquidGroup()), - } -} - -export const LiquidsSidebar = connect( - mapStateToProps, - mapDispatchToProps -)(LiquidsSidebarComponent) diff --git a/protocol-designer/src/components/ProtocolEditor.tsx b/protocol-designer/src/components/ProtocolEditor.tsx index ef963ec03f1..e483d6d448a 100644 --- a/protocol-designer/src/components/ProtocolEditor.tsx +++ b/protocol-designer/src/components/ProtocolEditor.tsx @@ -4,15 +4,15 @@ import { DragDropContext } from 'react-dnd' import MouseBackEnd from 'react-dnd-mouse-backend' import { ComputingSpinner } from '../components/ComputingSpinner' import { ConnectedNav } from '../containers/ConnectedNav' -import { ConnectedSidebar } from '../containers/ConnectedSidebar' +import { Sidebar } from '../containers/ConnectedSidebar' import { ConnectedTitleBar } from '../containers/ConnectedTitleBar' -import { ConnectedMainPanel } from '../containers/ConnectedMainPanel' +import { MainPanel } from '../containers/ConnectedMainPanel' import { PortalRoot as MainPageModalPortalRoot } from '../components/portals/MainPageModalPortal' import { MAIN_CONTENT_FORCED_SCROLL_CLASSNAME } from '../ui/steps/utils' import { PrereleaseModeIndicator } from './PrereleaseModeIndicator' import { PortalRoot as TopPortalRoot } from './portals/TopPortal' -import { FileUploadMessageModal } from './modals/FileUploadMessageModal' -import { LabwareUploadMessageModal } from './modals/LabwareUploadMessageModal' +import { FileUploadMessageModal } from './modals/FileUploadMessageModal/FileUploadMessageModal' +import { LabwareUploadMessageModal } from './modals/LabwareUploadMessageModal/LabwareUploadMessageModal' import { GateModal } from './modals/GateModal' import { AnnouncementModal } from './modals/AnnouncementModal' import styles from './ProtocolEditor.css' @@ -30,7 +30,7 @@ function ProtocolEditorComponent(): JSX.Element {
- +
@@ -47,7 +47,7 @@ function ProtocolEditorComponent(): JSX.Element { - +
diff --git a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx b/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx index 755027ee57e..12eb411fc47 100644 --- a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx +++ b/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx @@ -1,25 +1,35 @@ import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import sortBy from 'lodash/sortBy' import { ContinueModal, Card, ToggleButton } from '@opentrons/components' import { resetScrollElements } from '../../../ui/steps/utils' +import { + userFacingFlags, + FlagTypes, + actions as featureFlagActions, + selectors as featureFlagSelectors, +} from '../../../feature-flags' import { Portal } from '../../portals/MainPageModalPortal' import styles from '../SettingsPage.css' import modalStyles from '../../modals/modal.css' -import { userFacingFlags, Flags, FlagTypes } from '../../../feature-flags' -export interface Props { - flags: Flags - setFeatureFlags: (flags: Flags) => unknown -} +export const FeatureFlagCard = (): JSX.Element => { + const flags = useSelector(featureFlagSelectors.getFeatureFlagData) + const dispatch = useDispatch() -export const FeatureFlagCard = (props: Props): JSX.Element => { const [modalFlagName, setModalFlagName] = React.useState( null ) const { t } = useTranslation(['modal', 'card', 'feature_flags']) - const prereleaseModeEnabled = props.flags.PRERELEASE_MODE === true + const setFeatureFlags = ( + flags: Partial> + ): void => { + dispatch(featureFlagActions.setFeatureFlags(flags)) + } + + const prereleaseModeEnabled = flags.PRERELEASE_MODE === true // @ts-expect-error(sa, 2021-6-21): Object.keys not smart enough to take keys from props.flags const allFlags: FlagTypes[] = sortBy(Object.keys(props.flags)) @@ -59,7 +69,7 @@ export const FeatureFlagCard = (props: Props): JSX.Element => {

{ resetScrollElements() setModalFlagName(flagName) @@ -85,7 +95,7 @@ export const FeatureFlagCard = (props: Props): JSX.Element => { let flagSwitchDirection: string = 'on' if (modalFlagName) { - const isFlagOn: boolean | null | undefined = props.flags[modalFlagName] + const isFlagOn: boolean | null | undefined = flags[modalFlagName] flagSwitchDirection = isFlagOn ? 'off' : 'on' } return ( @@ -100,8 +110,8 @@ export const FeatureFlagCard = (props: Props): JSX.Element => { )} onCancelClick={() => setModalFlagName(null)} onContinueClick={() => { - props.setFeatureFlags({ - [modalFlagName as string]: !props.flags[modalFlagName], + setFeatureFlags({ + [modalFlagName as string]: !flags[modalFlagName], }) setModalFlagName(null) }} diff --git a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/index.ts b/protocol-designer/src/components/SettingsPage/FeatureFlagCard/index.ts deleted file mode 100644 index 034aba2e859..00000000000 --- a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux' -import { - FeatureFlagCard as FeatureFlagCardComponent, - Props as FeatureFlagCardProps, -} from './FeatureFlagCard' -import { - actions as featureFlagActions, - selectors as featureFlagSelectors, -} from '../../../feature-flags' -import { Dispatch } from 'redux' -import { BaseState } from '../../../types' -interface SP { - flags: FeatureFlagCardProps['flags'] -} -interface DP { - setFeatureFlags: FeatureFlagCardProps['setFeatureFlags'] -} - -const mapStateToProps = (state: BaseState): SP => ({ - flags: featureFlagSelectors.getFeatureFlagData(state), -}) - -const mapDispatchToProps = (dispatch: Dispatch): DP => ({ - setFeatureFlags: flags => dispatch(featureFlagActions.setFeatureFlags(flags)), -}) - -export const FeatureFlagCard = connect( - mapStateToProps, - mapDispatchToProps -)(FeatureFlagCardComponent) diff --git a/protocol-designer/src/components/SettingsPage/SettingsApp.tsx b/protocol-designer/src/components/SettingsPage/SettingsApp.tsx index 9f6f03c3253..a1c26ae6c21 100644 --- a/protocol-designer/src/components/SettingsPage/SettingsApp.tsx +++ b/protocol-designer/src/components/SettingsPage/SettingsApp.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { connect } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { Card, @@ -16,29 +16,20 @@ import { selectors as tutorialSelectors, } from '../../tutorial' import { OLDEST_MIGRATEABLE_VERSION } from '../../load-file/migration' -import { FeatureFlagCard } from './FeatureFlagCard' -import styles from './SettingsPage.css' -import { BaseState, ThunkDispatch } from '../../types' +import { FeatureFlagCard } from './FeatureFlagCard/FeatureFlagCard' -interface Props { - canClearHintDismissals: boolean - hasOptedIn: boolean | null - restoreHints: () => unknown - toggleOptedIn: () => unknown -} +import styles from './SettingsPage.css' -interface SP { - canClearHintDismissals: Props['canClearHintDismissals'] - hasOptedIn: Props['hasOptedIn'] -} +export function SettingsApp(): JSX.Element { + const dispatch = useDispatch() + const hasOptedIn = useSelector(analyticsSelectors.getHasOptedIn) + const canClearHintDismissals = useSelector( + tutorialSelectors.getCanClearHintDismissals + ) + const _toggleOptedIn = hasOptedIn + ? analyticsActions.optOut + : analyticsActions.optIn -function SettingsAppComponent(props: Props): JSX.Element { - const { - canClearHintDismissals, - hasOptedIn, - restoreHints, - toggleOptedIn, - } = props const { t } = useTranslation(['card', 'application', 'button']) return ( <> @@ -64,7 +55,9 @@ function SettingsAppComponent(props: Props): JSX.Element { + dispatch(tutorialActions.clearAllHintDismissals()) + } > {canClearHintDismissals ? t('button:restore') @@ -82,7 +75,7 @@ function SettingsAppComponent(props: Props): JSX.Element { dispatch(_toggleOptedIn())} /> @@ -99,33 +92,3 @@ function SettingsAppComponent(props: Props): JSX.Element { ) } - -function mapStateToProps(state: BaseState): SP { - return { - hasOptedIn: analyticsSelectors.getHasOptedIn(state), - canClearHintDismissals: tutorialSelectors.getCanClearHintDismissals(state), - } -} - -function mergeProps( - stateProps: SP, - dispatchProps: { dispatch: ThunkDispatch } -): Props { - const { dispatch } = dispatchProps - const { hasOptedIn } = stateProps - - const _toggleOptedIn = hasOptedIn - ? analyticsActions.optOut - : analyticsActions.optIn - return { - ...stateProps, - toggleOptedIn: () => dispatch(_toggleOptedIn()), - restoreHints: () => dispatch(tutorialActions.clearAllHintDismissals()), - } -} - -export const SettingsApp = connect( - mapStateToProps, - null, - mergeProps -)(SettingsAppComponent) diff --git a/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx b/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx index 837ba6ce166..31b75d9b128 100644 --- a/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { connect } from 'react-redux' +import { useSelector } from 'react-redux' import cx from 'classnames' import { @@ -15,9 +15,8 @@ import { selectors as uiLabwareSelectors } from '../../../ui/labware' import { getBlowoutLocationOptionsForForm } from '../utils' import { TextField } from './TextField' -import { FieldProps, FieldPropsByName } from '../types' -import { PathOption, StepType } from '../../../form-types' -import { BaseState } from '../../../types' +import type { FieldProps, FieldPropsByName } from '../types' +import type { PathOption, StepType } from '../../../form-types' import styles from '../StepEditForm.css' @@ -37,12 +36,7 @@ const DropdownFormField = (props: DropdownFormFieldProps): JSX.Element => { /> ) } - -interface SP { - disposalDestinationOptions: Options - maxDisposalVolume?: number | null -} -interface OP { +interface Props { aspirate_airGap_checkbox?: boolean | null aspirate_airGap_volume?: string | null path: PathOption @@ -51,11 +45,40 @@ interface OP { stepType: StepType volume: string | null } -type Props = SP & OP -const DisposalVolumeFieldComponent = (props: Props): JSX.Element => { - const { propsForFields, maxDisposalVolume } = props +export const DisposalVolumeField = (props: Props): JSX.Element => { + const { + path, + stepType, + volume, + pipette, + propsForFields, + aspirate_airGap_checkbox, + aspirate_airGap_volume, + } = props const { t } = useTranslation(['application', 'form']) + + const disposalOptions = useSelector(uiLabwareSelectors.getDisposalOptions) + const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) + const blowoutLocationOptions = getBlowoutLocationOptionsForForm({ + path, + stepType, + }) + const maxDisposalVolume = getMaxDisposalVolumeForMultidispense( + { + aspirate_airGap_checkbox, + aspirate_airGap_volume, + path, + pipette, + volume, + }, + pipetteEntities + ) + const disposalDestinationOptions = [ + ...disposalOptions, + ...blowoutLocationOptions, + ] + const volumeBoundsCaption = maxDisposalVolume != null ? `max ${maxDisposalVolume} ${t('units.microliter')}` @@ -97,7 +120,7 @@ const DisposalVolumeFieldComponent = (props: Props): JSX.Element => { ) : null} @@ -105,38 +128,3 @@ const DisposalVolumeFieldComponent = (props: Props): JSX.Element => { ) } -const mapSTP = (state: BaseState, ownProps: OP): SP => { - const { - aspirate_airGap_checkbox, - aspirate_airGap_volume, - path, - pipette, - stepType, - volume, - } = ownProps - - const blowoutLocationOptions = getBlowoutLocationOptionsForForm({ - path, - stepType, - }) - - const disposalOptions = uiLabwareSelectors.getDisposalOptions(state) - - const maxDisposalVolume = getMaxDisposalVolumeForMultidispense( - { - aspirate_airGap_checkbox, - aspirate_airGap_volume, - path, - pipette, - volume, - }, - stepFormSelectors.getPipetteEntities(state) - ) - - return { - maxDisposalVolume, - disposalDestinationOptions: [...disposalOptions, ...blowoutLocationOptions], - } -} - -export const DisposalVolumeField = connect(mapSTP)(DisposalVolumeFieldComponent) diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx index 6ee70be22ef..17e95716455 100644 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { connect } from 'react-redux' +import { useSelector } from 'react-redux' import { FormGroup, InputField, @@ -14,13 +14,12 @@ import { getIsDelayPositionField, } from '../../../../form-types' import { selectors as stepFormSelectors } from '../../../../step-forms' +import { TipPositionModal } from './TipPositionModal' +import { getDefaultMmFromBottom } from './utils' import stepFormStyles from '../../StepEditForm.css' import styles from './TipPositionInput.css' -import { TipPositionModal } from './TipPositionModal' -import { getDefaultMmFromBottom } from './utils' -import { BaseState } from '../../../../types' -import { FieldProps } from '../../types' +import type { FieldProps } from '../../types' interface OP extends FieldProps { labwareId?: string | null @@ -34,8 +33,35 @@ interface SP { type Props = OP & SP -function TipPositionInput(props: Props): JSX.Element { +export function TipPositionField(props: Props): JSX.Element { + const { + disabled, + name, + + tooltipContent, + + updateValue, + isIndeterminate, + labwareId, + } = props const [isModalOpen, setModalOpen] = React.useState(false) + const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) + const labwareDef = labwareId != null ? labwareEntities[labwareId].def : null + + let wellDepthMm: number = 0 + if (labwareDef != null) { + // NOTE: only taking depth of first well in labware def, UI not currently equipped for multiple depths + const firstWell = labwareDef.wells.A1 + if (firstWell) { + wellDepthMm = getWellsDepth(labwareDef, ['A1']) + } + } + + if (wellDepthMm === 0 && labwareId != null && labwareDef != null) { + console.error( + `expected to find the well depth mm with labwareId ${labwareId} but could not` + ) + } const handleOpen = (): void => { if (props.wellDepthMm) { @@ -45,30 +71,20 @@ function TipPositionInput(props: Props): JSX.Element { const handleClose = (): void => { setModalOpen(false) } - - const { - disabled, - name, - mmFromBottom, - tooltipContent, - wellDepthMm, - updateValue, - isIndeterminate, - } = props const { t } = useTranslation('application') const isTouchTipField = getIsTouchTipField(name) const isDelayPositionField = getIsDelayPositionField(name) - let value: number | string = '' + let value: number = 0 + const mmFromBottom = value ?? null if (wellDepthMm !== null) { // show default value for field in parens if no mmFromBottom value is selected value = - mmFromBottom !== null + mmFromBottom != null ? mmFromBottom : getDefaultMmFromBottom({ name, wellDepthMm }) } const [targetProps, tooltipProps] = useHoverTooltip() - return ( <> {tooltipContent} @@ -127,31 +143,3 @@ const Wrapper = (props: WrapperProps): JSX.Element => { ) } -const mapSTP = (state: BaseState, ownProps: OP): SP => { - const { labwareId, value } = ownProps - - let wellDepthMm = 0 - const labwareDef = - labwareId != null - ? stepFormSelectors.getLabwareEntities(state)[labwareId]?.def - : null - - if (labwareDef != null) { - // NOTE: only taking depth of first well in labware def, UI not currently equipped for multiple depths - const firstWell = labwareDef.wells.A1 - if (firstWell) wellDepthMm = getWellsDepth(labwareDef, ['A1']) - } - - if (wellDepthMm === 0 && labwareId != null && labwareDef != null) { - console.error( - `expected to find the well depth mm with labwareId ${labwareId} but could not` - ) - } - - return { - wellDepthMm, - mmFromBottom: typeof value === 'number' ? value : null, - } -} - -export const TipPositionField = connect(mapSTP, () => ({}))(TipPositionInput) diff --git a/protocol-designer/src/components/labware/BrowseLabwareModal.tsx b/protocol-designer/src/components/labware/BrowseLabwareModal.tsx index fc5516636e7..aab72cf0e00 100644 --- a/protocol-designer/src/components/labware/BrowseLabwareModal.tsx +++ b/protocol-designer/src/components/labware/BrowseLabwareModal.tsx @@ -1,39 +1,34 @@ import assert from 'assert' import * as React from 'react' import cx from 'classnames' -import { connect } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' - import { Modal } from '@opentrons/components' + import * as wellContentsSelectors from '../../top-selectors/well-contents' import { selectors } from '../../labware-ingred/selectors' import { selectors as stepFormSelectors } from '../../step-forms' import * as labwareIngredsActions from '../../labware-ingred/actions' import { BrowsableLabware } from './BrowsableLabware' -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { BaseState, ThunkDispatch } from '../../types' -import type { ContentsByWell } from '../../labware-ingred/types' -import type { WellIngredientNames } from '../../steplist/types' - import modalStyles from '../modals/modal.css' import styles from './labware.css' -interface SP { - definition?: LabwareDefinition2 | null - wellContents: ContentsByWell - ingredNames: WellIngredientNames -} - -interface DP { - drillUp: () => unknown -} - -type Props = SP & DP - -const BrowseLabwareModalComponent = (props: Props): JSX.Element | null => { - const { drillUp, definition, ingredNames, wellContents } = props +export const BrowseLabwareModal = (): JSX.Element | null => { const { t } = useTranslation('modal') + const dispatch = useDispatch() + const labwareId = useSelector(selectors.getDrillDownLabwareId) + const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) + const allWellContentsForActiveItem = useSelector( + wellContentsSelectors.getAllWellContentsForActiveItem + ) + const ingredNames = useSelector(selectors.getLiquidNamesById) + const definition = labwareId ? labwareEntities[labwareId]?.def : null + const wellContents = + labwareId && allWellContentsForActiveItem + ? allWellContentsForActiveItem[labwareId] + : null + if (!definition) { assert(definition, 'BrowseLabwareModal expected definition') return null @@ -46,7 +41,7 @@ const BrowseLabwareModalComponent = (props: Props): JSX.Element | null => { modalStyles.modal_contents, modalStyles.transparent_content )} - onCloseClick={drillUp} + onCloseClick={() => dispatch(labwareIngredsActions.drillUpFromLabware())} > { ) } - -function mapStateToProps(state: BaseState): SP { - const labwareId = selectors.getDrillDownLabwareId(state) - const definition = labwareId - ? stepFormSelectors.getLabwareEntities(state)[labwareId]?.def - : null - - const allWellContentsForActiveItem = wellContentsSelectors.getAllWellContentsForActiveItem( - state - ) - const wellContents = - labwareId && allWellContentsForActiveItem - ? allWellContentsForActiveItem[labwareId] - : null - const ingredNames = selectors.getLiquidNamesById(state) - return { - wellContents, - ingredNames, - definition, - } -} - -function mapDispatchToProps(dispatch: ThunkDispatch): DP { - return { drillUp: () => dispatch(labwareIngredsActions.drillUpFromLabware()) } -} - -export const BrowseLabwareModal = connect( - mapStateToProps, - mapDispatchToProps -)(BrowseLabwareModalComponent) diff --git a/protocol-designer/src/components/modals/FileUploadMessageModal/FileUploadMessageModal.tsx b/protocol-designer/src/components/modals/FileUploadMessageModal/FileUploadMessageModal.tsx index 875efea30a2..8567ab839cf 100644 --- a/protocol-designer/src/components/modals/FileUploadMessageModal/FileUploadMessageModal.tsx +++ b/protocol-designer/src/components/modals/FileUploadMessageModal/FileUploadMessageModal.tsx @@ -1,29 +1,30 @@ import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import cx from 'classnames' import { AlertModal, OutlineButton } from '@opentrons/components' -import modalStyles from '../modal.css' +import { + selectors as loadFileSelectors, + actions as loadFileActions, +} from '../../../load-file' import { getModalContents } from './modalContents' -import { FileUploadMessage } from '../../../load-file' - -export interface FileUploadMessageModalProps { - message?: FileUploadMessage | null - cancelProtocolMigration: (event: React.MouseEvent) => unknown - dismissModal: (event: React.MouseEvent) => unknown -} +import modalStyles from '../modal.css' -export function FileUploadMessageModal( - props: FileUploadMessageModalProps -): JSX.Element | null { - const { message, cancelProtocolMigration, dismissModal } = props +export function FileUploadMessageModal(): JSX.Element | null { + const message = useSelector(loadFileSelectors.getFileUploadMessages) + const dispatch = useDispatch() const { t } = useTranslation('button') + + const dismissModal = (): void => { + dispatch(loadFileActions.dismissFileUploadMessage()) + } if (!message) return null const { title, body, okButtonText } = getModalContents(message) let buttons = [ { children: t('cancel'), - onClick: cancelProtocolMigration, + onClick: () => dispatch(loadFileActions.undoLoadFile()), className: modalStyles.bottom_button, }, { diff --git a/protocol-designer/src/components/modals/FileUploadMessageModal/index.ts b/protocol-designer/src/components/modals/FileUploadMessageModal/index.ts deleted file mode 100644 index 9e15523bfde..00000000000 --- a/protocol-designer/src/components/modals/FileUploadMessageModal/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - FileUploadMessageModal as FileUploadMessageModalComponent, - FileUploadMessageModalProps, -} from './FileUploadMessageModal' -import { connect } from 'react-redux' -import { - selectors as loadFileSelectors, - actions as loadFileActions, -} from '../../../load-file' -import { Dispatch } from 'redux' -import { BaseState } from '../../../types' - -type Props = FileUploadMessageModalProps -interface SP { - message: Props['message'] -} -type DP = Omit - -function mapStateToProps(state: BaseState): SP { - return { - message: loadFileSelectors.getFileUploadMessages(state), - } -} - -function mapDispatchToProps(dispatch: Dispatch): DP { - return { - cancelProtocolMigration: () => dispatch(loadFileActions.undoLoadFile()), - dismissModal: () => dispatch(loadFileActions.dismissFileUploadMessage()), - } -} - -export const FileUploadMessageModal = connect( - mapStateToProps, - mapDispatchToProps -)(FileUploadMessageModalComponent) diff --git a/protocol-designer/src/components/modals/LabwareUploadMessageModal/LabwareUploadMessageModal.tsx b/protocol-designer/src/components/modals/LabwareUploadMessageModal/LabwareUploadMessageModal.tsx index 08e88586940..749613c7144 100644 --- a/protocol-designer/src/components/modals/LabwareUploadMessageModal/LabwareUploadMessageModal.tsx +++ b/protocol-designer/src/components/modals/LabwareUploadMessageModal/LabwareUploadMessageModal.tsx @@ -1,10 +1,15 @@ import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import assert from 'assert' import cx from 'classnames' import { AlertModal, OutlineButton, ButtonProps } from '@opentrons/components' +import { + selectors as labwareDefSelectors, + actions as labwareDefActions, + LabwareUploadMessage, +} from '../../../labware-defs' import modalStyles from '../modal.css' -import { LabwareUploadMessage } from '../../../labware-defs' const MessageBody = (props: { message: LabwareUploadMessage @@ -85,11 +90,32 @@ export interface LabwareUploadMessageModalProps { overwriteLabwareDef?: () => unknown } -export const LabwareUploadMessageModal = ( - props: LabwareUploadMessageModalProps -): JSX.Element | null => { - const { message, dismissModal, overwriteLabwareDef } = props +export const LabwareUploadMessageModal = (): JSX.Element | null => { + const message = useSelector(labwareDefSelectors.getLabwareUploadMessage) + const dispatch = useDispatch() const { t } = useTranslation('modal') + const dismissModal = (): void => { + dispatch(labwareDefActions.dismissLabwareUploadMessage()) + } + const overwriteLabwareDef = (): void => { + if (message && message.messageType === 'ASK_FOR_LABWARE_OVERWRITE') { + dispatch( + labwareDefActions.replaceCustomLabwareDef({ + defURIToOverwrite: message.defURIToOverwrite, + newDef: message.newDef, + isOverwriteMismatched: message.isOverwriteMismatched, + }) + ) + } else { + assert( + false, + `labware def should only be overwritten when messageType is ASK_FOR_LABWARE_OVERWRITE. Got ${String( + message?.messageType + )}` + ) + } + } + if (!message) return null let buttons: ButtonProps[] = [{ children: 'OK', onClick: dismissModal }] diff --git a/protocol-designer/src/components/modals/LabwareUploadMessageModal/index.ts b/protocol-designer/src/components/modals/LabwareUploadMessageModal/index.ts deleted file mode 100644 index dae8d981612..00000000000 --- a/protocol-designer/src/components/modals/LabwareUploadMessageModal/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import assert from 'assert' -import { connect } from 'react-redux' -import { - selectors as labwareDefSelectors, - actions as labwareDefActions, -} from '../../../labware-defs' -import { - LabwareUploadMessageModal as LabwareUploadMessageModalComponent, - LabwareUploadMessageModalProps, -} from './LabwareUploadMessageModal' -import { Dispatch } from 'redux' -import { BaseState } from '../../../types' - -type Props = LabwareUploadMessageModalProps -interface SP { - message: Props['message'] -} - -function mapStateToProps(state: BaseState): SP { - return { - message: labwareDefSelectors.getLabwareUploadMessage(state), - } -} - -function mergeProps( - stateProps: SP, - dispatchProps: { - dispatch: Dispatch - } -): Props { - const { dispatch } = dispatchProps - const { message } = stateProps - return { - ...stateProps, - overwriteLabwareDef: () => { - if (message && message.messageType === 'ASK_FOR_LABWARE_OVERWRITE') { - dispatch( - labwareDefActions.replaceCustomLabwareDef({ - defURIToOverwrite: message.defURIToOverwrite, - newDef: message.newDef, - isOverwriteMismatched: message.isOverwriteMismatched, - }) - ) - } else { - assert( - false, - `labware def should only be overwritten when messageType is ASK_FOR_LABWARE_OVERWRITE. Got ${String( - message?.messageType - )}` - ) - } - }, - dismissModal: () => - dispatch(labwareDefActions.dismissLabwareUploadMessage()), - } -} - -export const LabwareUploadMessageModal = connect( - mapStateToProps, - null, - mergeProps -)(LabwareUploadMessageModalComponent) diff --git a/protocol-designer/src/components/steplist/StartingDeckStateTerminalItem.tsx b/protocol-designer/src/components/steplist/StartingDeckStateTerminalItem.tsx index 0c40c7c0337..7a4009665ab 100644 --- a/protocol-designer/src/components/steplist/StartingDeckStateTerminalItem.tsx +++ b/protocol-designer/src/components/steplist/StartingDeckStateTerminalItem.tsx @@ -1,20 +1,14 @@ -import { connect } from 'react-redux' import * as React from 'react' -import { TerminalItem } from './TerminalItem' +import { useSelector } from 'react-redux' import { PDListItem } from '../lists' import { START_TERMINAL_TITLE } from '../../constants' -import { BaseState } from '../../types' import { START_TERMINAL_ITEM_ID } from '../../steplist' import { selectors as stepFormSelectors } from '../../step-forms' +import { TerminalItem } from './TerminalItem' -interface Props { - showHint: boolean -} - -type SP = Props - -function StartingDeckStateTerminalItemComponent(props: Props): JSX.Element { - const { showHint } = props +export function StartingDeckStateTerminalItem(): JSX.Element { + const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) + const showHint = Object.keys(labwareEntities).length <= 1 const hintContents = ( Add labware to the deck and assign liquids to the wells they start in @@ -27,14 +21,3 @@ function StartingDeckStateTerminalItemComponent(props: Props): JSX.Element { ) } - -function mapStateToProps(state: BaseState): SP { - // since default-trash counts as 1, labwareCount <= 1 means "user did not add labware" - const noLabware = - Object.keys(stepFormSelectors.getLabwareEntities(state)).length <= 1 - return { showHint: noLabware } -} - -export const StartingDeckStateTerminalItem = connect(mapStateToProps)( - StartingDeckStateTerminalItemComponent -) diff --git a/protocol-designer/src/components/steplist/StepList.tsx b/protocol-designer/src/components/steplist/StepList.tsx index da83c2afb16..5628d5b80b0 100644 --- a/protocol-designer/src/components/steplist/StepList.tsx +++ b/protocol-designer/src/components/steplist/StepList.tsx @@ -1,17 +1,23 @@ import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' import { SidePanel } from '@opentrons/components' -import { PresavedStepItem } from './PresavedStepItem' -import { StartingDeckStateTerminalItem } from './StartingDeckStateTerminalItem' -import { TerminalItem } from './TerminalItem' import { END_TERMINAL_TITLE } from '../../constants' -import { END_TERMINAL_ITEM_ID } from '../../steplist' - +import { + END_TERMINAL_ITEM_ID, + actions as steplistActions, +} from '../../steplist' +import { actions as stepsActions, getIsMultiSelectMode } from '../../ui/steps' +import { selectors as stepFormSelectors } from '../../step-forms' import { StepCreationButton } from '../StepCreationButton' import { DraggableStepItems } from './DraggableStepItems' import { MultiSelectToolbar } from './MultiSelectToolbar' +import { PresavedStepItem } from './PresavedStepItem' +import { StartingDeckStateTerminalItem } from './StartingDeckStateTerminalItem' +import { TerminalItem } from './TerminalItem' -import { StepIdType } from '../../form-types' +import type { StepIdType } from '../../form-types' +import type { ThunkDispatch } from '../../types' export interface StepListProps { isMultiSelectMode?: boolean | null @@ -20,9 +26,12 @@ export interface StepListProps { reorderSteps: (steps: StepIdType[]) => unknown } -export class StepList extends React.Component { - handleKeyDown: (e: KeyboardEvent) => void = e => { - const { reorderSelectedStep } = this.props +export const StepList = (): JSX.Element => { + const orderedStepIds = useSelector(stepFormSelectors.getOrderedStepIds) + const isMultiSelectMode = useSelector(getIsMultiSelectMode) + const dispatch: ThunkDispatch = useDispatch() + + const handleKeyDown: (e: KeyboardEvent) => void = e => { const key = e.key const altIsPressed = e.altKey @@ -34,34 +43,36 @@ export class StepList extends React.Component { delta = 1 } if (!delta) return - reorderSelectedStep(delta) + dispatch(stepsActions.reorderSelectedStep(delta)) } } - componentDidMount(): void { - global.addEventListener('keydown', this.handleKeyDown, false) - } + React.useEffect(() => { + const onKeyDown = (e: KeyboardEvent): void => { + handleKeyDown(e) + } - componentWillUnmount(): void { - global.removeEventListener('keydown', this.handleKeyDown, false) - } + global.addEventListener('keydown', onKeyDown, false) - render(): React.ReactNode { - return ( - - + return () => { + global.removeEventListener('keydown', onKeyDown, false) + } + }, []) - - - - - - - ) - } + return ( + + + + + { + dispatch(steplistActions.reorderSteps(stepIds)) + }} + /> + + + + + ) } diff --git a/protocol-designer/src/containers/ConnectedMainPanel.tsx b/protocol-designer/src/containers/ConnectedMainPanel.tsx index febe576eea2..0bb424298e3 100644 --- a/protocol-designer/src/containers/ConnectedMainPanel.tsx +++ b/protocol-designer/src/containers/ConnectedMainPanel.tsx @@ -1,7 +1,7 @@ import * as React from 'react' -import { connect } from 'react-redux' +import { useSelector } from 'react-redux' import { Splash } from '@opentrons/components' -import { START_TERMINAL_ITEM_ID, TerminalItemId } from '../steplist' +import { START_TERMINAL_ITEM_ID } from '../steplist' import { Portal as MainPageModalPortal } from '../components/portals/MainPageModalPortal' import { DeckSetupManager } from '../components/DeckSetupManager' import { SettingsPage } from '../components/SettingsPage' @@ -9,23 +9,21 @@ import { FilePage } from '../components/FilePage' import { LiquidsPage } from '../components/LiquidsPage' import { Hints } from '../components/Hints' import { LiquidPlacementModal } from '../components/LiquidPlacementModal' -import { LabwareSelectionModal } from '../components/LabwareSelectionModal' +import { LabwareSelectionModal } from '../components/LabwareSelectionModal/LabwareSelectionModal' import { FormManager } from '../components/FormManager' import { Alerts } from '../components/alerts/Alerts' - import { getSelectedTerminalItemId } from '../ui/steps' import { selectors as labwareIngredSelectors } from '../labware-ingred/selectors' -import { selectors, Page } from '../navigation' -import { BaseState } from '../types' +import { selectors } from '../navigation' -interface Props { - page: Page - selectedTerminalItemId: TerminalItemId | null | undefined - ingredSelectionMode: boolean -} +export function MainPanel(): JSX.Element { + const page = useSelector(selectors.getCurrentPage) + const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) + const selectedLabware = useSelector( + labwareIngredSelectors.getSelectedLabwareId + ) + const ingredSelectionMode = selectedLabware != null -function MainPanelComponent(props: Props): JSX.Element { - const { page, selectedTerminalItemId, ingredSelectionMode } = props switch (page) { case 'file-splash': return @@ -55,14 +53,3 @@ function MainPanelComponent(props: Props): JSX.Element { } } } - -function mapStateToProps(state: BaseState): Props { - return { - page: selectors.getCurrentPage(state), - selectedTerminalItemId: getSelectedTerminalItemId(state), - ingredSelectionMode: - labwareIngredSelectors.getSelectedLabwareId(state) != null, - } -} - -export const ConnectedMainPanel = connect(mapStateToProps)(MainPanelComponent) diff --git a/protocol-designer/src/containers/ConnectedSidebar.tsx b/protocol-designer/src/containers/ConnectedSidebar.tsx index 092c288f4d1..914dade085f 100644 --- a/protocol-designer/src/containers/ConnectedSidebar.tsx +++ b/protocol-designer/src/containers/ConnectedSidebar.tsx @@ -1,31 +1,25 @@ import * as React from 'react' -import { connect } from 'react-redux' -import { selectors, Page } from '../navigation' +import { useSelector } from 'react-redux' +import { selectors } from '../navigation' import { selectors as labwareIngredSelectors } from '../labware-ingred/selectors' -import { ConnectedStepList } from './ConnectedStepList' -import { IngredientsList } from './IngredientsList' +import { StepList } from '../components/steplist' import { FileSidebar } from '../components/FileSidebar/FileSidebar' import { LiquidsSidebar } from '../components/LiquidsSidebar' import { SettingsSidebar } from '../components/SettingsPage' +import { IngredientsList } from '../components/IngredientsList' -import { BaseState } from '../types' - -interface Props { - page: Page - liquidPlacementMode: boolean -} - -function Sidebar(props: Props): JSX.Element | null { - switch (props.page) { +export function Sidebar(): JSX.Element | null { + const page = useSelector(selectors.getCurrentPage) + const selectedLabware = useSelector( + labwareIngredSelectors.getSelectedLabwareId + ) + const liquidPlacementMode = selectedLabware != null + switch (page) { case 'liquids': return case 'steplist': - return props.liquidPlacementMode ? ( - - ) : ( - - ) + return liquidPlacementMode ? : case 'file-splash': case 'file-detail': return @@ -35,16 +29,3 @@ function Sidebar(props: Props): JSX.Element | null { } return null } - -function mapStateToProps(state: BaseState): Props { - const page = selectors.getCurrentPage(state) - const liquidPlacementMode = - labwareIngredSelectors.getSelectedLabwareId(state) != null - - return { - page, - liquidPlacementMode, - } -} - -export const ConnectedSidebar = connect(mapStateToProps)(Sidebar) diff --git a/protocol-designer/src/containers/ConnectedStepList.ts b/protocol-designer/src/containers/ConnectedStepList.ts deleted file mode 100644 index 20b2218c036..00000000000 --- a/protocol-designer/src/containers/ConnectedStepList.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { connect } from 'react-redux' -import { BaseState, ThunkDispatch } from '../types' -import { StepIdType } from '../form-types' -import { actions as steplistActions } from '../steplist' -import { actions as stepsActions, getIsMultiSelectMode } from '../ui/steps' -import { selectors as stepFormSelectors } from '../step-forms' -import { StepList, StepListProps } from '../components/steplist' - -type Props = StepListProps -interface SP { - orderedStepIds: Props['orderedStepIds'] - isMultiSelectMode: boolean | null | undefined -} -type DP = Omit - -function mapStateToProps(state: BaseState): SP { - return { - orderedStepIds: stepFormSelectors.getOrderedStepIds(state), - isMultiSelectMode: getIsMultiSelectMode(state), - } -} - -function mapDispatchToProps(dispatch: ThunkDispatch): DP { - return { - reorderSelectedStep: (delta: number) => { - dispatch(stepsActions.reorderSelectedStep(delta)) - }, - reorderSteps: (stepIds: StepIdType[]) => { - dispatch(steplistActions.reorderSteps(stepIds)) - }, - } -} - -export const ConnectedStepList = connect( - mapStateToProps, - mapDispatchToProps -)(StepList) diff --git a/protocol-designer/src/containers/IngredientsList.ts b/protocol-designer/src/containers/IngredientsList.ts deleted file mode 100644 index af2a3255613..00000000000 --- a/protocol-designer/src/containers/IngredientsList.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Dispatch } from 'redux' -import { selectors as labwareIngredSelectors } from '../labware-ingred/selectors' -import * as wellSelectionSelectors from '../top-selectors/well-contents' -import { removeWellsContents } from '../labware-ingred/actions' -import { BaseState } from '../types' -import { IngredientsList as IngredientsListComponent } from '../components/IngredientsList' -import { SelectedContainerId } from '../labware-ingred/reducers' -type Props = React.ComponentProps -type SP = Omit & { - _labwareId: string | null | undefined -} - -function mapStateToProps(state: BaseState): SP { - const selectedLabwareId: SelectedContainerId = labwareIngredSelectors.getSelectedLabwareId( - state - ) as SelectedContainerId - const labwareWellContents = - (selectedLabwareId && - labwareIngredSelectors.getLiquidsByLabwareId(state)[selectedLabwareId]) || - {} - return { - liquidGroupsById: labwareIngredSelectors.getLiquidGroupsById(state), - labwareWellContents, - selectedIngredientGroupId: wellSelectionSelectors.getSelectedWellsCommonIngredId( - state - ), - selected: false, - _labwareId: selectedLabwareId, - } -} - -function mergeProps( - stateProps: SP, - dispatchProps: { - dispatch: Dispatch - } -): Props { - const { dispatch } = dispatchProps - const { _labwareId, ...passThruProps } = stateProps - return { - ...passThruProps, - removeWellsContents: args => { - if (_labwareId) { - dispatch(removeWellsContents({ ...args, labwareId: _labwareId })) - } - }, - } -} - -export const IngredientsList = connect( - mapStateToProps, - null, - mergeProps -)(IngredientsListComponent)