Skip to content

Commit

Permalink
feat(protocol-designer, app, api): step grouping proof of concept
Browse files Browse the repository at this point in the history
addresses AUTH-573
  • Loading branch information
jerader committed Jul 22, 2024
1 parent aabb1b7 commit cafa1d0
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 62 deletions.
104 changes: 91 additions & 13 deletions protocol-designer/src/components/steplist/DraggableStepItems.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { useDrop, useDrag } from 'react-dnd'
import {
BORDERS,
Box,
COLORS,
Flex,
JUSTIFY_SPACE_BETWEEN,
LegacyStyledText,
PrimaryButton,
SPACING,
} from '@opentrons/components'

import { DND_TYPES } from '../../constants'
import { selectors as stepFormSelectors } from '../../step-forms'
import { stepIconsByType } from '../../form-types'
import { getStepGroups } from '../../step-forms/selectors'
import { ConnectedStepItem } from '../../containers/ConnectedStepItem'
import { PDTitledList } from '../lists'
import { ContextMenu } from './ContextMenu'
import styles from './StepItem.module.css'
import type { DragLayerMonitor, DropTargetOptions } from 'react-dnd'
import type { StepIdType } from '../../form-types'
import type { ConnectedStepItemProps } from '../../containers/ConnectedStepItem'
import { removeGroup } from '../../step-forms/actions/groups'

type GroupedStep = { groupName: string; stepIds: string[] } | string

