Feature request: Add delete confirmation dialog to file manager (#344)
* Feature request: Add delete confirmation dialog to file manager - Added confirmation dialog before deleting files/folders - Users must confirm deletion with a warning message - Works for both Delete key and right-click delete - Shows different messages for single file, folder, or multiple items - Includes permanent deletion warning - Follows existing design patterns using confirmWithToast * Adds confirmation for deletion of items including folders Updates the file deletion confirmation logic to distinguish between deleting multiple items with or without folders. Introduces a new translation string for a clearer user prompt when folders and their contents are included in the deletion. Improves clarity and reduces user error when performing bulk deletions. * feat: Add Chinese translations for delete confirmation messages
This commit was merged in pull request #344.
This commit is contained in:
@@ -844,8 +844,13 @@
|
|||||||
"selectServerToEdit": "Select a server from the sidebar to start editing files",
|
"selectServerToEdit": "Select a server from the sidebar to start editing files",
|
||||||
"fileOperations": "File Operations",
|
"fileOperations": "File Operations",
|
||||||
"confirmDeleteMessage": "Are you sure you want to delete <strong>{{name}}</strong>?",
|
"confirmDeleteMessage": "Are you sure you want to delete <strong>{{name}}</strong>?",
|
||||||
|
"confirmDeleteSingleItem": "Are you sure you want to permanently delete \"{{name}}\"?",
|
||||||
|
"confirmDeleteMultipleItems": "Are you sure you want to permanently delete {{count}} items?",
|
||||||
|
"confirmDeleteMultipleItemsWithFolders": "Are you sure you want to permanently delete {{count}} items? This includes folders and their contents.",
|
||||||
|
"confirmDeleteFolder": "Are you sure you want to permanently delete the folder \"{{name}}\" and all its contents?",
|
||||||
"deleteDirectoryWarning": "This will delete the folder and all its contents.",
|
"deleteDirectoryWarning": "This will delete the folder and all its contents.",
|
||||||
"actionCannotBeUndone": "This action cannot be undone.",
|
"actionCannotBeUndone": "This action cannot be undone.",
|
||||||
|
"permanentDeleteWarning": "This action cannot be undone. The item(s) will be permanently deleted from the server.",
|
||||||
"recent": "Recent",
|
"recent": "Recent",
|
||||||
"pinned": "Pinned",
|
"pinned": "Pinned",
|
||||||
"folderShortcuts": "Folder Shortcuts",
|
"folderShortcuts": "Folder Shortcuts",
|
||||||
|
|||||||
@@ -852,8 +852,13 @@
|
|||||||
"selectServerToEdit": "从侧边栏选择服务器以开始编辑文件",
|
"selectServerToEdit": "从侧边栏选择服务器以开始编辑文件",
|
||||||
"fileOperations": "文件操作",
|
"fileOperations": "文件操作",
|
||||||
"confirmDeleteMessage": "确定要删除 <strong>{{name}}</strong> 吗?",
|
"confirmDeleteMessage": "确定要删除 <strong>{{name}}</strong> 吗?",
|
||||||
|
"confirmDeleteSingleItem": "确定要永久删除 \"{{name}}\" 吗?",
|
||||||
|
"confirmDeleteMultipleItems": "确定要永久删除 {{count}} 个项目吗?",
|
||||||
|
"confirmDeleteMultipleItemsWithFolders": "确定要永久删除 {{count}} 个项目吗?这包括文件夹及其内容。",
|
||||||
|
"confirmDeleteFolder": "确定要永久删除文件夹 \"{{name}}\" 及其所有内容吗?",
|
||||||
"deleteDirectoryWarning": "这将删除文件夹及其所有内容。",
|
"deleteDirectoryWarning": "这将删除文件夹及其所有内容。",
|
||||||
"actionCannotBeUndone": "此操作无法撤销。",
|
"actionCannotBeUndone": "此操作无法撤销。",
|
||||||
|
"permanentDeleteWarning": "此操作无法撤销。项目将从服务器永久删除。",
|
||||||
"dragSystemFilesToUpload": "拖拽系统文件到此处上传",
|
"dragSystemFilesToUpload": "拖拽系统文件到此处上传",
|
||||||
"dragFilesToWindowToDownload": "拖拽文件到窗口外下载",
|
"dragFilesToWindowToDownload": "拖拽文件到窗口外下载",
|
||||||
"openTerminalHere": "在此处打开终端",
|
"openTerminalHere": "在此处打开终端",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { FileWindow } from "./components/FileWindow";
|
|||||||
import { DiffWindow } from "./components/DiffWindow";
|
import { DiffWindow } from "./components/DiffWindow";
|
||||||
import { useDragToDesktop } from "../../../hooks/useDragToDesktop";
|
import { useDragToDesktop } from "../../../hooks/useDragToDesktop";
|
||||||
import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop";
|
import { useDragToSystemDesktop } from "../../../hooks/useDragToSystemDesktop";
|
||||||
|
import { useConfirmation } from "@/hooks/use-confirmation.ts";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -82,6 +83,7 @@ function formatFileSize(bytes?: number): string {
|
|||||||
function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||||
const { openWindow } = useWindowManager();
|
const { openWindow } = useWindowManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { confirmWithToast } = useConfirmation();
|
||||||
|
|
||||||
const [currentHost, setCurrentHost] = useState<SSHHost | null>(
|
const [currentHost, setCurrentHost] = useState<SSHHost | null>(
|
||||||
initialHost || null,
|
initialHost || null,
|
||||||
@@ -587,54 +589,88 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
|||||||
async function handleDeleteFiles(files: FileItem[]) {
|
async function handleDeleteFiles(files: FileItem[]) {
|
||||||
if (!sshSessionId || files.length === 0) return;
|
if (!sshSessionId || files.length === 0) return;
|
||||||
|
|
||||||
try {
|
// Determine the confirmation message based on file count and type
|
||||||
await ensureSSHConnection();
|
let confirmMessage: string;
|
||||||
|
if (files.length === 1) {
|
||||||
for (const file of files) {
|
const file = files[0];
|
||||||
await deleteSSHItem(
|
if (file.type === "directory") {
|
||||||
sshSessionId,
|
confirmMessage = t("fileManager.confirmDeleteFolder", {
|
||||||
file.path,
|
name: file.name,
|
||||||
file.type === "directory",
|
});
|
||||||
currentHost?.id,
|
|
||||||
currentHost?.userId?.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedFiles = files.map((file) => ({
|
|
||||||
path: file.path,
|
|
||||||
name: file.name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const undoAction: UndoAction = {
|
|
||||||
type: "delete",
|
|
||||||
description: t("fileManager.deletedItems", { count: files.length }),
|
|
||||||
data: {
|
|
||||||
operation: "cut",
|
|
||||||
deletedFiles,
|
|
||||||
targetDirectory: currentPath,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
setUndoHistory((prev) => [...prev.slice(-9), undoAction]);
|
|
||||||
|
|
||||||
toast.success(
|
|
||||||
t("fileManager.itemsDeletedSuccessfully", { count: files.length }),
|
|
||||||
);
|
|
||||||
handleRefreshDirectory();
|
|
||||||
clearSelection();
|
|
||||||
} catch (error: any) {
|
|
||||||
if (
|
|
||||||
error.message?.includes("connection") ||
|
|
||||||
error.message?.includes("established")
|
|
||||||
) {
|
|
||||||
toast.error(
|
|
||||||
`SSH connection failed. Please check your connection to ${currentHost?.name} (${currentHost?.ip}:${currentHost?.port})`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
toast.error(t("fileManager.failedToDeleteItems"));
|
confirmMessage = t("fileManager.confirmDeleteSingleItem", {
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
console.error("Delete failed:", error);
|
} else {
|
||||||
|
const hasDirectory = files.some((file) => file.type === "directory");
|
||||||
|
const translationKey = hasDirectory
|
||||||
|
? "fileManager.confirmDeleteMultipleItemsWithFolders"
|
||||||
|
: "fileManager.confirmDeleteMultipleItems";
|
||||||
|
|
||||||
|
confirmMessage = t(translationKey, {
|
||||||
|
count: files.length,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add permanent deletion warning
|
||||||
|
const fullMessage = `${confirmMessage}\n\n${t("fileManager.permanentDeleteWarning")}`;
|
||||||
|
|
||||||
|
// Show confirmation dialog
|
||||||
|
confirmWithToast(
|
||||||
|
fullMessage,
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
await ensureSSHConnection();
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
await deleteSSHItem(
|
||||||
|
sshSessionId,
|
||||||
|
file.path,
|
||||||
|
file.type === "directory",
|
||||||
|
currentHost?.id,
|
||||||
|
currentHost?.userId?.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedFiles = files.map((file) => ({
|
||||||
|
path: file.path,
|
||||||
|
name: file.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const undoAction: UndoAction = {
|
||||||
|
type: "delete",
|
||||||
|
description: t("fileManager.deletedItems", { count: files.length }),
|
||||||
|
data: {
|
||||||
|
operation: "cut",
|
||||||
|
deletedFiles,
|
||||||
|
targetDirectory: currentPath,
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
setUndoHistory((prev) => [...prev.slice(-9), undoAction]);
|
||||||
|
|
||||||
|
toast.success(
|
||||||
|
t("fileManager.itemsDeletedSuccessfully", { count: files.length }),
|
||||||
|
);
|
||||||
|
handleRefreshDirectory();
|
||||||
|
clearSelection();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (
|
||||||
|
error.message?.includes("connection") ||
|
||||||
|
error.message?.includes("established")
|
||||||
|
) {
|
||||||
|
toast.error(
|
||||||
|
`SSH connection failed. Please check your connection to ${currentHost?.name} (${currentHost?.ip}:${currentHost?.port})`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.error(t("fileManager.failedToDeleteItems"));
|
||||||
|
}
|
||||||
|
console.error("Delete failed:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"destructive",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCreateNewFolder() {
|
function handleCreateNewFolder() {
|
||||||
|
|||||||
Reference in New Issue
Block a user