|
| 1 | +import { useTranslation } from 'react-i18next' |
| 2 | +import { useDispatch } from 'react-redux' |
| 3 | +import mapValues from 'lodash/mapValues' |
| 4 | + |
| 5 | +import { |
| 6 | + ALIGN_CENTER, |
| 7 | + Btn, |
| 8 | + COLORS, |
| 9 | + DIRECTION_COLUMN, |
| 10 | + DIRECTION_ROW, |
| 11 | + EmptySelectorButton, |
| 12 | + Flex, |
| 13 | + Icon, |
| 14 | + JUSTIFY_SPACE_BETWEEN, |
| 15 | + ListItem, |
| 16 | + SPACING, |
| 17 | + StyledText, |
| 18 | + TYPOGRAPHY, |
| 19 | +} from '@opentrons/components' |
| 20 | +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' |
| 21 | + |
| 22 | +import { PipetteInfoItem } from '../PipetteInfoItem' |
| 23 | +import { changeSavedStepForm } from '../../steplist/actions' |
| 24 | +import { deletePipettes } from '../../step-forms/actions' |
| 25 | +import { deleteContainer } from '../../labware-ingred/actions' |
| 26 | +import { toggleIsGripperRequired } from '../../step-forms/actions/additionalItems' |
| 27 | +import { getSectionsFromPipetteName } from './utils' |
| 28 | +import { INITIAL_DECK_SETUP_STEP_ID } from '../../constants' |
| 29 | +import { LINK_BUTTON_STYLE } from '../../atoms' |
| 30 | + |
| 31 | +import type { Dispatch, SetStateAction } from 'react' |
| 32 | +import type { AdditionalEquipmentName } from '@opentrons/step-generation' |
| 33 | +import type { PipetteMount, RobotType } from '@opentrons/shared-data' |
| 34 | +import type { |
| 35 | + AllTemporalPropertiesForTimelineFrame, |
| 36 | + PipetteOnDeck, |
| 37 | +} from '../../step-forms' |
| 38 | +import type { |
| 39 | + Gen, |
| 40 | + PipetteType, |
| 41 | +} from '../../pages/CreateNewProtocolWizard/types' |
| 42 | +import type { ThunkDispatch } from '../../types' |
| 43 | + |
| 44 | +interface Gripper { |
| 45 | + name: AdditionalEquipmentName |
| 46 | + id: string |
| 47 | + location?: string |
| 48 | +} |
| 49 | + |
| 50 | +interface PipetteOverviewProps { |
| 51 | + has96Channel: boolean |
| 52 | + pipettes: AllTemporalPropertiesForTimelineFrame['pipettes'] |
| 53 | + labware: AllTemporalPropertiesForTimelineFrame['labware'] |
| 54 | + robotType: RobotType |
| 55 | + setPage: Dispatch<SetStateAction<'add' | 'overview'>> |
| 56 | + setMount: Dispatch<SetStateAction<PipetteMount>> |
| 57 | + setPipetteType: Dispatch<SetStateAction<PipetteType | null>> |
| 58 | + setPipetteGen: Dispatch<SetStateAction<Gen | 'flex'>> |
| 59 | + setPipetteVolume: Dispatch<SetStateAction<string | null>> |
| 60 | + setSelectedTips: Dispatch<SetStateAction<string[]>> |
| 61 | + leftPipette?: PipetteOnDeck |
| 62 | + rightPipette?: PipetteOnDeck |
| 63 | + gripper?: Gripper |
| 64 | +} |
| 65 | + |
| 66 | +export function PipetteOverview({ |
| 67 | + has96Channel, |
| 68 | + pipettes, |
| 69 | + labware, |
| 70 | + robotType, |
| 71 | + setPage, |
| 72 | + setMount, |
| 73 | + setPipetteType, |
| 74 | + setPipetteGen, |
| 75 | + setPipetteVolume, |
| 76 | + setSelectedTips, |
| 77 | + leftPipette, |
| 78 | + rightPipette, |
| 79 | + gripper, |
| 80 | +}: PipetteOverviewProps): JSX.Element { |
| 81 | + const { i18n, t } = useTranslation('create_new_protocol') |
| 82 | + const dispatch = useDispatch<ThunkDispatch<any>>() |
| 83 | + |
| 84 | + const swapPipetteUpdate = mapValues(pipettes, pipette => { |
| 85 | + if (!pipette.mount) return pipette.mount |
| 86 | + return pipette.mount === 'left' ? 'right' : 'left' |
| 87 | + }) |
| 88 | + |
| 89 | + const targetPipetteMount = leftPipette == null ? 'left' : 'right' |
| 90 | + |
| 91 | + const rightInfo = |
| 92 | + rightPipette != null |
| 93 | + ? getSectionsFromPipetteName(rightPipette.name, rightPipette.spec) |
| 94 | + : null |
| 95 | + const leftInfo = |
| 96 | + leftPipette != null |
| 97 | + ? getSectionsFromPipetteName(leftPipette.name, leftPipette.spec) |
| 98 | + : null |
| 99 | + |
| 100 | + const previousLeftPipetteTipracks = Object.values(labware) |
| 101 | + .filter(lw => lw.def.parameters.isTiprack) |
| 102 | + .filter(tip => leftPipette?.tiprackDefURI.includes(tip.labwareDefURI)) |
| 103 | + const previousRightPipetteTipracks = Object.values(labware) |
| 104 | + .filter(lw => lw.def.parameters.isTiprack) |
| 105 | + .filter(tip => rightPipette?.tiprackDefURI.includes(tip.labwareDefURI)) |
| 106 | + |
| 107 | + return ( |
| 108 | + <Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing24}> |
| 109 | + <Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}> |
| 110 | + <Flex justifyContent={JUSTIFY_SPACE_BETWEEN} alignItems={ALIGN_CENTER}> |
| 111 | + <StyledText desktopStyle="bodyLargeSemiBold"> |
| 112 | + {t('your_pipettes')} |
| 113 | + </StyledText> |
| 114 | + {has96Channel || |
| 115 | + (leftPipette == null && rightPipette == null) ? null : ( |
| 116 | + <Btn |
| 117 | + css={LINK_BUTTON_STYLE} |
| 118 | + onClick={() => |
| 119 | + dispatch( |
| 120 | + changeSavedStepForm({ |
| 121 | + stepId: INITIAL_DECK_SETUP_STEP_ID, |
| 122 | + update: { |
| 123 | + pipetteLocationUpdate: swapPipetteUpdate, |
| 124 | + }, |
| 125 | + }) |
| 126 | + ) |
| 127 | + } |
| 128 | + > |
| 129 | + <Flex flexDirection={DIRECTION_ROW}> |
| 130 | + <Icon |
| 131 | + name="swap-horizontal" |
| 132 | + size="1rem" |
| 133 | + transform="rotate(90deg)" |
| 134 | + /> |
| 135 | + <StyledText desktopStyle="captionSemiBold"> |
| 136 | + {t('swap_pipette_mounts')} |
| 137 | + </StyledText> |
| 138 | + </Flex> |
| 139 | + </Btn> |
| 140 | + )} |
| 141 | + </Flex> |
| 142 | + <Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}> |
| 143 | + {leftPipette?.tiprackDefURI != null && leftInfo != null ? ( |
| 144 | + <PipetteInfoItem |
| 145 | + mount="left" |
| 146 | + pipetteName={leftPipette.name} |
| 147 | + tiprackDefURIs={leftPipette.tiprackDefURI} |
| 148 | + editClick={() => { |
| 149 | + setPage('add') |
| 150 | + setMount('left') |
| 151 | + setPipetteType(leftInfo.type) |
| 152 | + setPipetteGen(leftInfo.gen) |
| 153 | + setPipetteVolume(leftInfo.volume) |
| 154 | + setSelectedTips(leftPipette.tiprackDefURI as string[]) |
| 155 | + }} |
| 156 | + cleanForm={() => { |
| 157 | + dispatch(deletePipettes([leftPipette.id as string])) |
| 158 | + previousLeftPipetteTipracks.forEach(tip => |
| 159 | + dispatch(deleteContainer({ labwareId: tip.id })) |
| 160 | + ) |
| 161 | + }} |
| 162 | + /> |
| 163 | + ) : null} |
| 164 | + {rightPipette?.tiprackDefURI != null && rightInfo != null ? ( |
| 165 | + <PipetteInfoItem |
| 166 | + mount="right" |
| 167 | + pipetteName={rightPipette.name} |
| 168 | + tiprackDefURIs={rightPipette.tiprackDefURI} |
| 169 | + editClick={() => { |
| 170 | + setPage('add') |
| 171 | + setMount('right') |
| 172 | + setPipetteType(rightInfo.type) |
| 173 | + setPipetteGen(rightInfo.gen) |
| 174 | + setPipetteVolume(rightInfo.volume) |
| 175 | + setSelectedTips(rightPipette.tiprackDefURI as string[]) |
| 176 | + }} |
| 177 | + cleanForm={() => { |
| 178 | + dispatch(deletePipettes([rightPipette.id as string])) |
| 179 | + previousRightPipetteTipracks.forEach(tip => |
| 180 | + dispatch(deleteContainer({ labwareId: tip.id })) |
| 181 | + ) |
| 182 | + }} |
| 183 | + /> |
| 184 | + ) : null} |
| 185 | + {has96Channel || |
| 186 | + (leftPipette != null && rightPipette != null) ? null : ( |
| 187 | + <EmptySelectorButton |
| 188 | + onClick={() => { |
| 189 | + setPage('add') |
| 190 | + setMount(targetPipetteMount) |
| 191 | + }} |
| 192 | + text={t('add_pipette')} |
| 193 | + textAlignment="left" |
| 194 | + iconName="plus" |
| 195 | + /> |
| 196 | + )} |
| 197 | + </Flex> |
| 198 | + </Flex> |
| 199 | + {robotType === FLEX_ROBOT_TYPE ? ( |
| 200 | + <Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}> |
| 201 | + <Flex |
| 202 | + justifyContent={JUSTIFY_SPACE_BETWEEN} |
| 203 | + alignItems={ALIGN_CENTER} |
| 204 | + > |
| 205 | + <StyledText desktopStyle="bodyLargeSemiBold"> |
| 206 | + {t('protocol_overview:your_gripper')} |
| 207 | + </StyledText> |
| 208 | + </Flex> |
| 209 | + <Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}> |
| 210 | + {gripper != null ? ( |
| 211 | + <ListItem type="noActive"> |
| 212 | + <Flex |
| 213 | + padding={SPACING.spacing12} |
| 214 | + justifyContent={JUSTIFY_SPACE_BETWEEN} |
| 215 | + width="100%" |
| 216 | + > |
| 217 | + <Flex |
| 218 | + gridGap={SPACING.spacing4} |
| 219 | + flexDirection={DIRECTION_COLUMN} |
| 220 | + > |
| 221 | + <StyledText desktopStyle="bodyDefaultSemiBold"> |
| 222 | + {t('protocol_overview:extension')} |
| 223 | + </StyledText> |
| 224 | + <StyledText |
| 225 | + desktopStyle="bodyDefaultRegular" |
| 226 | + color={COLORS.grey60} |
| 227 | + > |
| 228 | + {i18n.format(t('gripper'), 'capitalize')} |
| 229 | + </StyledText> |
| 230 | + </Flex> |
| 231 | + <Btn |
| 232 | + css={LINK_BUTTON_STYLE} |
| 233 | + textDecoration={TYPOGRAPHY.textDecorationUnderline} |
| 234 | + padding={SPACING.spacing4} |
| 235 | + onClick={() => { |
| 236 | + dispatch(toggleIsGripperRequired()) |
| 237 | + }} |
| 238 | + > |
| 239 | + <StyledText desktopStyle="bodyDefaultRegular"> |
| 240 | + {t('remove')} |
| 241 | + </StyledText> |
| 242 | + </Btn> |
| 243 | + </Flex> |
| 244 | + </ListItem> |
| 245 | + ) : ( |
| 246 | + <EmptySelectorButton |
| 247 | + onClick={() => { |
| 248 | + dispatch(toggleIsGripperRequired()) |
| 249 | + }} |
| 250 | + text={t('protocol_overview:add_gripper')} |
| 251 | + textAlignment="left" |
| 252 | + iconName="plus" |
| 253 | + /> |
| 254 | + )} |
| 255 | + </Flex> |
| 256 | + </Flex> |
| 257 | + ) : null} |
| 258 | + </Flex> |
| 259 | + ) |
| 260 | +} |
0 commit comments