Batch: data set files name by File + Number, File + Data Set Name or Data Set Name

The Batch dialog has a File Name choice for a Data Sets source, starting
with File + Number (`source001`), as well as File + Data Set Name
(`source_Alpha`) and Data Set Name (`Alpha`). `action.batch` takes it as
`datasetFileNaming`; a call without it keeps File + Data Set Name, and a
folder source ignores it. An unknown value is an error.

A data set output whose name already exists in the destination folder
now takes the next free `-2`, `-3` suffix instead of replacing the file,
and a Windows device name under Data Set Name gets a trailing `-`.
This commit is contained in:
dotCooCoo
2026-11-01 01:22:06 +07:00
committed by bflatastic
parent 777ad6765c
commit f8bba3388e
31 changed files with 452 additions and 54 deletions
+101 -34
View File
@@ -21,26 +21,28 @@ pub fn open(app: &mut VectorcraftApp, set: usize, action: usize) -> Result<Value
return Err("the action has no steps".into());
}
let name = a.name.clone();
app.ui.dialog = Some(Dialog::new(
KIND,
json!({
"set": set,
"action": action,
"actionName": name,
"source": "folder",
"folder": "",
"includeSubdirs": false,
"destination": "folder",
"destinationFolder": "",
"overrideOpen": true,
"overrideSave": true,
"overrideExport": true,
"stopOnError": true,
}),
));
app.ui.dialog = Some(Dialog::new(KIND, initial_fields(set, action, &name)));
Ok(Value::Null)
}
fn initial_fields(set: usize, action: usize, name: &str) -> Value {
json!({
"set": set,
"action": action,
"actionName": name,
"source": "folder",
"datasetFileNaming": "fileNumber",
"folder": "",
"includeSubdirs": false,
"destination": "folder",
"destinationFolder": "",
"overrideOpen": true,
"overrideSave": true,
"overrideExport": true,
"stopOnError": true,
})
}
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let t = Tokens::get(ui.ctx());
ui.label(egui::RichText::new(d.str("actionName")).color(t.text_strong));
@@ -58,6 +60,14 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
form::check(ui, d, "includeSubdirs", "Include All Subdirectories");
} else {
ui.label(egui::RichText::new(tl!("Uses every data set of the open document.")).size(11.5).color(t.text_dim));
form::choice(
ui,
d,
"datasetFileNaming",
"File Name:",
(LABEL, 200.0),
&[("fileNumber", "File + Number"), ("fileDataSetName", "File + Data Set Name"), ("dataSetName", "Data Set Name")],
);
}
ui.add_space(8.0);
@@ -95,23 +105,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
if source == "datasets" && app.session.active().is_none() {
return Err("open a document with data sets".into());
}
let mut params = json!({
"commands": commands,
"label": a.name,
"source": source,
"destination": destination,
"overrideOpen": d.bool("overrideOpen"),
"overrideSave": d.bool("overrideSave"),
"overrideExport": d.bool("overrideExport"),
"stopOnError": d.bool("stopOnError"),
"includeSubdirs": d.bool("includeSubdirs"),
});
if source == "folder" {
params["folder"] = json!(d.str("folder"));
}
if destination == "folder" {
params["destinationFolder"] = json!(d.str("destinationFolder"));
}
let params = params(d, &a.name, commands);
let r = app.run("action.batch", params)?;
app.ui.dialog = None;
let count = r["count"].as_u64().unwrap_or(0);
@@ -123,3 +117,76 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
}
Ok(r)
}
fn params(d: &Dialog, action_name: &str, commands: Vec<Value>) -> Value {
let source = d.str("source");
let destination = d.str("destination");
let mut params = json!({
"commands": commands,
"label": action_name,
"source": source,
"destination": destination,
"overrideOpen": d.bool("overrideOpen"),
"overrideSave": d.bool("overrideSave"),
"overrideExport": d.bool("overrideExport"),
"stopOnError": d.bool("stopOnError"),
"includeSubdirs": d.bool("includeSubdirs"),
});
if source == "folder" {
params["folder"] = json!(d.str("folder"));
} else if source == "datasets" {
params["datasetFileNaming"] = json!(d.str("datasetFileNaming"));
}
if destination == "folder" {
params["destinationFolder"] = json!(d.str("destinationFolder"));
}
params
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::Session;
fn shown(source: &str) -> Vec<String> {
fn collect(shape: &egui::Shape, out: &mut Vec<String>) {
match shape {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(shapes) => shapes.iter().for_each(|shape| collect(shape, out)),
_ => {}
}
}
let mut app = VectorcraftApp::new(Session::new(), Default::default());
let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test"));
d.fields["source"] = json!(source);
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut frame = ctx.run_ui(Default::default(), |ui| {
body(&mut app, ui, &mut d);
});
frame.textures_delta.clear();
let mut labels = vec![];
frame.shapes.iter().for_each(|shape| collect(&shape.shape, &mut labels));
labels
}
#[test]
fn file_name_choice_shows_only_for_data_sets() {
assert!(shown("datasets").iter().any(|label| label == "File Name:"));
assert!(shown("datasets").iter().any(|label| label == "File + Number"));
assert!(!shown("folder").iter().any(|label| label == "File Name:"));
}
#[test]
fn data_set_naming_params_follow_dialog_choice() {
let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test"));
assert_eq!(d.str("datasetFileNaming"), "fileNumber");
d.fields["source"] = json!("datasets");
for choice in ["fileNumber", "fileDataSetName", "dataSetName"] {
d.fields["datasetFileNaming"] = json!(choice);
assert_eq!(params(&d, "Test", vec![])["datasetFileNaming"], choice);
}
d.fields["source"] = json!("folder");
assert!(params(&d, "Test", vec![]).get("datasetFileNaming").is_none());
}
}