explorer: drop rename feature

It turns out that renaming files while they are open is technically
difficult problem. We will forgo this feature for now.
This commit is contained in:
David Lechner
2022-04-08 14:30:21 -05:00
parent d238e68deb
commit 30741b13ef
7 changed files with 4 additions and 208 deletions
-33
View File
@@ -13,7 +13,6 @@ import {
explorerDeleteFile,
explorerExportFile,
explorerImportFiles,
explorerRenameFile,
} from './actions';
import { ExplorerFileInfo } from './reducers';
@@ -102,38 +101,6 @@ describe('tree item', () => {
expect(dispatch).toHaveBeenCalledWith(explorerActivateFile('test.file'));
});
describe('rename', () => {
it('should dispatch action when button is clicked', async () => {
const [explorer, dispatch] = testRender(<Explorer />, {
explorer: { files: [testFile] },
});
// NB: this button is intentionally not accessible (by role) since
// there is a keyboard shortcut.
const button = explorer.getByTitle('Rename test.file');
userEvent.click(button);
expect(dispatch).toHaveBeenCalledWith(explorerRenameFile('test.file'));
// should not propagate to treeitem
expect(dispatch).toHaveBeenCalledTimes(1);
});
it('should dispatch action when key is pressed', async () => {
const [explorer, dispatch] = testRender(<Explorer />, {
explorer: { files: [testFile] },
});
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
userEvent.click(treeItem);
userEvent.keyboard('{f2}');
expect(dispatch).toHaveBeenCalledWith(explorerRenameFile('test.file'));
});
});
describe('export', () => {
it('should dispatch export action when button is clicked', async () => {
const [explorer, dispatch] = testRender(<Explorer />, {
-27
View File
@@ -35,12 +35,10 @@ import {
explorerDeleteFile,
explorerExportFile,
explorerImportFiles,
explorerRenameFile,
} from './actions';
import DeleteFileAlert from './deleteFileAlert/DeleteFileAlert';
import { I18nId } from './i18n';
import NewFileWizard from './newFileWizard/NewFileWizard';
import RenameFileDialog from './renameFileDialog/RenameFileDialog';
type ActionButtonProps = {
/** The icon to use for the button. */
@@ -109,12 +107,6 @@ const FileActionButtonGroup: React.VoidFunctionComponent<ActionButtonGroupProps>
className="pb-explorer-file-action-button-group"
minimal={true}
>
<ActionButton
icon="edit"
tooltip={i18n.translate(I18nId.TreeItemRenameTooltip, { fileName })}
focusable={false}
onClick={() => dispatch(explorerRenameFile(fileName))}
/>
<ActionButton
// NB: the "import" icon has an arrow pointing down, which is
// what we want here since import is analogous to download
@@ -189,10 +181,6 @@ function useLiveDescriptors(i18n: I18n): LiveDescriptors {
I18nId.TreeLiveDescriptorIntroKeybindingsPrimaryAction,
{ key: '{keybinding:primaryAction}' },
)}</li>
<li>${i18n.translate(
I18nId.TreeLiveDescriptorIntroKeybindingsRename,
{ key: 'f2' },
)}</li>
<li>${i18n.translate(
I18nId.TreeLiveDescriptorIntroKeybindingsExport,
{ key: `${isMacOS() ? 'cmd' : 'ctrl'}+e` },
@@ -228,13 +216,6 @@ const renderTreeContainer: typeof renderers.renderTreeContainer = (props) => {
const hotKeyActive =
isActiveTree; /* && !dnd.isProgrammaticallyDragging && !isRenaming */
const handleRenameKeyDown = useCallback(() => {
if (focusedItem !== undefined) {
const fileName = environment.getItemTitle(environment.items[focusedItem]);
dispatch(explorerRenameFile(fileName));
}
}, [environment]);
const handleDeleteKeyDown = useCallback(() => {
if (focusedItem !== undefined) {
const fileName = environment.getItemTitle(environment.items[focusedItem]);
@@ -251,13 +232,6 @@ const renderTreeContainer: typeof renderers.renderTreeContainer = (props) => {
const hotkeys = useMemo<readonly HotkeyConfig[]>(
() => [
{
combo: 'f2',
label: 'Rename',
disabled: !hotKeyActive,
preventDefault: true,
onKeyDown: handleRenameKeyDown,
},
{
combo: 'del',
label: 'Delete',
@@ -380,7 +354,6 @@ const Explorer: React.VFC = () => {
<Divider />
<FileTree i18n={i18n} />
<NewFileWizard />
<RenameFileDialog />
<DeleteFileAlert />
</div>
);
-23
View File
@@ -102,29 +102,6 @@ export const explorerDidFailToActivateFile = createAction(
}),
);
/**
* Action that requests to rename a file.
* @param fileName The file name.
*/
export const explorerRenameFile = createAction((fileName: string) => ({
type: 'explorer.action.renameFile',
fileName,
}));
/**
* Action that indicates that {@link explorerRenameFile} succeeded.
*/
export const explorerDidRenameFile = createAction(() => ({
type: 'explorer.action.didRenameFile',
}));
/**
* Action that indicates that {@link explorerRenameFile} failed.
*/
export const explorerDidFailToRenameFile = createAction(() => ({
type: 'explorer.action.didFailToRenameFile',
}));
/**
* Request to export (download) a file.
* @param fileName The file name.
-2
View File
@@ -11,11 +11,9 @@ export enum I18nId {
TreeLiveDescriptorIntroAccessibilityGuide = 'tree.liveDescriptor.intro.accessibilityGuide',
TreeLiveDescriptorIntroNavigation = 'tree.liveDescriptor.intro.navigation',
TreeLiveDescriptorIntroKeybindingsPrimaryAction = 'tree.liveDescriptor.intro.keybindings.primaryAction',
TreeLiveDescriptorIntroKeybindingsRename = 'tree.liveDescriptor.intro.keybindings.rename',
TreeLiveDescriptorIntroKeybindingsExport = 'tree.liveDescriptor.intro.keybindings.export',
TreeLiveDescriptorIntroKeybindingsDelete = 'tree.liveDescriptor.intro.keybindings.delete',
TreeLiveDescriptorSearching = 'tree.liveDescriptor.searching',
TreeItemDeleteTooltip = 'treeItem.deleteTooltip',
TreeItemExportTooltip = 'treeItem.exportTooltip',
TreeItemRenameTooltip = 'treeItem.renameTooltip',
}
-57
View File
@@ -19,14 +19,11 @@ import {
fileStorageDidFailToDeleteFile,
fileStorageDidFailToDumpAllFiles,
fileStorageDidFailToReadFile,
fileStorageDidFailToRenameFile,
fileStorageDidReadFile,
fileStorageDidRemoveItem,
fileStorageDidRenameFile,
fileStorageDidWriteFile,
fileStorageDumpAllFiles,
fileStorageReadFile,
fileStorageRenameFile,
fileStorageWriteFile,
} from '../fileStorage/actions';
import { pythonFileExtension } from '../pybricksMicropython/lib';
@@ -46,12 +43,9 @@ import {
explorerDidFailToDeleteFile,
explorerDidFailToExportFile,
explorerDidFailToImportFiles,
explorerDidFailToRenameFile,
explorerDidImportFiles,
explorerDidRenameFile,
explorerExportFile,
explorerImportFiles,
explorerRenameFile,
} from './actions';
import {
deleteFileAlertDidAccept,
@@ -64,11 +58,6 @@ import {
newFileWizardDidCancel,
newFileWizardShow,
} from './newFileWizard/actions';
import {
renameFileDialogDidAccept,
renameFileDialogDidCancel,
renameFileDialogShow,
} from './renameFileDialog/actions';
import explorer from './sagas';
jest.mock('browser-fs-access');
@@ -263,52 +252,6 @@ describe('handleExplorerActivateFile', () => {
});
});
describe('handleExplorerRenameFile', () => {
let saga: AsyncSaga;
beforeEach(async () => {
saga = new AsyncSaga(explorer);
saga.put(explorerRenameFile('old.file'));
await expect(saga.take()).resolves.toEqual(renameFileDialogShow('old.file'));
});
it('should dispatch action if canceled', async () => {
saga.put(renameFileDialogDidCancel());
await expect(saga.take()).resolves.toEqual(explorerDidFailToRenameFile());
});
describe('should dispatch fileStorageOpenFile action if accepted', () => {
beforeEach(async () => {
saga.put(renameFileDialogDidAccept('old.file', 'new.file'));
await expect(saga.take()).resolves.toEqual(
fileStorageRenameFile('old.file', 'new.file'),
);
});
it('should dispatch action on fileStorageRenameFile failure', async () => {
saga.put(
fileStorageDidFailToRenameFile('old.file', new Error('test error')),
);
await expect(saga.take()).resolves.toEqual(explorerDidFailToRenameFile());
});
it('should dispatch action on fileStorageRenameFile success', async () => {
saga.put(fileStorageDidRenameFile('old.file'));
await expect(saga.take()).resolves.toEqual(explorerDidRenameFile());
});
});
afterEach(async () => {
await saga.end();
});
});
describe('handleExplorerExportFile', () => {
let saga: AsyncSaga;
const testFile = 'test.file';
+3 -63
View File
@@ -3,15 +3,7 @@
import { fileOpen, fileSave } from 'browser-fs-access';
import JSZip from 'jszip';
import {
call,
put,
race,
select,
take,
takeEvery,
takeLatest,
} from 'typed-redux-saga/macro';
import { call, put, race, select, take, takeEvery } from 'typed-redux-saga/macro';
import {
editorActivateFile,
editorCloseFile,
@@ -27,15 +19,12 @@ import {
fileStorageDidFailToDeleteFile,
fileStorageDidFailToDumpAllFiles,
fileStorageDidFailToReadFile,
fileStorageDidFailToRenameFile,
fileStorageDidFailToWriteFile,
fileStorageDidReadFile,
fileStorageDidRemoveItem,
fileStorageDidRenameFile,
fileStorageDidWriteFile,
fileStorageDumpAllFiles,
fileStorageReadFile,
fileStorageRenameFile,
fileStorageWriteFile,
} from '../fileStorage/actions';
import {
@@ -63,12 +52,9 @@ import {
explorerDidFailToDeleteFile,
explorerDidFailToExportFile,
explorerDidFailToImportFiles,
explorerDidFailToRenameFile,
explorerDidImportFiles,
explorerDidRenameFile,
explorerExportFile,
explorerImportFiles,
explorerRenameFile,
} from './actions';
import {
deleteFileAlertDidAccept,
@@ -80,11 +66,6 @@ import {
newFileWizardDidCancel,
newFileWizardShow,
} from './newFileWizard/actions';
import {
renameFileDialogDidAccept,
renameFileDialogDidCancel,
renameFileDialogShow,
} from './renameFileDialog/actions';
function* handleExplorerArchiveAllFiles(): Generator {
try {
@@ -265,43 +246,6 @@ function* handleExplorerActivateFile(
yield* put(explorerDidActivateFile(didActivate.fileName));
}
/** Connects user initiate rename file actions to the rename file dialog. */
function* handleExplorerRenameFile(
action: ReturnType<typeof explorerRenameFile>,
): Generator {
yield* put(renameFileDialogShow(action.fileName));
const { accepted, canceled } = yield* race({
accepted: take(renameFileDialogDidAccept),
canceled: take(renameFileDialogDidCancel),
});
if (canceled) {
yield* put(explorerDidFailToRenameFile());
return;
}
defined(accepted);
yield* put(fileStorageRenameFile(action.fileName, accepted.newName));
const didRename = yield* race({
succeeded: take(
fileStorageDidRenameFile.when((a) => a.fileName === action.fileName),
),
failed: take(
fileStorageDidFailToRenameFile.when((a) => a.fileName === action.fileName),
),
});
if (didRename.failed) {
yield* put(explorerDidFailToRenameFile());
return;
}
yield* put(explorerDidRenameFile());
}
function* handleExplorerExportFile(
action: ReturnType<typeof explorerExportFile>,
): Generator {
@@ -397,10 +341,6 @@ export default function* (): Generator {
yield* takeEvery(explorerImportFiles, handleExplorerImportFiles);
yield* takeEvery(explorerCreateNewFile, handleExplorerCreateNewFile);
yield* takeEvery(explorerActivateFile, handleExplorerActivateFile);
// takeLatest should ensure that if we trigger a new rename before the
// previous one is finished, the old one will be canceled. We don't expect
// this to happen in practice though.
yield* takeLatest(explorerRenameFile, handleExplorerRenameFile);
yield* takeLatest(explorerExportFile, handleExplorerExportFile);
yield* takeLatest(explorerDeleteFile, handleExplorerDeleteFile);
yield* takeEvery(explorerExportFile, handleExplorerExportFile);
yield* takeEvery(explorerDeleteFile, handleExplorerDeleteFile);
}
+1 -3
View File
@@ -12,7 +12,6 @@
"navigation": "Navigate the tree with the arrow keys. Start typing the name of a file to search for a file. Additional keybindings are available:",
"keybindings": {
"primaryAction": "{key} to open the file in the code editor",
"rename": "{key} to start renaming the focused file",
"export": "{key} to export the focused file",
"delete": "{key} to delete the focused file"
}
@@ -22,7 +21,6 @@
},
"treeItem": {
"deleteTooltip": "Delete {fileName}",
"exportTooltip": "Export {fileName}",
"renameTooltip": "Rename {fileName}"
"exportTooltip": "Export {fileName}"
}
}