/** * useSftpFileOperations - Shared file operations for SFTP components * * This hook provides common file operations like open, edit, preview * that can be shared between SFTPModal and SftpView components. */ import { useCallback, useState } from "react"; import { getFileExtension, isTextFile, FileOpenerType } from "../../lib/sftpFileUtils"; import { toast } from "../../components/ui/toast"; import { useI18n } from "../i18n/I18nProvider"; import { useSftpFileAssociations } from "./useSftpFileAssociations"; export interface FileOperationsState { // Text editor state showTextEditor: boolean; textEditorTarget: { name: string; fullPath: string } | null; textEditorContent: string; loadingTextContent: boolean; // File opener dialog state showFileOpenerDialog: boolean; fileOpenerTarget: { name: string; fullPath: string } | null; } export interface FileOperationsActions { // Open file based on type/association openFile: (fileName: string, fullPath: string) => void; // Edit text file editFile: ( fileName: string, fullPath: string, readContent: () => Promise ) => Promise; // Save text file saveTextFile: ( content: string, writeContent: (path: string, content: string) => Promise ) => Promise; // Handle file opener selection handleFileOpenerSelect: ( openerType: FileOpenerType, setAsDefault: boolean, readTextContent: () => Promise, readImageData: () => Promise ) => Promise; // Close modals closeTextEditor: () => void; closeFileOpenerDialog: () => void; // Check if file can be edited canEditFile: (fileName: string) => boolean; } export interface UseSftpFileOperationsResult { state: FileOperationsState; actions: FileOperationsActions; } export function useSftpFileOperations(): UseSftpFileOperationsResult { const { t } = useI18n(); const { getOpenerForFile, setOpenerForExtension } = useSftpFileAssociations(); // Text editor state const [showTextEditor, setShowTextEditor] = useState(false); const [textEditorTarget, setTextEditorTarget] = useState<{ name: string; fullPath: string } | null>(null); const [textEditorContent, setTextEditorContent] = useState(""); const [loadingTextContent, setLoadingTextContent] = useState(false); // File opener dialog state const [showFileOpenerDialog, setShowFileOpenerDialog] = useState(false); const [fileOpenerTarget, setFileOpenerTarget] = useState<{ name: string; fullPath: string } | null>(null); const canEditFile = useCallback((fileName: string) => { return isTextFile(fileName); }, []); const closeTextEditor = useCallback(() => { setShowTextEditor(false); setTextEditorTarget(null); setTextEditorContent(""); }, []); const closeFileOpenerDialog = useCallback(() => { setShowFileOpenerDialog(false); setFileOpenerTarget(null); }, []); const editFile = useCallback(async ( fileName: string, fullPath: string, readContent: () => Promise ) => { try { setLoadingTextContent(true); setTextEditorTarget({ name: fileName, fullPath }); const content = await readContent(); setTextEditorContent(content); setShowTextEditor(true); } catch (e) { toast.error( e instanceof Error ? e.message : t("sftp.error.loadFailed"), "SFTP", ); } finally { setLoadingTextContent(false); } }, [t]); const saveTextFile = useCallback(async ( content: string, writeContent: (path: string, content: string) => Promise ) => { if (!textEditorTarget) return; await writeContent(textEditorTarget.fullPath, content); }, [textEditorTarget]); const openFile = useCallback((fileName: string, fullPath: string) => { const savedOpener = getOpenerForFile(fileName); if (savedOpener) { // User has saved an opener for this file type // We'll just set the target and let the caller handle it setFileOpenerTarget({ name: fileName, fullPath }); // Return the opener type so caller knows which operation to perform if (savedOpener === 'builtin-editor' && canEditFile(fileName)) { // Don't show dialog, caller should call editFile return 'edit' as const; } } // No saved opener, show the dialog setFileOpenerTarget({ name: fileName, fullPath }); setShowFileOpenerDialog(true); return 'dialog' as const; }, [getOpenerForFile, canEditFile]); const handleFileOpenerSelect = useCallback(async ( openerType: FileOpenerType, setAsDefault: boolean, readTextContent: () => Promise, _readImageData: () => Promise ) => { if (!fileOpenerTarget) return; if (setAsDefault) { const ext = getFileExtension(fileOpenerTarget.name); if (ext !== 'file') { setOpenerForExtension(ext, openerType); } } setShowFileOpenerDialog(false); if (openerType === 'builtin-editor') { await editFile(fileOpenerTarget.name, fileOpenerTarget.fullPath, readTextContent); } }, [fileOpenerTarget, setOpenerForExtension, editFile]); return { state: { showTextEditor, textEditorTarget, textEditorContent, loadingTextContent, showFileOpenerDialog, fileOpenerTarget, }, actions: { openFile, editFile, saveTextFile, handleFileOpenerSelect, closeTextEditor, closeFileOpenerDialog, canEditFile, }, }; }