Merge PR #4211: feat(ui): add 'Split document...' to page thumbnail context menus

This commit is contained in:
aiongg
2026-10-24 06:22:35 +00:00
6 changed files with 103 additions and 4 deletions
+6
View File
@@ -665,6 +665,8 @@ pub enum ViewAction {
Save,
Extract,
Split,
/// Split before each selected page: opens Split on that mode.
SplitBeforeSelected,
/// Copy the selected pages (Cut also deletes them).
CopyPages {
cut: bool,
@@ -5489,6 +5491,10 @@ fn organize_grid(view: &mut DocView, info: &DocInfo, editable: bool, dirty: bool
view.pending_action = Some(ViewAction::Extract);
ui.close();
}
if ui.button(tl!("Split document…")).clicked() {
view.pending_action = Some(ViewAction::SplitBeforeSelected);
ui.close();
}
if ui.add_enabled(editable, egui::Button::new(tl!("Replace pages…"))).clicked() {
view.pending_action = Some(ViewAction::ReplacePages);
ui.close();
+4
View File
@@ -265,6 +265,10 @@ impl PdfCraftApp {
}
Some(crate::canvas::ViewAction::Extract) => self.open_extract_dialog(),
Some(crate::canvas::ViewAction::Split) => self.dialog = Some(crate::Dialog::Split),
Some(crate::canvas::ViewAction::SplitBeforeSelected) => {
self.split_draft.mode = crate::SplitMode::Selection;
self.dialog = Some(crate::Dialog::Split);
}
Some(crate::canvas::ViewAction::CopyPages { cut }) => self.copy_pages(cut),
Some(crate::canvas::ViewAction::PastePages) => self.paste_pages(),
Some(crate::canvas::ViewAction::EnhanceCamera) => self.enhance_camera(i),
+7
View File
@@ -1410,6 +1410,13 @@ fn pages(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, view: &mut crate::DocVie
view.pending_action = Some(crate::canvas::ViewAction::Extract);
ui.close();
}
if ui.button(tl!("Split document…")).clicked() {
if view.selected.is_empty() {
view.select_pages(&[i]);
}
view.pending_action = Some(crate::canvas::ViewAction::SplitBeforeSelected);
ui.close();
}
if ui.add_enabled(editable, egui::Button::new(tl!("Replace pages…"))).clicked() {
view.pending_action = Some(crate::canvas::ViewAction::ReplacePages);
ui.close();
+82
View File
@@ -2241,6 +2241,88 @@ fn split_before_selected_pages() {
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn split_from_organize_grid_context_menu() {
let dir = temp_path("split-org-ctx");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let mut h = organize(4);
h.state_mut().export_dir_override = Some(dir.to_string_lossy().into_owned());
h.get_by_label("Page 3").click_button(egui::PointerButton::Secondary);
h.run_steps(2);
h.get_all_by_label("Split document…").last().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().dialog, Some(pdfcraft_ui_egui::Dialog::Split));
assert_eq!(h.state().split_draft.mode, pdfcraft_ui_egui::SplitMode::Selection);
h.get_by_label_contains("Creates 2 files from 4 pages");
h.get_all_by_label("Split").last().unwrap().click();
h.run_steps(3);
let mut names: Vec<String> = std::fs::read_dir(&dir).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).collect();
names.sort();
assert_eq!(names, ["doc (pages 1-2).pdf", "doc (pages 3-4).pdf"]);
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn split_from_pages_panel_context_menu() {
let mut h = harness(4, |app| app.right = Some(pdfcraft_ui_egui::RightPanel::Pages));
let thumb = h.get_all_by_label("Page 3").max_by(|a, b| a.rect().left().total_cmp(&b.rect().left())).unwrap();
thumb.click_button(egui::PointerButton::Secondary);
h.run_steps(2);
h.get_all_by_label("Split document…").last().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().dialog, Some(pdfcraft_ui_egui::Dialog::Split));
assert_eq!(h.state().split_draft.mode, pdfcraft_ui_egui::SplitMode::Selection);
h.get_by_label_contains("Creates 2 files from 4 pages");
}
#[test]
fn split_from_pages_panel_context_menu_on_current_page_without_selection() {
let mut h = harness(4, |app| app.right = Some(pdfcraft_ui_egui::RightPanel::Pages));
// Navigate to Page 2 (0-indexed 1) so it is current, but keep selected empty
h.state_mut().views[0].go_to_page(1);
h.state_mut().views[0].selected.clear();
h.run_steps(2);
assert_eq!(h.state().views[0].current, 1);
assert!(h.state().views[0].selected.is_empty());
let thumb = h.get_all_by_label("Page 2").max_by(|a, b| a.rect().left().total_cmp(&b.rect().left())).unwrap();
thumb.click_button(egui::PointerButton::Secondary);
h.run_steps(2);
h.get_all_by_label("Split document…").last().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().dialog, Some(pdfcraft_ui_egui::Dialog::Split));
assert_eq!(h.state().split_draft.mode, pdfcraft_ui_egui::SplitMode::Selection);
h.get_by_label_contains("Creates 2 files from 4 pages");
}
#[test]
fn split_from_pages_panel_context_menu_on_page_one_falls_back_to_pages() {
let mut h = harness(4, |app| app.right = Some(pdfcraft_ui_egui::RightPanel::Pages));
let thumb = h.get_all_by_label("Page 1").max_by(|a, b| a.rect().left().total_cmp(&b.rect().left())).unwrap();
thumb.click_button(egui::PointerButton::Secondary);
h.run_steps(2);
h.get_all_by_label("Split document…").last().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().dialog, Some(pdfcraft_ui_egui::Dialog::Split));
assert_eq!(h.state().split_draft.mode, pdfcraft_ui_egui::SplitMode::Pages);
}
#[test]
fn split_from_pages_panel_context_menu_with_multi_selection() {
let mut h = harness(4, |app| app.right = Some(pdfcraft_ui_egui::RightPanel::Pages));
// Select pages 2 and 4 (indices 1 and 3)
h.state_mut().views[0].select_pages(&[1, 3]);
h.run_steps(2);
let thumb = h.get_all_by_label("Page 2").max_by(|a, b| a.rect().left().total_cmp(&b.rect().left())).unwrap();
thumb.click_button(egui::PointerButton::Secondary);
h.run_steps(2);
h.get_all_by_label("Split document…").last().unwrap().click();
h.run_steps(3);
assert_eq!(h.state().dialog, Some(pdfcraft_ui_egui::Dialog::Split));
assert_eq!(h.state().split_draft.mode, pdfcraft_ui_egui::SplitMode::Selection);
h.get_by_label_contains("Creates 3 files from 4 pages");
}
// ── Encrypted documents ───────────────────────────────────────────────────────────────────────
fn protected(user: &str, owner: &str, permissions: i32) -> Vec<u8> {
+1 -1
View File
@@ -1393,7 +1393,7 @@ fn pages_panel_thumbnails_have_a_context_menu() {
h.run_steps(2);
// The page right-clicked becomes the selection the menu acts on.
assert_eq!(h.state().views[0].target_pages(), vec![2]);
for item in ["Extract pages", "Cut", "Paste after"] {
for item in ["Extract pages", "Split document…", "Cut", "Paste after"] {
h.get_by_label(item);
}
h.get_by_label("Copy").click();
+3 -3
View File
@@ -1274,8 +1274,8 @@ milestone = "M4"
status = "shipped"
commands = []
tools = []
evidence = ["crates/ui-egui/tests/it/editing.rs::dragging_pages_panel_thumbnails_reorders_pages", "crates/ui-egui/tests/it/viewer.rs::pages_panel_thumbnails_have_a_context_menu", "crates/ui-egui/tests/it/editing.rs::deleting_pages_from_the_pages_panel_menu", "crates/ui-egui/tests/it/editing.rs::the_delete_key_deletes_pages_picked_in_the_pages_panel_but_never_every_page"]
notes = "Drag the selection (or the page grabbed) to reorder, with a drop bar and edge scrolling; right-click offers Extract pages, Cut, Copy, Paste after and Delete pages (also the Delete key over the panel). Not yet: Rotate, Insert and Page labels in the menu, dragging pages between documents, or dropping files onto the panel."
evidence = ["crates/ui-egui/tests/it/editing.rs::dragging_pages_panel_thumbnails_reorders_pages", "crates/ui-egui/tests/it/viewer.rs::pages_panel_thumbnails_have_a_context_menu", "crates/ui-egui/tests/it/editing.rs::split_from_pages_panel_context_menu", "crates/ui-egui/tests/it/editing.rs::deleting_pages_from_the_pages_panel_menu", "crates/ui-egui/tests/it/editing.rs::the_delete_key_deletes_pages_picked_in_the_pages_panel_but_never_every_page"]
notes = "Drag the selection (or the page grabbed) to reorder, with a drop bar and edge scrolling; right-click offers Split document…, Extract pages, Cut, Copy, Paste after and Delete pages (also the Delete key over the panel). Not yet: Rotate, Insert and Page labels in the menu, dragging pages between documents, or dropping files onto the panel."
[[feature]]
id = "view.bookmarks-panel"
@@ -2920,7 +2920,7 @@ milestone = "M4"
status = "shipped"
commands = ["page.split"]
tools = ["doc_split"]
evidence = ["crates/ui-egui/tests/it/editing.rs::split_before_selected_pages"]
evidence = ["crates/ui-egui/tests/it/editing.rs::split_before_selected_pages", "crates/ui-egui/tests/it/editing.rs::split_from_organize_grid_context_menu"]
notes = ""
[[feature]]