From c7c08c9637f05933b77ecc1bc7f05c7b552b1810 Mon Sep 17 00:00:00 2001 From: FTCHD <144691102+FTCHD@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:07:30 +0300 Subject: [PATCH] rename and delete in `Commander`, #18 --- src/components/navigator/FileList.tsx | 31 ++++++++- src/components/navigator/FilePanel.tsx | 8 ++- src/pages/Commander.tsx | 93 +++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/src/components/navigator/FileList.tsx b/src/components/navigator/FileList.tsx index 677e858..590308c 100644 --- a/src/components/navigator/FileList.tsx +++ b/src/components/navigator/FileList.tsx @@ -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 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({ )} + {onRename && ( + + )} + {onDelete && ( + + )} ) : (
diff --git a/src/components/navigator/FilePanel.tsx b/src/components/navigator/FilePanel.tsx index 5effc96..06941a8 100644 --- a/src/components/navigator/FilePanel.tsx +++ b/src/components/navigator/FilePanel.tsx @@ -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<
Name
@@ -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 diff --git a/src/pages/Commander.tsx b/src/pages/Commander.tsx index 719baf1..dd48c7a 100644 --- a/src/pages/Commander.tsx +++ b/src/pages/Commander.tsx @@ -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('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} />