commit
94808da369
8 files changed
+84
-13
No files matched your search
@@ -703,6 +703,7 @@
|
||||
"In memory — nothing is saved": "Im Arbeitsspeicher — nichts wird gespeichert",
|
||||
"In use": "In Verwendung",
|
||||
"In-memory session: these settings last until LightCraft quits.": "Sitzung im Arbeitsspeicher: Diese Einstellungen gelten bis zum Beenden von LightCraft.",
|
||||
"Include selected photos": "Ausgewählte Fotos einschließen",
|
||||
"Include subfolders": "Unterordner einbeziehen",
|
||||
"Increase Brush Feather": "Weiche Pinselkante vergrößern",
|
||||
"Increase Brush Size": "Pinselgröße vergrößern",
|
||||
|
||||
@@ -1806,7 +1806,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(h.app.ui.dialog, None, "Return confirmed the dialog ({typed})");
|
||||
};
|
||||
confirm(&mut h, Dialog::NewAlbum { name: String::new(), folder: false, parent: None }, "Weddings");
|
||||
confirm(&mut h, Dialog::NewAlbum { name: String::new(), folder: false, parent: None, include_selected: true }, "Weddings");
|
||||
assert!(album(&h, "Weddings").is_some(), "New Album");
|
||||
confirm(&mut h, Dialog::RenameAlbum { id: garden, name: "Garden".into() }, "Flowers");
|
||||
assert_eq!(album(&h, "Flowers"), Some(garden), "Rename Album");
|
||||
|
||||
@@ -1081,13 +1081,22 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"dialog.newFolder" => {
|
||||
app.ui.dialog =
|
||||
Some(Dialog::NewAlbum { name: p.get("name").and_then(Value::as_str).unwrap_or("").into(), folder: true, parent: parent_param(p) });
|
||||
app.ui.dialog = Some(Dialog::NewAlbum {
|
||||
name: p.get("name").and_then(Value::as_str).unwrap_or("").into(),
|
||||
folder: true,
|
||||
parent: parent_param(p),
|
||||
include_selected: false,
|
||||
});
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"dialog.newAlbum" => {
|
||||
app.ui.dialog =
|
||||
Some(Dialog::NewAlbum { name: p.get("name").and_then(Value::as_str).unwrap_or("").into(), folder: false, parent: parent_param(p) });
|
||||
// {name?, parent?, includeSelected? (true)}
|
||||
app.ui.dialog = Some(Dialog::NewAlbum {
|
||||
name: p.get("name").and_then(Value::as_str).unwrap_or("").into(),
|
||||
folder: false,
|
||||
parent: parent_param(p),
|
||||
include_selected: p.get("includeSelected").and_then(Value::as_bool).unwrap_or(true),
|
||||
});
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"dialog.autoStack" => {
|
||||
|
||||
@@ -609,7 +609,18 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
confirm = true;
|
||||
}
|
||||
}
|
||||
Dialog::NewAlbum { name, .. } | Dialog::RenameAlbum { name, .. } => {
|
||||
Dialog::NewAlbum { name, folder, include_selected, .. } => {
|
||||
if dialog_field(ui, "field:albumName", name, "Name") {
|
||||
confirm = true;
|
||||
}
|
||||
// as in Lightroom, the option is only there when something is selected
|
||||
if !*folder && !app.session.targets(&json!({})).is_empty() {
|
||||
ui.add_space(6.0);
|
||||
let r = ui.checkbox(include_selected, crate::i18n::tr("Include selected photos"));
|
||||
crate::widgets::register(ui.ctx(), "check:albumIncludeSelected", r.rect);
|
||||
}
|
||||
}
|
||||
Dialog::RenameAlbum { name, .. } => {
|
||||
if dialog_field(ui, "field:albumName", name, "Name") {
|
||||
confirm = true;
|
||||
}
|
||||
@@ -1529,8 +1540,8 @@ pub fn confirm_dialog(app: &mut LightcraftApp, dlg: &Dialog) -> Result<serde_jso
|
||||
}
|
||||
r
|
||||
}
|
||||
Dialog::NewAlbum { name, folder, parent } => {
|
||||
created_in(app, "album.create", json!({"name": name, "folder": folder, "addSelected": !folder, "parent": parent}))
|
||||
Dialog::NewAlbum { name, folder, parent, include_selected } => {
|
||||
created_in(app, "album.create", json!({"name": name, "folder": folder, "addSelected": !folder && *include_selected, "parent": parent}))
|
||||
}
|
||||
Dialog::RenameAlbum { id, name } => app.run("album.rename", json!({"id": id, "name": name})),
|
||||
Dialog::TextPrompt { value, command, params, key, .. } => {
|
||||
|
||||
@@ -533,7 +533,7 @@ fn sections_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, on: Option<SidebarS
|
||||
/// The menu of Albums' +: Create Album, Smart Album, Folder…
|
||||
fn new_album_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
if ui.button(crate::i18n::tr("Create Album…")).clicked() {
|
||||
app.ui.dialog = Some(crate::state::Dialog::NewAlbum { name: String::new(), folder: false, parent: None });
|
||||
app.ui.dialog = Some(crate::state::Dialog::NewAlbum { name: String::new(), folder: false, parent: None, include_selected: true });
|
||||
}
|
||||
if ui.button(crate::i18n::tr("Create Smart Album…")).clicked() {
|
||||
app.ui.dialog = Some(crate::state::Dialog::SmartRules {
|
||||
@@ -547,7 +547,7 @@ fn new_album_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
app.ui.dialog = Some(crate::state::Dialog::NewSmartAlbum { name: String::new(), parent: None });
|
||||
}
|
||||
if ui.button(crate::i18n::tr("Create Folder…")).clicked() {
|
||||
app.ui.dialog = Some(crate::state::Dialog::NewAlbum { name: String::new(), folder: true, parent: None });
|
||||
app.ui.dialog = Some(crate::state::Dialog::NewAlbum { name: String::new(), folder: true, parent: None, include_selected: false });
|
||||
}
|
||||
// only once the albums were put in an order by hand
|
||||
if app.session.catalog.album_children_are_ordered(None) {
|
||||
|
||||
@@ -768,12 +768,15 @@ pub enum Dialog {
|
||||
ContactSheet {
|
||||
options: lightcraft_engine::contact_sheet::Options,
|
||||
},
|
||||
/// `parent`: the folder to create it in (none: the top level).
|
||||
/// `parent`: the folder to create it in (none: the top level). `include_selected`: the new album
|
||||
/// holds the selected photos (Lightroom's "Include selected photos"; folders hold none).
|
||||
NewAlbum {
|
||||
name: String,
|
||||
folder: bool,
|
||||
#[serde(default)]
|
||||
parent: Option<u64>,
|
||||
#[serde(default = "yes")]
|
||||
include_selected: bool,
|
||||
},
|
||||
RenameAlbum {
|
||||
id: u64,
|
||||
|
||||
@@ -2186,7 +2186,7 @@ fn right_click_add_to_album_offers_a_new_album_when_there_is_none() {
|
||||
h.step();
|
||||
h.step();
|
||||
match &mut h.app.ui.dialog {
|
||||
Some(crate::state::Dialog::NewAlbum { name, folder: false, parent: None }) => *name = "Trip".into(),
|
||||
Some(crate::state::Dialog::NewAlbum { name, folder: false, parent: None, .. }) => *name = "Trip".into(),
|
||||
other => panic!("New Album dialog expected, got {other:?}"),
|
||||
}
|
||||
h.step();
|
||||
@@ -2309,4 +2309,51 @@ fn deleting_a_photo_asks_first_from_the_key_the_menu_and_the_right_click_menu()
|
||||
click(&mut h, "button:contextDeletePhoto");
|
||||
assert_eq!(h.app.ui.dialog, None);
|
||||
assert!(deleted(&h));
|
||||
/// New Album's "Include selected photos" (as in Lightroom): on by default; unticked, the new album
|
||||
/// starts empty; with nothing selected the option isn't shown, and a folder never has it.
|
||||
#[test]
|
||||
fn new_album_can_leave_the_selected_photos_out() {
|
||||
let mut h = demo([1400.0, 900.0], json!({"view": "photoGrid", "leftPanel": true}));
|
||||
let has = |h: &Headless, id: &str| h.app.widgets.iter().any(|(w, _)| w == id);
|
||||
let photo = lightcraft_catalog::PhotoId(h.app.session.visible_cloned()[1].0);
|
||||
h.app.session.execute("library.select", &json!({"ids": [photo.0]})).unwrap();
|
||||
let create = |h: &mut Headless, command: &str, name: &str| {
|
||||
h.app.run(command, json!({"name": name})).unwrap();
|
||||
h.step();
|
||||
h.step();
|
||||
};
|
||||
let confirm = |h: &mut Headless| {
|
||||
let r = h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.step();
|
||||
h.step();
|
||||
assert!(h.app.ui.dialog.is_none(), "{:?}", h.app.ui.dialog);
|
||||
};
|
||||
let photos = |h: &Headless, name: &str| h.app.session.catalog.albums().find(|a| a.name == name).expect("the album was created").photos.clone();
|
||||
|
||||
// ticked by default: the selection goes in, as before
|
||||
create(&mut h, "dialog.newAlbum", "With");
|
||||
assert!(has(&h, "check:albumIncludeSelected"), "the option is offered with a selection");
|
||||
confirm(&mut h);
|
||||
assert_eq!(photos(&h, "With"), vec![photo]);
|
||||
|
||||
// unticked: an empty album
|
||||
create(&mut h, "dialog.newAlbum", "Without");
|
||||
let r = h.request("ui.clickWidget", json!({"id": "check:albumIncludeSelected"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.step();
|
||||
confirm(&mut h);
|
||||
assert!(photos(&h, "Without").is_empty(), "nothing was added");
|
||||
|
||||
// a folder never offers it
|
||||
create(&mut h, "dialog.newFolder", "Folder");
|
||||
assert!(!has(&h, "check:albumIncludeSelected"), "folders hold no photos");
|
||||
confirm(&mut h);
|
||||
|
||||
// nothing selected: no option, an empty album
|
||||
h.app.session.selection = lightcraft_engine::Selection { ids: vec![], active: None };
|
||||
create(&mut h, "dialog.newAlbum", "Nothing");
|
||||
assert!(!has(&h, "check:albumIncludeSelected"), "no selection, no option");
|
||||
confirm(&mut h);
|
||||
assert!(photos(&h, "Nothing").is_empty());
|
||||
}
|
||||
@@ -104,7 +104,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
|
||||
| LR-LIB-ALLPHOTOS | All photos | P0 | ✅ | `cmd:library.source`, `crates/ui-egui/src/panels/left.rs` | |
|
||||
| LR-LIB-RECENT-ADDED | Recently added | P1 | ✅ | `cmd:library.source` (`recentlyAdded`), `crates/engine/src/view.rs` (`RECENT_DAYS`) | photos imported in the 30 days up to the latest import, newest import first, grouped by import day |
|
||||
| LR-LIB-BYDATE | Browse by date | P1 | ✅ | `cmd:library.filter` (`date`), `crates/ui-egui/src/panels/left.rs` (`date_row`), `crates/catalog/src/query.rs` (`date_groups`) | year → month → day tree with library-wide counts; a row filters the current source by that date. Active filters show as removable chips under the grid header (`crates/ui-egui/src/panels/chips.rs`, with “+N more” and Clear all), the header counts “N of M photos” (matching of the source's total), a badge on the top-bar filter icon counts them, and an empty result says no photos match the active filters |
|
||||
| LR-LIB-ALBUM | Albums | P0 | ✅ | `cmd:album.create`, `cmd:album.rename`, `cmd:album.delete`, `cmd:album.addPhotos`, `cmd:album.removePhotos`, `cmd:dialog.newAlbum`, `crates/engine/src/tests_albums_local.rs`, `crates/ui-egui/src/tests_panels.rs` | photos dragged from the grid onto an album row are added to it (LR-BEHAV-DRAGDROP); albums hold library photos (issue #684): `album.create {addSelected}`, `album.addPhotos` and `album.toggleTarget` (B) skip browsed Local photos and return `skipped` (a toast says so), a Local-only selection is an error and leaves Photo ▸ Add to Album, the grid's right-click Add to Album, a folder row's Add Selected Photos and a drop onto an album row off with the hint to Add to My Photos first (`button:contextAddToAlbum`, `button:contextAlbum:<id>`); right-click a photo ▸ Add to Album lists the albums and ends with New Album…, which creates one holding the selection — also when the library has no album yet (issue #676; `button:contextNewAlbum`) |
|
||||
| LR-LIB-ALBUM | Albums | P0 | ✅ | `cmd:album.create`, `cmd:album.rename`, `cmd:album.delete`, `cmd:album.addPhotos`, `cmd:album.removePhotos`, `cmd:dialog.newAlbum`, `crates/engine/src/tests_albums_local.rs`, `crates/ui-egui/src/tests_panels.rs` | photos dragged from the grid onto an album row are added to it (LR-BEHAV-DRAGDROP); albums hold library photos (issue #684): `album.create {addSelected}`, `album.addPhotos` and `album.toggleTarget` (B) skip browsed Local photos and return `skipped` (a toast says so), a Local-only selection is an error and leaves Photo ▸ Add to Album, the grid's right-click Add to Album, a folder row's Add Selected Photos and a drop onto an album row off with the hint to Add to My Photos first (`button:contextAddToAlbum`, `button:contextAlbum:<id>`); right-click a photo ▸ Add to Album lists the albums and ends with New Album…, which creates one holding the selection — also when the library has no album yet (issue #676; `button:contextNewAlbum`); New Album's "Include selected photos" (`check:albumIncludeSelected`, on by default; `dialog.newAlbum {includeSelected}`) leaves the selection out when unticked, and, as in Lightroom, is only shown when something is selected (never for a folder) |
|
||||
| LR-LIB-FOLDER | Folders of albums | P0 | ✅ | `cmd:album.create` (`folder`, `parent`), `cmd:album.move`, `cmd:album.reorder`, `cmd:album.sort`, `cmd:dialog.newAlbum` / `cmd:dialog.newFolder` / `cmd:dialog.smartAlbum` / `cmd:dialog.newSmartAlbum` (`parent`), `crates/ui-egui/src/panels/left.rs` (`albums_tree`, `folder_menu`, `disclosure_triangle`), `crates/ui-egui/src/tests_panels.rs` | Delete removes a folder and its nested albums as one undoable edit, keeps photos in the library and returns a deleted album view to All Photos (issue #501); folders nest and fold with the same disclosure triangle as Local / By Date / Folders / Keywords (`albumToggle:<id>`; the row folds them too; open by default); right-click a folder ▸ New ▸ Create Album… / Create Smart Album… / Create Smart Album from Filter… / Create Folder… creates inside it (the `dialog.new*` commands take `parent`), opening the folders down to the new row; creating is only allowed inside folders; moving: drag an album or folder row onto a folder (outlined when allowed; a closed folder opens after 0.6 s under the pointer; the "Albums" header takes an item that sits in a folder back to the top level while it is dragged), the Move to menu or `album.move` — one undo step each; not onto a plain album, itself or something inside it; drop on the top / bottom half of an album or the edge of a folder to place it there by hand (`cmd:album.reorder`, a line shows the place; ordered per folder, folders stay first, new albums go after the ordered ones; right-click ▸ Sort Contents A–Z or + ▸ Sort Albums A–Z = `cmd:album.sort`) |
|
||||
| LR-LIB-SMARTALBUM | Smart albums | P1 | ✅ | `cmd:album.createSmart`, `cmd:album.setRules`, `crates/catalog/src/query.rs`, `crates/ui-egui/src/panels/filterbar.rs`, `crates/ui-egui/src/lib.rs` (`album_counts`) | saved filters (rating/flag/label/kind/edited/keyword/camera/lens/date range/text/album) or a rule set from the rule editor (match all / any / none, nested groups; see LRC-LIB-SMARTCOLL), live; sidebar counts are cached per catalog revision (and per minute while an “in the last…” rule is used), counted without building id lists |
|
||||
| LR-LIB-SHARED-ALBUM | Shared albums | P2 | ⬜ | | needs a sharing service |
|
||||
|
||||
Reference in new issue
Block a user