Skip to content

Commit 92f86d1

Browse files
sroy3mattseddon
andauthored
Make drop zones larger when dragging in the same section (#2206)
* Make drop zone larger for when dragging in template plots * Fix tests * Add larger drop zones to checkpoint plots * Reduce number of lines * Reduce lines even more for DragDropContainer * Reduce lines even more for DragDropContainer * Add tests * Apply review comment Co-authored-by: mattseddon <[email protected]>
1 parent e46d42d commit 92f86d1

File tree

7 files changed

+241
-87
lines changed

7 files changed

+241
-87
lines changed

webview/src/plots/components/App.test.tsx

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,13 +801,75 @@ describe('App', () => {
801801

802802
dragEnter(
803803
anotherSingleViewPlot,
804-
'plots-section_template-single_0',
805-
DragEnterDirection.RIGHT
804+
'template-single_0',
805+
DragEnterDirection.LEFT
806806
)
807807

808808
expect(screen.getByTestId('plot_drop-target')).toBeInTheDocument()
809809
})
810810

811+
it('should show a drop target at the end of the template plots section when moving a plot inside of one section but not over any other plot', () => {
812+
renderAppWithOptionalData({
813+
template: complexTemplatePlotsFixture
814+
})
815+
816+
const aSingleViewPlot = screen.getByTestId(join('plot_other', 'plot.tsv'))
817+
818+
dragEnter(aSingleViewPlot, 'template-single_0', DragEnterDirection.LEFT)
819+
820+
expect(screen.getByTestId('plot_drop-target')).toBeInTheDocument()
821+
})
822+
823+
it('should drop a plot at the end of the template plots section when moving a plot inside of one section but not over any other plot', () => {
824+
renderAppWithOptionalData({
825+
template: complexTemplatePlotsFixture
826+
})
827+
828+
const aSingleViewPlot = screen.getByTestId(join('plot_other', 'plot.tsv'))
829+
const topSection = screen.getByTestId('plots-section_template-single_0')
830+
831+
dragAndDrop(aSingleViewPlot, topSection)
832+
const plots = within(topSection).getAllByTestId(/^plot_/)
833+
834+
expect(plots.map(plot => plot.id)).toStrictEqual([
835+
join('logs', 'loss.tsv'),
836+
join('other', 'plot.tsv')
837+
])
838+
})
839+
840+
it('should show a drop target at the end of the checkpoint plots when moving a plot inside the section but not over any other plot', () => {
841+
renderAppWithOptionalData({
842+
checkpoint: checkpointPlotsFixture
843+
})
844+
845+
const plots = screen.getAllByTestId(/summary\.json/)
846+
847+
dragEnter(plots[0], 'checkpoint-plots', DragEnterDirection.LEFT)
848+
849+
expect(screen.getByTestId('plot_drop-target')).toBeInTheDocument()
850+
})
851+
852+
it('should show a drop a plot at the end of the checkpoint plots when moving a plot inside the section but not over any other plot', () => {
853+
renderAppWithOptionalData({
854+
checkpoint: checkpointPlotsFixture
855+
})
856+
857+
const plots = screen.getAllByTestId(/summary\.json/)
858+
859+
dragAndDrop(plots[0], screen.getByTestId('checkpoint-plots'))
860+
861+
const expectedOrder = [
862+
'summary.json:accuracy',
863+
'summary.json:val_loss',
864+
'summary.json:val_accuracy',
865+
'summary.json:loss'
866+
]
867+
868+
expect(
869+
screen.getAllByTestId(/summary\.json/).map(plot => plot.id)
870+
).toStrictEqual(expectedOrder)
871+
})
872+
811873
it('should show a drop zone when hovering a new section', () => {
812874
renderAppWithOptionalData({
813875
template: complexTemplatePlotsFixture

webview/src/plots/components/checkpointPlots/CheckpointPlots.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react'
1+
import React, { DragEvent, useEffect, useState } from 'react'
22
import { useSelector } from 'react-redux'
33
import cx from 'classnames'
44
import { ColorScale } from 'dvc/src/plots/webview/contract'
@@ -17,6 +17,7 @@ import { VirtualizedGrid } from '../../../shared/components/virtualizedGrid/Virt
1717
import { shouldUseVirtualizedGrid } from '../util'
1818
import { useNbItemsPerRow } from '../../hooks/useNbItemsPerRow'
1919
import { PlotsState } from '../../store'
20+
import { changeOrderWithDraggedInfo } from '../../../util/array'
2021

2122
interface CheckpointPlotsProps {
2223
plotsIds: string[]
@@ -29,7 +30,11 @@ export const CheckpointPlots: React.FC<CheckpointPlotsProps> = ({
2930
}) => {
3031
const [order, setOrder] = useState(plotsIds)
3132
const { size, hasData } = useSelector((state: PlotsState) => state.checkpoint)
33+
const [onSection, setOnSection] = useState(false)
3234
const nbItemsPerRow = useNbItemsPerRow(size)
35+
const draggedRef = useSelector(
36+
(state: PlotsState) => state.dragAndDrop.draggedRef
37+
)
3338

3439
useEffect(() => {
3540
setOrder(pastOrder => performSimpleOrderedUpdate(pastOrder, plotsIds))
@@ -55,11 +60,26 @@ export const CheckpointPlots: React.FC<CheckpointPlotsProps> = ({
5560

5661
const useVirtualizedGrid = shouldUseVirtualizedGrid(items.length, size)
5762

63+
const handleDropAtTheEnd = () => {
64+
setMetricOrder(changeOrderWithDraggedInfo(order, draggedRef))
65+
}
66+
67+
const handleDragOver = (e: DragEvent) => {
68+
e.preventDefault()
69+
setOnSection(true)
70+
}
71+
5872
return items.length > 0 ? (
5973
<div
74+
data-testid="checkpoint-plots"
75+
id="checkpoint-plots"
6076
className={cx(styles.singleViewPlotsGrid, {
6177
[styles.noBigGrid]: !useVirtualizedGrid
6278
})}
79+
onDragEnter={() => setOnSection(true)}
80+
onDragLeave={() => setOnSection(false)}
81+
onDragOver={handleDragOver}
82+
onDrop={handleDropAtTheEnd}
6383
>
6484
<DragDropContainer
6585
order={order}
@@ -76,6 +96,7 @@ export const CheckpointPlots: React.FC<CheckpointPlotsProps> = ({
7696
}
7797
: undefined
7898
}
99+
parentDraggedOver={onSection}
79100
/>
80101
</div>
81102
) : (

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

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React, { DragEvent, useState, useEffect, useRef } from 'react'
77
import cx from 'classnames'
88
import { useDispatch, useSelector } from 'react-redux'
99
import { MessageFromWebviewType } from 'dvc/src/webview/contract'
10+
import { reorderObjectList } from 'dvc/src/util/array'
1011
import { AddedSection } from './AddedSection'
1112
import { TemplatePlotsGrid } from './TemplatePlotsGrid'
1213
import { removeFromPreviousAndAddToNewSection } from './util'
@@ -19,6 +20,8 @@ import { PlotsState } from '../../store'
1920
import { plotDataStore } from '../plotDataStore'
2021
import { setDraggedOverGroup } from '../../../shared/components/dragDrop/dragDropSlice'
2122
import { EmptyState } from '../../../shared/components/emptyState/EmptyState'
23+
import { isSameGroup } from '../../../shared/components/dragDrop/DragDropContainer'
24+
import { changeOrderWithDraggedInfo } from '../../../util/array'
2225

2326
export enum NewSectionBlock {
2427
TOP = 'drop-section-top',
@@ -120,7 +123,7 @@ export const TemplatePlots: React.FC = () => {
120123
draggedId: string,
121124
draggedGroup: string,
122125
groupId: string,
123-
position: number
126+
position?: number
124127
) => {
125128
if (draggedGroup === groupId) {
126129
return
@@ -169,34 +172,69 @@ export const TemplatePlots: React.FC = () => {
169172

170173
const isMultiView = section.group === TemplatePlotGroup.MULTI_VIEW
171174

172-
const classes = cx({
175+
const classes = cx(styles.sectionWrapper, {
173176
[styles.multiViewPlotsGrid]: isMultiView,
174177
[styles.singleViewPlotsGrid]: !isMultiView,
175178
[styles.noBigGrid]: !useVirtualizedGrid
176179
})
177180

181+
const handleDropAtTheEnd = () => {
182+
handleEnteringSection('')
183+
if (!draggedRef) {
184+
return
185+
}
186+
187+
if (draggedRef.group === groupId) {
188+
const order = section.entries.map(s => s.id)
189+
const updatedSections = [...sections]
190+
191+
const newOrder = changeOrderWithDraggedInfo(order, draggedRef)
192+
updatedSections[i] = {
193+
...sections[i],
194+
entries: reorderObjectList<TemplatePlotEntry>(
195+
newOrder,
196+
section.entries,
197+
'id'
198+
)
199+
}
200+
setSections(updatedSections)
201+
} else if (isSameGroup(draggedRef.group, groupId)) {
202+
handleDropInSection(
203+
draggedRef.itemId,
204+
draggedRef.group,
205+
groupId,
206+
section.entries.length
207+
)
208+
}
209+
}
210+
211+
const handleDragOver = (e: DragEvent) => {
212+
e.preventDefault()
213+
handleEnteringSection(groupId)
214+
}
215+
178216
return (
179-
section.entries.length > 0 && (
180-
<div
181-
key={groupId}
182-
id={groupId}
183-
data-testid={`plots-section_${groupId}`}
184-
className={classes}
185-
onDragEnter={() => handleEnteringSection(groupId)}
186-
>
187-
<TemplatePlotsGrid
188-
entries={section.entries}
189-
groupId={groupId}
190-
groupIndex={i}
191-
onDropInSection={handleDropInSection}
192-
multiView={isMultiView}
193-
setSectionEntries={setSectionEntries}
194-
useVirtualizedGrid={useVirtualizedGrid}
195-
nbItemsPerRow={nbItemsPerRow}
196-
parentDraggedOver={draggedOverGroup === groupId}
197-
/>
198-
</div>
199-
)
217+
<div
218+
key={groupId}
219+
id={groupId}
220+
data-testid={`plots-section_${groupId}`}
221+
className={classes}
222+
onDragEnter={() => handleEnteringSection(groupId)}
223+
onDragOver={handleDragOver}
224+
onDrop={handleDropAtTheEnd}
225+
>
226+
<TemplatePlotsGrid
227+
entries={section.entries}
228+
groupId={groupId}
229+
groupIndex={i}
230+
onDropInSection={handleDropInSection}
231+
multiView={isMultiView}
232+
setSectionEntries={setSectionEntries}
233+
useVirtualizedGrid={useVirtualizedGrid}
234+
nbItemsPerRow={nbItemsPerRow}
235+
parentDraggedOver={draggedOverGroup === groupId}
236+
/>
237+
</div>
200238
)
201239
})}
202240
<AddedSection

webview/src/plots/components/templatePlots/util.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import {
55

66
const remove = (section: TemplatePlotSection, entryId: string) => {
77
const entries = section.entries.filter(({ id }) => id !== entryId)
8-
return entries.length > 0
9-
? {
10-
entries,
11-
group: section.group
12-
}
13-
: null
8+
return {
9+
entries,
10+
group: section.group
11+
}
1412
}
1513

1614
const add = (
@@ -27,21 +25,27 @@ const add = (
2725
return { entries, group: section.group }
2826
}
2927

28+
const cleanup = (section: TemplatePlotSection) =>
29+
section.entries.length > 0 ? section : null
30+
3031
export const removeFromPreviousAndAddToNewSection = (
3132
sections: TemplatePlotSection[],
3233
oldSectionIndex: number,
3334
entryId: string,
3435
newGroupIndex?: number,
3536
entry?: TemplatePlotEntry,
3637
position?: number
37-
) =>
38-
sections
39-
.map((section, i) => {
40-
if (i === oldSectionIndex) {
41-
return remove(section, entryId)
42-
} else if (i === newGroupIndex && entry) {
43-
return add(section, entry, position)
44-
}
45-
return section
46-
})
38+
) => {
39+
const newSections = sections.map((section, i) => {
40+
if (i === oldSectionIndex) {
41+
return remove(section, entryId)
42+
} else if (i === newGroupIndex && entry) {
43+
return add(section, entry, position)
44+
}
45+
return section
46+
})
47+
48+
return newSections
49+
.map(section => cleanup(section))
4750
.filter(Boolean) as TemplatePlotSection[]
51+
}

0 commit comments

Comments
 (0)