/** * SettingsFileAssociationsTab - Manage SFTP file opener associations and behavior */ import { FileType, Pencil, Trash2 } from "lucide-react"; import React, { useCallback, useMemo, useState } from "react"; import { useI18n } from "../../../application/i18n/I18nProvider"; import { useSftpFileAssociations } from "../../../application/state/useSftpFileAssociations"; import { useSettingsState } from "../../../application/state/useSettingsState"; import type { FileOpenerType, SystemAppInfo } from "../../../lib/sftpFileUtils"; import { netcattyBridge } from "../../../infrastructure/services/netcattyBridge"; import { Button } from "../../ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip"; import { SectionHeader, SettingCard, SettingsTabContent, SettingRow, Toggle, Select, } from "../settings-ui"; const getOpenerLabel = ( openerType: FileOpenerType, systemApp: SystemAppInfo | undefined, t: (key: string) => string ): string => { if (openerType === 'builtin-editor') { return t('sftp.opener.builtInEditor'); } else if (openerType === 'system-app' && systemApp) { return systemApp.name; } return openerType; }; export default function SettingsFileAssociationsTab() { const { t } = useI18n(); const { getAllAssociations, removeAssociation, setOpenerForExtension, getDefaultOpener, setDefaultOpener, removeDefaultOpener } = useSftpFileAssociations(); const { sftpDoubleClickBehavior, setSftpDoubleClickBehavior, sftpAutoSync, setSftpAutoSync, sftpShowHiddenFiles, setSftpShowHiddenFiles, sftpUseCompressedUpload, setSftpUseCompressedUpload, sftpSkipUnchanged, setSftpSkipUnchanged, sftpAutoOpenSidebar, setSftpAutoOpenSidebar, sftpFollowTerminalCwd, setSftpFollowTerminalCwd, sftpDefaultViewMode, setSftpDefaultViewMode, sftpTransferConcurrency, setSftpTransferConcurrency, sshTransportIdleTtlMs, setSshTransportIdleTtlMs, } = useSettingsState(); const associations = getAllAssociations(); const defaultOpener = getDefaultOpener(); const [editingExtension, setEditingExtension] = useState(null); const [isSelectingDefaultApp, setIsSelectingDefaultApp] = useState(false); const defaultOpenerValue = useMemo(() => { if (!defaultOpener) return 'ask'; if (defaultOpener.openerType === 'builtin-editor') return 'builtin-editor'; return 'system-app'; }, [defaultOpener]); const handleRemove = useCallback((extension: string) => { if (confirm(t('settings.sftpFileAssociations.removeConfirm', { ext: extension === 'file' ? t('sftp.opener.noExtension') : extension }))) { removeAssociation(extension); } }, [removeAssociation, t]); const handleSelectDefaultSystemApp = useCallback(async () => { setIsSelectingDefaultApp(true); try { const bridge = netcattyBridge.get(); if (!bridge?.selectApplication) return; const result = await bridge.selectApplication(); if (result) { setDefaultOpener('system-app', { path: result.path, name: result.name }); } } catch (e) { console.error('Failed to select application:', e); } finally { setIsSelectingDefaultApp(false); } }, [setDefaultOpener]); const handleDefaultOpenerChange = useCallback((value: string) => { if (value === 'ask') { removeDefaultOpener(); return; } if (value === 'builtin-editor') { setDefaultOpener('builtin-editor'); return; } void handleSelectDefaultSystemApp(); }, [handleSelectDefaultSystemApp, removeDefaultOpener, setDefaultOpener]); const handleEdit = useCallback(async (extension: string) => { setEditingExtension(extension); try { const bridge = netcattyBridge.get(); if (!bridge?.selectApplication) { return; } const result = await bridge.selectApplication(); if (result) { setOpenerForExtension(extension, 'system-app', { path: result.path, name: result.name }); } } catch (e) { console.error('Failed to select application:', e); } finally { setEditingExtension(null); } }, [setOpenerForExtension]); return ( setSftpDefaultViewMode(value as 'list' | 'tree')} className="w-48" />
setSftpTransferConcurrency(Number(e.target.value))} className="w-40 accent-primary" /> {sftpTransferConcurrency}
{defaultOpener?.openerType === 'system-app' && ( )}

{t('settings.sftpFileAssociations.desc')}

{associations.length === 0 ? (

{t('settings.sftpFileAssociations.noAssociations')}

) : (
{associations.map(({ extension, openerType, systemApp }) => ( ))}
{t('settings.sftpFileAssociations.extension')} {t('settings.sftpFileAssociations.application')} {/* Actions */}
{extension === 'file' ? t('sftp.opener.noExtension') : `.${extension}`} {openerType === 'system-app' && systemApp ? ( {systemApp.name} {systemApp.path} ) : ( getOpenerLabel(openerType, systemApp, t) )} {t('common.edit')} {t('settings.sftpFileAssociations.remove')}
)}
); }