From a3b7c4caf42d7985a3566101117c19e970a44fab Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Fri, 9 Oct 2026 15:17:25 -0400 Subject: [PATCH] LRC-LIB-KEYWORDS: a chip's menu acts on the selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chip menu offered Delete Keyword, which takes the keyword off every photo in the library, beside "Remove from Photo". It now acts on the selected photos only: Add to All Selected Photos (for a keyword only some have), Remove from Selected Photos, Show Photos with Keyword, and Edit Keyword Tag… (the Keyword List's dialog, for renaming too). Deleting a keyword from the library is the Keyword List's. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/locales/de.json | 3 +- crates/ui-egui/locales/es.json | 3 +- crates/ui-egui/locales/ja.json | 3 +- crates/ui-egui/locales/pt-br.json | 3 +- crates/ui-egui/locales/ru.json | 3 +- crates/ui-egui/locales/zh-hans.json | 3 +- crates/ui-egui/locales/zh-hant.json | 3 +- crates/ui-egui/src/panels/keywording.rs | 42 +++++++++++++++---------- crates/ui-egui/src/tests_keywording.rs | 19 +++++++++++ 9 files changed, 59 insertions(+), 23 deletions(-) diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 23a5879e..be30fa17 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -1911,5 +1911,6 @@ "Delete Keyword…": "Stichwort löschen…", "Drop here for the top level": "Hier ablegen für die oberste Ebene", "Their photos and the keywords below them come together under one keyword.": "Ihre Fotos und die Stichwörter darunter kommen unter einem Stichwort zusammen.", - "Select photos to give them keywords.": "Wählen Sie Fotos aus, um ihnen Stichwörter zu geben." + "Select photos to give them keywords.": "Wählen Sie Fotos aus, um ihnen Stichwörter zu geben.", + "Add to All Selected Photos": "Allen ausgewählten Fotos hinzufügen" } diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index c8cd8f87..1095fac2 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -1756,5 +1756,6 @@ "Delete Keyword…": "Eliminar palabra clave…", "Drop here for the top level": "Soltar aquí para el nivel superior", "Their photos and the keywords below them come together under one keyword.": "Sus fotos y las palabras clave que contienen quedan bajo una sola palabra clave.", - "Select photos to give them keywords.": "Selecciona fotos para darles palabras clave." + "Select photos to give them keywords.": "Selecciona fotos para darles palabras clave.", + "Add to All Selected Photos": "Añadir a todas las fotos seleccionadas" } diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index e8610ea0..c17f3b3e 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -1756,5 +1756,6 @@ "Delete Keyword…": "キーワードを削除…", "Drop here for the top level": "ここにドロップすると最上位に移動", "Their photos and the keywords below them come together under one keyword.": "写真と下位のキーワードが1つのキーワードにまとめられます。", - "Select photos to give them keywords.": "写真を選択するとキーワードを付けられます。" + "Select photos to give them keywords.": "写真を選択するとキーワードを付けられます。", + "Add to All Selected Photos": "選択したすべての写真に追加" } diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index 7dddca87..2c26013a 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -1756,5 +1756,6 @@ "Delete Keyword…": "Excluir palavra-chave…", "Drop here for the top level": "Solte aqui para o nível superior", "Their photos and the keywords below them come together under one keyword.": "Suas fotos e as palavras-chave abaixo delas ficam sob uma só palavra-chave.", - "Select photos to give them keywords.": "Selecione fotos para dar palavras-chave a elas." + "Select photos to give them keywords.": "Selecione fotos para dar palavras-chave a elas.", + "Add to All Selected Photos": "Adicionar a todas as fotos selecionadas" } diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index 57440fe7..94d63635 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -1430,5 +1430,6 @@ "Drop here for the top level": "Отпустите здесь для верхнего уровня", "Their photos and the keywords below them come together under one keyword.": "Их фото и вложенные ключевые слова объединятся под одним ключевым словом.", "Select photos to give them keywords.": "Выберите фото, чтобы назначить им ключевые слова.", - "Person": "Человек" + "Person": "Человек", + "Add to All Selected Photos": "Добавить ко всем выбранным фото" } diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index c652318a..184b117e 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -1756,5 +1756,6 @@ "Delete Keyword…": "删除关键字…", "Drop here for the top level": "拖放到此处以移至顶层", "Their photos and the keywords below them come together under one keyword.": "它们的照片和下级关键字将合并到同一个关键字下。", - "Select photos to give them keywords.": "选择照片以为其添加关键字。" + "Select photos to give them keywords.": "选择照片以为其添加关键字。", + "Add to All Selected Photos": "添加到所有所选照片" } diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index d2910338..aa02f290 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -1756,5 +1756,6 @@ "Delete Keyword…": "刪除關鍵字…", "Drop here for the top level": "拖放到此處以移至最上層", "Their photos and the keywords below them come together under one keyword.": "它們的照片與下層關鍵字將合併到同一個關鍵字下。", - "Select photos to give them keywords.": "選取照片以加入關鍵字。" + "Select photos to give them keywords.": "選取照片以加入關鍵字。", + "Add to All Selected Photos": "加入所有選取的照片" } diff --git a/crates/ui-egui/src/panels/keywording.rs b/crates/ui-egui/src/panels/keywording.rs index 73c833d2..09107dc1 100644 --- a/crates/ui-egui/src/panels/keywording.rs +++ b/crates/ui-egui/src/panels/keywording.rs @@ -9,6 +9,30 @@ use crate::LightcraftApp; use crate::theme::Tokens; use crate::widgets::register; +/// A chip's menu: it acts on the selected photos (deleting a keyword from the whole library is the +/// Keyword List's). +fn menu(app: &mut LightcraftApp, ui: &mut egui::Ui, chip: &Chip) { + let item = |ui: &mut egui::Ui, id: &str, label: &str| { + let r = ui.button(label); + register(ui.ctx(), format!("keywordChipMenu:{id}"), r.rect); + r.clicked() + }; + if !chip.on_all() && item(ui, "add", crate::i18n::tr("Add to All Selected Photos")) { + let _ = app.run("photo.setMeta", json!({"addKeywords": [chip.path]})); + } + if item(ui, "remove", crate::i18n::tr("Remove from Selected Photos")) { + let _ = app.run("photo.setMeta", json!({"removeKeywords": [chip.path]})); + } + if item(ui, "show", crate::i18n::tr("Show Photos with Keyword")) { + super::left::browse_all_photos(app, true); + let _ = app.run("library.filter", json!({"keyword": chip.path})); + } + ui.separator(); + if item(ui, "edit", crate::i18n::tr("Edit Keyword Tag…")) { + app.ui.dialog = Some(super::keyword_list::edit_dialog(app, &chip.path)); + } +} + /// The selection's keywords as chips: the name (right-click for its menu), and × to take it off /// every selected photo. One only some of them have is marked with an asterisk. pub fn chip_row(app: &mut LightcraftApp, ui: &mut egui::Ui) { @@ -43,22 +67,8 @@ pub fn chip_row(app: &mut LightcraftApp, ui: &mut egui::Ui) { } label }); - let k = chip.path.clone(); - shown.inner.context_menu(|ui| { - if ui.button(crate::i18n::tr("Remove from Photo")).clicked() { - let _ = app.run("photo.setMeta", json!({"removeKeywords": [k]})); - } - if ui.button(crate::i18n::tr("Show Photos with Keyword")).clicked() { - let _ = app.run("library.filter", json!({"keyword": k})); - } - ui.separator(); - if ui.button(crate::i18n::tr("Rename Keyword…")).clicked() { - app.ui.dialog = Some(crate::state::Dialog::RenameKeyword { from: k.clone(), to: k.clone() }); - } - if ui.button(crate::i18n::tr("Delete Keyword")).clicked() { - let _ = app.run("keyword.delete", json!({"keyword": k})); - } - }); + let chip = chip.clone(); + shown.inner.context_menu(|ui| menu(app, ui, &chip)); } }); } diff --git a/crates/ui-egui/src/tests_keywording.rs b/crates/ui-egui/src/tests_keywording.rs index 8e3468cd..4f8b034f 100644 --- a/crates/ui-egui/src/tests_keywording.rs +++ b/crates/ui-egui/src/tests_keywording.rs @@ -67,3 +67,22 @@ fn only_the_cross_removes_a_keyword() { ask(&mut h, "ui.clickWidget", json!({"id": "keywordChipRemove:beach"})); assert!(ids.iter().all(|id| !keywords_of(&h, *id).contains(&"beach".to_string())), "× took it off both"); } + +/// A chip's menu acts on the selection, never on the whole library: give a keyword only some have +/// to all of them, take it off them, show its photos, or edit it (Edit Keyword Tag). Deleting a +/// keyword from every photo of the library is the Keyword List's. +#[test] +fn a_chips_menu_acts_on_the_selection() { + let (mut h, ids) = two_selected(); + ask(&mut h, "ui.clickWidget", json!({"id": "keywordChip:Weddings", "button": "right"})); + for item in ["add", "remove", "show", "edit"] { + assert!(has(&h, &format!("keywordChipMenu:{item}")), "{item}"); + } + assert!(!has(&h, "keywordChipMenu:delete"), "no library-wide delete"); + ask(&mut h, "ui.clickWidget", json!({"id": "keywordChipMenu:add"})); + assert!(ids.iter().all(|id| keywords_of(&h, *id).contains(&"Weddings".to_string())), "given to both"); + ask(&mut h, "ui.clickWidget", json!({"id": "keywordChip:Weddings", "button": "right"})); + assert!(!has(&h, "keywordChipMenu:add"), "both have it now"); + ask(&mut h, "ui.clickWidget", json!({"id": "keywordChipMenu:edit"})); + assert!(matches!(&h.app.ui.dialog, Some(crate::state::Dialog::KeywordTag { editing: Some(k), .. }) if k == "Weddings"), "{:?}", h.app.ui.dialog); +}