Skip to content

Commit 43f014b

Browse files
authored
Split drag and drop container into different files (#4880)
* Split drag and drop container in multiple files * Fix undefined * Remove mutation of item * Fix tests
1 parent 9dc008c commit 43f014b

File tree

7 files changed

+169
-88
lines changed

7 files changed

+169
-88
lines changed

webview/src/plots/components/comparisonTable/ComparisonTable.test.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { webviewInitialState } from '../webviewSlice'
2828
import { getThemeValue, hexToRGB, ThemeProperty } from '../../../util/styles'
2929
import * as EventCurrentTargetDistances from '../../../shared/components/dragDrop/currentTarget'
3030

31-
const getHeaders = () => screen.getAllByRole('columnheader')
31+
const getHeaders = (): HTMLElement[] => screen.getAllByRole('columnheader')
3232

3333
jest.mock('../../../shared/api')
3434
jest.mock('../../../shared/components/dragDrop/currentTarget', () => {
@@ -489,7 +489,7 @@ describe('ComparisonTable', () => {
489489

490490
const [, draggedHeader] = getHeaders()
491491

492-
expect(draggedHeader.isSameNode(startingNode)).toBe(true)
492+
expect(draggedHeader.isEqualNode(startingNode)).toBe(true)
493493
})
494494

495495
it('should wrap the drop target with the header we are dragging over', () => {
@@ -503,26 +503,25 @@ describe('ComparisonTable', () => {
503503

504504
// eslint-disable-next-line testing-library/no-node-access
505505
expect(headerWrapper.childElementCount).toBe(2)
506-
expect(headerWrapper.contains(endingNode)).toBe(true)
506+
expect(
507+
// eslint-disable-next-line testing-library/no-node-access
508+
Object.values(headerWrapper.children)
509+
.map(child => child.id)
510+
.includes(endingNode.id)
511+
).toBe(true)
507512
})
508513

509514
it('should not change the order when dropping a header in its own spot', () => {
510515
renderTable()
511516

512-
const [startingAndEndingNode, secondEndingNode] = getHeaders()
517+
const [startingNode] = getHeaders()
513518

514-
dragAndDrop(
515-
startingAndEndingNode,
516-
startingAndEndingNode,
517-
DragEnterDirection.RIGHT
518-
)
519+
dragAndDrop(startingNode, startingNode, DragEnterDirection.RIGHT)
519520
expect(mockPostMessage).not.toHaveBeenCalled()
520521

521-
dragAndDrop(
522-
startingAndEndingNode,
523-
secondEndingNode,
524-
DragEnterDirection.RIGHT
525-
)
522+
const [start, end] = getHeaders()
523+
524+
dragAndDrop(start, end, DragEnterDirection.RIGHT)
526525
expect(mockPostMessage).toHaveBeenCalled()
527526
})
528527

webview/src/plots/components/templatePlots/TemplatePlots.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import styles from '../styles.module.scss'
1212
import { shouldUseVirtualizedGrid } from '../util'
1313
import { PlotsState } from '../../store'
1414
import { setDraggedOverGroup } from '../../../shared/components/dragDrop/dragDropSlice'
15-
import { isSameGroup } from '../../../shared/components/dragDrop/DragDropContainer'
15+
import { isSameGroup } from '../../../shared/components/dragDrop/util'
1616
import { changeOrderWithDraggedInfo } from '../../../util/array'
1717
import { LoadingSection, sectionIsLoading } from '../LoadingSection'
1818
import { reorderTemplatePlots } from '../../util/messages'

webview/src/shared/components/dragDrop/DragDropContainer.tsx

Lines changed: 61 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,33 @@ import React, {
99
useLayoutEffect
1010
} from 'react'
1111
import { useDispatch, useSelector } from 'react-redux'
12-
import { DragEnterDirection, getDragEnterDirection } from './util'
12+
import {
13+
DragEnterDirection,
14+
getDragEnterDirection,
15+
isEnteringAfter,
16+
isExactGroup,
17+
isSameGroup
18+
} from './util'
1319
import { changeRef, setDraggedOverGroup } from './dragDropSlice'
1420
import styles from './styles.module.scss'
1521
import { DropTarget } from './DropTarget'
16-
import { getIDIndex, getIDWithoutIndex } from '../../../util/ids'
22+
import { DragDropItemWithTarget } from './DragDropItemWithTarget'
23+
import { DragDropItem } from './DragDropItem'
24+
import { getIDIndex } from '../../../util/ids'
1725
import { Any } from '../../../util/objects'
1826
import { PlotsState } from '../../../plots/store'
1927
import { getStyleProperty } from '../../../util/styles'
2028
import { idToNode } from '../../../util/helpers'
2129
import { useDeferedDragLeave } from '../../hooks/useDeferedDragLeave'
2230

23-
const AFTER_DIRECTIONS = new Set([
24-
DragEnterDirection.RIGHT,
25-
DragEnterDirection.BOTTOM
26-
])
27-
2831
const orderIdxTune = (direction: DragEnterDirection, isAfter: boolean) => {
29-
if (AFTER_DIRECTIONS.has(direction)) {
32+
if (isEnteringAfter(direction)) {
3033
return isAfter ? 0 : 1
3134
}
3235

3336
return isAfter ? -1 : 0
3437
}
3538

36-
export const isSameGroup = (group1?: string, group2?: string) =>
37-
getIDWithoutIndex(group1) === getIDWithoutIndex(group2)
38-
39-
const isExactGroup = (group1?: string, group1Alt?: string, group2?: string) =>
40-
group1 === group2 || group1Alt === group2
41-
4239
const setStyle = (elem: HTMLElement, style: CSSProperties, reset?: boolean) => {
4340
for (const [rule, value] of Object.entries(style)) {
4441
elem.style[getStyleProperty(rule)] = reset ? '' : value
@@ -199,7 +196,6 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
199196
const dragged = newOrder[draggedIndex]
200197
newOrder.splice(draggedIndex, 1)
201198
newOrder.splice(droppedIndex, 0, dragged)
202-
203199
setOrder(newOrder)
204200
dispatch(changeRef(undefined))
205201

@@ -266,26 +262,6 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
266262
deferedDragLeave()
267263
}
268264

269-
const buildItem = (id: string, draggable: JSX.Element) => (
270-
<draggable.type
271-
key={draggable.key}
272-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
273-
ref={(draggable as any).ref}
274-
{...draggable.props}
275-
onDragStart={handleDragStart}
276-
onDragEnd={cleanup}
277-
onDragOver={handleDragOver}
278-
onDragEnter={handleDragEnter}
279-
onDrop={handleOnDrop}
280-
onDragLeave={handleDragLeave}
281-
draggable={!disabledDropIds.includes(id)}
282-
style={
283-
(!shouldShowOnDrag && id === draggedId && { display: 'none' }) ||
284-
draggable.props.style
285-
}
286-
/>
287-
)
288-
289265
const getDropTargetClassNames = (isEnteringRight: boolean) =>
290266
shouldShowOnDrag
291267
? cx(styles.dropTargetWhenShowingOnDrag, {
@@ -300,7 +276,7 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
300276
wrapper: JSX.Element
301277
) => (
302278
<DropTarget
303-
key="drop-target"
279+
key={`drop-target-${id}`}
304280
onDragOver={handleDragOver}
305281
onDragLeave={handleDragLeave}
306282
onDrop={handleOnDrop}
@@ -312,50 +288,63 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
312288
</DropTarget>
313289
)
314290

315-
const createItemWithDropTarget = (id: string, item: JSX.Element) => {
316-
const isEnteringAfter = AFTER_DIRECTIONS.has(direction)
317-
const target = isExactGroup(draggedOverGroup, draggedRef?.group, group)
318-
? getTarget(
319-
id,
320-
isEnteringAfter,
321-
shouldShowOnDrag ? <div /> : <item.type />
322-
)
323-
: null
324-
325-
const itemWithTag = shouldShowOnDrag ? (
326-
<div key="item" {...item.props} />
327-
) : (
328-
item
329-
)
330-
const block = isEnteringAfter
331-
? [itemWithTag, target]
332-
: [target, itemWithTag]
333-
334-
return shouldShowOnDrag ? (
335-
<item.type key={item.key} className={styles.newBlockWhenShowingOnDrag}>
336-
{block}
337-
</item.type>
338-
) : (
339-
block
340-
)
341-
}
291+
const wrappedItems = items
292+
.map(draggable => {
293+
const id = draggable.props.id
294+
const isDraggedOver =
295+
id === draggedOverId && (hoveringSomething || !parentDraggedOver)
296+
297+
const item = (
298+
<DragDropItem
299+
key={draggable.key}
300+
cleanup={cleanup}
301+
disabledDropIds={disabledDropIds}
302+
draggable={draggable}
303+
id={id}
304+
onDragEnter={handleDragEnter}
305+
onDragLeave={handleDragLeave}
306+
onDragOver={handleDragOver}
307+
onDragStart={handleDragStart}
308+
onDrop={handleOnDrop}
309+
draggedId={draggedId}
310+
shouldShowOnDrag={shouldShowOnDrag}
311+
isDiv={isDraggedOver && shouldShowOnDrag}
312+
/>
313+
)
342314

343-
const wrappedItems = items.flatMap(draggable => {
344-
const id = draggable?.props?.id
345-
const item = id && buildItem(id, draggable)
315+
if (isDraggedOver) {
316+
const isAfter = isEnteringAfter(direction)
317+
const target = isExactGroup(draggedOverGroup, draggedRef?.group, group)
318+
? getTarget(
319+
id,
320+
isAfter,
321+
shouldShowOnDrag ? <div /> : <draggable.type />
322+
)
323+
: null
324+
325+
return (
326+
<DragDropItemWithTarget
327+
key={draggable.key}
328+
isAfter={isAfter}
329+
dropTarget={target}
330+
shouldShowOnDrag={shouldShowOnDrag}
331+
draggable={draggable}
332+
>
333+
{item}
334+
</DragDropItemWithTarget>
335+
)
336+
}
346337

347-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
348-
return id === draggedOverId && (hoveringSomething || !parentDraggedOver)
349-
? createItemWithDropTarget(id, item)
350-
: item
351-
})
338+
return item
339+
})
340+
.filter(Boolean) as JSX.Element[]
352341

353342
if (
354343
isSameGroup(draggedRef?.group, group) &&
355344
!hoveringSomething &&
356345
parentDraggedOver
357346
) {
358-
const lastItem = wrappedItems[wrappedItems.length - 1]
347+
const lastItem = items[items.length - 1]
359348
wrappedItems.push(getTarget(lastItem.props.id, false, <lastItem.type />))
360349
}
361350

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { DragEvent } from 'react'
2+
3+
interface DragDropItemProps {
4+
id: string
5+
draggable: JSX.Element
6+
onDragStart: (e: DragEvent<HTMLElement>) => void
7+
onDragOver: (e: DragEvent<HTMLElement>) => void
8+
onDragEnter: (e: DragEvent<HTMLElement>) => void
9+
onDrop: (e: DragEvent<HTMLElement>) => void
10+
onDragLeave: (e: DragEvent<HTMLElement>) => void
11+
cleanup: () => void
12+
disabledDropIds: string[]
13+
shouldShowOnDrag?: boolean
14+
draggedId?: string
15+
isDiv?: boolean
16+
}
17+
18+
export const DragDropItem: React.FC<DragDropItemProps> = ({
19+
id,
20+
draggable,
21+
onDragStart,
22+
onDragOver,
23+
onDragEnter,
24+
onDrop,
25+
onDragLeave,
26+
cleanup,
27+
disabledDropIds,
28+
shouldShowOnDrag,
29+
draggedId,
30+
isDiv
31+
}) => {
32+
const Type = isDiv ? 'div' : draggable.type
33+
return (
34+
<Type
35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
36+
ref={(draggable as any).ref}
37+
{...draggable.props}
38+
onDragStart={onDragStart}
39+
onDragEnd={cleanup}
40+
onDragOver={onDragOver}
41+
onDragEnter={onDragEnter}
42+
onDrop={onDrop}
43+
onDragLeave={onDragLeave}
44+
draggable={!disabledDropIds.includes(id)}
45+
style={
46+
(!shouldShowOnDrag && id === draggedId && { display: 'none' }) ||
47+
draggable.props.style
48+
}
49+
/>
50+
)
51+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React, { PropsWithChildren } from 'react'
2+
import styles from './styles.module.scss'
3+
4+
interface DragDropItemWithTargetProps {
5+
dropTarget: JSX.Element | null
6+
draggable: JSX.Element
7+
isAfter?: boolean
8+
shouldShowOnDrag?: boolean
9+
}
10+
11+
export const DragDropItemWithTarget: React.FC<
12+
PropsWithChildren<DragDropItemWithTargetProps>
13+
> = ({ dropTarget, isAfter, shouldShowOnDrag, draggable, children }) => {
14+
const block = isAfter ? [children, dropTarget] : [dropTarget, children]
15+
16+
return shouldShowOnDrag ? (
17+
<draggable.type className={styles.newBlockWhenShowingOnDrag}>
18+
{block}
19+
</draggable.type>
20+
) : (
21+
block
22+
)
23+
}

webview/src/shared/components/dragDrop/util.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DragEvent } from 'react'
22
import { getEventCurrentTargetDistances } from './currentTarget'
3+
import { getIDWithoutIndex } from '../../../util/ids'
34

45
export enum DragEnterDirection {
56
RIGHT = 'RIGHT',
@@ -9,6 +10,11 @@ export enum DragEnterDirection {
910
BOTTOM = 'BOTTOM'
1011
}
1112

13+
const AFTER_DIRECTIONS = new Set([
14+
DragEnterDirection.RIGHT,
15+
DragEnterDirection.BOTTOM
16+
])
17+
1218
export const getDragEnterDirection = (
1319
e: DragEvent<HTMLElement>,
1420
vertical?: boolean
@@ -40,3 +46,15 @@ export const getDragEnterDirection = (
4046
? DragEnterDirection.TOP
4147
: DragEnterDirection.BOTTOM
4248
}
49+
50+
export const isEnteringAfter = (direction: DragEnterDirection) =>
51+
AFTER_DIRECTIONS.has(direction)
52+
53+
export const isExactGroup = (
54+
group1?: string,
55+
group1Alt?: string,
56+
group2?: string
57+
) => group1 === group2 || group1Alt === group2
58+
59+
export const isSameGroup = (group1?: string, group2?: string) =>
60+
getIDWithoutIndex(group1) === getIDWithoutIndex(group2)

webview/src/test/dragDrop.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ export const dragAndDrop = (
109109
direction: DragEnterDirection = DragEnterDirection.LEFT,
110110
spyableModule?: SpyableEventCurrentTargetDistances
111111
) => {
112-
// When showing element on drag, the dragged over element is being recreated to be wrapped in another element, thus the endingNode does not exist as is in the document
112+
// When showing element on drag, the dragged over element is being recreated to be wrapped in another element,
113+
// thus the endingNode does not exist as is in the document
113114
const endingNodeId = endingNode.id
114115
dragEnter(startingNode, endingNodeId, direction, spyableModule)
115116

0 commit comments

Comments
 (0)