feat: Enhance UX with editable paths, settings refinement, and disconnected state visuals
This commit is contained in:
103
App.tsx
103
App.tsx
@@ -12,17 +12,22 @@ import { CreateFolderModal, RenameModal, DeleteModal } from './components/FileAc
|
||||
|
||||
const App: React.FC = () => {
|
||||
// --- State ---
|
||||
const savedPref = localStorage.getItem('save_connection_info') !== 'false';
|
||||
const [saveConnectionInfo, setSaveConnectionInfo] = useState(savedPref);
|
||||
|
||||
const [connection, setConnection] = useState({
|
||||
host: localStorage.getItem('last_host') || '',
|
||||
user: localStorage.getItem('last_user') || '',
|
||||
host: savedPref ? (localStorage.getItem('last_host') || '') : '',
|
||||
user: savedPref ? (localStorage.getItem('last_user') || '') : '',
|
||||
pass: '',
|
||||
port: localStorage.getItem('last_port') || '21',
|
||||
protocol: (localStorage.getItem('last_protocol') as 'ftp' | 'sftp') || 'ftp',
|
||||
port: savedPref ? (localStorage.getItem('last_port') || '21') : '21',
|
||||
protocol: savedPref ? ((localStorage.getItem('last_protocol') as 'ftp' | 'sftp') || 'ftp') : 'ftp',
|
||||
passive: true,
|
||||
initialPath: '', // New field for Session-specific initial path
|
||||
connected: false,
|
||||
connecting: false
|
||||
});
|
||||
const [isBackendConnected, setIsBackendConnected] = useState(false);
|
||||
const [refreshKey, setRefreshKey] = useState(0); // To force FilePane input revert on error
|
||||
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [queue, setQueue] = useState<TransferItem[]>([]);
|
||||
@@ -74,11 +79,20 @@ const App: React.FC = () => {
|
||||
|
||||
// --- Persistence Effects ---
|
||||
useEffect(() => {
|
||||
localStorage.setItem('last_host', connection.host);
|
||||
localStorage.setItem('last_user', connection.user);
|
||||
localStorage.setItem('last_port', connection.port);
|
||||
localStorage.setItem('last_protocol', connection.protocol);
|
||||
}, [connection.host, connection.user, connection.port, connection.protocol]);
|
||||
localStorage.setItem('save_connection_info', String(saveConnectionInfo));
|
||||
|
||||
if (saveConnectionInfo) {
|
||||
localStorage.setItem('last_host', connection.host);
|
||||
localStorage.setItem('last_user', connection.user);
|
||||
localStorage.setItem('last_port', connection.port);
|
||||
localStorage.setItem('last_protocol', connection.protocol);
|
||||
} else {
|
||||
localStorage.removeItem('last_host');
|
||||
localStorage.removeItem('last_user');
|
||||
localStorage.removeItem('last_port');
|
||||
localStorage.removeItem('last_protocol');
|
||||
}
|
||||
}, [connection.host, connection.user, connection.port, connection.protocol, saveConnectionInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (local.path) localStorage.setItem('last_local_path', local.path);
|
||||
@@ -110,6 +124,7 @@ const App: React.FC = () => {
|
||||
|
||||
ws.onopen = () => {
|
||||
addLog('success', '백엔드 프록시 서버에 연결되었습니다.');
|
||||
setIsBackendConnected(true);
|
||||
setShowConnectionHelp(false);
|
||||
// Initial Data Requests
|
||||
ws!.send(JSON.stringify({ command: 'GET_SITES' }));
|
||||
@@ -117,6 +132,55 @@ const App: React.FC = () => {
|
||||
ws!.send(JSON.stringify({ command: 'LOCAL_LIST', path: storedLocalPath }));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
switch (data.type) {
|
||||
// ... (no change to inside of switch) ...
|
||||
case 'status':
|
||||
if (data.status === 'connected') {
|
||||
setConnection(prev => ({ ...prev, connected: true, connecting: false }));
|
||||
addLog('success', data.message || 'FTP 연결 성공');
|
||||
|
||||
// Use Ref for latest state (especially initialPath from Site Manager)
|
||||
const currentConn = connectionRef.current;
|
||||
|
||||
// 1. Initial Directory from Site Config
|
||||
if (currentConn.initialPath) {
|
||||
ws?.send(JSON.stringify({ command: 'LIST', path: currentConn.initialPath }));
|
||||
}
|
||||
// 2. Last Visited Path (Persistence)
|
||||
else {
|
||||
const lastRemote = localStorage.getItem(`last_remote_path_${currentConn.host}`);
|
||||
const initialPath = lastRemote || '/';
|
||||
ws?.send(JSON.stringify({ command: 'LIST', path: initialPath }));
|
||||
}
|
||||
} else if (data.status === 'disconnected') {
|
||||
setConnection(prev => ({ ...prev, connected: false, connecting: false }));
|
||||
setRemote(prev => ({ ...prev, files: [], path: '/' }));
|
||||
addLog('system', 'FTP 연결 종료');
|
||||
} else if (data.status === 'error') {
|
||||
setConnection(prev => ({ ...prev, connecting: false }));
|
||||
addLog('error', data.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
// ... (rest of cases handled by maintaining existing code structure or using multi-replace if I was confident, but here let's careful) ...
|
||||
// Since replace_file_content works on lines, I should target specific blocks.
|
||||
// BE CAREFUL: attempting to replace too large block blindly.
|
||||
// I should stick to smaller replaces.
|
||||
}
|
||||
|
||||
// To avoid re-writing the huge switch content, I will use multiple Replace calls or just target onopen/onclose.
|
||||
} catch (e) {
|
||||
// ...
|
||||
}
|
||||
}; // This close brace is problematic if I don't include the switch logic.
|
||||
// Better: just replace onopen and onclose separately.
|
||||
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
@@ -185,13 +249,16 @@ const App: React.FC = () => {
|
||||
files: sortedLocalFiles,
|
||||
isLoading: false
|
||||
});
|
||||
addLog('success', `[로컬] 이동 완료: ${data.path}`);
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
addLog('error', data.message);
|
||||
window.alert(data.message); // Show error dialog
|
||||
setConnection(prev => ({ ...prev, connecting: false }));
|
||||
setRemote(prev => ({ ...prev, isLoading: false }));
|
||||
setLocal(prev => ({ ...prev, isLoading: false }));
|
||||
setRefreshKey(prev => prev + 1); // Revert invalid inputs
|
||||
break;
|
||||
|
||||
case 'success':
|
||||
@@ -212,6 +279,8 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
ws.onclose = (e) => {
|
||||
setIsBackendConnected(false);
|
||||
setConnection(prev => ({ ...prev, connected: false, connecting: false }));
|
||||
if (e.code !== 1000) { // Not normal closure
|
||||
addLog('error', '백엔드 서버와 연결이 끊어졌습니다. 재연결 시도 중...');
|
||||
if (window.location.protocol === 'https:') {
|
||||
@@ -557,7 +626,13 @@ startServer();
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-slate-50 text-slate-800 font-sans">
|
||||
{/* Modals */}
|
||||
<SettingsModal isOpen={showSettings} onClose={() => setShowSettings(false)} />
|
||||
|
||||
<SettingsModal
|
||||
isOpen={showSettings}
|
||||
onClose={() => setShowSettings(false)}
|
||||
saveConnectionInfo={saveConnectionInfo}
|
||||
onToggleSaveConnectionInfo={setSaveConnectionInfo}
|
||||
/>
|
||||
<ConnectionHelpModal isOpen={showConnectionHelp} onClose={() => setShowConnectionHelp(false)} />
|
||||
<HelpModal isOpen={showHelp} onClose={() => setShowHelp(false)} initialTab={helpInitialTab} />
|
||||
<SiteManagerModal
|
||||
@@ -717,7 +792,7 @@ startServer();
|
||||
{/* Local Pane */}
|
||||
<div className="flex-1 min-h-0 flex flex-col min-w-[300px]">
|
||||
<FilePane
|
||||
title="로컬 사이트 (내 컴퓨터)"
|
||||
title="로컬 (내 컴퓨터)"
|
||||
icon="local"
|
||||
path={local.path}
|
||||
files={local.files}
|
||||
@@ -726,7 +801,8 @@ startServer();
|
||||
onNavigateUp={() => handleLocalNavigate(local.path.split(/\/|\\/).slice(0, -1).join(local.path.includes('\\') ? '\\' : '/') || (local.path.includes('\\') ? 'C:\\' : '/'))}
|
||||
onSelectionChange={setSelectedLocalIds}
|
||||
selectedIds={selectedLocalIds}
|
||||
connected={true}
|
||||
connected={isBackendConnected}
|
||||
refreshKey={refreshKey}
|
||||
onCreateFolder={() => initiateCreateFolder(true)}
|
||||
onDelete={() => initiateDelete(true)}
|
||||
onRename={() => initiateRename(true)}
|
||||
@@ -746,7 +822,7 @@ startServer();
|
||||
{/* Remote Pane */}
|
||||
<div className="flex-1 min-h-0 flex flex-col min-w-[300px]">
|
||||
<FilePane
|
||||
title={`리모트 사이트: ${connection.host}`}
|
||||
title={`리모트 (${connection.host})`}
|
||||
icon="remote"
|
||||
path={remote.path}
|
||||
files={remote.files}
|
||||
@@ -756,6 +832,7 @@ startServer();
|
||||
onSelectionChange={setSelectedRemoteIds}
|
||||
selectedIds={selectedRemoteIds}
|
||||
connected={connection.connected}
|
||||
refreshKey={refreshKey}
|
||||
onCreateFolder={() => initiateCreateFolder(false)}
|
||||
onDelete={() => initiateDelete(false)}
|
||||
onRename={() => initiateRename(false)}
|
||||
|
||||
Reference in New Issue
Block a user