Merge pull request #239 from nledford/feat/smart-album-file-path
Smart albums: File Path rule field
This commit is contained in:
commit
3fe0bef364
|
|
@ -7,7 +7,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{Catalog, Photo};
|
||||
use crate::{Catalog, Photo, Source};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
|
|
@ -62,6 +62,7 @@ pub const FIELDS: &[(&str, &str, Kind)] = &[
|
|||
("keywords", "Keywords", Kind::Keywords),
|
||||
("text", "Any Searchable Text", Kind::Text),
|
||||
("fileName", "Filename", Kind::Text),
|
||||
("filePath", "File Path", Kind::Text),
|
||||
("format", "File Format", Kind::Text),
|
||||
("title", "Title", Kind::Text),
|
||||
("caption", "Caption", Kind::Text),
|
||||
|
|
@ -304,6 +305,20 @@ impl Rule {
|
|||
text(&all)
|
||||
}
|
||||
"fileName" => text(&p.file_name),
|
||||
"filePath" => {
|
||||
// `/` and `\` both separate, whatever platform the catalog came from; demo photos have no path
|
||||
let path = match &p.source {
|
||||
Source::File { path } => path.replace('\\', "/"),
|
||||
Source::Demo { .. } => String::new(),
|
||||
};
|
||||
let want = want.replace('\\', "/");
|
||||
match op {
|
||||
// the whole string, spaces included (not word by word like the other text fields)
|
||||
"contains" => !want.trim().is_empty() && path.to_lowercase().contains(want.trim()),
|
||||
"notContains" => want.trim().is_empty() || !path.to_lowercase().contains(want.trim()),
|
||||
_ => text_op(op, &path, &want),
|
||||
}
|
||||
}
|
||||
"format" => text(&p.format),
|
||||
"title" => text(&m.title),
|
||||
"caption" => text(&m.caption),
|
||||
|
|
@ -445,6 +460,17 @@ mod tests {
|
|||
);
|
||||
no(json!({"rules": [{"field": "keywords", "op": "isEmpty"}]}));
|
||||
yes(json!({"rules": [{"field": "fileName", "op": "startsWith", "value": "img_"}, {"field": "fileName", "op": "endsWith", "value": ".cr2"}]}));
|
||||
// file path: the whole string (spaces included), either separator, case-insensitive
|
||||
let mut fp = photo(3);
|
||||
fp.source = Source::File { path: "D:\\Photos\\Aliah Ira Polanco-Grylls\\2026\\IMG_0042.CR2".into() };
|
||||
let fpm = |v: serde_json::Value| rs(v).matches(&fp, &cat);
|
||||
assert!(fpm(json!({"rules": [{"field": "filePath", "op": "contains", "value": "/Aliah Ira Polanco-Grylls/"}]})));
|
||||
assert!(!fpm(json!({"rules": [{"field": "filePath", "op": "contains", "value": "/Ira Aliah/"}]})));
|
||||
assert!(!fpm(json!({"rules": [{"field": "filePath", "op": "contains", "value": "Polanco Grylls"}]})));
|
||||
assert!(fpm(json!({"rules": [{"field": "filePath", "op": "notContains", "value": "/Other/"}]})));
|
||||
assert!(fpm(json!({"rules": [{"field": "filePath", "op": "startsWith", "value": "d:/photos/"}]})));
|
||||
no(json!({"rules": [{"field": "filePath", "op": "contains", "value": "/Aliah/"}]})); // demo photo has no path
|
||||
yes(json!({"rules": [{"field": "filePath", "op": "isEmpty"}]}));
|
||||
yes(json!({"rules": [{"field": "camera", "op": "contains", "value": "x2"}, {"field": "title", "op": "isEmpty"}]}));
|
||||
yes(json!({"rules": [{"field": "iso", "op": "between", "value": [800, 3200]}, {"field": "aperture", "op": "lte", "value": "f/4"}]}));
|
||||
yes(json!({"rules": [{"field": "megapixels", "op": "gte", "value": 24}]}));
|
||||
|
|
|
|||
|
|
@ -717,8 +717,8 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
|
|||
| LRC-LIB-CATALOG-PANEL | Catalog sets | P1 | ✅ | `cmd:library.source` (`missing`) | all, recently added, picks, recently deleted, missing photos (when any) |
|
||||
| LRC-LIB-CATALOG-LOCK | Catalog open in one program at a time | P1 | ✅ | `cmd:app.openLibrary`, `crates/catalog/src/lock.rs`, `crates/engine/src/library.rs` (`open_library`) | exclusive OS lock on `catalog.lock` for the session (app and `lightcraft-cli --library`); a second opener is refused with who has it (`catalog.lock.owner`: program, process, computer); released by the OS on a crash, so never stale; file systems without locks open unprotected with a warning; no read-only mode; the browser build (OPFS) has no guard across tabs yet. A library that can't be opened at launch (locked, unreadable, newer format, drive missing) shows a window — Try Again, Choose Another Library…, Continue Without Saving (then a banner; nothing written), Quit — instead of a silent demo session (`crates/ui-egui/src/panels/library_problem.rs`) |
|
||||
| LRC-LIB-FOLDERS | Disk folder tree | P1 | ✅ | `cmd:library.browse`, `cmd:folder.rename`, `cmd:folder.move`, `crates/ui-egui/src/panels/left.rs` (`local_section`, `folder_tree`) | standard places + the browsed folder + Browse Folder…, each an expandable tree of subfolders, breadcrumb navigation; context menu: Rename Folder…, Move Folder To…, Show in Finder (on disk, photos relinked; one undo step each: undo / redo move the folder back and forth, refused when the destination is taken) |
|
||||
| LRC-LIB-COLLECTIONS | Collections & sets | P1 | ✅ | `cmd:album.create`, `cmd:album.createSmart`, `cmd:album.toggleTarget`, `cmd:album.setTarget`, `cmd:album.clearQuick` | albums + folders (sets), smart albums (rule editor), Quick Collection and target album (B in the grids adds / removes the selection; album menu ▸ Set as Target Album; the target is marked +) |
|
||||
| LRC-LIB-SMARTCOLL | Smart-collection rules | P1 | ✅ | `cmd:album.createSmart`, `cmd:album.setRules`, `cmd:album.ruleFields`, `cmd:dialog.smartAlbum`, `crates/catalog/src/rules.rs`, `crates/ui-egui/src/panels/rules_editor.rs` | rule editor: match all / any / none, nested groups (⌥+ or + Group), 26 fields (rating, flag, label, type, edits, keywords, any text, filename, format, title, caption, camera, lens, location, creator, copyright, copyright status, capture / import / edit date, ISO, aperture, focal length, megapixels, GPS, virtual copy, album) with text / number / date / in-the-last operators; live match count; also usable as a library filter (`ruleSet`) |
|
||||
| LRC-LIB-COLLECTIONS | Collections & sets | P1 | ✅ | `cmd:album.create`, `cmd:album.createSmart`, `cmd:album.toggleTarget`, `cmd:album.setTarget`, `cmd:album.clearQuick` | albums + folders (sets), smart albums (rule editor; rules incl. File Path — the whole string, either separator, case-insensitive), Quick Collection and target album (B in the grids adds / removes the selection; album menu ▸ Set as Target Album; the target is marked +) |
|
||||
| LRC-LIB-SMARTCOLL | Smart-collection rules | P1 | ✅ | `cmd:album.createSmart`, `cmd:album.setRules`, `cmd:album.ruleFields`, `cmd:dialog.smartAlbum`, `crates/catalog/src/rules.rs`, `crates/ui-egui/src/panels/rules_editor.rs` | rule editor: match all / any / none, nested groups (⌥+ or + Group), 30 fields (rating, flag, label, type, edits, keywords, any text, filename, file path, format, title, caption, camera, lens, location, creator, copyright, copyright status, capture / import / edit date, ISO, aperture, focal length, megapixels, GPS, virtual copy, album) with text / number / date / in-the-last operators; live match count; also usable as a library filter (`ruleSet`) |
|
||||
| LRC-LIB-PUBLISH | Publish services | P2 | ⬜ | | |
|
||||
| LRC-LIB-FILTERBAR | Library filter bar | P1 | ✅ | `cmd:view.filterBar`, `cmd:filter.applyPreset`, `cmd:library.filter` (`labels`, `ruleSet`), `crates/ui-egui/src/panels/filterbar.rs` | rating/flag/label (several labels at once = any of them)/kind/edited/camera/lens/keyword, clear, save as smart album, filter presets; the filter stays as you change albums (always locked, as in the desktop app); arbitrary conditions via the smart-album rule editor |
|
||||
| LRC-LIB-STACKS | Stacks (full) | P1 | ✅ | `crates/catalog/src/stacks.rs`, `cmd:stack.group`, `cmd:stack.split`, `cmd:stack.moveUp`, `cmd:stack.moveDown` | group / ungroup / toggle / set top / remove / auto by time / split / move up and down |
|
||||
|
|
|
|||
Loading…
Reference in New Issue