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) { 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(timestampMs?: number | null) { if (timestampMs == null || Number.isNaN(timestampMs)) return '-' const date = new Date(timestampMs) if (Number.isNaN(date.getTime())) return '-' const year = String(date.getFullYear()) const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } 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() 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() 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' } }