diff --git a/intuita-webview/src/main/App.css b/intuita-webview/src/main/App.css index d0d4dbcf..fdab19d9 100644 --- a/intuita-webview/src/main/App.css +++ b/intuita-webview/src/main/App.css @@ -60,3 +60,8 @@ flex-direction: column; align-items: center; } + +.clearing-progress-ring { + height: 4em; + width: 100%; +} diff --git a/intuita-webview/src/main/CodemodRuns.tsx b/intuita-webview/src/main/CodemodRuns.tsx index 79b527e2..e258b584 100644 --- a/intuita-webview/src/main/CodemodRuns.tsx +++ b/intuita-webview/src/main/CodemodRuns.tsx @@ -13,6 +13,8 @@ import { App as FileExplorer } from '../fileExplorer/App'; import { MainWebviewViewProps } from '../../../src/selectors/selectMainWebviewViewProps'; import { vscode } from '../shared/utilities/vscode'; import { useEffect, useMemo, useRef } from 'react'; +import { Command } from '../shared/types'; +import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'; export const CodemodRuns = ( props: MainWebviewViewProps & { @@ -50,6 +52,22 @@ export const CodemodRuns = ( [props.panelGroupSettings], ); + const commands: (Command & { icon: string })[] = []; + + if (props.clearingInProgress) { + commands.push({ + icon: 'circle-slash', + title: 'Stop clearing', + command: 'intuita.stopStateClearing', + }); + } else { + commands.push({ + icon: 'clear-all', + title: 'Clear all', + command: 'intuita.clearState', + }); + } + return (
{ event.preventDefault(); @@ -92,7 +104,11 @@ export const CodemodRuns = ( }); }} > - + {props.clearingInProgress ? ( + + ) : ( + + )} ; + +export const createClearStateCommand = + ({ fileService, store }: Dependencies) => + async () => { + const state = store.getState(); + + store.dispatch(actions.clearState()); + + try { + const uris: Uri[] = []; + + for (const job of Object.values(state.job.entities)) { + if ( + !job || + !doesJobAddNewFile(job.kind) || + job.newContentUri === null || + job.newContentUri.includes('.intuita/cases') + ) { + continue; + } + + uris.push(Uri.parse(job.newContentUri)); + } + + await fileService.deleteFiles({ uris }); + } catch (error) { + console.error(error); + } + + try { + const casesDirectoryUri = Uri.parse( + join(homedir(), '.intuita', 'cases'), + ); + + const files = await workspace.fs.readDirectory(casesDirectoryUri); + + const caseDirectoryUris = files + .filter(([, fileType]) => fileType === FileType.Directory) + .map(([name]) => Uri.joinPath(casesDirectoryUri, name)); + + await fileService.deleteDirectories({ uris: caseDirectoryUris }); + } catch (error) { + console.error(error); + } + + store.dispatch(actions.onStateCleared()); + }; diff --git a/src/components/webview/MainProvider.ts b/src/components/webview/MainProvider.ts index f7bdeb10..cec82ac4 100644 --- a/src/components/webview/MainProvider.ts +++ b/src/components/webview/MainProvider.ts @@ -218,7 +218,8 @@ export class MainViewProvider implements WebviewViewProvider { if ( prevProps.activeTabId !== nextProps.activeTabId && - nextProps.activeTabId === 'codemodRuns' + nextProps.activeTabId === 'codemodRuns' && + !nextProps.clearingInProgress ) { this.__messageBus.publish({ kind: MessageKind.loadHomeDirectoryData, diff --git a/src/data/slice.ts b/src/data/slice.ts index d090c4e6..68bb4f5a 100644 --- a/src/data/slice.ts +++ b/src/data/slice.ts @@ -45,6 +45,7 @@ export const jobAdapter = createEntityAdapter({ export const getInitialState = (): RootState => { return { + clearingInProgress: false, codemod: codemodAdapter.getInitialState(), privateCodemods: privateCodemodAdapter.getInitialState(), case: caseAdapter.getInitialState(), @@ -142,6 +143,8 @@ const rootSlice = createSlice({ jobAdapter.upsertMany(state.job, action.payload); }, clearState(state) { + state.clearingInProgress = true; + caseAdapter.removeAll(state.case); jobAdapter.removeAll(state.job); @@ -157,6 +160,9 @@ const rootSlice = createSlice({ state.reviewedExplorerNodes = {}; state.focusedExplorerNodes = {}; }, + onStateCleared(state) { + state.clearingInProgress = false; + }, setCodemods(state, action: PayloadAction>) { codemodAdapter.setAll(state.codemod, action.payload); }, diff --git a/src/extension.ts b/src/extension.ts index 37628aaf..7fd3e291 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -31,7 +31,6 @@ import { CodemodNodeHashDigest, selectCodemodArguments, } from './selectors/selectCodemodTree'; -import { doesJobAddNewFile } from './selectors/comparePersistedJobs'; import { buildHash, isNeitherNullNorUndefined } from './utilities'; import { mkdir, readFile, writeFile } from 'fs/promises'; import { homedir } from 'os'; @@ -47,6 +46,7 @@ import { parsePrivateCodemodsEnvelope } from './data/privateCodemodsEnvelopeSche import { GlobalStateTokenStorage, UserService } from './components/userService'; import { HomeDirectoryService } from './data/readHomeDirectoryCases'; import { isLeft } from 'fp-ts/lib/Either'; +import { createClearStateCommand } from './commands/clearStateCommand'; export const enum SEARCH_PARAMS_KEYS { ENGINE = 'engine', @@ -849,30 +849,15 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( - vscode.commands.registerCommand('intuita.clearState', () => { - const state = store.getState(); - - const uris: vscode.Uri[] = []; - - for (const job of Object.values(state.job.entities)) { - if ( - !job || - !doesJobAddNewFile(job.kind) || - job.newContentUri === null || - job.newContentUri.includes('.intuita/cases') - ) { - continue; - } - - uris.push(vscode.Uri.parse(job.newContentUri)); - } - - store.dispatch(actions.clearState()); + vscode.commands.registerCommand( + 'intuita.clearState', + createClearStateCommand({ fileService, store }), + ), + ); - messageBus.publish({ - kind: MessageKind.deleteDirectories, - uris: [vscode.Uri.parse(join(homedir(), '.intuita', 'cases'))], - }); + context.subscriptions.push( + vscode.commands.registerCommand('intuita.stopStateClearing', () => { + store.dispatch(actions.onStateCleared()); }), ); diff --git a/src/persistedState/codecs.ts b/src/persistedState/codecs.ts index 3ff807a5..cd808b6c 100644 --- a/src/persistedState/codecs.ts +++ b/src/persistedState/codecs.ts @@ -53,6 +53,7 @@ export const panelGroupSettingsCodec = t.record(t.string, t.array(t.number)); export type PanelGroupSettings = t.TypeOf; export const persistedStateCodecNew = buildTypeCodec({ + clearingInProgress: withFallback(t.boolean, false), case: buildCollectionCodec(caseCodec), codemod: buildCollectionCodec(codemodEntryCodec), privateCodemods: buildCollectionCodec(privateCodemodEntryCodec), diff --git a/src/selectors/selectMainWebviewViewProps.ts b/src/selectors/selectMainWebviewViewProps.ts index e48d55a9..75e0be52 100644 --- a/src/selectors/selectMainWebviewViewProps.ts +++ b/src/selectors/selectMainWebviewViewProps.ts @@ -39,6 +39,7 @@ export const selectMainWebviewViewProps = ( if (state.activeTabId === 'codemodRuns') { return { + clearingInProgress: state.clearingInProgress, activeTabId: state.activeTabId, toaster: state.toaster, applySelectedInProgress: state.applySelectedInProgress,