| @@ -17,6 +17,13 @@ struct RepoSettingsView: View { |
| 17 | /// Set once a rename succeeds: this screen's repository is gone and |
17 | /// Set once a rename succeeds: this screen's repository is gone and |
| 18 | /// the renamed one is a value push away. |
18 | /// the renamed one is a value push away. |
| 19 | @State private var renamedTo: String? |
19 | @State private var renamedTo: String? |
| |
20 | @State private var transferring = false |
| |
21 | @State private var newOwner = "" |
| |
22 | @State private var transferConfirm = "" |
| |
23 | @State private var deleting = false |
| |
24 | @State private var deleteConfirm = "" |
| |
25 | /// Set once the repository is deleted: nothing on this screen applies. |
| |
26 | @State private var deleted = false |
| 20 | @State private var loadedOnce = false |
27 | @State private var loadedOnce = false |
| 21 | @State private var removingTopic: String? |
28 | @State private var removingTopic: String? |
| 22 | @State private var newGrantUser = "" |
29 | @State private var newGrantUser = "" |
| @@ -37,12 +44,21 @@ struct RepoSettingsView: View { |
| 37 | } |
44 | } |
| 38 | .accessibilityIdentifier("settings-renamed-link") |
45 | .accessibilityIdentifier("settings-renamed-link") |
| 39 | } header: { |
46 | } header: { |
| 40 | Text("Renamed") |
47 | Text("Moved") |
| 41 | } footer: { |
48 | } footer: { |
| 42 | Text("This screen's repository moved; nothing here applies to it any more.") |
49 | Text("This screen's repository moved; nothing here applies to it any more.") |
| 43 | } |
50 | } |
| 44 | } |
51 | } |
| 45 | if let loaded = model.state.value { |
52 | if deleted { |
| |
53 | Section { |
| |
54 | Label("\(model.repoPath) is deleted", systemImage: "trash") |
| |
55 | .font(.gbSans(.subheadline)) |
| |
56 | .accessibilityIdentifier("settings-deleted") |
| |
57 | } footer: { |
| |
58 | Text("Its issues, merge requests, releases and builds went with it.") |
| |
59 | } |
| |
60 | } |
| |
61 | if !deleted, renamedTo == nil, let loaded = model.state.value { |
| 46 | if let error = model.actionError { |
62 | if let error = model.actionError { |
| 47 | Section { |
63 | Section { |
| 48 | GBNotice(error, .gbWarn) |
64 | GBNotice(error, .gbWarn) |
| @@ -60,6 +76,8 @@ struct RepoSettingsView: View { |
| 60 | webhooksSection |
76 | webhooksSection |
| 61 | depsSection() |
77 | depsSection() |
| 62 | renameSection |
78 | renameSection |
| |
79 | transferSection |
| |
80 | deleteSection |
| 63 | } |
81 | } |
| 64 | } |
82 | } |
| 65 | .overlay { LoadStateOverlay(state: model.state) } |
83 | .overlay { LoadStateOverlay(state: model.state) } |
| @@ -117,6 +135,40 @@ struct RepoSettingsView: View { |
| 117 | } message: { |
135 | } message: { |
| 118 | Text("Clone URLs change. The old path stops resolving. Type \(model.repoPath) to confirm.") |
136 | Text("Clone URLs change. The old path stops resolving. Type \(model.repoPath) to confirm.") |
| 119 | } |
137 | } |
| |
138 | .alert("Transfer \(model.repoPath)", isPresented: $transferring) { |
| |
139 | TextField("New owner", text: $newOwner) |
| |
140 | .autocorrectionDisabled() |
| |
141 | .textInputAutocapitalization(.never) |
| |
142 | TextField("type \(model.repoPath) to confirm", text: $transferConfirm) |
| |
143 | .autocorrectionDisabled() |
| |
144 | .textInputAutocapitalization(.never) |
| |
145 | Button("Transfer") { |
| |
146 | Task { |
| |
147 | if let path = await model.transfer(to: newOwner) { |
| |
148 | renamedTo = path |
| |
149 | } |
| |
150 | } |
| |
151 | } |
| |
152 | .disabled(newOwner.trimmingCharacters(in: .whitespaces).isEmpty |
| |
153 | || transferConfirm != model.repoPath) |
| |
154 | Button("Cancel", role: .cancel) {} |
| |
155 | } message: { |
| |
156 | Text("The repository moves to another account or organization, and its clone URLs change. Type \(model.repoPath) to confirm.") |
| |
157 | } |
| |
158 | .alert("Delete \(model.repoPath)", isPresented: $deleting) { |
| |
159 | TextField("type \(model.repoPath) to confirm", text: $deleteConfirm) |
| |
160 | .autocorrectionDisabled() |
| |
161 | .textInputAutocapitalization(.never) |
| |
162 | Button("Delete", role: .destructive) { |
| |
163 | Task { |
| |
164 | if await model.delete() { deleted = true } |
| |
165 | } |
| |
166 | } |
| |
167 | .disabled(deleteConfirm != model.repoPath) |
| |
168 | Button("Cancel", role: .cancel) {} |
| |
169 | } message: { |
| |
170 | Text("This deletes the repository with its issues, merge requests, releases and builds, and cannot be undone. Type \(model.repoPath) to confirm.") |
| |
171 | } |
| 120 | .confirmationDialog( |
172 | .confirmationDialog( |
| 121 | "Remove topic \(removingTopic ?? "")?", |
173 | "Remove topic \(removingTopic ?? "")?", |
| 122 | isPresented: Binding( |
174 | isPresented: Binding( |
| @@ -504,6 +556,39 @@ struct RepoSettingsView: View { |
| 504 | } |
556 | } |
| 505 | } |
557 | } |
| 506 | |
558 | |
| |
559 | private var transferSection: some View { |
| |
560 | Section { |
| |
561 | Button { |
| |
562 | newOwner = "" |
| |
563 | transferConfirm = "" |
| |
564 | transferring = true |
| |
565 | } label: { |
| |
566 | Label("Transfer repository", systemImage: "arrow.left.arrow.right") |
| |
567 | .font(.gbSans(.subheadline)) |
| |
568 | } |
| |
569 | .disabled(model.working) |
| |
570 | .accessibilityIdentifier("settings-transfer-button") |
| |
571 | } footer: { |
| |
572 | Text("To another account or an organization you can create repositories in.") |
| |
573 | } |
| |
574 | } |
| |
575 | |
| |
576 | private var deleteSection: some View { |
| |
577 | Section { |
| |
578 | Button(role: .destructive) { |
| |
579 | deleteConfirm = "" |
| |
580 | deleting = true |
| |
581 | } label: { |
| |
582 | Label("Delete repository", systemImage: "trash") |
| |
583 | .font(.gbSans(.subheadline)) |
| |
584 | } |
| |
585 | .disabled(model.working) |
| |
586 | .accessibilityIdentifier("settings-delete-button") |
| |
587 | } footer: { |
| |
588 | Text("Deletes it with everything in it. Only its owner or an admin can.") |
| |
589 | } |
| |
590 | } |
| |
591 | |
| 507 | private func depsSection() -> some View { |
592 | private func depsSection() -> some View { |
| 508 | Section { |
593 | Section { |
| 509 | if let deps = model.deps { |
594 | if let deps = model.deps { |