diff --git a/webview/src/experiments/components/App.tsx b/webview/src/experiments/components/App.tsx index 1b4c5149e7..da5c0dd30a 100644 --- a/webview/src/experiments/components/App.tsx +++ b/webview/src/experiments/components/App.tsx @@ -1,53 +1,14 @@ -import React, { useCallback } from 'react' -import { useDispatch } from 'react-redux' +import React from 'react' import { MessageToWebview, MessageToWebviewType } from 'dvc/src/webview/contract' import { TableData } from 'dvc/src/experiments/webview/contract' import Experiments from './Experiments' -import { - update, - updateSelectedBranches, - updateChanges, - updateCliError, - updateColumnOrder, - updateColumns, - updateColumnWidths, - updateFilters, - updateHasBranchesToSelect, - updateHasCheckpoints, - updateHasConfig, - updateHasMoreCommits, - updateHasRunningWorkspaceExperiment, - updateIsShowingMoreCommits, - updateRows, - updateSelectedForPlotsCount, - updateSorts, - updateShowOnlyChanged -} from '../state/tableDataSlice' +import { update } from '../state/tableDataSlice' import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' import { ExperimentsDispatch } from '../store' - -const actionToDispatch = { - changes: updateChanges, - cliError: updateCliError, - columnOrder: updateColumnOrder, - columnWidths: updateColumnWidths, - columns: updateColumns, - filters: updateFilters, - hasBranchesToSelect: updateHasBranchesToSelect, - hasCheckpoints: updateHasCheckpoints, - hasConfig: updateHasConfig, - hasMoreCommits: updateHasMoreCommits, - hasRunningWorkspaceExperiment: updateHasRunningWorkspaceExperiment, - isShowingMoreCommits: updateIsShowingMoreCommits, - rows: updateRows, - selectedBranches: updateSelectedBranches, - selectedForPlotsCount: updateSelectedForPlotsCount, - showOnlyChanged: updateShowOnlyChanged, - sorts: updateSorts -} as const +import { dispatchAction } from '../../shared/dispatchAction' const feedStore = ( data: MessageToWebview, @@ -56,30 +17,14 @@ const feedStore = ( if (data?.type !== MessageToWebviewType.SET_DATA) { return } - dispatch(update(!!data.data)) + const stateUpdate = data?.data + dispatch(update(!!stateUpdate)) - for (const key of Object.keys(data.data)) { - const tKey = key as keyof typeof data.data - const action = actionToDispatch[tKey] - const value = data.data[tKey] - if (!action) { - continue - } - dispatch(action(value as never)) - } + dispatchAction('experiments', stateUpdate, dispatch) } export const App: React.FC> = () => { - const dispatch = useDispatch() - - useVsCodeMessaging( - useCallback( - ({ data }: { data: MessageToWebview }) => { - feedStore(data, dispatch) - }, - [dispatch] - ) - ) + useVsCodeMessaging(feedStore) return } diff --git a/webview/src/plots/components/App.tsx b/webview/src/plots/components/App.tsx index ca83f3910f..602be7ddd3 100644 --- a/webview/src/plots/components/App.tsx +++ b/webview/src/plots/components/App.tsx @@ -1,5 +1,4 @@ -import React, { useCallback } from 'react' -import { useDispatch } from 'react-redux' +import React from 'react' import { PlotsData, PlotsDataKeys, @@ -8,30 +7,13 @@ import { } from 'dvc/src/plots/webview/contract' import { MessageToWebview } from 'dvc/src/webview/contract' import { Plots } from './Plots' -import { - setCollapsed as setCustomPlotsCollapsed, - update as updateCustomPlots -} from './customPlots/customPlotsSlice' -import { - setCollapsed as setComparisonTableCollapsed, - updateShouldShowTooManyPlotsMessage as updateShouldShowTooManyImagesMessage, - update as updateComparisonTable -} from './comparisonTable/comparisonTableSlice' -import { - setCollapsed as setTemplatePlotsCollapsed, - updateShouldShowTooManyPlotsMessage as updateShouldShowTooManyTemplatesMessage, - update as updateTemplatePlots -} from './templatePlots/templatePlotsSlice' -import { - initialize, - updateCliError, - updateHasPlots, - updateHasUnselectedPlots, - updatePlotErrors, - updateSelectedRevisions -} from './webviewSlice' +import { setCollapsed as setCustomPlotsCollapsed } from './customPlots/customPlotsSlice' +import { setCollapsed as setComparisonTableCollapsed } from './comparisonTable/comparisonTableSlice' +import { setCollapsed as setTemplatePlotsCollapsed } from './templatePlots/templatePlotsSlice' +import { initialize } from './webviewSlice' import { PlotsDispatch } from '../store' import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' +import { dispatchAction } from '../../shared/dispatchAction' const dispatchCollapsedSections = ( sections: SectionCollapsed, @@ -46,60 +28,28 @@ const dispatchCollapsedSections = ( } } -const actionToDispatch = { - [PlotsDataKeys.CLI_ERROR]: updateCliError, - [PlotsDataKeys.CUSTOM]: updateCustomPlots, - [PlotsDataKeys.COMPARISON]: updateComparisonTable, - [PlotsDataKeys.TEMPLATE]: updateTemplatePlots, - [PlotsDataKeys.HAS_PLOTS]: updateHasPlots, - [PlotsDataKeys.HAS_UNSELECTED_PLOTS]: updateHasUnselectedPlots, - [PlotsDataKeys.PLOT_ERRORS]: updatePlotErrors, - [PlotsDataKeys.SELECTED_REVISIONS]: updateSelectedRevisions, - [PlotsDataKeys.SHOW_TOO_MANY_TEMPLATE_PLOTS]: - updateShouldShowTooManyTemplatesMessage, - [PlotsDataKeys.SHOW_TOO_MANY_COMPARISON_IMAGES]: - updateShouldShowTooManyImagesMessage -} as const - export const feedStore = ( data: MessageToWebview, dispatch: PlotsDispatch ) => { - if (!data?.data) { + const stateUpdate = data?.data + if (!stateUpdate) { return } dispatch(initialize()) - const keys = Object.keys(data.data) as PlotsDataKeys[] - for (const key of keys) { - if (key === PlotsDataKeys.SECTION_COLLAPSED) { - dispatchCollapsedSections( - data.data[PlotsDataKeys.SECTION_COLLAPSED] as SectionCollapsed, - dispatch - ) - continue - } - - const action = actionToDispatch[key as keyof typeof actionToDispatch] - const value = data.data[key] - if (!action) { - continue - } - dispatch(action(value as never)) + const keys = Object.keys(stateUpdate) as PlotsDataKeys[] + if (keys.includes(PlotsDataKeys.SECTION_COLLAPSED)) { + dispatchCollapsedSections( + stateUpdate[PlotsDataKeys.SECTION_COLLAPSED] as SectionCollapsed, + dispatch + ) } + dispatchAction('plots', stateUpdate, dispatch) } export const App = () => { - const dispatch = useDispatch() - - useVsCodeMessaging( - useCallback( - ({ data }: { data: MessageToWebview }) => { - feedStore(data, dispatch) - }, - [dispatch] - ) - ) + useVsCodeMessaging(feedStore) return } diff --git a/webview/src/setup/components/App.tsx b/webview/src/setup/components/App.tsx index 5a679f9575..f45b2c0523 100644 --- a/webview/src/setup/components/App.tsx +++ b/webview/src/setup/components/App.tsx @@ -1,6 +1,6 @@ import { SetupSection, SetupData } from 'dvc/src/setup/webview/contract' import { MessageToWebview } from 'dvc/src/webview/contract' -import React, { useCallback } from 'react' +import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { Dvc } from './dvc/Dvc' import { Experiments } from './experiments/Experiments' @@ -10,30 +10,10 @@ import { Remotes } from './remotes/Remotes' import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' import { TooltipIconType } from '../../shared/components/sectionContainer/InfoTooltip' import { SetupDispatch, SetupState } from '../store' -import { initialize, updateSectionCollapsed } from '../state/webviewSlice' -import { - updateCanGitInitialize, - updateCliCompatible, - updateDvcCliDetails, - updateIsAboveLatestTestedVersion, - updateIsPythonEnvironmentGlobal, - updateIsPythonExtensionInstalled, - updateIsPythonExtensionUsed, - updateNeedsGitInitialized, - updateProjectInitialized, - updatePythonBinPath -} from '../state/dvcSlice' -import { - updateHasData as updateExperimentsHasData, - updateNeedsGitCommit -} from '../state/experimentsSlice' -import { updateRemoteList } from '../state/remoteSlice' -import { - updateIsStudioConnected, - updateSelfHostedStudioUrl, - updateShareLiveToStudio -} from '../state/studioSlice' +import { initialize } from '../state/webviewSlice' +import { updateShareLiveToStudio } from '../state/studioSlice' import { setStudioShareExperimentsLive } from '../util/messages' +import { dispatchAction } from '../../shared/dispatchAction' const getDvcStatusIcon = ( isDvcSetup: boolean, @@ -56,44 +36,17 @@ const getStudioStatusIcon = (cliCompatible: boolean, isConnected: boolean) => { return isConnected ? TooltipIconType.PASSED : TooltipIconType.WARNING } -const actionToDispatch = { - canGitInitialize: updateCanGitInitialize, - cliCompatible: updateCliCompatible, - dvcCliDetails: updateDvcCliDetails, - hasData: updateExperimentsHasData, - isAboveLatestTestedVersion: updateIsAboveLatestTestedVersion, - isPythonEnvironmentGlobal: updateIsPythonEnvironmentGlobal, - isPythonExtensionInstalled: updateIsPythonExtensionInstalled, - isPythonExtensionUsed: updateIsPythonExtensionUsed, - isStudioConnected: updateIsStudioConnected, - needsGitCommit: updateNeedsGitCommit, - needsGitInitialized: updateNeedsGitInitialized, - projectInitialized: updateProjectInitialized, - pythonBinPath: updatePythonBinPath, - remoteList: updateRemoteList, - sectionCollapsed: updateSectionCollapsed, - selfHostedStudioUrl: updateSelfHostedStudioUrl, - shareLiveToStudio: updateShareLiveToStudio -} as const - export const feedStore = ( data: MessageToWebview, dispatch: SetupDispatch ) => { - if (!data?.data) { + const stateUpdate = data?.data + if (!stateUpdate) { return } dispatch(initialize()) - for (const key of Object.keys(data.data)) { - const tKey = key as keyof typeof data.data - const action = actionToDispatch[tKey] - const value = data.data[tKey] - if (!action) { - continue - } - dispatch(action(value as never)) - } + dispatchAction('setup', stateUpdate, dispatch) } export const App: React.FC = () => { @@ -110,14 +63,7 @@ export const App: React.FC = () => { const dispatch = useDispatch() - useVsCodeMessaging( - useCallback( - ({ data }: { data: MessageToWebview }) => { - feedStore(data, dispatch) - }, - [dispatch] - ) - ) + useVsCodeMessaging(feedStore) const setShareLiveToStudio = (shouldShareLive: boolean) => { dispatch(updateShareLiveToStudio(shouldShareLive)) diff --git a/webview/src/shared/dispatchAction.ts b/webview/src/shared/dispatchAction.ts new file mode 100644 index 0000000000..0e43d0396b --- /dev/null +++ b/webview/src/shared/dispatchAction.ts @@ -0,0 +1,139 @@ +import { UnknownAction } from '@reduxjs/toolkit' +import { TableData } from 'dvc/src/experiments/webview/contract' +import { PlotsData, PlotsDataKeys } from 'dvc/src/plots/webview/contract' +import { SetupData } from 'dvc/src/setup/webview/contract' +import { + updateChanges, + updateColumnOrder, + updateColumnWidths, + updateColumns, + updateFilters, + updateHasBranchesToSelect, + updateHasCheckpoints, + updateHasConfig, + updateHasMoreCommits, + updateHasRunningWorkspaceExperiment, + updateIsShowingMoreCommits, + updateRows, + updateSelectedBranches, + updateSelectedForPlotsCount, + updateShowOnlyChanged, + updateSorts, + updateCliError as updateTableCliError +} from '../experiments/state/tableDataSlice' +import { ExperimentsDispatch } from '../experiments/store' +import { + update as updateComparisonTable, + updateShouldShowTooManyPlotsMessage as updateShouldShowTooManyImagesMessage +} from '../plots/components/comparisonTable/comparisonTableSlice' +import { update as updateCustomPlots } from '../plots/components/customPlots/customPlotsSlice' +import { + updateShouldShowTooManyPlotsMessage as updateShouldShowTooManyTemplatesMessage, + update as updateTemplatePlots +} from '../plots/components/templatePlots/templatePlotsSlice' +import { + updateHasPlots, + updateHasUnselectedPlots, + updatePlotErrors, + updateCliError as updatePlotsCliError, + updateSelectedRevisions +} from '../plots/components/webviewSlice' +import { PlotsDispatch } from '../plots/store' +import { + updateCanGitInitialize, + updateCliCompatible, + updateDvcCliDetails, + updateIsAboveLatestTestedVersion, + updateIsPythonEnvironmentGlobal, + updateIsPythonExtensionInstalled, + updateIsPythonExtensionUsed, + updateNeedsGitInitialized, + updateProjectInitialized, + updatePythonBinPath +} from '../setup/state/dvcSlice' +import { + updateHasData as updateExperimentsHasData, + updateNeedsGitCommit +} from '../setup/state/experimentsSlice' +import { updateRemoteList } from '../setup/state/remoteSlice' +import { + updateIsStudioConnected, + updateSelfHostedStudioUrl, + updateShareLiveToStudio +} from '../setup/state/studioSlice' +import { updateSectionCollapsed } from '../setup/state/webviewSlice' +import { SetupDispatch } from '../setup/store' + +const actionToDispatch = { + experiments: { + changes: updateChanges, + cliError: updateTableCliError, + columnOrder: updateColumnOrder, + columnWidths: updateColumnWidths, + columns: updateColumns, + filters: updateFilters, + hasBranchesToSelect: updateHasBranchesToSelect, + hasCheckpoints: updateHasCheckpoints, + hasConfig: updateHasConfig, + hasMoreCommits: updateHasMoreCommits, + hasRunningWorkspaceExperiment: updateHasRunningWorkspaceExperiment, + isShowingMoreCommits: updateIsShowingMoreCommits, + rows: updateRows, + selectedBranches: updateSelectedBranches, + selectedForPlotsCount: updateSelectedForPlotsCount, + showOnlyChanged: updateShowOnlyChanged, + sorts: updateSorts + }, + plots: { + [PlotsDataKeys.CLI_ERROR]: updatePlotsCliError, + [PlotsDataKeys.CUSTOM]: updateCustomPlots, + [PlotsDataKeys.COMPARISON]: updateComparisonTable, + [PlotsDataKeys.TEMPLATE]: updateTemplatePlots, + [PlotsDataKeys.HAS_PLOTS]: updateHasPlots, + [PlotsDataKeys.HAS_UNSELECTED_PLOTS]: updateHasUnselectedPlots, + [PlotsDataKeys.PLOT_ERRORS]: updatePlotErrors, + [PlotsDataKeys.SELECTED_REVISIONS]: updateSelectedRevisions, + [PlotsDataKeys.SHOW_TOO_MANY_TEMPLATE_PLOTS]: + updateShouldShowTooManyTemplatesMessage, + [PlotsDataKeys.SHOW_TOO_MANY_COMPARISON_IMAGES]: + updateShouldShowTooManyImagesMessage + }, + setup: { + canGitInitialize: updateCanGitInitialize, + cliCompatible: updateCliCompatible, + dvcCliDetails: updateDvcCliDetails, + hasData: updateExperimentsHasData, + isAboveLatestTestedVersion: updateIsAboveLatestTestedVersion, + isPythonEnvironmentGlobal: updateIsPythonEnvironmentGlobal, + isPythonExtensionInstalled: updateIsPythonExtensionInstalled, + isPythonExtensionUsed: updateIsPythonExtensionUsed, + isStudioConnected: updateIsStudioConnected, + needsGitCommit: updateNeedsGitCommit, + needsGitInitialized: updateNeedsGitInitialized, + projectInitialized: updateProjectInitialized, + pythonBinPath: updatePythonBinPath, + remoteList: updateRemoteList, + sectionCollapsed: updateSectionCollapsed, + selfHostedStudioUrl: updateSelfHostedStudioUrl, + shareLiveToStudio: updateShareLiveToStudio + } +} as const + +export const dispatchAction = ( + type: keyof typeof actionToDispatch, + stateUpdate: NonNullable, + dispatch: ExperimentsDispatch | PlotsDispatch | SetupDispatch +) => { + const actions = actionToDispatch[type] + + for (const key of Object.keys(stateUpdate)) { + const tKey = key as keyof typeof stateUpdate + const value = stateUpdate[tKey] + const action = actions[tKey] as (input: typeof value) => UnknownAction + + if (!action) { + continue + } + dispatch(action(value)) + } +} diff --git a/webview/src/shared/hooks/useVsCodeMessaging.ts b/webview/src/shared/hooks/useVsCodeMessaging.ts index 052c8cdd21..70cdb861a1 100644 --- a/webview/src/shared/hooks/useVsCodeMessaging.ts +++ b/webview/src/shared/hooks/useVsCodeMessaging.ts @@ -3,15 +3,31 @@ import { MessageToWebview, WebviewData } from 'dvc/src/webview/contract' -import { useEffect } from 'react' +import { useCallback, useEffect } from 'react' +import { useDispatch } from 'react-redux' import { sendMessage } from '../vscode' +import { ExperimentsDispatch } from '../../experiments/store' +import { SetupDispatch } from '../../setup/store' +import { PlotsDispatch } from '../../plots/store' const signalInitialized = () => sendMessage({ type: MessageFromWebviewType.INITIALIZED }) export function useVsCodeMessaging( - handler?: (event: { data: MessageToWebview }) => void + feedStore: ( + data: MessageToWebview, + dispatch: PlotsDispatch | ExperimentsDispatch | SetupDispatch + ) => void ) { + const dispatch = useDispatch() + + const handler = useCallback( + ({ data }: { data: MessageToWebview }) => { + feedStore(data, dispatch) + }, + [dispatch, feedStore] + ) + useEffect(() => { signalInitialized() }, [])