diff --git a/webview/src/experiments/components/Experiments.tsx b/webview/src/experiments/components/Experiments.tsx index 10e6832ec2..821fde2774 100644 --- a/webview/src/experiments/components/Experiments.tsx +++ b/webview/src/experiments/components/Experiments.tsx @@ -26,7 +26,6 @@ import buildDynamicColumns from '../util/buildDynamicColumns' import { sendMessage } from '../../shared/vscode' import { WebviewWrapper } from '../../shared/components/webviewWrapper/WebviewWrapper' import { GetStarted } from '../../shared/components/getStarted/GetStarted' -import { DragDropProvider } from '../../shared/components/dragDrop/DragDropContext' import { EmptyState } from '../../shared/components/emptyState/EmptyState' const DEFAULT_COLUMN_WIDTH = 90 @@ -224,11 +223,9 @@ export const ExperimentsTable: React.FC<{ } return ( - - - - - + +
+ ) } diff --git a/webview/src/experiments/components/table/Table.test.tsx b/webview/src/experiments/components/table/Table.test.tsx index 4e81c21771..26b949a319 100644 --- a/webview/src/experiments/components/table/Table.test.tsx +++ b/webview/src/experiments/components/table/Table.test.tsx @@ -3,6 +3,7 @@ */ /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectHeaders"] }] */ import '@testing-library/jest-dom/extend-expect' +import { configureStore } from '@reduxjs/toolkit' import { cleanup, fireEvent, @@ -10,6 +11,7 @@ import { render, screen } from '@testing-library/react' +import { Provider } from 'react-redux' import { Experiment, TableData } from 'dvc/src/experiments/webview/contract' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import React from 'react' @@ -20,7 +22,6 @@ import { Table } from './Table' import styles from './styles.module.scss' import { ExperimentsTable } from '../Experiments' import * as ColumnOrder from '../../hooks/useColumnOrder' - import { vsCodeApi } from '../../../shared/api' import { expectHeaders, @@ -29,6 +30,7 @@ import { } from '../../../test/sort' import { dragAndDrop } from '../../../test/dragDrop' import { DragEnterDirection } from '../../../shared/components/dragDrop/util' +import { experimentsReducers } from '../../store' import { customQueries } from '../../../test/queries' jest.mock('../../../shared/api') @@ -123,14 +125,23 @@ describe('Table', () => { } const renderTable = (testData = {}, tableInstance = instance) => { const tableData = { ...dummyTableData, ...testData } - return render(
) + return render( + +
+ + ) } const renderExperimentsTable = ( data: TableData = sortingTableDataFixture ) => { - return render(, { - queries: { ...queries, ...customQueries } - }) + return render( + + + , + { + queries: { ...queries, ...customQueries } + } + ) } beforeAll(() => { @@ -335,7 +346,11 @@ describe('Table', () => { ...sortingTableDataFixture, columnWidths } - render() + render( + + + + ) const [experimentColumnResizeHandle] = await screen.findAllByRole( 'separator' ) diff --git a/webview/src/experiments/index.tsx b/webview/src/experiments/index.tsx index 95de9279c6..75ea6a1fe1 100644 --- a/webview/src/experiments/index.tsx +++ b/webview/src/experiments/index.tsx @@ -1,7 +1,13 @@ import React from 'react' import ReactDOM from 'react-dom/client' +import { Provider } from 'react-redux' import '../shared/style.scss' import { App } from './components/App' +import { experimentsStore } from './store' const root = ReactDOM.createRoot(document.querySelector('#root') as HTMLElement) -root.render() +root.render( + + + +) diff --git a/webview/src/experiments/store.ts b/webview/src/experiments/store.ts new file mode 100644 index 0000000000..933f4dacb2 --- /dev/null +++ b/webview/src/experiments/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit' +import dragAndDropReducer from '../shared/components/dragDrop/dragDropSlice' + +export const experimentsReducers = { + dragAndDrop: dragAndDropReducer +} + +export const experimentsStore = configureStore({ + reducer: experimentsReducers +}) + +export type ExperimentsState = ReturnType +export type ExperimentsDispatch = typeof experimentsStore.dispatch diff --git a/webview/src/shared/components/dragDrop/DragDropContext.tsx b/webview/src/shared/components/dragDrop/DragDropContext.tsx deleted file mode 100644 index bde6b08555..0000000000 --- a/webview/src/shared/components/dragDrop/DragDropContext.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { createContext, useState } from 'react' - -export type DraggedInfo = - | { - itemIndex: string - itemId: string - group?: string - } - | undefined - -export interface DragDropGroupState { - draggedId?: string - draggedOverId?: string -} - -export type GroupStates = { - [group: string]: DragDropGroupState | undefined -} - -export type DragDropContextValue = { - draggedRef: DraggedInfo - setDraggedRef: ((draggedRef: DraggedInfo) => void) | undefined - groupStates?: GroupStates - setGroupState?: (group: string, handlers: DragDropGroupState) => void - removeGroupState?: (group: string) => void -} - -export const DragDropContext = createContext({ - draggedRef: undefined, - groupStates: undefined, - removeGroupState: undefined, - setDraggedRef: undefined, - setGroupState: undefined -}) - -type DragDropProviderProps = { - children: React.ReactNode -} - -export const DragDropProvider: React.FC = ({ - children -}) => { - const [draggedRef, setDraggedRef] = useState(undefined) - - const [groupStates, setGroupStates] = useState({}) - - const changeDraggedRef = (d: DraggedInfo) => setDraggedRef(d) - - const setGroupState = (group: string, handlers: DragDropGroupState) => { - setGroupStates({ - ...groupStates, - [group]: handlers - }) - } - - const removeGroupState = (group: string) => { - setGroupStates({ - ...groupStates, - [group]: undefined - }) - } - - return ( - - {children} - - ) -} diff --git a/webview/src/shared/components/dragDrop/DragDropWorkbench.tsx b/webview/src/shared/components/dragDrop/DragDropWorkbench.tsx index fef1886459..5a7b976e50 100644 --- a/webview/src/shared/components/dragDrop/DragDropWorkbench.tsx +++ b/webview/src/shared/components/dragDrop/DragDropWorkbench.tsx @@ -1,6 +1,8 @@ -import React, { DragEvent, useContext } from 'react' +import React, { DragEvent } from 'react' +import { useDispatch, useSelector } from 'react-redux' import { makeTarget } from './DragDropContainer' -import { DragDropContext, DragDropContextValue } from './DragDropContext' +import { setGroup } from './dragDropSlice' +import { ExperimentsState } from '../../../experiments/store' export type OnDrop = (draggedId: string, draggedOverId: string) => void export type OnDragStart = (draggedId: string) => void @@ -26,21 +28,33 @@ export const Draggable: React.FC = ({ onDrop, onDragOver, onDragStart + // eslint-disable-next-line sonarjs/cognitive-complexity }) => { - const { groupStates, setGroupState } = - useContext(DragDropContext) + const groupStates = useSelector( + (state: ExperimentsState) => state.dragAndDrop.groups + ) + const dispatch = useDispatch() - const groupState = groupStates?.[group] || {} + const groupState = groupStates[group] || {} const { draggedOverId, draggedId } = groupState + const modifyGroup = (id: string) => { + dispatch( + setGroup({ + group: { + ...groupState, + draggedId: id + }, + id: group + }) + ) + } + const handleDragStart = (e: DragEvent) => { const { id } = e.currentTarget e.dataTransfer.effectAllowed = 'move' e.dataTransfer.dropEffect = 'move' - setGroupState?.(group, { - ...groupState, - draggedId: id - }) + modifyGroup(id) onDragStart?.(id) } @@ -52,16 +66,14 @@ export const Draggable: React.FC = ({ } const handleDragEnter = (e: DragEvent) => { - const { id } = e.currentTarget - !disabled && - draggedId && - id !== draggedId && - id !== draggedOverId && - (setGroupState?.(group, { - ...groupState, - draggedOverId: id - }) || - onDragOver?.(draggedId, id)) + if (!disabled && draggedId) { + const { id } = e.currentTarget + + if (id !== draggedId && id !== draggedOverId) { + modifyGroup(id) + onDragOver?.(draggedId, id) + } + } } const handleDragOver = (e: DragEvent) => { @@ -69,11 +81,16 @@ export const Draggable: React.FC = ({ } const handleDragEnd = () => { - setGroupState?.(group, { - ...groupState, - draggedId: undefined, - draggedOverId: undefined - }) + dispatch( + setGroup({ + group: { + ...groupState, + draggedId: undefined, + draggedOverId: undefined + }, + id: group + }) + ) } const item = ( diff --git a/webview/src/shared/components/dragDrop/dragDropSlice.ts b/webview/src/shared/components/dragDrop/dragDropSlice.ts index b547e88cbb..95596f5874 100644 --- a/webview/src/shared/components/dragDrop/dragDropSlice.ts +++ b/webview/src/shared/components/dragDrop/dragDropSlice.ts @@ -7,12 +7,22 @@ export type DraggedInfo = group?: string } | undefined +export interface DragDropGroupState { + draggedId?: string + draggedOverId?: string +} + +export type GroupStates = { + [group: string]: DragDropGroupState | undefined +} export interface DragDropState { draggedRef: DraggedInfo + groups: GroupStates } export const dragDropInitialState: DragDropState = { - draggedRef: undefined + draggedRef: undefined, + groups: {} } export const dragDropSlice = createSlice({ @@ -24,10 +34,19 @@ export const dragDropSlice = createSlice({ ...state, draggedRef: action.payload } + }, + setGroup: ( + state, + action: PayloadAction<{ id: string; group: DragDropGroupState }> + ) => { + return { + ...state, + groups: { ...state.groups, [action.payload.id]: action.payload.group } + } } } }) -export const { changeRef } = dragDropSlice.actions +export const { changeRef, setGroup } = dragDropSlice.actions export default dragDropSlice.reducer diff --git a/webview/src/stories/Table.stories.tsx b/webview/src/stories/Table.stories.tsx index 61aee5aa03..1a6fcdd718 100644 --- a/webview/src/stories/Table.stories.tsx +++ b/webview/src/stories/Table.stories.tsx @@ -1,4 +1,6 @@ +import { configureStore } from '@reduxjs/toolkit' import React from 'react' +import { Provider } from 'react-redux' import { ComponentStory } from '@storybook/react' import { Meta } from '@storybook/react/types-6-0' import rowsFixture from 'dvc/src/test/fixtures/expShow/rows' @@ -22,6 +24,7 @@ import { setExperimentsAsSelected, setExperimentsAsStarred } from '../test/tableDataFixture' +import { experimentsReducers } from '../experiments/store' const tableData: TableData = { changes: workspaceChangesFixture, @@ -100,7 +103,11 @@ export default { } as Meta const Template: ComponentStory = ({ tableData }) => { - return + return ( + + + + ) } export const WithData = Template.bind({}) @@ -196,9 +203,11 @@ WithNoSortsOrFilters.args = { export const Scrolled: ComponentStory = ({ tableData }) => { return ( -
- -
+ +
+ +
+
) } Scrolled.play = async ({ canvasElement }) => { diff --git a/webview/src/test/experimentsTable.tsx b/webview/src/test/experimentsTable.tsx index be8e6730ad..e97a90bd52 100644 --- a/webview/src/test/experimentsTable.tsx +++ b/webview/src/test/experimentsTable.tsx @@ -1,3 +1,4 @@ +import { configureStore } from '@reduxjs/toolkit' import { fireEvent, render, @@ -6,12 +7,14 @@ import { queries } from '@testing-library/react' import React from 'react' +import { Provider } from 'react-redux' import deeplyNestedTableDataFixture from 'dvc/src/test/fixtures/expShow/deeplyNested' import tableDataFixture from 'dvc/src/test/fixtures/expShow/tableData' import { MessageToWebviewType } from 'dvc/src/webview/contract' import { tableData as sortingTableDataFixture } from './sort' import { customQueries, getRow } from './queries' import { App } from '../experiments/components/App' +import { experimentsReducers } from '../experiments/store' export const setTableData = (data = tableDataFixture) => { fireEvent( @@ -26,9 +29,14 @@ export const setTableData = (data = tableDataFixture) => { } export const renderTable = (data = tableDataFixture) => { - const renderedTable = render(, { - queries: { ...queries, ...customQueries } - }) + const renderedTable = render( + + + , + { + queries: { ...queries, ...customQueries } + } + ) setTableData(data) return renderedTable }