From 52d0fcd6c71cf8b3013acfe32f1973b5d6154297 Mon Sep 17 00:00:00 2001 From: Faabyy <68284158+Faabyy@users.noreply.github.com> Date: Sun, 25 Oct 2026 13:47:23 +0000 Subject: [PATCH] Reuse Slides: insert into the presentation and after the slide the picker was opened for (#1103) Co-Authored-By: Claude --- crates/ui-egui/src/lib.rs | 21 ++++++++++++---- crates/ui-egui/src/reuse_picker_tests.rs | 32 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 13f65637..c21a0855 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -459,6 +459,8 @@ pub struct SlideApp { pub show: Option, /// Current slide's animations playing on the editor canvas (not the full-screen show). anim_preview: Option, + /// Where the Reuse Slides picker inserts: the presentation and slide current when it opened. + reuse_target: Option, /// Audio/video playback (editor control bar, slide show, `media.*` commands). pub media: media::MediaHost, pub status: Option<(String, f64)>, @@ -557,6 +559,7 @@ impl SlideApp { thesaurus_query: String::new(), show: None, anim_preview: None, + reuse_target: None, media: media::MediaHost::new(audio_out), status: None, opening: None, @@ -1290,6 +1293,7 @@ impl SlideApp { Ok(list) => { let mut d = dialogs::Dialog::new("reuseSlides"); d.params = json!({"path": path, "source": source, "keep": keep, "slides": list["slides"]}); + self.reuse_target = Some(self.capture_file_target()); self.dialog = Some(d); } Err(e) => self.set_status(e.to_string()), @@ -1297,18 +1301,25 @@ impl SlideApp { } /// Insert the slides `picked` (indices) of `source` (the `name` + `data`, or `path`, that - /// `slide.reuseList` listed) after the current slide. Returns whether they were inserted. + /// `slide.reuseList` listed) after the slide that was current when the picker opened, in that + /// presentation, even if another one is active now; if it is closed or the slide is gone, + /// nothing is inserted. Returns whether they were inserted. pub fn reuse_slides(&mut self, source: &Value, keep: bool, picked: &[usize]) -> bool { let mut args = if source.is_object() { source.clone() } else { json!({}) }; args["keepFormatting"] = json!(keep); args["slides"] = json!(picked); - match self.session.execute("slide.reuse", &args) { - Ok(r) => { - self.set_status(format!("Inserted {} slides", r["slides"])); + let result = match self.reuse_target { + Some(target) if !self.viewing_target(&target) => self.execute_on_target(&target, "Reuse Slides", "slide.reuse", &args), + _ => self.session.execute("slide.reuse", &args).map(|_| ()).map_err(|e| e.to_string()), + }; + match result { + Ok(()) => { + self.reuse_target = None; + self.set_status(format!("Inserted {} slides", picked.len())); true } Err(e) => { - self.set_status(e.to_string()); + self.set_status(e); false } } diff --git a/crates/ui-egui/src/reuse_picker_tests.rs b/crates/ui-egui/src/reuse_picker_tests.rs index 1db99df5..4f640afc 100644 --- a/crates/ui-egui/src/reuse_picker_tests.rs +++ b/crates/ui-egui/src/reuse_picker_tests.rs @@ -88,6 +88,38 @@ fn inserts_what_was_listed_even_if_the_file_changed_meanwhile() { assert!(!t.iter().any(|x| x == "Other"), "{t:?}"); } +#[test] +fn inserts_into_the_presentation_the_picker_was_opened_for() { + let file = Rc::new(RefCell::new(deck_bytes(&["Alpha"]))); + let mut h = harness(&file); + open_dialog(&mut h); + let first = h.state().session.active().unwrap().uid; + // Another presentation becomes active while the dialog is open. + h.state_mut().session.execute("file.new", &json!({})).unwrap(); + let second = h.state().session.active().unwrap().uid; + assert_ne!(first, second); + press_insert(&mut h); + assert!(h.state().dialog.is_none()); + let s = &h.state().session; + let doc = |uid: u64| s.documents().iter().find(|d| d.uid == uid).unwrap(); + assert!(doc(first).doc.slides.iter().any(|x| x.title() == "Alpha"), "inserted where the picker was opened"); + assert_eq!(doc(second).doc.slides.len(), 1, "the other presentation is untouched"); + assert_eq!(s.active().unwrap().uid, second, "the view stays on the presentation the user moved to"); +} + +#[test] +fn nothing_is_inserted_once_that_presentation_is_closed() { + let file = Rc::new(RefCell::new(deck_bytes(&["Alpha"]))); + let mut h = harness(&file); + open_dialog(&mut h); + h.state_mut().session.execute("file.new", &json!({})).unwrap(); + h.state_mut().session.close_document(0); + press_insert(&mut h); + assert_eq!(h.state().session.documents().len(), 1); + assert!(!titles(&h).iter().any(|x| x == "Alpha"), "{:?}", titles(&h)); + assert_eq!(h.state().dialog.as_ref().map(|d| d.id.as_str()), Some("reuseSlides"), "the dialog stays open with the reason"); +} + #[test] fn a_failed_insert_keeps_the_dialog_open() { let file = Rc::new(RefCell::new(deck_bytes(&["Alpha"])));