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
4struct BuildDetailView: View { 4struct BuildDetailView: View {
5 5
6 @State private var model: BuildDetailViewModel 6 @State private var model: BuildDetailViewModel
7 @State private var confirmingCancel = false
7 8
8 init(client: GitbayClient, repo: String, number: Int64) { 9 init(client: GitbayClient, repo: String, number: Int64) {
9 _model = State(initialValue: BuildDetailViewModel( 10 _model = State(initialValue: BuildDetailViewModel(
@@ -16,6 +17,11 @@ struct BuildDetailView: View {
16 Color.clear 17 Color.clear
17 if let detail = model.state.value { 18 if let detail = model.state.value {
18 VStack(spacing: 0) { 19 VStack(spacing: 0) {
20 if let error = model.actionError {
21 GBNotice(error, .gbWarn)
22 .padding(.horizontal, 16)
23 .padding(.vertical, 8)
24 }
19 header(detail.build) 25 header(detail.build)
20 Divider() 26 Divider()
21 log(detail.log) 27 log(detail.log)
@@ -25,8 +31,38 @@ struct BuildDetailView: View {
25 .overlay { LoadStateOverlay(state: model.state) } 31 .overlay { LoadStateOverlay(state: model.state) }
26 .navigationTitle("Build #\(model.number)") 32 .navigationTitle("Build #\(model.number)")
27 .navigationBarTitleDisplayMode(.inline) 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 .task { await model.load() } 48 .task { await model.load() }
29 .refreshable { await model.load() } 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 private func header(_ build: Build) -> some View { 68 private func header(_ build: Build) -> some View {
gitbay/Views/Discovery/ProfileView.swift +33
@@ -4,7 +4,10 @@ import SwiftUI
4/// have been doing, and what they own that you can see. 4/// have been doing, and what they own that you can see.
5struct ProfileView: View { 5struct ProfileView: View {
6 6
7 @Environment(SessionStore.self) private var session
7 @State private var model: ProfileViewModel 8 @State private var model: ProfileViewModel
9 @State private var creatingOrg = false
10 @State private var newOrgName = ""
8 11
9 /// Someone else's profile is titled with their name. Your own is 12 /// Someone else's profile is titled with their name. Your own is
10 /// reached from a tab and titled to match it. 13 /// reached from a tab and titled to match it.
@@ -18,6 +21,11 @@ struct ProfileView: View {
18 var body: some View { 21 var body: some View {
19 List { 22 List {
20 if let profile = model.state.value { 23 if let profile = model.state.value {
24 if let error = model.actionError {
25 Section {
26 GBNotice(error, .gbWarn)
27 }
28 }
21 header(profile) 29 header(profile)
22 peopleSection(profile) 30 peopleSection(profile)
23 aboutSection(profile) 31 aboutSection(profile)
@@ -35,8 +43,33 @@ struct ProfileView: View {
35 .overlay { LoadStateOverlay(state: model.state) } 43 .overlay { LoadStateOverlay(state: model.state) }
36 .navigationTitle(title ?? model.name) 44 .navigationTitle(title ?? model.name)
37 .navigationBarTitleDisplayMode(.inline) 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 .task { await model.load() } 61 .task { await model.load() }
39 .refreshable { await model.load() } 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 // MARK: - Sections 75 // MARK: - Sections
gitbay/Views/Orgs/OrgView.swift +26
@@ -10,6 +10,8 @@ struct OrgView: View {
10 @State private var newTeam = "" 10 @State private var newTeam = ""
11 @State private var removingMember: OrgMember? 11 @State private var removingMember: OrgMember?
12 @State private var deletingTeam: String? 12 @State private var deletingTeam: String?
13 @State private var renamingOrg = false
14 @State private var newOrgName = ""
13 15
14 init(client: GitbayClient, org: String) { 16 init(client: GitbayClient, org: String) {
15 _model = State(initialValue: OrgViewModel(client: client, orgName: org)) 17 _model = State(initialValue: OrgViewModel(client: client, orgName: org))
@@ -30,8 +32,32 @@ struct OrgView: View {
30 .overlay { LoadStateOverlay(state: model.state) } 32 .overlay { LoadStateOverlay(state: model.state) }
31 .navigationTitle(model.orgName) 33 .navigationTitle(model.orgName)
32 .navigationBarTitleDisplayMode(.inline) 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 .task { await model.load() } 47 .task { await model.load() }
34 .refreshable { await model.load() } 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 .confirmationDialog( 61 .confirmationDialog(
36 "Remove \(removingMember?.user ?? "") from \(model.orgName)?", 62 "Remove \(removingMember?.user ?? "") from \(model.orgName)?",
37 isPresented: Binding( 63 isPresented: Binding(
gitbay/Views/Releases/ReleaseView.swift +27
@@ -6,6 +6,8 @@ struct ReleaseView: View {
6 @State private var editing = false 6 @State private var editing = false
7 @State private var draftTitle = "" 7 @State private var draftTitle = ""
8 @State private var draftNotes = "" 8 @State private var draftNotes = ""
9 @State private var confirmingDelete = false
10 @Environment(\.dismiss) private var dismiss
9 11
10 init(client: GitbayClient, repo: String, tag: String) { 12 init(client: GitbayClient, repo: String, tag: String) {
11 _model = State(initialValue: ReleaseDetailViewModel( 13 _model = State(initialValue: ReleaseDetailViewModel(
@@ -89,6 +91,31 @@ struct ReleaseView: View {
89 .accessibilityIdentifier("release-edit-button") 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 .sheet(isPresented: $editing) { 120 .sheet(isPresented: $editing) {
94 ComposeSheet( 121 ComposeSheet(