Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow selecting multiple files from left menu for deleting #426

Merged
merged 11 commits into from
Jul 1, 2024
3 changes: 2 additions & 1 deletion client/components/Application/Browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function DefaultBrowser() {
const path = store.useStore((state) => state.path)
const files = store.useStore((state) => state.files)
const fileEvent = store.useStore((state) => state.fileEvent)

const selectedMultiplePaths = store.useStore((state) => state.selectedMultiplePaths)
return (
<ErrorBoundary
fallback={
Expand All @@ -38,6 +38,7 @@ function DefaultBrowser() {
<FileTree
files={files}
event={fileEvent}
selectedMultiple={selectedMultiplePaths}
selected={path}
onSelect={store.selectFile}
/>
Expand Down
6 changes: 2 additions & 4 deletions client/components/Application/Buttons/Delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { useKeyPress } from 'ahooks'

export default function DeleteButton() {
const path = store.useStore((state) => state.path)
const isFolder = store.useStore(store.getIsFolder)

const type = isFolder ? 'Folder' : 'File'
useKeyPress(['ctrl.i'], (event) => {
event.preventDefault()
if (path) store.openDialog(`delete${type}`)
if (path) store.openDialog('deleteFilesFolders')
})

return (
Expand All @@ -24,7 +22,7 @@ export default function DeleteButton() {
disabled={!path}
variant="text"
color="warning"
onClick={() => store.openDialog(`delete${type}`)}
onClick={() => store.openDialog('deleteFilesFolders')}
/>
</Box>
</LightTooltip>
Expand Down
6 changes: 2 additions & 4 deletions client/components/Application/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import ConfigDialog from './Dialogs/Config'
import CopyFileDialog from './Dialogs/CopyFile'
import CopyFolderDialog from './Dialogs/CopyFolder'
import CreateDialog from './Dialogs/Create'
import DeleteFileDialog from './Dialogs/DeleteFile'
import DeleteFolderDialog from './Dialogs/DeleteFolder'
import DeleteFilesFoldersDialog from './Dialogs/DeleteFilesFolders'
import IndexFilesDialog from './Dialogs/IndexFiles'
import MoveFileDialog from './Dialogs/MoveFile'
import MoveFolderDialog from './Dialogs/MoveFolder'
Expand All @@ -29,8 +28,7 @@ const DIALOGS = {
copyFile: CopyFileDialog,
copyFolder: CopyFolderDialog,
create: CreateDialog,
deleteFile: DeleteFileDialog,
deleteFolder: DeleteFolderDialog,
deleteFilesFolders: DeleteFilesFoldersDialog,
indexFiles: IndexFilesDialog,
moveFile: MoveFileDialog,
moveFolder: MoveFolderDialog,
Expand Down
22 changes: 0 additions & 22 deletions client/components/Application/Dialogs/DeleteFile.tsx

This file was deleted.

53 changes: 53 additions & 0 deletions client/components/Application/Dialogs/DeleteFilesFolders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ConfirmDialog from '../../Parts/Dialogs/Confirm'
import * as store from '@client/store'

export default function DeleteFilesFoldersDialog() {
const path = store.useStore((state) => state.path)
const files = store.useStore((state) => state.files)
const selectedMultiplePaths = store.useStore((state) => state.selectedMultiplePaths)
const isFolder = store.useStore(store.getIsFolder)

// const selectedElements = files.filter((file) => {
// if( selectedMultiplePaths?.includes(file.path) && file.type === 'folder' ) return file
// else return
// })

const selectedFolders = files
.filter((file) => {
return selectedMultiplePaths?.includes(file.path) && file.type === 'folder'
})
.map((file) => file.path)

const selectedFiles = files
.filter((file) => {
return selectedMultiplePaths?.includes(file.path) && file.type !== 'folder'
})
.map((file) => file.path)

if (!path) return null

return (
<ConfirmDialog
open={true}
title="Delete File"
description={
selectedMultiplePaths
? 'Are you sure you want to delete these elements?'
: `Are you sure you want to delete this ${isFolder ? 'folder' : 'file'}?`
}
label="Yes"
cancelLabel="No"
onCancel={store.closeDialog}
onConfirm={async () => {
if (selectedMultiplePaths) {
if (selectedFolders.length > 0) await store.deleteFolders(selectedFolders)
if (selectedFiles.length > 0) await store.deleteFiles(selectedFiles)
}
// is only one selected file
else
guergana marked this conversation as resolved.
Show resolved Hide resolved
isFolder ? await store.deleteFolders([path]) : await store.deleteFiles([path])
store.closeDialog()
}}
/>
)
}
22 changes: 0 additions & 22 deletions client/components/Application/Dialogs/DeleteFolder.tsx

This file was deleted.

13 changes: 8 additions & 5 deletions client/components/Parts/Trees/File.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export interface FileTreeProps {
files: types.IFile[]
event?: types.IFileEvent
selected?: string
onSelect: (path?: string) => void
selectedMultiple?: string[]
onSelect: (path?: string[]) => void
defaultExpanded?: string[]
}

Expand All @@ -31,21 +32,23 @@ export default function FileTree(props: FileTreeProps) {
: props.defaultExpanded || []
setExpanded([...new Set([...expanded, ...defaultExpanded])])
}, [props.event, props.defaultExpanded])
const selectedMultiple = props.selectedMultiple || []
const selected = props.selected || ''
return (
<Context.Provider value={{ event: props.event }}>
<ScrollBox sx={{ padding: 2 }} height="100%">
<Stack alignItems="stretch" height="100%">
<TreeView
selected={selected}
multiSelect
selected={selectedMultiple.length > 0 ? selectedMultiple : [selected] }
expanded={expanded}
onNodeSelect={(_event, nodeId) => {
props.onSelect(nodeId as string)
onNodeSelect={(_event, nodeIds) => {
props.onSelect(nodeIds as string[])
}}
onNodeToggle={(_event: React.SyntheticEvent, nodeIds: string[]) => {
// On collapsing we don't collapse a folder if it's not yet selected
const isCollapsing = nodeIds.length < expanded.length
if (isCollapsing && !expanded.includes(props.selected || '')) return
if (isCollapsing && !expanded.every(value => selected.includes(value))) return
setExpanded(nodeIds)
}}
defaultCollapseIcon={<MinusSquare />}
Expand Down
48 changes: 31 additions & 17 deletions client/store/actions/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,18 @@ export async function copyFile(path: string, toPath: string) {
onFileCreate([result.path])
}

export async function deleteFile(path: string) {
const result = await client.fileDelete({ path })
export async function deleteFiles(paths: string[]) {
for (const path of paths) {
const result = await client.fileDelete({ path })

if (result instanceof client.Error) {
return store.setState('delete-file-error', (state) => {
state.error = result
})
}
if (result instanceof client.Error) {
return store.setState('delete-files-error', (state) => {
state.error = result
})
}

onFileDelete(path)
onFileDelete(path)
}
}

export async function moveFile(path: string, toPath: string) {
Expand Down Expand Up @@ -163,19 +165,31 @@ export async function locateFile(path: string) {
})
}

export async function selectFile(newPath?: string) {
export async function selectFile(newPath?: string[] | string) {
const { path, record } = store.getState()
if (path === newPath) return
if (newPath) {
if (!Array.isArray(newPath)) newPath = [newPath]
if (newPath.length === 1) {
store.setState('select-file-reset', (state) => {
state.selectedMultiplePaths = undefined
})

store.setState('select-file', (state) => {
state.path = newPath
})
if (path === newPath[0]) return
store.setState('select-file-start', (state) => {
state.path = newPath?.[0]
})

if (!newPath) return
if (record?.path === newPath) return
if (getIsFolder(store.getState())) return
if (!newPath) return
if (record?.path === newPath[0]) return
if (getIsFolder(store.getState())) return

await openFile(newPath)
await openFile(newPath[0])
} else {
store.setState('select-file-end', (state) => {
state.selectedMultiplePaths = newPath as string[]
guergana marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
}

export async function openFile(path: string) {
Expand Down
18 changes: 10 additions & 8 deletions client/store/actions/folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ export async function createFolder(path: string) {
onFileCreate([result.path])
}

export async function deleteFolder(path: string) {
const result = await client.folderDelete({ path })
export async function deleteFolders(paths: string[]) {
for (const path of paths) {
const result = await client.folderDelete({ path })

if (result instanceof client.Error) {
return store.setState('delete-folder-error', (state) => {
state.error = result
})
}
if (result instanceof client.Error) {
return store.setState('delete-folder-error', (state) => {
state.error = result
})
}

onFileDelete(path)
onFileDelete(path)
}
}

export async function moveFolder(path: string, toPath: string) {
Expand Down
4 changes: 2 additions & 2 deletions client/store/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type IState = {
measure?: types.IMeasure
files: types.IFile[]
fileEvent?: types.IFileEvent
selectedMultiplePaths?: string[]
error?: ClientError
dialog?: IDialog
loading?: boolean
Expand All @@ -27,8 +28,7 @@ export type IDialog =
| 'copyFile'
| 'copyFolder'
| 'create'
| 'deleteFile'
| 'deleteFolder'
| 'deleteFilesFolders'
| 'indexFiles'
| 'moveFile'
| 'moveFolder'
Expand Down
Loading