M1: show bottom confirmation for colour label actions
This commit is contained in:
parent
6277af55bf
commit
57c9e252c1
|
|
@ -147,6 +147,7 @@
|
|||
"Cloudy": "曇り",
|
||||
"Collapse All Stacks": "すべてのスタックを閉じる",
|
||||
"Color": "カラー",
|
||||
"Color label cleared": "カラーラベルを解除しました",
|
||||
"Color Grading": "カラーグレーディング",
|
||||
"Color Mixer": "カラーミキサー",
|
||||
"Color Noise Reduction": "カラーノイズ軽減",
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@
|
|||
"Cloudy": "阴天",
|
||||
"Collapse All Stacks": "折叠所有堆叠",
|
||||
"Color": "颜色",
|
||||
"Color label cleared": "已清除颜色标签",
|
||||
"Color Grading": "颜色分级",
|
||||
"Color Mixer": "颜色混合器",
|
||||
"Color Noise Reduction": "颜色降噪",
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@
|
|||
"Cloudy": "陰天",
|
||||
"Collapse All Stacks": "收合所有堆疊",
|
||||
"Color": "色彩",
|
||||
"Color label cleared": "已清除色彩標籤",
|
||||
"Color Grading": "色彩分級",
|
||||
"Color Label Names": "色彩標籤名稱",
|
||||
"Color Label Sets": "色彩標籤集",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ use serde_json::Value;
|
|||
pub use control::{ControlRequest, ControlResponse};
|
||||
pub use state::UiState;
|
||||
|
||||
const TOAST_SECONDS: f64 = 1.4;
|
||||
|
||||
pub type PickFiles = Box<dyn FnMut() -> Vec<String>>;
|
||||
/// A save dialog: suggested file name → chosen path (`None` = cancelled).
|
||||
pub type SaveFile = Box<dyn FnMut(&str) -> Option<String>>;
|
||||
|
|
@ -265,6 +267,19 @@ impl LightcraftApp {
|
|||
return r;
|
||||
}
|
||||
let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string());
|
||||
if r.is_ok() && id == "photo.label" {
|
||||
let text = match params.get("label").and_then(Value::as_str).and_then(lightcraft_catalog::ColorLabel::parse) {
|
||||
Some(label) => {
|
||||
let name =
|
||||
self.session.catalog.custom_label_name(label).map(str::to_owned).unwrap_or_else(|| i18n::tr(&format!("{label:?}")).into());
|
||||
i18n::tr_format!("{name} Label", name = name)
|
||||
}
|
||||
None => i18n::tr("Color label cleared").into(),
|
||||
};
|
||||
// Native menu clicks can arrive before logic() updates last_time after an idle gap.
|
||||
let now = self.tasks.repaint.as_ref().map(|ctx| ctx.input(|i| i.time)).unwrap_or(self.last_time);
|
||||
self.ui.toast = Some((text, now + TOAST_SECONDS));
|
||||
}
|
||||
if let Err(e) = &r {
|
||||
log::warn!("{id}: {e}");
|
||||
self.ui.status = e.clone();
|
||||
|
|
@ -351,7 +366,7 @@ impl LightcraftApp {
|
|||
}
|
||||
|
||||
pub fn toast(&mut self, ctx: &egui::Context, text: impl Into<String>) {
|
||||
self.toast_for(ctx, text, 1.4);
|
||||
self.toast_for(ctx, text, TOAST_SECONDS);
|
||||
}
|
||||
|
||||
/// A toast that stays `secs` seconds (messages that say where to look or what to do next).
|
||||
|
|
|
|||
|
|
@ -424,6 +424,37 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_label_actions_show_feedback_only_after_success() {
|
||||
use lightcraft_catalog::PhotoId;
|
||||
let mut h = library();
|
||||
for shift in [false, true] {
|
||||
for (k, name) in [("6", "Red"), ("7", "Yellow"), ("8", "Green"), ("9", "Blue")] {
|
||||
h.app.session.execute("library.select", &json!({"ids": [1]})).unwrap();
|
||||
h.app.ui.toast = None;
|
||||
key(&mut h, k, shift);
|
||||
assert_eq!(h.app.ui.toast.as_ref().map(|t| t.0.as_str()), Some(format!("{name} Label").as_str()));
|
||||
assert_eq!(h.app.session.active(), Some(PhotoId(if shift { 2 } else { 1 })));
|
||||
}
|
||||
}
|
||||
// Menus and the Info-panel swatches dispatch the same command, including purple/clear.
|
||||
h.app.run("photo.label", json!({"label": "purple"})).unwrap();
|
||||
assert_eq!(h.app.ui.toast.as_ref().map(|t| t.0.as_str()), Some("Purple Label"));
|
||||
h.app.run("photo.label", json!({"label": "none"})).unwrap();
|
||||
assert_eq!(h.app.ui.toast.as_ref().map(|t| t.0.as_str()), Some("Color label cleared"));
|
||||
h.app.session.execute("label.setNames", &json!({"names": {"red": "Needs review"}})).unwrap();
|
||||
key(&mut h, "6", false);
|
||||
assert_eq!(h.app.ui.toast.as_ref().map(|t| t.0.as_str()), Some("Needs review Label"));
|
||||
// Native menu callbacks run before logic() catches up to the current frame's clock.
|
||||
let now = h.view.ctx.input(|i| i.time);
|
||||
h.app.last_time = now - 10.0;
|
||||
h.app.run("photo.label", json!({"label": "blue"})).unwrap();
|
||||
assert!(h.app.ui.toast.as_ref().is_some_and(|t| t.1 > now), "menu feedback must survive an idle gap");
|
||||
h.app.ui.toast = None;
|
||||
assert!(h.app.run("photo.label", json!({"label": "invalid"})).is_err());
|
||||
assert!(h.app.ui.toast.is_none(), "failed actions must not announce success");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shifted_number_punctuation_rates_and_advances() {
|
||||
use lightcraft_catalog::PhotoId;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ Select photos in Photo Grid or Square Grid, then use:
|
|||
Purple and clearing a colour label are available in Photo → Set Color Label and the photo context
|
||||
menu. Purple has no default number key, matching Lightroom Classic. Rejection marks a photo for
|
||||
culling; it does not delete the original or remove it from the library.
|
||||
Setting a colour label shows the same brief bottom toast as a rating, using the colour or your
|
||||
custom label name. Clearing it shows a confirmation too, whether applied by a key, menu or swatch.
|
||||
|
||||
Grid actions apply to all selected photos and support Undo / Redo. In Compare and Survey, culling
|
||||
actions apply to the active photo. Auto Advance moves forward after an action; holding Shift with
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
|||
| LR-LIB-TARGET | Target album | P2 | ✅ | `cmd:album.setTarget`, `cmd:album.toggleTarget` | see LRC-LIB-COLLECTIONS |
|
||||
| LR-LIB-RATING | Star ratings | P0 | ✅ | `cmd:photo.rate` (`advance`), `crates/ui-egui/src/shortcuts.rs` | 0 clears; 1–5 set; Shift+0–5 applies and advances once, including with Auto Advance. Batch grid and undo/redo regressions; macOS native ownership corrected (PR #261) |
|
||||
| LR-LIB-FLAG | Pick / reject flags | P0 | ✅ | `cmd:photo.pick`, `cmd:photo.reject`, `cmd:photo.unflag`, `cmd:photo.flag` | P pick, X reject, U clear; Shift+P picks and advances in both grids (Presets elsewhere); Shift+X/U and legacy Shift+Z advance; no flag cycle; rejection does not delete the file |
|
||||
| LR-LIB-LABEL | Colour labels | P1 | ✅ | `cmd:photo.label`, `cmd:label.setNames`, `cmd:label.names`, `cmd:dialog.labelNames`, keys 6–9 in `crates/ui-egui/src/shortcuts.rs` | Photo menu and grid context menu (coloured, named), Info-panel swatches, label dot in grid cells; editable label names (undoable, journaled); no purple key (as in Lightroom), no custom extra labels |
|
||||
| LR-LIB-LABEL | Colour labels | P1 | ✅ | `cmd:photo.label`, `cmd:label.setNames`, `cmd:label.names`, `cmd:dialog.labelNames`, keys 6–9 in `crates/ui-egui/src/shortcuts.rs` | Photo menu and grid context menu (coloured, named), Info-panel swatches, label dot in grid cells; setting/clearing through keys (including Shift), menus or Info swatches shows the same bottom toast as ratings, with the colour/custom name; editable label names (undoable, journaled); no purple key (as in Lightroom), no custom extra labels |
|
||||
| LR-LIB-KEYWORD | Keywords | P0 | ✅ | `cmd:panel.keywords`, `cmd:photo.setMeta` (`addKeywords`/`removeKeywords`), `cmd:keyword.list`, `cmd:keyword.rename`, `cmd:keyword.delete`, `cmd:keyword.merge`, `cmd:keyword.suggest`, `crates/catalog/src/keywords.rs`, `crates/ui-egui/src/panels/left.rs` | left-panel keyword tree with counts (click filters, children included; context menu rename / merge / delete / add / remove); library-wide ops are one undo step and replay from the op log; suggestions (co-occurring / most used / completions) in the Keywords panel; no keyword drag-and-drop |
|
||||
| LR-LIB-PEOPLE | People / faces | P2 | 🟡 | `cmd:view.people`, `cmd:view.faceBoxes`, `cmd:photo.removeRegion`, `cmd:photo.setRegion`, `cmd:library.filter`, `crates/ui-egui/src/panels/people.rs`, `crates/catalog/src/query.rs` (`people`), `crates/engine/src/media.rs` (`face_job`), `crates/meta/src/xmp.rs` | MVP: named face regions read from XMP (MWG-RS, read-only, see `docs/xmp-interop.md`; clipped to the photo; Lightroom's `Rotation` of ±π/2 and π handled, mirrored orientations taken as written; boxes follow Rotate Left/Right and flips; a sidecar stating `mwg-rs:Regions`, even empty, replaces the photo's on re-read, one without leaves them) become cards in the People view (View ▸ People, or the People button in the toolbar): a close-up of the person's largest face, name and photo count; a click filters the grid (`person` in `library.filter`, `person:` search token, filter chip). Face boxes in the loupe can be switched off (View ▸ Face Boxes, toolbar) resized with eight drag handles and removed with the × shown on hover (catalog-only and undoable, one undo step per drag; the sidecar is never rewritten for either); no moving a box by dragging it yet. No face detection, no unnamed people / suggestions, no manual tagging, no writing regions back |
|
||||
| LR-LIB-STACK | Stacks | P1 | ✅ | `cmd:stack.group`, `cmd:stack.ungroup`, `cmd:stack.toggle`, `cmd:stack.setTop`, `cmd:stack.remove`, `cmd:stack.auto`, `crates/catalog/src/stacks.rs` | grid/filmstrip count badges, expand/collapse, auto-stack by capture time; no visual-similarity auto-stack |
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
## October 2026
|
||||
|
||||
### Library keyboard culling
|
||||
- Setting or clearing a colour label shows a brief bottom confirmation, like rating a photo; custom label names appear in the message too.
|
||||
- On macOS, ratings `0–5`, labels `6–9` and pick/unflag `P/U` now reach the app even when shown in the native menu (issue #283; adapted from PR #261).
|
||||
- `Shift+6–9` labels and advances. `Shift+P` picks and advances in Photo Grid and Square Grid; it opens Presets in other views. With Auto Advance on, Shift still moves only once.
|
||||
- Help → Keyboard Shortcuts includes the number-key bindings. See [library shortcuts](library-shortcuts.md).
|
||||
|
|
|
|||
Loading…
Reference in New Issue