diff --git a/Picnest/Review/ReviewViewModel.swift b/Picnest/Review/ReviewViewModel.swift index becbbfe..fd9fec3 100644 --- a/Picnest/Review/ReviewViewModel.swift +++ b/Picnest/Review/ReviewViewModel.swift @@ -470,6 +470,16 @@ final class ReviewViewModel { revealURLs(urls) } + /// Shows the photos of category `c` in Finder. Does nothing if the category has no photos. + func showCategoryInFinder(_ c: CategoryID) { + let session = review.session + let urls = session.visibleMedia + .filter { Self.includes($0, state: session.states[$0.id], in: .category(c)) } + .map(url(of:)) + guard !urls.isEmpty else { return } + revealURLs(urls) + } + func selectAll() { selection = Set(visibleIDs) } func clearSelection() { diff --git a/Picnest/Review/SidebarView.swift b/Picnest/Review/SidebarView.swift index c0dcd5c..ddebbbd 100644 --- a/Picnest/Review/SidebarView.swift +++ b/Picnest/Review/SidebarView.swift @@ -89,6 +89,9 @@ struct SidebarView: View { } .disabled(model.mergeTargets(for: id).isEmpty) Divider() + Button("Reveal in Finder") { model.showCategoryInFinder(id) } + .disabled(model.count(of: .category(id)) == 0) + Divider() Button("Delete…", role: .destructive) { deleting = id } .disabled(!model.canDeleteCategory) } diff --git a/PicnestTests/ReviewViewModelTests.swift b/PicnestTests/ReviewViewModelTests.swift index 668bdb2..22efb76 100644 --- a/PicnestTests/ReviewViewModelTests.swift +++ b/PicnestTests/ReviewViewModelTests.swift @@ -496,6 +496,14 @@ struct ReviewViewModelTests { #expect(Set(revealed[0].map(\.lastPathComponent)) == ["a.jpg", "c.jpg"]) } + @Test func showCategoryInFinderRevealsThePhotosOfThatCategory() throws { + var revealed: [[URL]] = [] + let model = try makeModelAndReview(revealURLs: { revealed.append($0) }).model + model.showCategoryInFinder(food.id) + try #require(revealed.count == 1) + #expect(Set(revealed[0].map(\.lastPathComponent)) == ["a.jpg", "c.jpg"]) + } + @Test func selectedURLsAreTheSelection() throws { let model = try makeModel() #expect(model.selectedURLs().isEmpty)