[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:
164
components/sftp/clipboardUpload.ts
Normal file
164
components/sftp/clipboardUpload.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import type { SftpFileEntry } from "../../types";
|
||||
import type { DropEntry } from "../../lib/sftpFileUtils";
|
||||
import type { KeyBinding } from "../../domain/models";
|
||||
import { joinPath } from "../../application/state/sftp/utils";
|
||||
import { isNavigableDirectory } from "./utils";
|
||||
|
||||
export interface ClipboardLocalFile {
|
||||
path: string;
|
||||
name: string;
|
||||
isDirectory: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface SftpClipboardUploadTreeSelection {
|
||||
name: string;
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
}
|
||||
|
||||
export interface ResolveSftpClipboardUploadTargetParams {
|
||||
currentPath: string;
|
||||
selectedFileNames: string[];
|
||||
files: SftpFileEntry[];
|
||||
treeSelection: SftpClipboardUploadTreeSelection[];
|
||||
}
|
||||
|
||||
export interface GetSftpClipboardSystemTextPathsParams {
|
||||
currentPath: string;
|
||||
selectedFileNames: string[];
|
||||
treeSelection: SftpClipboardUploadTreeSelection[];
|
||||
}
|
||||
|
||||
export function resolveSftpClipboardUploadTarget({
|
||||
currentPath,
|
||||
selectedFileNames,
|
||||
files,
|
||||
treeSelection,
|
||||
}: ResolveSftpClipboardUploadTargetParams): string {
|
||||
const selectedTreeFolders = treeSelection.filter((entry) => entry.isDirectory && entry.name !== "..");
|
||||
if (selectedTreeFolders.length === 1) {
|
||||
return selectedTreeFolders[0].path;
|
||||
}
|
||||
|
||||
if (selectedFileNames.length === 1) {
|
||||
const filesByName = new Map(files.map((entry) => [entry.name, entry]));
|
||||
const selectedEntry = filesByName.get(selectedFileNames[0]);
|
||||
if (selectedEntry && isNavigableDirectory(selectedEntry)) {
|
||||
return joinPath(currentPath, selectedEntry.name);
|
||||
}
|
||||
}
|
||||
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
export function getSftpClipboardSystemTextPaths({
|
||||
currentPath,
|
||||
selectedFileNames,
|
||||
treeSelection,
|
||||
}: GetSftpClipboardSystemTextPathsParams): string[] {
|
||||
if (treeSelection.length > 0) {
|
||||
return treeSelection.map((entry) => entry.path);
|
||||
}
|
||||
|
||||
return selectedFileNames.map((name) => joinPath(currentPath, name));
|
||||
}
|
||||
|
||||
export function createDropEntriesFromClipboardFiles(files: ClipboardLocalFile[]): DropEntry[] {
|
||||
return files.map((file) => ({
|
||||
file: null,
|
||||
localPath: file.path,
|
||||
relativePath: file.name,
|
||||
isDirectory: file.isDirectory,
|
||||
size: file.size,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getSupportedClipboardUploadFiles(files: ClipboardLocalFile[]): ClipboardLocalFile[] {
|
||||
return files;
|
||||
}
|
||||
|
||||
export function shouldLetNativePasteEventHandleSftpPaste(
|
||||
action: string,
|
||||
key: string | undefined,
|
||||
): boolean {
|
||||
if (action !== "sftpPaste" || !key) return false;
|
||||
const normalized = key.toLowerCase().replace(/\s+/g, "");
|
||||
return [
|
||||
"ctrl+v",
|
||||
"⌘+v",
|
||||
"cmd+v",
|
||||
"command+v",
|
||||
].includes(normalized);
|
||||
}
|
||||
|
||||
export function isSftpNativeClipboardPasteEnabled(
|
||||
hotkeyScheme: "disabled" | "mac" | "pc",
|
||||
keyBindings: KeyBinding[],
|
||||
): boolean {
|
||||
if (hotkeyScheme === "disabled") return false;
|
||||
const pasteBinding = keyBindings.find((binding) => (
|
||||
binding.category === "sftp" && binding.action === "sftpPaste"
|
||||
));
|
||||
if (!pasteBinding) return false;
|
||||
const key = hotkeyScheme === "mac" ? pasteBinding.mac : pasteBinding.pc;
|
||||
return shouldLetNativePasteEventHandleSftpPaste("sftpPaste", key);
|
||||
}
|
||||
|
||||
export interface SftpClipboardUploadRequest {
|
||||
scopeId: string;
|
||||
side: "left" | "right";
|
||||
targetPath: string;
|
||||
files: ClipboardLocalFile[];
|
||||
onConfirm: () => Promise<void>;
|
||||
}
|
||||
|
||||
type ClipboardUploadListener = () => void;
|
||||
|
||||
let clipboardUploadRequest: SftpClipboardUploadRequest | null = null;
|
||||
const clipboardUploadListeners = new Set<ClipboardUploadListener>();
|
||||
|
||||
const notifyClipboardUploadListeners = () => {
|
||||
clipboardUploadListeners.forEach((listener) => listener());
|
||||
};
|
||||
|
||||
export const sftpClipboardUploadStore = {
|
||||
trigger: (request: SftpClipboardUploadRequest) => {
|
||||
clipboardUploadRequest = request;
|
||||
notifyClipboardUploadListeners();
|
||||
},
|
||||
clear: (request?: SftpClipboardUploadRequest | null) => {
|
||||
if (request && clipboardUploadRequest !== request) return;
|
||||
clipboardUploadRequest = null;
|
||||
notifyClipboardUploadListeners();
|
||||
},
|
||||
getSnapshot: () => clipboardUploadRequest,
|
||||
subscribe: (listener: ClipboardUploadListener) => {
|
||||
clipboardUploadListeners.add(listener);
|
||||
return () => clipboardUploadListeners.delete(listener);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Hand off a confirmed clipboard upload without holding the modal open.
|
||||
* Clears the active request first, then runs the transfer (issue #2478).
|
||||
*/
|
||||
export async function confirmSftpClipboardUpload(params: {
|
||||
request: SftpClipboardUploadRequest;
|
||||
clear?: (request: SftpClipboardUploadRequest) => void;
|
||||
onUploaded?: (targetPath: string) => void;
|
||||
}): Promise<void> {
|
||||
const { request, onUploaded } = params;
|
||||
const clear = params.clear ?? sftpClipboardUploadStore.clear;
|
||||
clear(request);
|
||||
await request.onConfirm();
|
||||
onUploaded?.(request.targetPath);
|
||||
}
|
||||
|
||||
/** True when Upload should start for this request (blocks same-request double-click only). */
|
||||
export function shouldStartClipboardUploadConfirm(
|
||||
request: SftpClipboardUploadRequest | null | undefined,
|
||||
alreadyStartedFor: SftpClipboardUploadRequest | null | undefined,
|
||||
): boolean {
|
||||
return !!request && alreadyStartedFor !== request;
|
||||
}
|
||||
Reference in New Issue
Block a user