diff --git a/apps/lightcraft-web/src/web.rs b/apps/lightcraft-web/src/web.rs index 3a7914be2..6681590a0 100644 --- a/apps/lightcraft-web/src/web.rs +++ b/apps/lightcraft-web/src/web.rs @@ -526,9 +526,12 @@ impl WebApp { } let paths = self.originals.take_pending(); if !paths.is_empty() { - let n = paths.len(); match self.app.run("library.import", json!({"paths": paths})) { - Ok(_) => self.app.toast(ctx, lightcraft_ui_egui::i18n::dropped_photos_added(n)), + // what the import added, not what was submitted (duplicates and unsupported files add nothing) + Ok(report) => { + let added = report["imported"].as_array().map_or(0, Vec::len); + self.app.toast(ctx, lightcraft_ui_egui::i18n::dropped_photos_added(added)); + } Err(e) => { log::warn!("import: {e}"); self.app.toast(ctx, lightcraft_ui_egui::i18n::dropped_import_failed(&e)); diff --git a/crates/ui-egui/locales/cs.json b/crates/ui-egui/locales/cs.json index 661250c70..5eaa1c2d0 100644 --- a/crates/ui-egui/locales/cs.json +++ b/crates/ui-egui/locales/cs.json @@ -2402,5 +2402,6 @@ "Dropped Files": "Přetažené soubory", "Import dropped files without review": "Importovat přetažené soubory bez kontroly", "An import is running": "Import už běží", - "An import is running. Drop photos when it has finished": "Import už běží. Fotky přetáhněte, až skončí" + "An import is running. Drop photos when it has finished": "Import už běží. Fotky přetáhněte, až skončí", + "Finish Lightroom catalog import before adding photos": "Dokončete import katalogu Lightroom, než přidáte fotky" } diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 2ef804a2a..44594bc65 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -3430,5 +3430,6 @@ "Dropped Files": "Abgelegte Dateien", "Import dropped files without review": "Abgelegte Dateien ohne Prüfung importieren", "An import is running": "Ein Import läuft", - "An import is running. Drop photos when it has finished": "Ein Import läuft. Lege die Fotos ab, wenn er fertig ist" + "An import is running. Drop photos when it has finished": "Ein Import läuft. Lege die Fotos ab, wenn er fertig ist", + "Finish Lightroom catalog import before adding photos": "Schließe den Import des Lightroom-Katalogs ab, bevor du Fotos hinzufügst" } diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index abadd6474..41d745578 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -2350,5 +2350,6 @@ "Dropped Files": "Archivos soltados", "Import dropped files without review": "Importar los archivos soltados sin revisión", "An import is running": "Hay una importación en curso", - "An import is running. Drop photos when it has finished": "Hay una importación en curso. Suelta las fotos cuando termine" + "An import is running. Drop photos when it has finished": "Hay una importación en curso. Suelta las fotos cuando termine", + "Finish Lightroom catalog import before adding photos": "Termina la importación del catálogo de Lightroom antes de añadir fotos" } diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index 928fdf58e..1bebf1fa0 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -2348,5 +2348,6 @@ "Dropped Files": "Fichiers déposés", "Import dropped files without review": "Importer les fichiers déposés sans vérification", "An import is running": "Une importation est en cours", - "An import is running. Drop photos when it has finished": "Une importation est en cours. Déposez les photos quand elle sera terminée" + "An import is running. Drop photos when it has finished": "Une importation est en cours. Déposez les photos quand elle sera terminée", + "Finish Lightroom catalog import before adding photos": "Terminez l'importation du catalogue Lightroom avant d'ajouter des photos" } diff --git a/crates/ui-egui/locales/it.json b/crates/ui-egui/locales/it.json index 6d33c986c..13873ae86 100644 --- a/crates/ui-egui/locales/it.json +++ b/crates/ui-egui/locales/it.json @@ -2354,5 +2354,6 @@ "Dropped Files": "File rilasciati", "Import dropped files without review": "Importa i file rilasciati senza revisione", "An import is running": "Un'importazione è in corso", - "An import is running. Drop photos when it has finished": "Un'importazione è in corso. Trascina le foto quando è finita" + "An import is running. Drop photos when it has finished": "Un'importazione è in corso. Trascina le foto quando è finita", + "Finish Lightroom catalog import before adding photos": "Termina l'importazione del catalogo Lightroom prima di aggiungere foto" } diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index 41a207f75..174dc67e3 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -2350,5 +2350,6 @@ "Dropped Files": "ドロップしたファイル", "Import dropped files without review": "ドロップしたファイルを確認せずに読み込む", "An import is running": "読み込み中です", - "An import is running. Drop photos when it has finished": "読み込み中です。完了してから写真をドロップしてください" + "An import is running. Drop photos when it has finished": "読み込み中です。完了してから写真をドロップしてください", + "Finish Lightroom catalog import before adding photos": "写真を追加する前に Lightroom カタログの読み込みを完了してください" } diff --git a/crates/ui-egui/locales/ka.json b/crates/ui-egui/locales/ka.json index 2326c4787..320734ddb 100644 --- a/crates/ui-egui/locales/ka.json +++ b/crates/ui-egui/locales/ka.json @@ -2727,5 +2727,6 @@ "Dropped Files": "ჩაგდებული ფაილები", "Import dropped files without review": "ჩაგდებული ფაილების იმპორტი გადახედვის გარეშე", "An import is running": "იმპორტი მიმდინარეობს", - "An import is running. Drop photos when it has finished": "იმპორტი მიმდინარეობს. ფოტოები ჩააგდეთ, როცა დასრულდება" + "An import is running. Drop photos when it has finished": "იმპორტი მიმდინარეობს. ფოტოები ჩააგდეთ, როცა დასრულდება", + "Finish Lightroom catalog import before adding photos": "ფოტოების დამატებამდე დაასრულეთ Lightroom-ის კატალოგის იმპორტი" } diff --git a/crates/ui-egui/locales/pl.json b/crates/ui-egui/locales/pl.json index e9ca8902d..d85e706de 100644 --- a/crates/ui-egui/locales/pl.json +++ b/crates/ui-egui/locales/pl.json @@ -2277,5 +2277,6 @@ "Dropped Files": "Upuszczone pliki", "Import dropped files without review": "Importuj upuszczone pliki bez przeglądu", "An import is running": "Import jest w toku", - "An import is running. Drop photos when it has finished": "Import jest w toku. Upuść zdjęcia, gdy się zakończy" + "An import is running. Drop photos when it has finished": "Import jest w toku. Upuść zdjęcia, gdy się zakończy", + "Finish Lightroom catalog import before adding photos": "Zakończ import katalogu Lightroom, zanim dodasz zdjęcia" } diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index bfd01c8dc..24bdaec1d 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -2196,5 +2196,6 @@ "Dropped Files": "Arquivos soltos", "Import dropped files without review": "Importar arquivos soltos sem revisão", "An import is running": "Uma importação está em andamento", - "An import is running. Drop photos when it has finished": "Uma importação está em andamento. Solte as fotos quando ela terminar" + "An import is running. Drop photos when it has finished": "Uma importação está em andamento. Solte as fotos quando ela terminar", + "Finish Lightroom catalog import before adding photos": "Conclua a importação do catálogo do Lightroom antes de adicionar fotos" } diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index 4abbe5cfc..eba7789a2 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -2343,5 +2343,6 @@ "Dropped Files": "Перетащенные файлы", "Import dropped files without review": "Импортировать перетащенные файлы без проверки", "An import is running": "Импорт уже выполняется", - "An import is running. Drop photos when it has finished": "Импорт уже выполняется. Перетащите фото, когда он закончится" + "An import is running. Drop photos when it has finished": "Импорт уже выполняется. Перетащите фото, когда он закончится", + "Finish Lightroom catalog import before adding photos": "Завершите импорт каталога Lightroom, прежде чем добавлять фото" } diff --git a/crates/ui-egui/locales/uk.json b/crates/ui-egui/locales/uk.json index 26908a7c6..ffcff0ba0 100644 --- a/crates/ui-egui/locales/uk.json +++ b/crates/ui-egui/locales/uk.json @@ -2348,5 +2348,6 @@ "Dropped Files": "Перетягнуті файли", "Import dropped files without review": "Імпортувати перетягнуті файли без перегляду", "An import is running": "Імпорт уже виконується", - "An import is running. Drop photos when it has finished": "Імпорт уже виконується. Перетягніть фото, коли він завершиться" + "An import is running. Drop photos when it has finished": "Імпорт уже виконується. Перетягніть фото, коли він завершиться", + "Finish Lightroom catalog import before adding photos": "Завершіть імпорт каталогу Lightroom, перш ніж додавати фото" } diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index c48e3c736..768c65f65 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -2194,5 +2194,6 @@ "Dropped Files": "拖入的文件", "Import dropped files without review": "不经检查直接导入拖入的文件", "An import is running": "正在导入", - "An import is running. Drop photos when it has finished": "正在导入。导入完成后再拖放照片" + "An import is running. Drop photos when it has finished": "正在导入。导入完成后再拖放照片", + "Finish Lightroom catalog import before adding photos": "添加照片前,请先完成 Lightroom 目录的导入" } diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index 5af20f3ec..1c59ce9e1 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -2151,5 +2151,6 @@ "Dropped Files": "拖放的檔案", "Import dropped files without review": "不經檢查直接匯入拖放的檔案", "An import is running": "正在匯入", - "An import is running. Drop photos when it has finished": "正在匯入。匯入完成後再拖放照片" + "An import is running. Drop photos when it has finished": "正在匯入。匯入完成後再拖放照片", + "Finish Lightroom catalog import before adding photos": "新增照片前,請先完成 Lightroom 目錄的匯入" } diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 12bc9e14a..780250038 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -297,8 +297,9 @@ pub fn handle(app: &mut LightcraftApp, ctx: &egui::Context, req: &ControlRequest ok(Value::Null) } "ui.dropFiles" => { + // every entry must be a string: a script is not told `ok` for a part of what it sent let Some(paths) = - p.get("paths").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect::>()) + p.get("paths").and_then(Value::as_array).and_then(|a| a.iter().map(|v| v.as_str().map(str::to_string)).collect::>>()) else { return err("ui.dropFiles: paths must be an array of strings"); }; diff --git a/crates/ui-egui/src/drop_overlay.rs b/crates/ui-egui/src/drop_overlay.rs index 3754fb76a..e8c5d4724 100644 --- a/crates/ui-egui/src/drop_overlay.rs +++ b/crates/ui-egui/src/drop_overlay.rs @@ -40,7 +40,10 @@ fn lines(app: &LightcraftApp, paths: Vec, unknown: bool) -> Vec 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() && crate::import::running(app) && !cfg!(target_arch = "wasm32") { + 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") { out.push(crate::i18n::tr("An import is running. Drop photos when it has finished").to_string()); } else if !drop.photos.is_empty() { let n = drop.photos.len(); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 994b989c8..e016b4790 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1276,7 +1276,7 @@ impl LightcraftApp { return; } if self.lightroom.is_some() { - self.toast(ctx, "Finish Lightroom catalog import before adding photos"); + self.toast(ctx, crate::i18n::tr("Finish Lightroom catalog import before adding photos")); return; } if import::running(self) { diff --git a/crates/ui-egui/src/lightroom_import.rs b/crates/ui-egui/src/lightroom_import.rs index 2bda28cf4..ce462b1d7 100644 --- a/crates/ui-egui/src/lightroom_import.rs +++ b/crates/ui-egui/src/lightroom_import.rs @@ -140,6 +140,22 @@ fn unsupported_wasm() -> Result { Err("Lightroom catalog import is unavailable in browser builds; open the catalog in the native LightCraft app".into()) } +/// Tests: a catalog import that never ends while the returned value lives, so a drop meets it pending. +#[cfg(all(test, not(target_arch = "wasm32")))] +pub(crate) fn stall_import(app: &mut LightcraftApp) -> Box { + let (tx, rx) = mpsc::channel::(); + let task = LightroomTask::new( + &app.session, + Kind::Import, + PathBuf::from("catalog.lrcat"), + Arc::new(AtomicBool::new(false)), + LightroomProgress::default(), + rx, + ); + app.lightroom = Some(task); + Box::new(tx) +} + /// Whether a Lightroom task currently owns the catalog transition. pub fn is_running(app: &LightcraftApp) -> bool { app.lightroom.is_some() diff --git a/crates/ui-egui/src/tests_drop_import.rs b/crates/ui-egui/src/tests_drop_import.rs index fad64397f..bee26a184 100644 --- a/crates/ui-egui/src/tests_drop_import.rs +++ b/crates/ui-egui/src/tests_drop_import.rs @@ -199,3 +199,33 @@ fn a_direct_drop_while_an_import_runs_is_refused_too() { assert!(h.app.import.is_none(), "no second import"); assert!(h.app.ui.toast.is_some()); } + +/// While a Lightroom catalog import is pending the drop is refused, and the card says so instead of +/// promising a review. +#[test] +fn hovering_photos_while_a_lightroom_import_is_pending_says_they_will_be_refused() { + let dir = Scratch::new("lightroom"); + let mut h = app(&dir); + let _task = crate::lightroom_import::stall_import(&mut h.app); + drop_files(&mut h, &[dir.path("trip")], true); + assert!(said(&h, "Finish Lightroom catalog import"), "{:?}", h.painted_text()); + assert!(!said(&h, "review"), "{:?}", h.painted_text()); + drop_files(&mut h, &[dir.path("trip")], false); + assert!(h.app.ui.dialog.is_none(), "no review opened"); + assert!(h.app.ui.toast.is_some(), "the person is told why"); + assert_eq!(photos(&h), 0); +} + +/// A script is not told `ok` for the part of a `paths` list that was strings. +#[test] +fn drop_files_with_a_non_string_path_is_rejected_and_drops_nothing() { + let dir = Scratch::new("badpaths"); + let mut h = app(&dir); + let r = h.request("ui.dropFiles", json!({"paths": [dir.path("trip"), 7]}), T); + assert_eq!(r["ok"], false, "{r}"); + for _ in 0..30 { + h.step(); + } + assert!(h.app.ui.dialog.is_none(), "nothing was dropped"); + assert_eq!(photos(&h), 0); +}