feat: rebuild frontend with react
This commit is contained in:
@@ -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' }
|
||||
}
|
||||
Reference in New Issue
Block a user