mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 02:59:17 -06:00
Merge #2701@52ce53622 (photos dropped with no library open wait for one) into round3a-integration
crates/ui-egui/src/lib.rs: the batch's handle_dropped_files holds the photos (library_problem::hold_dropped) before the Lightroom / running-import checks. The drop card (drop_overlay.rs, LR-IMP-DRAGDROP) says the photos wait instead of announcing an import or a review; test photos_dropped_with_no_library_open_wait_for_one drops through `ui.dropFiles`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2384,5 +2384,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"
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ fn lines(app: &LightcraftApp, paths: Vec<String>, unknown: bool) -> Vec<String>
|
||||
let n = drop.presets.len();
|
||||
out.push(crate::i18n::tr_format!("Drop to import {n} preset{}", plural(n), n = n));
|
||||
}
|
||||
if !drop.photos.is_empty() && app.lightroom.is_some() && !cfg!(target_arch = "wasm32") {
|
||||
if !drop.photos.is_empty() && crate::panels::library_problem::holds_drops(app) {
|
||||
// no library open yet: they wait for the one chosen (the drop handler holds them)
|
||||
out.push(crate::i18n::tr("The photos are imported once a library is open").to_string());
|
||||
} else if !drop.photos.is_empty() && app.lightroom.is_some() && !cfg!(target_arch = "wasm32") {
|
||||
// the drop is refused while a Lightroom catalog import owns the catalog
|
||||
out.push(crate::i18n::tr("Finish Lightroom catalog import before adding photos").to_string());
|
||||
} else if !drop.photos.is_empty() && crate::import::running(app) && !cfg!(target_arch = "wasm32") {
|
||||
|
||||
@@ -1305,6 +1305,8 @@ impl LightcraftApp {
|
||||
if drop.photos.is_empty() {
|
||||
return;
|
||||
}
|
||||
// no library open yet (the launch list, a library that failed to open): they wait for one
|
||||
let Some(photos) = panels::library_problem::hold_dropped(self, ctx, drop.photos) else { return };
|
||||
if self.lightroom.is_some() {
|
||||
self.toast(ctx, crate::i18n::tr("Finish Lightroom catalog import before adding photos"));
|
||||
return;
|
||||
@@ -1315,9 +1317,9 @@ impl LightcraftApp {
|
||||
}
|
||||
let started = if self.ui.settings.import_dropped_directly {
|
||||
// read and added on a worker thread (dropped folders can be large, or on a slow drive)
|
||||
import::start_paths(self, drop.photos).map(|_| ())
|
||||
import::start_paths(self, photos).map(|_| ())
|
||||
} else {
|
||||
import::open(self, drop.photos).map(|_| ())
|
||||
import::open(self, photos).map(|_| ())
|
||||
};
|
||||
if let Err(e) = started {
|
||||
self.toast(ctx, e);
|
||||
|
||||
@@ -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<String>) -> Option<Vec<String>> {
|
||||
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<String>) {
|
||||
|
||||
@@ -251,3 +251,20 @@ fn drop_files_with_a_non_string_path_is_rejected_and_drops_nothing() {
|
||||
assert!(h.app.ui.dialog.is_none(), "nothing was dropped");
|
||||
assert_eq!(photos(&h), 0);
|
||||
}
|
||||
|
||||
/// No library open yet (the window of a library that failed to open, or the launch list): the
|
||||
/// card says the photos wait, and a drop keeps them for the library chosen instead of importing
|
||||
/// them into the session that library replaces.
|
||||
#[test]
|
||||
fn photos_dropped_with_no_library_open_wait_for_one() {
|
||||
let dir = Scratch::new("no-library");
|
||||
let mut h = app(&dir);
|
||||
h.app.library_problem = Some(crate::panels::library_problem::LibraryProblem::new(dir.path("Gone"), "not found"));
|
||||
drop_files(&mut h, &[dir.path("trip/a.png")], true);
|
||||
assert!(said(&h, "once a library is open"), "{:?}", h.painted_text());
|
||||
drop_files(&mut h, &[], true);
|
||||
drop_files(&mut h, &[dir.path("trip/a.png"), dir.path("trip/b.png")], false);
|
||||
assert_eq!(h.app.library_problem.as_ref().map(|p| p.pending_import.len()), Some(2));
|
||||
assert!(h.app.ui.dialog.is_none() && h.app.import.is_none(), "no review, no import into the temporary session");
|
||||
assert_eq!(photos(&h), 0);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user