Skip to content

Commit e64ae45

Browse files
authored
feat(protocol-designer): bring back deck setup drag & drop (#17477)
closes AUTH-1276 This PR brings back drag/drop to deck setup with the same functionality as it had in the pre-redesign. You can only drag top-level labware and can not drag: modules or adapters. When we add stacking capabilities, we will need to refactor it to account for that level of stacking.
1 parent ed6400f commit e64ae45

22 files changed

+1365
-365
lines changed

components/src/hardware-sim/Deck/RobotCoordsForeignDiv.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface RobotCoordsForeignDivProps {
1212
innerDivProps?: ComponentProps<typeof Box>
1313
transformWithSVG?: boolean
1414
extraTransform?: string
15+
/** optional data-testid to test foreignObjects in cypress */
16+
dataTestId?: string
1517
}
1618

1719
export const RobotCoordsForeignDiv = (
@@ -27,11 +29,13 @@ export const RobotCoordsForeignDiv = (
2729
innerDivProps,
2830
transformWithSVG = false,
2931
extraTransform = '',
32+
dataTestId = '',
3033
} = props
3134

3235
const transform = `scale(1, -1) ${extraTransform}`
3336
return (
3437
<foreignObject
38+
data-testid={dataTestId}
3539
{...{ x, y, height, width, ...outerProps }}
3640
transform={transformWithSVG ? transform : extraTransform}
3741
>

components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithRef.tsx

+21-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import type { DeckDefinition, DeckSlot } from '@opentrons/shared-data'
66

77
export interface RobotCoordinateSpaceWithRefRenderProps {
88
deckSlotsById: { [slotId: string]: DeckSlot }
9+
// used for PD's drag/drop DragPreview
10+
getRobotCoordsFromDOMCoords: (
11+
domX: number,
12+
domY: number
13+
) => { x: number; y: number }
914
}
1015

1116
interface RobotCoordinateSpaceWithRefProps extends ComponentProps<typeof Svg> {
@@ -20,6 +25,21 @@ export function RobotCoordinateSpaceWithRef(
2025
): JSX.Element | null {
2126
const { children, deckDef, viewBox, zoomed = false, ...restProps } = props
2227
const wrapperRef = useRef<SVGSVGElement>(null)
28+
const getRobotCoordsFromDOMCoords: RobotCoordinateSpaceWithRefRenderProps['getRobotCoordsFromDOMCoords'] = (
29+
x,
30+
y
31+
) => {
32+
if (wrapperRef.current == null) return { x: 0, y: 0 }
33+
34+
const cursorPoint = wrapperRef.current.createSVGPoint()
35+
36+
cursorPoint.x = x
37+
cursorPoint.y = y
38+
39+
return cursorPoint.matrixTransform(
40+
wrapperRef.current.getScreenCTM()?.inverse()
41+
)
42+
}
2343

2444
if (deckDef == null && viewBox == null) return null
2545

@@ -45,7 +65,7 @@ export function RobotCoordinateSpaceWithRef(
4565
height="100%"
4666
{...restProps}
4767
>
48-
{children?.({ deckSlotsById })}
68+
{children?.({ deckSlotsById, getRobotCoordsFromDOMCoords })}
4969
</Svg>
5070
)
5171
}

components/src/icons/icon-data.ts

+5
Original file line numberDiff line numberDiff line change
@@ -818,4 +818,9 @@ export const ICON_DATA_BY_NAME: Record<
818818
'M3.16848 9.22683C4.91915 7.43693 7.33915 6.33359 9.99973 6.33359C12.6604 6.33359 15.0804 7.43697 16.8311 9.22693L17.9996 8.03818C15.9522 5.95529 13.1239 4.66699 9.99973 4.66699C6.87563 4.66699 4.0473 5.95525 2 8.03809L3.16848 9.22683ZM6.1685 12.2783C7.15141 11.2696 8.51069 10.6495 9.99953 10.6495C11.4886 10.6495 12.848 11.2698 13.8309 12.2787L14.9994 11.0899C13.7199 9.78811 11.9521 8.98291 9.99953 8.98291C8.04712 8.98291 6.27954 9.78795 5 11.0895L6.1685 12.2783ZM10.0002 14.9654C9.6831 14.9654 9.38403 15.1024 9.16876 15.3306L8.00012 14.1417C8.51196 13.6209 9.2191 13.2988 10.0002 13.2988C10.7811 13.2988 11.4881 13.6208 11.9999 14.1414L10.8313 15.3303C10.6161 15.1023 10.3171 14.9654 10.0002 14.9654Z',
819819
viewBox: '0 0 20 20',
820820
},
821+
'no-icon': {
822+
path:
823+
'M9.88551 29.8885C12.1813 31.5538 14.9863 32.5294 18 32.5294C25.6771 32.5294 32 26.1981 32 18.2647C32 15.1922 31.0516 12.36 29.4435 10.0429L9.88551 29.8885ZM26.7212 7.10662L7.0116 27.1061C5.1288 24.6828 4 21.6168 4 18.2647C4 10.3313 10.3229 4 18 4C21.2847 4 24.3215 5.15901 26.7212 7.10662ZM18 36.5294C27.9411 36.5294 36 28.352 36 18.2647C36 8.17739 27.9411 0 18 0C8.05887 0 0 8.17739 0 18.2647C0 28.352 8.05887 36.5294 18 36.5294Z',
824+
viewBox: '0 0 36 37',
825+
},
821826
}

protocol-designer/cypress/support/SetupSteps.ts

+23-28
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export enum SetupContent {
109109
ModulePageH = 'Add your modules',
110110
ModulePageB = 'Select modules to use in your protocol.',
111111
EditProtocol = 'Edit protocol',
112-
EditSlot = 'Edit slot',
112+
EditLabware = 'Edit labware',
113113
AddLabwareToDeck = 'Add hardware/labware',
114114
EditHardwareLabwareOnDeck = 'Edit hardware/labware',
115115
LabwareH = 'Labware',
@@ -126,6 +126,10 @@ export enum SetupContent {
126126
Save = 'Save',
127127
}
128128

129+
export const RegexSetupContent = {
130+
slotText: /Edit (slot|labware)/i,
131+
}
132+
129133
export enum SetupLocators {
130134
Confirm = 'button:contains("Confirm")',
131135
GoBack = 'button:contains("Go back")',
@@ -167,29 +171,18 @@ const chooseDeckSlot = (
167171
| 'D3',
168172
() => Cypress.Chainable<JQuery<HTMLElement>>
169173
> = {
170-
A1: () =>
171-
cy.contains('foreignObject[x="0"][y="321"]', SetupContent.EditSlot),
172-
A2: () =>
173-
cy.contains('foreignObject[x="164"][y="321"]', SetupContent.EditSlot),
174-
A3: () =>
175-
cy.contains('foreignObject[x="328"][y="321"]', SetupContent.EditSlot),
176-
B1: () =>
177-
cy.contains('foreignObject[x="0"][y="214"]', SetupContent.EditSlot),
178-
B2: () =>
179-
cy.contains('foreignObject[x="164"][y="214"]', SetupContent.EditSlot),
180-
B3: () =>
181-
cy.contains('foreignObject[x="328"][y="214"]', SetupContent.EditSlot),
182-
C1: () =>
183-
cy.contains('foreignObject[x="0"][y="107"]', SetupContent.EditSlot),
184-
C2: () =>
185-
cy.contains('foreignObject[x="164"][y="107"]', SetupContent.EditSlot),
186-
C3: () =>
187-
cy.contains('foreignObject[x="328"][y="107"]', SetupContent.EditSlot),
188-
D1: () => cy.contains('foreignObject[x="0"][y="0"]', SetupContent.EditSlot),
189-
D2: () =>
190-
cy.contains('foreignObject[x="164"][y="0"]', SetupContent.EditSlot),
191-
D3: () =>
192-
cy.contains('foreignObject[x="328"][y="0"]', SetupContent.EditSlot),
174+
A1: () => cy.contains('[data-testid="A1"]', RegexSetupContent.slotText),
175+
A2: () => cy.contains('[data-testid="A2"]', RegexSetupContent.slotText),
176+
A3: () => cy.contains('[data-testid="A3"]', RegexSetupContent.slotText),
177+
B1: () => cy.contains('[data-testid="B1"]', RegexSetupContent.slotText),
178+
B2: () => cy.contains('[data-testid="B2"]', RegexSetupContent.slotText),
179+
B3: () => cy.contains('[data-testid="B3"]', RegexSetupContent.slotText),
180+
C1: () => cy.contains('[data-testid="C1"]', RegexSetupContent.slotText),
181+
C2: () => cy.contains('[data-testid="C2"]', RegexSetupContent.slotText),
182+
C3: () => cy.contains('[data-testid="C3"]', RegexSetupContent.slotText),
183+
D1: () => cy.contains('[data-testid="D1"]', RegexSetupContent.slotText),
184+
D2: () => cy.contains('[data-testid="D2"]', RegexSetupContent.slotText),
185+
D3: () => cy.contains('[data-testid="D3"]', RegexSetupContent.slotText),
193186
}
194187

195188
const slotAction = deckSlots[slot as keyof typeof deckSlots]
@@ -300,7 +293,10 @@ export const executeSetupSteps = (action: SetupActions): void => {
300293
chooseDeckSlot('B3').click()
301294
break
302295
case SetupActions.ChoseDeckSlotC1:
303-
chooseDeckSlot('C1').click()
296+
chooseDeckSlot('C1')
297+
.find('a[role="button"]')
298+
.contains(RegexSetupContent.slotText)
299+
.click({ force: true })
304300
break
305301
case SetupActions.ChoseDeckSlotC2:
306302
chooseDeckSlot('C2').click()
@@ -309,7 +305,6 @@ export const executeSetupSteps = (action: SetupActions): void => {
309305
chooseDeckSlot('C3').click()
310306
break
311307
case SetupActions.ChoseDeckSlotD1:
312-
chooseDeckSlot('D1').click()
313308
break
314309
case SetupActions.ChoseDeckSlotD2:
315310
chooseDeckSlot('D2').click()
@@ -321,7 +316,7 @@ export const executeSetupSteps = (action: SetupActions): void => {
321316
cy.contains(SetupContent.AddLabwareToDeck).click()
322317
break
323318
case SetupActions.EditHardwareLabwareOnDeck:
324-
cy.contains(SetupContent.EditHardwareLabwareOnDeck).click()
319+
cy.contains(SetupContent.EditHardwareLabwareOnDeck).click({ force: true })
325320
break
326321
case SetupActions.ClickLabwareHeader:
327322
cy.contains(SetupContent.LabwareH).click()
@@ -335,7 +330,7 @@ export const executeSetupSteps = (action: SetupActions): void => {
335330
chooseDeckSlot('C2')
336331
.find('.Box-sc-8ozbhb-0.kIDovv')
337332
.find('a[role="button"]')
338-
.contains(SetupContent.EditSlot)
333+
.contains(RegexSetupContent.slotText)
339334
.click({ force: true })
340335
break
341336
case SetupActions.SelectArmadillo96WellPlate: // New case for selecting Armadillo plate

protocol-designer/src/assets/localization/en/deck.json

+3-22
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,15 @@
88
"MODULE_INCOMPATIBLE_LABWARE_SWAP": "Swapping labware not possible due to module incompatibility",
99
"LABWARE_INCOMPATIBLE_WITH_ADAPTER": "Labware incompatible with this adapter"
1010
},
11-
"header": {
12-
"end": "Click on labware to inspect the result of your protocol"
13-
},
1411
"off_deck": {
1512
"slideout_title": "Off-deck labware",
1613
"slideout_empty_state": "There is currently no off-deck labware in this step of the protocol"
1714
},
1815
"overlay": {
19-
"name_labware": {
20-
"nickname_placeholder": "Add a nickname?",
21-
"add_liquids": "Add Liquids",
22-
"leave_empty": "Leave Empty"
23-
},
24-
"edit": {
25-
"name_and_liquids": "Name & Liquids",
26-
"duplicate": "Duplicate",
27-
"delete": "Delete"
28-
},
29-
"browse": {
30-
"view_liquids": "View Liquids"
31-
},
3216
"slot": {
33-
"add_labware": "Add Labware",
3417
"drag_to_new_slot": "Drag To New Slot",
35-
"place_here": "Place Here",
36-
"add_adapter_or_labware": "Add Labware or Adapter",
37-
"add_adapter": "Add adapter"
18+
"place_here": "Place here",
19+
"swap_labware": "Swap labware"
3820
}
39-
},
40-
"inactive_deck": "hover on a step to see deck state"
21+
}
4122
}

protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element {
271271
zoomed={zoomIn.slot != null}
272272
borderRadius={BORDERS.borderRadius12}
273273
>
274-
{() => (
274+
{({ getRobotCoordsFromDOMCoords }) => (
275275
<>
276276
{robotType === OT2_ROBOT_TYPE ? (
277277
<DeckFromLayers
@@ -373,6 +373,7 @@ export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element {
373373
)}
374374
<DeckSetupDetails
375375
selectedZoomInSlot={zoomIn.slot ?? undefined}
376+
getRobotCoordsFromDOMCoords={getRobotCoordsFromDOMCoords}
376377
hoveredLabware={hoveredLabware}
377378
hoveredModule={hoveredModule}
378379
hoveredFixture={hoveredFixture}

0 commit comments

Comments
 (0)