From 279b9c989888a768d2e5f0e920ee1f25f264bbde Mon Sep 17 00:00:00 2001 From: Jethary Rader <66035149+jerader@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:29:21 -0500 Subject: [PATCH] feat(protocol-designer): update React DnD from version 6.0.0 to 16.0.1 (#14485) close RAUT-964 --- ...ds.tsx => RobotCoordinateSpaceWithRef.tsx} | 31 +- .../RobotCoordinateSpace/index.ts | 2 +- protocol-designer/package.json | 4 +- .../components/DeckSetup/LabwareOnDeck.tsx | 1 + .../LabwareOverlays/AdapterControls.tsx | 207 +++++------ .../DeckSetup/LabwareOverlays/DragPreview.css | 5 - .../DeckSetup/LabwareOverlays/DragPreview.tsx | 48 --- .../DeckSetup/LabwareOverlays/EditLabware.tsx | 265 ++++++-------- .../LabwareOverlays/LabwareControls.tsx | 5 +- .../LabwareOverlays/LabwareOverlays.css | 2 +- .../LabwareOverlays/SlotControls.tsx | 187 +++++----- .../DeckSetup/LabwareOverlays/index.ts | 1 - .../src/components/DeckSetup/index.tsx | 20 +- .../src/components/ProtocolEditor.tsx | 10 +- .../src/components/steplist/ContextMenu.tsx | 17 +- .../steplist/DraggableStepItems.tsx | 332 +++++++----------- .../src/components/steplist/StepList.tsx | 2 +- .../src/containers/ConnectedStepItem.tsx | 6 +- .../src/localization/en/shared.json | 1 + yarn.lock | 119 +++---- 20 files changed, 503 insertions(+), 762 deletions(-) rename components/src/hardware-sim/RobotCoordinateSpace/{RobotCoordinateSpaceWithDOMCoords.tsx => RobotCoordinateSpaceWithRef.tsx} (54%) delete mode 100644 protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.css delete mode 100644 protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.tsx diff --git a/components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithDOMCoords.tsx b/components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithRef.tsx similarity index 54% rename from components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithDOMCoords.tsx rename to components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithRef.tsx index 5ca8396c5be..c1986711ed2 100644 --- a/components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithDOMCoords.tsx +++ b/components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithRef.tsx @@ -2,42 +2,23 @@ import * as React from 'react' import { Svg } from '../../primitives' import type { DeckDefinition, DeckSlot } from '@opentrons/shared-data' -export interface RobotCoordinateSpaceWithDOMCoordsRenderProps { +export interface RobotCoordinateSpaceWithRefRenderProps { deckSlotsById: { [slotId: string]: DeckSlot } - getRobotCoordsFromDOMCoords: ( - domX: number, - domY: number - ) => { x: number; y: number } } -interface RobotCoordinateSpaceWithDOMCoordsProps +interface RobotCoordinateSpaceWithRefProps extends React.ComponentProps { viewBox?: string | null deckDef?: DeckDefinition - children?: ( - props: RobotCoordinateSpaceWithDOMCoordsRenderProps - ) => React.ReactNode + children?: (props: RobotCoordinateSpaceWithRefRenderProps) => React.ReactNode } -type GetRobotCoordsFromDOMCoords = RobotCoordinateSpaceWithDOMCoordsRenderProps['getRobotCoordsFromDOMCoords'] - -export function RobotCoordinateSpaceWithDOMCoords( - props: RobotCoordinateSpaceWithDOMCoordsProps +export function RobotCoordinateSpaceWithRef( + props: RobotCoordinateSpaceWithRefProps ): JSX.Element | null { const { children, deckDef, viewBox, ...restProps } = props const wrapperRef = React.useRef(null) - const getRobotCoordsFromDOMCoords: GetRobotCoordsFromDOMCoords = (x, y) => { - if (wrapperRef.current == null) return { x: 0, y: 0 } - - const cursorPoint = wrapperRef.current.createSVGPoint() - - cursorPoint.x = x - cursorPoint.y = y - return cursorPoint.matrixTransform( - wrapperRef.current.getScreenCTM()?.inverse() - ) - } if (deckDef == null && viewBox == null) return null let wholeDeckViewBox @@ -59,7 +40,7 @@ export function RobotCoordinateSpaceWithDOMCoords( transform="scale(1, -1)" {...restProps} > - {children?.({ deckSlotsById, getRobotCoordsFromDOMCoords })} + {children?.({ deckSlotsById })} ) } diff --git a/components/src/hardware-sim/RobotCoordinateSpace/index.ts b/components/src/hardware-sim/RobotCoordinateSpace/index.ts index 71c518dc39a..07fadd1099a 100644 --- a/components/src/hardware-sim/RobotCoordinateSpace/index.ts +++ b/components/src/hardware-sim/RobotCoordinateSpace/index.ts @@ -1,2 +1,2 @@ -export * from './RobotCoordinateSpaceWithDOMCoords' +export * from './RobotCoordinateSpaceWithRef' export * from './RobotCoordinateSpace' diff --git a/protocol-designer/package.json b/protocol-designer/package.json index 6730a8da47d..a059becbd19 100755 --- a/protocol-designer/package.json +++ b/protocol-designer/package.json @@ -38,8 +38,8 @@ "query-string": "6.2.0", "react": "18.2.0", "react-color": "2.19.3", - "react-dnd": "6.0.0", - "react-dnd-mouse-backend": "0.1.2", + "react-dnd": "16.0.1", + "react-dnd-html5-backend": "16.0.1", "react-dom": "18.2.0", "react-hook-form": "7.49.3", "react-i18next": "14.0.0", diff --git a/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx b/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx index 1bc83f7e752..c84239e3b10 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOnDeck.tsx @@ -35,6 +35,7 @@ export function LabwareOnDeck(props: LabwareOnDeckProps): JSX.Element { const missingTips = missingTipsByLabwareId ? missingTipsByLabwareId[labwareOnDeck.id] : null + return ( JSX.Element - draggedItem: { labwareOnDeck: LabwareOnDeck } | null - itemType: string -} - -interface OP { +interface AdapterControlsProps { slotPosition: CoordinateTuple slotBoundingBox: Dimensions // labwareId is the adapter's labwareId @@ -43,38 +33,90 @@ interface OP { allLabware: LabwareOnDeck[] onDeck: boolean selectedTerminalItemId?: TerminalItemId | null - handleDragHover?: () => unknown -} -interface DP { - addLabware: (e: React.MouseEvent) => unknown - moveDeckItem: (item1: DeckSlot, item2: DeckSlot) => unknown - deleteLabware: () => void + handleDragHover?: () => void } -interface SP { - customLabwareDefs: LabwareDefByDefURI +interface DroppedItem { + labwareOnDeck: LabwareOnDeck } -export type SlotControlsProps = OP & DP & DNDP & SP - -export const AdapterControlsComponents = ( - props: SlotControlsProps +export const AdapterControls = ( + props: AdapterControlsProps ): JSX.Element | null => { const { slotPosition, slotBoundingBox, - addLabware, selectedTerminalItemId, - isOver, - connectDropTarget, - draggedItem, - itemType, - deleteLabware, labwareId, - customLabwareDefs, onDeck, + handleDragHover, allLabware, } = props + const customLabwareDefs = useSelector( + labwareDefSelectors.getCustomLabwareDefsByURI + ) + const activeDeckSetup = useSelector(getDeckSetupForActiveItem) + const labware = activeDeckSetup.labware + const ref = React.useRef(null) + const [newSlot, setSlot] = React.useState(null) + const dispatch = useDispatch() + + const adapterName = + allLabware.find(labware => labware.id === labwareId)?.def.metadata + .displayName ?? '' + + const [{ itemType, draggedItem, isOver }, drop] = useDrop({ + accept: DND_TYPES.LABWARE, + canDrop: (item: DroppedItem) => { + const draggedDef = item.labwareOnDeck?.def + assert(draggedDef, 'no labware def of dragged item, expected it on drop') + + if (draggedDef != null) { + const isCustomLabware = getLabwareIsCustom( + customLabwareDefs, + item.labwareOnDeck + ) + return ( + getAdapterLabwareIsAMatch( + labwareId, + allLabware, + draggedDef.parameters.loadName + ) || isCustomLabware + ) + } + return true + }, + drop: (item: DroppedItem) => { + const droppedLabware = item + if (newSlot != null) { + dispatch(moveDeckItem(newSlot, labwareId)) + } else if (droppedLabware.labwareOnDeck != null) { + const droppedSlot = droppedLabware.labwareOnDeck.slot + dispatch(moveDeckItem(droppedSlot, labwareId)) + } + }, + hover: () => { + if (handleDragHover != null) { + handleDragHover() + } + }, + collect: (monitor: DropTargetMonitor) => ({ + itemType: monitor.getItemType(), + isOver: !!monitor.isOver(), + draggedItem: monitor.getItem() as DroppedItem, + }), + }) + + const draggedLabware = Object.values(labware).find( + l => l.id === draggedItem?.labwareOnDeck?.id + ) + + React.useEffect(() => { + if (draggedLabware != null) { + setSlot(draggedLabware.slot) + } + }) + if ( selectedTerminalItemId !== START_TERMINAL_ITEM_ID || (itemType !== DND_TYPES.LABWARE && itemType !== null) @@ -101,8 +143,10 @@ export const AdapterControlsComponents = ( slotBlocked = 'Labware incompatible with this adapter' } - return connectDropTarget( - + drop(ref) + + return ( + {slotBlocked ? ( - + dispatch(openAddLabwareModal({ slot: labwareId }))} + > {!isOver && } {isOver ? 'Place Here' : 'Add Labware'} - + { + window.confirm( + `"Are you sure you want to remove this ${adapterName}?` + ) && dispatch(deleteContainer({ labwareId: labwareId })) + }} + > {!isOver && } {'Delete'} @@ -137,80 +191,3 @@ export const AdapterControlsComponents = ( ) } - -const mapStateToProps = (state: BaseState): SP => { - return { - customLabwareDefs: labwareDefSelectors.getCustomLabwareDefsByURI(state), - } -} - -const mapDispatchToProps = (dispatch: ThunkDispatch, ownProps: OP): DP => { - const adapterName = - ownProps.allLabware.find(labware => labware.id === ownProps.labwareId)?.def - .metadata.displayName ?? '' - return { - addLabware: () => - dispatch(openAddLabwareModal({ slot: ownProps.labwareId })), - moveDeckItem: (sourceSlot, destSlot) => - dispatch(moveDeckItem(sourceSlot, destSlot)), - deleteLabware: () => { - window.confirm(`"Are you sure you want to remove this ${adapterName}?`) && - dispatch(deleteContainer({ labwareId: ownProps.labwareId })) - }, - } -} - -const slotTarget = { - drop: (props: SlotControlsProps, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - if (draggedItem) { - props.moveDeckItem(draggedItem.labwareOnDeck.slot, props.labwareId) - } - }, - hover: (props: SlotControlsProps) => { - if (props.handleDragHover) { - props.handleDragHover() - } - }, - canDrop: (props: SlotControlsProps, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - const draggedDef = draggedItem?.labwareOnDeck?.def - assert(draggedDef, 'no labware def of dragged item, expected it on drop') - - if (draggedDef != null) { - const isCustomLabware = getLabwareIsCustom( - props.customLabwareDefs, - draggedItem.labwareOnDeck - ) - return ( - getAdapterLabwareIsAMatch( - props.labwareId, - props.allLabware, - draggedDef.parameters.loadName - ) || isCustomLabware - ) - } - return true - }, -} -const collectSlotTarget = ( - connect: DropTargetConnector, - monitor: DropTargetMonitor -): React.ReactNode => ({ - // @ts-expect-error(BC, 12-13-2023): react dnd needs to be updated or removed to include proper type - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - draggedItem: monitor.getItem(), - itemType: monitor.getItemType(), -}) - -export const AdapterControls = connect( - mapStateToProps, - mapDispatchToProps -)( - DropTarget( - DND_TYPES.LABWARE, - slotTarget, - collectSlotTarget - )(AdapterControlsComponents) -) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.css b/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.css deleted file mode 100644 index 1634b4980ed..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.css +++ /dev/null @@ -1,5 +0,0 @@ -@import '@opentrons/components'; - -.labware_drag_preview { - opacity: 0.5; -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.tsx deleted file mode 100644 index 700b683c946..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/DragPreview.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from 'react' -import { DragLayer } from 'react-dnd' -import { LabwareOnDeck } from '../LabwareOnDeck' -import { DND_TYPES } from '../../../constants' -import { LabwareOnDeck as LabwareOnDeckType } from '../../../step-forms' -import { RobotWorkSpaceRenderProps } from '@opentrons/components' -import styles from './DragPreview.css' - -interface DragPreviewProps { - isDragging: boolean - currentOffset?: { x: number; y: number } - item: { labwareOnDeck: LabwareOnDeckType } - itemType: string - getRobotCoordsFromDOMCoords: RobotWorkSpaceRenderProps['getRobotCoordsFromDOMCoords'] -} - -const LabwareDragPreview = (props: DragPreviewProps): JSX.Element | null => { - const { - item, - itemType, - isDragging, - currentOffset, - getRobotCoordsFromDOMCoords, - } = props - if (itemType !== DND_TYPES.LABWARE || !isDragging || !currentOffset) - return null - const { x, y } = currentOffset - - const cursor = getRobotCoordsFromDOMCoords(x, y) - - return ( - - ) -} - -export const DragPreview = DragLayer< - Omit ->(monitor => ({ - currentOffset: monitor.getSourceClientOffset(), - isDragging: monitor.isDragging(), - itemType: monitor.getItemType(), - item: monitor.getItem(), -}))(LabwareDragPreview) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx index e4f267114b0..b51489969ac 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx @@ -1,20 +1,13 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { connect } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' import { Icon } from '@opentrons/components' import { getLabwareDisplayName } from '@opentrons/shared-data' -import { - DragSource, - DragSourceConnector, - DragSourceMonitor, - DropTarget, - DropTargetConnector, - DropTargetMonitor, - DropTargetSpec, -} from 'react-dnd' +import { DropTargetMonitor, useDrag, useDrop } from 'react-dnd' import { NameThisLabware } from './NameThisLabware' import { DND_TYPES } from '../../../constants' +import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' import { deleteContainer, duplicateLabware, @@ -22,50 +15,90 @@ import { openIngredientSelector, } from '../../../labware-ingred/actions' import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import { BaseState, DeckSlot, ThunkDispatch } from '../../../types' +import { ThunkDispatch } from '../../../types' import { LabwareOnDeck } from '../../../step-forms' import styles from './LabwareOverlays.css' -interface OP { +interface Props { labwareOnDeck: LabwareOnDeck - setHoveredLabware: (val?: LabwareOnDeck | null) => unknown - setDraggedLabware: (val?: LabwareOnDeck | null) => unknown + setHoveredLabware: (val?: LabwareOnDeck | null) => void + setDraggedLabware: (val?: LabwareOnDeck | null) => void swapBlocked: boolean } -interface SP { - isYetUnnamed: boolean -} -interface DP { - editLiquids: () => unknown - duplicateLabware: () => unknown - deleteLabware: () => unknown - moveDeckItem: (item1: DeckSlot, item2: DeckSlot) => unknown -} -interface DNDP { - draggedLabware?: LabwareOnDeck | null - isOver: boolean - connectDragSource: (val: JSX.Element) => JSX.Element - connectDropTarget: (val: JSX.Element) => JSX.Element +interface DroppedItem { + labwareOnDeck: LabwareOnDeck } - -type Props = OP & SP & DP & DNDP - -const EditLabwareComponent = (props: Props): JSX.Element => { +export const EditLabware = (props: Props): JSX.Element | null => { const { labwareOnDeck, - isYetUnnamed, - editLiquids, - deleteLabware, - duplicateLabware, - draggedLabware, - isOver, - connectDragSource, - connectDropTarget, swapBlocked, + setDraggedLabware, + setHoveredLabware, } = props + const savedLabware = useSelector(labwareIngredSelectors.getSavedLabware) + const dispatch = useDispatch>() const { t } = useTranslation('deck') + const activeDeckSetup = useSelector(getDeckSetupForActiveItem) + const labware = activeDeckSetup.labware + const ref = React.useRef(null) + const [newSlot, setSlot] = React.useState(null) + const { isTiprack } = labwareOnDeck.def.parameters + const hasName = savedLabware[labwareOnDeck.id] + const isYetUnnamed = !labwareOnDeck.def.parameters.isTiprack && !hasName + + const editLiquids = (): void => { + dispatch(openIngredientSelector(labwareOnDeck.id)) + } + + const [, drag] = useDrag({ + type: DND_TYPES.LABWARE, + item: { labwareOnDeck }, + }) + + const [{ draggedLabware, isOver }, drop] = useDrop(() => ({ + accept: DND_TYPES.LABWARE, + canDrop: (item: DroppedItem) => { + const draggedLabware = item?.labwareOnDeck + const isDifferentSlot = + draggedLabware && draggedLabware.slot !== labwareOnDeck.slot + return isDifferentSlot && !swapBlocked + }, + drop: (item: DroppedItem) => { + const draggedLabware = item?.labwareOnDeck + if (newSlot != null) { + dispatch(moveDeckItem(newSlot, labwareOnDeck.slot)) + } else if (draggedLabware != null) { + dispatch(moveDeckItem(draggedLabware.slot, labwareOnDeck.slot)) + } + }, + + hover: (item: DroppedItem, monitor: DropTargetMonitor) => { + if (monitor.canDrop()) { + setHoveredLabware(labwareOnDeck) + } + }, + collect: (monitor: DropTargetMonitor) => ({ + isOver: monitor.isOver(), + draggedLabware: monitor.getItem() as DroppedItem, + }), + })) + + const draggedItem = Object.values(labware).find( + l => l.id === draggedLabware?.labwareOnDeck?.id + ) + + React.useEffect(() => { + if (draggedItem != null) { + setSlot(draggedItem.slot) + setDraggedLabware(draggedItem) + } else { + setHoveredLabware(null) + setDraggedLabware(null) + } + }) + if (isYetUnnamed && !isTiprack) { return ( { /> ) } else { - const isBeingDragged = draggedLabware?.slot === labwareOnDeck.slot + const isBeingDragged = draggedItem?.slot === labwareOnDeck.slot let contents: React.ReactNode | null = null if (swapBlocked) { contents = null - } else if (draggedLabware) { - contents = ( -
- {t( - `overlay.slot.${isBeingDragged ? 'drag_to_new_slot' : 'place_here'}` - )} -
- ) + } else if (draggedLabware != null) { + contents = null } else { contents = ( <> @@ -103,11 +126,23 @@ const EditLabwareComponent = (props: Props): JSX.Element => { ) : (
)} - + dispatch(duplicateLabware(labwareOnDeck.id))} + > {t('overlay.edit.duplicate')} - + { + window.confirm( + `Are you sure you want to permanently delete this ${getLabwareDisplayName( + labwareOnDeck.def + )}?` + ) && dispatch(deleteContainer({ labwareId: labwareOnDeck.id })) + }} + > {t('overlay.edit.delete')} @@ -115,113 +150,21 @@ const EditLabwareComponent = (props: Props): JSX.Element => { ) } - return connectDragSource( - connectDropTarget( -
- {contents} -
- ) - ) - } -} + drag(drop(ref)) -const labwareSource = { - beginDrag: (props: Props, monitor: DragSourceMonitor, component: any) => { - const { labwareOnDeck } = props - props.setDraggedLabware(labwareOnDeck) - return { labwareOnDeck } - }, - endDrag: (props: Props, monitor: DragSourceMonitor, component: any) => { - props.setHoveredLabware(null) - props.setDraggedLabware(null) - }, -} -const collectLabwareSource = ( - connect: DragSourceConnector, - monitor: DragSourceMonitor -): React.ReactNode => ({ - // @ts-expect-error(BC, 12-13-2023): react dnd needs to be updated or removed to include proper type - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging(), - draggedItem: monitor.getItem(), -}) -const DragEditLabware = DragSource( - DND_TYPES.LABWARE, - labwareSource, - collectLabwareSource -)(EditLabwareComponent) - -const labwareDropTarget = { - canDrop: (props: Props, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - const draggedLabware = draggedItem?.labwareOnDeck - const isDifferentSlot = - draggedLabware && draggedLabware.slot !== props.labwareOnDeck.slot - return isDifferentSlot && !props.swapBlocked - }, - hover: (props: Props, monitor: DropTargetSpec, component: any) => { - if (monitor.canDrop) { - props.setHoveredLabware(component.props.labwareOnDeck) - } - }, - drop: (props: Props, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - if (draggedItem) { - props.moveDeckItem( - draggedItem.labwareOnDeck.slot, - props.labwareOnDeck.slot - ) - } - }, -} -const collectLabwareDropTarget = ( - connect: DropTargetConnector, - monitor: DropTargetMonitor -): React.ReactNode => ({ - // @ts-expect-error(BC, 12-13-2023): react dnd needs to be updated or removed to include proper type - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - draggedLabware: monitor.getItem()?.labwareOnDeck || null, -}) -const DragDropEditLabware = DropTarget( - DND_TYPES.LABWARE, - labwareDropTarget, - collectLabwareDropTarget -)(DragEditLabware) + const dragResult = ( +
+ {contents} +
+ ) -const mapStateToProps = (state: BaseState, ownProps: OP): SP => { - const { id } = ownProps.labwareOnDeck - const hasName = labwareIngredSelectors.getSavedLabware(state)[id] - return { - isYetUnnamed: !ownProps.labwareOnDeck.def.parameters.isTiprack && !hasName, + return dragResult !== null ? dragResult : null } } - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, - ownProps: OP -): DP => ({ - editLiquids: () => - dispatch(openIngredientSelector(ownProps.labwareOnDeck.id)), - duplicateLabware: () => dispatch(duplicateLabware(ownProps.labwareOnDeck.id)), - deleteLabware: () => { - window.confirm( - `Are you sure you want to permanently delete this ${getLabwareDisplayName( - ownProps.labwareOnDeck.def - )}?` - ) && dispatch(deleteContainer({ labwareId: ownProps.labwareOnDeck.id })) - }, - moveDeckItem: (sourceSlot, destSlot) => - dispatch(moveDeckItem(sourceSlot, destSlot)), -}) - -export const EditLabware = connect( - mapStateToProps, - mapDispatchToProps -)(DragDropEditLabware) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx index 66d84a87ab3..fc7c011811c 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx +++ b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx @@ -16,8 +16,8 @@ import type { CoordinateTuple } from '@opentrons/shared-data' interface LabwareControlsProps { labwareOnDeck: LabwareOnDeck slotPosition: CoordinateTuple - setHoveredLabware: (labware?: LabwareOnDeck | null) => unknown - setDraggedLabware: (labware?: LabwareOnDeck | null) => unknown + setHoveredLabware: (labware?: LabwareOnDeck | null) => void + setDraggedLabware: (labware?: LabwareOnDeck | null) => void swapBlocked: boolean selectedTerminalItemId?: TerminalItemId | null } @@ -48,7 +48,6 @@ export const LabwareControls = (props: LabwareControlsProps): JSX.Element => { > {canEdit ? ( - // @ts-expect-error(sa, 2021-6-21): react dnd type mismatch JSX.Element - draggedItem: { labwareOnDeck: LabwareOnDeck } | null - itemType: string -} - -interface OP { +interface SlotControlsProps { slotPosition: CoordinateTuple | null slotBoundingBox: Dimensions // NOTE: slotId can be either AddressableAreaName or moduleId slotId: string moduleType: ModuleType | null selectedTerminalItemId?: TerminalItemId | null - handleDragHover?: () => unknown + handleDragHover?: () => void } -interface DP { - addLabware: (e: React.MouseEvent) => unknown - moveDeckItem: (item1: DeckSlot, item2: DeckSlot) => unknown +interface DroppedItem { + labwareOnDeck: LabwareOnDeck } -interface SP { - customLabwareDefs: LabwareDefByDefURI -} - -export type SlotControlsProps = OP & DP & DNDP & SP - -export const SlotControlsComponent = ( - props: SlotControlsProps -): JSX.Element | null => { +export const SlotControls = (props: SlotControlsProps): JSX.Element | null => { const { slotBoundingBox, slotPosition, - addLabware, + slotId, selectedTerminalItemId, - isOver, - connectDropTarget, moduleType, - draggedItem, - itemType, - customLabwareDefs, + handleDragHover, } = props + const customLabwareDefs = useSelector( + labwareDefSelectors.getCustomLabwareDefsByURI + ) + const activeDeckSetup = useSelector(getDeckSetupForActiveItem) + const labware = activeDeckSetup.labware + const ref = React.useRef(null) + const [newSlot, setSlot] = React.useState(null) + const dispatch = useDispatch() + const { t } = useTranslation('deck') + + const [, drag] = useDrag({ + type: DND_TYPES.LABWARE, + item: { labwareOnDeck: null }, + }) + + const [{ draggedItem, itemType, isOver }, drop] = useDrop({ + accept: DND_TYPES.LABWARE, + canDrop: (item: DroppedItem) => { + const draggedDef = item?.labwareOnDeck?.def + assert(draggedDef, 'no labware def of dragged item, expected it on drop') + + if (moduleType != null && draggedDef != null) { + // this is a module slot, prevent drop if the dragged labware is not compatible + const isCustomLabware = getLabwareIsCustom( + customLabwareDefs, + item.labwareOnDeck + ) + + return getLabwareIsCompatible(draggedDef, moduleType) || isCustomLabware + } + return true + }, + drop: (item: DroppedItem) => { + const droppedLabware = item + if (newSlot != null) { + dispatch(moveDeckItem(newSlot, slotId)) + } else if (droppedLabware.labwareOnDeck != null) { + const droppedSlot = droppedLabware.labwareOnDeck.slot + dispatch(moveDeckItem(droppedSlot, slotId)) + } + }, + hover: () => { + if (handleDragHover != null) { + handleDragHover() + } + }, + collect: (monitor: DropTargetMonitor) => ({ + itemType: monitor.getItemType(), + isOver: !!monitor.isOver(), + draggedItem: monitor.getItem() as DroppedItem, + }), + }) + + const draggedLabware = Object.values(labware).find( + l => l.id === draggedItem?.labwareOnDeck?.id + ) + + React.useEffect(() => { + if (draggedLabware != null) { + setSlot(draggedLabware.slot) + } + }) + if ( selectedTerminalItemId !== START_TERMINAL_ITEM_ID || (itemType !== DND_TYPES.LABWARE && itemType !== null) || @@ -84,6 +123,7 @@ export const SlotControlsComponent = ( return null const draggedDef = draggedItem?.labwareOnDeck?.def + const isCustomLabware = draggedItem ? getLabwareIsCustom(customLabwareDefs, draggedItem.labwareOnDeck) : false @@ -111,8 +151,14 @@ export const SlotControlsComponent = ( overlayText = 'add_labware' } - return connectDropTarget( - + const addLabware = (): void => { + dispatch(openAddLabwareModal({ slot: slotId })) + } + + drag(drop(ref)) + + return ( + {slotBlocked ? ( ) } - -const mapStateToProps = (state: BaseState): SP => { - return { - customLabwareDefs: labwareDefSelectors.getCustomLabwareDefsByURI(state), - } -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, - ownProps: OP -): DP => ({ - addLabware: () => dispatch(openAddLabwareModal({ slot: ownProps.slotId })), - moveDeckItem: (sourceSlot, destSlot) => - dispatch(moveDeckItem(sourceSlot, destSlot)), -}) - -const slotTarget = { - drop: (props: SlotControlsProps, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - if (draggedItem) { - props.moveDeckItem(draggedItem.labwareOnDeck.slot, props.slotId) - } - }, - hover: (props: SlotControlsProps) => { - if (props.handleDragHover) { - props.handleDragHover() - } - }, - canDrop: (props: SlotControlsProps, monitor: DropTargetMonitor) => { - const draggedItem = monitor.getItem() - const draggedDef = draggedItem?.labwareOnDeck?.def - const moduleType = props.moduleType - assert(draggedDef, 'no labware def of dragged item, expected it on drop') - - if (moduleType != null && draggedDef != null) { - // this is a module slot, prevent drop if the dragged labware is not compatible - const isCustomLabware = getLabwareIsCustom( - props.customLabwareDefs, - draggedItem.labwareOnDeck - ) - - return getLabwareIsCompatible(draggedDef, moduleType) || isCustomLabware - } - return true - }, -} -const collectSlotTarget = ( - connect: DropTargetConnector, - monitor: DropTargetMonitor -): React.ReactNode => ({ - // @ts-expect-error(BC, 12-13-2023): react dnd needs to be updated or removed to include proper type - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - draggedItem: monitor.getItem(), - itemType: monitor.getItemType(), -}) - -export const SlotControls = connect( - mapStateToProps, - mapDispatchToProps -)( - DropTarget( - DND_TYPES.LABWARE, - slotTarget, - collectSlotTarget - )(SlotControlsComponent) -) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts b/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts index 490ee828367..cd857edd17a 100644 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts +++ b/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts @@ -1,4 +1,3 @@ export { SlotControls } from './SlotControls' export { AdapterControls } from './AdapterControls' export { LabwareControls } from './LabwareControls' -export { DragPreview } from './DragPreview' diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx index 8044c838050..dcf7fce2855 100644 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ b/protocol-designer/src/components/DeckSetup/index.tsx @@ -7,8 +7,7 @@ import { DeckFromLayers, FlexTrash, Module, - RobotCoordinateSpaceWithDOMCoords, - RobotWorkSpaceRenderProps, + RobotCoordinateSpaceWithRef, SingleSlotFixture, StagingAreaFixture, StagingAreaLocation, @@ -64,7 +63,6 @@ import { AdapterControls, SlotControls, LabwareControls, - DragPreview, } from './LabwareOverlays' import { FlexModuleTag } from './FlexModuleTag' import { Ot2ModuleTag } from './Ot2ModuleTag' @@ -102,7 +100,6 @@ const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [ ] interface ContentsProps { - getRobotCoordsFromDOMCoords: RobotWorkSpaceRenderProps['getRobotCoordsFromDOMCoords'] activeDeckSetup: InitialDeckSetup selectedTerminalItemId?: TerminalItemId | null showGen1MultichannelCollisionWarnings: boolean @@ -118,7 +115,6 @@ const darkFill = COLORS.grey60 export const DeckSetupContents = (props: ContentsProps): JSX.Element => { const { activeDeckSetup, - getRobotCoordsFromDOMCoords, showGen1MultichannelCollisionWarnings, deckDef, robotType, @@ -265,7 +261,6 @@ export const DeckSetupContents = (props: ContentsProps): JSX.Element => { labwareOnDeck={labwareLoadedOnModule} /> {isAdapter ? ( - // @ts-expect-error { {labwareLoadedOnModule == null && !shouldHideChildren && !isAdapter ? ( - // @ts-expect-error { }) .map(addressableArea => { return ( - // @ts-expect-error { /> {labwareIsAdapter ? ( - // @ts-expect-error { ) })} - ) } @@ -560,8 +551,9 @@ export const DeckSetup = (): JSX.Element => { return (
{drilledDown && } +
- { : deckDef.cornerOffsetFromOrigin[1] } ${deckDef.dimensions[0]} ${deckDef.dimensions[1]}`} > - {({ getRobotCoordsFromDOMCoords }) => ( + {() => ( <> {robotType === OT2_ROBOT_TYPE ? ( { )} {...{ deckDef, - getRobotCoordsFromDOMCoords, + showGen1MultichannelCollisionWarnings, }} /> @@ -666,7 +658,7 @@ export const DeckSetup = (): JSX.Element => { /> )} - +
) diff --git a/protocol-designer/src/components/ProtocolEditor.tsx b/protocol-designer/src/components/ProtocolEditor.tsx index e483d6d448a..466f7bae844 100644 --- a/protocol-designer/src/components/ProtocolEditor.tsx +++ b/protocol-designer/src/components/ProtocolEditor.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import cx from 'classnames' -import { DragDropContext } from 'react-dnd' -import MouseBackEnd from 'react-dnd-mouse-backend' +import { DndProvider } from 'react-dnd' +import { HTML5Backend } from 'react-dnd-html5-backend' import { ComputingSpinner } from '../components/ComputingSpinner' import { ConnectedNav } from '../containers/ConnectedNav' import { Sidebar } from '../containers/ConnectedSidebar' @@ -55,6 +55,8 @@ function ProtocolEditorComponent(): JSX.Element { ) } -export const ProtocolEditor = DragDropContext(MouseBackEnd)( - ProtocolEditorComponent +export const ProtocolEditor = (): JSX.Element => ( + + + ) diff --git a/protocol-designer/src/components/steplist/ContextMenu.tsx b/protocol-designer/src/components/steplist/ContextMenu.tsx index 24a790575c0..e36344c39cf 100644 --- a/protocol-designer/src/components/steplist/ContextMenu.tsx +++ b/protocol-designer/src/components/steplist/ContextMenu.tsx @@ -8,12 +8,13 @@ import { } from '../modals/ConfirmDeleteModal' import { actions as stepsActions, getIsMultiSelectMode } from '../../ui/steps' import { actions as steplistActions } from '../../steplist' +import { getSavedStepForms } from '../../step-forms/selectors' import { Portal } from '../portals/TopPortal' import styles from './StepItem.css' -import { StepIdType } from '../../form-types' -import { getSavedStepForms } from '../../step-forms/selectors' -import { ThunkDispatch } from 'redux-thunk' -import { BaseState } from '../../types' + +import type { StepIdType } from '../../form-types' +import type { ThunkDispatch } from 'redux-thunk' +import type { BaseState } from '../../types' const MENU_OFFSET_PX = 5 @@ -21,7 +22,7 @@ interface Props { children: (args: { makeStepOnContextMenu: ( stepIdType: StepIdType - ) => (event: MouseEvent) => unknown + ) => (event: MouseEvent) => void }) => React.ReactNode } @@ -33,10 +34,9 @@ interface Position { export const ContextMenu = (props: Props): JSX.Element => { const { t } = useTranslation('context_menu') const dispatch = useDispatch>() - const deleteStep = ( - stepId: StepIdType - ): ReturnType => + const deleteStep = (stepId: StepIdType): void => { dispatch(steplistActions.deleteStep(stepId)) + } const duplicateStep = ( stepId: StepIdType ): ReturnType => @@ -80,7 +80,6 @@ export const ContextMenu = (props: Props): JSX.Element => { screenH - clickY > rootH ? clickY + MENU_OFFSET_PX : clickY - rootH - MENU_OFFSET_PX - setVisible(true) setStepId(stepId) setPosition({ left, top }) diff --git a/protocol-designer/src/components/steplist/DraggableStepItems.tsx b/protocol-designer/src/components/steplist/DraggableStepItems.tsx index 62a757cb82a..d02a87ee60a 100644 --- a/protocol-designer/src/components/steplist/DraggableStepItems.tsx +++ b/protocol-designer/src/components/steplist/DraggableStepItems.tsx @@ -1,194 +1,168 @@ import * as React from 'react' -import { connect } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' import { - DragSource, - DropTarget, - DragLayer, DragLayerMonitor, - DragSourceConnector, - DragSourceMonitor, - DropTargetConnector, - DropTargetMonitor, - DragElementWrapper, - DragSourceOptions, - ConnectDropTarget, + useDrop, + useDrag, + DropTargetOptions, } from 'react-dnd' import isEqual from 'lodash/isEqual' import { DND_TYPES } from '../../constants' -import { ConnectedStepItem } from '../../containers/ConnectedStepItem' -import { PDTitledList } from '../lists' -import { stepIconsByType, StepIdType, StepType } from '../../form-types' import { selectors as stepFormSelectors } from '../../step-forms' -import { BaseState } from '../../types' +import { stepIconsByType, StepIdType } from '../../form-types' +import { + ConnectedStepItem, + ConnectedStepItemProps, +} from '../../containers/ConnectedStepItem' +import { PDTitledList } from '../lists' import { ContextMenu } from './ContextMenu' + import styles from './StepItem.css' -type DragDropStepItemProps = React.ComponentProps & { - connectDragSource: (val: unknown) => React.ReactElement - connectDropTarget: (val: unknown) => React.ReactElement +interface DragDropStepItemProps extends ConnectedStepItemProps { stepId: StepIdType - stepNumber: number - isDragging: boolean - findStepIndex: (stepIdType: StepIdType) => number - onDrag: () => void + clickDrop: () => void moveStep: (stepId: StepIdType, value: number) => void + setIsOver: React.Dispatch> + findStepIndex: (stepId: StepIdType) => number } -const DragSourceStepItem = (props: DragDropStepItemProps): any => - props.connectDragSource( - props.connectDropTarget( -
- -
- ) - ) - -const stepItemSource = { - beginDrag: (props: DragDropStepItemProps) => { - props.onDrag() - return { stepId: props.stepId } - }, +interface DropType { + stepId: StepIdType } -const collectStepSource = ( - connect: DragSourceConnector, - monitor: DragSourceMonitor -): { - connectDragSource: DragElementWrapper - isDragging: boolean -} => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging(), -}) -const DraggableStepItem = DragSource( - DND_TYPES.STEP_ITEM, - stepItemSource, - collectStepSource -)(DragSourceStepItem) -const stepItemTarget = { - canDrop: () => { - return false - }, - hover: (props: DragDropStepItemProps, monitor: DropTargetMonitor) => { - const { stepId: draggedId } = monitor.getItem() - const { stepId: overId } = props - - if (draggedId !== overId) { - const overIndex = props.findStepIndex(overId) - props.moveStep(draggedId, overIndex) - } - }, +const DragDropStepItem = (props: DragDropStepItemProps): JSX.Element => { + const { stepId, moveStep, clickDrop, setIsOver, findStepIndex } = props + const ref = React.useRef(null) + + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPES.STEP_ITEM, + item: { stepId }, + collect: (monitor: DragLayerMonitor) => ({ + isDragging: monitor.isDragging(), + }), + }) + + const [{ isOver, handlerId }, drop] = useDrop(() => ({ + accept: DND_TYPES.STEP_ITEM, + canDrop: () => { + return true + }, + drop: () => { + clickDrop() + }, + hover: (item: DropType) => { + const draggedId = item.stepId + if (draggedId !== stepId) { + const overIndex = findStepIndex(stepId) + moveStep(draggedId, overIndex) + } + }, + collect: (monitor: DropTargetOptions) => ({ + isOver: monitor.isOver(), + handlerId: monitor.getHandlerId(), + }), + })) + + React.useEffect(() => { + setIsOver(isOver) + }, [isOver]) + + drag(drop(ref)) + return ( +
+ +
+ ) } -const collectStepTarget = ( - connect: DropTargetConnector -): { connectDropTarget: ReturnType } => ({ - connectDropTarget: connect.dropTarget(), -}) -const DragDropStepItem = DropTarget( - DND_TYPES.STEP_ITEM, - stepItemTarget, - collectStepTarget -)(DraggableStepItem) interface StepItemsProps { orderedStepIds: StepIdType[] - reorderSteps: (steps: StepIdType[]) => unknown - isOver: boolean - connectDropTarget: (val: unknown) => React.ReactElement + reorderSteps: (steps: StepIdType[]) => void } -interface StepItemsState { - stepIds: StepIdType[] -} -class StepItems extends React.Component { - constructor(props: StepItemsProps) { - super(props) - this.state = { stepIds: this.props.orderedStepIds } - } - - onDrag = (): void => { - this.setState({ stepIds: this.props.orderedStepIds }) - } - - submitReordering = (): void => { - if ( - confirm( - 'Are you sure you want to reorder these steps, it may cause errors?' - ) - ) { - this.props.reorderSteps(this.state.stepIds) +export const DraggableStepItems = ( + props: StepItemsProps +): JSX.Element | null => { + const { orderedStepIds, reorderSteps } = props + const { t } = useTranslation('shared') + const [isOver, setIsOver] = React.useState(false) + const [stepIds, setStepIds] = React.useState(orderedStepIds) + + // needed to initalize stepIds + React.useEffect(() => { + setStepIds(orderedStepIds) + }, [orderedStepIds]) + + const clickDrop = (): void => { + if (!isEqual(orderedStepIds, stepIds)) { + if (confirm(t('confirm_reorder'))) { + reorderSteps(stepIds) + } } } - // TODO: BC 2018-11-27 make util function for reordering and use it in hotkey implementation too - moveStep = (stepId: StepIdType, targetIndex: number): void => { - const { stepIds } = this.state - const currentIndex = this.findStepIndex(stepId) - const currentRemoved = [ - ...stepIds.slice(0, currentIndex), - ...stepIds.slice(currentIndex + 1, stepIds.length), - ] - const currentReinserted = [ - ...currentRemoved.slice(0, targetIndex), - stepId, - ...currentRemoved.slice(targetIndex, currentRemoved.length), - ] - this.setState({ stepIds: currentReinserted }) - } + const findStepIndex = (stepId: StepIdType): number => + stepIds.findIndex(id => stepId === id) + + const moveStep = (stepId: StepIdType, targetIndex: number): void => { + const currentIndex = orderedStepIds.findIndex(id => id === stepId) - findStepIndex = (stepId: StepIdType): number => - this.state.stepIds.findIndex(id => stepId === id) + const newStepIds = [...orderedStepIds] + newStepIds.splice(currentIndex, 1) + newStepIds.splice(targetIndex, 0, stepId) - render(): React.ReactNode { - const currentIds = this.props.isOver - ? this.state.stepIds - : this.props.orderedStepIds - return this.props.connectDropTarget( -
- - {({ makeStepOnContextMenu }) => - currentIds.map((stepId: StepIdType, index: number) => ( - - )) - } - - -
- ) + setStepIds(newStepIds) } -} -const NAV_OFFSET = 64 + const currentIds = isOver ? stepIds : orderedStepIds -interface StepDragPreviewSP { - stepType: StepType | null | undefined - stepName: string | null | undefined + return ( + <> + + {({ makeStepOnContextMenu }) => + currentIds.map((stepId: StepIdType, index: number) => ( + + )) + } + + + + ) } -interface StepDragPreviewOP { - currentOffset?: { y: number; x: number } - itemType: string - isDragging: boolean - item: { stepId: StepIdType } -} +const NAV_OFFSET = 64 -type StepDragPreviewProps = StepDragPreviewOP & StepDragPreviewSP -type DraggableStepItemProps = Omit< - StepItemsProps, - 'isOver' | 'connectDropTarget' -> +const StepDragPreview = (): JSX.Element | null => { + const [{ isDragging, itemType, item, currentOffset }] = useDrag(() => ({ + type: DND_TYPES.STEP_ITEM, + collect: (monitor: DragLayerMonitor) => ({ + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + itemType: monitor.getItemType(), + item: monitor.getItem() as { stepId: StepIdType }, + }), + })) + + const savedStepForms = useSelector(stepFormSelectors.getSavedStepForms) + const savedForm = item && savedStepForms[item.stepId] + const { stepType, stepName } = savedForm || {} -const StepDragPreview = (props: StepDragPreviewProps): JSX.Element | null => { - const { itemType, isDragging, currentOffset, stepType, stepName } = props if ( itemType !== DND_TYPES.STEP_ITEM || !isDragging || @@ -210,47 +184,3 @@ const StepDragPreview = (props: StepDragPreviewProps): JSX.Element | null => {
) } - -const mapSTPForPreview = ( - state: BaseState, - ownProps: StepDragPreviewProps -): StepDragPreviewSP => { - const savedForm = - ownProps.item && - stepFormSelectors.getSavedStepForms(state)[ownProps.item.stepId] - const { stepType, stepName } = savedForm || {} - return { stepType, stepName } -} - -export const StepDragPreviewLayer = DragLayer((monitor: DragLayerMonitor) => ({ - currentOffset: monitor.getSourceClientOffset(), - isDragging: monitor.isDragging(), - itemType: monitor.getItemType(), - item: monitor.getItem(), -}))(connect(mapSTPForPreview)(StepDragPreview)) - -const listTarget = { - drop: ( - props: DraggableStepItemProps, - monitor: DragLayerMonitor, - component: StepItems - ) => { - if (!isEqual(props.orderedStepIds, component.state.stepIds)) { - component.submitReordering() - } - }, -} -const collectListTarget = ( - connect: DropTargetConnector, - monitor: DropTargetMonitor -): { isOver: boolean; connectDropTarget: ConnectDropTarget } => ({ - isOver: monitor.isOver(), - connectDropTarget: connect.dropTarget(), -}) - -export const DraggableStepItems = DropTarget( - DND_TYPES.STEP_ITEM, - // @ts-expect-error(sa, 2021-6-21): fix when updating react dnd to hooks api - listTarget, - collectListTarget -)(StepItems) diff --git a/protocol-designer/src/components/steplist/StepList.tsx b/protocol-designer/src/components/steplist/StepList.tsx index f496a4a1922..a6f618bd352 100644 --- a/protocol-designer/src/components/steplist/StepList.tsx +++ b/protocol-designer/src/components/steplist/StepList.tsx @@ -65,7 +65,7 @@ export const StepList = (): JSX.Element => { { dispatch(steplistActions.reorderSteps(stepIds)) }} diff --git a/protocol-designer/src/containers/ConnectedStepItem.tsx b/protocol-designer/src/containers/ConnectedStepItem.tsx index b871e77c5cc..27ea034b099 100644 --- a/protocol-designer/src/containers/ConnectedStepItem.tsx +++ b/protocol-designer/src/containers/ConnectedStepItem.tsx @@ -45,7 +45,7 @@ import { BaseState, ThunkAction } from '../types' import { getAdditionalEquipmentEntities } from '../step-forms/selectors' import { ThunkDispatch } from 'redux-thunk' -interface Props { +export interface ConnectedStepItemProps { stepId: StepIdType stepNumber: number onStepContextMenu?: () => void @@ -66,7 +66,9 @@ const getMouseClickKeyInfo = ( return { isShiftKeyPressed, isMetaKeyPressed } } -export const ConnectedStepItem = (props: Props): JSX.Element => { +export const ConnectedStepItem = ( + props: ConnectedStepItemProps +): JSX.Element => { const { stepId, stepNumber } = props const step = useSelector(stepFormSelectors.getSavedStepForms)[stepId] diff --git a/protocol-designer/src/localization/en/shared.json b/protocol-designer/src/localization/en/shared.json index 433b1eba68a..d69d55ffe32 100644 --- a/protocol-designer/src/localization/en/shared.json +++ b/protocol-designer/src/localization/en/shared.json @@ -1,5 +1,6 @@ { "add": "add", + "confirm_reorder": "Are you sure you want to reorder these steps, it may cause errors?", "edit": "edit", "exit": "exit", "go_back": "go back", diff --git a/yarn.lock b/yarn.lock index b218f7b6a12..e24df6ff354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1726,6 +1726,11 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hookform/resolvers@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.1.1.tgz#b374d33e356428fff9c6ef3c933441fe15e40784" + integrity sha512-tS16bAUkqjITNSvbJuO1x7MXbn7Oe8ZziDTJdA9mMvsoYthnOOiznOTGBYwbdlYBgU+tgpI/BtTU3paRbCuSlg== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -1744,10 +1749,6 @@ version "2.0.2" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== -"@hookform/resolvers@3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.1.1.tgz#b374d33e356428fff9c6ef3c933441fe15e40784" - integrity sha512-tS16bAUkqjITNSvbJuO1x7MXbn7Oe8ZziDTJdA9mMvsoYthnOOiznOTGBYwbdlYBgU+tgpI/BtTU3paRbCuSlg== "@hutson/parse-repository-url@^3.0.0": version "3.0.2" @@ -2371,6 +2372,21 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.1.1.tgz#12c572ab88ef7345b43f21883fca26631c223085" integrity sha512-sLqWxCzC5/QHLhziXSCAksBxHfOnQlhPRVgPK0egEw+ktWvG75T2k+aYWVjVh9+WKeT3tlG3ZNbZQvZLmfuOIw== +"@react-dnd/asap@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488" + integrity sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A== + +"@react-dnd/invariant@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-4.0.2.tgz#b92edffca10a26466643349fac7cdfb8799769df" + integrity sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw== + +"@react-dnd/shallowequal@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" + integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== + "@react-spring/animated@~9.6.1": version "9.6.1" resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.6.1.tgz#ccc626d847cbe346f5f8815d0928183c647eb425" @@ -5215,7 +5231,7 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.6, asap@~2.0.3: +asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -6500,11 +6516,6 @@ chalk@^4.0.2, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-emitter@^0.1.2: - version "0.1.6" - resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" - integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -8492,15 +8503,14 @@ dmg-license@^1.0.11: smart-buffer "^4.0.2" verror "^1.10.0" -dnd-core@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-6.0.0.tgz#d347266ebd72f0a2de6ecf5e26e4ef006ebde84b" - integrity sha512-WnnFSbnC3grP/XJ+xfxgM8DyIsts3Q/rfgE6WGRWs6tCQcwILputNNm/Kw+WPS2N1e46hRy5iPl2pYwkP9kK9Q== +dnd-core@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-16.0.1.tgz#a1c213ed08961f6bd1959a28bb76f1a868360d19" + integrity sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng== dependencies: - asap "^2.0.6" - invariant "^2.2.4" - lodash "^4.17.11" - redux "^4.0.1" + "@react-dnd/asap" "^5.0.1" + "@react-dnd/invariant" "^4.0.1" + redux "^4.2.0" dns-equal@^1.0.0: version "1.0.0" @@ -10011,7 +10021,7 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbjs@^0.8.0, fbjs@^0.8.1: +fbjs@^0.8.0: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -11592,11 +11602,6 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1: - version "2.5.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -12199,7 +12204,7 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.4: +invariant@^2.2.1, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -15139,13 +15144,6 @@ node-abi@^3.45.0: dependencies: semver "^7.3.5" -node-abi@^3.45.0: - version "3.54.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" - integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA== - dependencies: - semver "^7.3.5" - node-addon-api@^1.6.3: version "1.7.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" @@ -17518,22 +17516,23 @@ react-color@2.19.3: reactcss "^1.2.0" tinycolor2 "^1.4.1" -react-dnd-mouse-backend@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/react-dnd-mouse-backend/-/react-dnd-mouse-backend-0.1.2.tgz#bf79e5cc20715fb1bc03f3ba20389cc5b062f5da" - integrity sha512-A1kgknzYKysVgqwHnB7aFzNmV0/CBK5rJdsCSAIxZxJYaNqymfFgtDD5KneR4dVEva2nFvJXH5th1uYGH4DZGQ== +react-dnd-html5-backend@16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" + integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw== + dependencies: + dnd-core "^16.0.1" -react-dnd@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-6.0.0.tgz#0780eafaa47293bf12dc8f79cf1e48ede8ba72f1" - integrity sha512-XI14rxF5eeGk8045xh/6KbjfLSzgkfNdQCqwkR5qAvBf0QYvkGAUz1AQfLrQudFs/DVw7WiCoCohRzJR1Kyn9Q== +react-dnd@16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" + integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== dependencies: - dnd-core "^6.0.0" - hoist-non-react-statics "^3.1.0" - invariant "^2.1.0" - lodash "^4.17.11" - recompose "^0.30.0" - shallowequal "^1.1.0" + "@react-dnd/invariant" "^4.0.1" + "@react-dnd/shallowequal" "^4.0.1" + dnd-core "^16.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" react-docgen-typescript@^1.21.0: version "1.22.0" @@ -17640,11 +17639,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-lifecycles-compat@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-popper@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.0.0.tgz#b99452144e8fe4acc77fa3d959a8c79e07a65084" @@ -17938,18 +17932,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -recompose@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" - integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== - dependencies: - "@babel/runtime" "^7.0.0" - change-emitter "^0.1.2" - fbjs "^0.8.1" - hoist-non-react-statics "^2.3.1" - react-lifecycles-compat "^3.0.2" - symbol-observable "^1.0.4" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -18014,13 +17996,20 @@ redux@4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" -redux@^4.0.0, redux@^4.0.1, redux@^4.0.5: +redux@^4.0.0, redux@^4.0.5: version "4.1.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== dependencies: "@babel/runtime" "^7.9.2" +redux@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -20267,7 +20256,7 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: +symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==