Merge pull request #4581 from storytold/feat/remember-native-save-options

Fix #4523: a new document's VectorCraft Options start as the last native save used them
This commit is contained in:
BFlat
2026-10-29 20:21:23 +07:00
committed by GitHub
4 changed files with 40 additions and 3 deletions
+12 -2
View File
@@ -867,6 +867,11 @@ impl SaveJob {
/// its own also ends its Data Recovery copy.
pub fn complete(self, s: &mut Session, uid: u64) {
let saved = self.plan.retargets() && self.plan.path.is_some();
if saved && self.plan.format.id == "vectorcraft" {
let mut last = self.plan.options.clone();
last.retain(|k, _| !ARTBOARD_PARAMS.contains(&k.as_str()) && !matches!(k.as_str(), "separateArtboards" | "compress"));
s.prefs.native_save_options = last;
}
// The other open documents that place this one show the new version.
let written = self.plan.path.clone().filter(|_| matches!(self.plan.format.id, "vectorcraft" | "template"));
if let Some(st) = s.document_mut(uid) {
@@ -932,7 +937,12 @@ fn format_options(s: &mut Session, p: &Value) -> Result<Value> {
let f = format(id)
.filter(|f| f.write || SAVE_FORMATS.contains(&f.id) || TEMPLATE_FORMATS.contains(&f.id))
.ok_or_else(|| bad(C, format!("`{id}` is no writable format (see document.formats)")))?;
let saved = st.filter(|d| d.format == f.id).map(remembered_save_options);
// A document not yet saved as a native file starts from the native options used last.
let saved = st
.filter(|d| d.format == f.id)
.map(remembered_save_options)
.filter(|m| !m.is_empty())
.or_else(|| (f.id == "vectorcraft" && !s.prefs.native_save_options.is_empty()).then(|| s.prefs.native_save_options.clone()));
// A native save compresses as Use Compression says, unless told otherwise.
let prefs = with_compression_pref(&s.prefs, &json!({}));
let mut v = f.to_json();
@@ -1008,7 +1018,7 @@ pub(super) fn specs() -> Vec<CommandSpec> {
"Format Options",
[],
None,
"{format?: a writable format id or ait (default: the document's own)} → {id, label, extensions, mime, read, write, raster, options: {name: {type, default, description, value}}, saveFormats: [{id, label, extensions}]}; value = the document's option as last saved in that format, else the default",
"{format?: a writable format id or ait (default: the document's own)} → {id, label, extensions, mime, read, write, raster, options: {name: {type, default, description, value}}, saveFormats: [{id, label, extensions}]}; value = the document's option as last saved in that format, else (native, for a document not yet saved as one) as the last native save used it, else the default",
always,
format_options
),
+7
View File
@@ -247,6 +247,12 @@ pub struct Prefs {
/// Preferences dialog field: resetting the preferences keeps it; `webExport.presets.*` edit it.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub web_export_presets: Vec<cmd::webexport::WebPreset>,
/// The VectorCraft Options a native Save or Save As used last (Preview, Embed ICC Profiles,
/// Create PDF-Compatible File…; not the artboards it picked, nor Use Compression, which its
/// preference decides): `file.formatOptions` offers them for a document not yet saved as a
/// native file. Not a Preferences dialog field.
#[serde(skip_serializing_if = "serde_json::Map::is_empty")]
pub native_save_options: serde_json::Map<String, serde_json::Value>,
/// Save for Web: the settings the dialog opens on (`webExport.settings`); none until set.
#[serde(skip_serializing_if = "Option::is_none")]
pub web_export_settings: Option<cmd::webexport::WebSettings>,
@@ -442,6 +448,7 @@ impl Default for Prefs {
append_converted: true,
use_compression: false,
web_export_presets: vec![],
native_save_options: serde_json::Map::new(),
web_export_settings: None,
print_presets: vec![],
constrain_proportions: false,
+19
View File
@@ -99,6 +99,25 @@ fn save_as_takes_on_path_format_and_options() {
let _ = std::fs::remove_dir_all(d);
}
/// #4523: a new document's VectorCraft Options start as the last native save used them (not its
/// artboard range, nor Use Compression, which its preference decides).
#[test]
fn a_new_document_offers_the_native_options_used_last() {
let d = dir("lastnative");
let mut s = session();
let opts = json!({"preview": true, "embedProfiles": false, "pdfCompatible": true, "separateArtboards": true, "range": "1"});
s.execute("file.saveAs", &json!({"path": path(&d, "a.vectorcraft"), "options": opts})).unwrap();
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
let o = s.execute("file.formatOptions", &json!({"format": "vectorcraft"})).unwrap();
let value = |k: &str| o["options"][k]["value"].clone();
assert_eq!((value("preview"), value("embedProfiles"), value("pdfCompatible")), (json!(true), json!(false), json!(true)));
assert_eq!(
(value("separateArtboards"), value("range")),
(o["options"]["separateArtboards"]["default"].clone(), o["options"]["range"]["default"].clone())
);
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn another_format_without_a_path_hands_the_bytes_back() {
let d = dir("otherfmt");
+2 -1
View File
@@ -588,7 +588,8 @@ it replaced by the template's extension, and any other path that does not end in
`card.png` is saved as `card.vctemplate` and `card` as `card.vctemplate`; the suggested name and the Templates folder
come back without a path), `file.newFromTemplate {path}`, `file.revert` and `file.formatOptions {format?}` (a format's options
with the values a save would use). Without a path the save commands return `{dataBase64, name, folder?, warnings}`. A document saved as SVG or PDF
remembers its options, so the next save reuses them. A PDF opened with `pages`, `cropTo` or `password` is only part
remembers its options, so the next save reuses them; a document not yet saved as a native file is offered the
VectorCraft Options the last native Save or Save As used (not its artboards, nor Use Compression). A PDF opened with `pages`, `cropTo` or `password` is only part
of the file, so Save asks for a new name instead of writing it back.
`file.documentColorMode {mode, convert?, intent?, grays?}` switches the document color mode, converting the colors of
the art (opacity masks included), symbols, graph designs, pattern tiles (with the tile that pattern editing's Cancel