From 28804d55d76b0adff4427c4b3466fbde1c837fdc Mon Sep 17 00:00:00 2001 From: pauliusiv123-rgb <235694617+pauliusiv123-rgb@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:53:34 +0300 Subject: [PATCH] Issue #430: clear the recent-files list, or remove one file from it (#557) --- ATTRIBUTION.toml | 22 +++++++-------- crates/engine/src/commands.rs | 2 ++ crates/ui-egui/src/commands.rs | 15 +++++++++- crates/ui-egui/src/home.rs | 38 +++++++++++++++++++++++-- crates/ui-egui/src/i18n/bg.tsv | 3 ++ crates/ui-egui/src/i18n/cs.tsv | 4 +++ crates/ui-egui/src/i18n/de.tsv | 3 ++ crates/ui-egui/src/i18n/es.tsv | 3 ++ crates/ui-egui/src/i18n/fr.tsv | 3 ++ crates/ui-egui/src/i18n/ja.tsv | 3 ++ crates/ui-egui/src/i18n/pt-br.tsv | 4 +++ crates/ui-egui/src/i18n/ru.tsv | 3 ++ crates/ui-egui/src/i18n/te.tsv | 3 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 3 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 3 ++ crates/ui-egui/tests/commands.rs | 43 +++++++++++++++++++++++++++++ crates/ui-egui/tests/ui.rs | 25 +++++++++++++++++ parity/acrobat-features.toml | 6 ++-- 18 files changed, 168 insertions(+), 18 deletions(-) diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml index 5261bc97..6fcd39e5 100644 --- a/ATTRIBUTION.toml +++ b/ATTRIBUTION.toml @@ -19,7 +19,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "8444a3bd135d61feb6c5b559a9eb0babb50de2ebbd1d86b1e87ccb064e2d3f0b" +sha256 = "6bed3ae5e307b0a225c49bb6a4c688911869f4bbf5df527a3bf84d0997e6baaf" [[asset]] path = "crates/ui-egui/src/i18n/zh-hant.tsv" @@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "8771d80405b8fba9da4a909fdd97b760f6b25a822807ca83ff20dc5cec83a73c" +sha256 = "42c3498eac138f95ac700f71332742658ed59bec171460aa675b1cb67e97ca36" [[asset]] path = "crates/ui-egui/src/i18n/cs.tsv" @@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "bf1ab8079af725264f9c4298b9737359ba831170dcba76a6bd39228239c87e83" +sha256 = "fb315c0c3dfbafd35d73e7dedd1260daa1da919b0b30db53d16725cbd0f0ae12" [[asset]] path = "crates/ui-egui/src/i18n/pt-br.tsv" @@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "b23b03cf4ac163e7a63e49a0900a125507c05ad4fc7930468b859acf32b09ecd" +sha256 = "20043dd4e64d6a8405dbf3167e9f315f68d4894c3d76c0b2b68e9c7e2879c06f" [[asset]] path = "crates/ui-egui/src/i18n/es.tsv" @@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "1b3371f239ca89a658e2c9b80cc070be5cd941dd1ecd3e7a247696716f25721d" +sha256 = "4c576bf5573a127ba2565980623e92b104699e7e03ac68dc3eddf4e788c7e8a4" [[asset]] path = "crates/ui-egui/src/i18n/fr.tsv" @@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "dbf725ffc9177a435e894f4405834e87c70b6fe31a34f1be533c60c356510b97" +sha256 = "66ea8fd85159745626643bfce304dbe7ae0f9ae45a006a5d3834c3cad181080b" [[asset]] path = "crates/ui-egui/src/i18n/de.tsv" @@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "3827d04146f65a02e9d5e69336a97efc46862a48b18a16acbbc023ee0c8b7e1b" +sha256 = "4ba9f2bfc1cca7e819dde79d09c3b128b19a289faf7caa32f6925791683e9538" [[asset]] path = "crates/ui-egui/src/i18n/te.tsv" @@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "f7a5ab0bcbdb8641b1bd4ae25db54ef187757dde3e4fe372e433a9e5c0fc4f3a" +sha256 = "2c399d16589f2d0c3659c87406cb45d27792e7286774ededaa6578a224e2fae9" [[asset]] path = "crates/ui-egui/src/i18n/ru.tsv" @@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "259f795a3cdc032aa670693d2ebff010c5dd3bcf57dda11548d5bd46e78969b8" +sha256 = "1782bfb95566a44e9cc3e22a595ff158965c55b67936d994a8dae9b3e51975a2" [[asset]] path = "crates/ui-egui/src/i18n/bg.tsv" @@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "e71f683e410618200c0abe66a362a720cb8ded5bfd155bf825f92644373c54b5" +sha256 = "eb0b3fa525198152d7cf514284460ff904bcb9af091142ddd1c09deb8461ed69" [[asset]] path = "crates/ui-egui/src/i18n/ja.tsv" @@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0" licence_file = "LICENSE-MIT" kind = "translation" usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" -sha256 = "cdcdba828efea9d402cb9ce8be2b52ab1d58c8f7837d75d3b9c86f2a05e7fb2e" +sha256 = "2bfcb94d72df43a6146e45e971950433a13bb008fc18c68463c096089f715466" [[asset]] path = "assets/icons/accessibility.svg" diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs index eb53cfd2..35576137 100644 --- a/crates/engine/src/commands.rs +++ b/crates/engine/src/commands.rs @@ -150,6 +150,8 @@ const HELP: Option<&str> = Some("Help"); pub const COMMANDS: &[CommandSpec] = &[ c("file.open", "Open…", FILE, Some(Shortcut::cmd("O")), Nothing, "folder-open"), c("file.open_recent", "Open Recent", FILE, None, Nothing, "clock"), + // Shown at the foot of File ▸ Open Recent and on Home, not as its own File menu item. + c("file.clear_recent", "Clear Recent Files", None, None, Nothing, "trash-2"), c("file.pin_folder", "Pin folder to Home…", FILE, None, Nothing, "folder-plus"), c("create.blank", "New blank PDF", FILE, None, Nothing, "file-plus-2"), c("measure.distance", "Measure distance", None, None, Annotate, "ruler"), diff --git a/crates/ui-egui/src/commands.rs b/crates/ui-egui/src/commands.rs index 126b7f31..4cf9d338 100644 --- a/crates/ui-egui/src/commands.rs +++ b/crates/ui-egui/src/commands.rs @@ -85,6 +85,8 @@ impl PdfCraftApp { Some(p) => self.open_recent(&p), None => self.notify_tr("No recent files"), }, + "file.clear_recent" if self.recent.is_empty() => self.notify_tr("No recent files"), + "file.clear_recent" => self.recent.clear(), "file.pin_folder" => self.pin_folder_dialog(), "page.combine" => self.open_combine_tab(), "file.save" => { @@ -570,13 +572,15 @@ pub(crate) fn registry_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui, menu: &str let label = commands::current_label(spec, &app.session, app.active_ids().map(|(_, id)| id)); let label = crate::i18n::menu_label(spec.id, &label); // Open Recent is a submenu of the live recent list, not one action: disabled while the - // list is empty, otherwise each entry opens its file (or focuses the tab showing it). + // list is empty, otherwise each entry opens its file (or focuses the tab showing it), and + // Clear Recent Files at the foot empties the list (#430). if spec.id == "file.open_recent" { if app.recent.is_empty() { ui.add_enabled(false, egui::Button::new(label)); continue; } let mut open: Option = None; + let mut clear = false; ui.menu_button(label, |ui| { for r in &app.recent { if ui.button(&r.name).on_hover_text(&r.path).clicked() { @@ -584,11 +588,20 @@ pub(crate) fn registry_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui, menu: &str ui.close(); } } + ui.separator(); + if ui.button(tl!("Clear Recent Files")).clicked() { + clear = true; + ui.close(); + } }); if let Some(p) = open { app.open_recent(&p); ui.close(); } + if clear { + app.execute("file.clear_recent"); + ui.close(); + } continue; } let shortcut = spec.shortcut.map(|s| s.label(mac)).unwrap_or_default(); diff --git a/crates/ui-egui/src/home.rs b/crates/ui-egui/src/home.rs index 35f4efb6..048aacdd 100644 --- a/crates/ui-egui/src/home.rs +++ b/crates/ui-egui/src/home.rs @@ -104,17 +104,42 @@ pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) { } ui.add_space(26.0); - ui.label(egui::RichText::new(tl!("Recent")).font(theme::semibold(17.0))); + let mut clear = false; + ui.horizontal(|ui| { + ui.label(egui::RichText::new(tl!("Recent")).font(theme::semibold(17.0))); + if !app.recent.is_empty() { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let resp = widgets::ghost_button(ui, "trash-2", tl!("Clear")); + resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!("Clear Recent Files"))); + clear = resp.on_hover_text(tl!("Clear Recent Files")).clicked(); + }); + } + }); ui.add_space(8.0); if app.recent.is_empty() { ui.label(egui::RichText::new(tl!("Files you open in PdfCraft appear here. Drop a PDF anywhere to open it.")).color(t.text_muted)); } let mut open = None; + let mut remove = None; for r in &app.recent { let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 46.0), Sense::click()); resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, &r.name)); - if resp.hovered() { + // The row, not its response: the pointer stays "in" the row over its remove button. + let hovered = ui.rect_contains_pointer(rect); + if hovered { ui.painter().rect_filled(rect, CornerRadius::same(8), t.hover); + // Remove just this file from the list (#430); the file itself is untouched. + let x_rect = Rect::from_center_size(rect.right_center() - vec2(26.0, 0.0), vec2(28.0, 28.0)); + let x = ui.interact(x_rect, ui.id().with(("remove-recent", &r.path)), Sense::click()); + let label = crate::i18n::fmt(tl!("Remove {name} from Recent"), &[("name", &r.name)]); + x.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, &label)); + if x.hovered() { + ui.painter().rect_filled(x_rect, CornerRadius::same(6), t.pressed); + } + icons::paint(ui, x_rect.shrink(6.0), "x", 16.0, t.icon); + if x.on_hover_text(tl!("Remove from Recent")).clicked() { + remove = Some(r.path.clone()); + } } icons::paint( ui, @@ -123,8 +148,9 @@ pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) { 22.0, egui::Color32::from_rgb(0xE0, 0x3E, 0x3E), ); + // Room on the right for the remove button. let detail = ui.painter().text( - rect.right_center() - vec2(12.0, 0.0), + rect.right_center() - vec2(48.0, 0.0), Align2::RIGHT_CENTER, format!("{} {} · {}", r.pages, tl!("pages"), human_size(r.size)), theme::regular(12.0), @@ -141,6 +167,12 @@ pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) { if let Some(p) = open { app.open_recent(&p); } + if let Some(p) = remove { + app.recent.retain(|r| r.path != p); + } + if clear { + app.execute("file.clear_recent"); + } ui.add_space(20.0); widgets::section_title(ui, tl!("Privacy")); ui.label( diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index d260b886..ec604567 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2043,3 +2043,6 @@ signature pad Type Въвеждане action wizard Start Старт Margins (in) Полета (in) Reopen the files that were open when PdfCraft last closed Отваряй отново файловете, които бяха отворени при последното затваряне на PdfCraft + Clear Recent Files Изчисти последно отваряните файлове + Remove from Recent Премахни от последно отваряните + Remove {name} from Recent Премахни {name} от последно отваряните diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index af17a38f..d9a78a03 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -29,6 +29,10 @@ OK OK Open… Otevřít… Open Recent Otevřít nedávné soubory + Clear Recent Files Vymazat nedávné soubory + Remove from Recent Odebrat z nedávných souborů + Remove {name} from Recent Odebrat {name} z nedávných souborů + Clear Vymazat Pin folder to Home… Připnout složku na úvodní stránku… No recent files Žádné nedávné soubory New blank PDF Nový prázdný dokument PDF diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index a8497fc9..cd242d2c 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2045,3 +2045,6 @@ comment menu Edit Bearbeiten signature pad Type Tippen action wizard Start Starten Reopen the files that were open when PdfCraft last closed Beim Start die Dateien wieder öffnen, die beim letzten Beenden von PdfCraft geöffnet waren + Clear Recent Files Liste „Zuletzt geöffnet“ leeren + Remove from Recent Aus „Zuletzt geöffnet“ entfernen + Remove {name} from Recent {name} aus „Zuletzt geöffnet“ entfernen diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c1e3d2fd..80a87c11 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -22,6 +22,9 @@ Name on new comments Nombre en los comentarios nuevos Open… Abrir… Open Recent Abrir recientes + Clear Recent Files Borrar archivos recientes + Remove from Recent Quitar de recientes + Remove {name} from Recent Quitar {name} de recientes Pin folder to Home… Anclar carpeta en Inicio… Folders Carpetas Pin a folder… Anclar una carpeta… diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 1d27e04f..771ba74b 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2014,6 +2014,9 @@ Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Choisissez une image PNG ou JPEG (jusqu’à 4 Mio). La transparence est conservée. Couldn't import the signature image: {e} Impossible d’importer l’image de la signature : {e} Open Recent Ouvrir un fichier récent + Clear Recent Files Effacer les fichiers récents + Remove from Recent Retirer des fichiers récents + Remove {name} from Recent Retirer {name} des fichiers récents Pin folder to Home… Épingler un dossier à l'Accueil… Folders Dossiers Pin a folder… Épingler un dossier… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index bd36ccd7..fc737888 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -28,6 +28,9 @@ Name on new comments 新しい注釈の作成者名 Open… 開く… Open Recent 最近のファイルを開く + Clear Recent Files 最近のファイルをクリア + Remove from Recent 最近のファイルから外す + Remove {name} from Recent {name} を最近のファイルから外す Pin folder to Home… フォルダーをホームにピン留め… Folders フォルダー Pin a folder… フォルダーをピン留め… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 8e38337d..2bd64c7d 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -21,6 +21,10 @@ Name on new comments Nome nos novos comentários Open… Abrir… Open Recent Abrir recentes + Clear Recent Files Limpar arquivos recentes + Remove from Recent Remover dos recentes + Remove {name} from Recent Remover {name} dos recentes + Clear Limpar Pin folder to Home… Fixar pasta no Início… No recent files Nenhum arquivo recente New blank PDF Novo PDF em branco diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 167525de..10ce91df 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -21,6 +21,9 @@ Name on new comments Имя в новых комментариях Open… Открыть… Open Recent Открыть недавние + Clear Recent Files Очистить недавние файлы + Remove from Recent Убрать из недавних + Remove {name} from Recent Убрать {name} из недавних Pin folder to Home… Закрепить папку на главной… Folders Папки Pin a folder… Закрепить папку… diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index 81f47b47..79c09709 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -2017,6 +2017,9 @@ action wizard Start ప్రారంభించు Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. PNG లేదా JPEG చిత్రాన్ని ఎంచుకోండి (గరిష్ఠంగా 4 MiB). పారదర్శకత అలాగే ఉంటుంది. Couldn't import the signature image: {e} సంతకం చిత్రాన్ని దిగుమతి చేయడం సాధ్యం కాలేదు: {e} Open Recent ఇటీవలివి తెరువు + Clear Recent Files ఇటీవలి ఫైళ్లను క్లియర్ చేయి + Remove from Recent ఇటీవలి ఫైళ్ల నుండి తీసివేయి + Remove {name} from Recent {name}ను ఇటీవలి ఫైళ్ల నుండి తీసివేయి Pin folder to Home… ఫోల్డర్‌ను హోమ్‌కు పిన్ చేయి… No recent files ఇటీవలి ఫైళ్లు లేవు Go to page… పేజీకి వెళ్లు… diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 5b66f382..6b0650f7 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -62,6 +62,9 @@ Name on new comments 新注释的作者姓名 Open… 打开… Open Recent 打开最近的文件 + Clear Recent Files 清除最近的文件 + Remove from Recent 从最近的文件中移除 + Remove {name} from Recent 从最近的文件中移除{name} Pin folder to Home… 将文件夹固定到主页… Folders 文件夹 Pin a folder… 固定文件夹… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 9a9e1e85..98f4893f 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -21,6 +21,9 @@ Name on new comments 新註解的作者名稱 Open… 開啟… Open Recent 開啟最近的檔案 + Clear Recent Files 清除最近的檔案 + Remove from Recent 從最近的檔案中移除 + Remove {name} from Recent 從最近的檔案中移除{name} Pin folder to Home… 將資料夾釘選到首頁… Folders 資料夾 Pin a folder… 釘選資料夾… diff --git a/crates/ui-egui/tests/commands.rs b/crates/ui-egui/tests/commands.rs index 27ac8304..7119821e 100644 --- a/crates/ui-egui/tests/commands.rs +++ b/crates/ui-egui/tests/commands.rs @@ -243,6 +243,49 @@ fn the_open_recent_menu_is_disabled_while_the_list_is_empty() { ); } +fn recent_file(name: &str) -> pdfcraft_ui_egui::RecentFile { + pdfcraft_ui_egui::RecentFile { name: name.into(), path: format!("/nowhere/{name}"), pages: 1, size: 0 } +} + +#[test] +fn clear_recent_files_at_the_foot_of_open_recent_empties_the_list() { + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| { + let mut app = PdfCraftApp::new(); + app.open_bytes("doc.pdf", None, fixture(3)).unwrap(); + app.recent.extend([recent_file("first.pdf"), recent_file("second.pdf")]); + app + }); + h.run_steps(4); + h.get_by_label("Menu").click(); + h.run_steps(2); + h.get_by_label("File ⏵").hover(); + h.run_steps(3); + h.get_by_label("Open Recent ⏵").hover(); + h.run_steps(3); + h.get_by_label("Clear Recent Files").click(); + h.run_steps(3); + assert!(h.state().recent.is_empty(), "the list is empty"); + h.get_by_label("Menu").click(); + h.run_steps(2); + h.get_by_label("File ⏵").hover(); + h.run_steps(3); + assert!(h.query_by(|n| n.label().as_deref() == Some("Open Recent") && n.is_disabled()).is_some(), "and Open Recent is disabled"); +} + +#[test] +fn clear_recent_files_runs_from_the_palette_and_is_saved() { + let mut app = PdfCraftApp::new(); + app.recent.extend([recent_file("first.pdf"), recent_file("second.pdf")]); + assert!(app.execute("file.clear_recent")); + assert!(app.recent.is_empty()); + let mut restarted = PdfCraftApp::new(); + restarted.recent.push(recent_file("stale.pdf")); + restarted.restore(&app.persist()); + assert!(restarted.recent.is_empty(), "the empty list is what's saved"); + app.execute("file.clear_recent"); + assert!(app.toast.as_ref().is_some_and(|(m, _)| m.contains("No recent files")), "nothing to clear is said, not silent"); +} + #[test] fn the_shortcuts_dialog_lists_the_real_bindings() { let mut h = harness(); diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs index 819adaea..970c5173 100644 --- a/crates/ui-egui/tests/ui.rs +++ b/crates/ui-egui/tests/ui.rs @@ -67,6 +67,31 @@ fn long_recent_names_and_paths_are_cut_before_the_page_count() { } } +#[test] +fn home_removes_one_recent_file_or_clears_them_all() { + // Tall enough that the Recent list is on screen without scrolling. + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 1600.0)).build_eframe(|_cc| { + let mut app = PdfCraftApp::new(); + for name in ["first.pdf", "second.pdf", "third.pdf"] { + app.recent.push(pdfcraft_ui_egui::RecentFile { name: name.into(), path: format!("/nowhere/{name}"), pages: 1, size: 0 }); + } + app + }); + h.run_steps(4); + assert!(h.query_by_label("Remove second.pdf from Recent").is_none(), "the remove button shows only over its row"); + h.get_by_label("second.pdf").hover(); + h.run_steps(2); + h.get_by_label("Remove second.pdf from Recent").click(); + h.run_steps(2); + let names: Vec = h.state().recent.iter().map(|r| r.name.clone()).collect(); + assert_eq!(names, ["first.pdf", "third.pdf"], "only that file left the list"); + h.get_by_label("Clear Recent Files").click(); + h.run_steps(2); + assert!(h.state().recent.is_empty(), "Clear empties the list"); + h.get_by_label_contains("Files you open in PdfCraft appear here"); + assert!(h.query_by_label("Clear Recent Files").is_none(), "and goes away with nothing left to clear"); +} + #[test] fn every_catalog_tool_is_listed_after_view_more() { let mut h = harness(|_| {}); diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index d711938d..1108c789 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -656,10 +656,10 @@ area = "A" tier = "P1" milestone = "M3" status = "shipped" -commands = ["file.open_recent"] +commands = ["file.open_recent", "file.clear_recent"] tools = [] -evidence = ["crates/ui-egui/tests/ui.rs::persistence_round_trips_and_tolerates_garbage", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_lists_files_and_opens_one", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_is_disabled_while_the_list_is_empty"] -notes = "never reads other apps' lists; File ▸ Open Recent lists them (#290), an already-open file focuses its tab instead of reopening it" +evidence = ["crates/ui-egui/tests/ui.rs::persistence_round_trips_and_tolerates_garbage", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_lists_files_and_opens_one", "crates/ui-egui/tests/commands.rs::the_open_recent_menu_is_disabled_while_the_list_is_empty", "crates/ui-egui/tests/commands.rs::clear_recent_files_at_the_foot_of_open_recent_empties_the_list", "crates/ui-egui/tests/commands.rs::clear_recent_files_runs_from_the_palette_and_is_saved", "crates/ui-egui/tests/ui.rs::home_removes_one_recent_file_or_clears_them_all"] +notes = "never reads other apps' lists; File ▸ Open Recent lists them (#290), an already-open file focuses its tab instead of reopening it; Clear Recent Files at the foot of Open Recent, on Home and in the palette, and a remove button on each Home row (#430); missing: the row's remove button shows on pointer hover only (keyboard users clear the whole list)" [[feature]] id = "core.reopen-last-session"