Browse / github.com/storytold/pdfcraft / patch
fix(ui-egui): add reverse pages to organize panel and thumbnail context menus (#4918)
Reverse Page Order Not Located in Same Place and Button in Drop Down Broken There appears to be a mismatch between the functionality present in the "Organize pages" section when you access it while normally viewing a PDF, vs. when you are working in the "Combine files" section. For example, "Reverse pages" is only in the "Organize pages" panel when you are in the operation of "Combine files." It does not show up when you navigate directly to "Organize pages" from "All tools." There are many other features in there that would be great if they were duplicated or matched the direct "Organize pages" screen. The "Reverse pages" function does appear in the "Menu" > "Pages"
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- YusufMntm (imported from pull request #4945, open)
- Base
1dd2737b47a9c1c9562f0af563053d1bf510e4bf- Size
- +66 −5 in 8 files
- Review
- pending
- Shared
- 2026-10-28
- Id
5d7a9b9dffb272eb152f93b2
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/canvas.rs crates/ui-egui/src/canvas/organize.rs crates/ui-egui/src/commands.rs crates/ui-egui/src/editing.rs crates/ui-egui/src/panels/organize_panel.rs crates/ui-egui/src/panels/pages.rs crates/ui-egui/tests/it/editing.rs parity/acrobat-features.toml
@@ -556,6 +556,8 @@ pub enum ViewAction { CameraLevel, /// After Enhance page: leave the tool for this command (Crop pages, Organize pages). CameraNext(&'static str),+ /// Run a registered command on the app (e.g. from a context menu or panel).+ Command(&'static str), } impl DocView {@@ -392,6 +392,13 @@ pub(super) fn organize_grid(view: &mut DocView, info: &DocInfo, editable: bool, view.pending_action = Some(ViewAction::ReplacePages); ui.close(); }+ if ui+ .add_enabled(editable && (view.selected.len() >= 2 || info.pages.len() >= 2), egui::Button::new(tl!("Reverse pages")))+ .clicked()+ {+ view.pending_action = Some(ViewAction::Command("page.reverse"));+ ui.close();+ } ui.separator(); if ui.add_enabled(editable, egui::Button::new(tl!("Cut"))).clicked() { view.pending_action = Some(ViewAction::CopyPages { cut: true });@@ -78,7 +78,7 @@ pub(crate) fn command_tip(ctx: &egui::Context, template: &str, id: &str) -> Stri impl PdfCraftApp { /// Whether a registered command can run now. The engine judges the document (security, /// contents, undo history); view state it can't see is checked here.- pub(crate) fn command_enabled(&self, spec: &CommandSpec) -> bool {+ pub fn command_enabled(&self, spec: &CommandSpec) -> bool { // Undo and Redo act on the Combine files list while its tab shows. if self.combine_showing() && matches!(spec.needs, commands::Needs::Undo | commands::Needs::Redo) { return self.combine_can_undo(spec.needs == commands::Needs::Undo);@@ -93,6 +93,7 @@ impl PdfCraftApp { && match spec.id { "view.previous_view" => self.active.and_then(|i| self.views.get(i)).is_some_and(|v| !v.back.is_empty()), "view.next_view" => self.active.and_then(|i| self.views.get(i)).is_some_and(|v| !v.forward.is_empty()),+ "page.reverse" => self.active.and_then(|i| self.views.get(i)).is_some_and(|v| v.page_count() >= 2), _ => true, } }@@ -654,8 +655,10 @@ impl PdfCraftApp { // Two or more selected pages reverse among themselves; otherwise the whole document. "page.reverse" => { let all = active.and_then(|i| self.views.get(i)).map_or(0, |v| v.page_count());- let pages = if targets.len() >= 2 { targets } else { (0..all).collect() };- self.apply_edit(Edit::ReversePages { pages });+ if all >= 2 {+ let pages = if targets.len() >= 2 { targets } else { (0..all).collect() };+ self.apply_edit(Edit::ReversePages { pages });+ } } "ocr.enhance_camera" => { // The corners start on the document's edges when they can be found, else@@ -134,6 +134,9 @@ impl PdfCraftApp { } Edit::InsertBlankPage { at, .. } => view.select_pages(&[at.min(info.pages.len() - 1)]), Edit::DeletePages { .. } => view.select_pages(&[]),+ Edit::ReversePages { ref pages } => {+ view.select_pages(pages);+ } // A stroke drawn with the pen stays unselected, so the selection box and // author popup don't sit over the next stroke (#429). Edit::AddAnnotation(a)@@ -278,6 +281,9 @@ impl PdfCraftApp { self.leave_camera(i); self.execute(command); }+ Some(crate::canvas::ViewAction::Command(command)) => {+ self.execute(command);+ } None => {} } applied@@ -137,6 +137,9 @@ pub(crate) fn panel(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) { if row(ui, t, "scissors", tl!("Split"), Availability::Ready, false).clicked() { act = Some(Act::View(ViewAction::Split)); }+ if row(ui, t, "arrow-up-down", tl!("Reverse pages"), Availability::Ready, false).clicked() {+ act = Some(Act::Command("page.reverse"));+ } }); if row(ui, t, "hash", tl!("Bates numbering"), Availability::Ready, false).clicked() { act = Some(Act::Command("edit.bates"));@@ -197,6 +197,10 @@ pub(super) fn pages(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, view: &mut cr view.pending_action = Some(crate::canvas::ViewAction::ReplacePages); ui.close(); }+ if ui.add_enabled(editable && (view.selected.len() >= 2 || count >= 2), egui::Button::new(tl!("Reverse pages"))).clicked() {+ view.pending_action = Some(crate::canvas::ViewAction::Command("page.reverse"));+ ui.close();+ } ui.separator(); if ui.add_enabled(editable, egui::Button::new(tl!("Cut"))).clicked() { view.pending_action = Some(crate::canvas::ViewAction::CopyPages { cut: true });@@ -369,6 +369,42 @@ fn reverse_pages_reverses_the_selection_or_else_the_whole_document() { assert_eq!(page_texts(h.state()), ["Page 2", "Page 3", "Page 4", "Page 1"]); } +#[test]+fn reverse_pages_command_disabled_on_single_page_document() {+ let mut h = harness(1, |_| ());+ let spec = pdfcraft_engine::commands::command("page.reverse").unwrap();+ assert!(!h.state().command_enabled(spec));+ assert!(!h.state_mut().execute("page.reverse"));+}++#[test]+fn reverse_pages_from_organize_panel_reverses_and_preserves_selection() {+ let mut h = organize(3);+ h.get_by_label("Reverse pages").click();+ h.run_steps(3);+ assert_eq!(page_texts(h.state()), ["Page 3", "Page 2", "Page 1"]);+ assert_eq!(h.state().views[0].selected.iter().copied().collect::<Vec<_>>(), [0, 1, 2]);+}++#[test]+fn reverse_pages_from_thumbnail_context_menu() {+ let mut h = organize(3);+ h.get_by_label("Page 2").click_button(egui::PointerButton::Secondary);+ h.run_steps(2);+ h.get_all_by_label("Reverse pages").last().unwrap().click();+ h.run_steps(3);+ assert_eq!(page_texts(h.state()), ["Page 3", "Page 2", "Page 1"]);+ assert_eq!(h.state().views[0].selected.iter().copied().collect::<Vec<_>>(), [0, 1, 2]);+}++#[test]+fn reverse_pages_context_menu_disabled_on_single_page() {+ let mut h = organize(1);+ h.get_by_label("Page 1").click_button(egui::PointerButton::Secondary);+ h.run_steps(2);+ assert!(h.query_by(|n| n.label().as_deref() == Some("Reverse pages") && n.is_disabled()).is_some());+}+ #[test] fn command_click_toggles_and_rotation_applies_to_selection() { let mut h = organize(3);@@ -2871,8 +2871,8 @@ milestone = "M4" status = "shipped" commands = ["page.reverse"] tools = ["page_reverse"]-evidence = ["crates/organize/src/tests.rs::reverse_pages_reorders", "crates/organize/src/tests.rs::cannot_reverse_missing_or_fewer_than_two_pages", "crates/automation/tests/it/automation.rs::reversing_pages_through_tools", "crates/ui-egui/tests/it/editing.rs::reverse_pages_reverses_the_selection_or_else_the_whole_document"]-notes = "Organize Pages ▸ More ▸ Reverse pages, or the command palette: two or more selected pages swap among their own positions, otherwise the whole document reverses. One undo step. Not yet: an entry in the page thumbnails' right-click menu"+evidence = ["crates/organize/src/tests.rs::reverse_pages_reorders", "crates/organize/src/tests.rs::cannot_reverse_missing_or_fewer_than_two_pages", "crates/automation/tests/it/automation.rs::reversing_pages_through_tools", "crates/ui-egui/tests/it/editing.rs::reverse_pages_reverses_the_selection_or_else_the_whole_document", "crates/ui-egui/tests/it/editing.rs::reverse_pages_from_thumbnail_context_menu"]+notes = "Organize pages panel, thumbnails' right-click context menu, Edit ▸ Pages ▸ Reverse pages, or the command palette: two or more selected pages swap among their own positions, otherwise the whole document reverses. Disabled on single-page documents. One undo step." [[feature]] id = "organize.cut-copy-paste-pages"