Skip to content

Commit

Permalink
feat(protocol-designer): foundation for batch edit and multi-select (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader authored Oct 15, 2024
1 parent a3826db commit 80176ba
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
{
"add_details": "Add step details",
"aspirated": "Aspirated",
"batch_edit_steps": "Batch edit steps",
"batch_edit": "Batch edit",
"batch_edits_saved": "Batch edits saved",
"change_tips": "Change tips",
"default_tip_option": "Default - get next tip",
"delete_steps": "Delete steps",
"delete": "Delete step",
"dispensed": "Dispensed",
"duplicate_steps": "Duplicate steps",
"duplicate": "Duplicate step",
"edit_step": "Edit step",
"engage_height": "Engage height",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function BatchEditMixTools(): JSX.Element {
return <div>Todo: wire this up</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function BatchEditMoveLiquidTools(): JSX.Element {
return <div>Todo: wire this up</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { Icon, PrimaryButton, StyledText, Toolbox } from '@opentrons/components'
import {
getBatchEditSelectedStepTypes,
getMultiSelectDisabledFields,
getMultiSelectFieldValues,
getMultiSelectItemIds,
} from '../../../../ui/steps/selectors'
import { useKitchen } from '../../../../organisms/Kitchen/hooks'
import { deselectAllSteps } from '../../../../ui/steps/actions/actions'
import {
// changeBatchEditField,
resetBatchEditFieldChanges,
saveStepFormsMulti,
} from '../../../../step-forms/actions'
import { BatchEditMoveLiquidTools } from './BatchEditMoveLiquidTools'
import { BatchEditMixTools } from './BatchEditMixTools'
// import { maskField } from '../../../../steplist/fieldLevel'

// import type { StepFieldName } from '../../../../steplist/fieldLevel'
import type { ThunkDispatch } from 'redux-thunk'
import type { BaseState } from '../../../../types'

export const BatchEditToolbox = (): JSX.Element | null => {
const { t } = useTranslation(['tooltip', 'protocol_steps', 'shared'])
const { makeSnackbar } = useKitchen()
const dispatch = useDispatch<ThunkDispatch<BaseState, any, any>>()
const fieldValues = useSelector(getMultiSelectFieldValues)
const stepTypes = useSelector(getBatchEditSelectedStepTypes)
const disabledFields = useSelector(getMultiSelectDisabledFields)
const selectedStepIds = useSelector(getMultiSelectItemIds)

// const handleChangeFormInput = (name: StepFieldName, value: unknown): void => {
// const maskedValue = maskField(name, value)
// dispatch(changeBatchEditField({ [name]: maskedValue }))
// }

const handleSave = (): void => {
dispatch(saveStepFormsMulti(selectedStepIds))
makeSnackbar(t('batch_edits_saved') as string)
dispatch(deselectAllSteps('EXIT_BATCH_EDIT_MODE_BUTTON_PRESS'))
}

const handleCancel = (): void => {
dispatch(resetBatchEditFieldChanges())
dispatch(deselectAllSteps('EXIT_BATCH_EDIT_MODE_BUTTON_PRESS'))
}

const stepType = stepTypes.length === 1 ? stepTypes[0] : null

if (stepType !== null && fieldValues !== null && disabledFields !== null) {
// const propsForFields = makeBatchEditFieldProps(
// fieldValues,
// disabledFields,
// handleChangeFormInput,
// t
// )
if (stepType === 'moveLiquid' || stepType === 'mix') {
return (
<Toolbox
height="calc(100vh - 64px)"
title={
<StyledText desktopStyle="bodyLargeSemiBold">
{t('protocol_steps:batch_edit')}
</StyledText>
}
childrenPadding="0"
onCloseClick={handleCancel}
closeButton={<Icon size="2rem" name="close" />}
confirmButton={
<PrimaryButton onClick={handleSave} width="100%">
{t('shared:save')}
</PrimaryButton>
}
>
{stepType === 'moveLiquid' ? (
<BatchEditMoveLiquidTools />
) : (
<BatchEditMixTools />
)}
</Toolbox>
)
} else {
return null
}
} else {
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import noop from 'lodash/noop'
import {
getFieldDefaultTooltip,
getFieldIndeterminateTooltip,
} from '../StepForm/utils'
import type {
DisabledFields,
MultiselectFieldValues,
} from '../../../../ui/steps/selectors'
import type { StepFieldName } from '../../../../form-types'
import type { FieldPropsByName } from '../StepForm/types'
export const makeBatchEditFieldProps = (
fieldValues: MultiselectFieldValues,
disabledFields: DisabledFields,
handleChangeFormInput: (name: string, value: unknown) => void,
t: any
): FieldPropsByName => {
const fieldNames: StepFieldName[] = Object.keys(fieldValues)
return fieldNames.reduce<FieldPropsByName>((acc, name) => {
const defaultTooltip = getFieldDefaultTooltip(name, t)
const isIndeterminate = fieldValues[name].isIndeterminate
const indeterminateTooltip = getFieldIndeterminateTooltip(name, t)
let tooltipContent = defaultTooltip // Default to the default content (or blank)

if (isIndeterminate && indeterminateTooltip) {
tooltipContent = indeterminateTooltip
}

if (name in disabledFields) {
tooltipContent = disabledFields[name] // Use disabled content if field is disabled, override indeterminate tooltip if applicable
}

acc[name] = {
disabled: name in disabledFields,
name,
updateValue: value => {
handleChangeFormInput(name, value)
},
value: fieldValues[name].value,
errorToShow: null,
onFieldBlur: noop,
onFieldFocus: noop,
isIndeterminate: isIndeterminate,
tooltipContent: tooltipContent,
}
return acc
}, {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
getHoveredSubstep,
getMultiSelectItemIds,
getSelectedStepId,
getMultiSelectLastSelected,
getIsMultiSelectMode,
} from '../../../../ui/steps'
import { selectors as fileDataSelectors } from '../../../../file-data'
import {
Expand All @@ -24,9 +26,19 @@ import {
} from '../../../../ui/steps/actions/actions'
import { getOrderedStepIds } from '../../../../step-forms/selectors'
import { StepContainer } from './StepContainer'
import {
getMetaSelectedSteps,
getMouseClickKeyInfo,
getShiftSelectedSteps,
nonePressed,
} from './utils'

import type * as React from 'react'
import type { ThunkDispatch } from 'redux-thunk'
import type { HoverOnStepAction } from '../../../../ui/steps'
import type {
HoverOnStepAction,
SelectMultipleStepsAction,
} from '../../../../ui/steps'
import type { StepIdType } from '../../../../form-types'
import type { BaseState, ThunkAction } from '../../../../types'
import type { DeleteModalType } from '../../../../components/modals/ConfirmDeleteModal'
Expand Down Expand Up @@ -65,6 +77,9 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
const hoveredStep = useSelector(getHoveredStepId)
const selectedStepId = useSelector(getSelectedStepId)
const multiSelectItemIds = useSelector(getMultiSelectItemIds)
const orderedStepIds = useSelector(stepFormSelectors.getOrderedStepIds)
const lastMultiSelectedStepId = useSelector(getMultiSelectLastSelected)
const isMultiSelectMode = useSelector(getIsMultiSelectMode)
const selected: boolean = multiSelectItemIds?.length
? multiSelectItemIds.includes(stepId)
: selectedStepId === stepId
Expand All @@ -74,6 +89,15 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
const singleEditFormHasUnsavedChanges = useSelector(
stepFormSelectors.getCurrentFormHasUnsavedChanges
)
const batchEditFormHasUnsavedChanges = useSelector(
stepFormSelectors.getBatchEditFormHasUnsavedChanges
)
const selectMultipleSteps = (
steps: StepIdType[],
lastSelected: StepIdType
): ThunkAction<SelectMultipleStepsAction> =>
dispatch(stepsActions.selectMultipleSteps(steps, lastSelected))

const selectStep = (): ThunkAction<any> =>
dispatch(stepsActions.resetSelectStep(stepId))
const selectStepOnDoubleClick = (): ThunkAction<any> =>
Expand All @@ -82,15 +106,51 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
dispatch(stepsActions.hoverOnStep(stepId))
const unhighlightStep = (): HoverOnStepAction =>
dispatch(stepsActions.hoverOnStep(null))
const handleSelectStep = (): void => {
selectStep()
const handleSelectStep = (event: React.MouseEvent): void => {
if (selectedStep !== stepId) {
dispatch(toggleViewSubstep(null))
dispatch(hoverOnStep(null))
}
const { isShiftKeyPressed, isMetaKeyPressed } = getMouseClickKeyInfo(event)
let stepsToSelect: StepIdType[] = []

// if user clicked on the last multi-selected step, shift/meta keys don't matter
const toggledLastSelected = stepId === lastMultiSelectedStepId
const noModifierKeys =
nonePressed([isShiftKeyPressed, isMetaKeyPressed]) || toggledLastSelected

if (noModifierKeys) {
selectStep()
} else if (
(isMetaKeyPressed || isShiftKeyPressed) &&
currentFormIsPresaved
) {
// current form is presaved, enter batch edit mode with only the clicked
stepsToSelect = [stepId]
} else {
if (isShiftKeyPressed) {
stepsToSelect = getShiftSelectedSteps(
selectedStepId,
orderedStepIds,
stepId,
multiSelectItemIds,
lastMultiSelectedStepId
)
} else if (isMetaKeyPressed) {
stepsToSelect = getMetaSelectedSteps(
multiSelectItemIds,
stepId,
selectedStepId
)
}
}
if (stepsToSelect.length > 0) {
selectMultipleSteps(stepsToSelect, stepId)
}
}
const handleSelectDoubleStep = (): void => {
selectStepOnDoubleClick()

if (selectedStep !== stepId) {
dispatch(toggleViewSubstep(null))
dispatch(hoverOnStep(null))
Expand All @@ -105,9 +165,12 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
handleSelectDoubleStep,
currentFormIsPresaved || singleEditFormHasUnsavedChanges
)

const { confirm, showConfirmation, cancel } = useConditionalConfirm(
handleSelectStep,
currentFormIsPresaved || singleEditFormHasUnsavedChanges
isMultiSelectMode
? batchEditFormHasUnsavedChanges
: currentFormIsPresaved || singleEditFormHasUnsavedChanges
)

const getModalType = (): DeleteModalType => {
Expand Down
Loading

0 comments on commit 80176ba

Please sign in to comment.