mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
Merge PR #3167: LR-BEHAV-DIALOGS: a one-line prompt that's refused stays open and says why
Conflicts: docs/whats-new.md (both entries kept), docs/parity-checklist.md (header change list and revision rows unioned; LR-BEHAV-DIALOGS row keeps main's dialog_copy text plus this PR's addition)
This commit is contained in:
@@ -108,6 +108,7 @@
|
||||
"Moved; {} photo(s) relinked": "Přesunuto; znovu propojeno fotografií: {}",
|
||||
"Named ({named_n})": "Pojmenované ({named_n})",
|
||||
"New Folder in “{name}”": "Nová složka v „{name}“",
|
||||
"Created folder “{name}” in “{parent}”": "Vytvořena složka „{name}“ v „{parent}“",
|
||||
"On {have} of {of} selected photos": "Na {have} z {of} vybraných fotografií",
|
||||
"Painting “{k}”: click photos to add or remove it · Esc stops": "Nanášení „{k}“: kliknutím na fotografie přidáte nebo odeberete slovo · Esc ukončí režim",
|
||||
"Paste into {n} photo{} — only settings that were copied are pasted": "Vložit do fotografií: {n}{:.0} — vloží se pouze zkopírovaná nastavení",
|
||||
|
||||
@@ -247,6 +247,7 @@
|
||||
"{what} merge cancelled": "{what}-Zusammenfügung abgebrochen",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Übersprungen: {n} lokale Fotos{0:.0} – Alben enthalten Bibliotheksfotos (zuerst zu „Meine Fotos“ hinzufügen)",
|
||||
"New Folder in “{name}”": "Neuer Ordner in „{name}“",
|
||||
"Created folder “{name}” in “{parent}”": "Ordner „{name}“ in „{parent}“ erstellt",
|
||||
"Moved {} photo(s)": "{} Foto(s) verschoben",
|
||||
"not added to Apple Photos: {e}": "nicht zu Apple Fotos hinzugefügt: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n} zum Apple-Fotos-Album „{album}“ hinzugefügt",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"{what} merge cancelled": "Combinación {what} cancelada",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Fotos locales omitidas: {n}{0:.0} — los álbumes contienen fotos de la biblioteca (añádelas primero a Mis fotos)",
|
||||
"New Folder in “{name}”": "Nueva carpeta en «{name}»",
|
||||
"Created folder “{name}” in “{parent}”": "Carpeta «{name}» creada en «{parent}»",
|
||||
"Moved {} photo(s)": "{} foto(s) movida(s)",
|
||||
"not added to Apple Photos: {e}": "no se añadieron a Fotos de Apple: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n} añadidas al álbum “{album}” de Fotos de Apple",
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"Moved; {} photo(s) relinked": "Siirretty; yhdistetty uudelleen kuvia: {}",
|
||||
"Named ({named_n})": "Nimetyt ({named_n})",
|
||||
"New Folder in “{name}”": "Uusi kansio kohteessa ”{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Kansio ”{name}” luotu kohteeseen ”{parent}”",
|
||||
"On {have} of {of} selected photos": "{have} / {of} valitulla kuvalla",
|
||||
"Painting “{k}”: click photos to add or remove it · Esc stops": "Maalataan ”{k}”: napsauta kuvia lisätäksesi tai poistaaksesi sen · Esc lopettaa",
|
||||
"Paste into {n} photo{} — only settings that were copied are pasted": "Liitä kuviin: {n}{:.0} — vain kopioidut asetukset liitetään",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"{what} merge cancelled": "Fusion {what} annulée",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Photos locales ignorées : {n}{0:.0} — les albums contiennent des photos de la bibliothèque (ajoutez-les d’abord à Mes photos)",
|
||||
"New Folder in “{name}”": "Nouveau dossier dans « {name} »",
|
||||
"Created folder “{name}” in “{parent}”": "Dossier « {name} » créé dans « {parent} »",
|
||||
"Moved {} photo(s)": "{} photo(s) déplacée(s)",
|
||||
"not added to Apple Photos: {e}": "non ajouté à Apple Photos : {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n} ajouté(s) à l'album Apple Photos « {album} »",
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
"{what} merge cancelled": "Penggabungan {what} dibatalkan",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "{n} foto Lokal{:.0} dilewati: album hanya dapat berisi foto pustaka (tambahkan ke Foto saya terlebih dahulu)",
|
||||
"New Folder in “{name}”": "Folder baru di “{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Folder “{name}” dibuat di “{parent}”",
|
||||
"Moved {} photo(s)": "{} foto dipindahkan",
|
||||
"not added to Apple Photos: {e}": "tidak ditambahkan ke Apple Photos: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n} ditambahkan ke album Apple Photos “{album}”",
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"{what} merge cancelled": "Unione {what} annullata",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Foto locali saltate: {n}{0:.0}; gli album contengono foto della libreria (prima aggiungile a Le mie foto)",
|
||||
"New Folder in “{name}”": "Nuova cartella in “{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Cartella “{name}” creata in “{parent}”",
|
||||
"Moved {} photo(s)": "Foto spostate: {}",
|
||||
"not added to Apple Photos: {e}": "non aggiunte a Foto di Apple: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "Aggiunte all'album “{album}” di Foto di Apple: {n}",
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"{what} merge cancelled": "{what}合成をキャンセルしました",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "ローカル写真{n}枚をスキップしました{0:.0}:アルバムに入れられるのはライブラリの写真です(先にマイフォトに追加してください)",
|
||||
"New Folder in “{name}”": "「{name}」に新規フォルダ",
|
||||
"Created folder “{name}” in “{parent}”": "「{parent}」にフォルダ「{name}」を作成しました",
|
||||
"Moved {} photo(s)": "{} 枚の写真を移動しました",
|
||||
"not added to Apple Photos: {e}": "Appleの写真に追加されませんでした:{e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n}件をAppleの写真のアルバム「{album}」に追加しました",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"Moved; {} photo(s) relinked": "გადატანილია; ხელახლა დაკავშირდა ფოტო: {}",
|
||||
"Named ({named_n})": "სახელიანი ({named_n})",
|
||||
"New Folder in “{name}”": "ახალი საქაღალდე „{name}“-ში",
|
||||
"Created folder “{name}” in “{parent}”": "საქაღალდე „{name}“ შეიქმნა „{parent}“-ში",
|
||||
"On {have} of {of} selected photos": "{of} მონიშნული ფოტოდან {have}-ზე",
|
||||
"Painting “{k}”: click photos to add or remove it · Esc stops": "„{k}“-ის მინიჭება: დაწკაპეთ ფოტოებზე მის დასამატებლად ან მოსახსნელად · Esc ასრულებს",
|
||||
"Paste into {n} photo{} — only settings that were copied are pasted": "ჩასმა ფოტოებში: {n}{:.0} — ჩაისმება მხოლოდ კოპირებული პარამეტრები",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Geometria z Lightrooma dla {path} nie została zachowana; tryb {mode} zostanie obliczony ponownie.",
|
||||
"Moved {} photo(s)": "Przeniesione zdjęcia: {}",
|
||||
"New Folder in “{name}”": "Nowy folder w „{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Utworzono folder „{name}” w „{parent}”",
|
||||
"Restored {} photo{} from Recently Deleted": "Przywrócone z Ostatnio usuniętych zdjęcia: {}{:.0}",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "Korekty XMP dla {path} nie zostały zaimportowane: {fields}.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Profil XMP „{profile}” nie został zastosowany do {path}; kolory mogą się różnić od Lightrooma.",
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"{what} merge cancelled": "{what} mesclagem cancelada",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Fotos locais ignoradas: {n}{0:.0} — álbuns contêm fotos da biblioteca (adicione-as às Minhas fotos primeiro)",
|
||||
"New Folder in “{name}”": "Nova pasta em “{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Pasta “{name}” criada em “{parent}”",
|
||||
"Moved {} photo(s)": "{} foto(s) movida(s)",
|
||||
"not added to Apple Photos: {e}": "não adicionadas ao Fotos da Apple: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "{n} adicionadas ao álbum “{album}” do Fotos da Apple",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"{what} merge cancelled": "{what}: слияние отменено",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Пропущено локальных фото: {n}{0:.0} — в альбомах только фото из библиотеки (сначала добавьте их в «Мои фото»)",
|
||||
"New Folder in “{name}”": "Новая папка в «{name}»",
|
||||
"Created folder “{name}” in “{parent}”": "Папка «{name}» создана в «{parent}»",
|
||||
"Moved {} photo(s)": "Перемещено фото: {}",
|
||||
"not added to Apple Photos: {e}": "не добавлено в Apple Фото: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "Добавлено в альбом «{album}» Apple Фото: {n}",
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"Moved; {} photo(s) relinked": "Flyttat; {} foto(n) har länkats om",
|
||||
"Named ({named_n})": "Namngivna ({named_n})",
|
||||
"New Folder in “{name}”": "Ny mapp i ”{name}”",
|
||||
"Created folder “{name}” in “{parent}”": "Mappen ”{name}” skapad i ”{parent}”",
|
||||
"new, inside {parent}": "ny, inuti {parent}",
|
||||
"none (CPU rendering): {why}": "ingen (CPU-rendering): {why}",
|
||||
"not added to Apple Photos: {e}": "inte tillagt i Apple Photos: {e}",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"{what} merge cancelled": "Об’єднання {what} скасовано",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Пропущено локальних фото: {n}{0:.0} — в альбомах лише фото з бібліотеки (спершу додайте їх до моїх фото)",
|
||||
"New Folder in “{name}”": "Нова тека в «{name}»",
|
||||
"Created folder “{name}” in “{parent}”": "Теку «{name}» створено в «{parent}»",
|
||||
"Moved {} photo(s)": "Переміщено фото: {}",
|
||||
"not added to Apple Photos: {e}": "не додано до Apple Фото: {e}",
|
||||
"{n} added to Apple Photos album “{album}”": "Додано до альбому Apple Фото «{album}»: {n}",
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"Moved; {} photo(s) relinked": "Đã di chuyển; liên kết lại {} ảnh",
|
||||
"Named ({named_n})": "Có tên ({named_n})",
|
||||
"New Folder in “{name}”": "Thư mục mới trong «{name}»",
|
||||
"Created folder “{name}” in “{parent}”": "Đã tạo thư mục «{name}» trong «{parent}»",
|
||||
"On {have} of {of} selected photos": "Trên {have} / {of} ảnh đã chọn",
|
||||
"Painting “{k}”: click photos to add or remove it · Esc stops": "Gán «{k}»: nhấp ảnh để thêm hoặc gỡ · Esc dừng",
|
||||
"Paste into {n} photo{} — only settings that were copied are pasted": "Dán vào {n} ảnh{:.0} — chỉ dán các thiết lập đã sao chép",
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"{what} merge cancelled": "已取消 {what} 合并",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "已跳过 {n} 张本地照片{0:.0}:相册只能收录图库中的照片(请先添加到我的照片)",
|
||||
"New Folder in “{name}”": "在“{name}”中新建文件夹",
|
||||
"Created folder “{name}” in “{parent}”": "已在“{parent}”中创建文件夹“{name}”",
|
||||
"Moved {} photo(s)": "已移动 {} 张照片",
|
||||
"not added to Apple Photos: {e}": "未添加到 Apple 照片:{e}",
|
||||
"{n} added to Apple Photos album “{album}”": "已将 {n} 个添加到 Apple 照片相册“{album}”",
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"{what} merge cancelled": "已取消{what}合併",
|
||||
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "已略過 {n} 張本機照片{0:.0}:相簿只能收錄照片庫中的照片(請先加入我的照片)",
|
||||
"New Folder in “{name}”": "在「{name}」中新增資料夾",
|
||||
"Created folder “{name}” in “{parent}”": "已在「{parent}」中建立資料夾「{name}」",
|
||||
"Moved {} photo(s)": "已移動 {} 張照片",
|
||||
"not added to Apple Photos: {e}": "未加入 Apple 照片:{e}",
|
||||
"{n} added to Apple Photos album “{album}”": "已將 {n} 個加入 Apple 照片相簿「{album}」",
|
||||
|
||||
@@ -414,7 +414,13 @@ pub fn handle(app: &mut LightcraftApp, ctx: &egui::Context, req: &ControlRequest
|
||||
"ui.dialog.confirm" => match app.ui.dialog.take() {
|
||||
Some(d) => {
|
||||
let r = crate::panels::dialogs::confirm_dialog(app, &d);
|
||||
// the import review and the rule editor stay open on an error, as with their buttons
|
||||
// a refused one-line prompt says why on screen, as with its button; a folder made says where
|
||||
match (&r, &d) {
|
||||
(Err(e), crate::state::Dialog::TextPrompt { .. }) => app.toast_error(ctx, e.clone()),
|
||||
(Ok(made), crate::state::Dialog::TextPrompt { .. }) => crate::dialogs::toast_created_folder(app, ctx, &d, made),
|
||||
_ => {}
|
||||
}
|
||||
// the import review, the rule editor and the one-line prompts stay open on an error, as with their buttons
|
||||
if (r.is_err()
|
||||
&& matches!(
|
||||
d,
|
||||
@@ -423,6 +429,7 @@ pub fn handle(app: &mut LightcraftApp, ctx: &egui::Context, req: &ControlRequest
|
||||
| crate::state::Dialog::SmartRules { .. }
|
||||
| crate::state::Dialog::SaveEffect { .. }
|
||||
| crate::state::Dialog::CaptureTime { .. }
|
||||
| crate::state::Dialog::TextPrompt { .. }
|
||||
))
|
||||
|| (r.is_ok() && crate::panels::dialogs::keeps_open(app, &d))
|
||||
|| (r.is_err() && crate::lightroom_destination::keeps_open_on_error(app, &d))
|
||||
|
||||
@@ -489,6 +489,17 @@ fn button_row(app: &LightcraftApp, ui: &mut egui::Ui, dlg: &Dialog, close: &mut
|
||||
}
|
||||
}
|
||||
|
||||
/// Say where a folder made on disk is: the new folder holds no photos yet, so the Folders list may not show it.
|
||||
/// Shared by the dialog's button and `ui.dialog.confirm`.
|
||||
pub(crate) fn toast_created_folder(app: &mut LightcraftApp, ctx: &egui::Context, dlg: &Dialog, made: &serde_json::Value) {
|
||||
if let Dialog::TextPrompt { command, params, .. } = dlg
|
||||
&& command == "folder.create"
|
||||
{
|
||||
let (path, parent) = (made["path"].as_str().unwrap_or_default(), params["parent"].as_str().unwrap_or_default());
|
||||
app.toast(ctx, crate::panels::left::created_text(path, parent));
|
||||
}
|
||||
}
|
||||
|
||||
/// The dialog's confirm was pressed: do it. Whether the dialog closes.
|
||||
fn confirmed(app: &mut LightcraftApp, ctx: &egui::Context, dlg: &mut Dialog) -> bool {
|
||||
let mut closed = false;
|
||||
@@ -522,6 +533,13 @@ fn confirmed(app: &mut LightcraftApp, ctx: &egui::Context, dlg: &mut Dialog) ->
|
||||
*error = Some(e);
|
||||
}
|
||||
}
|
||||
// a one-line prompt that is refused stays open with its text, so the name can be fixed
|
||||
Err(e) if matches!(dlg, Dialog::TextPrompt { .. }) => app.toast_error(ctx, e),
|
||||
// a folder made on disk says so (the new folder holds no photos yet)
|
||||
Ok(r) if matches!(&dlg, Dialog::TextPrompt { command, .. } if command == "folder.create") => {
|
||||
toast_created_folder(app, ctx, dlg, &r);
|
||||
closed = true;
|
||||
}
|
||||
Ok(r) if matches!(dlg, Dialog::NewAlbum { .. }) && r.get("skipped").and_then(serde_json::Value::as_u64).is_some_and(|n| n > 0) => {
|
||||
// albums hold library photos (issue #684): say which part of the selection stayed out
|
||||
crate::panels::grid::toast_skipped(app, ctx, &r);
|
||||
|
||||
@@ -55,6 +55,18 @@ pub fn confirm_dialog(app: &mut LightcraftApp, dlg: &Dialog) -> Result<serde_jso
|
||||
}
|
||||
Dialog::RenameAlbum { id, name } => app.run("album.rename", json!({"id": id, "name": name})),
|
||||
Dialog::TextPrompt { value, command, params, key, .. } => {
|
||||
// Rename Folder opens with the folder's own name typed: confirming it unchanged renames nothing, as it always has
|
||||
// (the engine would refuse it as a name that "already exists"). A name with spaces at its ends is the same
|
||||
// folder when typed back as it is, so it is compared as typed as well as trimmed.
|
||||
let kept = command == "folder.rename"
|
||||
&& params["path"].as_str().is_some_and(|path| {
|
||||
std::path::Path::new(path.trim_end_matches(['/', '\\']))
|
||||
.file_name()
|
||||
.is_some_and(|f| f.to_string_lossy() == value.as_str() || f.to_string_lossy() == value.trim())
|
||||
});
|
||||
if kept {
|
||||
return Ok(serde_json::Value::Null);
|
||||
}
|
||||
let mut p = params.clone();
|
||||
p[key.as_str()] = json!(value.trim());
|
||||
app.run(command, p)
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use albums::{album_drag_feedback, auto_scroll_speed};
|
||||
use albums::{albums_rows, drag_auto_scroll};
|
||||
pub(crate) use dates::browse_all_photos;
|
||||
use dates::date_rows;
|
||||
pub(crate) use folders::{folder_row_of, move_photos, moved_text};
|
||||
pub(crate) use folders::{created_text, folder_row_of, move_photos, moved_text};
|
||||
use folders::{folder_rows, reveal_chosen};
|
||||
use keywords::keyword_rows;
|
||||
pub(crate) use local::{fs_cached, fs_cached_running, same_folder};
|
||||
|
||||
@@ -484,6 +484,13 @@ pub(crate) fn moved_text(n: u64, name: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// What `folder.create` of the folder at `path` inside `parent` says when it succeeds: the folder
|
||||
/// is on disk now, but it has no photos, so the Folders list may not show it yet.
|
||||
pub(crate) fn created_text(path: &str, parent: &str) -> String {
|
||||
let name = std::path::Path::new(path).file_name().map_or_else(|| path.to_string(), |f| f.to_string_lossy().to_string());
|
||||
crate::i18n::tr_format!("Created folder “{name}” in “{parent}”", name = name, parent = lightcraft_catalog::folders::folder_label(parent))
|
||||
}
|
||||
|
||||
/// [`move_photos`], saying how it went in a toast.
|
||||
fn move_photos_here(app: &mut LightcraftApp, ctx: &egui::Context, ids: Option<&[u64]>, path: &str, name: &str) {
|
||||
match move_photos(app, ids, path) {
|
||||
|
||||
@@ -77,6 +77,7 @@ mod switch_library;
|
||||
mod sync;
|
||||
mod tallies;
|
||||
mod temp_slider;
|
||||
mod text_prompt;
|
||||
mod titlebar;
|
||||
mod tool_overlay;
|
||||
mod unsaved;
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
//! A one-line prompt that the engine refuses stays open with its text and says why, so the name can
|
||||
//! be fixed; a folder made on disk says it was made.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::headless::Headless;
|
||||
use crate::state::Dialog;
|
||||
use crate::{LightcraftApp, Services};
|
||||
|
||||
const T: Duration = Duration::from_secs(20);
|
||||
const SETTLE: Duration = Duration::from_secs(60);
|
||||
const CREATED: &str = "Created folder “{name}” in “{parent}”";
|
||||
|
||||
/// A fresh `Shoot` folder holding `Taken` and `Other`, in a temporary folder of its own.
|
||||
fn shoot(tag: &str) -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!("lc-text-prompt-{tag}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let shoot = root.join("Shoot");
|
||||
for name in ["Taken", "Other"] {
|
||||
std::fs::create_dir_all(shoot.join(name)).unwrap();
|
||||
}
|
||||
shoot
|
||||
}
|
||||
|
||||
/// Remove the temporary folder `shoot` made, which holds `dir`.
|
||||
fn clean(dir: &std::path::Path) {
|
||||
let _ = std::fs::remove_dir_all(dir.parent().unwrap());
|
||||
}
|
||||
|
||||
fn app() -> Headless {
|
||||
let session = lightcraft_engine::Session::new().with_fs();
|
||||
let mut h = Headless::new(LightcraftApp::new(session, Services { png: None, ..Default::default() }), [1300.0, 900.0], 1.0);
|
||||
h.settle(SETTLE);
|
||||
h
|
||||
}
|
||||
|
||||
fn prompt(command: &str, value: &str, params: serde_json::Value, key: &str) -> Dialog {
|
||||
Dialog::TextPrompt { title: "Title".into(), hint: "Name".into(), value: value.into(), command: command.into(), params, key: key.into() }
|
||||
}
|
||||
|
||||
/// Open `dialog`, press its OK button, and let the frame settle.
|
||||
fn confirm(h: &mut Headless, dialog: Dialog) {
|
||||
h.app.ui.dialog = Some(dialog);
|
||||
for _ in 0..4 {
|
||||
h.step();
|
||||
}
|
||||
let r = h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.settle(SETTLE);
|
||||
}
|
||||
|
||||
fn toast(h: &Headless) -> String {
|
||||
h.app.ui.toast.as_ref().map(|(t, _, _)| t.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn assert_stays_open(h: &Headless, value: &str, reason: &str) {
|
||||
match &h.app.ui.dialog {
|
||||
Some(Dialog::TextPrompt { value: kept, .. }) => assert_eq!(kept, value, "the typed text is kept"),
|
||||
other => panic!("the prompt stays open: {other:?}"),
|
||||
}
|
||||
assert!(toast(h).contains(reason), "the reason is shown: {:?}", toast(h));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_folder_whose_name_is_taken_keeps_the_prompt_open_and_says_why() {
|
||||
let dir = shoot("create-taken");
|
||||
let mut h = app();
|
||||
confirm(&mut h, prompt("folder.create", "Taken", json!({"parent": dir.to_string_lossy()}), "name"));
|
||||
assert_stays_open(&h, "Taken", "already exists");
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rename_to_a_taken_name_keeps_the_prompt_open_and_says_why() {
|
||||
let dir = shoot("rename-taken");
|
||||
let mut h = app();
|
||||
confirm(&mut h, prompt("folder.rename", "Taken", json!({"path": dir.join("Other").to_string_lossy()}), "name"));
|
||||
assert_stays_open(&h, "Taken", "already exists");
|
||||
assert!(dir.join("Other").is_dir(), "nothing was renamed");
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rename_that_keeps_the_name_closes_the_prompt_and_changes_nothing() {
|
||||
let dir = shoot("rename-same");
|
||||
let mut h = app();
|
||||
// Rename Folder opens with the folder's own name typed
|
||||
confirm(&mut h, prompt("folder.rename", "Other", json!({"path": dir.join("Other").to_string_lossy()}), "name"));
|
||||
assert_eq!(h.app.ui.dialog, None, "the prompt closes");
|
||||
assert!(toast(&h).is_empty(), "no error is shown: {:?}", toast(&h));
|
||||
assert!(dir.join("Other").is_dir(), "nothing was renamed");
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_control_channel_keeps_a_refused_prompt_open_too() {
|
||||
let dir = shoot("control-taken");
|
||||
let mut h = app();
|
||||
h.app.ui.dialog = Some(prompt("folder.create", "Taken", json!({"parent": dir.to_string_lossy()}), "name"));
|
||||
assert!(h.settle(SETTLE), "the prompt renders settled");
|
||||
let r = h.request("ui.dialog.confirm", json!({}), T);
|
||||
assert_ne!(r["ok"], true, "the name is refused: {r}");
|
||||
assert!(r.to_string().contains("already exists"), "{r}");
|
||||
match &h.app.ui.dialog {
|
||||
Some(Dialog::TextPrompt { value, .. }) => assert_eq!(value, "Taken", "the typed text is kept"),
|
||||
other => panic!("the prompt stays open: {other:?}"),
|
||||
}
|
||||
assert!(toast(&h).contains("already exists"), "the reason is on screen too, as with the button: {:?}", toast(&h));
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_control_channel_says_where_a_new_folder_was_made() {
|
||||
let dir = shoot("control-ok");
|
||||
let mut h = app();
|
||||
h.app.ui.dialog = Some(prompt("folder.create", "Selects", json!({"parent": dir.to_string_lossy()}), "name"));
|
||||
assert!(h.settle(SETTLE), "the prompt renders settled");
|
||||
let r = h.request("ui.dialog.confirm", json!({}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
assert!(dir.join("Selects").is_dir(), "made on disk");
|
||||
let parent = lightcraft_catalog::folders::folder_label(&dir.to_string_lossy());
|
||||
assert_eq!(toast(&h), CREATED.replace("{name}", "Selects").replace("{parent}", &parent));
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rename_that_keeps_a_name_with_spaces_at_its_ends_changes_nothing() {
|
||||
let dir = shoot("rename-padded");
|
||||
std::fs::create_dir_all(dir.join(" Pad ")).unwrap();
|
||||
let mut h = app();
|
||||
// Rename Folder opens with the folder's own name typed, spaces and all
|
||||
confirm(&mut h, prompt("folder.rename", " Pad ", json!({"path": dir.join(" Pad ").to_string_lossy()}), "name"));
|
||||
assert_eq!(h.app.ui.dialog, None, "the prompt closes");
|
||||
assert!(toast(&h).is_empty(), "no error is shown: {:?}", toast(&h));
|
||||
assert!(dir.join(" Pad ").is_dir() && !dir.join("Pad").exists(), "the folder keeps its name");
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_folder_closes_the_prompt_and_says_where_it_is() {
|
||||
let dir = shoot("create-ok");
|
||||
let mut h = app();
|
||||
confirm(&mut h, prompt("folder.create", " Selects ", json!({"parent": dir.to_string_lossy()}), "name"));
|
||||
assert!(dir.join("Selects").is_dir(), "made on disk");
|
||||
assert_eq!(h.app.ui.dialog, None);
|
||||
let parent = lightcraft_catalog::folders::folder_label(&dir.to_string_lossy());
|
||||
assert_eq!(toast(&h), CREATED.replace("{name}", "Selects").replace("{parent}", &parent));
|
||||
clean(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_created_folder_toast_comes_through_the_catalog() {
|
||||
let dir = shoot("create-de");
|
||||
let path = dir.join("Selects");
|
||||
let parent = lightcraft_catalog::folders::folder_label(&dir.to_string_lossy());
|
||||
// the catalog's own value (formats have no `Locale` accessor); a language without one shows the English wording
|
||||
let catalog: std::collections::BTreeMap<String, String> = serde_json::from_str(include_str!("../../locales/de-formats.json")).unwrap();
|
||||
let own = catalog.get(CREATED).filter(|text| !text.is_empty()).map_or(CREATED, String::as_str);
|
||||
let want = own.replace("{name}", "Selects").replace("{parent}", &parent);
|
||||
crate::i18n::set_language(crate::i18n::Locale::De);
|
||||
let text = crate::panels::left::created_text(&path.to_string_lossy(), &dir.to_string_lossy());
|
||||
crate::i18n::set_language(crate::i18n::Locale::En);
|
||||
assert_eq!(text, want);
|
||||
clean(&dir);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Lightroom parity checklist
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-27 · **Change:** minor (LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-27 · **Change:** minor (LR-BEHAV-DIALOGS: a one-line prompt that is refused stays open and says why, and a New Folder on Disk says it was made; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
|
||||
have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
|
||||
@@ -468,7 +468,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
|
||||
| LR-BEHAV-RENDER-FIDELITY | Rendering matches Lightroom | P1 | 🟡 | `crates/pipeline/src/lib.rs`, `crates/pipeline/src/tone.rs` | every slider exists and works, but the character of the result (tone curve shape, highlight recovery, texture / clarity / dehaze, noise reduction, sharpening, default look) is tuned by eye; there is no systematic side-by-side comparison against Lightroom on the same CC0 raws. Needs a fidelity suite: Lightroom reference renders kept only in the local `plan/` (never committed), compared per slider and preset with a perceptual metric. Tuning that changes how existing settings render ships as a new process version (LRC-DEV-PROCESS, `docs/process-versions.md`) |
|
||||
| LR-BEHAV-DRAGDROP | Drag and drop | P1 | ✅ | `crates/ui-egui/src/lib.rs`, `crates/ui-egui/src/panels/grid.rs` (`drag_feedback`), `crates/ui-egui/src/panels/left.rs` (`drop_target`, `album_drag_over`, `top_level_drop_target`, `drag_auto_scroll`) | files → app (a drop-target card while they hover, then the import review); grid photos → an album row (adds the selection, with a count badge while dragging); sidebar album / folder rows → a folder (moves it; its name follows the pointer); a drag of albums or grid photos near the top or bottom edge of the sidebar scrolls it (faster nearer the edge, only while the pointer is over the sidebar) |
|
||||
| LR-BEHAV-TOAST | Toast notifications | P1 | ✅ | `crates/ui-egui/src/panels.rs` | |
|
||||
| LR-BEHAV-DIALOGS | Dialogs fit the window | P1 | ✅ | `crates/ui-egui/src/dialogs.rs` (`dialog_body`, `list_height`), `crates/ui-egui/src/tests/dialog_fit.rs`, `crates/ui-egui/src/tests/dialog_copy.rs`, `crates/ui-egui/src/tests/dialogs.rs` (`a_click_outside_closes_the_dialog_when_settings_allow_it`) | a dialog taller than the window (Export on a 1366×768 screen, issue #781) never runs off it: its options scroll while the title bar and the Cancel / confirm row stay on screen (the confirm button names its action: Create, Rename, Create Stacks, Export 3 Photos…, with "OK" kept only for a one-field prompt opened with a command that has no verb, `crates/ui-egui/src/tests/dialog_copy.rs`); lists that scroll on their own (What's New, All Metadata, shortcuts, smart-album rules, credits) get shorter first; dialogs that fit look as before; Import keeps its own resizable, scrolling window; Settings → Dialogs → Close a dialog by clicking outside it (off by default, so a stray click never throws away a dialog's choices): a click pressed and released on the dimmed backdrop closes the dialog like Esc and doesn't reach the panels below, while a drag that ends outside (a slider pulled past the edge) or a click that closes a dropdown keeps it; the import review never closes this way (`clicked_backdrop`) |
|
||||
| LR-BEHAV-DIALOGS | Dialogs fit the window | P1 | ✅ | `crates/ui-egui/src/dialogs.rs` (`dialog_body`, `list_height`), `crates/ui-egui/src/tests/dialog_fit.rs`, `crates/ui-egui/src/tests/dialog_copy.rs`, `crates/ui-egui/src/tests/dialogs.rs` (`a_click_outside_closes_the_dialog_when_settings_allow_it`) | a dialog taller than the window (Export on a 1366×768 screen, issue #781) never runs off it: its options scroll while the title bar and the Cancel / confirm row stay on screen (the confirm button names its action: Create, Rename, Create Stacks, Export 3 Photos…, with "OK" kept only for a one-field prompt opened with a command that has no verb, `crates/ui-egui/src/tests/dialog_copy.rs`); lists that scroll on their own (What's New, All Metadata, shortcuts, smart-album rules, credits) get shorter first; dialogs that fit look as before; Import keeps its own resizable, scrolling window; Settings → Dialogs → Close a dialog by clicking outside it (off by default, so a stray click never throws away a dialog's choices): a click pressed and released on the dimmed backdrop closes the dialog like Esc and doesn't reach the panels below, while a drag that ends outside (a slider pulled past the edge) or a click that closes a dropdown keeps it; the import review never closes this way (`clicked_backdrop`); a one-line prompt (Rename Folder, New Folder on Disk, the preset and version names, New Library when its name can't be used, Auto-Tag from Tracklog) the engine refuses stays open with its text and shows the reason in a toast, so the name can be corrected, where it used to close without a word; a New Folder on Disk that works says so (“Created folder “Selects” in “2026/Shoot””, naming the parent by its last two folders as dialogs do) |
|
||||
| LR-BEHAV-SIDEBAR-COLLAPSE | Collapsible left-sidebar sections | P2 | ✅ | `crates/ui-egui/src/panels/left.rs` (`section`, `section_header`), `crates/ui-egui/src/sidebar.rs` (`SidebarLayout`), `crates/ui-egui/src/tests/panels.rs` | click the Albums, Local, By Date, Folders or Keywords header (or drive `ui.clickWidget` `sidebarSection:<albums\|local\|byDate\|folders\|keywords>`) to fold or unfold the section; a chevron before the title shows the state; the choice is kept across restarts (`sidebar` in the UI state; the `collapsedSidebar` list of older settings files is still read). the click itself is UI-only, like the By Date / Keywords disclosure triangles, and `cmd:view.sidebarSection` `{section, collapsed}` folds and unfolds for agents; photos dropped on a folded Albums section have no target |
|
||||
| LR-BEHAV-SIDEBAR-SECTIONS | Left-sidebar sections share one frame and one arrangement | P2 | ✅ | `crates/ui-egui/src/sidebar.rs` (`SidebarSection`, `SidebarLayout`), `crates/ui-egui/src/panels/left.rs` (`section`, `section_header`, `tree_toggle`), `crates/ui-egui/src/tests/panels.rs` | Albums, Local, By Date, Folders and Keywords are drawn by one `section` frame (the same gap above, header, fold and accessibility name) from one arrangement, `sidebar` in the UI state: a list of `{id, hidden, collapsed}` top to bottom, also settable with `ui.set`. Whatever is read, every section is in it exactly once (unknown and repeated entries are dropped, the rest follow in the usual order), so a damaged or newer settings file never loses the other settings. Rows that open (album folders, Local folders, dates, library folders, keywords) share `tree_toggle`: the same triangle, the same remembered open state, announced as Expand / Collapse. A section with nothing to list (Folders before anything is imported, Keywords before any is added) stays, open, with a one-line note in place of rows (`sidebarEmpty:<id>`) instead of disappearing. `ui.set` reads `sidebar` strictly (`SidebarLayout::parse`): an unknown or repeated section, or a flag that is not true / false, is refused and changes nothing; sections left out follow in the usual order, shown and open; the older `collapsedSidebar` list still folds and unfolds. A section may have one button at the end of its header, declared in one place (`header_button`: an icon, a tooltip, and either an action or a menu): the frame draws it at the visible edge in the header and its pinned copy, keeps its click from folding the section and leaves it room beside a folded section's count. Albums has + (`icon:albumNew`: Create Album / Smart Album / Folder…), Keywords has + (`icon:keywordNew`: Create Keyword Tag, for a keyword inside the default parent, not inside what the Keyword List has picked; once made, Keywords is shown and opened down to it). My Photos (All Photos, Recently Added, Picks, Missing Photos, Recently Deleted) is not a section: it stays on top |
|
||||
| LR-BEHAV-SIDEBAR-HIDE | Show and hide left-sidebar sections | P2 | ✅ | `cmd:view.sidebarSection`, `cmd:view.sidebarReset`, `crates/ui-egui/src/sidebar.rs`, `crates/ui-egui/src/panels/left.rs` (`sections_menu`), `crates/ui-egui/src/menus/menubar.rs`, `crates/ui-egui/src/tests/panels.rs` | View ▸ Sidebar Sections lists Albums, Local, By Date, Folders and Keywords in the sidebar's order, each checked while shown; unchecking one takes its header and rows off the sidebar, checking it puts it back where and how it was (still folded if it was). The same list opens with a right-click on any section header (with Hide This Section first; widgets `sidebarMenu:hide`, `sidebarMenu:section:<id>`, `sidebarMenu:reset`) and on the My Photos title (`sidebarTitle`), which is always there, so hidden sections can always come back. Reset Sidebar Sections (enabled once something changed) shows and opens every section in the usual order. `view.sidebarSection` `{section, show?, collapsed?}` flips shown / hidden when neither flag is given and refuses an unknown section or a flag that is not true / false. Hiding the section of what the grid shows (an album, a keyword filter) leaves the grid as it is; an album made while Albums is hidden or folded brings Albums back, open. Kept with the UI state (`sidebar`). My Photos itself can't be hidden |
|
||||
@@ -961,3 +961,4 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
|
||||
| 2026-10-23 | patch | LRC-LIB-FILTERBAR: showing an album (`library.source`) while the filter holds an album drops the filter's album, as showing a library folder already dropped its folder filter; before, the old album stayed in the filter unseen (the filter bar has no album chip) and narrowed the next source such as All Photos; the row's note says so; status unchanged (✅) |
|
||||
| 2026-10-27 | patch | MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Select Only Active Photo and Paste Settings from Previous were not in the Edit menu's layout and ended up after Settings…, near the end of the menu; they now sit with the other Select and Paste rows. On macOS the Edit menu no longer shows two separators in a row where Settings… moved to the app menu; status unchanged (✅) |
|
||||
| 2026-10-27 | patch | MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say that Close Window and the red close button of the main window quit the app, after the check Quit makes; kept on purpose; a user page says so; status unchanged (✅) |
|
||||
| 2026-10-27 | patch | LR-BEHAV-DIALOGS: a one-line prompt the engine refuses (a name already taken, a bad time zone) stays open with the typed text and shows the reason, instead of closing silently; a New Folder on Disk that works shows “Created folder “…” in “…””; status unchanged (✅) |
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
### The Edit menu groups Select Only Active Photo and Paste Settings from Previous with their kin
|
||||
- Select Only Active Photo now sits under Deselect All, and Paste Settings from Previous under Paste Selected Settings…, instead of both after Settings…, near the end of the Edit menu. On macOS, where Settings… lives in the application menu, the Edit menu no longer shows two separator lines in a row.
|
||||
|
||||
### A refused name prompt stays open and says why
|
||||
- A one-line prompt (Rename Folder, New Folder on Disk, the preset and version names, New Library for a name it can't
|
||||
use, Auto-Tag from Tracklog) that the app refuses, for example a folder name that is taken, now stays open with what
|
||||
you typed and shows the reason, so you can change the name. Before, it closed without a word. A New Folder on Disk
|
||||
that works now says so: “Created folder “Selects” in “2026/Shoot””. Cancel and Esc still close the prompt.
|
||||
|
||||
## 2026-10-25
|
||||
|
||||
### `>=` and `<=` work in the search box
|
||||
|
||||
Reference in New Issue
Block a user