diff --git a/webview/src/experiments/components/table/MergeHeaderGroups.tsx b/webview/src/experiments/components/table/MergeHeaderGroups.tsx index f10b2b5b18..22b5292535 100644 --- a/webview/src/experiments/components/table/MergeHeaderGroups.tsx +++ b/webview/src/experiments/components/table/MergeHeaderGroups.tsx @@ -13,6 +13,7 @@ export const MergedHeaderGroups: React.FC<{ onDragUpdate: DragFunction onDragStart: DragFunction onDragEnd: DragFunction + onDrop: DragFunction setExpColumnNeedsShadow: (needsShadow: boolean) => void root: HTMLElement | null }> = ({ @@ -22,6 +23,7 @@ export const MergedHeaderGroups: React.FC<{ onDragUpdate, onDragEnd, onDragStart, + onDrop, root, setExpColumnNeedsShadow }) => { @@ -40,7 +42,8 @@ export const MergedHeaderGroups: React.FC<{ columns={columns} onDragEnter={onDragUpdate} onDragStart={onDragStart} - onDrop={onDragEnd} + onDragEnd={onDragEnd} + onDrop={onDrop} root={root} /> ))} diff --git a/webview/src/experiments/components/table/Table.test.tsx b/webview/src/experiments/components/table/Table.test.tsx index 4ac1efeb8c..8cf5162470 100644 --- a/webview/src/experiments/components/table/Table.test.tsx +++ b/webview/src/experiments/components/table/Table.test.tsx @@ -427,7 +427,7 @@ describe('Table', () => { let headers = await getHeaders() expect(headers.indexOf('threshold')).toBeGreaterThan( - headers.indexOf('loss') + headers.indexOf('accuracy') ) expect(headers.indexOf('test')).toBeGreaterThan( headers.indexOf('accuracy') @@ -441,12 +441,13 @@ describe('Table', () => { headers = await getHeaders() - expect(headers.indexOf('loss')).toBeGreaterThan( + expect(headers.indexOf('accuracy')).toBeGreaterThan( headers.indexOf('threshold') ) + const [, startingNode] = screen.getAllByText('summary.json') dragAndDrop( - screen.getByText('summary.json'), + startingNode, getDraggableHeaderFromText('test'), DragEnterDirection.AUTO ) diff --git a/webview/src/experiments/components/table/Table.tsx b/webview/src/experiments/components/table/Table.tsx index f788d317e2..de24b64b25 100644 --- a/webview/src/experiments/components/table/Table.tsx +++ b/webview/src/experiments/components/table/Table.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react' +import React, { useRef, useState, CSSProperties } from 'react' import { useSelector } from 'react-redux' import cx from 'classnames' import styles from './styles.module.scss' @@ -61,7 +61,10 @@ export const Table: React.FC = ({ instance }) => { ) return ( -
+
root: HTMLElement | null @@ -34,7 +36,12 @@ export const TableHead = ({ const columns = useSelector( (state: ExperimentsState) => state.tableData.columns ) + const headerDropTargetId = useSelector( + (state: ExperimentsState) => state.headerDropTarget + ) + const dispatch = useDispatch() const orderedColumns = useColumnOrder(columns, columnOrder) + const allHeaders: HeaderGroup[] = [] for (const headerGroup of headerGroups) { allHeaders.push(...headerGroup.headers) @@ -73,26 +80,33 @@ export const TableHead = ({ } const onDragUpdate = (e: DragEvent) => { - const displacer = draggingIds.current - displacer && - findDisplacedHeader(e.currentTarget.id, displacedHeader => { - const displaced = leafColumnIds(displacedHeader) - if (!displaced.some(id => displacer.includes(id))) { - fullColumnOrder.current && - setColumnOrder( - reorderColumnIds(fullColumnOrder.current, displacer, displaced) - ) - } - }) + findDisplacedHeader(e.currentTarget.id, displacedHeader => { + const displaced = leafColumnIds(displacedHeader) + dispatch(setDropTarget(displaced[displaced.length - 1])) + }) } const onDragEnd = () => { - draggingIds.current = undefined fullColumnOrder.current = undefined - sendMessage({ - payload: columnOrder, - type: MessageFromWebviewType.REORDER_COLUMNS - }) + draggingIds.current = undefined + dispatch(setDropTarget('')) + } + + const onDrop = () => { + const fullOrder = fullColumnOrder.current + const displacer = draggingIds.current + let newOrder: string[] = [] + + if (fullOrder && displacer) { + newOrder = reorderColumnIds(fullOrder, displacer, [headerDropTargetId]) + + setColumnOrder(newOrder) + sendMessage({ + payload: newOrder, + type: MessageFromWebviewType.REORDER_COLUMNS + }) + onDragEnd() + } } const selectedForPlotsCount = getSelectedForPlotsCount(rows) @@ -110,6 +124,7 @@ export const TableHead = ({ onDragStart={onDragStart} onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} + onDrop={onDrop} root={root} setExpColumnNeedsShadow={setExpColumnNeedsShadow} /> diff --git a/webview/src/experiments/components/table/TableHeader.tsx b/webview/src/experiments/components/table/TableHeader.tsx index ec97aa2464..4833095029 100644 --- a/webview/src/experiments/components/table/TableHeader.tsx +++ b/webview/src/experiments/components/table/TableHeader.tsx @@ -31,6 +31,7 @@ interface TableHeaderProps { columns: HeaderGroup[] orderedColumns: Column[] onDragEnter: DragFunction + onDragEnd: DragFunction onDragStart: DragFunction onDrop: DragFunction setExpColumnNeedsShadow: (needsShadow: boolean) => void @@ -42,6 +43,7 @@ export const TableHeader: React.FC = ({ columns, orderedColumns, onDragEnter, + onDragEnd, onDragStart, onDrop, root, @@ -100,6 +102,7 @@ export const TableHeader: React.FC = ({ sortEnabled={isSortable} hasFilter={hasFilter} onDragEnter={onDragEnter} + onDragEnd={onDragEnd} onDragStart={onDragStart} onDrop={onDrop} menuDisabled={!isSortable && column.group !== ColumnType.PARAMS} diff --git a/webview/src/experiments/components/table/TableHeaderCell.tsx b/webview/src/experiments/components/table/TableHeaderCell.tsx index 89a35734d6..aef24799ef 100644 --- a/webview/src/experiments/components/table/TableHeaderCell.tsx +++ b/webview/src/experiments/components/table/TableHeaderCell.tsx @@ -4,6 +4,7 @@ import { ColumnType } from 'dvc/src/experiments/webview/contract' import React, { useEffect } from 'react' +import { useSelector } from 'react-redux' import { HeaderGroup } from 'react-table' import cx from 'classnames' import { useInView } from 'react-intersection-observer' @@ -17,6 +18,7 @@ import { } from '../../util/columns' import { ContextMenu } from '../../../shared/components/contextMenu/ContextMenu' import { DragFunction } from '../../../shared/components/dragDrop/Draggable' +import { ExperimentsState } from '../../store' const calcResizerHeight = ( isPlaceholder: boolean, @@ -86,6 +88,7 @@ export const TableHeaderCell: React.FC<{ menuDisabled?: boolean menuContent?: React.ReactNode onDragEnter: DragFunction + onDragEnd: DragFunction onDragStart: DragFunction onDrop: DragFunction setExpColumnNeedsShadow: (needsShadow: boolean) => void @@ -100,15 +103,20 @@ export const TableHeaderCell: React.FC<{ menuContent, menuDisabled, onDragEnter, + onDragEnd, onDragStart, onDrop, root, setExpColumnNeedsShadow }) => { const [menuSuppressed, setMenuSuppressed] = React.useState(false) + const headerDropTargetId = useSelector( + (state: ExperimentsState) => state.headerDropTarget + ) const isDraggable = !column.placeholderOf && column.id !== 'id' const isPlaceholder = !!column.placeholderOf + const canResize = column.canResize && !isPlaceholder const resizerHeight = calcResizerHeight( isPlaceholder, @@ -127,6 +135,7 @@ export const TableHeaderCell: React.FC<{ menuSuppressed={menuSuppressed} onDragEnter={onDragEnter} onDragStart={onDragStart} + onDragEnd={onDragEnd} onDrop={onDrop} canResize={canResize} setMenuSuppressed={setMenuSuppressed} @@ -159,6 +168,12 @@ export const TableHeaderCell: React.FC<{ ) : ( cellContents )} +
) diff --git a/webview/src/experiments/components/table/TableHeaderCellContents.tsx b/webview/src/experiments/components/table/TableHeaderCellContents.tsx index 10c9a1b733..e09577bfdc 100644 --- a/webview/src/experiments/components/table/TableHeaderCellContents.tsx +++ b/webview/src/experiments/components/table/TableHeaderCellContents.tsx @@ -34,7 +34,8 @@ export const ColumnDragHandle: React.FC<{ onDragEnter: DragFunction onDragStart: DragFunction onDrop: DragFunction -}> = ({ disabled, column, onDragEnter, onDragStart, onDrop }) => { + onDragEnd: DragFunction +}> = ({ disabled, column, onDragEnter, onDragStart, onDragEnd, onDrop }) => { const DropTarget = {column?.name} return ( @@ -51,6 +52,7 @@ export const ColumnDragHandle: React.FC<{ dropTarget={DropTarget} onDragEnter={onDragEnter} onDragStart={onDragStart} + onDragEnd={onDragEnd} onDrop={onDrop} > {column?.render('Header')} @@ -67,6 +69,7 @@ export const TableHeaderCellContents: React.FC<{ isDraggable: boolean menuSuppressed: boolean onDragEnter: DragFunction + onDragEnd: DragFunction onDragStart: DragFunction onDrop: DragFunction canResize: boolean @@ -80,6 +83,7 @@ export const TableHeaderCellContents: React.FC<{ isDraggable, menuSuppressed, onDragEnter, + onDragEnd, onDragStart, onDrop, canResize, @@ -106,6 +110,7 @@ export const TableHeaderCellContents: React.FC<{ onDragEnter={onDragEnter} onDragStart={onDragStart} onDrop={onDrop} + onDragEnd={onDragEnd} /> {canResize && (
) => { + return action.payload || '' + } + } +}) + +export const { setDropTarget } = headerDropTargetSlice.actions + +export default headerDropTargetSlice.reducer diff --git a/webview/src/experiments/components/table/styles.module.scss b/webview/src/experiments/components/table/styles.module.scss index eb7e44fc86..91b76c7605 100644 --- a/webview/src/experiments/components/table/styles.module.scss +++ b/webview/src/experiments/components/table/styles.module.scss @@ -461,6 +461,22 @@ $bullet-size: calc(var(--design-unit) * 4px); .th { height: auto; background-color: $header-bg-color; + + .dropTarget { + position: absolute; + right: -3px; + bottom: 0; + width: 3px; + z-index: -1; + height: var(--table-head-height); + background-color: $header-resizer-color; + z-index: 1; + display: none; + + &.active { + display: block; + } + } } .td { height: auto; diff --git a/webview/src/experiments/store.ts b/webview/src/experiments/store.ts index 754b48e041..382c2c9d18 100644 --- a/webview/src/experiments/store.ts +++ b/webview/src/experiments/store.ts @@ -1,10 +1,12 @@ import { configureStore } from '@reduxjs/toolkit' import tableDataReducer from './components/table/tableDataSlice' import headersReducer from './components/table/headersSlice' +import headerDropTargetReducer from './components/table/headerDropTargetSlice' import dragAndDropReducer from '../shared/components/dragDrop/dragDropSlice' export const experimentsReducers = { dragAndDrop: dragAndDropReducer, + headerDropTarget: headerDropTargetReducer, headers: headersReducer, tableData: tableDataReducer } diff --git a/webview/src/experiments/util/columns.test.ts b/webview/src/experiments/util/columns.test.ts index fd23e5ff63..302375be1e 100644 --- a/webview/src/experiments/util/columns.test.ts +++ b/webview/src/experiments/util/columns.test.ts @@ -15,8 +15,8 @@ describe('reorderColumnIds()', () => { 'id_1' ]) expect(reorderColumnIds(twoColumnIds, ['id_2'], ['id_1'])).toStrictEqual([ - 'id_2', - 'id_1' + 'id_1', + 'id_2' ]) const threeColumnIds = [...twoColumnIds, 'id_3'] @@ -41,6 +41,6 @@ describe('reorderColumnIds()', () => { ).toStrictEqual(['id_3', 'id_1', 'id_2']) expect( reorderColumnIds(threeColumnIds, ['id_2', 'id_3'], ['id_1']) - ).toStrictEqual(['id_2', 'id_3', 'id_1']) + ).toStrictEqual(['id_1', 'id_2', 'id_3']) }) }) diff --git a/webview/src/experiments/util/columns.ts b/webview/src/experiments/util/columns.ts index 44f752cc74..c95d1c2f94 100644 --- a/webview/src/experiments/util/columns.ts +++ b/webview/src/experiments/util/columns.ts @@ -96,10 +96,11 @@ export const reorderColumnIds = ( ...columnIds.slice(displacedIndex + displaced.length) ] } + return [ ...columnIds.slice(0, displacedIndex), - ...displacer, ...displaced, + ...displacer, ...columnIds.slice(displacedIndex + displaced.length, displacerIndex), ...columnIds.slice(displacerIndex + displacer.length) ] diff --git a/webview/src/shared/components/dragDrop/Draggable.tsx b/webview/src/shared/components/dragDrop/Draggable.tsx index 51e442fbf8..9d46f7db96 100644 --- a/webview/src/shared/components/dragDrop/Draggable.tsx +++ b/webview/src/shared/components/dragDrop/Draggable.tsx @@ -15,6 +15,7 @@ export interface DraggableProps { onDrop: DragFunction onDragStart: DragFunction onDragEnter: DragFunction + onDragEnd: DragFunction } export const Draggable: React.FC = ({ @@ -25,7 +26,8 @@ export const Draggable: React.FC = ({ dropTarget, onDrop, onDragEnter, - onDragStart + onDragStart, + onDragEnd }) => { const groupState = useSelector( (state: ExperimentsState) => state.dragAndDrop.groups[group] || {} @@ -56,7 +58,7 @@ export const Draggable: React.FC = ({ } const handleDragEnter = (e: DragEvent) => { - if (!disabled && draggedId) { + if (draggedId) { const { id } = e.currentTarget if (id !== draggedId && id !== draggedOverId) { @@ -70,7 +72,7 @@ export const Draggable: React.FC = ({ e.preventDefault() } - const handleDragEnd = () => { + const handleDragEnd = (e: DragEvent) => { dispatch( setGroup({ group: { @@ -81,12 +83,14 @@ export const Draggable: React.FC = ({ id: group }) ) + onDragEnd(e) } if (dropTarget && id === draggedOverId) { return (