Files
ShadowsocksX-NG/ShadowsocksX-NG2/App/SidebarRow.swift
T
Qiu Yuzhou 139944dba2 feat(ng2): restyle server detail and catalog rows (#55)
ServersView 扁平双栏承接目录与详情;ServerDetailView 重排为详情头(图标 +
名称 + 来源说明,活动目标由父视图传入)+ 两列 Grid 表单 + 底部取消/保存,
插件区拆 ServerPluginSection、分享/二维码拆 +Share 扩展;SidebarRow 分组行
加「手动/订阅分组 · N 项」副题;GroupDetailView 加同款详情头。行为语义不变。

地图 #52 · 票 #55;task build + test 全绿。
2026-09-24 19:32:24 +08:00

88 lines
2.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
/// 侧栏树行(issue #41):来源标识、有效性提示、活动目标标记与右键菜单。
/// 数据来自目录工作流 module 的树 projection;激活经 seam 的 typed command,
/// 活动目标标记由父视图传入(运行时事实,不进目录 projection)。删除一律
/// 交父视图确认弹窗(空手动组单次、非空二次,CONTEXT.md)。
struct SidebarRow: View {
let node: CatalogTreeNode
let workflow: CatalogWorkflow
let activeTargetID: NodeID?
let errors: ErrorAlertPresenter
let onRename: (NodeID) -> Void
let onNewGroup: (NodeID?) -> Void
let onMove: (NodeID) -> Void
let onDelete: (NodeID) -> Void
private var isSubscription: Bool { node.source == .subscription }
var body: some View {
HStack(spacing: 6) {
Image(systemName: node.isGroup ? "folder" : "server.rack")
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 1) {
Text(node.name)
.foregroundStyle(node.isInvalid ? .secondary : .primary)
if node.isGroup {
// 分组行尾数量说明(票 #55):原型「手动分组 · N 项」口径。
Text("\(node.isManual ? "手动分组" : "订阅分组") · \(node.childCount) 项")
.font(.caption)
.foregroundStyle(.tertiary)
}
}
if node.isInvalid {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.help(
node.invalidReasons.first.map {
AppPresentation.message(
for: ActivationFailure.invalidLeaf(node: node.id, reason: $0))
} ?? "服务器存在已知阻塞问题")
} else if node.invalidDescendantCount > 0 {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(.orange)
.help("包含 \(node.invalidDescendantCount) 个存在已知阻塞问题的服务器")
}
if isSubscription {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundStyle(.tertiary)
.help("订阅节点:由远端管理")
}
if activeTargetID == node.id {
Image(systemName: "bolt.fill")
.foregroundStyle(.orange)
.help("活动目标")
}
}
.contextMenu { contextMenu }
}
@ViewBuilder
private var contextMenu: some View {
Button("激活") {
Task {
do {
_ = try await workflow.activate(node.id)
} catch {
errors.present(error)
}
}
}
if !isSubscription {
Divider()
if node.isGroup {
Button("重命名…") { onRename(node.id) }
}
Button("新建分组…") {
onNewGroup(node.isGroup ? node.id : node.parentID)
}
Divider()
Button("移动到…") { onMove(node.id) }
Divider()
Button("删除…", role: .destructive) {
onDelete(node.id)
}
}
}
}