mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 02:59:17 -06:00
LRC-LIB-KEYWORDS: Edit Keyword Set refuses taken names in the user's words
- Edit Set… on Recent Keywords saves a new set (`new: true`): a name
another set has is refused, where it used to replace that set.
- The dialog checks names before saving and says why in the user's
language ("There is a keyword set “…” already"; Recent Keywords is
built in), instead of showing the engine's message.
- A set deleted while its dialog was open is saved again, as a new set,
rather than refused with no way out but Cancel.
- Save as a new set (Lightroom Classic's Save as New Preset) keeps the
edited set and saves the edits under the new name.
Russian gets "Recent Keywords", which showed in English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
efa758a6ba
commit
94d88dd4cc
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "Auf der obersten Ebene gibt es bereits „{name}“. Beide zusammenführen?",
|
||||
"Added “{keyword}” to {n} photo{}": "„{keyword}“ zu {n} Foto{} hinzugefügt",
|
||||
"On {have} of {of} selected photos": "Auf {have} von {of} ausgewählten Fotos",
|
||||
"Remove “{name}”": "„{name}“ entfernen"
|
||||
"Remove “{name}”": "„{name}“ entfernen",
|
||||
"There is a keyword set “{name}” already": "Es gibt bereits einen Stichwortsatz „{name}“"
|
||||
}
|
||||
|
||||
@@ -1919,5 +1919,7 @@
|
||||
"Edit Set…": "Satz bearbeiten…",
|
||||
"Edit Keyword Set": "Stichwortsatz bearbeiten",
|
||||
"Keywords (⌥1–⌥9)": "Stichwörter (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "Ein Stichwortsatz braucht einen Namen"
|
||||
"A keyword set needs a name": "Ein Stichwortsatz braucht einen Namen",
|
||||
"Save as a new set": "Als neuen Satz speichern",
|
||||
"Recent Keywords is built in: choose another name": "„Letzte Stichwörter“ ist vorgegeben: Wählen Sie einen anderen Namen"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "Ya hay “{name}” en el nivel superior. ¿Combinar las dos?",
|
||||
"Added “{keyword}” to {n} photo{}": "Se añadió “{keyword}” a {n} foto{}",
|
||||
"On {have} of {of} selected photos": "En {have} de {of} fotos seleccionadas",
|
||||
"Remove “{name}”": "Quitar “{name}”"
|
||||
"Remove “{name}”": "Quitar “{name}”",
|
||||
"There is a keyword set “{name}” already": "Ya hay un conjunto de palabras clave “{name}”"
|
||||
}
|
||||
|
||||
@@ -1764,5 +1764,7 @@
|
||||
"Edit Set…": "Editar conjunto…",
|
||||
"Edit Keyword Set": "Editar conjunto de palabras clave",
|
||||
"Keywords (⌥1–⌥9)": "Palabras clave (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "Un conjunto de palabras clave necesita un nombre"
|
||||
"A keyword set needs a name": "Un conjunto de palabras clave necesita un nombre",
|
||||
"Save as a new set": "Guardar como conjunto nuevo",
|
||||
"Recent Keywords is built in: choose another name": "“Palabras clave recientes” es fijo: elige otro nombre"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "最上位にはすでに「{name}」があります。統合しますか?",
|
||||
"Added “{keyword}” to {n} photo{}": "写真{n}枚に「{keyword}」を追加しました{:.0}",
|
||||
"On {have} of {of} selected photos": "選択した写真{of}枚のうち{have}枚",
|
||||
"Remove “{name}”": "「{name}」を削除"
|
||||
"Remove “{name}”": "「{name}」を削除",
|
||||
"There is a keyword set “{name}” already": "キーワードセット「{name}」はすでにあります"
|
||||
}
|
||||
|
||||
@@ -1764,5 +1764,7 @@
|
||||
"Edit Set…": "セットを編集…",
|
||||
"Edit Keyword Set": "キーワードセットを編集",
|
||||
"Keywords (⌥1–⌥9)": "キーワード (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "キーワードセットには名前が必要です"
|
||||
"A keyword set needs a name": "キーワードセットには名前が必要です",
|
||||
"Save as a new set": "新しいセットとして保存",
|
||||
"Recent Keywords is built in: choose another name": "「最近使用したキーワード」は組み込みです。別の名前を選んでください"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "Já existe “{name}” no nível superior. Mesclar as duas?",
|
||||
"Added “{keyword}” to {n} photo{}": "“{keyword}” adicionada a {n} foto{}",
|
||||
"On {have} of {of} selected photos": "Em {have} de {of} fotos selecionadas",
|
||||
"Remove “{name}”": "Remover “{name}”"
|
||||
"Remove “{name}”": "Remover “{name}”",
|
||||
"There is a keyword set “{name}” already": "Já existe um conjunto de palavras-chave “{name}”"
|
||||
}
|
||||
|
||||
@@ -1764,5 +1764,7 @@
|
||||
"Edit Set…": "Editar conjunto…",
|
||||
"Edit Keyword Set": "Editar conjunto de palavras-chave",
|
||||
"Keywords (⌥1–⌥9)": "Palavras-chave (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "Um conjunto de palavras-chave precisa de um nome"
|
||||
"A keyword set needs a name": "Um conjunto de palavras-chave precisa de um nome",
|
||||
"Save as a new set": "Salvar como novo conjunto",
|
||||
"Recent Keywords is built in: choose another name": "“Palavras-chave recentes” é fixo: escolha outro nome"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "На верхнем уровне уже есть «{name}». Объединить их?",
|
||||
"Added “{keyword}” to {n} photo{}": "«{keyword}» добавлено к фото ({n}){:.0}",
|
||||
"On {have} of {of} selected photos": "На {have} из {of} выбранных фото",
|
||||
"Remove “{name}”": "Убрать «{name}»"
|
||||
"Remove “{name}”": "Убрать «{name}»",
|
||||
"There is a keyword set “{name}” already": "Набор ключевых слов «{name}» уже есть"
|
||||
}
|
||||
|
||||
@@ -1439,5 +1439,8 @@
|
||||
"Edit Keyword Set": "Изменить набор ключевых слов",
|
||||
"Keywords (⌥1–⌥9)": "Ключевые слова (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "Набору ключевых слов нужно название",
|
||||
"Set name": "Название набора"
|
||||
"Set name": "Название набора",
|
||||
"Save as a new set": "Сохранить как новый набор",
|
||||
"Recent Keywords is built in: choose another name": "«Недавние ключевые слова» — встроенный набор: выберите другое название",
|
||||
"Recent Keywords": "Недавние ключевые слова"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "顶层已有“{name}”。要合并两者吗?",
|
||||
"Added “{keyword}” to {n} photo{}": "已将“{keyword}”添加到 {n} 张照片{:.0}",
|
||||
"On {have} of {of} selected photos": "所选 {of} 张照片中的 {have} 张",
|
||||
"Remove “{name}”": "移除“{name}”"
|
||||
"Remove “{name}”": "移除“{name}”",
|
||||
"There is a keyword set “{name}” already": "已有关键字集“{name}”"
|
||||
}
|
||||
|
||||
@@ -1764,5 +1764,7 @@
|
||||
"Edit Set…": "编辑集…",
|
||||
"Edit Keyword Set": "编辑关键字集",
|
||||
"Keywords (⌥1–⌥9)": "关键字 (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "关键字集需要名称"
|
||||
"A keyword set needs a name": "关键字集需要名称",
|
||||
"Save as a new set": "另存为新关键字集",
|
||||
"Recent Keywords is built in: choose another name": "“最近使用的关键字”为内置集:请另选名称"
|
||||
}
|
||||
|
||||
@@ -215,5 +215,6 @@
|
||||
"There is a “{name}” at the top level already. Merge the two?": "最上層已有「{name}」。要合併兩者嗎?",
|
||||
"Added “{keyword}” to {n} photo{}": "已將「{keyword}」加入 {n} 張照片{:.0}",
|
||||
"On {have} of {of} selected photos": "選取的 {of} 張照片中的 {have} 張",
|
||||
"Remove “{name}”": "移除「{name}」"
|
||||
"Remove “{name}”": "移除「{name}」",
|
||||
"There is a keyword set “{name}” already": "已有關鍵字集「{name}」"
|
||||
}
|
||||
|
||||
@@ -1764,5 +1764,7 @@
|
||||
"Edit Set…": "編輯集…",
|
||||
"Edit Keyword Set": "編輯關鍵字集",
|
||||
"Keywords (⌥1–⌥9)": "關鍵字 (⌥1–⌥9)",
|
||||
"A keyword set needs a name": "關鍵字集需要名稱"
|
||||
"A keyword set needs a name": "關鍵字集需要名稱",
|
||||
"Save as a new set": "另存為新關鍵字集",
|
||||
"Recent Keywords is built in: choose another name": "「最近使用的關鍵字」為內建集:請另選名稱"
|
||||
}
|
||||
|
||||
@@ -773,7 +773,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
crate::widgets::register(ui.ctx(), id, r.rect);
|
||||
}
|
||||
}
|
||||
Dialog::KeywordSet { name, slots, .. } => {
|
||||
Dialog::KeywordSet { replaces, name, slots, as_new } => {
|
||||
ui.label(egui::RichText::new(crate::i18n::tr("Set name")).color(t.text_dim));
|
||||
let r = crate::text_field::TextField::singleline("field:keywordSetName", name)
|
||||
.hint(crate::i18n::tr("Set name"))
|
||||
@@ -801,6 +801,11 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
});
|
||||
if replaces.is_some() {
|
||||
ui.add_space(6.0);
|
||||
let r = ui.checkbox(as_new, crate::i18n::tr("Save as a new set"));
|
||||
crate::widgets::register(ui.ctx(), "check:keywordSetAsNew", r.rect);
|
||||
}
|
||||
// Return in any field saves the set
|
||||
if returned {
|
||||
confirm = true;
|
||||
@@ -1225,13 +1230,26 @@ pub fn confirm_dialog(app: &mut LightcraftApp, dlg: &Dialog) -> Result<serde_jso
|
||||
}
|
||||
}
|
||||
Dialog::DeleteKeyword { keyword, .. } => app.run("keyword.delete", json!({"keyword": keyword})),
|
||||
Dialog::KeywordSet { replaces, name, slots } => {
|
||||
if name.trim().is_empty() {
|
||||
Dialog::KeywordSet { replaces, name, slots, as_new } => {
|
||||
use lightcraft_catalog::keywords::same;
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err(crate::i18n::tr("A keyword set needs a name").to_string());
|
||||
}
|
||||
let mut params = json!({"name": name.trim(), "keywords": slots});
|
||||
if let Some(old) = replaces {
|
||||
params["replace"] = json!(old);
|
||||
if same(name, lightcraft_engine::cmd::keywords::RECENT) {
|
||||
return Err(crate::i18n::tr("Recent Keywords is built in: choose another name").to_string());
|
||||
}
|
||||
// the set it renames: none when saving as new, or when it was deleted meanwhile (then it is
|
||||
// saved anew rather than refused)
|
||||
let sets = &app.session.keyword_sets;
|
||||
let renames = replaces.as_deref().filter(|_| !as_new).filter(|old| sets.iter().any(|x| same(&x.name, old)));
|
||||
if sets.iter().any(|x| same(&x.name, name) && !renames.is_some_and(|old| same(&x.name, old))) {
|
||||
return Err(crate::i18n::tr_format!("There is a keyword set “{name}” already", name = name));
|
||||
}
|
||||
let mut params = json!({"name": name, "keywords": slots});
|
||||
match renames {
|
||||
Some(old) => params["replace"] = json!(old),
|
||||
None => params["new"] = json!(true),
|
||||
}
|
||||
app.run("keyword.saveSet", params)
|
||||
}
|
||||
|
||||
@@ -602,6 +602,9 @@ mod tests {
|
||||
"Edit Set…",
|
||||
"Edit Keyword Set",
|
||||
"A keyword set needs a name",
|
||||
"Recent Keywords",
|
||||
"Keywords (⌥1–⌥9)",
|
||||
"Save as a new set",
|
||||
];
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("locales");
|
||||
for code in ["de", "es", "pt-br", "ru", "ja", "zh-hans", "zh-hant"] {
|
||||
|
||||
@@ -889,6 +889,7 @@ fn keyword_set(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
replaces: named.then(|| current.clone()),
|
||||
name: if named { current.clone() } else { String::new() },
|
||||
slots,
|
||||
as_new: false,
|
||||
});
|
||||
}
|
||||
if ui.button(crate::i18n::tr("Save Current Keywords as Set…")).clicked() {
|
||||
|
||||
@@ -603,6 +603,9 @@ pub enum Dialog {
|
||||
replaces: Option<String>,
|
||||
name: String,
|
||||
slots: Vec<String>,
|
||||
/// Save as a new set, leaving the edited one (Lightroom Classic's Save as New Preset).
|
||||
#[serde(default)]
|
||||
as_new: bool,
|
||||
},
|
||||
/// Move a keyword inside `parent` (`None`: the top level) where one of its name is already:
|
||||
/// asks before merging the two.
|
||||
|
||||
@@ -49,7 +49,7 @@ fn editing_recent_keywords_saves_them_as_a_set() {
|
||||
let mut h = keywords_panel();
|
||||
let recent = sets(&mut h)["keywords"].clone();
|
||||
open_editor(&mut h);
|
||||
let Some(crate::state::Dialog::KeywordSet { replaces, name, slots }) = h.app.ui.dialog.clone() else { panic!("{:?}", h.app.ui.dialog) };
|
||||
let Some(crate::state::Dialog::KeywordSet { replaces, name, slots, .. }) = h.app.ui.dialog.clone() else { panic!("{:?}", h.app.ui.dialog) };
|
||||
assert_eq!((replaces, name.as_str()), (None, ""));
|
||||
assert_eq!(json!(slots.iter().filter(|k| !k.is_empty()).collect::<Vec<_>>()), recent, "the current nine");
|
||||
ask(&mut h, "ui.text", json!({"text": "Beach Weddings"}));
|
||||
@@ -111,3 +111,52 @@ fn the_slots_are_alike() {
|
||||
assert!((r.width() - slots[0].width()).abs() < 1.0, "{r:?} vs {:?}", slots[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Naming Recent Keywords like a set that exists says so, in the user's words, and keeps the
|
||||
/// dialog (and that set) as they were.
|
||||
#[test]
|
||||
fn a_taken_name_says_so() {
|
||||
let mut h = keywords_panel();
|
||||
h.app.session.execute("keyword.saveSet", &json!({"name": "Travel", "keywords": ["harbour"]})).unwrap();
|
||||
h.app.session.execute("keyword.useSet", &json!({"name": "Recent Keywords"})).unwrap();
|
||||
h.settle(SETTLE);
|
||||
open_editor(&mut h);
|
||||
ask(&mut h, "ui.text", json!({"text": "travel"}));
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "button:dialogOk"}));
|
||||
assert!(matches!(h.app.ui.dialog, Some(crate::state::Dialog::KeywordSet { .. })), "still open");
|
||||
let toast = h.app.ui.toast.clone().map(|t| t.0).unwrap_or_default();
|
||||
assert!(toast.starts_with("There is a keyword set"), "{toast}");
|
||||
let s = sets(&mut h);
|
||||
let travel = s["sets"].as_array().unwrap().iter().find(|x| x["name"] == "Travel").cloned().unwrap();
|
||||
assert_eq!(travel["keywords"], json!(["harbour"]), "left alone");
|
||||
}
|
||||
|
||||
/// A set deleted while its dialog was open is saved again, as a new set, rather than refused.
|
||||
#[test]
|
||||
fn a_set_deleted_meanwhile_is_saved_anew() {
|
||||
let mut h = keywords_panel();
|
||||
h.app.session.execute("keyword.saveSet", &json!({"name": "Weddings", "keywords": ["ceremony"]})).unwrap();
|
||||
h.settle(SETTLE);
|
||||
open_editor(&mut h);
|
||||
h.app.session.execute("keyword.deleteSet", &json!({"name": "Weddings"})).unwrap();
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "button:dialogOk"}));
|
||||
assert_eq!(h.app.ui.dialog, None);
|
||||
assert_eq!(sets(&mut h)["current"], "Weddings");
|
||||
}
|
||||
|
||||
/// Save as a new set (Lightroom Classic's Save as New Preset): the edited set stays, and the new
|
||||
/// one, under its own name, becomes current.
|
||||
#[test]
|
||||
fn save_as_a_new_set_keeps_the_original() {
|
||||
let mut h = keywords_panel();
|
||||
h.app.session.execute("keyword.saveSet", &json!({"name": "Weddings", "keywords": ["ceremony"]})).unwrap();
|
||||
h.settle(SETTLE);
|
||||
open_editor(&mut h);
|
||||
ask(&mut h, "ui.text", json!({"text": "Ceremonies"}));
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "check:keywordSetAsNew"}));
|
||||
ask(&mut h, "ui.clickWidget", json!({"id": "button:dialogOk"}));
|
||||
let s = sets(&mut h);
|
||||
let names: Vec<&str> = s["sets"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
|
||||
assert_eq!(names, ["Recent Keywords", "Weddings", "Ceremonies"]);
|
||||
assert_eq!(s["current"], "Ceremonies");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user