From 69e1191e97cfdf36b2eac8d688cd8c674074843d Mon Sep 17 00:00:00 2001 From: Tinh Nguyen Date: Wed, 28 Oct 2026 18:48:40 +0700 Subject: [PATCH] feat(review): add Reveal in Finder to the category right-click menu --- Picnest/Review/ReviewViewModel.swift | 10 ++++++++++ Picnest/Review/SidebarView.swift | 3 +++ PicnestTests/ReviewViewModelTests.swift | 8 ++++++++ 3 files changed, 21 insertions(+) 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)