rename and delete in Commander, #18

This commit is contained in:
FTCHD
2026-02-12 18:07:30 +03:00
parent 05926c405d
commit c7c08c9637
3 changed files with 128 additions and 4 deletions
+29 -2
View File
@@ -1,6 +1,6 @@
import { Button, Checkbox, Listbox, ListboxItem, cn } from '@heroui/react'
import { platform } from '@tauri-apps/plugin-os'
import { DownloadIcon, EyeIcon, StarIcon } from 'lucide-react'
import { DownloadIcon, EyeIcon, PencilIcon, StarIcon, Trash2Icon } from 'lucide-react'
import { useCallback } from 'react'
import { formatBytes } from '../../../lib/format.ts'
import FileIcon from './FileIcon'
@@ -24,6 +24,8 @@ export default function FileList({
favoritedKeys,
onToggleFavorite,
onDownload,
onRename,
onDelete,
listHeight,
}: {
items: (VirtualizedEntry | PaddingItem)[]
@@ -42,6 +44,8 @@ export default function FileList({
favoritedKeys?: Record<string, boolean>
onToggleFavorite?: (entry: Entry, isFavorited: boolean) => void
onDownload?: (entry: Entry) => void
onRename?: (entry: Entry) => void
onDelete?: (entry: Entry) => void
listHeight: number
}) {
const showCheckbox = selectionMode === 'checkbox' || selectionMode === 'both'
@@ -158,7 +162,7 @@ export default function FileList({
}
const gridCols = showPreviewColumn
? 'grid-cols-[2.5rem_1fr_6rem_9rem_5rem]'
? 'grid-cols-[2.5rem_1fr_6rem_9rem_9rem]'
: 'grid-cols-[2.5rem_1fr_6rem_9rem_2.5rem]'
return (
@@ -294,6 +298,29 @@ export default function FileList({
<DownloadIcon className="size-5" />
</Button>
)}
{onRename && (
<Button
isIconOnly={true}
size="sm"
variant="light"
className="transition-opacity duration-300 opacity-0 group-hover:opacity-100"
onPress={() => onRename(entry)}
>
<PencilIcon className="size-4" />
</Button>
)}
{onDelete && (
<Button
isIconOnly={true}
size="sm"
variant="light"
color="danger"
className="transition-opacity duration-300 opacity-0 group-hover:opacity-100"
onPress={() => onDelete(entry)}
>
<Trash2Icon className="size-4" />
</Button>
)}
</div>
) : (
<div className="flex items-center justify-center w-10 shrink-0">
+7 -1
View File
@@ -36,6 +36,8 @@ const FilePanel = forwardRef<
showPreviewColumn?: boolean
onPreviewRequest?: (item: Entry) => void
onDownload?: (item: Entry) => void
onRename?: (item: Entry) => void
onDelete?: (item: Entry) => void
contextMenuItems?: ContextMenuItem[]
allowedKeys?: AllowedKey[]
renderToolbar?: (buttons: ToolbarButtons) => React.ReactNode[][]
@@ -57,6 +59,8 @@ const FilePanel = forwardRef<
showPreviewColumn = true,
onPreviewRequest,
onDownload,
onRename,
onDelete,
contextMenuItems,
allowedKeys = ['REMOTES', 'LOCAL_FS'],
renderToolbar,
@@ -301,7 +305,7 @@ const FilePanel = forwardRef<
<div className="relative flex flex-col w-full h-full overflow-hidden">
<div
className={`sticky top-0 z-10 grid ${showPreviewColumn ? 'grid-cols-[2.5rem_1fr_6rem_9rem_5rem]' : 'grid-cols-[2.5rem_1fr_6rem_9rem_2.5rem]'} items-center py-2 bg-default-100`}
className={`sticky top-0 z-10 grid ${showPreviewColumn ? 'grid-cols-[2.5rem_1fr_6rem_9rem_9rem]' : 'grid-cols-[2.5rem_1fr_6rem_9rem_2.5rem]'} items-center py-2 bg-default-100`}
>
<div />
<div className="pl-2 font-semibold text-small">Name</div>
@@ -323,6 +327,8 @@ const FilePanel = forwardRef<
showPreviewColumn={showPreviewColumn}
onPreviewClick={handlePreviewClick}
onDownload={onDownload}
onRename={onRename}
onDelete={onDelete}
draggable={selectionMode === 'drag' || selectionMode === 'both'}
onDragStart={handleDragStartInternal}
// handled at the Browser level
+92 -1
View File
@@ -17,7 +17,8 @@ import {
Tooltip,
} from '@heroui/react'
import { useMutation, useQuery } from '@tanstack/react-query'
import { message, save } from '@tauri-apps/plugin-dialog'
import { invoke } from '@tauri-apps/api/core'
import { ask, message, save } from '@tauri-apps/plugin-dialog'
import { platform } from '@tauri-apps/plugin-os'
import { AnimatePresence, motion } from 'framer-motion'
import {
@@ -148,6 +149,92 @@ export default function Browser() {
setContextMenu(null)
}, [])
const handleDelete = useCallback(
async (entry: Entry) => {
const confirmed = await ask(
`Are you sure you want to delete "${entry.name}"?`,
{ title: 'Confirm Delete', kind: 'warning' }
)
if (!confirmed) return
try {
const source = entry.fullPath + (entry.isDir ? '/' : '')
const info = getFsInfo(source)
const endpoint = entry.isDir ? '/operations/purge' : '/operations/deletefile'
await rclone(endpoint as any, {
params: {
query: {
fs: info.root,
remote: info.filePath,
},
},
})
leftPanelRef.current?.refresh()
rightPanelRef.current?.refresh()
} catch (error) {
await message(error instanceof Error ? error.message : 'Delete failed', {
title: 'Error',
kind: 'error',
})
}
},
[]
)
const handleRename = useCallback(
async (entry: Entry) => {
const newName = await invoke<string | null>('prompt', {
title: 'Rename',
message: `Enter a new name for "${entry.name}"`,
default: entry.name,
sensitive: false,
})
if (!newName || newName === entry.name) return
try {
const info = getFsInfo(entry.fullPath)
const parentDir = info.filePath.includes('/')
? info.filePath.slice(0, info.filePath.lastIndexOf('/') + 1)
: ''
const dstRemote = `${parentDir}${newName}`
if (entry.isDir) {
await rclone('/sync/move' as any, {
params: {
query: {
srcFs: `${info.root}${info.filePath}/`,
dstFs: `${info.root}${dstRemote}/`,
deleteEmptySrcDirs: true,
},
},
})
} else {
await rclone('/operations/movefile' as any, {
params: {
query: {
srcFs: info.root,
srcRemote: info.filePath,
dstFs: info.root,
dstRemote,
},
},
})
}
leftPanelRef.current?.refresh()
rightPanelRef.current?.refresh()
} catch (error) {
await message(error instanceof Error ? error.message : 'Rename failed', {
title: 'Error',
kind: 'error',
})
}
},
[]
)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'r' && (e.metaKey || e.ctrlKey)) {
@@ -183,6 +270,8 @@ export default function Browser() {
showPreviewColumn={true}
onDrop={(items, dest) => handleDrop(items, dest, 'left')}
onDownload={handleDownload}
onRename={handleRename}
onDelete={handleDelete}
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
isActive={true}
/>
@@ -201,6 +290,8 @@ export default function Browser() {
showPreviewColumn={true}
onDrop={(items, dest) => handleDrop(items, dest, 'right')}
onDownload={handleDownload}
onRename={handleRename}
onDelete={handleDelete}
allowedKeys={['REMOTES', 'LOCAL_FS', 'FAVORITES']}
isActive={true}
/>