feat: replace browser confirm with gui modal for delete
All checks were successful
Automated Container Build / build-and-push (push) Successful in 40s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 40s
This commit is contained in:
parent
ee36e16dae
commit
ee441dfefd
1 changed files with 58 additions and 10 deletions
|
|
@ -244,6 +244,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
const [draggedFile, setDraggedFile] = useState<string | null>(null);
|
||||
const [uploads, setUploads] = useState<UploadProgress[]>([]);
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'single' | 'multiple'; file?: FileItem; count?: number } | null>(null);
|
||||
|
||||
function showToast(message: string, type: 'success' | 'error' | 'info' = 'success') {
|
||||
const id = Date.now() + Math.random();
|
||||
|
|
@ -384,7 +385,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
e.preventDefault();
|
||||
navigateUp();
|
||||
} else if (e.key === 'Delete') {
|
||||
deleteSelected();
|
||||
requestDeleteSelected();
|
||||
} else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
selectAll();
|
||||
|
|
@ -613,18 +614,29 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
setSelectedFiles(new Set(files.map(f => f.path)));
|
||||
}
|
||||
|
||||
async function deleteSelected() {
|
||||
function requestDeleteSelected() {
|
||||
if (selectedFiles.size === 0) return;
|
||||
if (!confirm(`Move ${selectedFiles.size} items to trash?`)) return;
|
||||
setDeleteConfirm({ type: 'multiple', count: selectedFiles.size });
|
||||
}
|
||||
|
||||
async function executeDeleteSelected() {
|
||||
const count = selectedFiles.size;
|
||||
for (const path of selectedFiles) {
|
||||
await api.deleteFile(path);
|
||||
}
|
||||
setSelectedFiles(new Set());
|
||||
loadFiles(undefined, true);
|
||||
setDeleteConfirm(null);
|
||||
showToast(`Deleted ${count} item${count > 1 ? 's' : ''}`);
|
||||
}
|
||||
|
||||
async function executeDeleteSingle(file: FileItem) {
|
||||
await api.deleteFile(file.path);
|
||||
loadFiles(undefined, true);
|
||||
setDeleteConfirm(null);
|
||||
showToast(`Deleted ${file.name}`);
|
||||
}
|
||||
|
||||
async function handlePin(file: FileItem) {
|
||||
await api.togglePin(file.path);
|
||||
loadFiles(undefined, true);
|
||||
|
|
@ -1578,7 +1590,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
<button
|
||||
className="p-2 hover:bg-red-500/10 rounded-lg transition-colors flex items-center gap-2 text-sm text-red-500"
|
||||
title="Delete Selected"
|
||||
onClick={() => deleteSelected()}
|
||||
onClick={() => requestDeleteSelected()}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
|
|
@ -1693,12 +1705,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
label="Move to Trash"
|
||||
danger
|
||||
onClick={() => {
|
||||
if (confirm(`Move ${contextMenu.file.name} to trash?`)) {
|
||||
api.deleteFile(contextMenu.file.path).then(() => {
|
||||
loadFiles(undefined, true);
|
||||
showToast(`Deleted ${contextMenu.file.name}`);
|
||||
});
|
||||
}
|
||||
setDeleteConfirm({ type: 'single', file: contextMenu.file });
|
||||
setContextMenu(null);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1857,6 +1864,47 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{deleteConfirm && (
|
||||
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
className="w-full max-w-md overflow-hidden rounded-2xl shadow-xl"
|
||||
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)' }}
|
||||
>
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold mb-2" style={{ color: 'var(--color-text-primary)' }}>Move to Trash?</h2>
|
||||
<p className="mb-6" style={{ color: 'var(--color-text-secondary)' }}>
|
||||
{deleteConfirm.type === 'multiple'
|
||||
? `Are you sure you want to move ${deleteConfirm.count} items to the trash?`
|
||||
: `Are you sure you want to move ${deleteConfirm.file?.name} to the trash?`}
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setDeleteConfirm(null)}
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-80"
|
||||
style={{ backgroundColor: 'var(--color-bg-tertiary)', color: 'var(--color-text-primary)' }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (deleteConfirm.type === 'multiple') executeDeleteSelected();
|
||||
else if (deleteConfirm.file) executeDeleteSingle(deleteConfirm.file);
|
||||
}}
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-90 bg-red-500 text-white"
|
||||
>
|
||||
Move to Trash
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{showTrashModal && (
|
||||
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
|
|
|
|||
Reference in a new issue