Skip to content

Commit

Permalink
feat(protocol-designer): add modal to add a pause after set temp (#5182)
Browse files Browse the repository at this point in the history
Closes #5117

* refactor add/select step selectors for mocking, and add tests
  • Loading branch information
IanLondon authored Mar 16, 2020
1 parent 1f979dd commit dbae680
Show file tree
Hide file tree
Showing 17 changed files with 803 additions and 160 deletions.
2 changes: 1 addition & 1 deletion protocol-designer/src/components/StepCreationButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const mapSTP = (state: BaseState): SP => {

const mapDTP = (dispatch: ThunkDispatch<*>): DP => ({
makeAddStep: (stepType: StepType) => (e: SyntheticEvent<>) =>
dispatch(stepsActions.addStep({ stepType })),
dispatch(stepsActions.addAndSelectStepWithHints({ stepType })),
})

export const StepCreationButton = connect<Props, {||}, SP, DP, _, _>(
Expand Down
108 changes: 73 additions & 35 deletions protocol-designer/src/components/StepEditForm/ButtonRow/index.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,99 @@
// @flow
import React from 'react'
import React, { useCallback, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import cx from 'classnames'
import { OutlineButton, PrimaryButton } from '@opentrons/components'

import { AutoAddPauseUntilTempStepModal } from '../../modals/AutoAddPauseUntilTempStepModal'
import { actions as steplistActions } from '../../../steplist'
import { actions as stepFormActions } from '../../../step-forms'
import { actions as stepsActions } from '../../../ui/steps'

import { getCurrentFormCanBeSaved } from '../../../step-forms/selectors'
import {
getCurrentFormCanBeSaved,
getUnsavedForm,
getUnsavedFormIsPristineSetTempForm,
} from '../../../step-forms/selectors'

import type { BaseState } from '../../../types'

import modalStyles from '../../modals/modal.css'
import styles from './styles.css'

const { cancelStepForm } = steplistActions
const { saveStepForm } = stepFormActions

type Props = {|
onClickMoreOptions: (event: SyntheticEvent<>) => mixed,
onDelete?: (event: SyntheticEvent<>) => mixed,
|}

export const ButtonRow = ({ onDelete, onClickMoreOptions }: Props) => {
const [
showAddPauseUntilTempStepModal,
setShowAddPauseUntilTempStepModal,
] = useState<boolean>(false)

const dispatch = useDispatch()
const canSave = useSelector((state: BaseState): boolean =>
getCurrentFormCanBeSaved(state)
)
const dispatch = useDispatch()
const unsavedForm = useSelector(getUnsavedForm)
const shouldShowPauseUntilTempStepModal = useSelector(
getUnsavedFormIsPristineSetTempForm
)

const handleClickSave = useCallback(() => {
if (!canSave) {
return
}
if (shouldShowPauseUntilTempStepModal) {
setShowAddPauseUntilTempStepModal(true)
} else {
dispatch(stepsActions.saveStepForm())
}
}, [canSave, shouldShowPauseUntilTempStepModal, dispatch])

return (
<div className={cx(modalStyles.button_row_divided, styles.form_wrapper)}>
<div>
<OutlineButton className={styles.form_button} onClick={onDelete}>
Delete
</OutlineButton>
<OutlineButton
className={styles.form_button}
onClick={onClickMoreOptions}
>
Notes
</OutlineButton>
</div>
<div>
<PrimaryButton
className={styles.form_button}
onClick={() => dispatch(cancelStepForm())}
>
Close
</PrimaryButton>
<PrimaryButton
className={styles.form_button}
disabled={!canSave}
onClick={canSave ? () => dispatch(saveStepForm()) : undefined}
>
Save
</PrimaryButton>
<>
{showAddPauseUntilTempStepModal && (
<AutoAddPauseUntilTempStepModal
displayTemperature={unsavedForm?.targetTemperature ?? '?'}
handleCancelClick={() => {
setShowAddPauseUntilTempStepModal(false)
// save normally
dispatch(stepsActions.saveStepForm())
}}
handleContinueClick={() => {
setShowAddPauseUntilTempStepModal(false)
// save this form and add a subsequent pause
dispatch(stepsActions.saveSetTempFormWithAddedPauseUntilTemp())
}}
/>
)}
<div className={cx(modalStyles.button_row_divided, styles.form_wrapper)}>
<div>
<OutlineButton className={styles.form_button} onClick={onDelete}>
Delete
</OutlineButton>
<OutlineButton
className={styles.form_button}
onClick={onClickMoreOptions}
>
Notes
</OutlineButton>
</div>
<div>
<PrimaryButton
className={styles.form_button}
onClick={() => dispatch(steplistActions.cancelStepForm())}
>
Close
</PrimaryButton>
<PrimaryButton
className={styles.form_button}
disabled={!canSave}
onClick={handleClickSave}
>
Save
</PrimaryButton>
</div>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import '@opentrons/components';

.header {
@apply --font-header-dark;

margin-bottom: 1rem;
}

.body {
@apply --font-body-2-dark;
}

.later_button {
width: 16rem;
}

.now_button {
width: 15rem;
margin-left: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @flow
import React from 'react'
import { AlertModal, OutlineButton, PrimaryButton } from '@opentrons/components'
import { i18n } from '../../localization'
import modalStyles from './modal.css'
import styles from './AutoAddPauseUntilTempStepModal.css'

type Props = {|
displayTemperature: string,
handleCancelClick: () => mixed,
handleContinueClick: () => mixed,
|}

export const AutoAddPauseUntilTempStepModal = (props: Props) => (
<AlertModal
className={modalStyles.modal}
contentsClassName={modalStyles.modal_contents}
>
<div className={styles.header}>
{i18n.t('modal.auto_add_pause_until_temp_step.title', {
temperature: props.displayTemperature,
})}
</div>
<p className={styles.body}>
{i18n.t('modal.auto_add_pause_until_temp_step.body', {
temperature: props.displayTemperature,
})}
</p>
<div className={modalStyles.button_row}>
<OutlineButton
className={styles.later_button}
onClick={props.handleCancelClick}
>
{i18n.t('modal.auto_add_pause_until_temp_step.later_button')}
</OutlineButton>
<PrimaryButton
className={styles.now_button}
onClick={props.handleContinueClick}
>
{i18n.t('modal.auto_add_pause_until_temp_step.now_button')}
</PrimaryButton>
</div>
</AlertModal>
)
4 changes: 3 additions & 1 deletion protocol-designer/src/form-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,15 @@ export type HydratedMixFormDataLegacy = {
blowout_location: ?string, // labwareId or 'SOURCE_WELL' or 'DEST_WELL'
}

export type MagnetAction = 'engage' | 'disengage'

export type HydratedMagnetFormData = {|
...AnnotationFields,
id: string,
stepType: 'magnet',
stepDetails: string | null,
moduleId: string | null,
magnetAction: 'engage' | 'disengage',
magnetAction: MagnetAction,
engageHeight: string | null,
|}

Expand Down
4 changes: 2 additions & 2 deletions protocol-designer/src/labware-ingred/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
LiquidGroup,
OrderedLiquids,
} from './types'
import type { BaseState, MemoizedSelector } from './../types'
import type { BaseState, MemoizedSelector, Selector } from './../types'

// TODO: Ian 2019-02-15 no RootSlice, use BaseState
type RootSlice = { labwareIngred: RootState }
Expand Down Expand Up @@ -143,7 +143,7 @@ const getLiquidGroupsOnDeck: MemoizedSelector<Array<string>> = createSelector(
}
)

const getDeckHasLiquid: MemoizedSelector<boolean> = createSelector(
const getDeckHasLiquid: Selector<boolean> = createSelector(
getLiquidGroupsOnDeck,
liquidGroups => liquidGroups.length > 0
)
Expand Down
6 changes: 6 additions & 0 deletions protocol-designer/src/localization/en/modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,11 @@
"body1": "Warning: Any experimental features used in this protocol may cause inconsistent behavior in the Protocol Designer. For example, the Protocol Designer may no longer display all warnings or errors that exist when no experimental features have been used.",
"body2": "We encourage you to report any bugs you find, however note that at this time we are unable to prioritize fixes as they are a result of experimental features."
}
},
"auto_add_pause_until_temp_step": {
"title": "Pause until module is {{temperature}}°C?",
"body": "Should your protocol pause until the module is {{temperature}}°C? Or should the protocol continue while the module changes temperature?",
"now_button": "Pause protocol now",
"later_button": "I will build a pause later"
}
}
15 changes: 0 additions & 15 deletions protocol-designer/src/step-forms/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
// @flow
import { getUnsavedForm } from '../selectors'
import type { Dispatch } from 'redux'
import type { StepIdType } from '../../form-types'
import type { GetState } from '../../types'

export * from './modules'
export * from './pipettes'

export const SAVE_STEP_FORM: 'SAVE_STEP_FORM' = 'SAVE_STEP_FORM'

export type SaveStepFormAction = {|
type: typeof SAVE_STEP_FORM,
payload: { id: StepIdType },
|}

export const saveStepForm = () => (dispatch: Dispatch<*>, getState: GetState) =>
dispatch({ type: SAVE_STEP_FORM, payload: getUnsavedForm(getState()) })
4 changes: 2 additions & 2 deletions protocol-designer/src/step-forms/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ import type {
import type {
DuplicateStepAction,
ReorderSelectedStepAction,
} from '../../ui/steps'
} from '../../ui/steps/actions/types'
import type { SaveStepFormAction } from '../../ui/steps/actions/thunks'
import type { StepItemData } from '../../steplist/types'
import type {
NormalizedPipetteById,
Expand All @@ -73,7 +74,6 @@ import type {
CreateModuleAction,
EditModuleAction,
DeleteModuleAction,
SaveStepFormAction,
} from '../actions'

type FormState = FormData | null
Expand Down
11 changes: 11 additions & 0 deletions protocol-designer/src/step-forms/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,17 @@ export const getIsNewStepForm: Selector<boolean> = createSelector(
formData && formData.id != null ? !savedForms[formData.id] : true
)

export const getUnsavedFormIsPristineSetTempForm: Selector<boolean> = createSelector(
getUnsavedForm,
getIsNewStepForm,
(unsavedForm, isNewStepForm) => {
const isSetTempForm =
unsavedForm?.stepType === 'temperature' &&
unsavedForm?.setTemperature === 'true'
return isNewStepForm && isSetTempForm
}
)

export const getFormLevelWarningsForUnsavedForm: Selector<
Array<FormWarning>
> = createSelector(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @flow
import last from 'lodash/last'
import type { StepIdType, FormData } from '../../../form-types'
import type { StepIdType, FormData, MagnetAction } from '../../../form-types'

export function getNextDefaultMagnetAction(
savedForms: { [StepIdType]: FormData },
orderedStepIds: Array<StepIdType>
): ?string {
): MagnetAction {
const prevMagnetSteps = orderedStepIds
.map(stepId => savedForms[stepId])
.filter(form => form && form.magnetAction)
Expand All @@ -14,7 +14,7 @@ export function getNextDefaultMagnetAction(

// default the first magnet step to engage so that
// recommended engage height can auto populate
let nextDefaultMagnetAction: ?string = 'engage'
let nextDefaultMagnetAction: MagnetAction = 'engage'

if (lastMagnetStep && lastMagnetStep.magnetAction) {
nextDefaultMagnetAction =
Expand Down
Loading

0 comments on commit dbae680

Please sign in to comment.