mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 14:39:41 -06:00
stack.toggle: ids name the stacks without selecting them; badges keep the selection and are named for screen readers
A badge click no longer selects the photo first: stack.toggle opts into explicit_targets, so `ids` skip the selection guard and the command refuses ids with no stacked photo. Compare keeps its pair. Both badges announce Expand Stack / Collapse Stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
35be24bcbe
commit
fb3babf8be
@@ -188,12 +188,27 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
Ok(json!({"changed": commit_ops(s, "Split Stack", vec![op])?}))
|
||||
}
|
||||
),
|
||||
cmd!("stack.toggle", "Expand/Collapse Stack", ["Photo", "Stack"], Some("S"), "{ids?, collapsed?: bool}", in_stack, |s, p| {
|
||||
let stacks = stacks_of(s, &s.targets(p)?);
|
||||
let first = stacks.first().and_then(|id| s.catalog.stack(*id)).map(|st| st.collapsed).unwrap_or(false);
|
||||
let collapsed = bool_or(p, "collapsed", !first);
|
||||
set_collapsed(s, stacks, collapsed, if collapsed { "Collapse Stack" } else { "Expand Stack" })
|
||||
}),
|
||||
// `ids` names the stacks' photos without selecting them (a click on a stack badge)
|
||||
CommandSpec {
|
||||
explicit_targets: true,
|
||||
..cmd!(
|
||||
"stack.toggle",
|
||||
"Expand/Collapse Stack",
|
||||
["Photo", "Stack"],
|
||||
Some("S"),
|
||||
"{ids?, collapsed?: bool} — the stacks of the named photos (else of the selection)",
|
||||
in_stack,
|
||||
|s, p| {
|
||||
let stacks = stacks_of(s, &s.targets(p)?);
|
||||
if stacks.is_empty() {
|
||||
return Err(bad("stack.toggle", "none of the photos is in a stack"));
|
||||
}
|
||||
let first = stacks.first().and_then(|id| s.catalog.stack(*id)).map(|st| st.collapsed).unwrap_or(false);
|
||||
let collapsed = bool_or(p, "collapsed", !first);
|
||||
set_collapsed(s, stacks, collapsed, if collapsed { "Collapse Stack" } else { "Expand Stack" })
|
||||
}
|
||||
)
|
||||
},
|
||||
cmd!("stack.expandAll", "Expand All Stacks", ["View", "Stacks"], None, "{}", has_stacks, |s, _| {
|
||||
let all: Vec<StackId> = s.catalog.stacks().map(|st| st.id).collect();
|
||||
set_collapsed(s, all, false, "Expand All Stacks")
|
||||
|
||||
@@ -356,3 +356,27 @@ fn rename_huge_start_is_a_bad_param() {
|
||||
let r = s.execute("photo.renamePreview", &json!({"template": "{seq}", "start": fits + 1, "ids": ids}));
|
||||
assert!(r.is_err(), "{r:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_toggle_by_ids_leaves_the_selection_alone() {
|
||||
let mut s = Session::with_demo();
|
||||
let vis = s.visible_cloned();
|
||||
let (loose, top, member) = (vis[0], vis[1], vis[2]);
|
||||
s.execute("library.select", &json!({"ids": [top.0, member.0], "active": top.0})).unwrap();
|
||||
s.execute("stack.group", &json!({})).unwrap();
|
||||
// a loose photo is selected: the stack is named by `ids` (a badge click), not selected
|
||||
s.execute("library.select", &json!({"ids": [loose.0]})).unwrap();
|
||||
let r = s.execute("stack.toggle", &json!({"ids": [member.0]})).unwrap();
|
||||
assert_eq!(r["changed"], 1, "{r}");
|
||||
assert!(!s.catalog.stack_of(top).unwrap().collapsed);
|
||||
assert_eq!((s.selection.ids.clone(), s.selection.active), (vec![loose], Some(loose)), "the selection is untouched");
|
||||
s.execute("stack.toggle", &json!({"ids": [top.0]})).unwrap();
|
||||
assert!(s.catalog.stack_of(top).unwrap().collapsed);
|
||||
// naming only loose photos is refused without a commit, as is an unknown id
|
||||
let commits = s.commits();
|
||||
assert!(s.execute("stack.toggle", &json!({"ids": [loose.0]})).is_err());
|
||||
assert!(s.execute("stack.toggle", &json!({"ids": [999_999]})).is_err());
|
||||
assert_eq!(s.commits(), commits);
|
||||
// without `ids` the selection still decides (a loose photo selected: not available)
|
||||
assert!(matches!(s.execute("stack.toggle", &json!({})), Err(crate::EngineError::Disabled(..))));
|
||||
}
|
||||
|
||||
@@ -2654,6 +2654,7 @@ pub(crate) fn filmstrip(app: &mut LightcraftApp, ui: &mut egui::Ui, r: Rect) {
|
||||
} else {
|
||||
"Stack — click to collapse"
|
||||
});
|
||||
crate::widgets::announce(&badge, crate::i18n::tr(if collapsed { "Expand Stack" } else { "Collapse Stack" }));
|
||||
register(ui.ctx(), format!("filmstack:{}", id.0), br);
|
||||
let p = ui.painter();
|
||||
let fill = if badge.hovered() { Color32::from_black_alpha(230) } else { Color32::from_black_alpha(170) };
|
||||
|
||||
@@ -1026,6 +1026,7 @@ pub fn stack_badge(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, sid:
|
||||
} else {
|
||||
"Stack — click to collapse (S)"
|
||||
});
|
||||
crate::widgets::announce(&resp, crate::i18n::tr(if collapsed { "Expand Stack" } else { "Collapse Stack" }));
|
||||
register(ui.ctx(), format!("stack:{}", id.0), br);
|
||||
let fill = if resp.hovered() { Color32::from_black_alpha(220) } else { Color32::from_black_alpha(165) };
|
||||
p.rect_filled(br, 10.0, fill);
|
||||
@@ -1039,12 +1040,9 @@ pub fn stack_badge(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId, sid:
|
||||
}
|
||||
}
|
||||
|
||||
/// A click on a stack badge (grid or filmstrip): expand / collapse that photo's stack. The
|
||||
/// photo is selected first when it isn't, as `stack.toggle` acts on a selected stack.
|
||||
/// A click on a stack badge (grid or filmstrip): expand / collapse that photo's stack, selected or
|
||||
/// not. The selection stays as it is (Compare keeps its pair).
|
||||
pub fn toggle_stack(app: &mut LightcraftApp, ctx: &egui::Context, id: PhotoId) {
|
||||
if !app.session.selection.contains(id) {
|
||||
let _ = app.run("library.select", json!({"ids": [id.0]}));
|
||||
}
|
||||
if let Err(e) = app.run("stack.toggle", json!({"ids": [id.0]})) {
|
||||
app.toast(ctx, e);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ fn filmstrip_stack_badge_expands_and_collapses_the_stack() {
|
||||
h.settle(SETTLE);
|
||||
assert!(!h.app.session.catalog.stack(StackId(1)).unwrap().collapsed, "a click on the badge expands the stack");
|
||||
assert!(h.app.session.visible_cloned().contains(&member));
|
||||
assert_eq!(h.app.session.selection.active, Some(loose), "the loupe keeps its photo");
|
||||
let r = h.request("ui.clickWidget", json!({"id": badge}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
|
||||
@@ -189,4 +189,5 @@ fn grid_stack_badge_toggles_a_stack_that_is_not_selected() {
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
assert!(h.app.session.catalog.stack_of(top).unwrap().collapsed, "a click on an unselected stack's badge toggles it");
|
||||
assert_eq!((h.app.session.selection.ids.clone(), h.app.session.selection.active), (vec![loose], Some(loose)), "and leaves the selection alone");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user