Show and set colour labels in the grid and develop, and filter by them
Colour labels could be read from a Lightroom sidecar and queried by the selector, but nothing drew one or set one, so the only labels a library held were ones another program had written. Every mark carries its label's initial on its colour — R, Y, G, B, P — so a label is read without telling red from green, which is what NFR-A11Y-3 asks of colour labels by name. A grid cell shows the mark before its filename. In the grid, 6, 7, 8 and 9 set red, yellow, green and blue as Lightroom's keys do, on the photograph under the pointer or on the selection by the rule the star keys follow; the same key again takes the label off, and over a mixed selection it sets it on all. The selection bar gains Label, which opens the six choices — each a mark and a name — and purple, which has no key, is there. In develop the top bar says "Label: Green" beside the mark, opens the same choices, and 6-9 label the open photograph. Each gesture is one catalog transaction, then the grid, the counts and both sidecars are written as a rating's are. The filter bar gains a chip per label, its mark and its name with a count, one at a time; the filter is one SQL term, travels in the place record, and "All" clears it.
This commit is contained in:
@@ -153,6 +153,10 @@ pub struct RatingFilter {
|
||||
/// a star count does, and nothing here rejects or rates a frame — the
|
||||
/// line §3.9.1 draws.
|
||||
pub eyes_open: bool,
|
||||
/// TRACES: FR-CAT-6
|
||||
/// Only photographs carrying this colour label. One at a time, as the
|
||||
/// flag is: the chips are a choice of one pile, not a set of them.
|
||||
pub label: Option<dr_types::ColourLabel>,
|
||||
}
|
||||
|
||||
/// How several people combine when the grid is narrowed by identity.
|
||||
@@ -188,6 +192,7 @@ impl RatingFilter {
|
||||
&& self.captured_to.is_none()
|
||||
&& self.people.is_empty()
|
||||
&& !self.eyes_open
|
||||
&& self.label.is_none()
|
||||
}
|
||||
|
||||
/// Whether a date range is narrowing the grid.
|
||||
@@ -349,6 +354,19 @@ impl RatingFilter {
|
||||
));
|
||||
}
|
||||
|
||||
// TRACES: FR-CAT-6
|
||||
// A code this module owns, formatted in like the flag above. No
|
||||
// coalesce: an unlabelled image's NULL never equals a code, which is
|
||||
// the answer.
|
||||
if let Some(label) = self.label {
|
||||
terms.push(format!(
|
||||
"(SELECT dv.label FROM versions dv
|
||||
WHERE dv.image_id = i.id AND dv.is_default = 1
|
||||
LIMIT 1) = {}",
|
||||
dr_catalog::rating::label_code(label)
|
||||
));
|
||||
}
|
||||
|
||||
if self.local_only {
|
||||
// `tier_actual`, not `tier_desired`: the question is what is
|
||||
// *here*, not what a pin has promised will be. An image queued for
|
||||
@@ -419,6 +437,31 @@ mod tests {
|
||||
assert_eq!(local_original_count(&catalog).unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_label_chip_shows_that_label_and_nothing_else() {
|
||||
// TRACES: FR-CAT-6
|
||||
let catalog = with_images(4);
|
||||
let ids = image_ids(&catalog);
|
||||
let c = catalog.connection();
|
||||
dr_catalog::rating::set_label(c, ids[0], Some(dr_types::ColourLabel::Red)).unwrap();
|
||||
dr_catalog::rating::set_label(c, ids[1], Some(dr_types::ColourLabel::Red)).unwrap();
|
||||
dr_catalog::rating::set_label(c, ids[2], Some(dr_types::ColourLabel::Green)).unwrap();
|
||||
|
||||
let red = RatingFilter {
|
||||
label: Some(dr_types::ColourLabel::Red),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(!red.is_unfiltered());
|
||||
assert_eq!(read_cells_all(&catalog, &red, 0, 120).unwrap().len(), 2);
|
||||
assert_eq!(total_images_filtered(&catalog, &red).unwrap(), 2);
|
||||
|
||||
let purple = RatingFilter {
|
||||
label: Some(dr_types::ColourLabel::Purple),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(total_images_filtered(&catalog, &purple).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cached_preview_is_not_a_local_original() {
|
||||
// The filter answers "can I open this in develop right now", and a
|
||||
|
||||
@@ -914,15 +914,24 @@ mod tests {
|
||||
fn a_label_from_another_device_reaches_the_catalog_and_none_clears_nothing() {
|
||||
// TRACES: FR-CAT-5
|
||||
let cat = library(&["2026/a.CR2"]);
|
||||
assert_eq!(apply_judgement(cat.connection(), 1, "2026/a.drsc", 0, 0, 4).unwrap(), 1);
|
||||
assert_eq!(
|
||||
apply_judgement(cat.connection(), 1, "2026/a.drsc", 0, 0, 4).unwrap(),
|
||||
1
|
||||
);
|
||||
let label = |cat: &Catalog| -> Option<i64> {
|
||||
cat.connection()
|
||||
.query_row("SELECT label FROM versions WHERE is_default = 1", [], |r| r.get(0))
|
||||
.query_row("SELECT label FROM versions WHERE is_default = 1", [], |r| {
|
||||
r.get(0)
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(label(&cat), Some(4));
|
||||
apply_judgement(cat.connection(), 1, "2026/a.drsc", 3, 0, 0).unwrap();
|
||||
assert_eq!(label(&cat), Some(4), "a sidecar with no label left it alone");
|
||||
assert_eq!(
|
||||
label(&cat),
|
||||
Some(4),
|
||||
"a sidecar with no label left it alone"
|
||||
);
|
||||
}
|
||||
|
||||
/// The regression, in one line: a rating in a sidecar reaches the grid.
|
||||
|
||||
Reference in New Issue
Block a user