Place: the files after a multi-page PDF in a batch wait for its options

They were left behind in place_paths when the PDF's Import Options opened, so the
PDF was placed alone and the rest of what was chosen never loaded. They now wait in
the dialog and carry on when it is confirmed; Cancel ends the batch.
This commit is contained in:
texjer committed 2026-11-05 15:45:06 -05:00
1 parent ba2237f8d5
commit 3111d4086a
3 files changed
+84 -16

No files matched your search

+69 -13
View File
@@ -52,18 +52,21 @@ impl DesignApp {
self.place_file(if append { json!({"path": path, "append": true}) } else { json!({"path": path}) })
}
/// Place an ordered batch of files, as File › Place with several chosen. The batch stops when
/// a file opens a dialog (a multi-page PDF's Import Options, an alert), as the inbox's does;
/// every file that loads the cursor adds to what the earlier ones loaded, in the order the
/// files were chosen (images in a row load together, before the file after them). A file that
/// can't be placed gets an alert and stops the batch; its error is returned, so automation
/// doesn't carry on as if the batch had loaded.
/// Place an ordered batch of files, as File › Place with several chosen. A multi-page PDF's
/// Import Options pause the batch: the files after it wait in the dialog and carry on when it
/// is confirmed, and Cancel ends the batch. Any other dialog (an alert, one already open) stops
/// it, as the inbox's does. Every file that loads the cursor adds to what the earlier ones
/// loaded, in the order the files were chosen (images in a row load together, before the file
/// after them). A file that can't be placed gets an alert and stops the batch; its error is
/// returned, so automation doesn't carry on as if the batch had loaded.
pub(crate) fn place_paths(&mut self, paths: Vec<String>) -> Result<(), String> {
let mut images = Vec::new();
// Images already in the place cursor stay: the batch queues after them.
let mut loaded = self.session.loaded.is_some();
// `None` at the end loads the images left.
for path in paths.into_iter().map(Some).chain([None]) {
let mut paths = paths.into_iter();
loop {
// `None` at the end loads the images left.
let path = paths.next();
if let Some(p) = &path
&& is_cursor_image(p)
{
@@ -71,7 +74,7 @@ impl DesignApp {
continue;
}
if path.is_none() && images.is_empty() {
break;
return Ok(());
}
if self.ui.dialog.is_some() {
self.status(crate::i18n::tr(&self.ui.language, "Place cancelled while a dialog is open"));
@@ -81,12 +84,14 @@ impl DesignApp {
let r = self.load_place_cursor(std::mem::take(&mut images), loaded)?;
loaded |= r.get("loaded").and_then(Value::as_bool) == Some(true);
}
if let Some(p) = path {
let r = self.place_path(&p, loaded)?;
loaded |= r.get("loaded").and_then(Value::as_bool) == Some(true);
let Some(p) = path else { return Ok(()) };
let r = self.place_path(&p, loaded)?;
loaded |= r.get("loaded").and_then(Value::as_bool) == Some(true);
if let Some(d) = self.ui.dialog.as_mut().filter(|d| d.id == "pdfImport") {
d.fields.insert(dialogs::PDF_IMPORT_REST.into(), json!(paths.collect::<Vec<_>>()));
return Ok(());
}
}
Ok(())
}
/// `place.load` of `files` (`{path}` or `{name, base64}` each), queued after the images
@@ -996,6 +1001,57 @@ pub(crate) mod browser_import_tests {
assert_eq!(loaded_names(&app), ["a.png", "b.png", "three.pdf"]);
}
/// The files after a multi-page PDF in a native batch wait for its Import Options, through a
/// second such PDF too, and keep the order chosen.
#[test]
fn native_batch_continues_after_a_multi_page_pdf_is_confirmed() {
let dir = tempfile::tempdir().unwrap();
let path = |n: &str| dir.path().join(n).to_string_lossy().to_string();
for name in ["a.svg", "b.svg", "c.svg", "d.svg"] {
std::fs::write(path(name), image()).unwrap();
}
std::fs::write(path("three.pdf"), pdf_pages(3)).unwrap();
std::fs::write(path("two.pdf"), pdf_pages(2)).unwrap();
let mut app = app();
app.services.read = Some(Box::new(|p| std::fs::read(p).map_err(|e| e.to_string())));
let batch = ["a.svg", "three.pdf", "b.svg", "c.svg", "two.pdf", "d.svg"].map(path).to_vec();
app.place_paths(batch).unwrap();
assert_eq!(loaded_names(&app), ["a.svg"], "waiting for the first PDF's options");
dialogs::confirm(&mut app).unwrap();
assert_eq!(app.ui.dialog.as_ref().map(|d| d.id.as_str()), Some("pdfImport"), "the second PDF asks in its turn");
assert_eq!(loaded_names(&app), ["a.svg", "three.pdf", "b.svg", "c.svg"]);
dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
assert_eq!(loaded_names(&app), ["a.svg", "three.pdf", "b.svg", "c.svg", "two.pdf", "d.svg"]);
}
/// A refused page number keeps the rest of the batch waiting; Cancel ends the batch, and what
/// loaded before the PDF stays.
#[test]
fn native_batch_waits_through_a_refused_page_and_ends_at_cancel() {
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("b.svg"), image()).unwrap();
std::fs::write(path("three.pdf"), pdf_pages(3)).unwrap();
let mut app = app();
app.services.read = Some(Box::new(|p| std::fs::read(p).map_err(|e| e.to_string())));
let batch = ["a.svg", "three.pdf", "b.svg"].map(path).to_vec();
app.place_paths(batch.clone()).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("page".into(), json!("4-5"));
assert!(dialogs::confirm(&mut app).is_err());
app.ui.dialog.as_mut().unwrap().fields.insert("page".into(), json!("2"));
dialogs::confirm(&mut app).unwrap();
assert_eq!(loaded_names(&app), ["a.svg", "three.pdf", "b.svg"]);
let mut app = self::app();
app.services.read = Some(Box::new(|p| std::fs::read(p).map_err(|e| e.to_string())));
app.place_paths(batch).unwrap();
dialogs::cancel(&mut app);
assert!(app.ui.dialog.is_none());
assert_eq!(loaded_names(&app), ["a.svg"]);
}
/// A single multi-page PDF still replaces what the cursor holds, as any single Place does.
#[test]
fn a_single_multi_page_pdf_replaces_the_cursor_when_confirmed() {
+14 -2
View File
@@ -1562,6 +1562,9 @@ fn text_frame_params(app: &DesignApp, d: &Dialog) -> Result<Map<String, Value>,
Ok(p)
}
/// In a native batch's `pdfImport` dialog: the paths chosen after its PDF, placed once it is confirmed.
pub(crate) const PDF_IMPORT_REST: &str = "rest";
/// File › Document Setup, filled from the document.
/// The active document's ruler unit (picas without a document).
fn doc_unit(app: &DesignApp) -> Unit {
@@ -4711,9 +4714,18 @@ fn confirm_part3(app: &mut DesignApp, d: Dialog) -> Result<Value, String> {
if let Some(target) = d.fields.get("target").and_then(Value::as_u64) {
app.activate_import_target(&crate::ImportRequest { purpose: "place".into(), target: Some(target) })?;
}
app.place_file(
let placed = app.place_file(
json!({"path": d.s("path"), "pdfPage": page, "pdfCrop": if crop.is_empty() { "crop".to_string() } else { crop }, "append": d.b("append")}),
)
)?;
// The files chosen after this PDF, which waited for its options.
let rest: Vec<String> = d
.fields
.get(PDF_IMPORT_REST)
.and_then(Value::as_array)
.map(|paths| paths.iter().filter_map(Value::as_str).map(str::to_string).collect())
.unwrap_or_default();
app.place_paths(rest)?;
Ok(placed)
}
"print" => {
let pages = if d.s("range") == "range" { json!(d.s("pages")) } else { Value::Null };
+1 -1
View File
@@ -9,7 +9,7 @@ Choose **File > Place** (Cmd+D on macOS) and select several images, or drop a se
- Press Escape or select another tool to cancel the remaining placement.
- Switching to, opening or closing a document also cancels it, and goes back to the Selection tool.
A single selected file retains the normal Place behavior, including filling a selected frame and PDF import options. A multi-page PDF in a batch stops the batch at its import options; once confirmed, it queues after the images loaded before it. The place cursor holds up to 1000 images, counting those already in it when a batch adds to them. Rows and columns each range from 1 to 100. A batch containing an unreadable image fails without replacing the existing queue or changing the document. An invalid grid area also leaves the document and queue unchanged.
A single selected file retains the normal Place behavior, including filling a selected frame and PDF import options. A multi-page PDF in a batch pauses the batch at its import options; once confirmed, it queues after the images loaded before it and the rest of the batch follows. Cancelling them ends the batch, and the images already loaded stay. The place cursor holds up to 1000 images, counting those already in it when a batch adds to them. Rows and columns each range from 1 to 100. A batch containing an unreadable image fails without replacing the existing queue or changing the document. An invalid grid area also leaves the document and queue unchanged.
## Dropping files