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; 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>; setShowHostPickerRight: React.Dispatch>; setHostSearchLeft: React.Dispatch>; setHostSearchRight: React.Dispatch>; handleAddTabLeft: () => string; handleAddTabRight: () => string; handleCloseTabLeft: (tabId: string) => Promise; handleCloseTabRight: (tabId: string) => Promise; 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; handleDuplicateTabRight: (tabId: string, mode: SftpTabDuplicateMode) => Promise; handleHostSelectLeft: ( host: Host | "local", options?: Parameters[2], ) => void; handleHostSelectRight: ( host: Host | "local", options?: Parameters[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 => { 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[2], ) => { sftpRef.current.connect("left", host, options); setShowHostPickerLeft(false); }, [sftpRef]); const handleHostSelectRight = useCallback(( host: Host | "local", options?: Parameters[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, }; };