interface DragDropStepItemProps extends ConnectedStepItemProps {
stepId: StepIdType
Expand Down Expand Up @@ -81,6 +95,8 @@ export const DraggableStepItems = (
): JSX.Element | null => {
const { orderedStepIds, reorderSteps } = props
const { t } = useTranslation('shared')
const groups = useSelector(getStepGroups)
const dispatch = useDispatch()

const findStepIndex = (stepId: StepIdType): number =>
orderedStepIds.findIndex(id => stepId === id)
Expand All @@ -102,22 +118,84 @@ export const DraggableStepItems = (
}
}

const groupedSteps: GroupedStep[] = []
const seenSteps: Set<string> = new Set()

orderedStepIds.forEach(stepId => {
// If stepId is already processed, skip it
if (seenSteps.has(stepId)) return

// Find group for the current stepId
const group = Object.entries(groups).find(([groupName, stepIds]) =>
stepIds.includes(stepId)
)

if (group) {
const [groupName, stepIds] = group
// Add the whole group to the result and mark all stepIds as seen
groupedSteps.push({ groupName, stepIds })
stepIds.forEach(id => seenSteps.add(id))
} else {
// If not part of a group, add stepId as is
groupedSteps.push(stepId)
}
})

return (
<>
<ContextMenu>
{({ makeStepOnContextMenu }) =>
orderedStepIds.map((stepId: StepIdType, index: number) => (
<DragDropStepItem
key={`${stepId}_${index}`}
stepNumber={index + 1}
stepId={stepId}
// @ts-expect-error
onStepContextMenu={makeStepOnContextMenu(stepId)}
moveStep={moveStep}
findStepIndex={findStepIndex}
orderedStepIds={orderedStepIds}
/>
))
groupedSteps.map((item, index) => {
if (typeof item === 'string') {
// Render a single step
return (
<DragDropStepItem
key={item}
stepNumber={index + 1}
stepId={item}
// @ts-expect-error
onStepContextMenu={makeStepOnContextMenu(item)}
moveStep={moveStep}
findStepIndex={findStepIndex}
orderedStepIds={orderedStepIds}
/>
)
} else {
// Render a group of steps
return (
<Box
key={item.groupName}
border={BORDERS.lineBorder}
backgroundColor={COLORS.grey30}
>
<Flex justifyContent={JUSTIFY_SPACE_BETWEEN}>
<LegacyStyledText padding={SPACING.spacing8}>
{item.groupName}
</LegacyStyledText>
<PrimaryButton
onClick={() => {
dispatch(removeGroup({ groupName: item.groupName }))
}}
>
Ungroup
</PrimaryButton>
</Flex>
{item.stepIds.map((stepId, subIndex) => (
<DragDropStepItem
key={`${stepId}_${subIndex}`}
stepNumber={index + 1}
stepId={stepId}
// @ts-expect-error
onStepContextMenu={makeStepOnContextMenu(stepId)}
moveStep={moveStep}
findStepIndex={findStepIndex}
orderedStepIds={orderedStepIds}
/>
))}
</Box>
)
}
})
}
</ContextMenu>
<StepDragPreview />
Expand Down
53 changes: 31 additions & 22 deletions protocol-designer/src/components/steplist/StepList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
DIRECTION_COLUMN,
DIRECTION_ROW,
Flex,
Modal,
PrimaryButton,
Expand All @@ -17,6 +18,7 @@ import {
} from '../../steplist'
import { actions as stepsActions, getIsMultiSelectMode } from '../../ui/steps'
import { selectors as stepFormSelectors } from '../../step-forms'
import { getEnableStepGrouping } from '../../feature-flags/selectors'
import { StepCreationButton } from '../StepCreationButton'
import { DraggableStepItems } from './DraggableStepItems'
import { MultiSelectToolbar } from './MultiSelectToolbar'
Expand All @@ -29,7 +31,7 @@ import type { ThunkDispatch } from '../../types'
import { getUnsavedGroup } from '../../step-forms/selectors'
import {
addStepToGroup,
clearGroup,
clearUnsavedGroup,
createGroup,
} from '../../step-forms/actions/groups'

Expand All @@ -41,20 +43,22 @@ export interface StepListProps {
}

export const StepList = (): JSX.Element => {
const enableStepGrouping = useSelector(getEnableStepGrouping)
const orderedStepIds = useSelector(stepFormSelectors.getOrderedStepIds)
const isMultiSelectMode = useSelector(getIsMultiSelectMode)
const dispatch = useDispatch<ThunkDispatch<any>>()
const [group, setGroup] = React.useState<boolean>(false)

const [groupName, setGroupName] = React.useState<string>('')
const stepIds = useSelector(getUnsavedGroup)
const unsavedStepIds = useSelector(getUnsavedGroup)

const handleCreateGroup = (): void => {
if (groupName && stepIds.length > 0) {
if (groupName && unsavedStepIds.length > 0) {
dispatch(createGroup({ groupName }))
dispatch(addStepToGroup({ groupName, stepIds }))
dispatch(clearGroup())
dispatch(addStepToGroup({ groupName, stepIds: unsavedStepIds }))
dispatch(clearUnsavedGroup())
setGroupName('')
setGroup(false)
}
}

Expand Down Expand Up @@ -89,13 +93,6 @@ export const StepList = (): JSX.Element => {
return group ? (
<Modal>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing16}>
<PrimaryButton
onClick={() => {
setGroup(false)
}}
>
close
</PrimaryButton>
<input
type="text"
value={groupName}
Expand All @@ -104,20 +101,32 @@ export const StepList = (): JSX.Element => {
}}
placeholder="Enter group name"
/>
<SecondaryButton onClick={handleCreateGroup}>
create group
</SecondaryButton>
<Flex flexDirection={DIRECTION_ROW} gridGap={SPACING.spacing16}>
<PrimaryButton
onClick={() => {
setGroup(false)
}}
>
Close
</PrimaryButton>
<SecondaryButton onClick={handleCreateGroup}>
Create group
</SecondaryButton>
</Flex>
</Flex>
</Modal>
) : (
<SidePanel title="Protocol Timeline">
<PrimaryButton
onClick={() => {
setGroup(true)
}}
>
make group
</PrimaryButton>
{enableStepGrouping ? (
<PrimaryButton
disabled={unsavedStepIds.length === 0}
onClick={() => {
setGroup(true)
}}
>
Make group
</PrimaryButton>
) : null}
<MultiSelectToolbar isMultiSelectMode={Boolean(isMultiSelectMode)} />

<StartingDeckStateTerminalItem />
Expand Down
30 changes: 20 additions & 10 deletions protocol-designer/src/containers/ConnectedStepItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ import {
import {
getAdditionalEquipmentEntities,
getInitialDeckSetup,
getStepGroups,
getUnsavedGroup,
} from '../step-forms/selectors'
import { selectStepForGroup } from '../step-forms/actions/groups'
import { selectStepForUnsavedGroup } from '../step-forms/actions/groups'
import { getEnableStepGrouping } from '../feature-flags/selectors'

import type { ThunkDispatch } from 'redux-thunk'
import type {
Expand Down Expand Up @@ -75,7 +77,9 @@ export const ConnectedStepItem = (
props: ConnectedStepItemProps
): JSX.Element => {
const { stepId, stepNumber } = props
const enableStepGrouping = useSelector(getEnableStepGrouping)
const unsavedGroup = useSelector(getUnsavedGroup)
const groups = useSelector(getStepGroups)
const step = useSelector(stepFormSelectors.getSavedStepForms)[stepId]
const argsAndErrors = useSelector(stepFormSelectors.getArgsAndErrorsByStepId)[
stepId
Expand Down Expand Up @@ -143,7 +147,7 @@ export const ConnectedStepItem = (
dispatch(stepsActions.hoverOnStep(null))

const addStep = (stepId: string): void => {
dispatch(selectStepForGroup({ stepId }))
dispatch(selectStepForUnsavedGroup({ stepId }))
}

const handleStepItemSelection = (event: React.MouseEvent): void => {
Expand Down Expand Up @@ -237,7 +241,7 @@ export const ConnectedStepItem = (
hoveredSubstep,
}
const name = unsavedGroup.includes(stepId) ? 'ot-checkbox' : 'minus-box'
console.log('unsavedGroup', unsavedGroup)

const getModalType = (): DeleteModalType => {
if (isMultiSelectMode) {
return CLOSE_BATCH_EDIT_FORM
Expand All @@ -247,6 +251,10 @@ export const ConnectedStepItem = (
return CLOSE_STEP_FORM_WITH_CHANGES
}
}

const isStepInGroup = Object.values(groups).find(groupStepId =>
groupStepId.includes(stepId)
)
return (
<>
{showConfirmation && (
Expand All @@ -257,13 +265,15 @@ export const ConnectedStepItem = (
/>
)}
<Box>
<Btn
onClick={() => {
addStep(stepId)
}}
>
<Icon name={name} width="2rem" height="2rem" />
</Btn>
{enableStepGrouping && !isStepInGroup ? (
<Btn
onClick={() => {
addStep(stepId)
}}
>
<Icon name={name} width="2rem" height="2rem" />
</Btn>
) : null}
<StepItem
{...stepItemProps}
onStepContextMenu={props.onStepContextMenu}
Expand Down
2 changes: 2 additions & 0 deletions protocol-designer/src/feature-flags/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const initialFlags: Flags = {
OT_PD_ENABLE_REDESIGN: process.env.OT_PD_ENABLE_REDESIGN === '1' || false,
OT_PD_ENABLE_MOAM: process.env.OT_PD_ENABLE_MOAM === '1' || false,
OT_PD_ENABLE_COMMENT: process.env.OT_PD_ENABLE_COMMENT === '1' || false,
OT_PD_ENABLE_STEP_GROUPING:
process.env.OT_PD_ENABLE_STEP_GROUPING === '1' || false,
}
// @ts-expect-error(sa, 2021-6-10): cannot use string literals as action type
// TODO IMMEDIATELY: refactor this to the old fashioned way if we cannot have type safety: https://github.com/redux-utilities/redux-actions/issues/282#issuecomment-595163081
Expand Down
4 changes: 4 additions & 0 deletions protocol-designer/src/feature-flags/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ export const getEnableComment: Selector<boolean> = createSelector(
getFeatureFlagData,
flags => flags.OT_PD_ENABLE_COMMENT ?? false
)
export const getEnableStepGrouping: Selector<boolean> = createSelector(
getFeatureFlagData,
flags => flags.OT_PD_ENABLE_STEP_GROUPING ?? false
)
2 changes: 2 additions & 0 deletions protocol-designer/src/feature-flags/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type FlagTypes =
| 'OT_PD_ENABLE_REDESIGN'
| 'OT_PD_ENABLE_MOAM'
| 'OT_PD_ENABLE_COMMENT'
| 'OT_PD_ENABLE_STEP_GROUPING'
// flags that are not in this list only show in prerelease mode
export const userFacingFlags: FlagTypes[] = [
'OT_PD_DISABLE_MODULE_RESTRICTIONS',
Expand All @@ -45,5 +46,6 @@ export const allFlags: FlagTypes[] = [
'OT_PD_ENABLE_REDESIGN',
'OT_PD_ENABLE_MOAM',
'OT_PD_ENABLE_COMMENT',
'OT_PD_ENABLE_STEP_GROUPING',
]
export type Flags = Partial<Record<FlagTypes, boolean | null | undefined>>
2 changes: 1 addition & 1 deletion protocol-designer/src/file-data/selectors/fileCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ export const createFile: Selector<ProtocolFile> = createSelector(
const annotation: SecondOrderCommandAnnotation = {
annotationType: 'secondOrderCommand',
machineReadableName: name,
params: {},
params: {}, // what is this used for?
commandKeys,
}

Expand Down
4 changes: 4 additions & 0 deletions protocol-designer/src/localization/en/feature_flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@
"OT_PD_ENABLE_COMMENT": {
"title": "Enable comment step",
"description": "You can add comments anywhere between timeline steps."
},
"OT_PD_ENABLE_STEP_GROUPING": {
"title": "Enable step grouping",
"description": "Define groups and add steps to them."
}
}
Loading

0 comments on commit cafa1d0

Please sign in to comment.