mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Fix #37: a name typed without an extension gets the chosen format's on Export too
Save, Save As and Save a Copy already added the save format's extension to a name typed in the save panel, but every other write through it (Export, Export Selection, Save as PDF, text and preset exports, Document Info) wrote the name as typed, so "art" became an extensionless file. The save panel's answer now goes through one rule (io::with_extension): kept when it ends in the suggested name's extension or names a known format, else that extension is added. The web already downloads under the suggested name or adds the format's extension in its Save As dialog. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6992c843b4
commit
9c707b37fc
3 files changed
+92
-6
No files matched your search
@@ -134,12 +134,25 @@ pub(crate) fn write_named(app: &mut VectorcraftApp, path: Option<String>, name:
|
||||
}
|
||||
|
||||
/// Where a file goes when no path was given: the suggested name on the web (a download), else the
|
||||
/// save panel's choice.
|
||||
/// save panel's choice, given the suggested name's extension when it names no file type (a name
|
||||
/// typed without one).
|
||||
fn pick_path(app: &mut VectorcraftApp, pick: &FilePick) -> Result<String, String> {
|
||||
if is_web(app) {
|
||||
return Ok(pick.name.clone());
|
||||
}
|
||||
app.services.pick_save.as_mut().and_then(|f| f(pick)).ok_or_else(|| "cancelled".into())
|
||||
let picked = app.services.pick_save.as_mut().and_then(|f| f(pick)).ok_or("cancelled")?;
|
||||
Ok(with_extension(&picked, &fileio::extension(&pick.name), |_| true))
|
||||
}
|
||||
|
||||
/// `path` as typed in a save panel: kept when it ends in `ext` or its extension names a format
|
||||
/// `keeps` takes, else with `.ext` added.
|
||||
fn with_extension(path: &str, ext: &str, keeps: impl Fn(&Format) -> bool) -> String {
|
||||
let has = fileio::extension(path);
|
||||
if ext.is_empty() || has.eq_ignore_ascii_case(ext) || fileio::format_for_name(path).is_some_and(keeps) {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("{path}.{ext}")
|
||||
}
|
||||
}
|
||||
|
||||
/// The active document's file name with extension `ext`, and its folder.
|
||||
@@ -172,10 +185,7 @@ pub fn remember_view(app: &mut VectorcraftApp) {
|
||||
/// A path typed in a save panel: kept when its extension names a save format (the panel's file
|
||||
/// type), else `f`'s extension is added.
|
||||
fn with_save_extension(path: &str, f: &Format) -> String {
|
||||
match fileio::format_for_name(path) {
|
||||
Some(g) if SAVE_FORMATS.contains(&g.id) => path.to_string(),
|
||||
_ => format!("{path}.{}", f.extensions[0]),
|
||||
}
|
||||
with_extension(path, f.extensions[0], |g| SAVE_FORMATS.contains(&g.id))
|
||||
}
|
||||
|
||||
/// Write every file of an export (one per artboard, linked images) for the destination `path`.
|
||||
|
||||
@@ -74,6 +74,8 @@ mod tests_recovery;
|
||||
#[cfg(test)]
|
||||
mod tests_save;
|
||||
#[cfg(test)]
|
||||
mod tests_saveext;
|
||||
#[cfg(test)]
|
||||
mod tests_slices;
|
||||
#[cfg(test)]
|
||||
mod tests_svg;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//! A name typed in a save panel without an extension gets the chosen format's: Save, Save As,
|
||||
//! Save a Copy, Export, Export Selection and other writes through a save panel.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use crate::{FilePick, Services, VectorcraftApp, io};
|
||||
|
||||
type Written = Rc<RefCell<Vec<String>>>;
|
||||
|
||||
/// An app with a document whose save panel answers `typed` and whose writer records the paths.
|
||||
fn saving_app(typed: &'static str) -> (VectorcraftApp, Written) {
|
||||
let written = Written::default();
|
||||
let w = written.clone();
|
||||
let services = Services {
|
||||
write: Some(Box::new(move |p: &str, _: &[u8]| {
|
||||
w.borrow_mut().push(p.to_string());
|
||||
Ok(())
|
||||
})),
|
||||
pick_save: Some(Box::new(move |_: &FilePick| Some(typed.to_string()))),
|
||||
..Default::default()
|
||||
};
|
||||
let mut app = VectorcraftApp::new(Session::new(), services);
|
||||
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
(app, written)
|
||||
}
|
||||
|
||||
fn last(w: &Written) -> String {
|
||||
w.borrow().last().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saves_add_the_format_extension_to_a_bare_name() {
|
||||
let (mut app, w) = saving_app("/tmp/Untitled-1");
|
||||
app.run("file.save", json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/Untitled-1.vectorcraft");
|
||||
assert_eq!(app.session.active().unwrap().path.as_deref(), Some("/tmp/Untitled-1.vectorcraft"));
|
||||
let (mut app, w) = saving_app("/tmp/copy");
|
||||
app.run("file.saveCopy", json!({"format": "svg", "svg": {}})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/copy.svg");
|
||||
let (mut app, w) = saving_app("/tmp/as");
|
||||
app.run("file.saveAs", json!({"format": "pdf", "options": {}})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/as.pdf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exports_add_the_format_extension_to_a_bare_name() {
|
||||
let (mut app, w) = saving_app("/tmp/art");
|
||||
io::export(&mut app, Some("png"), None, &json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/art.png");
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
app.run("document.exportSelection", json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/art.png", "Export Selection: its default format");
|
||||
io::save_command_output(&mut app, "document.export", "txt", json!({"format": "txt"})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/art.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_extensions_are_kept() {
|
||||
let (mut app, w) = saving_app("/tmp/art.svg");
|
||||
io::export(&mut app, Some("svg"), None, &json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/art.svg");
|
||||
let (mut app, w) = saving_app("/tmp/My.Poster.SVG");
|
||||
io::export(&mut app, Some("svg"), None, &json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/My.Poster.SVG");
|
||||
// A dot in the name isn't an extension.
|
||||
let (mut app, w) = saving_app("/tmp/v1.2");
|
||||
io::export(&mut app, Some("png"), None, &json!({})).unwrap();
|
||||
assert_eq!(last(&w), "/tmp/v1.2.png");
|
||||
}
|
||||
Reference in new issue
Block a user