diff --git a/webview/src/experiments/components/App.tsx b/webview/src/experiments/components/App.tsx index c8296d0f8d..469a14a37b 100644 --- a/webview/src/experiments/components/App.tsx +++ b/webview/src/experiments/components/App.tsx @@ -1,19 +1,19 @@ -import React, { useCallback } from 'react' -import { useDispatch } from 'react-redux' +import { TableData } from 'dvc/src/experiments/webview/contract' import { MessageToWebview, MessageToWebviewType } from 'dvc/src/webview/contract' -import { TableData } from 'dvc/src/experiments/webview/contract' +import React from 'react' import Experiments from './Experiments' +import { dispatchActions } from '../../shared/dispatchActions' +import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' import { update, - updateSelectedBranches, updateChanges, updateCliError, updateColumnOrder, - updateColumns, updateColumnWidths, + updateColumns, updateFilters, updateHasBranchesToSelect, updateHasCheckpoints, @@ -22,92 +22,50 @@ import { updateHasRunningWorkspaceExperiment, updateIsShowingMoreCommits, updateRows, + updateSelectedBranches, updateSelectedForPlotsCount, - updateSorts, - updateShowOnlyChanged + updateShowOnlyChanged, + updateSorts } from '../state/tableDataSlice' -import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' +import { ExperimentsDispatch } from '../store' -export const App: React.FC> = () => { - const dispatch = useDispatch() +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 +} + +export type ExperimentsActions = typeof actionToDispatch + +const feedStore = ( + data: MessageToWebview, + dispatch: ExperimentsDispatch +) => { + if (data?.type !== MessageToWebviewType.SET_DATA) { + return + } + const stateUpdate = data?.data + dispatch(update(!!stateUpdate)) - useVsCodeMessaging( - useCallback( - ({ data }: { data: MessageToWebview }) => { - if (data.type === MessageToWebviewType.SET_DATA) { - dispatch(update(!!data.data)) - for (const key of Object.keys(data.data)) { - switch (key) { - case 'changes': - dispatch(updateChanges(data.data.changes)) - continue - case 'cliError': - dispatch(updateCliError(data.data.cliError)) - continue - case 'columnOrder': - dispatch(updateColumnOrder(data.data.columnOrder)) - continue - case 'columns': - dispatch(updateColumns(data.data.columns)) - continue - case 'columnsWidths': - dispatch(updateColumnWidths(data.data.columnWidths)) - continue - case 'filters': - dispatch(updateFilters(data.data.filters)) - continue - case 'hasBranchesToSelect': - dispatch( - updateHasBranchesToSelect(data.data.hasBranchesToSelect) - ) - continue - case 'hasCheckpoints': - dispatch(updateHasCheckpoints(data.data.hasCheckpoints)) - continue - case 'hasConfig': - dispatch(updateHasConfig(data.data.hasConfig)) - continue - case 'hasMoreCommits': - dispatch(updateHasMoreCommits(data.data.hasMoreCommits)) - continue - case 'hasRunningWorkspaceExperiment': - dispatch( - updateHasRunningWorkspaceExperiment( - data.data.hasRunningWorkspaceExperiment - ) - ) - continue - case 'isShowingMoreCommits': - dispatch( - updateIsShowingMoreCommits(data.data.isShowingMoreCommits) - ) - continue - case 'rows': - dispatch(updateRows(data.data.rows)) - continue - case 'selectedBranches': - dispatch(updateSelectedBranches(data.data.selectedBranches)) - continue - case 'selectedForPlotsCount': - dispatch( - updateSelectedForPlotsCount(data.data.selectedForPlotsCount) - ) - continue - case 'showOnlyChanged': - dispatch(updateShowOnlyChanged(data.data.showOnlyChanged)) - continue - case 'sorts': - dispatch(updateSorts(data.data.sorts)) - continue - default: - continue - } - } - } - }, - [dispatch] - ) - ) + dispatchActions(actionToDispatch, stateUpdate, dispatch) +} + +export const App: React.FC> = () => { + useVsCodeMessaging(feedStore) return } diff --git a/webview/src/plots/components/App.tsx b/webview/src/plots/components/App.tsx index cfb94cdaac..bb2ac9afd3 100644 --- a/webview/src/plots/components/App.tsx +++ b/webview/src/plots/components/App.tsx @@ -1,25 +1,21 @@ -import React, { useCallback } from 'react' -import { useDispatch } from 'react-redux' import { - CustomPlotsData, - PlotsComparisonData, PlotsData, PlotsDataKeys, PlotsSection, - SectionCollapsed, - TemplatePlotsData + SectionCollapsed } from 'dvc/src/plots/webview/contract' import { MessageToWebview } from 'dvc/src/webview/contract' +import React from 'react' import { Plots } from './Plots' +import { + setCollapsed as setComparisonTableCollapsed, + update as updateComparisonTable, + updateShouldShowTooManyPlotsMessage as updateShouldShowTooManyImagesMessage +} from './comparisonTable/comparisonTableSlice' 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, @@ -35,6 +31,7 @@ import { } from './webviewSlice' import { PlotsDispatch } from '../store' import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' +import { dispatchActions } from '../../shared/dispatchActions' const dispatchCollapsedSections = ( sections: SectionCollapsed, @@ -49,73 +46,45 @@ 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 type PlotsActions = typeof actionToDispatch + export const feedStore = ( data: MessageToWebview, dispatch: PlotsDispatch ) => { - if (data.data) { - dispatch(initialize()) - const keys = Object.keys(data.data) as PlotsDataKeys[] - for (const key of keys) { - switch (key) { - case PlotsDataKeys.CLI_ERROR: - dispatch(updateCliError(data.data[key])) - continue - case PlotsDataKeys.CUSTOM: - dispatch(updateCustomPlots(data.data[key] as CustomPlotsData)) - continue - case PlotsDataKeys.COMPARISON: - dispatch(updateComparisonTable(data.data[key] as PlotsComparisonData)) - continue - case PlotsDataKeys.TEMPLATE: - dispatch(updateTemplatePlots(data.data[key] as TemplatePlotsData)) - continue - case PlotsDataKeys.SECTION_COLLAPSED: - dispatchCollapsedSections( - data.data[key] as SectionCollapsed, - dispatch - ) - continue - case PlotsDataKeys.HAS_PLOTS: - dispatch(updateHasPlots(!!data.data[key])) - continue - case PlotsDataKeys.HAS_UNSELECTED_PLOTS: - dispatch(updateHasUnselectedPlots(!!data.data[key])) - continue - case PlotsDataKeys.PLOT_ERRORS: - dispatch(updatePlotErrors(data.data[key])) - continue - case PlotsDataKeys.SELECTED_REVISIONS: - dispatch(updateSelectedRevisions(data.data[key])) - continue - case PlotsDataKeys.SHOW_TOO_MANY_TEMPLATE_PLOTS: - dispatch( - updateShouldShowTooManyTemplatesMessage(data.data[key] as boolean) - ) - continue - case PlotsDataKeys.SHOW_TOO_MANY_COMPARISON_IMAGES: - dispatch( - updateShouldShowTooManyImagesMessage(data.data[key] as boolean) - ) - continue - default: - continue - } - } + const stateUpdate = data?.data + if (!stateUpdate) { + return + } + dispatch(initialize()) + + const keys = Object.keys(stateUpdate) as PlotsDataKeys[] + if (keys.includes(PlotsDataKeys.SECTION_COLLAPSED)) { + dispatchCollapsedSections( + stateUpdate[PlotsDataKeys.SECTION_COLLAPSED] as SectionCollapsed, + dispatch + ) } + dispatchActions(actionToDispatch, 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 6df301ecba..39368d84d6 100644 --- a/webview/src/setup/components/App.tsx +++ b/webview/src/setup/components/App.tsx @@ -1,19 +1,15 @@ -import { SetupSection, SetupData } from 'dvc/src/setup/webview/contract' +import { SetupData, SetupSection } 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 { SetupContainer } from './SetupContainer' import { Dvc } from './dvc/Dvc' import { Experiments } from './experiments/Experiments' -import { Studio } from './studio/Studio' -import { SetupContainer } from './SetupContainer' import { Remotes } from './remotes/Remotes' -import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' +import { Studio } from './studio/Studio' import { TooltipIconType } from '../../shared/components/sectionContainer/InfoTooltip' -import { SetupDispatch, SetupState } from '../store' -import { - updateSectionCollapsed, - updateHasData as updateWebviewHasData -} from '../state/webviewSlice' +import { dispatchActions } from '../../shared/dispatchActions' +import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' import { updateCanGitInitialize, updateCliCompatible, @@ -36,6 +32,8 @@ import { updateSelfHostedStudioUrl, updateShareLiveToStudio } from '../state/studioSlice' +import { initialize, updateSectionCollapsed } from '../state/webviewSlice' +import { SetupDispatch, SetupState } from '../store' import { setStudioShareExperimentsLive } from '../util/messages' const getDvcStatusIcon = ( @@ -59,78 +57,39 @@ 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 type SetupActions = typeof actionToDispatch + export const feedStore = ( data: MessageToWebview, dispatch: SetupDispatch ) => { - if (!data?.data) { + const stateUpdate = data?.data + if (!stateUpdate) { return } - dispatch(updateWebviewHasData()) - for (const key of Object.keys(data.data)) { - switch (key) { - case 'canGitInitialize': - dispatch(updateCanGitInitialize(data.data.canGitInitialize)) - continue - case 'cliCompatible': - dispatch(updateCliCompatible(data.data.cliCompatible)) - continue - case 'dvcCliDetails': - dispatch(updateDvcCliDetails(data.data.dvcCliDetails)) - continue - case 'hasData': - dispatch(updateExperimentsHasData(data.data.hasData)) - continue - case 'isPythonEnvironmentGlobal': - dispatch( - updateIsPythonEnvironmentGlobal(data.data.isPythonEnvironmentGlobal) - ) - continue - case 'isPythonExtensionInstalled': - dispatch( - updateIsPythonExtensionInstalled(data.data.isPythonExtensionInstalled) - ) - continue - case 'isPythonExtensionUsed': - dispatch(updateIsPythonExtensionUsed(data.data.isPythonExtensionUsed)) - continue - case 'isAboveLatestTestedVersion': - dispatch( - updateIsAboveLatestTestedVersion(data.data.isAboveLatestTestedVersion) - ) - continue - case 'isStudioConnected': - dispatch(updateIsStudioConnected(data.data.isStudioConnected)) - continue - case 'needsGitCommit': - dispatch(updateNeedsGitCommit(data.data.needsGitCommit)) - continue - case 'needsGitInitialized': - dispatch(updateNeedsGitInitialized(data.data.needsGitInitialized)) - continue - case 'projectInitialized': - dispatch(updateProjectInitialized(data.data.projectInitialized)) - continue - case 'pythonBinPath': - dispatch(updatePythonBinPath(data.data.pythonBinPath)) - continue - case 'sectionCollapsed': - dispatch(updateSectionCollapsed(data.data.sectionCollapsed)) - continue - case 'shareLiveToStudio': - dispatch(updateShareLiveToStudio(data.data.shareLiveToStudio)) - continue - case 'selfHostedStudioUrl': - dispatch(updateSelfHostedStudioUrl(data.data.selfHostedStudioUrl)) - continue - case 'remoteList': - dispatch(updateRemoteList(data.data.remoteList)) - continue + dispatch(initialize()) - default: - continue - } - } + dispatchActions(actionToDispatch, stateUpdate, dispatch) } export const App: React.FC = () => { @@ -147,14 +106,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/setup/state/webviewSlice.ts b/webview/src/setup/state/webviewSlice.ts index 4c875042f9..75e950ee6b 100644 --- a/webview/src/setup/state/webviewSlice.ts +++ b/webview/src/setup/state/webviewSlice.ts @@ -19,6 +19,9 @@ export const webviewSlice = createSlice({ initialState: webviewInitialState, name: 'webview', reducers: { + initialize: state => { + state.hasData = true + }, toggleSectionCollapsed: (state, action: PayloadAction) => { const section = action.payload state.sectionCollapsed = { @@ -26,9 +29,6 @@ export const webviewSlice = createSlice({ [section]: !state.sectionCollapsed[section] } }, - updateHasData: state => { - state.hasData = true - }, updateSectionCollapsed: ( state, action: PayloadAction @@ -40,7 +40,7 @@ export const webviewSlice = createSlice({ } }) -export const { updateHasData, updateSectionCollapsed, toggleSectionCollapsed } = +export const { initialize, updateSectionCollapsed, toggleSectionCollapsed } = webviewSlice.actions export default webviewSlice.reducer diff --git a/webview/src/shared/dispatchActions.ts b/webview/src/shared/dispatchActions.ts new file mode 100644 index 0000000000..ad951ae35c --- /dev/null +++ b/webview/src/shared/dispatchActions.ts @@ -0,0 +1,29 @@ +import { UnknownAction } from '@reduxjs/toolkit' +import { TableData } from 'dvc/src/experiments/webview/contract' +import { PlotsData } from 'dvc/src/plots/webview/contract' +import { SetupData } from 'dvc/src/setup/webview/contract' +import type { ExperimentsActions } from '../experiments/components/App' +import type { ExperimentsDispatch } from '../experiments/store' +import type { PlotsActions } from '../plots/components/App' +import type { PlotsDispatch } from '../plots/store' +import type { SetupActions } from '../setup/components/App' +import type { SetupDispatch } from '../setup/store' + +export const dispatchActions = ( + actionToDispatch: ExperimentsActions | PlotsActions | SetupActions, + stateUpdate: NonNullable, + dispatch: ExperimentsDispatch | PlotsDispatch | SetupDispatch +) => { + for (const key of Object.keys(stateUpdate)) { + const tKey = key as keyof typeof stateUpdate + const value = stateUpdate[tKey] + const action = actionToDispatch[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() }, [])