diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index e4b5e1909..cb7c3e373 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -2385,5 +2385,6 @@ "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", - "The photos are imported once a library is open": "Les photos seront importées dès qu'une bibliothèque sera ouverte" + "The photos are imported once a library is open": "Les photos seront importées dès qu'une bibliothèque sera ouverte", + "Open a library first, then drop the presets again": "Ouvrez d'abord une bibliothèque, puis déposez à nouveau les préréglages" } diff --git a/crates/ui-egui/src/drop_overlay.rs b/crates/ui-egui/src/drop_overlay.rs index 59aa5bd91..0e4825988 100644 --- a/crates/ui-egui/src/drop_overlay.rs +++ b/crates/ui-egui/src/drop_overlay.rs @@ -36,12 +36,16 @@ fn lines(app: &LightcraftApp, paths: Vec, unknown: bool) -> Vec out.push(crate::i18n::tr("Drop to open the face model").to_string()); } let plural = |n: usize| if n == 1 { "" } else { "s" }; - if !drop.presets.is_empty() { + // no library open yet (desktop: the web build's drops import into its session as before): + // photos wait for the library chosen, presets are refused (the drop handler does the same) + let waiting = crate::panels::library_problem::holds_drops(app) && !cfg!(target_arch = "wasm32"); + if !drop.presets.is_empty() && waiting { + out.push(crate::i18n::tr("Open a library first, then drop the presets again").to_string()); + } else if !drop.presets.is_empty() { 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::panels::library_problem::holds_drops(app) { - // no library open yet: they wait for the one chosen (the drop handler holds them) + if !drop.photos.is_empty() && waiting { 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 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index f777b3afd..07997e207 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1300,7 +1300,12 @@ impl LightcraftApp { self.toast(ctx, e); } if !drop.presets.is_empty() { - let _ = self.run("file.importPresets", serde_json::json!({"paths": drop.presets})); + // presets live in a library: with none open they would go with the session it replaces + if panels::library_problem::holds_drops(self) { + self.toast(ctx, crate::i18n::tr("Open a library first, then drop the presets again")); + } else { + let _ = self.run("file.importPresets", serde_json::json!({"paths": drop.presets})); + } } if drop.photos.is_empty() { return; diff --git a/crates/ui-egui/src/tests_drop_import.rs b/crates/ui-egui/src/tests_drop_import.rs index d4b362d5b..9751546df 100644 --- a/crates/ui-egui/src/tests_drop_import.rs +++ b/crates/ui-egui/src/tests_drop_import.rs @@ -260,11 +260,18 @@ 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); + std::fs::write(dir.path("warm.lcpreset"), "{}").unwrap(); + drop_files(&mut h, &[dir.path("trip/a.png"), dir.path("warm.lcpreset")], true); assert!(said(&h, "once a library is open"), "{:?}", h.painted_text()); + assert!(said(&h, "drop the presets again") && !said(&h, "1 preset"), "{:?}", 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); + // a preset belongs to a library: refused, not imported into the session the library replaces + h.app.ui.toast = None; + drop_files(&mut h, &[dir.path("warm.lcpreset")], false); + let toast = h.app.ui.toast.as_ref().map(|t| t.0.clone()).unwrap_or_default(); + assert!(toast.contains("drop the presets again"), "{toast}"); } diff --git a/docs/control-protocol.md b/docs/control-protocol.md index a42e43ffe..9c1463ef4 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -168,7 +168,8 @@ are `button:libraryRetry`, `button:libraryChoose`, `button:libraryTemporary` (Co `button:libraryQuit`. A temporary session shows a banner (`indicator:temporarySession`, `button:libraryReopen`) and 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. +up (desktop app); they are imported into the library that opens, or into the temporary session. Presets dropped +then are refused with a toast. ### Choosing the library at launch diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 05818ca7d..d285b88bb 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -70,7 +70,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | Id | Feature | Tier | Status | Evidence | Missing / notes | |---|---|---|---|---|---| | LR-IMP-ADD-DIALOG | Add photos/folders | P0 | ✅ | `cmd:file.addPhotos`, `cmd:library.importPreview`, `cmd:library.import` (`mode`, `album`, `albumName`, `preset`, `keywords`), `crates/ui-egui/src/import.rs` | review dialog (Import Photos): the scanned source (a scanned folder is not added to Local), candidate grid with thumbnails and checkboxes (duplicates by path/content marked and unchecked; with several file types, a toggle per type checks or unchecks all of its files, e.g. the raws without the JPEGs beside them, issue #344) filling a resizable window (issue #337), transfer mode (add in place / copy / move, each explained), album (existing/new): the destination picker follows the sidebar folder hierarchy and order, searches full paths, and distinguishes duplicate names by their folder path; folders navigate and smart albums are excluded, with None, New album and album-context preselection retained (#2362; `crates/ui-egui/src/album_picker.rs`, `crates/ui-egui/src/tests_album_picker.rs`); preset, keywords; the folder scan runs on a background thread, shown in the activity stack with ✕ to cancel (a NAS folder no longer freezes the window); the import itself (probing, sidecars, copying / moving files) runs on a worker thread and its batches join the library between frames, shown in the activity stack with ✕ to cancel; one undo step. Drag-and-drop goes through the same background import. File dialogs list the importable extensions (`lightcraft_engine::import::EXTENSIONS`) in any letter case on Linux, where GTK and the desktop portal match globs case-sensitively (`apps/lightcraft/src/dialog_filter.rs`, issue #342; 0.4.0 hid `.JPG` / `.NEF`, issue #705). The folder scan, Browse Folder, drag-and-drop and the sidecar lookup match extensions in any case (`crates/engine/src/tests_import.rs`) | -| LR-IMP-DRAGDROP | Drop files/folders to import | P0 | ✅ | `crates/ui-egui/src/lib.rs` (dropped files → the import review), `crates/ui-egui/src/drop_overlay.rs`, `crates/ui-egui/src/tests_drop_import.rs` | while files are dragged over the window a veil and a card say what dropping does (review N items for import, import N presets, open the face model; a note to close the open dialog first when one is open); photos and folders open the import review as File ▸ Import does (Settings ▸ Import ▸ Import dropped files without review imports them at once; the web build always does); a drop while a dialog is open (also in the web build) or while an import is running is refused with a toast, and the card says so; a finished scan waits for an open dialog to close before its review opens; the control channel's `ui.dropFiles` hovers or drops files for scripts; dropping onto a specific album not supported | +| LR-IMP-DRAGDROP | Drop files/folders to import | P0 | ✅ | `crates/ui-egui/src/lib.rs` (dropped files → the import review), `crates/ui-egui/src/drop_overlay.rs`, `crates/ui-egui/src/tests_drop_import.rs` | while files are dragged over the window a veil and a card say what dropping does (review N items for import, import N presets, open the face model; a note to close the open dialog first when one is open); photos and folders open the import review as File ▸ Import does (Settings ▸ Import ▸ Import dropped files without review imports them at once; the web build always does); with no library open on the desktop (the launch list, a library that couldn't open) photos wait for the library opened and presets are refused, and the card says so; a drop while a dialog is open (also in the web build) or while an import is running is refused with a toast, and the card says so; a finished scan waits for an open dialog to close before its review opens; the control channel's `ui.dropFiles` hovers or drops files for scripts; dropping onto a specific album not supported | | LR-IMP-DUPES | Skip duplicates by content | P1 | ✅ | `crates/engine/src/import.rs`, `crates/engine/src/tests_import.rs` | | | LR-IMP-DEVICE | Import from camera/card | P1 | ✅ | `cmd:library.devices`, `cmd:file.addFromDevice`, `crates/engine/src/devices.rs` | mounted volumes with a DCIM folder (macOS /Volumes, Linux /media and /run/media, Windows drive letters); File → Import from Device → the import review, copying into the library by default; menus serve the last scan and rescan in the background (never blocking a frame; hot-plugs repaint); copies are verified (a new file, synced, checked against the content hash the scan computed — byte compare with the source when there is none, issue #134; a bad copy is removed and reported as failed, never counted as imported; taken names get -1, -2…, never replaced — issue #96, `crates/engine/src/import_move.rs`); no PTP/MTP (cameras that don't mount as a disk); Settings → Import → Cameras and Cards can open the import review when a camera or card is connected (off by default; a card that was already in doesn't ask, and an open dialog or running import is finished first; `crates/ui-egui/src/import.rs`) | | LR-IMP-AUTO | Watched-folder auto import | P2 | ✅ | `cmd:library.autoImport`, `cmd:library.autoImportScan`, `crates/ui-egui/src/panels/settings.rs` | Settings ▸ Import ▸ Auto Import: a watched folder whose new photos are added (in place or copied) once complete, into an optional album; scanned every 3 s | @@ -798,7 +798,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| -| 2026-10-23 | minor | LRC-LIB-CATALOG-SWITCH, LR-PREF-GENERAL: Settings ▸ General ▸ At launch (Open the last library / Ask me) with a launch window listing the recent libraries, Alt/Option at launch asks once, File ▸ Open Recent Library ▸ Remove from List and `app.recentLibraries {remove}` (issue #1290); status and counts unchanged (✅) | +| 2026-10-23 | minor | LRC-LIB-CATALOG-SWITCH, LR-PREF-GENERAL: Settings ▸ General ▸ At launch (Open the last library / Ask me) with a launch window listing the recent libraries, Alt/Option at launch asks once, File ▸ Open Recent Library ▸ Remove from List and `app.recentLibraries {remove}` (issue #1290); photos dropped while no library is open wait for the one opened (presets are refused then); status and counts unchanged (✅) | | 2026-10-23 | minor | LRC-LIB-CATALOG-IMPORT: Import Lightroom Catalog… (and Open Library's offer for a Lightroom catalog) asks whether the catalog merges into the open library or goes into a new library made next to it; Settings ▸ General or "Don't ask again" switches the question off; `file.importLightroom` takes `into` / `name` / `location` (#2672); status unchanged (🟡) | | 2026-10-22 | minor | LR-VIEW-INFOOVERLAY: the info overlay shows the star rating and flag, and in the full-screen preview on macOS it starts below the window controls (issue #2460); status unchanged | | 2026-10-22 | patch | KEY-GRID: both grid rows in View show the cycle shortcut and follow keymap changes; direct bindings and native accelerator ownership are preserved (#2500); status and counts unchanged | diff --git a/docs/whats-new.md b/docs/whats-new.md index 05215e429..0f864e4e2 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -35,6 +35,9 @@ - File ▸ Open Recent Library ▸ **Remove from List** forgets one library, and the launch window's **Remove from List** does the same. Only the entry goes: the library and its photos stay where they are. Agents use `app.recentLibraries {remove: path}`; its list now also says when each library was last opened (`lastOpened`). +- Photos dropped on the window while the list (or the window of a library that couldn't open) is up wait for the + library you open and are imported into it, as files given on the command line are; presets dropped then are + refused with a note, since they belong to a library. ## 2026-10-21