Add confirmed project deletion
This commit is contained in:
@@ -5,6 +5,7 @@ import SwiftUI
|
||||
struct ProjectBrowserView: View {
|
||||
@StateObject private var viewModel: ProjectBrowserViewModel
|
||||
@State private var selectedProjectID: String?
|
||||
@State private var projectPendingDeletion: SongProjectListItem?
|
||||
|
||||
init(store: any ProjectStore = LocalSongProjectStore(directoryURL: ProjectBrowserStorage.projectsDirectoryURL)) {
|
||||
_viewModel = StateObject(wrappedValue: ProjectBrowserViewModel(store: store))
|
||||
@@ -15,8 +16,17 @@ struct ProjectBrowserView: View {
|
||||
List(selection: $selectedProjectID) {
|
||||
Section("Projects") {
|
||||
ForEach(viewModel.projects) { project in
|
||||
ProjectListRow(project: project)
|
||||
ProjectListRow(project: project) {
|
||||
projectPendingDeletion = project
|
||||
}
|
||||
.tag(project.id)
|
||||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
projectPendingDeletion = project
|
||||
} label: {
|
||||
Label("Delete Project", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,26 +82,78 @@ struct ProjectBrowserView: View {
|
||||
await viewModel.openProject(id: projectID)
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
deletionAlertTitle,
|
||||
isPresented: deletionAlertBinding
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
guard let project = projectPendingDeletion else { return }
|
||||
Task {
|
||||
await deleteProject(project)
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text("This permanently removes the project and its saved versions from this Mac.")
|
||||
}
|
||||
}
|
||||
|
||||
private var deletionAlertBinding: Binding<Bool> {
|
||||
Binding(
|
||||
get: { projectPendingDeletion != nil },
|
||||
set: { isPresented in
|
||||
if !isPresented {
|
||||
projectPendingDeletion = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var deletionAlertTitle: String {
|
||||
guard let projectPendingDeletion else { return "Delete Project?" }
|
||||
return "Delete \(projectPendingDeletion.title)?"
|
||||
}
|
||||
|
||||
private func deleteProject(_ project: SongProjectListItem) async {
|
||||
guard await viewModel.deleteProject(id: project.id) else { return }
|
||||
|
||||
if selectedProjectID == project.id {
|
||||
selectedProjectID = viewModel.projects.first?.id
|
||||
}
|
||||
projectPendingDeletion = nil
|
||||
}
|
||||
}
|
||||
|
||||
private struct ProjectListRow: View {
|
||||
let project: SongProjectListItem
|
||||
let deleteAction: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(project.title)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(project.title)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(project.idea.isEmpty ? "No idea yet" : project.idea)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
Text(project.idea.isEmpty ? "No idea yet" : project.idea)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
|
||||
Text(project.updatedAt.formatted(date: .abbreviated, time: .shortened))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
Text(project.updatedAt.formatted(date: .abbreviated, time: .shortened))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
|
||||
Spacer(minLength: 4)
|
||||
|
||||
Button(action: deleteAction) {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.foregroundStyle(.secondary)
|
||||
.help("Delete project")
|
||||
.accessibilityLabel("Delete \(project.title)")
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
@@ -140,6 +202,7 @@ final class ProjectBrowserViewModel: ObservableObject {
|
||||
@Published private(set) var isLoading = false
|
||||
@Published private(set) var isOpeningProject = false
|
||||
@Published private(set) var isCreatingProject = false
|
||||
@Published private(set) var isDeletingProject = false
|
||||
@Published private(set) var errorMessage: String?
|
||||
|
||||
private let store: any ProjectStore
|
||||
@@ -216,6 +279,26 @@ final class ProjectBrowserViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteProject(id projectID: String) async -> Bool {
|
||||
guard !isDeletingProject else { return false }
|
||||
isDeletingProject = true
|
||||
errorMessage = nil
|
||||
|
||||
do {
|
||||
try await store.delete(id: projectID)
|
||||
projects = try await store.loadProjectList()
|
||||
if selectedProject?.id == projectID {
|
||||
selectedProject = nil
|
||||
}
|
||||
isDeletingProject = false
|
||||
return true
|
||||
} catch {
|
||||
errorMessage = "Unable to delete this project."
|
||||
isDeletingProject = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func clearSelection() {
|
||||
selectedProject = nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user