From 7fd287238fcd0de0fa177c675b780179fd1e06ec Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 17:28:30 -0700 Subject: [PATCH 1/6] Actions: Batch refuses Save and Close over data sets instead of saving each one over the open document Data sets share one open document, so Save and Close saved every data set over the same file and only the last one survived. action.batch now returns an error for source datasets with destination saveAndClose before it selects a data set or runs a step, and the Batch dialog offers None and Folder for a Data Sets source, moving a Save and Close choice to Folder. --- crates/engine/src/cmd/fileio/action_batch.rs | 76 ++++++++++++++++++++ crates/engine/src/cmd/fileio/commands.rs | 2 +- crates/ui-egui/src/dialogs/action_batch.rs | 42 +++++++++-- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/mcp.md | 4 +- docs/target-app-parity.md | 1 + 31 files changed, 145 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/cmd/fileio/action_batch.rs b/crates/engine/src/cmd/fileio/action_batch.rs index e11b6adcd..c1b5ae016 100644 --- a/crates/engine/src/cmd/fileio/action_batch.rs +++ b/crates/engine/src/cmd/fileio/action_batch.rs @@ -73,6 +73,10 @@ pub(super) fn action_batch(s: &mut Session, p: &Value) -> Result { if !matches!(destination, "none" | "saveAndClose" | "folder") { return Err(bad(C, "`destination` must be `none`, `saveAndClose` or `folder`")); } + // Data sets share one open document, so Save and Close would write every row over one file. + if source == "datasets" && destination == "saveAndClose" { + return Err(bad(C, "Save and Close cannot batch data sets: choose Folder to save each data set to its own file")); + } let dest_folder = str_param(p, "destinationFolder").map(absolute_path); if destination == "folder" { let Some(ref folder) = dest_folder else { @@ -1292,6 +1296,78 @@ mod tests { assert_eq!(file_names(&dest), ["Untitled_A-B-3.vectorcraft", "Untitled_a-b-2.vectorcraft", "Untitled_a-b.vectorcraft"], "{r}"); } + /// A saved document with a text object bound to `Name` and two data sets, `A` and `B`. + fn saved_with_two_data_sets(path: &std::path::Path) -> Session { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap(); + let id = s.execute("text.create", &json!({"x": 10, "y": 40, "text": "A"})).unwrap()["id"].as_u64().unwrap(); + s.execute("variable.define", &json!({"name": "Name", "kind": "text"})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Name", "ids": [id]})).unwrap(); + s.execute("dataset.capture", &json!({"name": "A"})).unwrap(); + s.execute("text.setText", &json!({"ids": [id], "text": "B"})).unwrap(); + s.execute("dataset.capture", &json!({"name": "B"})).unwrap(); + s.execute("document.save", &json!({"path": path.to_string_lossy()})).unwrap(); + s + } + + fn save_and_close_over_data_sets(s: &mut Session) -> Result { + s.execute( + "action.batch", + &json!({"source": "datasets", "destination": "saveAndClose", "commands": [{"command": "object.unlockAll", "params": {}}]}), + ) + } + + #[test] + fn batch_refuses_save_and_close_over_data_sets_before_writing() { + let path = tmp("save-close-sets").join("template.vectorcraft"); + let mut s = saved_with_two_data_sets(&path); + let bytes = std::fs::read(&path).unwrap(); + let (active, revision) = { + let st = s.doc().unwrap(); + (st.doc.variables.active_dataset.clone(), st.revision) + }; + let msg = save_and_close_over_data_sets(&mut s).unwrap_err().to_string(); + assert!(msg.contains("choose Folder"), "{msg}"); + assert_eq!(std::fs::read(&path).unwrap(), bytes, "the template is not written"); + let st = s.doc().unwrap(); + assert_eq!(st.doc.variables.active_dataset, active, "no data set is selected"); + assert_eq!(st.revision, revision, "no step runs"); + assert_eq!(st.path.as_deref(), Some(path.to_string_lossy().as_ref()), "the template stays open"); + } + + #[test] + fn batch_refuses_save_and_close_over_data_sets_of_an_unsaved_document() { + let mut s = with_data_sets(&["a", "b"]); + let (active, revision) = { + let st = s.doc().unwrap(); + (st.doc.variables.active_dataset.clone(), st.revision) + }; + let msg = save_and_close_over_data_sets(&mut s).unwrap_err().to_string(); + assert!(msg.contains("choose Folder"), "{msg}"); + let st = s.doc().unwrap(); + assert_eq!(st.doc.variables.active_dataset, active, "no data set is selected"); + assert_eq!(st.revision, revision, "no step runs"); + assert!(st.path.is_none()); + } + + #[test] + fn batch_save_and_close_saves_and_closes_each_source_file() { + let src = tmp("save-close-src"); + let a = write_svg(&src, "a.svg", "#f00"); + let before = std::fs::read(&a).unwrap(); + let mut s = Session::new(); + let r = s + .execute( + "action.batch", + &json!({"folder": src.to_string_lossy(), "destination": "saveAndClose", "commands": [{"command": "object.unlockAll", "params": {}}]}), + ) + .unwrap(); + assert_eq!(r["failed"], 0, "{r}"); + assert_ne!(std::fs::read(&a).unwrap(), before, "the source file is saved over"); + assert_eq!(file_names(&src), ["a.svg"], "{r}"); + assert!(s.doc().is_err(), "the source file is closed"); + } + #[test] fn batch_refuses_nesting_and_empty_commands() { let mut s = Session::new(); diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index 011d025e3..0c010de10 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { "Batch…", [], None, - "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped}", + "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; refused for source datasets: use folder to save each data set to its own file) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped}", always, action_batch::action_batch ), diff --git a/crates/ui-egui/src/dialogs/action_batch.rs b/crates/ui-egui/src/dialogs/action_batch.rs index 0d6382e9a..36369132a 100644 --- a/crates/ui-egui/src/dialogs/action_batch.rs +++ b/crates/ui-egui/src/dialogs/action_batch.rs @@ -77,7 +77,12 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } ui.add_space(8.0); - form::choice(ui, d, "destination", "Destination:", (LABEL, 200.0), &[("none", "None"), ("saveAndClose", "Save and Close"), ("folder", "Folder")]); + let choices = destinations(&source); + let destination = d.str("destination"); + if !choices.iter().any(|(value, _)| *value == destination) { + d.fields.insert("destination".into(), json!("folder")); + } + form::choice(ui, d, "destination", "Destination:", (LABEL, 200.0), choices); if d.str("destination") == "folder" { widgets::label_row(ui, tl!("Destination Folder:"), LABEL, |ui| { form::text(ui, d, "destinationFolder", 200.0); @@ -95,6 +100,16 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { false } +/// The Destination choices for `source`. Data sets share one open document, so Save and Close is +/// offered for a Folder source only. +fn destinations(source: &str) -> &'static [(&'static str, &'static str)] { + if source == "datasets" { + &[("none", "None"), ("folder", "Folder")] + } else { + &[("none", "None"), ("saveAndClose", "Save and Close"), ("folder", "Folder")] + } +} + fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let set = d.fields.get("set").and_then(Value::as_u64).unwrap_or(0) as usize; let action = d.fields.get("action").and_then(Value::as_u64).unwrap_or(0) as usize; @@ -161,6 +176,13 @@ mod tests { use vectorcraft_engine::Session; fn shown(source: &str) -> Vec { + let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test")); + d.fields["source"] = json!(source); + render(&mut d) + } + + /// Draws `d` once and returns the texts it shows. + fn render(d: &mut Dialog) -> Vec { fn collect(shape: &egui::Shape, out: &mut Vec) { match shape { egui::Shape::Text(t) => out.push(t.galley.text().to_string()), @@ -169,12 +191,10 @@ mod tests { } } 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); + body(&mut app, ui, d); }); frame.textures_delta.clear(); let mut labels = vec![]; @@ -220,4 +240,18 @@ mod tests { d.fields["source"] = json!("folder"); assert!(params(&d, "Test", vec![]).get("datasetFileNaming").is_none()); } + + #[test] + fn data_sets_do_not_offer_save_and_close() { + assert!(!destinations("datasets").iter().any(|(value, _)| *value == "saveAndClose")); + assert!(destinations("folder").iter().any(|(value, _)| *value == "saveAndClose")); + let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test")); + d.fields["destination"] = json!("saveAndClose"); + assert!(render(&mut d).iter().any(|label| label == "Save and Close")); + d.fields["source"] = json!("datasets"); + let labels = render(&mut d); + assert!(!labels.iter().any(|label| label == "Save and Close"), "{labels:?}"); + assert!(labels.iter().any(|label| label == "Destination Folder:"), "{labels:?}"); + assert_eq!(params(&d, "Test", vec![])["destination"], "folder"); + } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 8bdbf65a9..85c714b78 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6099,6 +6099,7 @@ breakText Glyphs Глифи File + Data Set Name Файл + име на набора от данни Data Set Name Име на набора от данни @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` трябва да е `fileNumber`, `fileDataSetName` или `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Запазване и затваряне“ не може да обработва набори от данни: изберете „Папка“, за да запишете всеки набор от данни в отделен файл @msg no free data set file name in destination folder Няма свободно име за файл на набор от данни в целевата папка # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index ca52bf517..edd42d7c4 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6111,6 +6111,7 @@ shape Open Otvoren File + Data Set Name Datoteka + naziv skupa podataka Data Set Name Naziv skupa podataka @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` mora biti `fileNumber`, `fileDataSetName` ili `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Sačuvaj i zatvori“ ne može obraditi skupove podataka: odaberite „Folder“ da biste svaki skup podataka sačuvali u zasebnu datoteku @msg no free data set file name in destination folder Nema slobodnog naziva datoteke skupa podataka u odredišnoj mapi # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 0f39585bb..f2b00581a 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6255,6 +6255,7 @@ breakText Glyphs Glifs File + Data Set Name Fitxer + nom del conjunt de dades Data Set Name Nom del conjunt de dades @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` ha de ser `fileNumber`, `fileDataSetName` o `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Desa i tanca» no pot processar conjunts de dades: trieu «Carpeta» per desar cada conjunt de dades en un fitxer propi @msg no free data set file name in destination folder No hi ha cap nom de fitxer de conjunt de dades lliure a la carpeta de destinació # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 13700edfa..66aaefa8e 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6274,6 +6274,7 @@ breakText Glyphs Glyfy File + Data Set Name Soubor + název sady dat Data Set Name Název sady dat @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musí být `fileNumber`, `fileDataSetName` nebo `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Uložit a zavřít“ nelze použít pro sady dat: zvolte „Složka“, aby se každá sada dat uložila do vlastního souboru @msg no free data set file name in destination folder V cílové složce není volný název souboru sady dat # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index d50e862bb..3e9389579 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6114,6 +6114,7 @@ breakText Glyphs Glyffer File + Data Set Name Fil + datasætnavn Data Set Name Datasætnavn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` skal være `fileNumber`, `fileDataSetName` eller `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Gem og luk" kan ikke køre på datasæt: vælg "Mappe" for at gemme hvert datasæt i sin egen fil @msg no free data set file name in destination folder Intet ledigt filnavn til datasættet i destinationsmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 9938513a8..75345b1f7 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5859,6 +5859,7 @@ breakText Glyphs Glyphen File + Data Set Name Datei + Datensatzname Data Set Name Datensatzname @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` muss `fileNumber`, `fileDataSetName` oder `dataSetName` sein +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Speichern und schließen“ kann nicht auf Datensätze angewendet werden: Wählen Sie „Ordner“, um jeden Datensatz in einer eigenen Datei zu speichern @msg no free data set file name in destination folder Kein freier Dateiname für den Datensatz im Zielordner # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index c2330c4bf..cf071420c 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6225,6 +6225,7 @@ breakText Glyphs Γλύφοι File + Data Set Name Αρχείο + όνομα συνόλου δεδομένων Data Set Name Όνομα συνόλου δεδομένων @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` Το `datasetFileNaming` πρέπει να είναι `fileNumber`, `fileDataSetName` ή `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file Η «Αποθήκευση και κλείσιμο» δεν μπορεί να εφαρμοστεί σε σύνολα δεδομένων: επιλέξτε «Φάκελος» για να αποθηκευτεί κάθε σύνολο δεδομένων σε δικό του αρχείο @msg no free data set file name in destination folder Δεν υπάρχει διαθέσιμο όνομα αρχείου συνόλου δεδομένων στον φάκελο προορισμού # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e4807fae6..29d345748 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6216,6 +6216,7 @@ breakText Glyphs Glifos File + Data Set Name Archivo + nombre del conjunto de datos Data Set Name Nombre del conjunto de datos @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` debe ser `fileNumber`, `fileDataSetName` o `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Guardar y cerrar» no puede procesar conjuntos de datos: elija «Carpeta» para guardar cada conjunto de datos en su propio archivo @msg no free data set file name in destination folder No hay un nombre de archivo libre para el conjunto de datos en la carpeta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 3970072a3..7e0695592 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6304,6 +6304,7 @@ breakText Glyphs Merkit File + Data Set Name Tiedosto + tietojoukon nimi Data Set Name Tietojoukon nimi @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`-arvon on oltava `fileNumber`, `fileDataSetName` tai `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file ”Tallenna ja sulje” ei voi käsitellä tietojoukkoja: valitse ”Kansio”, niin jokainen tietojoukko tallennetaan omaan tiedostoonsa @msg no free data set file name in destination folder Kohdekansiossa ei ole vapaata tietojoukon tiedostonimeä # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 1dd0177b6..69a5d71e7 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6269,6 +6269,7 @@ breakText Glyphs Glyphes File + Data Set Name Fichier + nom du jeu de données Data Set Name Nom du jeu de données @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` doit être `fileNumber`, `fileDataSetName` ou `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file « Enregistrer et fermer » ne peut pas traiter les jeux de données : choisissez « Dossier » pour enregistrer chaque jeu de données dans son propre fichier @msg no free data set file name in destination folder Aucun nom de fichier libre pour le jeu de données dans le dossier de destination # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index fe27d894f..f33c5d7d6 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6258,6 +6258,7 @@ breakText Glyphs Glifák File + Data Set Name Fájl + adatkészlet neve Data Set Name Adatkészlet neve @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` A `datasetFileNaming` értéke `fileNumber`, `fileDataSetName` vagy `dataSetName` lehet +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file A „Mentés és bezárás” nem dolgozhat fel adatkészleteket: válassza a „Mappa” lehetőséget, hogy minden adatkészlet külön fájlba kerüljön @msg no free data set file name in destination folder Nincs szabad adatkészlet-fájlnév a célmappában # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 4d0377083..e7b1655d6 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6114,6 +6114,7 @@ breakText Glyphs Glif File + Data Set Name File + nama set data Data Set Name Nama set data @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` harus `fileNumber`, `fileDataSetName`, atau `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Simpan dan tutup" tidak dapat memproses set data: pilih "Folder" untuk menyimpan setiap set data ke file tersendiri @msg no free data set file name in destination folder Tidak ada nama file set data yang tersedia di folder tujuan # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index cf88a606a..231ed0230 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5893,6 +5893,7 @@ breakText Glyphs Glifi File + Data Set Name File + nome set di dati Data Set Name Nome set di dati @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve essere `fileNumber`, `fileDataSetName` o `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Salva e chiudi» non può elaborare i set di dati: scegli «Cartella» per salvare ogni set di dati in un file a sé @msg no free data set file name in destination folder Nessun nome file libero per il set di dati nella cartella di destinazione # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index d4ee9ab66..ac6bb51bc 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5958,6 +5958,7 @@ breakText Glyphs 字形 File + Data Set Name ファイル名 + データセット名 Data Set Name データセット名 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` は `fileNumber`、`fileDataSetName`、`dataSetName` のいずれかである必要があります +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 「保存して閉じる」ではデータセットをバッチ処理できません:各データセットを個別のファイルに保存するには「フォルダー」を選択してください @msg no free data set file name in destination folder 保存先フォルダーに使用可能なデータセットのファイル名がありません # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index f46a2eb14..a0b1d42a6 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6271,6 +6271,7 @@ breakText Glyphs გლიფები File + Data Set Name ფაილი + მონაცემთა ნაკრების სახელი Data Set Name მონაცემთა ნაკრების სახელი @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` უნდა იყოს `fileNumber`, `fileDataSetName` ან `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „შენახვა და დახურვა“ ვერ ამუშავებს მონაცემთა ნაკრებებს: აირჩიეთ „საქაღალდე“, რომ თითოეული მონაცემთა ნაკრები ცალკე ფაილში შეინახოთ @msg no free data set file name in destination folder დანიშნულების საქაღალდეში მონაცემთა ნაკრებისთვის თავისუფალი ფაილის სახელი არ არის # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 9b6c84093..c083822b2 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5854,6 +5854,7 @@ breakText Glyphs 글리프 File + Data Set Name 파일 + 데이터 세트 이름 Data Set Name 데이터 세트 이름 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`은 `fileNumber`, `fileDataSetName` 또는 `dataSetName`이어야 합니다 +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file '저장 후 닫기'로는 데이터 세트를 일괄 처리할 수 없습니다: 각 데이터 세트를 별도의 파일로 저장하려면 '폴더'를 선택하세요 @msg no free data set file name in destination folder 대상 폴더에 사용할 수 있는 데이터 세트 파일 이름이 없습니다 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index bd525526b..5c9bd74f1 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6160,6 +6160,7 @@ breakText Glyphs Glyphs File + Data Set Name Bestand + naam gegevensset Data Set Name Naam gegevensset @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` moet `fileNumber`, `fileDataSetName` of `dataSetName` zijn +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 'Opslaan en sluiten' kan geen gegevenssets verwerken: kies 'Map' om elke gegevensset in een eigen bestand op te slaan @msg no free data set file name in destination folder Geen vrije bestandsnaam voor de gegevensset in de doelmap # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index f00ae7858..cb934d784 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -6128,6 +6128,7 @@ breakText Glyphs Glify File + Data Set Name Plik + nazwa zestawu danych Data Set Name Nazwa zestawu danych @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musi mieć wartość `fileNumber`, `fileDataSetName` lub `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Zapisz i zamknij” nie może przetwarzać zestawów danych: wybierz „Folder”, aby zapisać każdy zestaw danych w osobnym pliku @msg no free data set file name in destination folder Brak wolnej nazwy pliku zestawu danych w folderze docelowym # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5d277fab5..8ac21f8e9 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6190,6 +6190,7 @@ breakText Glyphs Glifos File + Data Set Name Arquivo + nome do conjunto de dados Data Set Name Nome do conjunto de dados @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve ser `fileNumber`, `fileDataSetName` ou `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Salvar e fechar" não pode processar conjuntos de dados: escolha "Pasta" para salvar cada conjunto de dados em um arquivo próprio @msg no free data set file name in destination folder Não há nome de arquivo livre para o conjunto de dados na pasta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 42d30eadd..d436e1b7e 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6251,6 +6251,7 @@ breakText Glyphs Глифы File + Data Set Name Файл + имя набора данных Data Set Name Имя набора данных @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` должно быть `fileNumber`, `fileDataSetName` или `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Сохранить и закрыть» не работает с наборами данных: выберите «Папка», чтобы сохранить каждый набор данных в отдельный файл @msg no free data set file name in destination folder В папке назначения нет свободного имени файла для набора данных # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 896b59c2e..4438027d8 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6326,6 +6326,7 @@ breakText Glyphs Glyfer File + Data Set Name Fil + datauppsättningens namn Data Set Name Datauppsättningens namn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` måste vara `fileNumber`, `fileDataSetName` eller `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file ”Spara och stäng” kan inte köras på datauppsättningar: välj ”Mapp” för att spara varje datauppsättning i en egen fil @msg no free data set file name in destination folder Inget ledigt filnamn för datauppsättningen i målmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 5cf7665d4..5157d5bf3 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6231,6 +6231,7 @@ breakText Glyphs Glifler File + Data Set Name Dosya + veri kümesi adı Data Set Name Veri kümesi adı @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` değeri `fileNumber`, `fileDataSetName` veya `dataSetName` olmalıdır +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Kaydet ve Kapat" veri kümelerini toplu işleyemez: her veri kümesini ayrı bir dosyaya kaydetmek için "Klasör" seçeneğini seçin @msg no free data set file name in destination folder Hedef klasörde veri kümesi için boş bir dosya adı yok # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index e7055639b..db7e9ccd2 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5873,6 +5873,7 @@ breakText Glyphs Гліфи File + Data Set Name Файл + назва набору даних Data Set Name Назва набору даних @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` має бути `fileNumber`, `fileDataSetName` або `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Зберегти й закрити» не працює з наборами даних: виберіть «Папка», щоб зберегти кожен набір даних в окремий файл @msg no free data set file name in destination folder У папці призначення немає вільного імені файлу набору даних # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 4b9de70d4..f59304809 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5874,6 +5874,7 @@ breakText Glyphs Glyph File + Data Set Name Tệp + tên tập dữ liệu Data Set Name Tên tập dữ liệu @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` phải là `fileNumber`, `fileDataSetName` hoặc `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Lưu và đóng" không thể xử lý các tập dữ liệu: chọn "Thư mục" để lưu mỗi tập dữ liệu vào một tệp riêng @msg no free data set file name in destination folder Không có tên tệp tập dữ liệu còn trống trong thư mục đích # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 225ca1112..379f4dc23 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6115,6 +6115,7 @@ breakText Glyphs 字形 File + Data Set Name 文件 + 数据集名称 Data Set Name 数据集名称 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必须是 `fileNumber`、`fileDataSetName` 或 `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file “存储并关闭”无法批处理数据集:请选择“文件夹”,将每个数据集存储为单独的文件 @msg no free data set file name in destination folder 目标文件夹中没有可用的数据集文件名 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index e18a0fc58..46c332bd4 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5911,6 +5911,7 @@ breakText Glyphs 字符 File + Data Set Name 檔案 + 資料集名稱 Data Set Name 資料集名稱 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必須是 `fileNumber`、`fileDataSetName` 或 `dataSetName` +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 「儲存並關閉」無法批次處理資料集:請選擇「資料夾」,將每個資料集儲存為個別檔案 @msg no free data set file name in destination folder 目的地資料夾中沒有可用的資料集檔案名稱 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/docs/mcp.md b/docs/mcp.md index 426fb7d88..41d518456 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3514,7 +3514,9 @@ Actions panel plays an action as a `command.batch`, omitting steps whose playbac cleared. An action set saved as `.vcactions` keeps each excluded step with `excluded: true`; loading an earlier version 1 set without that flag includes every step. The Actions panel menu's Batch… runs `action.batch`: the same steps on every readable file in a folder (or every data set of the open document), with -destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. For a Data Sets source, +destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. A Data Sets source does not +take `saveAndClose`: `action.batch` refuses it before it selects a data set or runs a step, and the dialog offers +None and Folder only. For a Data Sets source, `datasetFileNaming?: fileNumber | fileDataSetName | dataSetName` chooses File + Number (`source001`), File + Data Set Name (`source_Alpha`), or Data Set Name (`Alpha`). An omitted parameter keeps File + Data Set Name for existing callers; the dialog starts with File + Number. A folder source ignores this parameter. The folder diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index d1d0c19e6..470d36886 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -643,6 +643,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used. - Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html). + - Batch… over data sets offers None and Folder as the destination, and `action.batch` refuses Save and Close for a Data Sets source before it runs a step, so the batch does not save every data set over the open document's file. This is VectorCraft's own rule: [Play actions on a batch of files](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/play-actions-on-a-batch-of-files.html) describes Save and Close as saving "the file in their current location" and does not say what it does over data sets. - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`). - Export presets: the Asset Export panel menu lists the export presets of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included. - Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`. From 8a52d50154d7ea18e3687ab2b4d5342290ca6d87 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 17:39:03 -0700 Subject: [PATCH 2/6] i18n: French rows keep the no-break space before punctuation --- crates/ui-egui/src/i18n/fr.tsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 69a5d71e7..e814e8378 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6269,7 +6269,7 @@ breakText Glyphs Glyphes File + Data Set Name Fichier + nom du jeu de données Data Set Name Nom du jeu de données @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` doit être `fileNumber`, `fileDataSetName` ou `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file « Enregistrer et fermer » ne peut pas traiter les jeux de données : choisissez « Dossier » pour enregistrer chaque jeu de données dans son propre fichier +@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file « Enregistrer et fermer » ne peut pas traiter les jeux de données : choisissez « Dossier » pour enregistrer chaque jeu de données dans son propre fichier @msg no free data set file name in destination folder Aucun nom de fichier libre pour le jeu de données dans le dossier de destination # Path Precision Tools add-on: Path Effects (Effect › Path Effects). From 2ad0268388427bfc2b8a85520ed7b7e790e44172 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 01:06:03 -0700 Subject: [PATCH 3/6] Actions: Batch over data sets keeps Save and Close and warns when each data set saves over one file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batch… with a Data Sets source offers None, Save and Close and Folder again. Save and Close saves the open document over its own file after each data set. When every data set saves over the same file, the dialog shows a warning and `action.batch` returns `warning`: with Save and Close, or with a recorded step that writes a file and that the batch does not point into the destination folder. A Save step without a path counts when it writes the document's own file; Save As, Save a Copy, Save as Template and Export steps count when they give a path; Export for Screens counts when its folder, given or from its preset, names a folder. Save and Close over the data sets of a document that was never saved is refused with "the document has never been saved" before a data set is selected. --- crates/engine/src/cmd/fileio.rs | 1 + crates/engine/src/cmd/fileio/action_batch.rs | 142 ++++++++++++++++--- crates/engine/src/cmd/fileio/commands.rs | 2 +- crates/engine/src/cmd/fileio/screens.rs | 2 +- crates/ui-egui/src/dialogs/action_batch.rs | 68 ++++++--- crates/ui-egui/src/i18n/bg.tsv | 2 +- crates/ui-egui/src/i18n/bs.tsv | 2 +- crates/ui-egui/src/i18n/ca.tsv | 2 +- crates/ui-egui/src/i18n/cs.tsv | 2 +- crates/ui-egui/src/i18n/da.tsv | 2 +- crates/ui-egui/src/i18n/de.tsv | 2 +- crates/ui-egui/src/i18n/el.tsv | 2 +- crates/ui-egui/src/i18n/es.tsv | 2 +- crates/ui-egui/src/i18n/fi.tsv | 2 +- crates/ui-egui/src/i18n/fr.tsv | 2 +- crates/ui-egui/src/i18n/hu.tsv | 2 +- crates/ui-egui/src/i18n/id.tsv | 2 +- crates/ui-egui/src/i18n/it.tsv | 2 +- crates/ui-egui/src/i18n/ja.tsv | 2 +- crates/ui-egui/src/i18n/ka.tsv | 2 +- crates/ui-egui/src/i18n/ko.tsv | 2 +- crates/ui-egui/src/i18n/nl.tsv | 2 +- crates/ui-egui/src/i18n/pl.tsv | 2 +- crates/ui-egui/src/i18n/pt-br.tsv | 2 +- crates/ui-egui/src/i18n/ru.tsv | 2 +- crates/ui-egui/src/i18n/sv.tsv | 2 +- crates/ui-egui/src/i18n/tr.tsv | 2 +- crates/ui-egui/src/i18n/uk.tsv | 2 +- crates/ui-egui/src/i18n/vi.tsv | 2 +- crates/ui-egui/src/i18n/zh-hans.tsv | 2 +- crates/ui-egui/src/i18n/zh-hant.tsv | 2 +- docs/mcp.md | 9 +- docs/target-app-parity.md | 2 +- 33 files changed, 206 insertions(+), 72 deletions(-) diff --git a/crates/engine/src/cmd/fileio.rs b/crates/engine/src/cmd/fileio.rs index 0c888d13c..ae2ecde66 100644 --- a/crates/engine/src/cmd/fileio.rs +++ b/crates/engine/src/cmd/fileio.rs @@ -54,6 +54,7 @@ pub mod zip; use serde_json::{Value, json}; +pub use action_batch::batch_overwrites_one_file; pub use dxf::{UNSUPPORTED, Unsupported, unsupported}; pub use encode::{ARTBOARD_PARAMS, ArtboardPick, Encoded, encode, encode_all, encode_with_warnings}; pub(crate) use encode::{anti_alias, background, with_single_artboard}; diff --git a/crates/engine/src/cmd/fileio/action_batch.rs b/crates/engine/src/cmd/fileio/action_batch.rs index c1b5ae016..26d6c476e 100644 --- a/crates/engine/src/cmd/fileio/action_batch.rs +++ b/crates/engine/src/cmd/fileio/action_batch.rs @@ -73,10 +73,6 @@ pub(super) fn action_batch(s: &mut Session, p: &Value) -> Result { if !matches!(destination, "none" | "saveAndClose" | "folder") { return Err(bad(C, "`destination` must be `none`, `saveAndClose` or `folder`")); } - // Data sets share one open document, so Save and Close would write every row over one file. - if source == "datasets" && destination == "saveAndClose" { - return Err(bad(C, "Save and Close cannot batch data sets: choose Folder to save each data set to its own file")); - } let dest_folder = str_param(p, "destinationFolder").map(absolute_path); if destination == "folder" { let Some(ref folder) = dest_folder else { @@ -99,11 +95,65 @@ pub(super) fn action_batch(s: &mut Session, p: &Value) -> Result { }; match source { "folder" => batch_folder(s, C, p, override_open, &run), - "datasets" => batch_datasets(s, C, &run), + "datasets" => { + let one_file = batch_overwrites_one_file(s, p); + let mut r = batch_datasets(s, C, &run)?; + if one_file { + r["warning"] = json!(ONE_FILE_WARNING); + } + Ok(r) + } _ => Err(bad(C, "`source` must be `folder` or `datasets`")), } } +/// The `warning` of a Data Sets batch for which [`batch_overwrites_one_file`] holds. +#[cfg(not(target_arch = "wasm32"))] +const ONE_FILE_WARNING: &str = "each data set saves over the same file, which keeps only the last data set"; + +/// Whether an `action.batch` with params `p` plays on data sets of the open document in `s` and +/// writes each data set to the same file. Save and Close saves the open document over its own file +/// after every data set. A step that the batch does not point into `destinationFolder` writes the +/// same file every time when it writes one: a Save, Save As, Save a Copy, Save as Template, Export +/// or Export Selection step that gives a `path`; a Save step without a `path` when the document has +/// a file of its own (saved, and not converted from an older version or restored by Data Recovery) +/// and the step gives no `format` or the document's own; an Export for Screens +/// step whose `folder`, given or from its `exportPreset`, names a folder, whatever the overrides. +/// Other steps return their data instead of writing a file. +pub fn batch_overwrites_one_file(s: &Session, p: &Value) -> bool { + if p.get("source").and_then(Value::as_str) != Some("datasets") { + return false; + } + if p.get("destination").and_then(Value::as_str) == Some("saveAndClose") { + return true; + } + let folder = p.get("destinationFolder").and_then(Value::as_str).is_some(); + let redirected = |key: &str| folder && p.get(key).and_then(Value::as_bool).unwrap_or(true); + let (save, export) = (redirected("overrideSave"), redirected("overrideExport")); + // The format of the open document's own file, which a Save step without a `path` writes. + let own_format = s.active().filter(|st| st.path.is_some() && !st.converted && !st.recovered).map(|st| st.format); + let names = |v: Option<&Value>| v.and_then(Value::as_str).is_some_and(|s| !s.is_empty()); + p.get("commands").and_then(Value::as_array).is_some_and(|cmds| { + cmds.iter().any(|c| { + let params = c.get("params").cloned().unwrap_or_else(|| Value::Object(Default::default())); + let path = names(params.get("path")); + let own_file = || match params.get("format").and_then(Value::as_str).filter(|f| !f.is_empty()) { + None => own_format.is_some(), + Some(f) => own_format.is_some_and(|own| super::format(f).is_some_and(|f| f.id == own)), + }; + match c.get("command").and_then(Value::as_str).unwrap_or("") { + "document.save" | "file.save" => !save && (path || own_file()), + "file.saveAs" => !save && path, + "file.saveCopy" | "file.saveAsTemplate" => path, + "document.export" | "document.exportSelection" => !export && path, + // Override Export sets `path`, which Export for Screens does not read: its `folder` stays. + "document.exportForScreens" => super::screens::with_preset(s, ¶ms).is_ok_and(|p| names(p.get("folder"))), + _ => false, + } + }) + }) +} + #[cfg(not(target_arch = "wasm32"))] fn batch_folder(s: &mut Session, cmd: &str, p: &Value, override_open: bool, run: &Run<'_>) -> Result { use super::{absolute_path, may_read}; @@ -149,6 +199,9 @@ fn batch_datasets(s: &mut Session, cmd: &str, run: &Run<'_>) -> Result { if names.len() > MAX_BATCH_FILES { return Err(bad(cmd, format!("more than {MAX_BATCH_FILES} data sets"))); } + if run.destination == "saveAndClose" && st.path.is_none() { + return Err(bad(cmd, "the document has never been saved")); + } let doc_stem = st.path.as_deref().and_then(|p| std::path::Path::new(p).file_stem().and_then(|s| s.to_str())).unwrap_or("Untitled").to_string(); let stems = dataset_stems(&names, &doc_stem, run.dataset_file_naming, run, &st.doc)?; let steps = filter_steps(run.steps, true, false); @@ -1318,21 +1371,76 @@ mod tests { } #[test] - fn batch_refuses_save_and_close_over_data_sets_before_writing() { + fn batch_save_and_close_over_data_sets_saves_each_over_the_template_and_warns() { let path = tmp("save-close-sets").join("template.vectorcraft"); let mut s = saved_with_two_data_sets(&path); - let bytes = std::fs::read(&path).unwrap(); - let (active, revision) = { - let st = s.doc().unwrap(); - (st.doc.variables.active_dataset.clone(), st.revision) - }; - let msg = save_and_close_over_data_sets(&mut s).unwrap_err().to_string(); - assert!(msg.contains("choose Folder"), "{msg}"); - assert_eq!(std::fs::read(&path).unwrap(), bytes, "the template is not written"); + std::fs::write(&path, b"stale").unwrap(); + let r = save_and_close_over_data_sets(&mut s).unwrap(); + assert_eq!((r["count"].as_u64(), r["failed"].as_u64()), (Some(2), Some(0)), "{r}"); + assert_eq!(r["warning"], ONE_FILE_WARNING, "{r}"); + assert_ne!(std::fs::read(&path).unwrap(), b"stale", "the template's file is saved over"); + assert_eq!(file_names(path.parent().unwrap()), ["template.vectorcraft"], "{r}"); let st = s.doc().unwrap(); - assert_eq!(st.doc.variables.active_dataset, active, "no data set is selected"); - assert_eq!(st.revision, revision, "no step runs"); + assert_eq!(st.doc.variables.active_dataset.as_deref(), Some("B"), "the last data set is applied"); assert_eq!(st.path.as_deref(), Some(path.to_string_lossy().as_ref()), "the template stays open"); + assert!(!st.is_dirty(), "the last data set is saved"); + } + + #[test] + fn batch_warns_when_a_recorded_save_writes_one_file_for_every_data_set() { + let path = tmp("recorded-save-sets").join("template.vectorcraft"); + let mut s = saved_with_two_data_sets(&path); + let save = json!([{"command": "document.save", "params": {}}]); + let r = s.execute("action.batch", &json!({"source": "datasets", "commands": save})).unwrap(); + assert_eq!(r["failed"], 0, "{r}"); + assert_eq!(r["warning"], ONE_FILE_WARNING, "{r}"); + let dest = tmp("recorded-save-dest"); + let p = json!({"source": "datasets", "destination": "folder", "destinationFolder": dest.to_string_lossy(), "commands": save}); + let r = s.execute("action.batch", &p).unwrap(); + assert!(r.get("warning").is_none(), "Override Save points each save into the folder: {r}"); + let unlock = json!({"source": "datasets", "commands": [{"command": "object.unlockAll", "params": {}}]}); + let r = s.execute("action.batch", &unlock).unwrap(); + assert!(r.get("warning").is_none(), "{r}"); + } + + #[test] + fn one_file_rule_covers_save_and_close_and_steps_left_in_place() { + let saved = saved_with_two_data_sets(&tmp("one-file-rule").join("template.vectorcraft")); + let unsaved = with_data_sets(&["a"]); + let with = |extra: Value| { + let mut p = json!({"source": "datasets", "commands": [{"command": "document.export", "params": {"path": "/x/a.png"}}]}); + for (k, v) in extra.as_object().unwrap() { + p[k] = v.clone(); + } + batch_overwrites_one_file(&saved, &p) + }; + assert!(with(json!({})), "an export with its own path"); + assert!(!with(json!({"destination": "folder", "destinationFolder": "/out"}))); + assert!(with(json!({"destination": "folder", "destinationFolder": "/out", "overrideExport": false}))); + assert!(!with(json!({"destination": "folder", "destinationFolder": "/out", "overrideSave": false}))); + assert!(!with(json!({"source": "folder"})), "a folder source opens a file per pass"); + assert!(with(json!({"destination": "saveAndClose", "commands": [{"command": "object.unlockAll"}]}))); + let saved_as = json!({"commands": [{"command": "file.saveAs", "params": {"path": "/x/a.svg"}}]}); + assert!(with(saved_as.clone())); + assert!(!with(json!({"destination": "folder", "destinationFolder": "/out", "commands": saved_as["commands"]}))); + assert!(!with(json!({"commands": [{"command": "file.saveAs", "params": {}}]})), "no path: the data comes back"); + assert!(!with(json!({"commands": [{"command": "document.export", "params": {"format": "png"}}]})), "no path: the data comes back"); + let copy = json!([{"command": "file.saveCopy", "params": {"path": "/x/a.vectorcraft"}}]); + assert!(with(json!({"destination": "folder", "destinationFolder": "/out", "commands": copy})), "Override Save leaves copies alone"); + let save = |format: Option<&str>| { + let params = format.map_or(json!({}), |f| json!({"format": f})); + json!({"source": "datasets", "commands": [{"command": "document.save", "params": params}]}) + }; + assert!(batch_overwrites_one_file(&saved, &save(None)), "the document's own file"); + assert!(batch_overwrites_one_file(&saved, &save(Some("vectorcraft"))), "the document's own format"); + assert!(!batch_overwrites_one_file(&unsaved, &save(None)), "a document never saved has no file to write"); + assert!(!batch_overwrites_one_file(&saved, &save(Some("svg"))), "another format without a path: the data comes back"); + assert!(!with(json!({"commands": [{"command": "file.saveCopy", "params": {"folder": "/x"}}]})), "a folder alone: the data comes back"); + let screens = json!([{"command": "document.exportForScreens", "params": {"folder": "/x"}}]); + assert!(with(json!({"destination": "folder", "destinationFolder": "/out", "commands": screens})), "Override Export leaves its folder"); + assert!(!with(json!({"commands": [{"command": "document.exportForScreens", "params": {}}]}))); + let preset = json!([{"command": "document.exportForScreens", "params": {"exportPreset": "Logo Package"}}]); + assert!(!with(json!({"commands": preset})), "a preset without a folder: the data comes back"); } #[test] @@ -1343,7 +1451,7 @@ mod tests { (st.doc.variables.active_dataset.clone(), st.revision) }; let msg = save_and_close_over_data_sets(&mut s).unwrap_err().to_string(); - assert!(msg.contains("choose Folder"), "{msg}"); + assert!(msg.ends_with("the document has never been saved"), "{msg}"); let st = s.doc().unwrap(); assert_eq!(st.doc.variables.active_dataset, active, "no data set is selected"); assert_eq!(st.revision, revision, "no step runs"); diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index 0c010de10..9b62a5342 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { "Batch…", [], None, - "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; refused for source datasets: use folder to save each data set to its own file) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped}", + "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close, or a Save, Save a Copy, Save as Template or Export step that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", always, action_batch::action_batch ), diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs index 201a4b479..11e878bc1 100644 --- a/crates/engine/src/cmd/fileio/screens.rs +++ b/crates/engine/src/cmd/fileio/screens.rs @@ -670,7 +670,7 @@ fn find_preset(s: &Session, name: &str) -> Option { } /// `p` over the settings of its `exportPreset`, when it names one (its own keys win). -fn with_preset(s: &Session, p: &Value) -> Result { +pub(super) fn with_preset(s: &Session, p: &Value) -> Result { let Some(name) = str_param(p, "exportPreset") else { return Ok(p.clone()) }; let preset = find_preset(s, name).ok_or_else(|| bad(C, format!("no export preset `{name}` (see document.exportPresets.list)")))?; let mut merged = preset.settings; diff --git a/crates/ui-egui/src/dialogs/action_batch.rs b/crates/ui-egui/src/dialogs/action_batch.rs index 36369132a..4e8a3bfae 100644 --- a/crates/ui-egui/src/dialogs/action_batch.rs +++ b/crates/ui-egui/src/dialogs/action_batch.rs @@ -77,12 +77,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { } ui.add_space(8.0); - let choices = destinations(&source); - let destination = d.str("destination"); - if !choices.iter().any(|(value, _)| *value == destination) { - d.fields.insert("destination".into(), json!("folder")); - } - form::choice(ui, d, "destination", "Destination:", (LABEL, 200.0), choices); + form::choice(ui, d, "destination", "Destination:", (LABEL, 200.0), &[("none", "None"), ("saveAndClose", "Save and Close"), ("folder", "Folder")]); if d.str("destination") == "folder" { widgets::label_row(ui, tl!("Destination Folder:"), LABEL, |ui| { form::text(ui, d, "destinationFolder", 200.0); @@ -97,17 +92,21 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { form::check(ui, d, "overrideSave", "Override Action \"Save\" Commands"); form::check(ui, d, "overrideExport", "Override Action \"Export\" Commands"); form::check(ui, d, "stopOnError", "Stop on Errors"); + if overwrites_one_file(app, d) { + ui.add_space(6.0); + ui.label(egui::RichText::new(tl!("Each data set saves over the same file, which keeps only the last data set.")).size(11.5).color(t.warning)); + } false } -/// The Destination choices for `source`. Data sets share one open document, so Save and Close is -/// offered for a Folder source only. -fn destinations(source: &str) -> &'static [(&'static str, &'static str)] { - if source == "datasets" { - &[("none", "None"), ("folder", "Folder")] - } else { - &[("none", "None"), ("saveAndClose", "Save and Close"), ("folder", "Folder")] - } +/// Whether the batch the dialog would run saves every data set over the same file +/// ([`vectorcraft_engine::cmd::fileio::batch_overwrites_one_file`]). +fn overwrites_one_file(app: &VectorcraftApp, d: &Dialog) -> bool { + let set = d.fields.get("set").and_then(Value::as_u64).unwrap_or(0) as usize; + let action = d.fields.get("action").and_then(Value::as_u64).unwrap_or(0) as usize; + let steps = app.ui.action_sets.get(set).and_then(|s| s.actions.get(action)).map_or(&[][..], |a| &a.steps[..]); + let commands = steps.iter().map(|(c, p)| json!({"command": c, "params": p})).collect(); + vectorcraft_engine::cmd::fileio::batch_overwrites_one_file(&app.session, ¶ms(d, "", commands)) } fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { @@ -183,6 +182,11 @@ mod tests { /// Draws `d` once and returns the texts it shows. fn render(d: &mut Dialog) -> Vec { + render_in(&mut VectorcraftApp::new(Session::new(), Default::default()), d) + } + + /// Draws `d` once over `app` and returns the texts it shows. + fn render_in(app: &mut VectorcraftApp, d: &mut Dialog) -> Vec { fn collect(shape: &egui::Shape, out: &mut Vec) { match shape { egui::Shape::Text(t) => out.push(t.galley.text().to_string()), @@ -190,11 +194,10 @@ mod tests { _ => {} } } - let mut app = VectorcraftApp::new(Session::new(), Default::default()); let ctx = egui::Context::default(); crate::theme::install_fonts(&ctx); let mut frame = ctx.run_ui(Default::default(), |ui| { - body(&mut app, ui, d); + body(app, ui, d); }); frame.textures_delta.clear(); let mut labels = vec![]; @@ -241,17 +244,36 @@ mod tests { assert!(params(&d, "Test", vec![]).get("datasetFileNaming").is_none()); } + const ONE_FILE: &str = "Each data set saves over the same file, which keeps only the last data set."; + #[test] - fn data_sets_do_not_offer_save_and_close() { - assert!(!destinations("datasets").iter().any(|(value, _)| *value == "saveAndClose")); - assert!(destinations("folder").iter().any(|(value, _)| *value == "saveAndClose")); + fn data_sets_offer_save_and_close_with_a_warning() { let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test")); d.fields["destination"] = json!("saveAndClose"); - assert!(render(&mut d).iter().any(|label| label == "Save and Close")); + let labels = render(&mut d); + assert!(labels.iter().any(|label| label == "Save and Close"), "{labels:?}"); + assert!(!labels.iter().any(|label| label == ONE_FILE), "a folder source opens a file per pass: {labels:?}"); d.fields["source"] = json!("datasets"); let labels = render(&mut d); - assert!(!labels.iter().any(|label| label == "Save and Close"), "{labels:?}"); - assert!(labels.iter().any(|label| label == "Destination Folder:"), "{labels:?}"); - assert_eq!(params(&d, "Test", vec![])["destination"], "folder"); + assert!(labels.iter().any(|label| label == "Save and Close"), "{labels:?}"); + assert!(labels.iter().any(|label| label == ONE_FILE), "{labels:?}"); + assert_eq!(params(&d, "Test", vec![])["destination"], "saveAndClose"); + d.fields["destination"] = json!("folder"); + assert!(!render(&mut d).iter().any(|label| label == ONE_FILE)); + } + + #[test] + fn data_sets_warn_about_a_recorded_save_left_in_place() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.ui.action_sets[0].actions[0].steps.push(("document.save".into(), json!({"path": "/x/a.vectorcraft"}))); + let mut d = Dialog::new(KIND, initial_fields(0, 0, "Test")); + d.fields["source"] = json!("datasets"); + d.fields["destinationFolder"] = json!("/out"); + assert!(!render_in(&mut app, &mut d).iter().any(|label| label == ONE_FILE), "Override Save points the save into the folder"); + d.fields["overrideSave"] = json!(false); + assert!(render_in(&mut app, &mut d).iter().any(|label| label == ONE_FILE)); + d.fields["overrideSave"] = json!(true); + d.fields["destination"] = json!("none"); + assert!(render_in(&mut app, &mut d).iter().any(|label| label == ONE_FILE)); } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 85c714b78..b94e7895c 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6099,7 +6099,7 @@ breakText Glyphs Глифи File + Data Set Name Файл + име на набора от данни Data Set Name Име на набора от данни @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` трябва да е `fileNumber`, `fileDataSetName` или `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Запазване и затваряне“ не може да обработва набори от данни: изберете „Папка“, за да запишете всеки набор от данни в отделен файл + Each data set saves over the same file, which keeps only the last data set. Всеки набор от данни се записва върху същия файл, в който остава само последният набор от данни. @msg no free data set file name in destination folder Няма свободно име за файл на набор от данни в целевата папка # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index edd42d7c4..176aafe80 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6111,7 +6111,7 @@ shape Open Otvoren File + Data Set Name Datoteka + naziv skupa podataka Data Set Name Naziv skupa podataka @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` mora biti `fileNumber`, `fileDataSetName` ili `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Sačuvaj i zatvori“ ne može obraditi skupove podataka: odaberite „Folder“ da biste svaki skup podataka sačuvali u zasebnu datoteku + Each data set saves over the same file, which keeps only the last data set. Svaki skup podataka se sačuva preko iste datoteke, u kojoj ostaje samo posljednji skup podataka. @msg no free data set file name in destination folder Nema slobodnog naziva datoteke skupa podataka u odredišnoj mapi # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index f2b00581a..2efc35f97 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6255,7 +6255,7 @@ breakText Glyphs Glifs File + Data Set Name Fitxer + nom del conjunt de dades Data Set Name Nom del conjunt de dades @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` ha de ser `fileNumber`, `fileDataSetName` o `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Desa i tanca» no pot processar conjunts de dades: trieu «Carpeta» per desar cada conjunt de dades en un fitxer propi + Each data set saves over the same file, which keeps only the last data set. Cada conjunt de dades es desa sobre el mateix fitxer, que només conserva l'últim conjunt de dades. @msg no free data set file name in destination folder No hi ha cap nom de fitxer de conjunt de dades lliure a la carpeta de destinació # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 66aaefa8e..5e72b3e66 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6274,7 +6274,7 @@ breakText Glyphs Glyfy File + Data Set Name Soubor + název sady dat Data Set Name Název sady dat @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musí být `fileNumber`, `fileDataSetName` nebo `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Uložit a zavřít“ nelze použít pro sady dat: zvolte „Složka“, aby se každá sada dat uložila do vlastního souboru + Each data set saves over the same file, which keeps only the last data set. Každá sada dat se uloží přes stejný soubor, ve kterém zůstane jen poslední sada dat. @msg no free data set file name in destination folder V cílové složce není volný název souboru sady dat # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 3e9389579..516fada6e 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6114,7 +6114,7 @@ breakText Glyphs Glyffer File + Data Set Name Fil + datasætnavn Data Set Name Datasætnavn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` skal være `fileNumber`, `fileDataSetName` eller `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Gem og luk" kan ikke køre på datasæt: vælg "Mappe" for at gemme hvert datasæt i sin egen fil + Each data set saves over the same file, which keeps only the last data set. Hvert datasæt gemmes oven i den samme fil, som kun beholder det sidste datasæt. @msg no free data set file name in destination folder Intet ledigt filnavn til datasættet i destinationsmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 75345b1f7..73217a465 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5859,7 +5859,7 @@ breakText Glyphs Glyphen File + Data Set Name Datei + Datensatzname Data Set Name Datensatzname @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` muss `fileNumber`, `fileDataSetName` oder `dataSetName` sein -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Speichern und schließen“ kann nicht auf Datensätze angewendet werden: Wählen Sie „Ordner“, um jeden Datensatz in einer eigenen Datei zu speichern + Each data set saves over the same file, which keeps only the last data set. Jeder Datensatz wird über dieselbe Datei gespeichert, die nur den letzten Datensatz behält. @msg no free data set file name in destination folder Kein freier Dateiname für den Datensatz im Zielordner # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index cf071420c..1a703f159 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6225,7 +6225,7 @@ breakText Glyphs Γλύφοι File + Data Set Name Αρχείο + όνομα συνόλου δεδομένων Data Set Name Όνομα συνόλου δεδομένων @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` Το `datasetFileNaming` πρέπει να είναι `fileNumber`, `fileDataSetName` ή `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file Η «Αποθήκευση και κλείσιμο» δεν μπορεί να εφαρμοστεί σε σύνολα δεδομένων: επιλέξτε «Φάκελος» για να αποθηκευτεί κάθε σύνολο δεδομένων σε δικό του αρχείο + Each data set saves over the same file, which keeps only the last data set. Κάθε σύνολο δεδομένων αποθηκεύεται πάνω στο ίδιο αρχείο, το οποίο κρατά μόνο το τελευταίο σύνολο δεδομένων. @msg no free data set file name in destination folder Δεν υπάρχει διαθέσιμο όνομα αρχείου συνόλου δεδομένων στον φάκελο προορισμού # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 29d345748..0e4df7790 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6216,7 +6216,7 @@ breakText Glyphs Glifos File + Data Set Name Archivo + nombre del conjunto de datos Data Set Name Nombre del conjunto de datos @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` debe ser `fileNumber`, `fileDataSetName` o `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Guardar y cerrar» no puede procesar conjuntos de datos: elija «Carpeta» para guardar cada conjunto de datos en su propio archivo + Each data set saves over the same file, which keeps only the last data set. Cada conjunto de datos se guarda sobre el mismo archivo, que solo conserva el último conjunto de datos. @msg no free data set file name in destination folder No hay un nombre de archivo libre para el conjunto de datos en la carpeta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 7e0695592..8d1fe5f74 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6304,7 +6304,7 @@ breakText Glyphs Merkit File + Data Set Name Tiedosto + tietojoukon nimi Data Set Name Tietojoukon nimi @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`-arvon on oltava `fileNumber`, `fileDataSetName` tai `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file ”Tallenna ja sulje” ei voi käsitellä tietojoukkoja: valitse ”Kansio”, niin jokainen tietojoukko tallennetaan omaan tiedostoonsa + Each data set saves over the same file, which keeps only the last data set. Jokainen tietojoukko tallennetaan saman tiedoston päälle, johon jää vain viimeinen tietojoukko. @msg no free data set file name in destination folder Kohdekansiossa ei ole vapaata tietojoukon tiedostonimeä # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index e814e8378..6f1bc1715 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6269,7 +6269,7 @@ breakText Glyphs Glyphes File + Data Set Name Fichier + nom du jeu de données Data Set Name Nom du jeu de données @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` doit être `fileNumber`, `fileDataSetName` ou `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file « Enregistrer et fermer » ne peut pas traiter les jeux de données : choisissez « Dossier » pour enregistrer chaque jeu de données dans son propre fichier + Each data set saves over the same file, which keeps only the last data set. Chaque jeu de données est enregistré sur le même fichier, qui ne conserve que le dernier jeu de données. @msg no free data set file name in destination folder Aucun nom de fichier libre pour le jeu de données dans le dossier de destination # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index f33c5d7d6..b52b62022 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6258,7 +6258,7 @@ breakText Glyphs Glifák File + Data Set Name Fájl + adatkészlet neve Data Set Name Adatkészlet neve @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` A `datasetFileNaming` értéke `fileNumber`, `fileDataSetName` vagy `dataSetName` lehet -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file A „Mentés és bezárás” nem dolgozhat fel adatkészleteket: válassza a „Mappa” lehetőséget, hogy minden adatkészlet külön fájlba kerüljön + Each data set saves over the same file, which keeps only the last data set. Minden adatkészlet ugyanabba a fájlba mentődik, amely csak az utolsó adatkészletet őrzi meg. @msg no free data set file name in destination folder Nincs szabad adatkészlet-fájlnév a célmappában # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index e7b1655d6..176e1da88 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6114,7 +6114,7 @@ breakText Glyphs Glif File + Data Set Name File + nama set data Data Set Name Nama set data @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` harus `fileNumber`, `fileDataSetName`, atau `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Simpan dan tutup" tidak dapat memproses set data: pilih "Folder" untuk menyimpan setiap set data ke file tersendiri + Each data set saves over the same file, which keeps only the last data set. Setiap set data disimpan menimpa file yang sama, sehingga hanya set data terakhir yang tersisa. @msg no free data set file name in destination folder Tidak ada nama file set data yang tersedia di folder tujuan # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 231ed0230..c9d316985 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5893,7 +5893,7 @@ breakText Glyphs Glifi File + Data Set Name File + nome set di dati Data Set Name Nome set di dati @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve essere `fileNumber`, `fileDataSetName` o `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Salva e chiudi» non può elaborare i set di dati: scegli «Cartella» per salvare ogni set di dati in un file a sé + Each data set saves over the same file, which keeps only the last data set. Ogni set di dati viene salvato sullo stesso file, che conserva solo l'ultimo set di dati. @msg no free data set file name in destination folder Nessun nome file libero per il set di dati nella cartella di destinazione # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index ac6bb51bc..6dfa754c7 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5958,7 +5958,7 @@ breakText Glyphs 字形 File + Data Set Name ファイル名 + データセット名 Data Set Name データセット名 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` は `fileNumber`、`fileDataSetName`、`dataSetName` のいずれかである必要があります -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 「保存して閉じる」ではデータセットをバッチ処理できません:各データセットを個別のファイルに保存するには「フォルダー」を選択してください + Each data set saves over the same file, which keeps only the last data set. 各データセットが同じファイルに上書き保存されるため、最後のデータセットだけが残ります。 @msg no free data set file name in destination folder 保存先フォルダーに使用可能なデータセットのファイル名がありません # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index a0b1d42a6..fb53b753d 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6271,7 +6271,7 @@ breakText Glyphs გლიფები File + Data Set Name ფაილი + მონაცემთა ნაკრების სახელი Data Set Name მონაცემთა ნაკრების სახელი @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` უნდა იყოს `fileNumber`, `fileDataSetName` ან `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „შენახვა და დახურვა“ ვერ ამუშავებს მონაცემთა ნაკრებებს: აირჩიეთ „საქაღალდე“, რომ თითოეული მონაცემთა ნაკრები ცალკე ფაილში შეინახოთ + Each data set saves over the same file, which keeps only the last data set. თითოეული მონაცემთა ნაკრები ერთსა და იმავე ფაილზე ინახება, რომელშიც მხოლოდ ბოლო მონაცემთა ნაკრები რჩება. @msg no free data set file name in destination folder დანიშნულების საქაღალდეში მონაცემთა ნაკრებისთვის თავისუფალი ფაილის სახელი არ არის # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c083822b2..81e98bb47 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5854,7 +5854,7 @@ breakText Glyphs 글리프 File + Data Set Name 파일 + 데이터 세트 이름 Data Set Name 데이터 세트 이름 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`은 `fileNumber`, `fileDataSetName` 또는 `dataSetName`이어야 합니다 -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file '저장 후 닫기'로는 데이터 세트를 일괄 처리할 수 없습니다: 각 데이터 세트를 별도의 파일로 저장하려면 '폴더'를 선택하세요 + Each data set saves over the same file, which keeps only the last data set. 각 데이터 세트가 같은 파일에 덮어써지므로 마지막 데이터 세트만 남습니다. @msg no free data set file name in destination folder 대상 폴더에 사용할 수 있는 데이터 세트 파일 이름이 없습니다 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 5c9bd74f1..345d7918e 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6160,7 +6160,7 @@ breakText Glyphs Glyphs File + Data Set Name Bestand + naam gegevensset Data Set Name Naam gegevensset @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` moet `fileNumber`, `fileDataSetName` of `dataSetName` zijn -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 'Opslaan en sluiten' kan geen gegevenssets verwerken: kies 'Map' om elke gegevensset in een eigen bestand op te slaan + Each data set saves over the same file, which keeps only the last data set. Elke gegevensset wordt over hetzelfde bestand opgeslagen, dat alleen de laatste gegevensset bewaart. @msg no free data set file name in destination folder Geen vrije bestandsnaam voor de gegevensset in de doelmap # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index cb934d784..1290a0f79 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -6128,7 +6128,7 @@ breakText Glyphs Glify File + Data Set Name Plik + nazwa zestawu danych Data Set Name Nazwa zestawu danych @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musi mieć wartość `fileNumber`, `fileDataSetName` lub `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file „Zapisz i zamknij” nie może przetwarzać zestawów danych: wybierz „Folder”, aby zapisać każdy zestaw danych w osobnym pliku + Each data set saves over the same file, which keeps only the last data set. Każdy zestaw danych jest zapisywany w tym samym pliku, który zachowuje tylko ostatni zestaw danych. @msg no free data set file name in destination folder Brak wolnej nazwy pliku zestawu danych w folderze docelowym # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 8ac21f8e9..ab767fcc2 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6190,7 +6190,7 @@ breakText Glyphs Glifos File + Data Set Name Arquivo + nome do conjunto de dados Data Set Name Nome do conjunto de dados @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve ser `fileNumber`, `fileDataSetName` ou `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Salvar e fechar" não pode processar conjuntos de dados: escolha "Pasta" para salvar cada conjunto de dados em um arquivo próprio + Each data set saves over the same file, which keeps only the last data set. Cada conjunto de dados é salvo sobre o mesmo arquivo, que mantém apenas o último conjunto de dados. @msg no free data set file name in destination folder Não há nome de arquivo livre para o conjunto de dados na pasta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index d436e1b7e..d126c5dab 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6251,7 +6251,7 @@ breakText Glyphs Глифы File + Data Set Name Файл + имя набора данных Data Set Name Имя набора данных @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` должно быть `fileNumber`, `fileDataSetName` или `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Сохранить и закрыть» не работает с наборами данных: выберите «Папка», чтобы сохранить каждый набор данных в отдельный файл + Each data set saves over the same file, which keeps only the last data set. Каждый набор данных сохраняется поверх одного и того же файла, в котором остаётся только последний набор данных. @msg no free data set file name in destination folder В папке назначения нет свободного имени файла для набора данных # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 4438027d8..4d9e8da51 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6326,7 +6326,7 @@ breakText Glyphs Glyfer File + Data Set Name Fil + datauppsättningens namn Data Set Name Datauppsättningens namn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` måste vara `fileNumber`, `fileDataSetName` eller `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file ”Spara och stäng” kan inte köras på datauppsättningar: välj ”Mapp” för att spara varje datauppsättning i en egen fil + Each data set saves over the same file, which keeps only the last data set. Varje datauppsättning sparas över samma fil, som bara behåller den sista datauppsättningen. @msg no free data set file name in destination folder Inget ledigt filnamn för datauppsättningen i målmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 5157d5bf3..ec0c64cd1 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6231,7 +6231,7 @@ breakText Glyphs Glifler File + Data Set Name Dosya + veri kümesi adı Data Set Name Veri kümesi adı @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` değeri `fileNumber`, `fileDataSetName` veya `dataSetName` olmalıdır -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Kaydet ve Kapat" veri kümelerini toplu işleyemez: her veri kümesini ayrı bir dosyaya kaydetmek için "Klasör" seçeneğini seçin + Each data set saves over the same file, which keeps only the last data set. Her veri kümesi aynı dosyanın üzerine kaydedilir ve dosyada yalnızca son veri kümesi kalır. @msg no free data set file name in destination folder Hedef klasörde veri kümesi için boş bir dosya adı yok # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index db7e9ccd2..014ee77f0 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5873,7 +5873,7 @@ breakText Glyphs Гліфи File + Data Set Name Файл + назва набору даних Data Set Name Назва набору даних @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` має бути `fileNumber`, `fileDataSetName` або `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file «Зберегти й закрити» не працює з наборами даних: виберіть «Папка», щоб зберегти кожен набір даних в окремий файл + Each data set saves over the same file, which keeps only the last data set. Кожен набір даних зберігається поверх того самого файлу, у якому лишається тільки останній набір даних. @msg no free data set file name in destination folder У папці призначення немає вільного імені файлу набору даних # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f59304809..f79734084 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5874,7 +5874,7 @@ breakText Glyphs Glyph File + Data Set Name Tệp + tên tập dữ liệu Data Set Name Tên tập dữ liệu @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` phải là `fileNumber`, `fileDataSetName` hoặc `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file "Lưu và đóng" không thể xử lý các tập dữ liệu: chọn "Thư mục" để lưu mỗi tập dữ liệu vào một tệp riêng + Each data set saves over the same file, which keeps only the last data set. Mỗi tập dữ liệu được lưu đè lên cùng một tệp, nên tệp chỉ giữ lại tập dữ liệu cuối cùng. @msg no free data set file name in destination folder Không có tên tệp tập dữ liệu còn trống trong thư mục đích # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 379f4dc23..2c6da8373 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6115,7 +6115,7 @@ breakText Glyphs 字形 File + Data Set Name 文件 + 数据集名称 Data Set Name 数据集名称 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必须是 `fileNumber`、`fileDataSetName` 或 `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file “存储并关闭”无法批处理数据集:请选择“文件夹”,将每个数据集存储为单独的文件 + Each data set saves over the same file, which keeps only the last data set. 每个数据集都会存储到同一个文件并覆盖它,该文件只保留最后一个数据集。 @msg no free data set file name in destination folder 目标文件夹中没有可用的数据集文件名 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 46c332bd4..096e8602a 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5911,7 +5911,7 @@ breakText Glyphs 字符 File + Data Set Name 檔案 + 資料集名稱 Data Set Name 資料集名稱 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必須是 `fileNumber`、`fileDataSetName` 或 `dataSetName` -@msg Save and Close cannot batch data sets: choose Folder to save each data set to its own file 「儲存並關閉」無法批次處理資料集:請選擇「資料夾」,將每個資料集儲存為個別檔案 + Each data set saves over the same file, which keeps only the last data set. 每個資料集都會儲存到同一個檔案並覆寫它,該檔案只保留最後一個資料集。 @msg no free data set file name in destination folder 目的地資料夾中沒有可用的資料集檔案名稱 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/docs/mcp.md b/docs/mcp.md index 41d518456..78c3082cd 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3514,9 +3514,12 @@ Actions panel plays an action as a `command.batch`, omitting steps whose playbac cleared. An action set saved as `.vcactions` keeps each excluded step with `excluded: true`; loading an earlier version 1 set without that flag includes every step. The Actions panel menu's Batch… runs `action.batch`: the same steps on every readable file in a folder (or every data set of the open document), with -destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. A Data Sets source does not -take `saveAndClose`: `action.batch` refuses it before it selects a data set or runs a step, and the dialog offers -None and Folder only. For a Data Sets source, +destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. With a Data Sets source, +`saveAndClose` saves the open document over its own file after each data set, so the file keeps only the last one, +and a document that was never saved is refused before a data set is selected. The result carries `warning` when +every data set saves over the same file: with `saveAndClose`, or with a Save, Save a Copy, Save as Template or +Export step that writes a file and that the batch does not point into `destinationFolder`. The dialog shows the same +warning. For a Data Sets source, `datasetFileNaming?: fileNumber | fileDataSetName | dataSetName` chooses File + Number (`source001`), File + Data Set Name (`source_Alpha`), or Data Set Name (`Alpha`). An omitted parameter keeps File + Data Set Name for existing callers; the dialog starts with File + Number. A folder source ignores this parameter. The folder diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 470d36886..ac4da65b5 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -643,7 +643,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used. - Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html). - - Batch… over data sets offers None and Folder as the destination, and `action.batch` refuses Save and Close for a Data Sets source before it runs a step, so the batch does not save every data set over the open document's file. This is VectorCraft's own rule: [Play actions on a batch of files](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/play-actions-on-a-batch-of-files.html) describes Save and Close as saving "the file in their current location" and does not say what it does over data sets. + - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set. A recorded Save, Save a Copy, Save as Template or Export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy or template step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. `action.batch` refuses Save and Close over the data sets of a document that was never saved. - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`). - Export presets: the Asset Export panel menu lists the export presets of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included. - Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`. From 64cfa8b5531df2ea031baac66328b2cb860359f2 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 06:49:25 -0800 Subject: [PATCH 4/6] Actions: Batch over data sets warns about PDF, EPS, DXF and web exports and refuses Save and Close on converted documents A recorded PDF, EPS, DXF, Save for Web or Save for Office Documents step that gives a `path` writes the same file for every data set, because the batch does not point these steps into the destination folder. The one-file rule now counts them, so the dialog and `action.batch` warn. Save and Close over the data sets of a document converted from an older version or restored by Data Recovery is refused with "save the document with Save As before Save and Close" before a data set is selected. Saving such a document does not write its file, so the batch reported success without saving anything. The message has a row in each of the 26 catalogs. --- crates/engine/src/cmd/fileio/action_batch.rs | 53 +++++++++++++++++--- crates/engine/src/cmd/fileio/commands.rs | 2 +- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/mcp.md | 7 +-- docs/target-app-parity.md | 2 +- 30 files changed, 78 insertions(+), 12 deletions(-) diff --git a/crates/engine/src/cmd/fileio/action_batch.rs b/crates/engine/src/cmd/fileio/action_batch.rs index 26d6c476e..5c29ec88f 100644 --- a/crates/engine/src/cmd/fileio/action_batch.rs +++ b/crates/engine/src/cmd/fileio/action_batch.rs @@ -114,11 +114,12 @@ const ONE_FILE_WARNING: &str = "each data set saves over the same file, which ke /// Whether an `action.batch` with params `p` plays on data sets of the open document in `s` and /// writes each data set to the same file. Save and Close saves the open document over its own file /// after every data set. A step that the batch does not point into `destinationFolder` writes the -/// same file every time when it writes one: a Save, Save As, Save a Copy, Save as Template, Export -/// or Export Selection step that gives a `path`; a Save step without a `path` when the document has -/// a file of its own (saved, and not converted from an older version or restored by Data Recovery) -/// and the step gives no `format` or the document's own; an Export for Screens -/// step whose `folder`, given or from its `exportPreset`, names a folder, whatever the overrides. +/// same file every time when it writes one: a Save, Save As, Save a Copy, Save as Template, Export, +/// Export Selection, PDF, EPS, DXF, Save for Web or Save for Office Documents step that gives a +/// `path`; a Save step without a `path` when the document has a file of its own (saved, and not +/// converted from an older version or restored by Data Recovery) and the step gives no `format` or +/// the document's own; an Export for Screens step whose `folder`, given or from its +/// `exportPreset`, names a folder, whatever the overrides. /// Other steps return their data instead of writing a file. pub fn batch_overwrites_one_file(s: &Session, p: &Value) -> bool { if p.get("source").and_then(Value::as_str) != Some("datasets") { @@ -146,6 +147,8 @@ pub fn batch_overwrites_one_file(s: &Session, p: &Value) -> bool { "file.saveAs" => !save && path, "file.saveCopy" | "file.saveAsTemplate" => path, "document.export" | "document.exportSelection" => !export && path, + // The batch does not point these exports into `destinationFolder`. + "document.exportPdf" | "document.exportEps" | "document.exportDxf" | "document.exportForWeb" | "document.exportForOffice" => path, // Override Export sets `path`, which Export for Screens does not read: its `folder` stays. "document.exportForScreens" => super::screens::with_preset(s, ¶ms).is_ok_and(|p| names(p.get("folder"))), _ => false, @@ -199,8 +202,15 @@ fn batch_datasets(s: &mut Session, cmd: &str, run: &Run<'_>) -> Result { if names.len() > MAX_BATCH_FILES { return Err(bad(cmd, format!("more than {MAX_BATCH_FILES} data sets"))); } - if run.destination == "saveAndClose" && st.path.is_none() { - return Err(bad(cmd, "the document has never been saved")); + if run.destination == "saveAndClose" { + if st.path.is_none() { + return Err(bad(cmd, "the document has never been saved")); + } + // `save_plan` writes no file for a document converted from an older version or restored by + // Data Recovery. + if st.converted || st.recovered { + return Err(bad(cmd, "save the document with Save As before Save and Close")); + } } let doc_stem = st.path.as_deref().and_then(|p| std::path::Path::new(p).file_stem().and_then(|s| s.to_str())).unwrap_or("Untitled").to_string(); let stems = dataset_stems(&names, &doc_stem, run.dataset_file_naming, run, &st.doc)?; @@ -1441,6 +1451,35 @@ mod tests { assert!(!with(json!({"commands": [{"command": "document.exportForScreens", "params": {}}]}))); let preset = json!([{"command": "document.exportForScreens", "params": {"exportPreset": "Logo Package"}}]); assert!(!with(json!({"commands": preset})), "a preset without a folder: the data comes back"); + for id in ["document.exportPdf", "document.exportEps", "document.exportDxf", "document.exportForWeb", "document.exportForOffice"] { + let step = json!([{"command": id, "params": {"path": "/x/a.out"}}]); + assert!(with(json!({"destination": "folder", "destinationFolder": "/out", "commands": step})), "{id} keeps its path"); + assert!(!with(json!({"commands": [{"command": id, "params": {}}]})), "{id} without a path: the data comes back"); + } + } + + #[test] + fn batch_refuses_save_and_close_over_data_sets_of_a_converted_or_recovered_document() { + for converted in [true, false] { + let path = tmp(if converted { "converted-sets" } else { "recovered-sets" }).join("template.vectorcraft"); + let mut s = saved_with_two_data_sets(&path); + let bytes = std::fs::read(&path).unwrap(); + let (active, revision) = { + let st = s.doc_mut().unwrap(); + if converted { + st.converted = true; + } else { + st.recovered = true; + } + (st.doc.variables.active_dataset.clone(), st.revision) + }; + let msg = save_and_close_over_data_sets(&mut s).unwrap_err().to_string(); + assert!(msg.ends_with("save the document with Save As before Save and Close"), "{msg}"); + assert_eq!(std::fs::read(&path).unwrap(), bytes, "the file is not written"); + let st = s.doc().unwrap(); + assert_eq!(st.doc.variables.active_dataset, active, "no data set is selected"); + assert_eq!(st.revision, revision, "no step runs"); + } } #[test] diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index 9b62a5342..d982b6127 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { "Batch…", [], None, - "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close, or a Save, Save a Copy, Save as Template or Export step that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", + "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved, was converted from an older version or was restored by Data Recovery) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close, or a Save, Save a Copy, Save as Template or export step (document.export, exportSelection, exportForScreens, exportPdf, exportEps, exportDxf, exportForWeb, exportForOffice) that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", always, action_batch::action_batch ), diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index b94e7895c..d8ada61f0 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6100,6 +6100,7 @@ breakText Glyphs Глифи Data Set Name Име на набора от данни @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` трябва да е `fileNumber`, `fileDataSetName` или `dataSetName` Each data set saves over the same file, which keeps only the last data set. Всеки набор от данни се записва върху същия файл, в който остава само последният набор от данни. +@msg save the document with Save As before Save and Close запазете документа със „Запази като“ преди „Запази и затвори“ @msg no free data set file name in destination folder Няма свободно име за файл на набор от данни в целевата папка # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 176aafe80..bcd447280 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6112,6 +6112,7 @@ shape Open Otvoren Data Set Name Naziv skupa podataka @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` mora biti `fileNumber`, `fileDataSetName` ili `dataSetName` Each data set saves over the same file, which keeps only the last data set. Svaki skup podataka se sačuva preko iste datoteke, u kojoj ostaje samo posljednji skup podataka. +@msg save the document with Save As before Save and Close sačuvajte dokument pomoću „Sačuvaj kao“ prije „Sačuvaj i zatvori“ @msg no free data set file name in destination folder Nema slobodnog naziva datoteke skupa podataka u odredišnoj mapi # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 2efc35f97..bb3c3a35a 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6256,6 +6256,7 @@ breakText Glyphs Glifs Data Set Name Nom del conjunt de dades @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` ha de ser `fileNumber`, `fileDataSetName` o `dataSetName` Each data set saves over the same file, which keeps only the last data set. Cada conjunt de dades es desa sobre el mateix fitxer, que només conserva l'últim conjunt de dades. +@msg save the document with Save As before Save and Close deseu el document amb «Desa com a» abans de «Desa i tanca» @msg no free data set file name in destination folder No hi ha cap nom de fitxer de conjunt de dades lliure a la carpeta de destinació # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 5e72b3e66..9991617c3 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6275,6 +6275,7 @@ breakText Glyphs Glyfy Data Set Name Název sady dat @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musí být `fileNumber`, `fileDataSetName` nebo `dataSetName` Each data set saves over the same file, which keeps only the last data set. Každá sada dat se uloží přes stejný soubor, ve kterém zůstane jen poslední sada dat. +@msg save the document with Save As before Save and Close před „Uložit a zavřít“ uložte dokument pomocí „Uložit jako“ @msg no free data set file name in destination folder V cílové složce není volný název souboru sady dat # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 516fada6e..874d196f0 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6115,6 +6115,7 @@ breakText Glyphs Glyffer Data Set Name Datasætnavn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` skal være `fileNumber`, `fileDataSetName` eller `dataSetName` Each data set saves over the same file, which keeps only the last data set. Hvert datasæt gemmes oven i den samme fil, som kun beholder det sidste datasæt. +@msg save the document with Save As before Save and Close gem dokumentet med Gem som før Gem og luk @msg no free data set file name in destination folder Intet ledigt filnavn til datasættet i destinationsmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 73217a465..114657cc7 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5860,6 +5860,7 @@ breakText Glyphs Glyphen Data Set Name Datensatzname @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` muss `fileNumber`, `fileDataSetName` oder `dataSetName` sein Each data set saves over the same file, which keeps only the last data set. Jeder Datensatz wird über dieselbe Datei gespeichert, die nur den letzten Datensatz behält. +@msg save the document with Save As before Save and Close speichern Sie das Dokument mit „Speichern unter“, bevor Sie „Speichern und schließen“ wählen @msg no free data set file name in destination folder Kein freier Dateiname für den Datensatz im Zielordner # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 1a703f159..00dcc7044 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6226,6 +6226,7 @@ breakText Glyphs Γλύφοι Data Set Name Όνομα συνόλου δεδομένων @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` Το `datasetFileNaming` πρέπει να είναι `fileNumber`, `fileDataSetName` ή `dataSetName` Each data set saves over the same file, which keeps only the last data set. Κάθε σύνολο δεδομένων αποθηκεύεται πάνω στο ίδιο αρχείο, το οποίο κρατά μόνο το τελευταίο σύνολο δεδομένων. +@msg save the document with Save As before Save and Close αποθηκεύστε το έγγραφο με «Αποθήκευση ως» πριν από «Αποθήκευση και κλείσιμο» @msg no free data set file name in destination folder Δεν υπάρχει διαθέσιμο όνομα αρχείου συνόλου δεδομένων στον φάκελο προορισμού # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 0e4df7790..1ae8b7fae 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6217,6 +6217,7 @@ breakText Glyphs Glifos Data Set Name Nombre del conjunto de datos @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` debe ser `fileNumber`, `fileDataSetName` o `dataSetName` Each data set saves over the same file, which keeps only the last data set. Cada conjunto de datos se guarda sobre el mismo archivo, que solo conserva el último conjunto de datos. +@msg save the document with Save As before Save and Close guarde el documento con «Guardar como» antes de «Guardar y cerrar» @msg no free data set file name in destination folder No hay un nombre de archivo libre para el conjunto de datos en la carpeta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 8d1fe5f74..bf26c1cc3 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6305,6 +6305,7 @@ breakText Glyphs Merkit Data Set Name Tietojoukon nimi @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`-arvon on oltava `fileNumber`, `fileDataSetName` tai `dataSetName` Each data set saves over the same file, which keeps only the last data set. Jokainen tietojoukko tallennetaan saman tiedoston päälle, johon jää vain viimeinen tietojoukko. +@msg save the document with Save As before Save and Close tallenna asiakirja komennolla Tallenna nimellä ennen kuin valitset Tallenna ja sulje @msg no free data set file name in destination folder Kohdekansiossa ei ole vapaata tietojoukon tiedostonimeä # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 6f1bc1715..455af6aa5 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6270,6 +6270,7 @@ breakText Glyphs Glyphes Data Set Name Nom du jeu de données @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` doit être `fileNumber`, `fileDataSetName` ou `dataSetName` Each data set saves over the same file, which keeps only the last data set. Chaque jeu de données est enregistré sur le même fichier, qui ne conserve que le dernier jeu de données. +@msg save the document with Save As before Save and Close enregistrez le document avec Enregistrer sous avant Enregistrer et fermer @msg no free data set file name in destination folder Aucun nom de fichier libre pour le jeu de données dans le dossier de destination # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index b52b62022..9574c304a 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6259,6 +6259,7 @@ breakText Glyphs Glifák Data Set Name Adatkészlet neve @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` A `datasetFileNaming` értéke `fileNumber`, `fileDataSetName` vagy `dataSetName` lehet Each data set saves over the same file, which keeps only the last data set. Minden adatkészlet ugyanabba a fájlba mentődik, amely csak az utolsó adatkészletet őrzi meg. +@msg save the document with Save As before Save and Close mentse a dokumentumot a „Mentés másként” paranccsal a „Mentés és bezárás” előtt @msg no free data set file name in destination folder Nincs szabad adatkészlet-fájlnév a célmappában # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 176e1da88..2b4ab48e8 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6115,6 +6115,7 @@ breakText Glyphs Glif Data Set Name Nama set data @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` harus `fileNumber`, `fileDataSetName`, atau `dataSetName` Each data set saves over the same file, which keeps only the last data set. Setiap set data disimpan menimpa file yang sama, sehingga hanya set data terakhir yang tersisa. +@msg save the document with Save As before Save and Close simpan dokumen dengan Simpan sebagai sebelum Simpan dan tutup @msg no free data set file name in destination folder Tidak ada nama file set data yang tersedia di folder tujuan # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index c9d316985..1f6d0a139 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5894,6 +5894,7 @@ breakText Glyphs Glifi Data Set Name Nome set di dati @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve essere `fileNumber`, `fileDataSetName` o `dataSetName` Each data set saves over the same file, which keeps only the last data set. Ogni set di dati viene salvato sullo stesso file, che conserva solo l'ultimo set di dati. +@msg save the document with Save As before Save and Close salva il documento con «Salva con nome» prima di «Salva e chiudi» @msg no free data set file name in destination folder Nessun nome file libero per il set di dati nella cartella di destinazione # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 6dfa754c7..17b2aa06f 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5959,6 +5959,7 @@ breakText Glyphs 字形 Data Set Name データセット名 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` は `fileNumber`、`fileDataSetName`、`dataSetName` のいずれかである必要があります Each data set saves over the same file, which keeps only the last data set. 各データセットが同じファイルに上書き保存されるため、最後のデータセットだけが残ります。 +@msg save the document with Save As before Save and Close 「保存して閉じる」の前に「別名で保存」でドキュメントを保存してください @msg no free data set file name in destination folder 保存先フォルダーに使用可能なデータセットのファイル名がありません # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index fb53b753d..f9545785b 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6272,6 +6272,7 @@ breakText Glyphs გლიფები Data Set Name მონაცემთა ნაკრების სახელი @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` უნდა იყოს `fileNumber`, `fileDataSetName` ან `dataSetName` Each data set saves over the same file, which keeps only the last data set. თითოეული მონაცემთა ნაკრები ერთსა და იმავე ფაილზე ინახება, რომელშიც მხოლოდ ბოლო მონაცემთა ნაკრები რჩება. +@msg save the document with Save As before Save and Close „შენახვა და დახურვამდე“ შეინახეთ დოკუმენტი „შენახვა როგორც“ ბრძანებით @msg no free data set file name in destination folder დანიშნულების საქაღალდეში მონაცემთა ნაკრებისთვის თავისუფალი ფაილის სახელი არ არის # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 81e98bb47..ae73d31ac 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5855,6 +5855,7 @@ breakText Glyphs 글리프 Data Set Name 데이터 세트 이름 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming`은 `fileNumber`, `fileDataSetName` 또는 `dataSetName`이어야 합니다 Each data set saves over the same file, which keeps only the last data set. 각 데이터 세트가 같은 파일에 덮어써지므로 마지막 데이터 세트만 남습니다. +@msg save the document with Save As before Save and Close '저장 후 닫기' 전에 '다른 이름으로 저장'으로 문서를 저장하십시오 @msg no free data set file name in destination folder 대상 폴더에 사용할 수 있는 데이터 세트 파일 이름이 없습니다 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 345d7918e..c52207339 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6161,6 +6161,7 @@ breakText Glyphs Glyphs Data Set Name Naam gegevensset @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` moet `fileNumber`, `fileDataSetName` of `dataSetName` zijn Each data set saves over the same file, which keeps only the last data set. Elke gegevensset wordt over hetzelfde bestand opgeslagen, dat alleen de laatste gegevensset bewaart. +@msg save the document with Save As before Save and Close sla het document op met Opslaan als voordat u Opslaan en sluiten kiest @msg no free data set file name in destination folder Geen vrije bestandsnaam voor de gegevensset in de doelmap # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 1290a0f79..f3538c540 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -6129,6 +6129,7 @@ breakText Glyphs Glify Data Set Name Nazwa zestawu danych @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` musi mieć wartość `fileNumber`, `fileDataSetName` lub `dataSetName` Each data set saves over the same file, which keeps only the last data set. Każdy zestaw danych jest zapisywany w tym samym pliku, który zachowuje tylko ostatni zestaw danych. +@msg save the document with Save As before Save and Close zapisz dokument poleceniem „Zapisz jako” przed „Zapisz i zamknij” @msg no free data set file name in destination folder Brak wolnej nazwy pliku zestawu danych w folderze docelowym # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index ab767fcc2..d1028010b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6191,6 +6191,7 @@ breakText Glyphs Glifos Data Set Name Nome do conjunto de dados @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` deve ser `fileNumber`, `fileDataSetName` ou `dataSetName` Each data set saves over the same file, which keeps only the last data set. Cada conjunto de dados é salvo sobre o mesmo arquivo, que mantém apenas o último conjunto de dados. +@msg save the document with Save As before Save and Close salve o documento com Salvar Como antes de Salvar e fechar @msg no free data set file name in destination folder Não há nome de arquivo livre para o conjunto de dados na pasta de destino # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index d126c5dab..f0a56b613 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6252,6 +6252,7 @@ breakText Glyphs Глифы Data Set Name Имя набора данных @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` должно быть `fileNumber`, `fileDataSetName` или `dataSetName` Each data set saves over the same file, which keeps only the last data set. Каждый набор данных сохраняется поверх одного и того же файла, в котором остаётся только последний набор данных. +@msg save the document with Save As before Save and Close сохраните документ командой «Сохранить как» перед «Сохранить и закрыть» @msg no free data set file name in destination folder В папке назначения нет свободного имени файла для набора данных # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 4d9e8da51..9e2a2ce01 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6327,6 +6327,7 @@ breakText Glyphs Glyfer Data Set Name Datauppsättningens namn @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` måste vara `fileNumber`, `fileDataSetName` eller `dataSetName` Each data set saves over the same file, which keeps only the last data set. Varje datauppsättning sparas över samma fil, som bara behåller den sista datauppsättningen. +@msg save the document with Save As before Save and Close spara dokumentet med Spara som före Spara och stäng @msg no free data set file name in destination folder Inget ledigt filnamn för datauppsättningen i målmappen # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index ec0c64cd1..7cd9822fd 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6232,6 +6232,7 @@ breakText Glyphs Glifler Data Set Name Veri kümesi adı @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` değeri `fileNumber`, `fileDataSetName` veya `dataSetName` olmalıdır Each data set saves over the same file, which keeps only the last data set. Her veri kümesi aynı dosyanın üzerine kaydedilir ve dosyada yalnızca son veri kümesi kalır. +@msg save the document with Save As before Save and Close Kaydet ve Kapat'tan önce belgeyi Farklı Kaydet ile kaydedin @msg no free data set file name in destination folder Hedef klasörde veri kümesi için boş bir dosya adı yok # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 014ee77f0..2f50e0991 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5874,6 +5874,7 @@ breakText Glyphs Гліфи Data Set Name Назва набору даних @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` має бути `fileNumber`, `fileDataSetName` або `dataSetName` Each data set saves over the same file, which keeps only the last data set. Кожен набір даних зберігається поверх того самого файлу, у якому лишається тільки останній набір даних. +@msg save the document with Save As before Save and Close збережіть документ командою «Зберегти як» перед «Зберегти й закрити» @msg no free data set file name in destination folder У папці призначення немає вільного імені файлу набору даних # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f79734084..d2dc9a478 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5875,6 +5875,7 @@ breakText Glyphs Glyph Data Set Name Tên tập dữ liệu @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` phải là `fileNumber`, `fileDataSetName` hoặc `dataSetName` Each data set saves over the same file, which keeps only the last data set. Mỗi tập dữ liệu được lưu đè lên cùng một tệp, nên tệp chỉ giữ lại tập dữ liệu cuối cùng. +@msg save the document with Save As before Save and Close hãy lưu tài liệu bằng Lưu thành trước khi dùng Lưu và đóng @msg no free data set file name in destination folder Không có tên tệp tập dữ liệu còn trống trong thư mục đích # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 2c6da8373..100128f3c 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6116,6 +6116,7 @@ breakText Glyphs 字形 Data Set Name 数据集名称 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必须是 `fileNumber`、`fileDataSetName` 或 `dataSetName` Each data set saves over the same file, which keeps only the last data set. 每个数据集都会存储到同一个文件并覆盖它,该文件只保留最后一个数据集。 +@msg save the document with Save As before Save and Close 请先用“另存为”保存文档,再使用“保存并关闭” @msg no free data set file name in destination folder 目标文件夹中没有可用的数据集文件名 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 096e8602a..d0f9c079e 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5912,6 +5912,7 @@ breakText Glyphs 字符 Data Set Name 資料集名稱 @msg `datasetFileNaming` must be `fileNumber`, `fileDataSetName` or `dataSetName` `datasetFileNaming` 必須是 `fileNumber`、`fileDataSetName` 或 `dataSetName` Each data set saves over the same file, which keeps only the last data set. 每個資料集都會儲存到同一個檔案並覆寫它,該檔案只保留最後一個資料集。 +@msg save the document with Save As before Save and Close 請先用「另存新檔」儲存文件,再使用「儲存並關閉」 @msg no free data set file name in destination folder 目的地資料夾中沒有可用的資料集檔案名稱 # Path Precision Tools add-on: Path Effects (Effect › Path Effects). diff --git a/docs/mcp.md b/docs/mcp.md index 78c3082cd..deb110a53 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3516,9 +3516,10 @@ an earlier version 1 set without that flag includes every step. The Actions pane `action.batch`: the same steps on every readable file in a folder (or every data set of the open document), with destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. With a Data Sets source, `saveAndClose` saves the open document over its own file after each data set, so the file keeps only the last one, -and a document that was never saved is refused before a data set is selected. The result carries `warning` when -every data set saves over the same file: with `saveAndClose`, or with a Save, Save a Copy, Save as Template or -Export step that writes a file and that the batch does not point into `destinationFolder`. The dialog shows the same +and a document that was never saved, was converted from an older version or was restored by Data Recovery is +refused before a data set is selected. The result carries `warning` when every data set saves over the same file: +with `saveAndClose`, or with a Save, Save a Copy, Save as Template or export step (PDF, EPS, DXF, Save for Web and +Save for Office Documents included) that writes a file and that the batch does not point into `destinationFolder`. The dialog shows the same warning. For a Data Sets source, `datasetFileNaming?: fileNumber | fileDataSetName | dataSetName` chooses File + Number (`source001`), File + Data Set Name (`source_Alpha`), or Data Set Name (`Alpha`). An omitted parameter keeps File + Data Set Name diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ac4da65b5..b932d6cf0 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -643,7 +643,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used. - Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html). - - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set. A recorded Save, Save a Copy, Save as Template or Export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy or template step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. `action.batch` refuses Save and Close over the data sets of a document that was never saved. + - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set. A recorded Save, Save a Copy, Save as Template or export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy, template, PDF, EPS, DXF, Save for Web or Save for Office Documents step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. `action.batch` refuses Save and Close over the data sets of a document that was never saved, was converted from an older version or was restored by Data Recovery. - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`). - Export presets: the Asset Export panel menu lists the export presets of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included. - Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`. From 427029def66385d7951f69953147b7e696261124 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 07:07:20 -0800 Subject: [PATCH 5/6] Actions: Batch Save and Close over data sets follows a redirected Save step With a destination folder and Override Save on, a recorded Save or Save As step writes each data set to its own file in that folder and makes that file the document's own, so Save and Close then saves each data set to its own file. The batch no longer warns that every data set saves over one file in that case, and no longer refuses a document that was never saved, converted or restored, since the recorded step gives it a file first. --- crates/engine/src/cmd/fileio/action_batch.rs | 89 ++++++++++++++------ crates/engine/src/cmd/fileio/commands.rs | 2 +- docs/mcp.md | 6 +- docs/target-app-parity.md | 2 +- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/crates/engine/src/cmd/fileio/action_batch.rs b/crates/engine/src/cmd/fileio/action_batch.rs index 5c29ec88f..942f58e77 100644 --- a/crates/engine/src/cmd/fileio/action_batch.rs +++ b/crates/engine/src/cmd/fileio/action_batch.rs @@ -113,7 +113,8 @@ const ONE_FILE_WARNING: &str = "each data set saves over the same file, which ke /// Whether an `action.batch` with params `p` plays on data sets of the open document in `s` and /// writes each data set to the same file. Save and Close saves the open document over its own file -/// after every data set. A step that the batch does not point into `destinationFolder` writes the +/// after every data set, unless a Save step that Override Save points into `destinationFolder` +/// first makes each data set's file the document's own. A step that the batch does not point into `destinationFolder` writes the /// same file every time when it writes one: a Save, Save As, Save a Copy, Save as Template, Export, /// Export Selection, PDF, EPS, DXF, Save for Web or Save for Office Documents step that gives a /// `path`; a Save step without a `path` when the document has a file of its own (saved, and not @@ -125,38 +126,44 @@ pub fn batch_overwrites_one_file(s: &Session, p: &Value) -> bool { if p.get("source").and_then(Value::as_str) != Some("datasets") { return false; } - if p.get("destination").and_then(Value::as_str) == Some("saveAndClose") { - return true; - } let folder = p.get("destinationFolder").and_then(Value::as_str).is_some(); let redirected = |key: &str| folder && p.get(key).and_then(Value::as_bool).unwrap_or(true); let (save, export) = (redirected("overrideSave"), redirected("overrideExport")); + let steps = p.get("commands").and_then(Value::as_array).map_or(&[][..], Vec::as_slice); + if p.get("destination").and_then(Value::as_str) == Some("saveAndClose") && !saves_move_to_each_file(steps, save) { + return true; + } // The format of the open document's own file, which a Save step without a `path` writes. let own_format = s.active().filter(|st| st.path.is_some() && !st.converted && !st.recovered).map(|st| st.format); let names = |v: Option<&Value>| v.and_then(Value::as_str).is_some_and(|s| !s.is_empty()); - p.get("commands").and_then(Value::as_array).is_some_and(|cmds| { - cmds.iter().any(|c| { - let params = c.get("params").cloned().unwrap_or_else(|| Value::Object(Default::default())); - let path = names(params.get("path")); - let own_file = || match params.get("format").and_then(Value::as_str).filter(|f| !f.is_empty()) { - None => own_format.is_some(), - Some(f) => own_format.is_some_and(|own| super::format(f).is_some_and(|f| f.id == own)), - }; - match c.get("command").and_then(Value::as_str).unwrap_or("") { - "document.save" | "file.save" => !save && (path || own_file()), - "file.saveAs" => !save && path, - "file.saveCopy" | "file.saveAsTemplate" => path, - "document.export" | "document.exportSelection" => !export && path, - // The batch does not point these exports into `destinationFolder`. - "document.exportPdf" | "document.exportEps" | "document.exportDxf" | "document.exportForWeb" | "document.exportForOffice" => path, - // Override Export sets `path`, which Export for Screens does not read: its `folder` stays. - "document.exportForScreens" => super::screens::with_preset(s, ¶ms).is_ok_and(|p| names(p.get("folder"))), - _ => false, - } - }) + steps.iter().any(|c| { + let params = c.get("params").cloned().unwrap_or_else(|| Value::Object(Default::default())); + let path = names(params.get("path")); + let own_file = || match params.get("format").and_then(Value::as_str).filter(|f| !f.is_empty()) { + None => own_format.is_some(), + Some(f) => own_format.is_some_and(|own| super::format(f).is_some_and(|f| f.id == own)), + }; + match c.get("command").and_then(Value::as_str).unwrap_or("") { + "document.save" | "file.save" => !save && (path || own_file()), + "file.saveAs" => !save && path, + "file.saveCopy" | "file.saveAsTemplate" => path, + "document.export" | "document.exportSelection" => !export && path, + // The batch does not point these exports into `destinationFolder`. + "document.exportPdf" | "document.exportEps" | "document.exportDxf" | "document.exportForWeb" | "document.exportForOffice" => path, + // Override Export sets `path`, which Export for Screens does not read: its `folder` stays. + "document.exportForScreens" => super::screens::with_preset(s, ¶ms).is_ok_and(|p| names(p.get("folder"))), + _ => false, + } }) } +/// Whether `steps` hold a Save, Save As or `document.save` step that Override Save points at each +/// data set's own file in the destination folder (`redirected`). Such a step makes that file the +/// document's own, so Save and Close then saves each data set to its own file. +fn saves_move_to_each_file(steps: &[Value], redirected: bool) -> bool { + redirected && steps.iter().any(|c| matches!(c.get("command").and_then(Value::as_str), Some("document.save" | "file.save" | "file.saveAs"))) +} + #[cfg(not(target_arch = "wasm32"))] fn batch_folder(s: &mut Session, cmd: &str, p: &Value, override_open: bool, run: &Run<'_>) -> Result { use super::{absolute_path, may_read}; @@ -202,7 +209,7 @@ fn batch_datasets(s: &mut Session, cmd: &str, run: &Run<'_>) -> Result { if names.len() > MAX_BATCH_FILES { return Err(bad(cmd, format!("more than {MAX_BATCH_FILES} data sets"))); } - if run.destination == "saveAndClose" { + if run.destination == "saveAndClose" && !saves_move_to_each_file(run.steps, run.override_save && run.dest_folder.is_some()) { if st.path.is_none() { return Err(bad(cmd, "the document has never been saved")); } @@ -1413,6 +1420,38 @@ mod tests { assert!(r.get("warning").is_none(), "{r}"); } + #[test] + fn batch_save_and_close_after_a_redirected_save_writes_each_data_set_to_its_own_file() { + let save_close = |dest: &std::path::Path| { + json!({ + "source": "datasets", + "destination": "saveAndClose", + "destinationFolder": dest.to_string_lossy(), + "commands": [{"command": "document.save", "params": {}}] + }) + }; + let path = tmp("redirected-save-close").join("template.vectorcraft"); + let mut s = saved_with_two_data_sets(&path); + let bytes = std::fs::read(&path).unwrap(); + let dest = tmp("redirected-save-close-dest"); + let p = save_close(&dest); + let mut kept = p.clone(); + kept["overrideSave"] = json!(false); + assert!(batch_overwrites_one_file(&s, &kept), "without Override Save the save stays on the template"); + let r = s.execute("action.batch", &p).unwrap(); + assert_eq!(r["failed"], 0, "{r}"); + assert!(r.get("warning").is_none(), "{r}"); + assert_eq!(file_names(&dest), ["template_A.vectorcraft", "template_B.vectorcraft"], "{r}"); + assert_eq!(std::fs::read(&path).unwrap(), bytes, "the template's file stays as it was"); + // A document never saved gets its files from the redirected save too. + let mut unsaved = with_data_sets(&["a", "b"]); + let dest = tmp("redirected-save-close-unsaved"); + let r = unsaved.execute("action.batch", &save_close(&dest)).unwrap(); + assert_eq!(r["failed"], 0, "{r}"); + assert!(r.get("warning").is_none(), "{r}"); + assert_eq!(file_names(&dest), ["Untitled_a.vectorcraft", "Untitled_b.vectorcraft"], "{r}"); + } + #[test] fn one_file_rule_covers_save_and_close_and_steps_left_in_place() { let saved = saved_with_two_data_sets(&tmp("one-file-rule").join("template.vectorcraft")); diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index d982b6127..65872508f 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { "Batch…", [], None, - "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved, was converted from an older version or was restored by Data Recovery) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close, or a Save, Save a Copy, Save as Template or export step (document.export, exportSelection, exportForScreens, exportPdf, exportEps, exportDxf, exportForWeb, exportForOffice) that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", + "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved, was converted from an older version or was restored by Data Recovery; a Save or Save As step that overrideSave points into destinationFolder makes each data set's file the document's own first) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close without such a step, or a Save, Save a Copy, Save as Template or export step (document.export, exportSelection, exportForScreens, exportPdf, exportEps, exportDxf, exportForWeb, exportForOffice) that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", always, action_batch::action_batch ), diff --git a/docs/mcp.md b/docs/mcp.md index deb110a53..312230a86 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3517,8 +3517,10 @@ an earlier version 1 set without that flag includes every step. The Actions pane destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. With a Data Sets source, `saveAndClose` saves the open document over its own file after each data set, so the file keeps only the last one, and a document that was never saved, was converted from an older version or was restored by Data Recovery is -refused before a data set is selected. The result carries `warning` when every data set saves over the same file: -with `saveAndClose`, or with a Save, Save a Copy, Save as Template or export step (PDF, EPS, DXF, Save for Web and +refused before a data set is selected. When `destinationFolder` is set and Override Save points a recorded Save or +Save As step into it, that step makes each data set's file the document's own, and `saveAndClose` saves each data +set to its own file. The result carries `warning` when every data set saves over the same file: +with `saveAndClose` without such a step, or with a Save, Save a Copy, Save as Template or export step (PDF, EPS, DXF, Save for Web and Save for Office Documents included) that writes a file and that the batch does not point into `destinationFolder`. The dialog shows the same warning. For a Data Sets source, `datasetFileNaming?: fileNumber | fileDataSetName | dataSetName` chooses File + Number (`source001`), diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index b932d6cf0..cddb2ecb9 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -643,7 +643,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used. - Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html). - - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set. A recorded Save, Save a Copy, Save as Template or export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy, template, PDF, EPS, DXF, Save for Web or Save for Office Documents step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. `action.batch` refuses Save and Close over the data sets of a document that was never saved, was converted from an older version or was restored by Data Recovery. + - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set, unless a recorded Save step that Override Save points into the destination folder first makes each data set's file the document's own. A recorded Save, Save a Copy, Save as Template or export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy, template, PDF, EPS, DXF, Save for Web or Save for Office Documents step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. Without such a Save step, `action.batch` refuses Save and Close over the data sets of a document that was never saved, was converted from an older version or was restored by Data Recovery. - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`). - Export presets: the Asset Export panel menu lists the export presets of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included. - Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`. From 0d1a8b6ffa838cb46ce6d252f1882a604de63a82 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 07:21:38 -0800 Subject: [PATCH 6/6] Actions: Batch Save and Close over data sets counts only saves that make the file the document's own A recorded save in the template format writes a copy and leaves the document's file as it was, so it no longer clears the warning or the refusals. A recorded Save or Save As with its own path gives a never-saved, converted or restored document its file before Save and Close, so the batch runs and warns that every data set saves over that one file. --- crates/engine/src/cmd/fileio/action_batch.rs | 43 +++++++++++++++++++- crates/engine/src/cmd/fileio/commands.rs | 2 +- docs/mcp.md | 3 +- docs/target-app-parity.md | 2 +- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/fileio/action_batch.rs b/crates/engine/src/cmd/fileio/action_batch.rs index 942f58e77..ded4dcd5d 100644 --- a/crates/engine/src/cmd/fileio/action_batch.rs +++ b/crates/engine/src/cmd/fileio/action_batch.rs @@ -161,7 +161,25 @@ pub fn batch_overwrites_one_file(s: &Session, p: &Value) -> bool { /// data set's own file in the destination folder (`redirected`). Such a step makes that file the /// document's own, so Save and Close then saves each data set to its own file. fn saves_move_to_each_file(steps: &[Value], redirected: bool) -> bool { - redirected && steps.iter().any(|c| matches!(c.get("command").and_then(Value::as_str), Some("document.save" | "file.save" | "file.saveAs"))) + redirected && steps.iter().any(|c| retargeting_save(c, true)) +} + +/// Whether step `c` is a Save, Save As or `document.save` that writes a file (its own `path`, or one +/// in the destination folder when `redirected`) and makes that file the document's own: a save in +/// the native template format writes a copy and leaves the document's file as it was. +fn retargeting_save(c: &Value, redirected: bool) -> bool { + if !matches!(c.get("command").and_then(Value::as_str), Some("document.save" | "file.save" | "file.saveAs")) { + return false; + } + let params = c.get("params"); + let path = params.and_then(|p| p.get("path")).and_then(Value::as_str).filter(|p| !p.is_empty()); + let format_ok = match params.and_then(|p| p.get("format")).and_then(Value::as_str).filter(|f| !f.is_empty()) { + Some(f) => super::format(f).is_some_and(|f| f.id != "template"), + // Override Save gives the step a native file name; else its own path names the format. + None if redirected => true, + None => path.and_then(super::format_for_name).is_none_or(|f| f.id != "template"), + }; + format_ok && (redirected || path.is_some()) } #[cfg(not(target_arch = "wasm32"))] @@ -209,7 +227,9 @@ fn batch_datasets(s: &mut Session, cmd: &str, run: &Run<'_>) -> Result { if names.len() > MAX_BATCH_FILES { return Err(bad(cmd, format!("more than {MAX_BATCH_FILES} data sets"))); } - if run.destination == "saveAndClose" && !saves_move_to_each_file(run.steps, run.override_save && run.dest_folder.is_some()) { + let redirected = run.override_save && run.dest_folder.is_some(); + // A recorded save with a file to write gives the document a file of its own before Save and Close. + if run.destination == "saveAndClose" && !run.steps.iter().any(|c| retargeting_save(c, redirected)) { if st.path.is_none() { return Err(bad(cmd, "the document has never been saved")); } @@ -1450,6 +1470,25 @@ mod tests { assert_eq!(r["failed"], 0, "{r}"); assert!(r.get("warning").is_none(), "{r}"); assert_eq!(file_names(&dest), ["Untitled_a.vectorcraft", "Untitled_b.vectorcraft"], "{r}"); + // A template save writes a copy: Save and Close still saves over the document's own file. + let mut template = save_close(&dest); + template["commands"] = json!([{"command": "document.save", "params": {"format": "template"}}]); + assert!(batch_overwrites_one_file(&s, &template), "{template}"); + let mut never = with_data_sets(&["a", "b"]); + let msg = never.execute("action.batch", &template).unwrap_err().to_string(); + assert!(msg.ends_with("the document has never been saved"), "{msg}"); + // A recorded Save As with its own path gives a never-saved document its file, the same one + // for every data set. + let one = tmp("save-as-then-close").join("one.vectorcraft"); + let p = json!({ + "source": "datasets", + "destination": "saveAndClose", + "commands": [{"command": "file.saveAs", "params": {"path": one.to_string_lossy()}}] + }); + let r = never.execute("action.batch", &p).unwrap(); + assert_eq!(r["failed"], 0, "{r}"); + assert_eq!(r["warning"], ONE_FILE_WARNING, "{r}"); + assert!(one.exists(), "{r}"); } #[test] diff --git a/crates/engine/src/cmd/fileio/commands.rs b/crates/engine/src/cmd/fileio/commands.rs index 65872508f..b71ede096 100644 --- a/crates/engine/src/cmd/fileio/commands.rs +++ b/crates/engine/src/cmd/fileio/commands.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { "Batch…", [], None, - "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved, was converted from an older version or was restored by Data Recovery; a Save or Save As step that overrideSave points into destinationFolder makes each data set's file the document's own first) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close without such a step, or a Save, Save a Copy, Save as Template or export step (document.export, exportSelection, exportForScreens, exportPdf, exportEps, exportDxf, exportForWeb, exportForOffice) that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", + "{commands: [{command, params}] (the action's steps), source?: folder (default) | datasets, folder? (required for folder: the source folder), includeSubdirs?: false, destination?: none (default: leave each file open) | saveAndClose (save over the source and close; for source datasets, save the open document over its own file after each data set, and refuse a document that was never saved, was converted from an older version or was restored by Data Recovery, unless a recorded Save or Save As step writes a file first; a Save or Save As step that overrideSave points into destinationFolder makes each data set's file the document's own) | folder (save or export into destinationFolder and close), destinationFolder? (required when destination is folder; also the folder Override Save/Export write into), datasetFileNaming?: fileNumber | fileDataSetName (default) | dataSetName (used only for source datasets; ignored for folder), overrideOpen?: true (skip document.open / file.open steps: the batch opens each source file), overrideSave?: true (point document.save / file.save / file.saveAs at destinationFolder/{stem}.{ext}), overrideExport?: true (point document.export / document.exportForScreens / document.exportSelection the same way), stopOnError?: true, label?} play the commands on every readable file in folder (and its subfolders when includeSubdirs), or on every data set of the open document. Data set names use the document stem plus a three-digit ordinal, the document stem and an underscore plus the data set name, or the data set name alone. Invalid file name characters become `-`; a name already used in the batch or whose output already exists in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix before the format-derived extension. Caps: 500 files or data sets, 8 folder levels, 256 steps. Batches cannot nest. → {processed: [{path|dataset, result|error}…], count, failed, stopped, warning?} (warning: for source datasets, when Save and Close without such a step, or a Save, Save a Copy, Save as Template or export step (document.export, exportSelection, exportForScreens, exportPdf, exportEps, exportDxf, exportForWeb, exportForOffice) that writes a file and is not pointed into destinationFolder, saves every data set over the same file)", always, action_batch::action_batch ), diff --git a/docs/mcp.md b/docs/mcp.md index 312230a86..facbe02e4 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3517,7 +3517,8 @@ an earlier version 1 set without that flag includes every step. The Actions pane destination `none` | `saveAndClose` | `folder` and Override Open / Save / Export. With a Data Sets source, `saveAndClose` saves the open document over its own file after each data set, so the file keeps only the last one, and a document that was never saved, was converted from an older version or was restored by Data Recovery is -refused before a data set is selected. When `destinationFolder` is set and Override Save points a recorded Save or +refused before a data set is selected, unless a recorded Save or Save As step (not in the template format) writes a +file and makes it the document's own first. When `destinationFolder` is set and Override Save points a recorded Save or Save As step into it, that step makes each data set's file the document's own, and `saveAndClose` saves each data set to its own file. The result carries `warning` when every data set saves over the same file: with `saveAndClose` without such a step, or with a Save, Save a Copy, Save as Template or export step (PDF, EPS, DXF, Save for Web and diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index cddb2ecb9..794ab3fb8 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -643,7 +643,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used. - Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html). - - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set, unless a recorded Save step that Override Save points into the destination folder first makes each data set's file the document's own. A recorded Save, Save a Copy, Save as Template or export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy, template, PDF, EPS, DXF, Save for Web or Save for Office Documents step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. Without such a Save step, `action.batch` refuses Save and Close over the data sets of a document that was never saved, was converted from an older version or was restored by Data Recovery. + - Batch… over data sets offers None, Save and Close and Folder as the destination, as for a folder source. Save and Close saves the open document over its own file after each data set, so the file keeps only the last data set, unless a recorded Save step that Override Save points into the destination folder first makes each data set's file the document's own. A recorded Save, Save a Copy, Save as Template or export step that writes a file and that the batch does not point into the destination folder (destination None, Override Save or Override Export off, or a copy, template, PDF, EPS, DXF, Save for Web or Save for Office Documents step) also writes the same file for every data set. In either case the dialog shows a warning and `action.batch` returns a `warning`. Unless a recorded Save or Save As step writes a file and makes it the document's own first, `action.batch` refuses Save and Close over the data sets of a document that was never saved, was converted from an older version or was restored by Data Recovery. - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`). - Export presets: the Asset Export panel menu lists the export presets of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included. - Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`.