From 52ce53622530f2296e441c6ee9be363827e53950 Mon Sep 17 00:00:00 2001 From: francoisgn <32835330+francoisgn@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:51:09 +0300 Subject: [PATCH] #1290: photos dropped on the launch list wait for the library chosen While no library is open (the launch list, or the window of a library that failed to open), a drop went into the in-memory session that the library chosen then replaced, so the photos were gone from view. They now join the command line's files (`pendingImport`) and are imported into the library that opens (or the temporary session), with a toast saying so. Found by CodeRabbit on #2689. Test photos_dropped_on_the_launch_list_go_into_the_library_chosen fails with the hold disabled. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/locales/fr.json | 3 +- crates/ui-egui/src/lib.rs | 1 + crates/ui-egui/src/panels/library_problem.rs | 20 ++++++++++++++ crates/ui-egui/src/tests_launch_library.rs | 29 ++++++++++++++++++++ docs/control-protocol.md | 4 ++- 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index bd656a9e4..33365533c 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -2361,5 +2361,6 @@ "The library's catalog can't be read:": "Le catalogue de la bibliothèque ne peut pas être lu :", "Not answering yet: the drive may be asleep or offline": "Pas encore de réponse : le disque est peut-être en veille ou hors ligne", "This library couldn't be checked": "Impossible de vérifier cette bibliothèque", - "Checking recent libraries": "Vérification des bibliothèques récentes" + "Checking recent libraries": "Vérification des bibliothèques récentes", + "The photos are imported once a library is open": "Les photos seront importées dès qu'une bibliothèque sera ouverte" } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 2c7b185bf..634771214 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1259,6 +1259,7 @@ impl LightcraftApp { let _ = self.run("file.importPresets", serde_json::json!({"paths": presets})); } // read and added on a worker thread (dropped folders can be large, or on a slow drive) + let photos = panels::library_problem::hold_dropped(self, ctx, photos).unwrap_or_default(); if !photos.is_empty() { if self.lightroom.is_some() { self.toast(ctx, "Finish Lightroom catalog import before adding photos"); diff --git a/crates/ui-egui/src/panels/library_problem.rs b/crates/ui-egui/src/panels/library_problem.rs index ca11f5e5a..67ce88ca6 100644 --- a/crates/ui-egui/src/panels/library_problem.rs +++ b/crates/ui-egui/src/panels/library_problem.rs @@ -426,6 +426,26 @@ enum Choice { Keep, } +/// Whether dropped photos wait for a library ([`hold_dropped`]): none is open and the window +/// asking for one is up (not dismissed by Continue Without Saving). +pub fn holds_drops(app: &LightcraftApp) -> bool { + app.session.library.is_none() && app.library_problem.as_ref().is_some_and(|p| !p.dismissed) +} + +/// Photos dropped on the window while no library is open (the launch list, or the window of a +/// library that failed to open): kept with the command line's files and imported once a library +/// opens, instead of going into the session the next library replaces. Gives the photos back when +/// a library is open or Continue Without Saving was chosen. +pub fn hold_dropped(app: &mut LightcraftApp, ctx: &egui::Context, photos: Vec) -> Option> { + if !holds_drops(app) { + return Some(photos); + } + let problem = app.library_problem.as_mut()?; + problem.pending_import.extend(photos); + app.toast(ctx, crate::i18n::tr("The photos are imported once a library is open")); + None +} + /// Open `path` (`None`: ask for a folder) through `app.openLibrary`; on success the problem is /// over (and command-line files are imported), else it shows the new error. fn open(app: &mut LightcraftApp, path: Option) { diff --git a/crates/ui-egui/src/tests_launch_library.rs b/crates/ui-egui/src/tests_launch_library.rs index b8a334487..13f59fc8f 100644 --- a/crates/ui-egui/src/tests_launch_library.rs +++ b/crates/ui-egui/src/tests_launch_library.rs @@ -832,3 +832,32 @@ fn a_failed_open_or_an_unreadable_catalog_doesnt_mark_a_library_missing() { assert!(matches!(open_recent(&h.app).first(), Some(MenuNode::Item { enabled: true, .. }))); let _ = std::fs::remove_dir_all(&dir); } + +/// Photos dropped on the launch list wait for the library chosen (CodeRabbit on #2689): before, +/// they went into the session the library then replaced, and were gone from view. +#[test] +fn photos_dropped_on_the_launch_list_go_into_the_library_chosen() { + let dir = temp("drop"); + let a = make_library(&dir.join("Alpha")); + let mut h = asking(vec![a.clone()], &a, None); + let img = lightcraft_raster::Rgba8 { width: 8, height: 8, data: vec![[90, 3, 9, 255]; 64] }; + let png = lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &lightcraft_codecs::EncodeMeta::default()).unwrap(); + let photo = dir.join("a.png"); + std::fs::write(&photo, png).unwrap(); + let ctx = h.view.ctx.clone(); + let photos = vec![photo.to_string_lossy().into_owned()]; + assert!(crate::panels::library_problem::holds_drops(&h.app)); + assert_eq!(crate::panels::library_problem::hold_dropped(&mut h.app, &ctx, photos.clone()), None, "held while no library is open"); + assert_eq!(h.app.library_problem.as_ref().map(|p| p.pending_import.clone()), Some(photos.clone())); + assert!(h.app.ui.toast.as_ref().is_some_and(|t| t.0.contains("once a library is open")), "{:?}", h.app.ui.toast); + assert_eq!(h.app.session.catalog.len(), 0, "nothing goes into the session the library replaces"); + assert_eq!(h.request("ui.clickWidget", json!({"id": "button:libraryOpen"}), T)["ok"], true); + settle(&mut h); + h.step_until(T, |h| h.app.import.is_none() && h.app.session.catalog.len() == 1); + assert_eq!(h.app.session.catalog.len(), 1, "the dropped photo is imported into the library opened"); + // with a library open, a drop is the usual import + assert!(!crate::panels::library_problem::holds_drops(&h.app)); + assert_eq!(crate::panels::library_problem::hold_dropped(&mut h.app, &ctx, photos.clone()), Some(photos)); + drop(h); + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/docs/control-protocol.md b/docs/control-protocol.md index c51c7d708..ac949a549 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -165,7 +165,9 @@ catalog, a missing drive), the session starts empty and in memory — never with to do: `ui.inspect` → `libraryProblem` is `{path, error, temporarySession, pendingImport}` (else `null`); its buttons are `button:libraryRetry`, `button:libraryChoose`, `button:libraryTemporary` (Continue Without Saving) and `button:libraryQuit`. A temporary session shows a banner (`indicator:temporarySession`, `button:libraryReopen`) and -writes nothing. `app.openLibrary` opening a library ends it. +writes nothing. `app.openLibrary` opening a library ends it. `pendingImport` counts the files waiting for a +library: those given on the command line, and photos dropped on the window while it (or the launch list below) is +up; they are imported into the library that opens, or into the temporary session. ### Choosing the library at launch