[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
203
components/sftp/useSftpPaneTreeContextMenu.tsx
Normal file
203
components/sftp/useSftpPaneTreeContextMenu.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
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,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user