mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
Flag chip and filter bar show what flag and flags let through together
With both set (only via library.filter) they AND, but the chip and the bar's buttons showed their union. Filter::flags_shown gives the flags that pass both; the chip names both constraints when none does.
This commit is contained in:
@@ -270,6 +270,17 @@ impl Filter {
|
||||
self.describe_in(Some(cat))
|
||||
}
|
||||
|
||||
/// The flags that `flag` and `flags` let through together (they AND), in the filter bar's
|
||||
/// order; `None` when neither is set, empty when they exclude each other. The chip and the
|
||||
/// filter bar's buttons show this, so they match what is filtered.
|
||||
pub fn flags_shown(&self) -> Option<Vec<Flag>> {
|
||||
if self.flag.is_none() && self.flags.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let passes = |f: &Flag| self.flag.is_none_or(|x| x == *f) && (self.flags.is_empty() || self.flags.contains(f));
|
||||
Some([Flag::Pick, Flag::Reject, Flag::None].into_iter().filter(passes).collect())
|
||||
}
|
||||
|
||||
fn describe_in(&self, cat: Option<&crate::Catalog>) -> String {
|
||||
let mut v: Vec<String> = Vec::new();
|
||||
if self.rating > 0 {
|
||||
|
||||
@@ -391,6 +391,27 @@ fn filter_any_of_several_flags() {
|
||||
assert_eq!(s.visible_cloned().len(), picks);
|
||||
}
|
||||
|
||||
/// `flag` and `flags` set together (only through `library.filter`) AND, and the chip says what
|
||||
/// they let through, not their union.
|
||||
#[test]
|
||||
fn filter_flag_and_flags_together_chip() {
|
||||
use lightcraft_catalog::Flag;
|
||||
let mut s = crate::Session::with_demo();
|
||||
let ids: Vec<u64> = s.catalog.photos().take(3).map(|p| p.id.0).collect();
|
||||
for (id, f) in ids.iter().zip(["pick", "reject", "none"]) {
|
||||
s.execute("photo.flag", &json!({"ids": [id], "flag": f})).unwrap();
|
||||
}
|
||||
let picks = s.catalog.photos().filter(|p| p.in_library() && p.flag == Flag::Pick).count();
|
||||
let chip = |s: &Session| crate::filter_chips(&s.filter, &s.catalog).into_iter().find(|c| c.label.starts_with("Flag")).expect("a flag chip").label;
|
||||
s.execute("library.filter", &json!({"flag": "pick", "flags": ["pick", "none"]})).unwrap();
|
||||
assert_eq!(s.visible_cloned().len(), picks);
|
||||
assert_eq!(chip(&s), "Flag: pick");
|
||||
// nothing passes both: the chip names both constraints
|
||||
s.execute("library.filter", &json!({"flag": "pick", "flags": ["reject", "none"]})).unwrap();
|
||||
assert_eq!(s.visible_cloned().len(), 0);
|
||||
assert_eq!(chip(&s), "Flag: pick and reject or none");
|
||||
}
|
||||
|
||||
/// Quick Collection / target album: B toggles the selection in it (created on first use);
|
||||
/// another album can be the target; clear empties it; all undoable.
|
||||
#[test]
|
||||
|
||||
@@ -158,11 +158,12 @@ pub fn filter_chips(f: &Filter, cat: &Catalog) -> Vec<FilterChip> {
|
||||
};
|
||||
add(format!("Rating {op} {}★", f.rating), json!({"rating": 0}));
|
||||
}
|
||||
let mut flags = f.flags.clone();
|
||||
flags.extend(f.flag.filter(|fl| !flags.contains(fl)));
|
||||
if !flags.is_empty() {
|
||||
let names: Vec<String> = flags.iter().map(|fl| format!("{fl:?}").to_lowercase()).collect();
|
||||
add(format!("Flag: {}", names.join(" or ")), json!({"flag": Null, "flags": []}));
|
||||
if let Some(shown) = f.flags_shown() {
|
||||
let name = |fl: &lightcraft_catalog::Flag| format!("{fl:?}").to_lowercase();
|
||||
let or = |fls: &[lightcraft_catalog::Flag]| fls.iter().map(name).collect::<Vec<_>>().join(" or ");
|
||||
// empty: `flag` and `flags` exclude each other, so name both constraints
|
||||
let names = if shown.is_empty() { format!("{} and {}", f.flag.as_ref().map(name).unwrap_or_default(), or(&f.flags)) } else { or(&shown) };
|
||||
add(format!("Flag: {names}"), json!({"flag": Null, "flags": []}));
|
||||
}
|
||||
if let Some(shown) = f.labels_shown() {
|
||||
let or = |ls: &[lightcraft_catalog::ColorLabel]| ls.iter().map(|l| cat.label_name(*l)).collect::<Vec<_>>().join(" or ");
|
||||
|
||||
@@ -1611,6 +1611,11 @@ fn filter_bar_flags_combine() {
|
||||
h.request("ui.clickWidget", json!({"id": "filter:none"}), t);
|
||||
let f = h.app.session.filter.clone();
|
||||
assert_eq!((f.flag, f.flags), (None, vec![Flag::Reject, Flag::None]));
|
||||
// both set through the API: only Picked passes, so only Picked is lit and Unflagged joins it
|
||||
h.app.session.execute("library.filter", &json!({"flag": "pick", "flags": ["pick", "none"]})).unwrap();
|
||||
h.request("ui.clickWidget", json!({"id": "filter:none"}), t);
|
||||
let f = h.app.session.filter.clone();
|
||||
assert_eq!((f.flag, f.flags), (None, vec![Flag::Pick, Flag::None]));
|
||||
h.settle(SETTLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -605,6 +605,8 @@ fn german_switches_via_menu_and_control_and_persists() {
|
||||
assert!(label.contains("Color") && !label.contains(&red) && label.contains(&blue), "{label}");
|
||||
let filter = lightcraft_catalog::Filter { flags: vec![lightcraft_catalog::Flag::Pick, lightcraft_catalog::Flag::None], ..Default::default() };
|
||||
assert_eq!(filter_label(&filter, &app.session.catalog), "Markierung: ausgewählt oder keine");
|
||||
let filter = lightcraft_catalog::Filter { flag: Some(lightcraft_catalog::Flag::Pick), ..filter };
|
||||
assert_eq!(filter_label(&filter, &app.session.catalog), "Markierung: ausgewählt");
|
||||
set_language(Locale::En);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,15 @@ pub(crate) fn display_label(chip: &FilterChip, filter: &lightcraft_catalog::Filt
|
||||
return format!("{}: {}", tr("Label"), label_names(filter, catalog));
|
||||
}
|
||||
if chip.clear.get("flags").is_some() {
|
||||
let mut flags = filter.flags.clone();
|
||||
flags.extend(filter.flag.filter(|flag| !flags.contains(flag)));
|
||||
let names = flags.iter().map(|flag| tr(&format!("{flag:?}").to_lowercase()).to_string()).collect::<Vec<_>>();
|
||||
return format!("{}: {}", tr("Flag"), names.join(&format!(" {} ", tr("or"))));
|
||||
let shown = filter.flags_shown().unwrap_or_default();
|
||||
let name = |flag: &lightcraft_catalog::Flag| tr(&format!("{flag:?}").to_lowercase()).to_string();
|
||||
let or = |flags: &[lightcraft_catalog::Flag]| flags.iter().map(name).collect::<Vec<_>>().join(&format!(" {} ", tr("or")));
|
||||
let names = if shown.is_empty() {
|
||||
format!("{} {} {}", filter.flag.as_ref().map(name).unwrap_or_default(), tr("and"), or(&filter.flags))
|
||||
} else {
|
||||
or(&shown)
|
||||
};
|
||||
return format!("{}: {names}", tr("Flag"));
|
||||
}
|
||||
if chip.clear.get("dateFrom").is_some() || chip.clear.get("dateTo").is_some() {
|
||||
return match (&filter.date_from, &filter.date_to) {
|
||||
|
||||
@@ -137,12 +137,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
|
||||
// flags: several can be on at once (any of them matches), as in Lightroom Classic
|
||||
caption(ui, "Flag");
|
||||
let mut chosen: Vec<Flag> = f.flags.clone();
|
||||
if let Some(fl) = f.flag
|
||||
&& !chosen.contains(&fl)
|
||||
{
|
||||
chosen.push(fl);
|
||||
}
|
||||
let chosen: Vec<Flag> = f.flags_shown().unwrap_or_default();
|
||||
for (id, icon, flag, tip) in [
|
||||
("pick", Icon::FlagPick, Flag::Pick, "Picked"),
|
||||
("reject", Icon::FlagReject, Flag::Reject, "Rejected"),
|
||||
|
||||
Reference in New Issue
Block a user