mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 14:39:41 -06:00
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:
co-authored by
Claude Sonnet 5.5
parent
4ab5dcc319
commit
110aea1a1a
@@ -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);
|
||||
|
||||
@@ -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")?;
|
||||
|
||||
@@ -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!({}));
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user