Issue #2980: Clear Color Label can be given a keyboard shortcut

Setting a colour label is photo.label with the colour as a parameter, and the
keyboard shortcuts editor binds a key to a command, not to a parameter, so
only 6-9 had keys and clearing a label could not be given one. photo.clearLabel
is the same as photo.label with none, as a command of its own. The Photo >
Set Color Label > None row runs it and shows its key.
This commit is contained in:
x-labb
2026-10-30 21:03:50 +01:00
parent 8aabf61449
commit cc8a9f3d3d
11 changed files with 249 additions and 7 deletions
+4
View File
@@ -29,6 +29,10 @@ mod flag_cycle_tests;
#[path = "library/tests_rated_out.rs"]
mod rated_out_tests;
#[cfg(test)]
#[path = "library/tests_clear_label.rs"]
mod clear_label_tests;
#[cfg(test)]
#[path = "library/tests_enable_filters.rs"]
mod enable_filters_tests;
+10
View File
@@ -575,6 +575,16 @@ pub fn specs() -> Vec<CommandSpec> {
cull(s, p, |s| for_targets(s, p, "Set Color Label", |id| Some(Op::SetLabel { id, label: l })))
}
),
// `photo.label` takes the colour as a parameter, so a key can't be given to "none" alone
cmd!(
"photo.clearLabel",
"Clear Color Label",
["Photo", "Set Color Label"],
None,
"{ids?, advance?: bool} — `photo.label` with `none`; without `ids`, the selection moves on when this takes the active photo out of the view",
has_selection,
|s, p| { cull(s, p, |s| for_targets(s, p, "Set Color Label", |id| Some(Op::SetLabel { id, label: None }))) }
),
// ---- orientation
cmd!("photo.rotateLeft", "Rotate Left", ["Photo"], Some("Cmd+["), "{ids?}", has_selection, |s, p| rotate(s, p, false)),
cmd!("photo.rotateRight", "Rotate Right", ["Photo"], Some("Cmd+]"), "{ids?}", has_selection, |s, p| rotate(s, p, true)),
@@ -0,0 +1,70 @@
//! `photo.clearLabel`: the colour label taken off, as a command that can have a key of its own (#2980).
use lightcraft_catalog::{ColorLabel, Op, Photo, PhotoId, Source};
use serde_json::json;
use crate::{Result, Session};
fn session() -> Result<Session> {
let mut s = Session::new();
for (id, label) in [(1, Some(ColorLabel::Red)), (2, Some(ColorLabel::Purple)), (3, Some(ColorLabel::Blue)), (4, None)] {
let mut photo = Photo::new(
PhotoId(id),
Source::File { path: format!("/lightcraft-clear-label/{id}.jpg") },
&format!("{id}.jpg"),
"JPEG",
40,
30,
"2026-10-25",
);
photo.label = label;
s.catalog.apply(Op::AddPhoto { photo: Box::new(photo) })?;
}
s.execute("library.sort", &json!({"key": "fileName", "ascending": true}))?;
Ok(s)
}
fn labels(s: &Session) -> Vec<Option<ColorLabel>> {
(1..=4).map(|id| s.catalog.photo(PhotoId(id)).unwrap().label).collect()
}
#[test]
fn the_selected_photos_lose_their_label_in_one_undo_step() {
let mut s = session().unwrap();
s.execute("library.select", &json!({"ids": [1, 2], "active": 2})).unwrap();
s.execute("photo.clearLabel", &json!({})).unwrap();
assert_eq!(labels(&s), [None, None, Some(ColorLabel::Blue), None]);
assert_eq!(s.undo.len(), 1);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(labels(&s), [Some(ColorLabel::Red), Some(ColorLabel::Purple), Some(ColorLabel::Blue), None]);
}
#[test]
fn it_does_what_photo_label_none_does() {
let mut a = session().unwrap();
let mut b = session().unwrap();
for s in [&mut a, &mut b] {
s.execute("library.select", &json!({"ids": [3]})).unwrap();
}
a.execute("photo.clearLabel", &json!({})).unwrap();
b.execute("photo.label", &json!({"label": "none"})).unwrap();
assert_eq!(labels(&a), labels(&b));
assert_eq!(a.undo.len(), b.undo.len());
}
#[test]
fn ids_name_the_photos_and_an_unlabelled_or_unknown_photo_is_handled() {
let mut s = session().unwrap();
s.execute("library.select", &json!({"ids": [4]})).unwrap();
s.execute("photo.clearLabel", &json!({"ids": [1]})).unwrap();
assert_eq!(labels(&s), [None, Some(ColorLabel::Purple), Some(ColorLabel::Blue), None]);
// a photo without a label is left as it is, and a photo that does not exist is an error
s.execute("photo.clearLabel", &json!({"ids": [4]})).unwrap();
assert!(s.execute("photo.clearLabel", &json!({"ids": [999]})).is_err());
}
#[test]
fn it_has_no_key_of_its_own_and_is_listed_for_the_keyboard_shortcuts_editor() {
let spec = crate::command_specs().iter().find(|c| c.id == "photo.clearLabel").expect("registered");
assert_eq!(spec.shortcut, None, "the 6-9 keys are Lightroom's; none is given to clearing");
assert_eq!(spec.label, "Clear Color Label");
}
@@ -65,6 +65,7 @@ fn flags_and_labels_that_leave_the_view_go_on_to_the_next_photo_too() {
(json!({"flag": "pick"}), "photo.cycleFlag", json!({})),
(json!({"flag": "pick"}), "photo.decreaseFlag", json!({})),
(json!({"label": "red"}), "photo.label", json!({"label": "none"})),
(json!({"label": "red"}), "photo.clearLabel", json!({})),
(json!({}), "photo.increaseRating", json!({})),
] {
let mut s = one_star().unwrap();