Files
NetMesh/components/sftp/hooks/useSftpViewPaneCallbacks.ts

236 lines
9.9 KiB
TypeScript
Raw Normal View History

import { useEffect, useMemo, useRef } from "react";
import type { MutableRefObject } from "react";
import type { SftpStateApi } from "../../../application/state/useSftpState";
import type { RemoteFile, SftpFilenameEncoding } from "../../../types";
import type { SftpPaneCallbacks } from "../SftpContext";
import type { SftpPane } from "../../../application/state/sftp/types";
import { useSftpViewPaneActions } from "./useSftpViewPaneActions";
import { useSftpViewFileOps } from "./useSftpViewFileOps";
import type { FileOpenerType, SystemAppInfo } from "../../../lib/sftpFileUtils";
import { formatFileSize, formatDate } from '../../../application/state/sftp/utils';
import { isSessionError } from "../../../application/state/sftp/errors";
import { filterHiddenFiles } from "../utils";
interface UseSftpViewPaneCallbacksParams {
sftpRef: MutableRefObject<SftpStateApi>;
behaviorRef: MutableRefObject<string>;
autoSyncRef: MutableRefObject<boolean>;
getOpenerForFileRef: MutableRefObject<
(fileName: string) => { openerType?: FileOpenerType; systemApp?: SystemAppInfo } | null
>;
setOpenerForExtension: (
extension: string,
openerType: FileOpenerType,
systemApp?: SystemAppInfo,
) => void;
t: (key: string, vars?: Record<string, string | number>) => string;
listSftp?: (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => Promise<RemoteFile[]>;
showSaveDialog?: (defaultPath: string, filters?: Array<{ name: string; extensions: string[] }>) => Promise<string | null>;
selectDirectory?: (title?: string, defaultPath?: string) => Promise<string | null>;
getSftpIdForConnection?: (connectionId: string) => string | undefined;
listLocalFiles: (path: string) => Promise<RemoteFile[]>;
mkdirLocal?: (path: string) => Promise<void>;
deleteLocalFile?: (path: string) => Promise<void>;
listDrives: () => Promise<string[]>;
}
export const useSftpViewPaneCallbacks = ({
sftpRef,
behaviorRef,
autoSyncRef,
getOpenerForFileRef,
setOpenerForExtension,
t,
listSftp,
showSaveDialog,
selectDirectory,
getSftpIdForConnection,
listLocalFiles,
listDrives,
}: UseSftpViewPaneCallbacksParams) => {
const paneActions = useSftpViewPaneActions({ sftpRef, t });
const fileOps = useSftpViewFileOps({
sftpRef,
behaviorRef,
autoSyncRef,
getOpenerForFileRef,
setOpenerForExtension,
t,
showSaveDialog,
selectDirectory,
getSftpIdForConnection,
});
const listLocalFilesRef = useRef(listLocalFiles);
const listSftpRef = useRef(listSftp);
const getSftpIdForConnectionRef = useRef(getSftpIdForConnection);
useEffect(() => {
listLocalFilesRef.current = listLocalFiles;
listSftpRef.current = listSftp;
getSftpIdForConnectionRef.current = getSftpIdForConnection;
}, [listLocalFiles, listSftp, getSftpIdForConnection]);
const makeListDirectory = (side: "left" | "right", getPane: () => SftpPane) =>
async (path: string) => {
const pane = getPane();
if (!pane.connection) return [];
const toSize = (raw: string) => parseInt(raw) || 0;
const toTs = (raw: string) => new Date(raw).getTime();
const normalizeEntries = (rawFiles: RemoteFile[]) =>
filterHiddenFiles(
rawFiles.map(f => {
const s = toSize(f.size);
const ms = toTs(f.lastModified);
return {
name: f.name,
type: f.type as 'file' | 'directory' | 'symlink',
size: s,
sizeFormatted: formatFileSize(s),
lastModified: ms,
lastModifiedFormatted: formatDate(ms),
permissions: f.permissions,
owner: f.owner,
linkTarget: f.linkTarget as 'file' | 'directory' | null | undefined,
hidden: f.hidden,
};
}),
pane.showHiddenFiles,
);
if (pane.connection.isLocal) {
return normalizeEntries(await listLocalFilesRef.current(path));
}
const sftpId = getSftpIdForConnectionRef.current?.(pane.connection.id);
if (!sftpId) {
const error = new Error("SFTP session not found");
sftpRef.current.reportSessionError(side, error);
throw error;
}
let rawFiles: RemoteFile[] | undefined;
try {
rawFiles = await listSftpRef.current?.(sftpId, path, pane.filenameEncoding);
} catch (err) {
if (isSessionError(err)) {
sftpRef.current.reportSessionError(side, err as Error);
}
throw err;
}
if (!rawFiles) return [];
return normalizeEntries(rawFiles);
};
/* eslint-disable react-hooks/exhaustive-deps -- Handlers use refs, so they are stable */
const leftCallbacks = useMemo<SftpPaneCallbacks>(
() => ({
onConnect: paneActions.onConnectLeft,
onDisconnect: paneActions.onDisconnectLeft,
onPrepareSelection: paneActions.onPrepareSelectionLeft,
onNavigateTo: paneActions.onNavigateToLeft,
onNavigateUp: paneActions.onNavigateUpLeft,
onRefresh: paneActions.onRefreshLeft,
onRefreshTab: paneActions.onRefreshTabLeft,
onSetFilenameEncoding: paneActions.onSetFilenameEncodingLeft,
onOpenEntry: fileOps.onOpenEntryLeft,
onToggleSelection: paneActions.onToggleSelectionLeft,
onRangeSelect: paneActions.onRangeSelectLeft,
onClearSelection: paneActions.onClearSelectionLeft,
onSetFilter: paneActions.onSetFilterLeft,
onCreateDirectory: paneActions.onCreateDirectoryLeft,
onCreateDirectoryAtPath: paneActions.onCreateDirectoryAtPathLeft,
onCreateFile: paneActions.onCreateFileLeft,
onCreateFileAtPath: paneActions.onCreateFileAtPathLeft,
onDeleteFiles: paneActions.onDeleteFilesLeft,
onDeleteFilesAtPath: paneActions.onDeleteFilesAtPathLeft,
onRenameFile: paneActions.onRenameFileLeft,
onRenameFileAtPath: paneActions.onRenameFileAtPathLeft,
onMoveEntriesToPath: paneActions.onMoveEntriesToPathLeft,
onCopyToOtherPane: paneActions.onCopyToOtherPaneLeft,
onReceiveFromOtherPane: paneActions.onReceiveFromOtherPaneLeft,
onEditPermissions: fileOps.onEditPermissionsLeft,
onEditFile: fileOps.onEditFileLeft,
onOpenFile: fileOps.onOpenFileLeft,
onOpenFileWithSystemDefault: fileOps.onOpenFileWithSystemDefaultLeft,
onOpenFileWith: fileOps.onOpenFileWithLeft,
onDownloadFile: fileOps.onDownloadFileLeft,
onDownloadFiles: fileOps.onDownloadFilesLeft,
onExtractArchive: fileOps.onExtractArchiveLeft,
onUploadExternalFiles: fileOps.onUploadExternalFilesLeft,
onUploadExternalFileList: fileOps.onUploadExternalFileListLeft,
onUploadExternalFolder: fileOps.onUploadExternalFolderLeft,
onListDirectory: makeListDirectory("left", () => sftpRef.current.leftPane),
onListDrives: listDrives,
}),
[],
);
const rightCallbacks = useMemo<SftpPaneCallbacks>(
() => ({
onConnect: paneActions.onConnectRight,
onDisconnect: paneActions.onDisconnectRight,
onPrepareSelection: paneActions.onPrepareSelectionRight,
onNavigateTo: paneActions.onNavigateToRight,
onNavigateUp: paneActions.onNavigateUpRight,
onRefresh: paneActions.onRefreshRight,
onRefreshTab: paneActions.onRefreshTabRight,
onSetFilenameEncoding: paneActions.onSetFilenameEncodingRight,
onOpenEntry: fileOps.onOpenEntryRight,
onToggleSelection: paneActions.onToggleSelectionRight,
onRangeSelect: paneActions.onRangeSelectRight,
onClearSelection: paneActions.onClearSelectionRight,
onSetFilter: paneActions.onSetFilterRight,
onCreateDirectory: paneActions.onCreateDirectoryRight,
onCreateDirectoryAtPath: paneActions.onCreateDirectoryAtPathRight,
onCreateFile: paneActions.onCreateFileRight,
onCreateFileAtPath: paneActions.onCreateFileAtPathRight,
onDeleteFiles: paneActions.onDeleteFilesRight,
onDeleteFilesAtPath: paneActions.onDeleteFilesAtPathRight,
onRenameFile: paneActions.onRenameFileRight,
onRenameFileAtPath: paneActions.onRenameFileAtPathRight,
onMoveEntriesToPath: paneActions.onMoveEntriesToPathRight,
onCopyToOtherPane: paneActions.onCopyToOtherPaneRight,
onReceiveFromOtherPane: paneActions.onReceiveFromOtherPaneRight,
onEditPermissions: fileOps.onEditPermissionsRight,
onEditFile: fileOps.onEditFileRight,
onOpenFile: fileOps.onOpenFileRight,
onOpenFileWithSystemDefault: fileOps.onOpenFileWithSystemDefaultRight,
onOpenFileWith: fileOps.onOpenFileWithRight,
onDownloadFile: fileOps.onDownloadFileRight,
onDownloadFiles: fileOps.onDownloadFilesRight,
onExtractArchive: fileOps.onExtractArchiveRight,
onUploadExternalFiles: fileOps.onUploadExternalFilesRight,
onUploadExternalFileList: fileOps.onUploadExternalFileListRight,
onUploadExternalFolder: fileOps.onUploadExternalFolderRight,
onListDirectory: makeListDirectory("right", () => sftpRef.current.rightPane),
onListDrives: listDrives,
}),
[],
);
/* eslint-enable react-hooks/exhaustive-deps */
return {
leftCallbacks,
rightCallbacks,
dragCallbacks: paneActions.dragCallbacks,
draggedFiles: paneActions.draggedFiles,
permissionsState: fileOps.permissionsState,
setPermissionsState: fileOps.setPermissionsState,
showTextEditor: fileOps.showTextEditor,
setShowTextEditor: fileOps.setShowTextEditor,
textEditorTarget: fileOps.textEditorTarget,
setTextEditorTarget: fileOps.setTextEditorTarget,
textEditorContent: fileOps.textEditorContent,
setTextEditorContent: fileOps.setTextEditorContent,
loadingTextContent: fileOps.loadingTextContent,
showFileOpenerDialog: fileOps.showFileOpenerDialog,
setShowFileOpenerDialog: fileOps.setShowFileOpenerDialog,
fileOpenerTarget: fileOps.fileOpenerTarget,
setFileOpenerTarget: fileOps.setFileOpenerTarget,
handleSaveTextFile: fileOps.handleSaveTextFile,
onPromoteToTab: fileOps.onPromoteToTab,
handleFileOpenerSelect: fileOps.handleFileOpenerSelect,
handleSelectSystemApp: fileOps.handleSelectSystemApp,
};
};