feat: rebuild frontend with react

This commit is contained in:
liumangmang
2026-04-22 09:53:06 +08:00
parent 42836aa4c3
commit 423cca97a6
81 changed files with 2907 additions and 10230 deletions
+156
View File
@@ -0,0 +1,156 @@
import type { Connection, SessionTreeLayoutPayload, SessionTreeNodePayload } from '../types'
export interface BuiltTreeNode {
id: string
type: 'folder' | 'connection'
name: string
order: number
parentId: string | null
expanded?: boolean
connection?: Connection
children: BuiltTreeNode[]
}
export function cn(...parts: Array<string | false | null | undefined>) {
return parts.filter(Boolean).join(' ')
}
export function formatBytes(bytes?: number | null) {
if (bytes == null || Number.isNaN(bytes)) return '-'
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}
export function formatTimestamp(timestamp?: number | null) {
if (!timestamp) return '-'
return new Date(timestamp).toLocaleString()
}
export function formatSftpDate(epochSeconds: number) {
return new Date(epochSeconds * 1000).toLocaleString()
}
export function formatSftpPermissions(entry: { directory: boolean }) {
return entry.directory ? 'drwxr-xr-x' : '-rw-r--r--'
}
export function buildSessionTree(layout: SessionTreeLayoutPayload | null, connections: Connection[]): BuiltTreeNode[] {
const connectionMap = new Map(connections.map((item) => [item.id, item]))
const nodes = layout?.nodes ?? []
if (nodes.length === 0) {
return connections
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map((connection, index) => ({
id: `connection-${connection.id}`,
type: 'connection',
name: connection.name,
order: index,
parentId: null,
connection,
children: [],
}))
}
const built = new Map<string, BuiltTreeNode>()
for (const node of nodes) {
built.set(node.id, {
id: node.id,
type: node.type,
name: node.name,
order: node.order,
parentId: node.parentId,
expanded: node.expanded,
connection: node.connectionId ? connectionMap.get(node.connectionId) : undefined,
children: [],
})
}
const roots: BuiltTreeNode[] = []
for (const node of nodes) {
const current = built.get(node.id)
if (!current) continue
if (node.type === 'connection' && node.connectionId && !current.connection) continue
if (node.parentId && built.has(node.parentId)) {
built.get(node.parentId)!.children.push(current)
} else {
roots.push(current)
}
}
const sortNodes = (items: BuiltTreeNode[]) => {
items.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name))
items.forEach((item) => sortNodes(item.children))
}
sortNodes(roots)
return roots
}
export function collectConnectionIds(nodes: BuiltTreeNode[]) {
const ids: number[] = []
const walk = (list: BuiltTreeNode[]) => {
list.forEach((node) => {
if (node.type === 'connection' && node.connection) ids.push(node.connection.id)
if (node.children.length > 0) walk(node.children)
})
}
walk(nodes)
return ids
}
export function updateExpandedState(layout: SessionTreeLayoutPayload | null, nodeId: string): SessionTreeLayoutPayload | null {
if (!layout) return layout
return {
...layout,
nodes: layout.nodes.map((node) =>
node.id === nodeId && node.type === 'folder'
? { ...node, expanded: !node.expanded, updatedAt: Date.now() }
: node,
),
}
}
export function buildDefaultTreeLayout(connections: Connection[]): SessionTreeLayoutPayload {
const now = Date.now()
const groups = new Map<string, Connection[]>()
connections.forEach((connection) => {
const key = connection.name.includes('-') ? connection.name.split('-')[0] : '默认分组'
const group = groups.get(key) ?? []
group.push(connection)
groups.set(key, group)
})
const nodes: SessionTreeNodePayload[] = []
let order = 0
Array.from(groups.entries()).forEach(([groupName, list]) => {
const folderId = `folder-${groupName}-${order}`
nodes.push({
id: folderId,
type: 'folder',
name: groupName,
parentId: null,
order,
expanded: true,
createdAt: now,
updatedAt: now,
})
list
.sort((a, b) => a.name.localeCompare(b.name))
.forEach((connection, index) => {
nodes.push({
id: `connection-${connection.id}`,
type: 'connection',
name: connection.name,
parentId: folderId,
order: index,
connectionId: connection.id,
createdAt: now,
updatedAt: now,
})
})
order += 1
})
return { nodes, sortMode: 'manual' }
}