LR-IMP-DRAGDROP: count what the web drop added, reject bad ui.dropFiles paths, and say the drop is refused during a Lightroom import

Review fixes:
- the web build's "Added n photos" toast counts the photos `library.import` reports, not the paths submitted
- `ui.dropFiles` rejects a `paths` list with a non-string entry instead of dropping part of it
- the hover card says a drop will be refused while a Lightroom catalog import is pending, and that refusal text is now translated in all 13 catalogs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-22 01:58:50 -04:00
co-authored by Claude Opus 5.5
parent f84347a88e
commit 34cfc9cd3b
19 changed files with 84 additions and 18 deletions
+5 -2
View File
@@ -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));
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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 カタログの読み込みを完了してください"
}
+2 -1
View File
@@ -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-ის კატალოგის იმპორტი"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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"
}
+2 -1
View File
@@ -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, прежде чем добавлять фото"
}
+2 -1
View File
@@ -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, перш ніж додавати фото"
}
+2 -1
View File
@@ -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 目录的导入"
}
+2 -1
View File
@@ -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 目錄的匯入"
}
+2 -1
View File
@@ -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::<Vec<_>>())
p.get("paths").and_then(Value::as_array).and_then(|a| a.iter().map(|v| v.as_str().map(str::to_string)).collect::<Option<Vec<_>>>())
else {
return err("ui.dropFiles: paths must be an array of strings");
};
+4 -1
View File
@@ -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() && 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();
+1 -1
View File
@@ -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) {
+16
View File
@@ -140,6 +140,22 @@ fn unsupported_wasm() -> Result<Value, String> {
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<dyn std::any::Any> {
let (tx, rx) = mpsc::channel::<Message>();
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()
+30
View File
@@ -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);
}