From e2b21cb3db126feb09e4bc4f6253c7c895ea01eb Mon Sep 17 00:00:00 2001 From: texjer Date: Thu, 5 Nov 2026 14:13:55 -0500 Subject: [PATCH] Place: a PDF later in a batch joins the place cursor, whatever is selected With a graphic frame selected, `file.place` chose that frame before it looked at the place cursor, so a PDF confirmed with `append` filled the frame and left the images around it queued. An appended file now skips the selected-frame fallback; an explicit `frame` still applies, and a single placed file still fills the selected frame. --- crates/engine/src/cmd/file/place.rs | 9 ++++++--- crates/ui-egui/src/app/files.rs | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/file/place.rs b/crates/engine/src/cmd/file/place.rs index 081e3bd27..5ecd31268 100644 --- a/crates/engine/src/cmd/file/place.rs +++ b/crates/engine/src/cmd/file/place.rs @@ -100,8 +100,11 @@ pub(super) fn file_place(s: &mut Session, p: &Value) -> Result { (nw, nh) } }; + // A file joining the place cursor's queue goes after the ones already there, whatever is selected. + let load_cursor_param = p.get("loadCursor").and_then(Value::as_bool) == Some(true); + let append = p.get("append").and_then(Value::as_bool) == Some(true); let target_frame = crate::cmd::id_param(p, "frame").or_else(|| { - if ["x", "y", "spread", "width"].iter().any(|key| p.get(*key).is_some_and(|value| !value.is_null())) { + if (load_cursor_param && append) || ["x", "y", "spread", "width"].iter().any(|key| p.get(*key).is_some_and(|value| !value.is_null())) { return None; } let st = s.active()?; @@ -115,8 +118,8 @@ pub(super) fn file_place(s: &mut Session, p: &Value) -> Result { let lid = s.doc()?.active_layer; let px = p.get("x").and_then(Value::as_f64); let py = p.get("y").and_then(Value::as_f64); - if target_frame.is_none() && px.is_none() && py.is_none() && p.get("loadCursor").and_then(Value::as_bool) == Some(true) { - return load_cursor(s, asset, (bw, bh), p.get("append").and_then(Value::as_bool) == Some(true)); + if target_frame.is_none() && px.is_none() && py.is_none() && load_cursor_param { + return load_cursor(s, asset, (bw, bh), append); } s.edit(|d, sel| { let fresh = AssetId(d.alloc()); diff --git a/crates/ui-egui/src/app/files.rs b/crates/ui-egui/src/app/files.rs index d98fba286..ba5b23105 100644 --- a/crates/ui-egui/src/app/files.rs +++ b/crates/ui-egui/src/app/files.rs @@ -1063,6 +1063,27 @@ pub(crate) mod browser_import_tests { assert_eq!(loaded_names(&app), ["a.svg", "one.pdf", "b.svg", "c.svg", "a.svg"]); } + /// A PDF later in a batch joins the place cursor with the images around it, even with a + /// graphic frame selected: it doesn't fill that frame and leave the others queued. + #[test] + fn a_pdf_in_a_batch_queues_rather_than_filling_the_selected_frame() { + let dir = tempfile::tempdir().unwrap(); + let path = |n: &str| dir.path().join(n).to_string_lossy().to_string(); + std::fs::write(path("a.svg"), image()).unwrap(); + std::fs::write(path("one.pdf"), pdf_pages(1)).unwrap(); + std::fs::write(path("b.svg"), image()).unwrap(); + let mut app = app(); + app.services.read = Some(Box::new(|p| std::fs::read(p).map_err(|e| e.to_string()))); + let f = designcraft_doc::ItemId( + app.run("frame.create", json!({"rect": [200, 200, 300, 300], "content": "graphic"})).unwrap()["id"].as_u64().unwrap(), + ); + assert!(app.session.active().unwrap().selection.items.contains(&f)); + app.place_paths(vec![path("a.svg"), path("one.pdf"), path("b.svg")]).unwrap(); + assert_eq!(loaded_names(&app), ["a.svg", "one.pdf", "b.svg"]); + let frame = app.session.active().unwrap().doc.item(f).unwrap(); + assert!(!matches!(frame.content, designcraft_doc::Content::Graphic(_)), "the selected frame stays empty"); + } + /// File › Place with several files chosen in the dialog loads them all into the place /// cursor, in the order chosen; with one, it is placed as before. #[test]