[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:
260
components/sftp/hooks/useSftpViewTabs.ts
Normal file
260
components/sftp/hooks/useSftpViewTabs.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import type { MutableRefObject } from "react";
|
||||
import type { Host } from "../../../types";
|
||||
import type { SftpStateApi } from "../../../application/state/useSftpState";
|
||||
import { editorTabStore } from "../../../application/state/editorTabStore";
|
||||
import type { EditorTab, EditorTabId } from "../../../application/state/editorTabStore";
|
||||
import { releaseEditorTabSaveCoordinator, saveEditorTab } from "../../../application/state/editorTabSave";
|
||||
import { promptUnsavedChanges } from "../../editor/UnsavedChangesDialog";
|
||||
import {
|
||||
getSftpTabDuplicateRequest,
|
||||
type SftpTabDuplicateMode,
|
||||
} from "../sftpTabDuplication";
|
||||
|
||||
interface UseSftpViewTabsParams {
|
||||
sftp: SftpStateApi;
|
||||
sftpRef: MutableRefObject<SftpStateApi>;
|
||||
hosts?: Host[];
|
||||
}
|
||||
|
||||
interface UseSftpViewTabsResult {
|
||||
leftPanes: SftpStateApi["leftPane"][];
|
||||
rightPanes: SftpStateApi["rightPane"][];
|
||||
leftTabsInfo: { id: string; label: string; isLocal: boolean; hostId: string | null; canDuplicate: boolean }[];
|
||||
rightTabsInfo: { id: string; label: string; isLocal: boolean; hostId: string | null; canDuplicate: boolean }[];
|
||||
showHostPickerLeft: boolean;
|
||||
showHostPickerRight: boolean;
|
||||
hostSearchLeft: string;
|
||||
hostSearchRight: string;
|
||||
setShowHostPickerLeft: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setShowHostPickerRight: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setHostSearchLeft: React.Dispatch<React.SetStateAction<string>>;
|
||||
setHostSearchRight: React.Dispatch<React.SetStateAction<string>>;
|
||||
handleAddTabLeft: () => string;
|
||||
handleAddTabRight: () => string;
|
||||
handleCloseTabLeft: (tabId: string) => Promise<void>;
|
||||
handleCloseTabRight: (tabId: string) => Promise<void>;
|
||||
handleSelectTabLeft: (tabId: string) => void;
|
||||
handleSelectTabRight: (tabId: string) => void;
|
||||
handleReorderTabsLeft: (draggedId: string, targetId: string, position: "before" | "after") => void;
|
||||
handleReorderTabsRight: (draggedId: string, targetId: string, position: "before" | "after") => void;
|
||||
handleMoveTabFromLeftToRight: (tabId: string) => void;
|
||||
handleMoveTabFromRightToLeft: (tabId: string) => void;
|
||||
handleDuplicateTabLeft: (tabId: string, mode: SftpTabDuplicateMode) => Promise<string | null>;
|
||||
handleDuplicateTabRight: (tabId: string, mode: SftpTabDuplicateMode) => Promise<string | null>;
|
||||
handleHostSelectLeft: (
|
||||
host: Host | "local",
|
||||
options?: Parameters<SftpStateApi["connect"]>[2],
|
||||
) => void;
|
||||
handleHostSelectRight: (
|
||||
host: Host | "local",
|
||||
options?: Parameters<SftpStateApi["connect"]>[2],
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useSftpViewTabs = ({ sftp, sftpRef, hosts = [] }: UseSftpViewTabsParams): UseSftpViewTabsResult => {
|
||||
const [showHostPickerLeft, setShowHostPickerLeft] = useState(false);
|
||||
const [showHostPickerRight, setShowHostPickerRight] = useState(false);
|
||||
const [hostSearchLeft, setHostSearchLeft] = useState("");
|
||||
const [hostSearchRight, setHostSearchRight] = useState("");
|
||||
const hostsRef = React.useRef(hosts);
|
||||
hostsRef.current = hosts;
|
||||
|
||||
const handleAddTabLeft = useCallback(() => {
|
||||
const tabId = sftpRef.current.addTab("left");
|
||||
setShowHostPickerLeft(true);
|
||||
return tabId;
|
||||
}, [sftpRef]);
|
||||
|
||||
const handleAddTabRight = useCallback(() => {
|
||||
const tabId = sftpRef.current.addTab("right");
|
||||
setShowHostPickerRight(true);
|
||||
return tabId;
|
||||
}, [sftpRef]);
|
||||
|
||||
const confirmCloseEditorTabsByOwner = useCallback(async (owner: {
|
||||
sessionId?: string;
|
||||
sftpTabId?: string;
|
||||
}): Promise<boolean> => {
|
||||
const choice = (tab: EditorTab) => promptUnsavedChanges(tab.fileName);
|
||||
const saveTab = async (id: EditorTabId) => {
|
||||
const ok = await saveEditorTab(id);
|
||||
const tab = editorTabStore.getTab(id);
|
||||
if (!ok || (tab && tab.content !== tab.baselineContent)) {
|
||||
throw new Error(tab?.saveError ?? "Save failed");
|
||||
}
|
||||
};
|
||||
return editorTabStore.confirmCloseByOwner(
|
||||
owner,
|
||||
choice,
|
||||
saveTab,
|
||||
releaseEditorTabSaveCoordinator,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleCloseSftpTab = useCallback(async (side: "left" | "right", tabId: string) => {
|
||||
const sideTabs = side === "left" ? sftpRef.current.leftTabs : sftpRef.current.rightTabs;
|
||||
const pane = sideTabs.tabs.find((tab) => tab.id === tabId);
|
||||
if (pane?.connection?.id || pane) {
|
||||
const ok = await confirmCloseEditorTabsByOwner({
|
||||
sessionId: pane?.connection?.id,
|
||||
sftpTabId: tabId,
|
||||
});
|
||||
if (!ok) return;
|
||||
}
|
||||
await sftpRef.current.closeTab(side, tabId);
|
||||
}, [confirmCloseEditorTabsByOwner, sftpRef]);
|
||||
|
||||
const handleCloseTabLeft = useCallback((tabId: string) => (
|
||||
handleCloseSftpTab("left", tabId)
|
||||
), [handleCloseSftpTab]);
|
||||
|
||||
const handleCloseTabRight = useCallback((tabId: string) => (
|
||||
handleCloseSftpTab("right", tabId)
|
||||
), [handleCloseSftpTab]);
|
||||
|
||||
const handleSelectTabLeft = useCallback((tabId: string) => {
|
||||
sftpRef.current.selectTab("left", tabId);
|
||||
}, [sftpRef]);
|
||||
|
||||
const handleSelectTabRight = useCallback((tabId: string) => {
|
||||
sftpRef.current.selectTab("right", tabId);
|
||||
}, [sftpRef]);
|
||||
|
||||
const leftPanes = useMemo(
|
||||
() => (sftp.leftTabs.tabs.length > 0 ? sftp.leftTabs.tabs : [sftp.leftPane]),
|
||||
[sftp.leftTabs.tabs, sftp.leftPane],
|
||||
);
|
||||
const rightPanes = useMemo(
|
||||
() => (sftp.rightTabs.tabs.length > 0 ? sftp.rightTabs.tabs : [sftp.rightPane]),
|
||||
[sftp.rightTabs.tabs, sftp.rightPane],
|
||||
);
|
||||
|
||||
const handleReorderTabsLeft = useCallback(
|
||||
(draggedId: string, targetId: string, position: "before" | "after") => {
|
||||
sftpRef.current.reorderTabs("left", draggedId, targetId, position);
|
||||
},
|
||||
[sftpRef],
|
||||
);
|
||||
|
||||
const handleReorderTabsRight = useCallback(
|
||||
(draggedId: string, targetId: string, position: "before" | "after") => {
|
||||
sftpRef.current.reorderTabs("right", draggedId, targetId, position);
|
||||
},
|
||||
[sftpRef],
|
||||
);
|
||||
|
||||
const handleMoveTabFromLeftToRight = useCallback((tabId: string) => {
|
||||
sftpRef.current.moveTabToOtherSide("left", tabId);
|
||||
}, [sftpRef]);
|
||||
|
||||
const handleMoveTabFromRightToLeft = useCallback((tabId: string) => {
|
||||
sftpRef.current.moveTabToOtherSide("right", tabId);
|
||||
}, [sftpRef]);
|
||||
|
||||
const handleDuplicateTab = useCallback(
|
||||
async (side: "left" | "right", tabId: string, mode: SftpTabDuplicateMode) => {
|
||||
const sideTabs = side === "left" ? sftpRef.current.leftTabs : sftpRef.current.rightTabs;
|
||||
const pane = sideTabs.tabs.find((tab) => tab.id === tabId);
|
||||
const request = getSftpTabDuplicateRequest(pane, mode);
|
||||
if (!request) return null;
|
||||
|
||||
const host = request.kind === "local"
|
||||
? "local"
|
||||
: hostsRef.current.find((item) => item.id === request.hostId);
|
||||
if (!host) return null;
|
||||
|
||||
let duplicatedTabId: string | null = null;
|
||||
await sftpRef.current.connect(side, host, {
|
||||
forceNewTab: true,
|
||||
ignoreSharedCache: mode === "defaultPath",
|
||||
initialPath: request.path,
|
||||
onTabCreated: (createdTabId) => {
|
||||
duplicatedTabId = createdTabId;
|
||||
},
|
||||
});
|
||||
|
||||
return duplicatedTabId;
|
||||
},
|
||||
[sftpRef],
|
||||
);
|
||||
|
||||
const handleDuplicateTabLeft = useCallback(
|
||||
(tabId: string, mode: SftpTabDuplicateMode) => handleDuplicateTab("left", tabId, mode),
|
||||
[handleDuplicateTab],
|
||||
);
|
||||
|
||||
const handleDuplicateTabRight = useCallback(
|
||||
(tabId: string, mode: SftpTabDuplicateMode) => handleDuplicateTab("right", tabId, mode),
|
||||
[handleDuplicateTab],
|
||||
);
|
||||
|
||||
const handleHostSelectLeft = useCallback((
|
||||
host: Host | "local",
|
||||
options?: Parameters<SftpStateApi["connect"]>[2],
|
||||
) => {
|
||||
sftpRef.current.connect("left", host, options);
|
||||
setShowHostPickerLeft(false);
|
||||
}, [sftpRef]);
|
||||
|
||||
const handleHostSelectRight = useCallback((
|
||||
host: Host | "local",
|
||||
options?: Parameters<SftpStateApi["connect"]>[2],
|
||||
) => {
|
||||
sftpRef.current.connect("right", host, options);
|
||||
setShowHostPickerRight(false);
|
||||
}, [sftpRef]);
|
||||
|
||||
const leftTabsInfo = useMemo(
|
||||
() =>
|
||||
sftp.leftTabs.tabs.map((pane) => ({
|
||||
id: pane.id,
|
||||
label: pane.connection?.hostLabel || "New Tab",
|
||||
isLocal: pane.connection?.isLocal || false,
|
||||
hostId: pane.connection?.hostId || null,
|
||||
canDuplicate: pane.connection?.status === "connected",
|
||||
})),
|
||||
[sftp.leftTabs.tabs],
|
||||
);
|
||||
|
||||
const rightTabsInfo = useMemo(
|
||||
() =>
|
||||
sftp.rightTabs.tabs.map((pane) => ({
|
||||
id: pane.id,
|
||||
label: pane.connection?.hostLabel || "New Tab",
|
||||
isLocal: pane.connection?.isLocal || false,
|
||||
hostId: pane.connection?.hostId || null,
|
||||
canDuplicate: pane.connection?.status === "connected",
|
||||
})),
|
||||
[sftp.rightTabs.tabs],
|
||||
);
|
||||
|
||||
return {
|
||||
leftPanes,
|
||||
rightPanes,
|
||||
leftTabsInfo,
|
||||
rightTabsInfo,
|
||||
showHostPickerLeft,
|
||||
showHostPickerRight,
|
||||
hostSearchLeft,
|
||||
hostSearchRight,
|
||||
setShowHostPickerLeft,
|
||||
setShowHostPickerRight,
|
||||
setHostSearchLeft,
|
||||
setHostSearchRight,
|
||||
handleAddTabLeft,
|
||||
handleAddTabRight,
|
||||
handleCloseTabLeft,
|
||||
handleCloseTabRight,
|
||||
handleSelectTabLeft,
|
||||
handleSelectTabRight,
|
||||
handleReorderTabsLeft,
|
||||
handleReorderTabsRight,
|
||||
handleMoveTabFromLeftToRight,
|
||||
handleMoveTabFromRightToLeft,
|
||||
handleDuplicateTabLeft,
|
||||
handleDuplicateTabRight,
|
||||
handleHostSelectLeft,
|
||||
handleHostSelectRight,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user