204 lines
8.5 KiB
TypeScript
204 lines
8.5 KiB
TypeScript
|
|
import { useMemo } from 'react';
|
||
|
|
import { AppWindow, Archive, ArrowRight, ArrowUp, ClipboardCopy, Copy, Download, Edit2, ExternalLink, FilePlus, Folder, FolderInput, FolderPlus, Pencil, RefreshCw, Shield, Trash2, Upload } from 'lucide-react';
|
||
|
|
import { ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from '../ui/context-menu';
|
||
|
|
import { getParentPath } from '../../application/state/sftp/utils';
|
||
|
|
import { isKnownBinaryFile } from '../../lib/sftpFileUtils';
|
||
|
|
import { isExtractableArchive } from '../../domain/sftpArchive';
|
||
|
|
import { isNavigableDirectory } from './utils';
|
||
|
|
import { getSftpTreeUploadFilesTargetPath, getSftpUploadFilesLabelKey, getSftpUploadFolderLabelKey } from './sftpUploadMenu';
|
||
|
|
|
||
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
|
type SftpPaneTreeContextMenuProps = Record<string, any>;
|
||
|
|
|
||
|
|
export function useSftpPaneTreeContextMenu(props: SftpPaneTreeContextMenuProps) {
|
||
|
|
const {
|
||
|
|
contextTarget, pane, toggleExpand, stableOnOpenEntry, stableOnRefresh, getActionPaths, toTransferSources,
|
||
|
|
executeMoveAction, triggerUploadPicker, onUploadExternalFolder, uploadEnabled, folderUploadEnabled,
|
||
|
|
setMoveTargetPaths, setMoveToPath, setMoveToError, setMoveToSuggestions, setMoveToSuggestionIndex,
|
||
|
|
setIsMoving, setShowMoveToDialog, tRef, onCopyToOtherPaneRef, onNavigateToRef, onOpenFileWithSystemDefaultRef, onOpenFileWithRef,
|
||
|
|
onEditFileRef, onDownloadFileRef, onExtractArchiveRef, onEditPermissionsRef, openDeleteConfirmRef, openRenameDialogRef,
|
||
|
|
openNewFolderDialogRef, openNewFileDialogRef,
|
||
|
|
} = props;
|
||
|
|
|
||
|
|
return useMemo(() => {
|
||
|
|
const target = contextTarget;
|
||
|
|
if (!target) return null;
|
||
|
|
|
||
|
|
const { entry, entryPath } = target;
|
||
|
|
const isDir = isNavigableDirectory(entry);
|
||
|
|
const isLocal = pane.connection?.isLocal;
|
||
|
|
|
||
|
|
const handleOpen = () => {
|
||
|
|
if (isDir) void toggleExpand(entry, entryPath);
|
||
|
|
else stableOnOpenEntry(entry, entryPath);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleCopyToOtherPane = () => {
|
||
|
|
const paths = getActionPaths(entryPath);
|
||
|
|
const files = toTransferSources(paths);
|
||
|
|
if (files.length === 0) {
|
||
|
|
files.push({
|
||
|
|
name: entry.name,
|
||
|
|
isDirectory: isDir,
|
||
|
|
sourceConnectionId: pane.connection?.id,
|
||
|
|
sourcePath: getParentPath(entryPath),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
onCopyToOtherPaneRef.current(files);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDelete = () => {
|
||
|
|
openDeleteConfirmRef.current(getActionPaths(entryPath));
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ContextMenuContent>
|
||
|
|
<ContextMenuItem onClick={handleOpen}>
|
||
|
|
{isDir
|
||
|
|
? <><Folder size={14} className="mr-2" />{tRef.current('sftp.context.open')}</>
|
||
|
|
: <><ExternalLink size={14} className="mr-2" />{tRef.current('sftp.context.open')}</>}
|
||
|
|
</ContextMenuItem>
|
||
|
|
{isDir && (
|
||
|
|
<ContextMenuItem onClick={() => onNavigateToRef.current(entryPath)}>
|
||
|
|
<ArrowRight size={14} className="mr-2" />{tRef.current('sftp.context.navigateTo')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{!isDir && onOpenFileWithSystemDefaultRef.current && (
|
||
|
|
<ContextMenuItem onClick={() => onOpenFileWithSystemDefaultRef.current?.(entry, entryPath)}>
|
||
|
|
<AppWindow size={14} className="mr-2" />{tRef.current('sftp.context.openWithDefault')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{!isDir && onOpenFileWithRef.current && (
|
||
|
|
<ContextMenuItem onClick={() => onOpenFileWithRef.current?.(entry, entryPath)}>
|
||
|
|
<ExternalLink size={14} className="mr-2" />{tRef.current('sftp.context.openWith')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{!isDir && !isKnownBinaryFile(entry.name) && onEditFileRef.current && (
|
||
|
|
<ContextMenuItem onClick={() => onEditFileRef.current?.(entry, entryPath)}>
|
||
|
|
<Edit2 size={14} className="mr-2" />{tRef.current('sftp.context.edit')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{onDownloadFileRef.current && (!isDir || !isLocal) && (
|
||
|
|
<ContextMenuItem onClick={() => onDownloadFileRef.current?.(entry, entryPath)}>
|
||
|
|
<Download size={14} className="mr-2" />{tRef.current('sftp.context.download')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{!isDir && onExtractArchiveRef.current && isExtractableArchive(entry.name) && (
|
||
|
|
<ContextMenuItem onClick={() => onExtractArchiveRef.current?.(entry, entryPath)}>
|
||
|
|
<Archive size={14} className="mr-2" />{tRef.current('sftp.context.extract')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
<ContextMenuSeparator />
|
||
|
|
<ContextMenuItem onClick={handleCopyToOtherPane}>
|
||
|
|
<Copy size={14} className="mr-2" />{tRef.current('sftp.context.copyToOtherPane')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuItem onClick={() => navigator.clipboard.writeText(entryPath)}>
|
||
|
|
<ClipboardCopy size={14} className="mr-2" />{tRef.current('sftp.context.copyPath')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuSeparator />
|
||
|
|
{(() => {
|
||
|
|
const sourceParent = getParentPath(entryPath);
|
||
|
|
const targetParent = getParentPath(sourceParent);
|
||
|
|
if (sourceParent === targetParent) return null;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ContextMenuItem onClick={() => {
|
||
|
|
const paths = getActionPaths(entryPath);
|
||
|
|
void executeMoveAction(paths, targetParent);
|
||
|
|
}}>
|
||
|
|
<ArrowUp size={14} className="mr-2" />{tRef.current('sftp.context.moveToParent')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
);
|
||
|
|
})()}
|
||
|
|
<ContextMenuItem onClick={() => {
|
||
|
|
setMoveTargetPaths(getActionPaths(entryPath));
|
||
|
|
setMoveToPath('');
|
||
|
|
setMoveToError(null);
|
||
|
|
setMoveToSuggestions([]);
|
||
|
|
setMoveToSuggestionIndex(-1);
|
||
|
|
setIsMoving(false);
|
||
|
|
setShowMoveToDialog(true);
|
||
|
|
}}>
|
||
|
|
<FolderInput size={14} className="mr-2" />{tRef.current('sftp.context.moveTo')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuItem onClick={() => openRenameDialogRef.current(entryPath)}>
|
||
|
|
<Pencil size={14} className="mr-2" />{tRef.current('common.rename')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
{onEditPermissionsRef.current && !isLocal && (
|
||
|
|
<ContextMenuItem onClick={() => onEditPermissionsRef.current?.(entry, entryPath)}>
|
||
|
|
<Shield size={14} className="mr-2" />{tRef.current('sftp.context.permissions')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
<ContextMenuItem
|
||
|
|
className="text-destructive"
|
||
|
|
onClick={handleDelete}
|
||
|
|
>
|
||
|
|
<Trash2 size={14} className="mr-2" />{tRef.current('action.delete')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuSeparator />
|
||
|
|
<ContextMenuItem onClick={stableOnRefresh}>
|
||
|
|
<RefreshCw size={14} className="mr-2" />{tRef.current('common.refresh')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuItem onClick={() => openNewFolderDialogRef.current(isDir ? entryPath : getParentPath(entryPath))}>
|
||
|
|
<FolderPlus size={14} className="mr-2" />{tRef.current('sftp.newFolder')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
<ContextMenuItem onClick={() => openNewFileDialogRef.current(isDir ? entryPath : getParentPath(entryPath))}>
|
||
|
|
<FilePlus size={14} className="mr-2" />{tRef.current('sftp.newFile')}
|
||
|
|
</ContextMenuItem>
|
||
|
|
{uploadEnabled && (
|
||
|
|
<ContextMenuItem
|
||
|
|
onClick={() => {
|
||
|
|
triggerUploadPicker(getSftpTreeUploadFilesTargetPath(entry, entryPath));
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Upload size={14} className="mr-2" />{tRef.current(getSftpUploadFilesLabelKey(entry))}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
{folderUploadEnabled && (
|
||
|
|
<ContextMenuItem
|
||
|
|
onClick={() => {
|
||
|
|
void onUploadExternalFolder?.(getSftpTreeUploadFilesTargetPath(entry, entryPath));
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Upload size={14} className="mr-2" />{tRef.current(getSftpUploadFolderLabelKey(entry))}
|
||
|
|
</ContextMenuItem>
|
||
|
|
)}
|
||
|
|
</ContextMenuContent>
|
||
|
|
);
|
||
|
|
}, [
|
||
|
|
contextTarget,
|
||
|
|
pane.connection?.isLocal,
|
||
|
|
pane.connection?.id,
|
||
|
|
toggleExpand,
|
||
|
|
stableOnOpenEntry,
|
||
|
|
stableOnRefresh,
|
||
|
|
getActionPaths,
|
||
|
|
toTransferSources,
|
||
|
|
executeMoveAction,
|
||
|
|
triggerUploadPicker,
|
||
|
|
uploadEnabled,
|
||
|
|
folderUploadEnabled,
|
||
|
|
onUploadExternalFolder,
|
||
|
|
onCopyToOtherPaneRef,
|
||
|
|
onDownloadFileRef,
|
||
|
|
onExtractArchiveRef,
|
||
|
|
onEditFileRef,
|
||
|
|
onEditPermissionsRef,
|
||
|
|
onNavigateToRef,
|
||
|
|
onOpenFileWithSystemDefaultRef,
|
||
|
|
onOpenFileWithRef,
|
||
|
|
openDeleteConfirmRef,
|
||
|
|
openNewFileDialogRef,
|
||
|
|
openNewFolderDialogRef,
|
||
|
|
openRenameDialogRef,
|
||
|
|
setIsMoving,
|
||
|
|
setMoveTargetPaths,
|
||
|
|
setMoveToError,
|
||
|
|
setMoveToPath,
|
||
|
|
setMoveToSuggestionIndex,
|
||
|
|
setMoveToSuggestions,
|
||
|
|
setShowMoveToDialog,
|
||
|
|
tRef,
|
||
|
|
]);
|
||
|
|
}
|