fix(ui): Show in Folders reveals the right-clicked photo and stays off for folders below the tree's depth

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Arekatla Nishanth Chowdary
2026-10-21 15:42:57 +05:30
co-authored by Claude Sonnet 5.5
parent 4ab5dcc319
commit 110aea1a1a
4 changed files with 53 additions and 11 deletions
+4
View File
@@ -1749,6 +1749,10 @@ impl Caches {
self.folder_tree = Some((cat.revision, t.clone()));
t
}
/// The folder tree as last built, when it still holds (a read-only check cannot rebuild it).
pub(crate) fn folder_tree_if_fresh(&self, cat: &lightcraft_catalog::Catalog) -> Option<std::sync::Arc<Vec<lightcraft_catalog::FolderNode>>> {
self.folder_tree.as_ref().filter(|(r, _)| still_right(cat, *r, READS_FILE)).map(|(_, t)| t.clone())
}
/// The library's keyword tree.
pub fn keyword_tree(&mut self, cat: &lightcraft_catalog::Catalog) -> std::sync::Arc<Vec<lightcraft_catalog::KeywordNode>> {
let kept = self.keyword_tree.take().filter(|(r, _)| still_right(cat, *r, READS_META)).map(|(_, t)| t);
+18 -9
View File
@@ -2107,7 +2107,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
}
"app.export" => crate::control::export_active(app, p),
"app.showInFinder" => show_in_finder(app),
"app.showInFolders" => show_in_folders(app),
"app.showInFolders" => show_in_folders(app, p.get("id").and_then(Value::as_u64).map(lightcraft_catalog::PhotoId)),
"app.openLogFolder" => open_log_folder(app),
"app.discord" | "app.website" | "app.github" | "app.artcraft" | "app.help" | "app.feedback" => {
let url = crate::links::url_of(id).unwrap_or(crate::links::WEBSITE);
@@ -2162,7 +2162,7 @@ pub fn ui_enabled(app: &LightcraftApp, id: &str) -> bool {
.and_then(|id| app.session.catalog.photo(id))
.is_some_and(|p| matches!(p.source, lightcraft_engine::catalog::Source::File { .. }))
}
"app.showInFolders" => active_folder(app).is_some(),
"app.showInFolders" => show_in_folders_enabled(app, None),
"app.openSamModelFolder" => app.session.segmenter.dir.is_some() && app.services.open_with.is_some(),
"file.exportPresets" => app.session.presets.iter().any(|p| !p.builtin),
"file.exportMaskEffects" => !app.session.mask_effects.is_empty(),
@@ -2295,18 +2295,27 @@ fn show_in_finder(app: &mut LightcraftApp) -> Result<Value, String> {
/// The folder the active photo was imported from, as the sidebar's Folders section counts it (a
/// photo straight in a disk's root is in the root). `None` for a demo scene, a photo only browsed
/// in Local or in Recently Deleted, and one on the startup disk's root, which has no row to show.
fn active_folder(app: &LightcraftApp) -> Option<String> {
let photo = app.session.active().and_then(|id| app.session.catalog.photo(id)).filter(|p| p.in_library())?;
fn photo_folder(app: &LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> Option<String> {
let photo = id.or_else(|| app.session.active()).and_then(|id| app.session.catalog.photo(id)).filter(|p| p.in_library())?;
let lightcraft_engine::catalog::Source::File { path } = &photo.source else { return None };
let dir = path.rfind(['/', '\\']).and_then(|i| path.get(..i.max(1)))?;
(!lightcraft_catalog::folders::is_startup_disk(dir)).then(|| dir.to_string())
}
/// Photo ▸ Show in Folders (Lightroom's Go to Folder in Library): show the active photo's folder
/// as a click on its row in the sidebar's Folders section does, and bring the row into view: the
/// sidebar, the section and the folders above it open, and it scrolls there (`reveal_folder`).
fn show_in_folders(app: &mut LightcraftApp) -> Result<Value, String> {
let dir = active_folder(app).ok_or("this photo is not in a folder of the library")?;
/// Whether Show in Folders has a row to show for photo `id` (the active one when `None`): its
/// folder must be a row of the tree, which stops `MAX_DEPTH` levels down. With no fresh tree to
/// look in, the folder alone decides, and the command still checks.
pub fn show_in_folders_enabled(app: &LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> bool {
let Some(dir) = photo_folder(app, id) else { return false };
app.caches.folder_tree_if_fresh(&app.session.catalog).is_none_or(|tree| crate::panels::left::folder_row_of(&tree, &dir).is_some())
}
/// Photo ▸ Show in Folders (Lightroom's Go to Folder in Library): show the folder of photo `id`
/// (the active one when `None`; the grid's menu passes the photo right-clicked) as a click on its
/// row in the sidebar's Folders section does, and bring the row into view: the sidebar, the
/// section and the folders above it open, and it scrolls there (`reveal_folder`).
fn show_in_folders(app: &mut LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> Result<Value, String> {
let dir = photo_folder(app, id).ok_or("this photo is not in a folder of the library")?;
let tree = app.caches.folder_tree(&app.session.catalog);
// the row's own spelling (forward slashes): what its click hands `library.source`, and its id
let path = crate::panels::left::folder_row_of(&tree, &dir).map(|n| n.path.clone()).ok_or("this photo's folder is not in Folders")?;
+3 -2
View File
@@ -1231,8 +1231,9 @@ pub fn context_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
let _ = app.run("app.showInFinder", json!({}));
}
// the photo's folder in the sidebar's Folders section, as Lightroom's Go to Folder in Library
if ui.add_enabled(crate::menus::ui_enabled(app, "app.showInFolders"), egui::Button::new(crate::i18n::tr("Show in Folders"))).clicked() {
let _ = app.run("app.showInFolders", json!({}));
// (the photo right-clicked, which a multi-selection may leave other than the active one)
if ui.add_enabled(crate::menus::show_in_folders_enabled(app, Some(id)), egui::Button::new(crate::i18n::tr("Show in Folders"))).clicked() {
let _ = app.run("app.showInFolders", json!({"id": id.0}));
}
if ui.button(crate::i18n::tr("Export…")).clicked() {
let _ = app.run("dialog.export", json!({}));
+28
View File
@@ -1721,6 +1721,34 @@ fn show_in_folders_needs_a_photo_in_a_library_folder() {
assert!(!crate::menus::ui_enabled(&demo, "app.showInFolders"), "a demo scene has no folder");
}
/// The grid's menu reveals the photo right-clicked, not the active one a multi-selection keeps.
#[test]
fn show_in_folders_reveals_the_photo_asked_for_and_follows_to_its_folder() {
let mut h = folders_app(&["/pics/trip/a.jpg", "/pics/home/c.jpg"]);
let id_of = |h: &Headless, path: &str| {
h.app.session.catalog.photos().find(|p| matches!(&p.source, lightcraft_catalog::Source::File { path: f } if f == path)).unwrap().id
};
let (a, c) = (id_of(&h, "/pics/trip/a.jpg"), id_of(&h, "/pics/home/c.jpg"));
h.app.run("library.select", json!({"ids": [a.0, c.0], "active": a.0})).unwrap();
h.step();
let r = h.app.run("app.showInFolders", json!({"id": c.0})).unwrap();
assert_eq!(r["path"], "/pics/home");
assert_eq!(h.app.session.library_folder.as_deref(), Some("/pics/home"));
assert_eq!(h.app.session.active(), Some(c), "the view of that folder shows the photo asked for");
assert!(crate::menus::show_in_folders_enabled(&h.app, Some(c)));
}
/// A folder deeper than the tree goes has no row: the entry is disabled rather than failing.
#[test]
fn show_in_folders_is_disabled_for_a_folder_below_the_trees_depth() {
let deep = format!("/pics{}/a.jpg", "/d".repeat(lightcraft_catalog::folders::MAX_DEPTH + 4));
let mut h = folders_app(&[deep.as_str()]);
activate(&mut h, &deep);
assert!(!h.app.caches.folder_tree(&h.app.session.catalog).is_empty());
assert!(!crate::menus::ui_enabled(&h.app, "app.showInFolders"));
assert!(h.app.run("app.showInFolders", json!({})).is_err());
}
#[test]
fn a_renamed_folder_keeps_its_place_in_the_tree() {
/// Removes the folder however the test ends.