[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:
150
domain/sftpTransferNavigation.ts
Normal file
150
domain/sftpTransferNavigation.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { TransferTask } from "./models";
|
||||
|
||||
export type SftpTransferNavigationTarget = {
|
||||
kind: "local-path" | "local-copy-panel" | "remote-host";
|
||||
hostId?: string;
|
||||
/** When true, open the source path instead of the target path. */
|
||||
useSourcePath: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decide which endpoint the global transfer center should open.
|
||||
*
|
||||
* Resume needs a remote host available for re-auth/reconnect. Opening the
|
||||
* destination folder after completion should open the target instead.
|
||||
*
|
||||
* Important: dual-pane downloads store a real local connection UUID in
|
||||
* `targetConnectionId`, not the sentinel `"local"`. Prefer direction + host
|
||||
* ids over connection-id string equality alone.
|
||||
*
|
||||
* Uploads always target a remote host even when `targetHostId` is missing
|
||||
* (older rows / drag-drop without a vault id) — never treat a remote path as
|
||||
* a local `openPath` target.
|
||||
*/
|
||||
export function resolveSftpTransferNavigationTarget(
|
||||
task: Pick<
|
||||
TransferTask,
|
||||
| "direction"
|
||||
| "sourceHostId"
|
||||
| "targetHostId"
|
||||
| "sourceConnectionId"
|
||||
| "targetConnectionId"
|
||||
| "sourcePath"
|
||||
| "targetPath"
|
||||
| "isDirectory"
|
||||
>,
|
||||
forResume: boolean,
|
||||
): SftpTransferNavigationTarget {
|
||||
const sourceIsRemote = Boolean(task.sourceHostId)
|
||||
|| (task.direction === "download" || task.direction === "remote-to-remote");
|
||||
const targetIsRemote = Boolean(task.targetHostId)
|
||||
|| task.direction === "upload"
|
||||
|| task.direction === "remote-to-remote";
|
||||
const isLocalCopy = task.direction === "local-copy"
|
||||
|| (
|
||||
task.direction !== "upload"
|
||||
&& task.direction !== "download"
|
||||
&& task.direction !== "remote-to-remote"
|
||||
&& !task.sourceHostId
|
||||
&& !task.targetHostId
|
||||
);
|
||||
|
||||
if (forResume) {
|
||||
if (isLocalCopy) {
|
||||
return { kind: "local-copy-panel", useSourcePath: false };
|
||||
}
|
||||
// Prefer the remote source when present (downloads, remote-to-remote).
|
||||
// Uploads only have a remote target.
|
||||
if (sourceIsRemote && task.direction !== "upload") {
|
||||
return { kind: "remote-host", hostId: task.sourceHostId, useSourcePath: true };
|
||||
}
|
||||
return { kind: "remote-host", hostId: task.targetHostId, useSourcePath: false };
|
||||
}
|
||||
|
||||
// Open destination folder: local filesystem for downloads/local-copy;
|
||||
// remote SFTP panel for uploads and server-to-server targets.
|
||||
if (task.direction === "upload" || task.direction === "remote-to-remote" || targetIsRemote) {
|
||||
return { kind: "remote-host", hostId: task.targetHostId, useSourcePath: false };
|
||||
}
|
||||
return { kind: "local-path", useSourcePath: false };
|
||||
}
|
||||
|
||||
export function resolveSftpTransferNavigationPath(
|
||||
task: Pick<TransferTask, "sourcePath" | "targetPath" | "isDirectory">,
|
||||
useSourcePath: boolean,
|
||||
): string {
|
||||
const rawPath = useSourcePath ? task.sourcePath : task.targetPath;
|
||||
if (task.isDirectory) return rawPath;
|
||||
return rawPath.replace(/[\\/][^\\/]+$/, "") || "/";
|
||||
}
|
||||
|
||||
/** Host label used when `hostId` is missing and we fall back to name matching. */
|
||||
export function resolveSftpTransferNavigationHostLabel(
|
||||
task: Pick<TransferTask, "sourceHostLabel" | "targetHostLabel">,
|
||||
useSourcePath: boolean,
|
||||
): string | undefined {
|
||||
return useSourcePath ? task.sourceHostLabel : task.targetHostLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level tabs that can host the SFTP side panel (session or workspace ids).
|
||||
* Vault / full-page SFTP / editor tabs are not valid SFTP side-panel scopes.
|
||||
*/
|
||||
export function isTransferNavigationTerminalTabId(tabId: string | null | undefined): boolean {
|
||||
if (!tabId) return false;
|
||||
if (tabId === "vault" || tabId === "sftp") return false;
|
||||
if (tabId.startsWith("editor:")) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export type TransferNavigationHostLike = {
|
||||
id: string;
|
||||
label?: string;
|
||||
hostname?: string;
|
||||
isLocal?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a vault/session host for open-folder / resume routing.
|
||||
* Prefer id, then label/hostname, then optional live SFTP panel hosts
|
||||
* (covers drag-drop uploads that never persisted a vault host id).
|
||||
*/
|
||||
export function pickHostForTransferNavigation(params: {
|
||||
hostId?: string;
|
||||
hostLabel?: string;
|
||||
vaultHosts: readonly TransferNavigationHostLike[];
|
||||
/** Currently open SFTP hosts (active tab first). */
|
||||
liveHosts?: readonly TransferNavigationHostLike[];
|
||||
/** When true and no id/label match, use the first non-local live host. */
|
||||
allowLiveUploadFallback?: boolean;
|
||||
}): TransferNavigationHostLike | null {
|
||||
const needle = (params.hostLabel || "").trim().toLowerCase();
|
||||
const matchIn = (hosts: readonly TransferNavigationHostLike[]) => {
|
||||
if (params.hostId) {
|
||||
const byId = hosts.find((host) => host.id === params.hostId);
|
||||
if (byId) return byId;
|
||||
}
|
||||
if (needle && needle !== "local") {
|
||||
const byLabel = hosts.find((host) => {
|
||||
const label = (host.label || "").trim().toLowerCase();
|
||||
const hostname = (host.hostname || "").trim().toLowerCase();
|
||||
return label === needle || hostname === needle;
|
||||
});
|
||||
if (byLabel) return byLabel;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const fromVault = matchIn(params.vaultHosts);
|
||||
if (fromVault) return fromVault;
|
||||
|
||||
const live = params.liveHosts ?? [];
|
||||
const fromLive = matchIn(live);
|
||||
if (fromLive) return fromLive;
|
||||
|
||||
if (params.allowLiveUploadFallback) {
|
||||
const openRemote = live.find((host) => host && host.isLocal !== true && host.id && host.id !== "local");
|
||||
if (openRemote) return openRemote;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user