import React from "react"; import { Loader2, Trash2 } from "lucide-react"; import { Button } from "../ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../ui/dialog"; import { Input } from "../ui/input"; import { Label } from "../ui/label"; import { getFileName, getParentPath } from "../../application/state/sftp/utils"; import { SftpHostPicker } from "./SftpHostPicker"; import type { Host } from "../../types"; interface SftpPaneDialogsProps { t: (key: string, params?: Record) => string; hostLabel?: string; currentPath?: string; // New folder showNewFolderDialog: boolean; setShowNewFolderDialog: (open: boolean) => void; newFolderName: string; setNewFolderName: (value: string) => void; handleCreateFolder: () => void; isCreating: boolean; // New file showNewFileDialog: boolean; setShowNewFileDialog: (open: boolean) => void; newFileName: string; setNewFileName: (value: string) => void; fileNameError: string | null; setFileNameError: (value: string | null) => void; handleCreateFile: () => void; isCreatingFile: boolean; // Overwrite confirm showOverwriteConfirm: boolean; setShowOverwriteConfirm: (open: boolean) => void; overwriteTarget: string | null; handleOverwriteConfirm: () => void; // Rename showRenameDialog: boolean; setShowRenameDialog: (open: boolean) => void; renameName: string; setRenameName: (value: string) => void; handleRename: () => void; isRenaming: boolean; // Delete showDeleteConfirm: boolean; setShowDeleteConfirm: (open: boolean) => void; deleteTargets: string[]; handleDelete: () => void; isDeleting: boolean; // Host picker (connected view) showHostPicker: boolean; setShowHostPicker: (open: boolean) => void; hosts: Host[]; connectedHosts?: import("../../domain/sftpConnectedHosts").SftpConnectedHostEntry[]; side: "left" | "right"; hostSearch: string; setHostSearch: (value: string) => void; onConnect: ( host: Host | "local", options?: { sourceSessionId?: string }, ) => void; onDisconnect: () => Promise; } const HostHint: React.FC<{ label?: string }> = ({ label }) => label ? (
{label}
) : null; export const SftpPaneDialogs: React.FC = ({ t, hostLabel, currentPath, showNewFolderDialog, setShowNewFolderDialog, newFolderName, setNewFolderName, handleCreateFolder, isCreating, showNewFileDialog, setShowNewFileDialog, newFileName, setNewFileName, fileNameError, setFileNameError, handleCreateFile, isCreatingFile, showOverwriteConfirm, setShowOverwriteConfirm, overwriteTarget, handleOverwriteConfirm, showRenameDialog, setShowRenameDialog, renameName, setRenameName, handleRename, isRenaming, showDeleteConfirm, setShowDeleteConfirm, deleteTargets, handleDelete, isDeleting, showHostPicker, setShowHostPicker, hosts, connectedHosts = [], side, hostSearch, setHostSearch, onConnect, onDisconnect, }) => { // Focus the confirm button when a confirmation dialog opens so Enter confirms it. // These dialogs are opened from a context menu, whose focus-return can otherwise // leave focus outside the dialog, making Enter do nothing. const deleteConfirmButtonRef = React.useRef(null); const overwriteConfirmButtonRef = React.useRef(null); const isSingleDeleteTarget = deleteTargets.length === 1; const deletePath = (() => { if (isSingleDeleteTarget) { return deleteTargets[0]; } const uniquePaths = Array.from(new Set(deleteTargets.map((target) => getParentPath(target)).filter(Boolean))); if (uniquePaths.length === 1) return uniquePaths[0]; if (uniquePaths.length > 1) return "Multiple locations"; return currentPath; })(); const showDeleteList = deleteTargets.length > 1; const deleteListItems = (() => { if (!showDeleteList) return []; const uniquePaths = Array.from(new Set(deleteTargets.map((target) => getParentPath(target)).filter(Boolean))); if (uniquePaths.length === 1) { return deleteTargets.map((target) => getFileName(target) || target); } return deleteTargets; })(); return ( <> {/* Dialogs */} {t("sftp.newFolder")}
setNewFolderName(e.target.value)} placeholder={t("sftp.folderName.placeholder")} onKeyDown={(e) => e.key === "Enter" && handleCreateFolder()} autoFocus />
{ setShowNewFileDialog(open); if (!open) { setFileNameError(null); } }}> {t("sftp.newFile")}
{ setNewFileName(e.target.value); setFileNameError(null); }} placeholder={t("sftp.fileName.placeholder")} onKeyDown={(e) => e.key === "Enter" && handleCreateFile()} autoFocus /> {fileNameError && (
{fileNameError}
)}
{/* Overwrite Confirmation Dialog */} { e.preventDefault(); overwriteConfirmButtonRef.current?.focus(); }} > {t("sftp.overwrite.title")} {t("sftp.overwrite.desc", { name: overwriteTarget || "" })} {t("sftp.rename.title")}
setRenameName(e.target.value)} placeholder={t("sftp.rename.placeholder")} onKeyDown={(e) => e.key === "Enter" && handleRename()} autoFocus />
{ e.preventDefault(); deleteConfirmButtonRef.current?.focus(); }} > {t("sftp.deleteConfirm.title", { count: deleteTargets.length })} {t(showDeleteList ? "sftp.deleteConfirm.desc" : "sftp.deleteConfirm.descSingle")}
{hostLabel || deletePath ? (
{hostLabel ? (
{t("sftp.deleteConfirm.host")}: {hostLabel}
) : null} {deletePath ? (
{t("sftp.deleteConfirm.path")}: {deletePath}
) : null}
) : null} {showDeleteList ? (
{deleteListItems.map((name) => (
{name}
))}
) : null}
{ // Only connect to the new target if the disconnect actually happened. // A cancel on the dirty-editor prompt must keep the user on the // current host instead of silently switching and stranding tabs. const ok = await onDisconnect(); if (ok) onConnect("local"); }} onSelectHost={async (host, options) => { const ok = await onDisconnect(); if (ok) onConnect(host, options); }} /> ); };