Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions webview/src/plots/components/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,30 @@ describe('App', () => {
])
})

it('should show a drop target at the end of the section when moving a plot from one section to another but not over any other plot', async () => {
renderAppWithOptionalData({
template: complexTemplatePlotsFixture
})

const bottomSection = screen.getByTestId(NewSectionBlock.BOTTOM)
const aSingleViewPlot = screen.getByTestId(join('plot_other', 'plot.tsv'))

dragAndDrop(aSingleViewPlot, bottomSection)

await screen.findByTestId('plots-section_template-single_2')
const anotherSingleViewPlot = screen.getByTestId(
join('plot_logs', 'loss.tsv')
)

dragEnter(
anotherSingleViewPlot,
'plots-section_template-single_0',
DragEnterDirection.RIGHT
)

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

it('should show a drop zone when hovering a new section', () => {
renderAppWithOptionalData({
template: complexTemplatePlotsFixture
Expand All @@ -797,13 +821,7 @@ describe('App', () => {

expect(topDropIcon).not.toBeInTheDocument()

act(() => {
multiViewPlot.dispatchEvent(createBubbledEvent('dragstart'))
})

act(() => {
topSection.dispatchEvent(createBubbledEvent('dragenter'))
})
dragEnter(multiViewPlot, topSection.id, DragEnterDirection.LEFT)

topDropIcon = screen.queryByTestId(`${NewSectionBlock.TOP}_drop-icon`)

Expand Down
4 changes: 4 additions & 0 deletions webview/src/plots/components/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ $gap: 20px;
}
}

.dropSectionWrapper {
height: min-content;
}

.dropSection {
height: 10px;
}
Expand Down
21 changes: 14 additions & 7 deletions webview/src/plots/components/templatePlots/AddedSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,21 @@ export const AddedSection: React.FC<AddedSectionProps> = ({
const isHovered = hoveredSection === id

return (
<div className={cx(styles.singleViewPlotsGrid, styles.noBigGrid)}>
<div
id={id}
data-testid={id}
onDragEnter={handleDragEnter}
onDragExit={handleDragLeave}
onDragOver={(e: DragEvent<HTMLElement>) => e.preventDefault()}
onDrop={onDrop}
draggable
className={cx(
styles.singleViewPlotsGrid,
styles.noBigGrid,
styles.dropSectionWrapper
)}
>
<div
id={id}
data-testid={id}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={(e: DragEvent<HTMLElement>) => e.preventDefault()}
onDrop={onDrop}
className={cx(styles.dropSection, {
[styles.dropSectionMaximized]: isHovered,
[styles.plot]: isHovered
Expand Down
14 changes: 12 additions & 2 deletions webview/src/plots/components/templatePlots/TemplatePlots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export const TemplatePlots: React.FC = () => {
const { plotsSnapshot, size } = useSelector(
(state: PlotsState) => state.template
)
const draggedOverGroup = useSelector(
(state: PlotsState) => state.dragAndDrop.draggedOverGroup
)
const draggedRef = useSelector(
(state: PlotsState) => state.dragAndDrop.draggedRef
)
const [sections, setSections] = useState<TemplatePlotSection[]>([])
const [hoveredSection, setHoveredSection] = useState('')
const nbItemsPerRow = useNbItemsPerRow(size)
Expand Down Expand Up @@ -76,8 +82,11 @@ export const TemplatePlots: React.FC = () => {
}

const handleDropInNewSection = (e: DragEvent<HTMLElement>) => {
const draggedSectionId = getIDIndex(e.dataTransfer.getData('group'))
const draggedId = e.dataTransfer.getData('itemId')
if (!draggedRef) {
return
}
const draggedSectionId = getIDIndex(draggedRef.group)
const draggedId = draggedRef.itemId

const updatedSections = removeFromPreviousAndAddToNewSection(
sections,
Expand Down Expand Up @@ -184,6 +193,7 @@ export const TemplatePlots: React.FC = () => {
setSectionEntries={setSectionEntries}
useVirtualizedGrid={useVirtualizedGrid}
nbItemsPerRow={nbItemsPerRow}
parentDraggedOver={draggedOverGroup === groupId}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface TemplatePlotsGridProps {
setSectionEntries: (groupIndex: number, entries: TemplatePlotEntry[]) => void
useVirtualizedGrid?: boolean
nbItemsPerRow: number
parentDraggedOver?: boolean
}

const autoSize = {
Expand All @@ -38,7 +39,8 @@ export const TemplatePlotsGrid: React.FC<TemplatePlotsGridProps> = ({
multiView,
setSectionEntries,
useVirtualizedGrid,
nbItemsPerRow
nbItemsPerRow,
parentDraggedOver
}) => {
const [order, setOrder] = useState<string[]>([])

Expand Down Expand Up @@ -101,6 +103,7 @@ export const TemplatePlotsGrid: React.FC<TemplatePlotsGridProps> = ({
}
: undefined
}
parentDraggedOver={parentDraggedOver}
/>
)
}
79 changes: 48 additions & 31 deletions webview/src/shared/components/dragDrop/DragDropContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface DragDropContainerProps {
}
shouldShowOnDrag?: boolean
ghostElemStyle?: CSSProperties
parentDraggedOver?: boolean
}

export const DragDropContainer: React.FC<DragDropContainerProps> = ({
Expand All @@ -66,7 +67,8 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
dropTarget,
wrapperComponent,
shouldShowOnDrag,
ghostElemStyle
ghostElemStyle,
parentDraggedOver
// eslint-disable-next-line sonarjs/cognitive-complexity
}) => {
const [draggedOverId, setDraggedOverId] = useState('')
Expand Down Expand Up @@ -123,29 +125,25 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
}
}
const itemIndex = idx.toString()
e.dataTransfer.setData('itemIndex', itemIndex)
e.dataTransfer.setData('itemId', id)
e.dataTransfer.setData('group', group)
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.dropEffect = 'move'
dispatch(
changeRef({
group,
itemId: id,
itemIndex
})
)

createGhostStyle(e)
draggedOverIdTimeout.current = window.setTimeout(() => {
dispatch(
changeRef({
group,
itemId: id,
itemIndex
})
)
setDraggedId(id)
setDraggedOverId(order[toIdx])
resetDraggedStyle(id)
}, 0)
}

const applyDrop = (
e: DragEvent<HTMLElement>,
droppedIndex: number,
draggedIndex: number,
newOrder: string[],
Expand All @@ -159,34 +157,38 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
setOrder(newOrder)
dispatch(changeRef(undefined))

onDrop?.(oldDraggedId, e.dataTransfer.getData('group'), group, droppedIndex)
onDrop?.(oldDraggedId, draggedRef?.group || '', group, droppedIndex)
}

const handleOnDrop = (e: DragEvent<HTMLElement>) => {
if (!draggedRef) {
return
}
const dragged = draggedRef.itemId

draggedOverIdTimeout.current = window.setTimeout(() => {
setDraggedId('')
}, 0)
if (e.dataTransfer.getData('itemId') === draggedOverId) {
if (dragged === draggedOverId) {
dispatch(changeRef(undefined))
return
}
const newOrder = [...order]
const oldDraggedId = e.dataTransfer.getData('itemId')
const isNew = !order.includes(draggedId)
const isNew = !order.includes(dragged)
if (isNew) {
newOrder.push(draggedId)
newOrder.push(dragged)
}
const draggedIndex = isNew
? newOrder.length - 1
: getIDIndex(e.dataTransfer.getData('itemIndex'))
: getIDIndex(draggedRef.itemIndex)

const droppedIndex = order.indexOf(e.currentTarget.id.split('__')[0])
const orderIdxChange = orderIdxTune(direction, droppedIndex > draggedIndex)
const orderIdxChanged = droppedIndex + orderIdxChange
const isEnabled = !disabledDropIds.includes(order[orderIdxChanged])

if (isEnabled && isSameGroup(e.dataTransfer.getData('group'), group)) {
applyDrop(e, orderIdxChanged, draggedIndex, newOrder, oldDraggedId)
if (isEnabled && isSameGroup(draggedRef.group, group)) {
applyDrop(orderIdxChanged, draggedIndex, newOrder, dragged)
}
}

Expand Down Expand Up @@ -245,20 +247,24 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
})
: undefined

const getTarget = (id: string, isEnteringRight: boolean) => (
<DropTarget
key="drop-target"
onDragOver={handleDragOver}
onDrop={handleOnDrop}
id={id}
className={getDropTargetClassNames(isEnteringRight)}
>
{dropTarget}
</DropTarget>
)

const createItemWithDropTarget = (id: string, item: JSX.Element) => {
const isEnteringRight = direction === DragEnterDirection.RIGHT
const target =
draggedOverGroup === group || draggedRef?.group === group ? (
<DropTarget
key="drop-target"
onDragOver={handleDragOver}
onDrop={handleOnDrop}
id={id}
className={getDropTargetClassNames(isEnteringRight)}
>
{dropTarget}
</DropTarget>
) : null
draggedOverGroup === group || draggedRef?.group === group
? getTarget(id, isEnteringRight)
: null

const itemWithTag = shouldShowOnDrag ? (
<div key="item" {...item.props} />
Expand All @@ -285,6 +291,17 @@ export const DragDropContainer: React.FC<DragDropContainerProps> = ({
return id === draggedOverId ? createItemWithDropTarget(id, item) : item
})

if (
isSameGroup(draggedRef?.group, group) &&
draggedRef?.itemId !== draggedId &&
!draggedOverId &&
parentDraggedOver &&
wrappedItems.length > 0
) {
const lastId = wrappedItems[wrappedItems.length - 1].id
wrappedItems.push(getTarget(lastId, false))
}

const Wrapper = wrapperComponent?.component
return Wrapper ? (
<Wrapper {...wrapperComponent.props} items={wrappedItems} />
Expand Down
2 changes: 1 addition & 1 deletion webview/src/shared/components/dragDrop/dragDropSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type DraggedInfo =
| {
itemIndex: string
itemId: string
group?: string
group: string
}
| undefined
export interface DragDropGroupState {
Expand Down