Commit 3ee1c43ddb
3ee1c43ddb8a317d1525e9c55dd123736f643826
parent: 0a5413dcd9
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-07 00:28 UTC
Cancel, delete and organisation actions in their screens
Layout: unified · split
gitbay/Views/Builds/BuildDetailView.swift
+36
| @@ -4,6 +4,7 @@ import SwiftUI |
| 4 | 4 | struct BuildDetailView: View { |
| 5 | 5 | |
| 6 | 6 | @State private var model: BuildDetailViewModel |
| 7 | @State private var confirmingCancel = false |
| 7 | 8 | |
| 8 | 9 | init(client: GitbayClient, repo: String, number: Int64) { |
| 9 | 10 | _model = State(initialValue: BuildDetailViewModel( |
| @@ -16,6 +17,11 @@ struct BuildDetailView: View { |
| 16 | 17 | Color.clear |
| 17 | 18 | if let detail = model.state.value { |
| 18 | 19 | VStack(spacing: 0) { |
| 20 | if let error = model.actionError { |
| 21 | GBNotice(error, .gbWarn) |
| 22 | .padding(.horizontal, 16) |
| 23 | .padding(.vertical, 8) |
| 24 | } |
| 19 | 25 | header(detail.build) |
| 20 | 26 | Divider() |
| 21 | 27 | log(detail.log) |
| @@ -25,8 +31,38 @@ struct BuildDetailView: View { |
| 25 | 31 | .overlay { LoadStateOverlay(state: model.state) } |
| 26 | 32 | .navigationTitle("Build #\(model.number)") |
| 27 | 33 | .navigationBarTitleDisplayMode(.inline) |
| 34 | .toolbar { |
| 35 | ToolbarItem(placement: .topBarTrailing) { |
| 36 | if model.isCancellable { |
| 37 | if model.working { |
| 38 | ProgressView() |
| 39 | } else { |
| 40 | Button("Cancel", role: .destructive) { |
| 41 | confirmingCancel = true |
| 42 | } |
| 43 | .accessibilityIdentifier("build-cancel-button") |
| 44 | } |
| 45 | } |
| 46 | } |
| 47 | } |
| 28 | 48 | .task { await model.load() } |
| 29 | 49 | .refreshable { await model.load() } |
| 50 | .confirmationDialog( |
| 51 | "Cancel this build?", |
| 52 | isPresented: $confirmingCancel, |
| 53 | titleVisibility: .visible |
| 54 | ) { |
| 55 | Button("Cancel Build", role: .destructive) { |
| 56 | Task { await model.cancel() } |
| 57 | } |
| 58 | Button("Keep Running", role: .cancel) {} |
| 59 | } message: { |
| 60 | Text( |
| 61 | "A running step ends within a couple of seconds. If this is a " + |
| 62 | "duplicate of a commit that already passed, that earlier result " + |
| 63 | "is restored on the commit." |
| 64 | ) |
| 65 | } |
| 30 | 66 | } |
| 31 | 67 | |
| 32 | 68 | private func header(_ build: Build) -> some View { |
gitbay/Views/Discovery/ProfileView.swift
+33
| @@ -4,7 +4,10 @@ import SwiftUI |
| 4 | 4 | /// have been doing, and what they own that you can see. |
| 5 | 5 | struct ProfileView: View { |
| 6 | 6 | |
| 7 | @Environment(SessionStore.self) private var session |
| 7 | 8 | @State private var model: ProfileViewModel |
| 9 | @State private var creatingOrg = false |
| 10 | @State private var newOrgName = "" |
| 8 | 11 | |
| 9 | 12 | /// Someone else's profile is titled with their name. Your own is |
| 10 | 13 | /// reached from a tab and titled to match it. |
| @@ -18,6 +21,11 @@ struct ProfileView: View { |
| 18 | 21 | var body: some View { |
| 19 | 22 | List { |
| 20 | 23 | if let profile = model.state.value { |
| 24 | if let error = model.actionError { |
| 25 | Section { |
| 26 | GBNotice(error, .gbWarn) |
| 27 | } |
| 28 | } |
| 21 | 29 | header(profile) |
| 22 | 30 | peopleSection(profile) |
| 23 | 31 | aboutSection(profile) |
| @@ -35,8 +43,33 @@ struct ProfileView: View { |
| 35 | 43 | .overlay { LoadStateOverlay(state: model.state) } |
| 36 | 44 | .navigationTitle(title ?? model.name) |
| 37 | 45 | .navigationBarTitleDisplayMode(.inline) |
| 46 | .toolbar { |
| 47 | if let profile = model.state.value, |
| 48 | !profile.isOrg, session.current?.username == profile.name { |
| 49 | ToolbarItem(placement: .topBarTrailing) { |
| 50 | Button { |
| 51 | newOrgName = "" |
| 52 | creatingOrg = true |
| 53 | } label: { |
| 54 | Image(systemName: "plus.circle") |
| 55 | } |
| 56 | .disabled(model.working) |
| 57 | .accessibilityIdentifier("org-create-button") |
| 58 | } |
| 59 | } |
| 60 | } |
| 38 | 61 | .task { await model.load() } |
| 39 | 62 | .refreshable { await model.load() } |
| 63 | .alert("New Organization", isPresented: $creatingOrg) { |
| 64 | TextField("Name", text: $newOrgName) |
| 65 | .autocorrectionDisabled() |
| 66 | .textInputAutocapitalization(.never) |
| 67 | Button("Create") { |
| 68 | Task { await model.createOrg(newOrgName) } |
| 69 | } |
| 70 | .disabled(newOrgName.trimmingCharacters(in: .whitespaces).isEmpty) |
| 71 | Button("Cancel", role: .cancel) {} |
| 72 | } |
| 40 | 73 | } |
| 41 | 74 | |
| 42 | 75 | // MARK: - Sections |
gitbay/Views/Orgs/OrgView.swift
+26
| @@ -10,6 +10,8 @@ struct OrgView: View { |
| 10 | 10 | @State private var newTeam = "" |
| 11 | 11 | @State private var removingMember: OrgMember? |
| 12 | 12 | @State private var deletingTeam: String? |
| 13 | @State private var renamingOrg = false |
| 14 | @State private var newOrgName = "" |
| 13 | 15 | |
| 14 | 16 | init(client: GitbayClient, org: String) { |
| 15 | 17 | _model = State(initialValue: OrgViewModel(client: client, orgName: org)) |
| @@ -30,8 +32,32 @@ struct OrgView: View { |
| 30 | 32 | .overlay { LoadStateOverlay(state: model.state) } |
| 31 | 33 | .navigationTitle(model.orgName) |
| 32 | 34 | .navigationBarTitleDisplayMode(.inline) |
| 35 | .toolbar { |
| 36 | ToolbarItem(placement: .topBarTrailing) { |
| 37 | Button { |
| 38 | newOrgName = model.orgName |
| 39 | renamingOrg = true |
| 40 | } label: { |
| 41 | Image(systemName: "pencil") |
| 42 | } |
| 43 | .disabled(model.working) |
| 44 | .accessibilityIdentifier("org-rename-button") |
| 45 | } |
| 46 | } |
| 33 | 47 | .task { await model.load() } |
| 34 | 48 | .refreshable { await model.load() } |
| 49 | .alert("Rename \(model.orgName)", isPresented: $renamingOrg) { |
| 50 | TextField("New name", text: $newOrgName) |
| 51 | .autocorrectionDisabled() |
| 52 | .textInputAutocapitalization(.never) |
| 53 | Button("Rename") { |
| 54 | Task { await model.rename(to: newOrgName) } |
| 55 | } |
| 56 | .disabled(newOrgName.trimmingCharacters(in: .whitespaces).isEmpty) |
| 57 | Button("Cancel", role: .cancel) {} |
| 58 | } message: { |
| 59 | Text("Clone URLs for every repository in this organization change to the new name.") |
| 60 | } |
| 35 | 61 | .confirmationDialog( |
| 36 | 62 | "Remove \(removingMember?.user ?? "") from \(model.orgName)?", |
| 37 | 63 | isPresented: Binding( |
gitbay/Views/Releases/ReleaseView.swift
+27
| @@ -6,6 +6,8 @@ struct ReleaseView: View { |
| 6 | 6 | @State private var editing = false |
| 7 | 7 | @State private var draftTitle = "" |
| 8 | 8 | @State private var draftNotes = "" |
| 9 | @State private var confirmingDelete = false |
| 10 | @Environment(\.dismiss) private var dismiss |
| 9 | 11 | |
| 10 | 12 | init(client: GitbayClient, repo: String, tag: String) { |
| 11 | 13 | _model = State(initialValue: ReleaseDetailViewModel( |
| @@ -89,6 +91,31 @@ struct ReleaseView: View { |
| 89 | 91 | .accessibilityIdentifier("release-edit-button") |
| 90 | 92 | } |
| 91 | 93 | } |
| 94 | ToolbarItem(placement: .topBarTrailing) { |
| 95 | if model.state.value != nil { |
| 96 | Button("Delete", role: .destructive) { |
| 97 | confirmingDelete = true |
| 98 | } |
| 99 | .disabled(model.working) |
| 100 | .accessibilityIdentifier("release-delete-button") |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | .confirmationDialog( |
| 105 | "Delete \(model.tag)?", |
| 106 | isPresented: $confirmingDelete, |
| 107 | titleVisibility: .visible |
| 108 | ) { |
| 109 | Button("Delete", role: .destructive) { |
| 110 | Task { |
| 111 | if await model.delete() { |
| 112 | dismiss() |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | Button("Cancel", role: .cancel) {} |
| 117 | } message: { |
| 118 | Text("This deletes the release's assets along with it.") |
| 92 | 119 | } |
| 93 | 120 | .sheet(isPresented: $editing) { |
| 94 | 121 | ComposeSheet( |