mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 09:59:47 -06:00
Fix #4920: Document Tools exports every open document
addon.document.exportAllOpen {folder, format?} writes a copy of every
open document to one folder as Save a Copy does, each named after its
document with taken names numbered so no file is replaced. Documents
keep their paths and modified state, the active one stays active, and a
document that can't be written is reported while the others are still
written. Object > Plug-ins > Document Tools gets Export All Open
Documents as PDF and as SVG, which ask for the folder.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Document Tools (`addon.document.*`), a built-in add-on: Save a Timestamped Copy writes a copy of
|
||||
//! the document named with the date and time through File › Save a Copy's save; New Symbols from
|
||||
//! Layers makes a symbol of each layer's art as New Symbol does. Its submenu (Object › Plug-ins ›
|
||||
//! Layers makes a symbol of each layer's art as New Symbol does; Export All Open Documents writes a
|
||||
//! copy of every open document to one folder as Save a Copy does (#4920). Its submenu (Object › Plug-ins ›
|
||||
//! Document Tools) is the UI's.
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -30,6 +31,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_clock,
|
||||
save_timestamped_copy
|
||||
),
|
||||
cmd!(
|
||||
"addon.document.exportAllOpen",
|
||||
"Export All Open Documents",
|
||||
["Object", "Plug-ins", "Document Tools"],
|
||||
None,
|
||||
"{folder, format?: vectorcraft|pdf|svg|svgz|ai|eps (default: pdf)} write a copy of every open document, in tab order, to `folder` as File › Save a Copy writes it, each named after its document (<name>.<ext>; -2, -3… added when that name is taken, so no file is replaced): every document keeps its path, title and modified state, and no undo step is made. A document that can't be written is listed in `failed` and the others are still written; the active document stays active → {files: [{title, path, warnings}], failed: [{title, error}], format}",
|
||||
has_doc,
|
||||
export_all_open
|
||||
),
|
||||
cmd!(
|
||||
"addon.document.symbolsFromLayers",
|
||||
"New Symbols from Layers",
|
||||
@@ -88,6 +98,37 @@ fn save_timestamped_copy(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id }))
|
||||
}
|
||||
|
||||
fn export_all_open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "addon.document.exportAllOpen";
|
||||
let format = fileio::save_format(Some(str_param(p, "format").unwrap_or("pdf")), None).map_err(|e| bad(C, e))?;
|
||||
let folder = str_param(p, "folder").map(str::trim).filter(|f| !f.is_empty()).ok_or_else(|| bad(C, "give `folder`"))?.to_string();
|
||||
let ext = format.extensions.first().copied().ok_or_else(|| bad(C, "the format has no file extension"))?;
|
||||
let (mut files, mut failed, mut written) = (vec![], vec![], Vec::<String>::new());
|
||||
let active = s.active;
|
||||
for i in 0..s.docs.len() {
|
||||
// Each document in turn is the one Save a Copy writes; switching tabs this way leaves every
|
||||
// document's tool and interaction alone, and the active one comes back below.
|
||||
s.active = Some(i);
|
||||
let Ok(st) = s.doc() else { continue };
|
||||
let title = st.doc.title.clone();
|
||||
let stem = fileio::file_stem(st.path.as_deref().unwrap_or(&title));
|
||||
// Names the copies already took count as taken, as do files on disk.
|
||||
let Some(path) = free_name(&folder, &stem, ext, |p| written.iter().any(|w| w == p) || std::path::Path::new(p).exists()) else {
|
||||
failed.push(json!({ "title": title, "error": "every numbered name for the copy is taken" }));
|
||||
continue;
|
||||
};
|
||||
match fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id })) {
|
||||
Ok(r) => {
|
||||
written.push(path.clone());
|
||||
files.push(json!({ "title": title, "path": r.get("path").cloned().unwrap_or(json!(path)), "warnings": r.get("warnings").cloned().unwrap_or(json!([])) }));
|
||||
}
|
||||
Err(e) => failed.push(json!({ "title": title, "error": e.to_string() })),
|
||||
}
|
||||
}
|
||||
s.active = active;
|
||||
Ok(json!({ "files": files, "failed": failed, "format": format.id }))
|
||||
}
|
||||
|
||||
/// `n` with each layer in it, and itself, made a group of the same art and clipping (a symbol holds
|
||||
/// no layers).
|
||||
fn without_layers(n: &Node) -> Node {
|
||||
|
||||
@@ -194,3 +194,26 @@ fn the_layers_highlighted_in_the_layers_panel_are_the_default() {
|
||||
let r = s.execute("addon.document.symbolsFromLayers", &json!({})).unwrap();
|
||||
assert_eq!((r["symbols"].clone(), r["skipped"].as_array().unwrap().len()), (json!(["Icons"]), 1));
|
||||
}
|
||||
|
||||
/// #4920: Export All Open Documents writes an SVG of each open document; a folder that can't be
|
||||
/// written lists every document in `failed`, and either way the active document stays active.
|
||||
#[test]
|
||||
fn export_all_open_documents_writes_each_and_reports_failures() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-export-all-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100, "name": "one"})).unwrap();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100, "name": "two"})).unwrap();
|
||||
s.execute("document.activate", &json!({"index": 0})).unwrap();
|
||||
let r = s.execute("addon.document.exportAllOpen", &json!({"folder": dir, "format": "svg"})).unwrap();
|
||||
assert_eq!(r["files"].as_array().unwrap().len(), 2, "{r}");
|
||||
assert!(dir.join("one.svg").exists() && dir.join("two.svg").exists());
|
||||
assert_eq!(s.active_index(), Some(0));
|
||||
assert!(s.documents().iter().all(|d| d.path.is_none()), "the documents stay unsaved");
|
||||
let missing = dir.join("missing").join("deeper");
|
||||
let r = s.execute("addon.document.exportAllOpen", &json!({"folder": missing, "format": "svg"})).unwrap();
|
||||
assert_eq!((r["files"].as_array().unwrap().len(), r["failed"].as_array().unwrap().len()), (0, 2), "{r}");
|
||||
assert_eq!(s.active_index(), Some(0));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user