Skip to content

Commit

Permalink
feat(odd): instruments dashboard; attach, detach, and calibrate for p…
Browse files Browse the repository at this point in the history
…ipette and grippers (#12448)

Create instruments dashboard basic functionality and wire up instrument detail pages for attaching,
detaching, and calibrating grippers and pipettes.

Re: RLIQ-355

Co-authored-by: koji <[email protected]>
  • Loading branch information
b-cooper and koji committed Apr 10, 2023
1 parent 6d14c58 commit 0b04510
Show file tree
Hide file tree
Showing 25 changed files with 860 additions and 476 deletions.
15 changes: 11 additions & 4 deletions app/src/App/OnDeviceDisplayApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import { ProtocolDashboard } from '../pages/OnDeviceDisplay/ProtocolDashboard'
import { ProtocolDetails } from '../pages/OnDeviceDisplay/ProtocolDetails'
import { RunningProtocol } from '../pages/OnDeviceDisplay/RunningProtocol'
import { UpdateRobot } from '../pages/OnDeviceDisplay/UpdateRobot'
import { AttachInstrumentsDashboard } from '../pages/OnDeviceDisplay/AttachInstrumentsDashboard'
import { InstrumentsDashboard } from '../pages/OnDeviceDisplay/InstrumentsDashboard'
import { InstrumentDetail } from '../pages/OnDeviceDisplay/InstrumentDetail'
import { Welcome } from '../pages/OnDeviceDisplay/Welcome'
import { PortalRoot as ModalPortalRoot } from './portal'
import { getOnDeviceDisplaySettings } from '../redux/config'
Expand Down Expand Up @@ -107,11 +108,17 @@ export const onDeviceDisplayRoutes: RouteProps[] = [
path: '/protocols/:runId/run',
},
{
Component: AttachInstrumentsDashboard,
Component: InstrumentsDashboard,
exact: true,
name: 'Instruments',
navLinkTo: '/attach-instruments',
path: '/attach-instruments',
navLinkTo: '/instruments',
path: '/instruments',
},
{
Component: InstrumentDetail,
exact: true,
name: 'Instrument Detail',
path: '/instruments/:mount',
},
// insert attach instruments subroutes
{
Expand Down
18 changes: 9 additions & 9 deletions app/src/App/__tests__/OnDeviceDisplayApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ConnectViaEthernet } from '../../pages/OnDeviceDisplay/ConnectViaEthern
import { ConnectViaUSB } from '../../pages/OnDeviceDisplay/ConnectViaUSB'
import { ConnectViaWifi } from '../../pages/OnDeviceDisplay/ConnectViaWifi'
import { NetworkSetupMenu } from '../../pages/OnDeviceDisplay/NetworkSetupMenu'
import { AttachInstrumentsDashboard } from '../../pages/OnDeviceDisplay/AttachInstrumentsDashboard'
import { InstrumentsDashboard } from '../../pages/OnDeviceDisplay/InstrumentsDashboard'
import { RobotDashboard } from '../../pages/OnDeviceDisplay/RobotDashboard'
import { RobotSettingsDashboard } from '../../pages/OnDeviceDisplay/RobotSettingsDashboard'
import { ProtocolDashboard } from '../../pages/OnDeviceDisplay/ProtocolDashboard'
Expand All @@ -25,7 +25,7 @@ jest.mock('../../pages/OnDeviceDisplay/RobotDashboard')
jest.mock('../../pages/OnDeviceDisplay/RobotSettingsDashboard')
jest.mock('../../pages/OnDeviceDisplay/ProtocolDashboard')
jest.mock('../../pages/OnDeviceDisplay/ProtocolSetup')
jest.mock('../../pages/OnDeviceDisplay/AttachInstrumentsDashboard')
jest.mock('../../pages/OnDeviceDisplay/InstrumentsDashboard')
jest.mock('../../pages/OnDeviceDisplay/RunningProtocol')

const mockNetworkSetupMenu = NetworkSetupMenu as jest.MockedFunction<
Expand All @@ -52,8 +52,8 @@ const mockProtocolSetup = ProtocolSetup as jest.MockedFunction<
const mockRobotSettingsDashboard = RobotSettingsDashboard as jest.MockedFunction<
typeof RobotSettingsDashboard
>
const mockAttachInstrumentsDashboard = AttachInstrumentsDashboard as jest.MockedFunction<
typeof AttachInstrumentsDashboard
const mockInstrumentsDashboard = InstrumentsDashboard as jest.MockedFunction<
typeof InstrumentsDashboard
>
const mockRunningProtocol = RunningProtocol as jest.MockedFunction<
typeof RunningProtocol
Expand All @@ -70,8 +70,8 @@ const render = (path = '/') => {

describe('OnDeviceDisplayApp', () => {
beforeEach(() => {
mockAttachInstrumentsDashboard.mockReturnValue(
<div>Mock AttachInstrumentsDashboard</div>
mockInstrumentsDashboard.mockReturnValue(
<div>Mock InstrumentsDashboard</div>
)
mockNetworkSetupMenu.mockReturnValue(<div>Mock NetworkSetupMenu</div>)
mockConnectViaEthernet.mockReturnValue(<div>Mock ConnectViaEthernet</div>)
Expand Down Expand Up @@ -125,9 +125,9 @@ describe('OnDeviceDisplayApp', () => {
const [{ getByText }] = render('/robot-settings')
getByText('Mock RobotSettingsDashboard')
})
it('renders a AttachInstrumentsDashboard component from /attach-instruments', () => {
const [{ getByText }] = render('/attach-instruments')
getByText('Mock AttachInstrumentsDashboard')
it('renders a InstrumentsDashboard component from /instruments', () => {
const [{ getByText }] = render('/instruments')
getByText('Mock InstrumentsDashboard')
})
it('renders a RunningProtocol component from /protocols/:runId/run', () => {
const [{ getByText }] = render('/protocols/my-run-id/run')
Expand Down
2 changes: 2 additions & 0 deletions app/src/assets/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import device_settings from './device_settings.json'
import devices_landing from './devices_landing.json'
import gripper_wizard_flows from './gripper_wizard_flows.json'
import heater_shaker from './heater_shaker.json'
import instruments_dashboard from './instruments_dashboard.json'
import labware_details from './labware_details.json'
import labware_landing from './labware_landing.json'
import labware_position_check from './labware_position_check.json'
Expand Down Expand Up @@ -36,6 +37,7 @@ export const en = {
devices_landing,
gripper_wizard_flows,
heater_shaker,
instruments_dashboard,
labware_details,
labware_landing,
labware_position_check,
Expand Down
7 changes: 7 additions & 0 deletions app/src/assets/localization/en/instruments_dashboard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"detach": "detach",
"firmware_version": "firmware version",
"last_calibrated": "last calibrated",
"recalibrate": "recalibrate",
"serial_number": "serial number"
}
67 changes: 26 additions & 41 deletions app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import {
Btn,
COLORS,
DIRECTION_ROW,
Flex,
Icon,
SPACING,
styleProps,
TYPOGRAPHY,
} from '@opentrons/components'
import { StyledText } from '../../text'
Expand All @@ -27,8 +25,7 @@ interface MediumButtonProps extends StyleProps {
buttonType?: MediumButtonTypes
disabled?: boolean
iconName?: IconName
onClick: () => void
width?: string
onClick: React.MouseEventHandler
}

export function MediumButton(props: MediumButtonProps): JSX.Element {
Expand All @@ -37,13 +34,8 @@ export function MediumButton(props: MediumButtonProps): JSX.Element {
buttonType = 'primary',
disabled = false,
iconName,
onClick,
width,
...buttonProps
} = props
const buttonProps = {
disabled,
onClick,
}

const MEDIUM_BUTTON_PROPS_BY_TYPE: Record<
MediumButtonTypes,
Expand Down Expand Up @@ -103,10 +95,6 @@ export function MediumButton(props: MediumButtonProps): JSX.Element {
box-shadow: none;
color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor};
cursor: default;
text-align: ${TYPOGRAPHY.textAlignLeft};
text-transform: ${TYPOGRAPHY.textTransformNone};
${styleProps}
&:focus {
background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType]
Expand Down Expand Up @@ -136,43 +124,40 @@ export function MediumButton(props: MediumButtonProps): JSX.Element {
`
return (
<Btn
{...buttonProps}
disabled={disabled}
css={MEDIUM_BUTTON_STYLE}
aria-label={`MediumButton_${buttonType}`}
display="flex"
alignItems={ALIGN_CENTER}
flexDirection={DIRECTION_ROW}
gridGap={SPACING.spacingSM}
padding={
iconName !== undefined
? `${SPACING.spacingM} ${SPACING.spacing5}`
: `${SPACING.spacingM} ${SPACING.spacingXXL}`
}
width={width}
{...buttonProps}
>
<Flex
alignItems={ALIGN_CENTER}
flexDirection={DIRECTION_ROW}
gridGap={SPACING.spacingSM}
{iconName !== undefined && (
<Icon
name={iconName ?? 'play'}
aria-label={`MediumButton_${iconName ?? 'play'}`}
color={
disabled
? COLORS.darkBlack_sixty
: MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].iconColor
}
width={SPACING.spacingXL}
height={SPACING.spacingXL}
/>
)}
<StyledText
fontSize={TYPOGRAPHY.fontSize28}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
lineHeight={TYPOGRAPHY.lineHeight36}
>
{iconName !== undefined && (
<Icon
name={iconName ?? 'play'}
aria-label={`MediumButton_${iconName ?? 'play'}`}
color={
disabled
? COLORS.darkBlack_sixty
: MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].iconColor
}
width={SPACING.spacingXL}
height={SPACING.spacingXL}
/>
)}
<StyledText
fontSize={TYPOGRAPHY.fontSize28}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
lineHeight={TYPOGRAPHY.lineHeight36}
>
{buttonText}
</StyledText>
</Flex>
{buttonText}
</StyledText>
</Btn>
)
}
2 changes: 1 addition & 1 deletion app/src/atoms/buttons/OnDeviceDisplay/SmallButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type SmallButtonCategory = 'default' | 'rounded'

type IconPlacement = 'startIcon' | 'endIcon'
interface SmallButtonProps extends StyleProps {
onClick: () => void
onClick: React.MouseEventHandler
buttonType: SmallButtonTypes
buttonText: React.ReactNode
iconPlacement?: IconPlacement
Expand Down
2 changes: 1 addition & 1 deletion app/src/molecules/WizardHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { getIsOnDevice } from '../../redux/config'

interface WizardHeaderProps {
title: string
onExit?: (() => void) | null
onExit?: React.MouseEventHandler | null
totalSteps?: number
currentStep?: number | null
exitDisabled?: boolean
Expand Down
48 changes: 34 additions & 14 deletions app/src/organisms/GripperWizardFlows/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { UseMutateFunction } from 'react-query'
import { useConditionalConfirm } from '@opentrons/components'
import {
useConditionalConfirm,
Flex,
DIRECTION_COLUMN,
POSITION_ABSOLUTE,
COLORS,
} from '@opentrons/components'
import {
useCreateRunMutation,
useStopRunMutation,
} from '@opentrons/react-api-client'
import { ModalShell } from '../../molecules/Modal'
import { Portal } from '../../App/portal'
import { WizardHeader } from '../../molecules/WizardHeader'
import { getIsOnDevice } from '../../redux/config'
import {
useChainRunCommands,
useCreateRunCommandMutation,
Expand Down Expand Up @@ -113,6 +121,7 @@ export const GripperWizard = (
isRobotMoving,
createRunCommand,
} = props
const isOnDevice = useSelector(getIsOnDevice)
const { t } = useTranslation('gripper_wizard_flows')
const gripperWizardSteps = getGripperWizardSteps(flowType)
const [currentStepIndex, setCurrentStepIndex] = React.useState<number>(0)
Expand Down Expand Up @@ -210,21 +219,32 @@ export const GripperWizard = (
handleExit = handleCleanUpAndClose
}

const wizardHeader = (
<WizardHeader
title={titleByFlowType[flowType]}
currentStep={currentStepIndex}
totalSteps={totalStepCount}
onExit={handleExit}
/>
)

return (
<Portal level="top">
<ModalShell
width="48rem"
header={
<WizardHeader
title={titleByFlowType[flowType]}
currentStep={currentStepIndex}
totalSteps={totalStepCount}
onExit={handleExit}
/>
}
>
{modalContent}
</ModalShell>
{Boolean(isOnDevice) ? (
<Flex
flexDirection={DIRECTION_COLUMN}
width="100%"
position={POSITION_ABSOLUTE}
backgroundColor={COLORS.white}
>
{wizardHeader}
{modalContent}
</Flex>
) : (
<ModalShell width="48rem" header={wizardHeader}>
{modalContent}
</ModalShell>
)}
</Portal>
)
}
Loading

0 comments on commit 0b04510

Please sign in to comment.