diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json
index 99b496a3479..fe3f490b1eb 100644
--- a/app/src/assets/localization/en/protocol_setup.json
+++ b/app/src/assets/localization/en/protocol_setup.json
@@ -227,7 +227,8 @@
"resolve": "Resolve",
"restart_setup_and_try": "Restart setup and try using different parameter values.",
"restart_setup": "Restart setup",
- "restore_default": "Restore default values",
+ "restore_defaults": "Restore default values",
+ "restore_default": "Restore default value",
"robot_cal_description": "Robot calibration establishes how the robot knows where it is in relation to the deck. Accurate Robot calibration is essential to run protocols successfully. Robot calibration has 3 parts: Deck calibration, Tip Length calibration and Pipette Offset calibration.",
"robot_cal_help_title": "How Robot Calibration Works",
"robot_calibration_step_description_pipettes_only": "Review required instruments and calibrations for this protocol.",
@@ -265,6 +266,8 @@
"usb_port_connected": "USB Port {{port}}",
"value": "Value",
"values_are_view_only": "Values are view-only",
+ "value_out_of_range_generic": "Value must be in range",
+ "value_out_of_range": "Value must be between {{min}}-{{max}}",
"view_current_offsets": "View current offsets",
"view_moam": "View setup instructions for placing modules of the same type to the robot.",
"view_setup_instructions": "View setup instructions",
diff --git a/app/src/atoms/InputField/index.tsx b/app/src/atoms/InputField/index.tsx
index c1ff5fbeddd..9be59bf1903 100644
--- a/app/src/atoms/InputField/index.tsx
+++ b/app/src/atoms/InputField/index.tsx
@@ -101,15 +101,16 @@ function Input(props: InputFieldProps): JSX.Element {
tooltipText,
...inputProps
} = props
- const error = props.error != null
+ const hasError = props.error != null
const value = props.isIndeterminate ?? false ? '' : props.value ?? ''
const placeHolder = props.isIndeterminate ?? false ? '-' : props.placeholder
const [targetProps, tooltipProps] = useHoverTooltip()
const OUTER_CSS = css`
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
+ grid-gap: ${SPACING.spacing8};
&:focus-within {
- filter: ${error
+ filter: ${hasError
? 'none'
: `drop-shadow(0px 0px 10px ${COLORS.blue50})`};
}
@@ -121,7 +122,7 @@ function Input(props: InputFieldProps): JSX.Element {
background-color: ${COLORS.white};
border-radius: ${BORDERS.borderRadius4};
padding: ${SPACING.spacing8};
- border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey50};
+ border: 1px ${BORDERS.styleSolid} ${hasError ? COLORS.red50 : COLORS.grey50};
font-size: ${TYPOGRAPHY.fontSizeP};
width: 100%;
height: 2rem;
@@ -144,17 +145,20 @@ function Input(props: InputFieldProps): JSX.Element {
}
&:hover {
- border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey60};
+ border: 1px ${BORDERS.styleSolid}
+ ${hasError ? COLORS.red50 : COLORS.grey60};
}
&:focus-visible {
- border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey60};
+ border: 1px ${BORDERS.styleSolid}
+ ${hasError ? COLORS.red50 : COLORS.grey60};
outline: 2px ${BORDERS.styleSolid} ${COLORS.blue50};
outline-offset: 3px;
}
&:focus-within {
- border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.blue50};
+ border: 1px ${BORDERS.styleSolid}
+ ${hasError ? COLORS.red50 : COLORS.blue50};
}
&:disabled {
@@ -168,15 +172,16 @@ function Input(props: InputFieldProps): JSX.Element {
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
height: ${size === 'small' ? '4.25rem' : '5rem'};
- box-shadow: ${error ? BORDERS.shadowBig : 'none'};
+ box-shadow: ${hasError ? BORDERS.shadowBig : 'none'};
font-size: ${TYPOGRAPHY.fontSize28};
padding: ${SPACING.spacing16} ${SPACING.spacing24};
- border: 2px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey50};
+ border: 2px ${BORDERS.styleSolid}
+ ${hasError ? COLORS.red50 : COLORS.grey50};
&:focus-within {
box-shadow: none;
- border: ${error ? '2px' : '3px'} ${BORDERS.styleSolid}
- ${error ? COLORS.red50 : COLORS.blue50};
+ border: ${hasError ? '2px' : '3px'} ${BORDERS.styleSolid}
+ ${hasError ? COLORS.red50 : COLORS.blue50};
}
& input {
@@ -191,19 +196,17 @@ function Input(props: InputFieldProps): JSX.Element {
`
const FORM_BOTTOM_SPACE_STYLE = css`
- padding: ${SPACING.spacing4} 0rem;
+ padding-top: ${SPACING.spacing4};
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
+ padding: ${SPACING.spacing8} 0rem;
padding-bottom: 0;
}
`
const TITLE_STYLE = css`
- color: ${error ? COLORS.red50 : COLORS.black90};
+ color: ${hasError ? COLORS.red50 : COLORS.black90};
padding-bottom: ${SPACING.spacing8};
- font-size: ${TYPOGRAPHY.fontSizeLabel};
- font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
- line-height: ${TYPOGRAPHY.lineHeight12};
- align-text: ${textAlign};
+ text-align: ${textAlign};
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
font-size: ${TYPOGRAPHY.fontSize22};
font-weight: ${TYPOGRAPHY.fontWeightRegular};
@@ -214,9 +217,11 @@ function Input(props: InputFieldProps): JSX.Element {
const ERROR_TEXT_STYLE = css`
color: ${COLORS.red50};
+ padding-top: ${SPACING.spacing4};
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
font-size: ${TYPOGRAPHY.fontSize22};
color: ${COLORS.red50};
+ padding-top: ${SPACING.spacing8};
}
`
@@ -239,9 +244,14 @@ function Input(props: InputFieldProps): JSX.Element {
{title != null ? (
-
+
{title}
-
+
{tooltipText != null ? (
<>
@@ -277,16 +287,6 @@ function Input(props: InputFieldProps): JSX.Element {
{props.units}
) : null}
- {props.error != null ? (
-
- {props.error}
-
- ) : null}
{props.caption != null ? (
) : null}
+ {hasError ? (
+
+ {props.error}
+
+ ) : null}
)
}
diff --git a/app/src/organisms/ChooseProtocolSlideout/index.tsx b/app/src/organisms/ChooseProtocolSlideout/index.tsx
index d743ef17468..c1b2c2eea72 100644
--- a/app/src/organisms/ChooseProtocolSlideout/index.tsx
+++ b/app/src/organisms/ChooseProtocolSlideout/index.tsx
@@ -252,6 +252,7 @@ export function ChooseProtocolSlideoutComponent(
key={runtimeParam.variableName}
type="number"
units={runtimeParam.suffix}
+ placeholder={runtimeParam.default.toString()}
value={value}
title={runtimeParam.displayName}
tooltipText={runtimeParam.description}
diff --git a/app/src/organisms/ChooseRobotSlideout/index.tsx b/app/src/organisms/ChooseRobotSlideout/index.tsx
index 82a7a795363..c8f5a674257 100644
--- a/app/src/organisms/ChooseRobotSlideout/index.tsx
+++ b/app/src/organisms/ChooseRobotSlideout/index.tsx
@@ -393,6 +393,7 @@ export function ChooseRobotSlideout(
key={runtimeParam.variableName}
type="number"
units={runtimeParam.suffix}
+ placeholder={runtimeParam.default.toString()}
value={value}
title={runtimeParam.displayName}
tooltipText={runtimeParam.description}
diff --git a/app/src/organisms/Devices/utils.ts b/app/src/organisms/Devices/utils.ts
index a4d72e0d279..61c133f176b 100644
--- a/app/src/organisms/Devices/utils.ts
+++ b/app/src/organisms/Devices/utils.ts
@@ -9,7 +9,9 @@ import type {
Instruments,
PipetteData,
PipetteOffsetCalibration,
+ RunTimeParameterCreateData,
} from '@opentrons/api-client'
+import type { RunTimeParameter } from '@opentrons/shared-data'
/**
* formats a string if it is in ISO 8601 date format
@@ -89,3 +91,15 @@ export function getShowPipetteCalibrationWarning(
}) ?? false
)
}
+
+export function getRunTimeParameterValuesForRun(
+ runTimeParameters: RunTimeParameter[]
+): RunTimeParameterCreateData {
+ return runTimeParameters.reduce(
+ (acc, param) =>
+ param.value !== param.default
+ ? { ...acc, [param.variableName]: param.value }
+ : acc,
+ {}
+ )
+}
diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx b/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx
index 60e1d7a1b03..1e49e0d8eb0 100644
--- a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx
@@ -29,12 +29,7 @@ export function ChooseEnum({
const { makeSnackbar } = useToaster()
const { t } = useTranslation(['protocol_setup', 'shared'])
- if (parameter.type !== 'str') {
- console.error(
- `parameter type is expected to be a string for parameter ${parameter.displayName}`
- )
- }
- const options = parameter.type === 'str' ? parameter.choices : undefined
+ const options = 'choices' in parameter ? parameter.choices : null
const handleOnClick = (newValue: string | number | boolean): void => {
setParameter(newValue, parameter.variableName)
}
diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx b/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx
new file mode 100644
index 00000000000..da3c34a14c1
--- /dev/null
+++ b/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx
@@ -0,0 +1,164 @@
+import * as React from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+ ALIGN_CENTER,
+ DIRECTION_COLUMN,
+ Flex,
+ SPACING,
+ StyledText,
+ TYPOGRAPHY,
+} from '@opentrons/components'
+import { InputField } from '../../atoms/InputField'
+import { useToaster } from '../ToasterOven'
+import { ChildNavigation } from '../ChildNavigation'
+import { NumericalKeyboard } from '../../atoms/SoftwareKeyboard'
+import type { NumberParameter } from '@opentrons/shared-data'
+
+interface ChooseNumberProps {
+ handleGoBack: () => void
+ parameter: NumberParameter
+ setParameter: (value: number, variableName: string) => void
+}
+
+export function ChooseNumber({
+ handleGoBack,
+ parameter,
+ setParameter,
+}: ChooseNumberProps): JSX.Element | null {
+ const { makeSnackbar } = useToaster()
+
+ const { i18n, t } = useTranslation(['protocol_setup', 'shared'])
+ const keyboardRef = React.useRef(null)
+ const [paramValue, setParamValue] = React.useState(
+ String(parameter.value)
+ )
+
+ // We need to arbitrarily set the value of the keyboard to a string the
+ // same length as the initial parameter value (as string) when the component mounts
+ // so that the delete button operates properly on the exisiting input field value.
+ const [prevKeyboardValue, setPrevKeyboardValue] = React.useState('')
+ React.useEffect(() => {
+ const arbitraryInput = new Array(paramValue).join('*')
+ // @ts-expect-error keyboard should expose for `setInput` method
+ keyboardRef.current?.setInput(arbitraryInput)
+ setPrevKeyboardValue(arbitraryInput)
+ }, [])
+
+ if (parameter.type !== 'int' && parameter.type !== 'float') {
+ console.log(`Incorrect parameter type: ${parameter.type}`)
+ return null
+ }
+ const handleClickGoBack = (newValue: number): void => {
+ if (error != null) {
+ makeSnackbar(t('value_out_of_range_generic'))
+ } else {
+ setParameter(newValue, parameter.variableName)
+ handleGoBack()
+ }
+ }
+
+ const handleKeyboardInput = (e: string): void => {
+ if (prevKeyboardValue.length < e.length) {
+ const lastDigit = e.slice(-1)
+ if (
+ !'.-'.includes(lastDigit) ||
+ (lastDigit === '.' && !paramValue.includes('.')) ||
+ (lastDigit === '-' && paramValue.length === 0)
+ ) {
+ setParamValue(paramValue + lastDigit)
+ }
+ } else {
+ setParamValue(paramValue.slice(0, paramValue.length - 1))
+ }
+ setPrevKeyboardValue(e)
+ }
+
+ const paramValueAsNumber = Number(paramValue)
+ const resetValueDisabled = parameter.default === paramValueAsNumber
+ const { min, max } = parameter
+ const error =
+ paramValue === '' ||
+ Number.isNaN(paramValueAsNumber) ||
+ paramValueAsNumber < min ||
+ paramValueAsNumber > max
+ ? t(`value_out_of_range`, {
+ min: parameter.type === 'int' ? min : min.toFixed(1),
+ max: parameter.type === 'int' ? max : max.toFixed(1),
+ })
+ : null
+
+ return (
+ <>
+ {
+ handleClickGoBack(paramValueAsNumber)
+ }}
+ buttonType="tertiaryLowLight"
+ buttonText={t('restore_default')}
+ onClickButton={() =>
+ resetValueDisabled
+ ? makeSnackbar(t('no_custom_values'))
+ : setParamValue(String(parameter.default))
+ }
+ />
+
+
+
+ {parameter.description}
+
+ {
+ const updatedValue =
+ parameter.type === 'int'
+ ? Math.round(e.target.valueAsNumber)
+ : e.target.valueAsNumber
+ setParamValue(
+ Number.isNaN(updatedValue) ? '' : String(updatedValue)
+ )
+ }}
+ />
+
+
+ {
+ handleKeyboardInput(e)
+ }}
+ />
+
+
+ >
+ )
+}
diff --git a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
index 09dcaf26c47..3ce9169f77f 100644
--- a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
@@ -1,6 +1,6 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
-import { formatRunTimeParameterDefaultValue } from '@opentrons/shared-data'
+import { formatRunTimeParameterValue } from '@opentrons/shared-data'
import {
ALIGN_CENTER,
BORDERS,
@@ -16,7 +16,6 @@ import {
import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { ChildNavigation } from '../ChildNavigation'
import { useToaster } from '../ToasterOven'
-import { mockData } from './index'
import type { SetupScreens } from '../../pages/ProtocolSetup'
@@ -36,8 +35,7 @@ export function ViewOnlyParameters({
makeSnackbar(t('reset_setup'))
}
- // TODO(jr, 3/18/24): remove mockData
- const parameters = mostRecentAnalysis?.runTimeParameters ?? mockData
+ const parameters = mostRecentAnalysis?.runTimeParameters ?? []
return (
<>
@@ -68,9 +66,6 @@ export function ViewOnlyParameters({
{t('value')}
{parameters.map((parameter, index) => {
- // TODO(jr, 3/20/24): plug in the info if the
- // parameter changed from the default
- const hasCustomValue = true
return (
-
- {formatRunTimeParameterDefaultValue(parameter, t)}
+
+ {formatRunTimeParameterValue(parameter, t)}
- {hasCustomValue ? (
+ {parameter.value !== parameter.default ? (
{
})
it('calls the prop if reset default is clicked when the default has changed', () => {
render(props)
- fireEvent.click(screen.getByText('Restore default values'))
+ fireEvent.click(screen.getByText('Restore default value'))
expect(props.setParameter).toHaveBeenCalled()
})
it('calls does not call prop if reset default is clicked when the default has not changed', () => {
@@ -61,7 +61,7 @@ describe('ChooseEnum', () => {
rawValue: 'none',
}
render(props)
- fireEvent.click(screen.getByText('Restore default values'))
+ fireEvent.click(screen.getByText('Restore default value'))
expect(props.setParameter).not.toHaveBeenCalled()
})
it('should render the text and buttons for choice param', () => {
diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx b/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx
index 90893117b6f..6e20fe65658 100644
--- a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx
@@ -60,6 +60,8 @@ describe('ViewOnlyParameters', () => {
fireEvent.click(screen.getAllByRole('button')[0])
expect(props.setSetupScreen).toHaveBeenCalled()
})
- // TODO(jr, 3/20/24):test the update chip when
- // custom value boolean is wired up
+ it('renders chip for updated values', () => {
+ render(props)
+ screen.getByTestId('Chip_USE_GRIPPER')
+ })
})
diff --git a/app/src/organisms/ProtocolSetupParameters/index.tsx b/app/src/organisms/ProtocolSetupParameters/index.tsx
index 1312844b2ab..ac1f3fd700f 100644
--- a/app/src/organisms/ProtocolSetupParameters/index.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/index.tsx
@@ -12,159 +12,18 @@ import {
import { formatRunTimeParameterValue } from '@opentrons/shared-data'
import { ProtocolSetupStep } from '../../pages/ProtocolSetup'
+import { getRunTimeParameterValuesForRun } from '../Devices/utils'
import { ChildNavigation } from '../ChildNavigation'
import { ResetValuesModal } from './ResetValuesModal'
import { ChooseEnum } from './ChooseEnum'
+import { ChooseNumber } from './ChooseNumber'
-import type { RunTimeParameter } from '@opentrons/shared-data'
+import type { NumberParameter, RunTimeParameter } from '@opentrons/shared-data'
import type { LabwareOffsetCreateData } from '@opentrons/api-client'
-export const mockData: RunTimeParameter[] = [
- {
- value: false,
- displayName: 'Dry Run',
- variableName: 'DRYRUN',
- description: 'Is this a dry or wet run? Wet is true, dry is false',
- type: 'bool',
- default: false,
- },
- {
- value: true,
- displayName: 'Use Gripper',
- variableName: 'USE_GRIPPER',
- description: 'For using the gripper.',
- type: 'bool',
- default: true,
- },
- {
- value: true,
- displayName: 'Trash Tips',
- variableName: 'TIP_TRASH',
- description:
- 'to throw tip into the trash or to not throw tip into the trash',
- type: 'bool',
- default: true,
- },
- {
- value: true,
- displayName: 'Deactivate Temperatures',
- variableName: 'DEACTIVATE_TEMP',
- description: 'deactivate temperature on the module',
- type: 'bool',
- default: true,
- },
- {
- value: 4,
- displayName: 'Columns of Samples',
- variableName: 'COLUMNS',
- description: 'How many columns do you want?',
- type: 'int',
- min: 1,
- max: 14,
- default: 4,
- },
- {
- value: 6,
- displayName: 'PCR Cycles',
- variableName: 'PCR_CYCLES',
- description: 'number of PCR cycles on a thermocycler',
- type: 'int',
- min: 1,
- max: 10,
- default: 6,
- },
- {
- value: 6.5,
- displayName: 'EtoH Volume',
- variableName: 'ETOH_VOLUME',
- description: '70% ethanol volume',
- type: 'float',
- suffix: 'mL',
- min: 1.5,
- max: 10.0,
- default: 6.5,
- },
- {
- value: 'none',
- displayName: 'Default Module Offsets',
- variableName: 'DEFAULT_OFFSETS',
- description: 'default module offsets for temp, H-S, and none',
- type: 'str',
- choices: [
- {
- displayName: 'No offsets',
- value: 'none',
- },
- {
- displayName: 'temp offset',
- value: '1',
- },
- {
- displayName: 'heater-shaker offset',
- value: '2',
- },
- ],
- default: 'none',
- },
- {
- value: 'left',
- displayName: 'pipette mount',
- variableName: 'mont',
- description: 'pipette mount',
- type: 'str',
- choices: [
- {
- displayName: 'Left',
- value: 'left',
- },
- {
- displayName: 'Right',
- value: 'right',
- },
- ],
- default: 'left',
- },
- {
- value: 'flex',
- displayName: 'short test case',
- variableName: 'short 2 options',
- description: 'this play 2 short options',
- type: 'str',
- choices: [
- {
- displayName: 'OT-2',
- value: 'ot2',
- },
- {
- displayName: 'Flex',
- value: 'flex',
- },
- ],
- default: 'flex',
- },
- {
- value: 'flex',
- displayName: 'long test case',
- variableName: 'long 2 options',
- description: 'this play 2 long options',
- type: 'str',
- choices: [
- {
- displayName: 'I am kind of long text version',
- value: 'ot2',
- },
- {
- displayName: 'I am kind of long text version. Today is 3/15',
- value: 'flex',
- },
- ],
- default: 'flex',
- },
-]
-
interface ProtocolSetupParametersProps {
protocolId: string
- runTimeParameters?: RunTimeParameter[]
+ runTimeParameters: RunTimeParameter[]
labwareOffsets?: LabwareOffsetCreateData[]
}
@@ -181,23 +40,24 @@ export function ProtocolSetupParameters({
chooseValueScreen,
setChooseValueScreen,
] = React.useState(null)
+ const [
+ showNumericalInputScreen,
+ setShowNumericalInputScreen,
+ ] = React.useState(null)
const [resetValuesModal, showResetValuesModal] = React.useState(
false
)
-
- // todo (nd:04/01/2024): remove mock and look at runTimeParameters prop
- // const parameters = runTimeParameters ?? []
- const parameters = runTimeParameters ?? mockData
+ const [startSetup, setStartSetup] = React.useState(false)
const [
runTimeParametersOverrides,
setRunTimeParametersOverrides,
- ] = React.useState(parameters)
+ ] = React.useState(runTimeParameters)
const updateParameters = (
value: boolean | string | number,
variableName: string
): void => {
- const updatedParameters = parameters.map(parameter => {
+ const updatedParameters = runTimeParametersOverrides.map(parameter => {
if (parameter.variableName === variableName) {
return { ...parameter, value }
}
@@ -212,10 +72,19 @@ export function ProtocolSetupParameters({
setChooseValueScreen(updatedParameter)
}
}
+ if (
+ showNumericalInputScreen &&
+ showNumericalInputScreen.variableName === variableName
+ ) {
+ const updatedParameter = updatedParameters.find(
+ parameter => parameter.variableName === variableName
+ )
+ if (updatedParameter != null) {
+ setShowNumericalInputScreen(updatedParameter as NumberParameter)
+ }
+ }
}
- // TODO(jr, 3/20/24): modify useCreateRunMutation to take in optional run time parameters
- // newRunTimeParameters will be the param to plug in!
const { createRun, isLoading } = useCreateRunMutation({
onSuccess: data => {
queryClient
@@ -226,8 +95,29 @@ export function ProtocolSetupParameters({
},
})
const handleConfirmValues = (): void => {
- createRun({ protocolId, labwareOffsets })
+ setStartSetup(true)
+ createRun({
+ protocolId,
+ labwareOffsets,
+ runTimeParameterValues: getRunTimeParameterValuesForRun(
+ runTimeParametersOverrides
+ ),
+ })
}
+
+ const handleSetParameter = (parameter: RunTimeParameter): void => {
+ if ('choices' in parameter) {
+ setChooseValueScreen(parameter)
+ } else if (parameter.type === 'bool') {
+ updateParameters(!parameter.value, parameter.variableName)
+ } else if (parameter.type === 'int' || parameter.type === 'float') {
+ setShowNumericalInputScreen(parameter)
+ } else {
+ // bad param
+ console.log('error')
+ }
+ }
+
let children = (
<>
history.goBack()}
onClickButton={handleConfirmValues}
buttonText={t('confirm_values')}
- iconName={isLoading ? 'ot-spinner' : undefined}
+ iconName={isLoading || startSetup ? 'ot-spinner' : undefined}
iconPlacement="startIcon"
secondaryButtonProps={{
buttonType: 'tertiaryLowLight',
- buttonText: t('restore_default'),
+ buttonText: t('restore_defaults'),
onClick: () => showResetValuesModal(true),
}}
/>
@@ -249,6 +139,7 @@ export function ProtocolSetupParameters({
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing8}
paddingX={SPACING.spacing40}
+ paddingBottom={SPACING.spacing40}
>
{runTimeParametersOverrides.map((parameter, index) => {
return (
@@ -257,11 +148,7 @@ export function ProtocolSetupParameters({
hasIcon={!(parameter.type === 'bool')}
status="general"
title={parameter.displayName}
- onClickSetupStep={() =>
- parameter.type === 'bool'
- ? updateParameters(!parameter.value, parameter.variableName)
- : setChooseValueScreen(parameter)
- }
+ onClickSetupStep={() => handleSetParameter(parameter)}
detail={formatRunTimeParameterValue(parameter, t)}
description={parameter.description}
fontSize="h4"
@@ -272,7 +159,7 @@ export function ProtocolSetupParameters({
>
)
- if (chooseValueScreen != null && chooseValueScreen.type === 'str') {
+ if (chooseValueScreen != null) {
children = (
setChooseValueScreen(null)}
@@ -282,7 +169,15 @@ export function ProtocolSetupParameters({
/>
)
}
- // TODO(jr, 4/1/24): add the int/float component
+ if (showNumericalInputScreen != null) {
+ children = (
+ setShowNumericalInputScreen(null)}
+ parameter={showNumericalInputScreen}
+ setParameter={updateParameters}
+ />
+ )
+ }
return (
<>
diff --git a/app/src/organisms/RunTimeControl/hooks.ts b/app/src/organisms/RunTimeControl/hooks.ts
index c56f552b3ae..db042a2ce65 100644
--- a/app/src/organisms/RunTimeControl/hooks.ts
+++ b/app/src/organisms/RunTimeControl/hooks.ts
@@ -187,5 +187,6 @@ export function useRunErrors(runId: string | null): RunData['errors'] {
export function useProtocolHasRunTimeParameters(runId: string | null): boolean {
const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId)
- return mostRecentAnalysis?.runTimeParameters != null
+ const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? []
+ return runTimeParameters.length > 0
}
diff --git a/app/src/pages/ProtocolDetails/fixtures.ts b/app/src/pages/ProtocolDetails/fixtures.ts
index d1752853bda..dd23bc4623e 100644
--- a/app/src/pages/ProtocolDetails/fixtures.ts
+++ b/app/src/pages/ProtocolDetails/fixtures.ts
@@ -14,7 +14,7 @@ export const mockRunTimeParameterData: RunTimeParameter[] = [
variableName: 'USE_GRIPPER',
description: '',
type: 'bool',
- default: true,
+ default: false,
value: true,
},
{
diff --git a/app/src/pages/ProtocolSetup/index.tsx b/app/src/pages/ProtocolSetup/index.tsx
index f2fb24feaa5..97499316f27 100644
--- a/app/src/pages/ProtocolSetup/index.tsx
+++ b/app/src/pages/ProtocolSetup/index.tsx
@@ -258,6 +258,7 @@ function PrepareToRun({
const history = useHistory()
const { makeSnackbar } = useToaster()
const hasRunTimeParameters = useProtocolHasRunTimeParameters(runId)
+ console.log(hasRunTimeParameters)
// Watch for scrolling to toggle dropshadow
const scrollRef = React.useRef(null)
const [isScrolled, setIsScrolled] = React.useState(false)
diff --git a/shared-data/js/types.ts b/shared-data/js/types.ts
index 75466e7558e..318db1d04e4 100644
--- a/shared-data/js/types.ts
+++ b/shared-data/js/types.ts
@@ -590,7 +590,7 @@ export interface AnalysisError {
createdAt: string
}
-export interface NumberParameter {
+export interface NumberParameter extends BaseRunTimeParameter {
type: NumberParameterType
min: number
max: number
@@ -602,13 +602,13 @@ export interface Choice {
value: number | boolean | string
}
-interface ChoiceParameter {
+interface ChoiceParameter extends BaseRunTimeParameter {
type: RunTimeParameterType
choices: Choice[]
default: number | boolean | string
}
-interface BooleanParameter {
+interface BooleanParameter extends BaseRunTimeParameter {
type: BooleanParameterType
default: boolean
}
@@ -621,7 +621,6 @@ type RunTimeParameterType =
| BooleanParameterType
| StringParameterType
-type ParameterType = NumberParameter | ChoiceParameter | BooleanParameter
interface BaseRunTimeParameter {
displayName: string
variableName: string
@@ -630,7 +629,10 @@ interface BaseRunTimeParameter {
suffix?: string
}
-export type RunTimeParameter = BaseRunTimeParameter & ParameterType
+export type RunTimeParameter =
+ | BooleanParameter
+ | ChoiceParameter
+ | NumberParameter
// TODO(BC, 10/25/2023): this type (and others in this file) probably belong in api-client, not here
export interface CompletedProtocolAnalysis {