mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 11:09:23 -06:00
LR-META-INFO: an Info field saves to the photos selected when its edit started
The text fields and GPS saved with no `ids`, so `photo.setMeta` wrote to whatever was selected when the edit ended. A selection change during an edit then gave the typed text to the new selection. Each edit now keeps the selected photos from its start and saves to them.
This commit is contained in:
@@ -3078,6 +3078,47 @@ mod tests {
|
||||
assert_eq!((title(&h, 0), title(&h, 1), title(&h, 2)), ("Same!".into(), "Same!".into(), "Other".into()));
|
||||
}
|
||||
|
||||
/// An Info field saves to the photos selected when its edit started, also when the selection
|
||||
/// changes while the field is being edited.
|
||||
#[test]
|
||||
fn info_field_saves_to_the_photos_selected_when_the_edit_started() {
|
||||
// (tall enough for the GPS field to show without scrolling)
|
||||
let mut h = demo([1300.0, 1800.0]);
|
||||
let t = Duration::from_secs(10);
|
||||
let ask = |h: &mut Headless, method: &str, params: serde_json::Value| {
|
||||
let r = h.request(method, params.clone(), t);
|
||||
assert_eq!(r["ok"], true, "{method} {params}: {r}");
|
||||
h.step();
|
||||
h.step();
|
||||
};
|
||||
let exec = |h: &mut Headless, command: &str, params: serde_json::Value| {
|
||||
ask(h, "engine.execute", json!({"command": command, "params": params}));
|
||||
};
|
||||
let ids: Vec<u64> = h.app.session.visible_cloned().iter().take(4).map(|p| p.0).collect();
|
||||
let meta = |h: &Headless, i: usize| h.app.session.catalog.photo(lightcraft_catalog::PhotoId(ids[i])).unwrap().meta.clone();
|
||||
exec(&mut h, "photo.setMeta", json!({"ids": ids, "title": "Same", "gps": ""}));
|
||||
h.request("ui.set", json!({"view": "detail", "right": "info"}), t);
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[0], ids[1]], "active": ids[0]}));
|
||||
h.settle(SETTLE);
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "field:title"}));
|
||||
ask(&mut h, "ui.key", json!({"key": "End"}));
|
||||
ask(&mut h, "ui.text", json!({"text": "!"}));
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[2]]}));
|
||||
ask(&mut h, "ui.key", json!({"key": "Tab"}));
|
||||
h.settle(SETTLE);
|
||||
assert_eq!((meta(&h, 0).title, meta(&h, 1).title, meta(&h, 2).title), ("Same!".into(), "Same!".into(), "Same".into()));
|
||||
// GPS too (the new selection is two photos as well, so the panel keeps its layout)
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[0], ids[1]], "active": ids[0]}));
|
||||
h.settle(SETTLE);
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "field:gps"}));
|
||||
ask(&mut h, "ui.text", json!({"text": "41.15, -8.61"}));
|
||||
exec(&mut h, "library.select", json!({"ids": [ids[2], ids[3]], "active": ids[2]}));
|
||||
ask(&mut h, "ui.key", json!({"key": "Tab"}));
|
||||
h.settle(SETTLE);
|
||||
let gps: Vec<bool> = (0..4).map(|i| meta(&h, i).gps.is_some()).collect();
|
||||
assert_eq!(gps, [true, true, false, false]);
|
||||
}
|
||||
|
||||
/// Local: a folder's photos show without joining the library; the breadcrumb, Include
|
||||
/// subfolders and Add to My Photos work from the grid header.
|
||||
#[test]
|
||||
|
||||
@@ -756,7 +756,7 @@ fn info(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
let gps = if gps_mixed { String::new() } else { m.gps.map(|(la, lo)| format!("{la:.6}, {lo:.6}")).unwrap_or_default() };
|
||||
let gid = egui::Id::new("info-gps");
|
||||
ui.label(egui::RichText::new("GPS").size(11.5).color(t.text_dim));
|
||||
let mut edit: FieldEdit = ui.data(|d| d.get_temp(gid)).unwrap_or_else(|| FieldEdit::new(&gps, gps_mixed));
|
||||
let mut edit: FieldEdit = ui.data(|d| d.get_temp(gid)).unwrap_or_else(|| FieldEdit::new(&gps, gps_mixed, &selected));
|
||||
let hint = crate::i18n::tr(if gps_mixed { MIXED } else { "latitude, longitude" });
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut edit.text).hint_text(hint).desired_width(f32::INFINITY));
|
||||
crate::widgets::screen_reader_name(ui.ctx(), r.id, "GPS");
|
||||
@@ -770,7 +770,7 @@ fn info(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
let text = edit.text.trim();
|
||||
if r.lost_focus()
|
||||
&& (text != edit.start || edit.clears())
|
||||
&& let Err(e) = app.run("photo.setMeta", json!({"gps": text}))
|
||||
&& let Err(e) = app.run("photo.setMeta", json!({"ids": edit.ids, "gps": text}))
|
||||
{
|
||||
app.toast(ui.ctx(), e);
|
||||
}
|
||||
@@ -915,11 +915,14 @@ const MIXED: &str = "Mixed";
|
||||
|
||||
/// An Info panel field's edit under way, kept in egui memory between frames. A field saves when
|
||||
/// its text is not the text the edit started from, so a field left as it was saves nothing, even
|
||||
/// when what the panel shows changed during the edit.
|
||||
/// when what the panel shows changed during the edit. It saves to the photos selected when the
|
||||
/// edit started, also when the selection changed since.
|
||||
#[derive(Clone)]
|
||||
struct FieldEdit {
|
||||
text: String,
|
||||
start: String,
|
||||
/// The selected photos when the edit started (`photo.setMeta` `ids`).
|
||||
ids: Vec<u64>,
|
||||
/// The field said Mixed when the edit started.
|
||||
mixed: bool,
|
||||
/// Something was typed or deleted since.
|
||||
@@ -927,8 +930,9 @@ struct FieldEdit {
|
||||
}
|
||||
|
||||
impl FieldEdit {
|
||||
fn new(value: &str, mixed: bool) -> Self {
|
||||
FieldEdit { text: value.to_string(), start: value.to_string(), mixed, typed: false }
|
||||
fn new(value: &str, mixed: bool, selected: &[PhotoId]) -> Self {
|
||||
let ids = selected.iter().map(|id| id.0).collect();
|
||||
FieldEdit { text: value.to_string(), start: value.to_string(), ids, mixed, typed: false }
|
||||
}
|
||||
|
||||
/// A Mixed field that was typed in and left empty: it clears the field on every selected photo.
|
||||
@@ -1053,7 +1057,7 @@ fn meta_field(app: &mut LightcraftApp, ui: &mut egui::Ui, label: &str, key: &str
|
||||
ui.label(egui::RichText::new(crate::i18n::tr(label)).size(11.5).color(t.text_dim));
|
||||
let id = egui::Id::new(("info-field", key));
|
||||
let value = if mixed { "" } else { value };
|
||||
let mut edit: FieldEdit = ui.data(|d| d.get_temp(id)).unwrap_or_else(|| FieldEdit::new(value, mixed));
|
||||
let mut edit: FieldEdit = ui.data(|d| d.get_temp(id)).unwrap_or_else(|| FieldEdit::new(value, mixed, &app.session.selected()));
|
||||
let widget = format!("field:{key}");
|
||||
let field = if lines > 1 {
|
||||
crate::text_field::TextField::multiline(&widget, &mut edit.text).rows(lines)
|
||||
@@ -1071,7 +1075,7 @@ fn meta_field(app: &mut LightcraftApp, ui: &mut egui::Ui, label: &str, key: &str
|
||||
}
|
||||
// Return (one line) or leaving saves; Esc gives the edit up
|
||||
if r.committed() && (edit.text != edit.start || edit.clears()) {
|
||||
let _ = app.run("photo.setMeta", json!({key: edit.text}));
|
||||
let _ = app.run("photo.setMeta", json!({"ids": edit.ids, key: edit.text}));
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user