Please provide the code changes or file diffs you would like me to summarize.
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
export default function RenameFolderModal({
|
||||||
|
open,
|
||||||
|
initialName,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
}: {
|
||||||
|
open: boolean
|
||||||
|
initialName: string
|
||||||
|
onClose: () => void
|
||||||
|
onSubmit: (name: string) => Promise<void>
|
||||||
|
}) {
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
setName(initialName)
|
||||||
|
setSubmitting(false)
|
||||||
|
setError(null)
|
||||||
|
}, [initialName, open])
|
||||||
|
|
||||||
|
if (!open) return null
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
const trimmedName = name.trim()
|
||||||
|
if (!trimmedName) {
|
||||||
|
setError('请输入文件夹名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedName === initialName) {
|
||||||
|
onClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
await onSubmit(trimmedName)
|
||||||
|
onClose()
|
||||||
|
} catch (err) {
|
||||||
|
const message =
|
||||||
|
(err as { response?: { data?: { message?: string; error?: string } } }).response?.data?.message || '重命名文件夹失败'
|
||||||
|
setError(message)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="重命名文件夹"
|
||||||
|
onClose={onClose}
|
||||||
|
maxWidth="max-w-md"
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<button className="rounded-xl bg-slate-700 px-4 py-2 text-sm text-slate-200 transition hover:bg-slate-600" onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="rounded-xl bg-blue-600 px-4 py-2 text-sm text-white transition hover:bg-blue-500 disabled:opacity-60"
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
{submitting ? '保存中...' : '保存'}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-5">
|
||||||
|
<label className="block space-y-2">
|
||||||
|
<span className="text-sm text-slate-300">文件夹名称</span>
|
||||||
|
<input
|
||||||
|
className="w-full rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-white outline-none focus:border-blue-500"
|
||||||
|
placeholder="例如:生产环境"
|
||||||
|
value={name}
|
||||||
|
onChange={(event) => setName(event.target.value)}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
void handleSave()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{error ? <div className="rounded-xl border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-300">{error}</div> : null}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ import BatchCommandModal from '../components/BatchCommandModal'
|
|||||||
import ChangePasswordModal from '../components/ChangePasswordModal'
|
import ChangePasswordModal from '../components/ChangePasswordModal'
|
||||||
import ConnectionModal from '../components/ConnectionModal'
|
import ConnectionModal from '../components/ConnectionModal'
|
||||||
import FolderModal from '../components/FolderModal'
|
import FolderModal from '../components/FolderModal'
|
||||||
|
import RenameFolderModal from '../components/RenameFolderModal'
|
||||||
import SessionTree from '../components/SessionTree'
|
import SessionTree from '../components/SessionTree'
|
||||||
import SettingsModal from '../components/SettingsModal'
|
import SettingsModal from '../components/SettingsModal'
|
||||||
import SftpPane from '../components/SftpPane'
|
import SftpPane from '../components/SftpPane'
|
||||||
@@ -119,6 +120,7 @@ export default function WorkspacePage({
|
|||||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([])
|
const [selectedFiles, setSelectedFiles] = useState<string[]>([])
|
||||||
const [showConnectionModal, setShowConnectionModal] = useState(false)
|
const [showConnectionModal, setShowConnectionModal] = useState(false)
|
||||||
const [showFolderModal, setShowFolderModal] = useState(false)
|
const [showFolderModal, setShowFolderModal] = useState(false)
|
||||||
|
const [renameFolderNodeId, setRenameFolderNodeId] = useState<string | null>(null)
|
||||||
const [editingConnection, setEditingConnection] = useState<Connection | null>(null)
|
const [editingConnection, setEditingConnection] = useState<Connection | null>(null)
|
||||||
const [showBatchModal, setShowBatchModal] = useState(false)
|
const [showBatchModal, setShowBatchModal] = useState(false)
|
||||||
const [showTransferModal, setShowTransferModal] = useState(initialTool === 'transfers')
|
const [showTransferModal, setShowTransferModal] = useState(initialTool === 'transfers')
|
||||||
@@ -366,6 +368,13 @@ export default function WorkspacePage({
|
|||||||
await persistTreeLayout(nextLayout)
|
await persistTreeLayout(nextLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRenameFolderSubmit(newName: string) {
|
||||||
|
if (!renameFolderNodeId) return
|
||||||
|
const nextLayout = renameSessionTreeNode(getCurrentTreeLayout(), renameFolderNodeId, newName)
|
||||||
|
if (!nextLayout) return
|
||||||
|
await persistTreeLayout(nextLayout)
|
||||||
|
}
|
||||||
|
|
||||||
function handleOpenTreeContextMenu(payload: { nodeId: string; nodeType: TreeContextMenuTargetType; x: number; y: number }) {
|
function handleOpenTreeContextMenu(payload: { nodeId: string; nodeType: TreeContextMenuTargetType; x: number; y: number }) {
|
||||||
const position = getClampedContextMenuPosition(payload.x, payload.y, payload.nodeType === 'folder' ? 4 : 2)
|
const position = getClampedContextMenuPosition(payload.x, payload.y, payload.nodeType === 'folder' ? 4 : 2)
|
||||||
setSelectedNodeId(payload.nodeId)
|
setSelectedNodeId(payload.nodeId)
|
||||||
@@ -429,13 +438,7 @@ export default function WorkspacePage({
|
|||||||
const targetNode = findTreeNode(targetId)
|
const targetNode = findTreeNode(targetId)
|
||||||
if (!targetNode || targetNode.type !== 'folder') return
|
if (!targetNode || targetNode.type !== 'folder') return
|
||||||
|
|
||||||
const nextName = window.prompt('重命名文件夹', targetNode.name)
|
setRenameFolderNodeId(targetId)
|
||||||
const trimmedName = nextName?.trim()
|
|
||||||
if (!trimmedName || trimmedName === targetNode.name) return
|
|
||||||
|
|
||||||
const nextLayout = renameSessionTreeNode(getCurrentTreeLayout(), targetId, trimmedName)
|
|
||||||
if (!nextLayout) return
|
|
||||||
await persistTreeLayout(nextLayout)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,15 +585,6 @@ export default function WorkspacePage({
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="relative">
|
|
||||||
<Search size={16} className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
|
|
||||||
<input
|
|
||||||
className="w-52 rounded-full border border-slate-800 bg-slate-950 py-1.5 pl-9 pr-4 text-sm text-slate-200 outline-none focus:border-blue-500"
|
|
||||||
placeholder="搜索主机..."
|
|
||||||
value={search}
|
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
className="rounded-full p-2 text-slate-400 transition hover:bg-slate-800 hover:text-white"
|
className="rounded-full p-2 text-slate-400 transition hover:bg-slate-800 hover:text-white"
|
||||||
onClick={() => setShowSettingsModal(true)}
|
onClick={() => setShowSettingsModal(true)}
|
||||||
@@ -615,7 +609,7 @@ export default function WorkspacePage({
|
|||||||
|
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
<aside className="flex w-72 shrink-0 flex-col border-r border-slate-800 bg-slate-900">
|
<aside className="flex w-72 shrink-0 flex-col border-r border-slate-800 bg-slate-900">
|
||||||
<div className="border-b border-slate-800 px-4 py-3">
|
<div className="flex flex-col gap-3 border-b border-slate-800 px-4 py-3">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="text-sm font-medium text-slate-300">会话管理</div>
|
<div className="text-sm font-medium text-slate-300">会话管理</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -659,6 +653,15 @@ export default function WorkspacePage({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Search size={14} className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-500" />
|
||||||
|
<input
|
||||||
|
className="w-full rounded-md border border-slate-800 bg-slate-950/50 py-1.5 pl-8 pr-3 text-xs text-slate-200 outline-none transition-colors placeholder:text-slate-600 focus:border-slate-700 focus:bg-slate-900"
|
||||||
|
placeholder="搜索主机..."
|
||||||
|
value={search}
|
||||||
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="flex-1 overflow-auto p-2"
|
className="flex-1 overflow-auto p-2"
|
||||||
@@ -844,6 +847,12 @@ export default function WorkspacePage({
|
|||||||
onClose={() => setShowFolderModal(false)}
|
onClose={() => setShowFolderModal(false)}
|
||||||
onSubmit={handleCreateFolder}
|
onSubmit={handleCreateFolder}
|
||||||
/>
|
/>
|
||||||
|
<RenameFolderModal
|
||||||
|
open={!!renameFolderNodeId}
|
||||||
|
initialName={renameFolderNodeId ? findTreeNode(renameFolderNodeId)?.name || '' : ''}
|
||||||
|
onClose={() => setRenameFolderNodeId(null)}
|
||||||
|
onSubmit={handleRenameFolderSubmit}
|
||||||
|
/>
|
||||||
<BatchCommandModal
|
<BatchCommandModal
|
||||||
open={showBatchModal}
|
open={showBatchModal}
|
||||||
connections={connections}
|
connections={connections}
|
||||||
|
|||||||
Reference in New Issue
Block a user