refactor(servers): replace group rename alert with a sheet

The pre-filled current name in the rename alert only showed on the
first presentation: SwiftUI on macOS recycles the alert panel and its
TextField does not pick up binding changes made in the same update as
re-presenting. Swap the alert for a RenameGroupSheet modeled on
NewGroupSheet, presented via sheet(item:) so content state is rebuilt
each time and the prefill always matches the target group.

As with the creation form, a failed rename now keeps the sheet open
with the entered name instead of closing the alert first and losing
the input. The NodeContext adapter previously used by the move sheet
is shared by both presentations.

Add the manual catalog keys 重命名分组 (Rename Group) and 名称 (Name);
保存/取消 already existed.
This commit is contained in:
Qiu Yuzhou
2026-10-06 20:41:01 +08:00
parent 5e89c021c7
commit c4bbcb022e
3 changed files with 85 additions and 29 deletions
@@ -1100,6 +1100,28 @@
}
}
}
},
"名称": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Name"
}
}
}
},
"重命名分组": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Rename Group"
}
}
}
}
},
"version": "1.0"
@@ -0,0 +1,53 @@
import SwiftUI
/// 重命名分组表单:名称初值取现名;失败保留名称,成功才关闭。
struct RenameGroupSheet: View {
let workflow: CatalogWorkflow
let errors: ErrorAlertPresenter
let nodeID: NodeID
@Environment(\.dismiss) private var dismiss
@State private var name: String
@State private var isSubmitting = false
@FocusState private var nameFocused: Bool
init(workflow: CatalogWorkflow, errors: ErrorAlertPresenter, nodeID: NodeID) {
self.workflow = workflow
self.errors = errors
self.nodeID = nodeID
// 打开时的现名仅作为初值,后续编辑由本表单持有。
_name = State(initialValue: workflow.displayName(for: nodeID))
}
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("重命名分组").font(.title2)
TextField("名称", text: $name)
.focused($nameFocused)
HStack {
Spacer()
Button("取消", role: .cancel) { dismiss() }
Button("保存") { rename() }
.keyboardShortcut(.defaultAction)
}
}
.padding(20)
.frame(width: 360)
.disabled(isSubmitting)
.interactiveDismissDisabled(isSubmitting)
.defaultFocus($nameFocused, true)
}
private func rename() {
guard !isSubmitting else { return }
isSubmitting = true
Task {
defer { isSubmitting = false }
do {
try await workflow.renameGroup(nodeID, to: name)
dismiss()
} catch {
errors.present(error)
}
}
}
}
@@ -26,7 +26,6 @@ struct ServersView: View {
// 纯窗口状态(issue #41):selection/sheet/alert 不进目录 module。
@State private var renameTarget: NodeID?
@State private var renameText = ""
@State private var newGroupParent: NodeID?
@State private var isPresentingNewGroup = false
@State private var serverFormOperation: ServerFormSheet.Operation?
@@ -45,15 +44,12 @@ struct ServersView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(minHeight: 420)
.alert(
"重命名分组",
isPresented: Binding(
get: { renameTarget != nil },
set: { if !$0 { renameTarget = nil } })
) {
TextField("名称", text: $renameText)
Button("确定") { commitRename() }
Button("取消", role: .cancel) { renameTarget = nil }
.sheet(
item: Binding(
get: { renameTarget.map(NodeContext.init) },
set: { renameTarget = $0?.nodeID })
) { context in
RenameGroupSheet(workflow: workflow, errors: errors, nodeID: context.nodeID)
}
.sheet(isPresented: $isPresentingNewGroup) {
NewGroupSheet(
@@ -75,7 +71,7 @@ struct ServersView: View {
}
.sheet(
item: Binding(
get: { moveTarget.map(MoveContext.init) },
get: { moveTarget.map(NodeContext.init) },
set: { moveTarget = $0?.nodeID })
) { context in
MoveNodeSheet(workflow: workflow, errors: errors, nodeID: context.nodeID)
@@ -146,10 +142,7 @@ struct ServersView: View {
activeTargetID: activeTargetID,
errors: errors,
onEdit: presentEditServer,
onRename: { id in
renameTarget = id
renameText = workflow.displayName(for: id)
},
onRename: { renameTarget = $0 },
onNewGroup: { parent in
presentNewGroup(in: parent)
},
@@ -362,18 +355,6 @@ extension ServersView {
}
}
private func commitRename() {
guard let id = renameTarget else { return }
renameTarget = nil
Task {
do {
try await workflow.renameGroup(id, to: renameText)
} catch {
errors.present(error)
}
}
}
/// 删除确认文案:档位与规模事实来自 seam;句子由 UI 拼(Q3-A)。
private var deleteTitle: String {
guard let id = deleteTarget else { return "" }
@@ -413,8 +394,8 @@ extension ServersView {
}
}
/// moveTarget 的 sheet(item:) 适配壳。
private struct MoveContext: Identifiable {
/// moveTarget/renameTarget 的 sheet(item:) 适配壳。
private struct NodeContext: Identifiable {
let nodeID: NodeID
var id: NodeID { nodeID }
}