mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
Save: save panels for files other than documents offer the file type they write
Document Info's Save, Save Swatch Library and Save Graphic Style Library to a file, the Libraries panel's Export Library…, the presets dialogs' Export…, the CSS Properties export, Print to a PostScript file and Keyboard Shortcuts › Export… passed no file type to the save panel. On Windows the panel listed "All Files", and neither the Windows nor the macOS panel added the extension to a name typed without one. Each of these saves now passes the one file type it writes, as Save and Export do for document formats. The Windows panel then lists it, and the Windows and macOS panels add its extension. The labels are the ones the open dialogs use for these files (Swatch libraries, Libraries, the presets, Shortcut sets), the Window menu's Graphic Style Libraries, Text for Document Info, CSS and PostScript.
This commit is contained in:
@@ -286,14 +286,16 @@ pub(crate) fn is_web(app: &VectorcraftApp) -> bool {
|
||||
app.services.download.is_some()
|
||||
}
|
||||
|
||||
/// Write `bytes` to `path`, else to a file picked with `name` suggested next to the document (the
|
||||
/// web downloads them as `name`) → where they went.
|
||||
/// Write `bytes` to `path`, else to a file picked in a save panel that suggests `name` next to the
|
||||
/// document and shows its file type, [`file_types`] (the web downloads them as `name`) → where
|
||||
/// they went.
|
||||
pub(crate) fn write_named(app: &mut VectorcraftApp, path: Option<String>, name: &str, bytes: &[u8]) -> Result<String, String> {
|
||||
let path = match path {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let (_, folder) = suggested(app, "");
|
||||
pick_path(app, &FilePick { name: name.to_string(), folder, ..Default::default() })?
|
||||
let filters = file_types(&fileio::extension(name));
|
||||
pick_path(app, &FilePick { name: name.to_string(), folder, filters })?
|
||||
}
|
||||
};
|
||||
write_reporting(app, &path, bytes)?;
|
||||
@@ -344,14 +346,40 @@ fn suggested(app: &VectorcraftApp, ext: &str) -> (String, Option<String>) {
|
||||
(format!("{}.{ext}", fileio::file_stem(&t)), folder.filter(|f| !f.is_empty()))
|
||||
}
|
||||
|
||||
/// The save panel's file type, `(label, extensions)`, for the files `write_named` and
|
||||
/// `target_path` write that are not a document format (`fileio::format` has those).
|
||||
const FILE_TYPES: &[(&str, &[&str])] = &[
|
||||
("Swatch libraries", &["vcswatches"]),
|
||||
("Swatch libraries", &["gpl"]),
|
||||
("Swatch libraries", &["ase"]),
|
||||
("Graphic Style Libraries", &[vectorcraft_doc::style_libs::STYLES_EXT]),
|
||||
("Libraries", vectorcraft_engine::cmd::library::LIBRARY_EXTS),
|
||||
("Flattener presets", vectorcraft_engine::cmd::flatten::PRESET_EXTS),
|
||||
("PDF presets", vectorcraft_engine::cmd::pdfcmds::PRESET_EXTS),
|
||||
("Perspective grid presets", vectorcraft_engine::cmd::perspgrid::PRESET_EXTS),
|
||||
("Print presets", vectorcraft_engine::cmd::printpresets::PRESET_EXTS),
|
||||
("CSS", &["css"]),
|
||||
("PostScript", &["ps"]),
|
||||
];
|
||||
|
||||
/// The file type a save panel shows for a file with extension `ext`: its format's, else its
|
||||
/// [`FILE_TYPES`] row; none for any other extension.
|
||||
fn file_types(ext: &str) -> Vec<(&'static str, &'static [&'static str])> {
|
||||
if let Some(f) = fileio::format(ext) {
|
||||
return vec![(f.label, f.extensions)];
|
||||
}
|
||||
let ext = ext.trim_start_matches('.').to_ascii_lowercase();
|
||||
FILE_TYPES.iter().find(|(_, exts)| exts.contains(&ext.as_str())).copied().into_iter().collect()
|
||||
}
|
||||
|
||||
/// `path`, else one picked in a save panel (the web: the suggested name) for the active document
|
||||
/// with extension `ext`, filtered to that format when it is one.
|
||||
/// with extension `ext`, filtered to its file type when it has one ([`file_types`]).
|
||||
pub(crate) fn target_path(app: &mut VectorcraftApp, path: Option<String>, ext: &str) -> Result<String, String> {
|
||||
if let Some(p) = path {
|
||||
return Ok(p);
|
||||
}
|
||||
let (name, folder) = suggested(app, ext);
|
||||
let filters = fileio::format(ext).map(|f| vec![(f.label, f.extensions)]).unwrap_or_default();
|
||||
let filters = file_types(ext);
|
||||
pick_path(app, &FilePick { name, folder, filters })
|
||||
}
|
||||
|
||||
@@ -684,7 +712,8 @@ pub fn export(app: &mut VectorcraftApp, format: Option<&str>, path: Option<Strin
|
||||
}
|
||||
|
||||
/// Run an engine command that returns `{dataBase64}` and write the bytes to a picked path
|
||||
/// (Export Selection, Save as Template).
|
||||
/// (Export Selection, Save for Office Documents, Text Export Options and the presets dialogs'
|
||||
/// Export…).
|
||||
pub fn save_command_output(app: &mut VectorcraftApp, id: &str, ext: &str, params: Value) -> Result<String, String> {
|
||||
let (path, _) = run_to_file(app, id, ext, params)?;
|
||||
app.status(format!("Saved {path}"));
|
||||
|
||||
@@ -476,7 +476,8 @@ pub fn run_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Resu
|
||||
"shortcuts.export" => {
|
||||
let v = export_json(&app.ui.shortcut_set, &app.ui.shortcut_overrides);
|
||||
let bytes = serde_json::to_vec_pretty(&v).unwrap_or_default();
|
||||
let path = s("path").or_else(|| crate::picks::save(app, &crate::FilePick::named("VectorCraft Shortcuts.json")));
|
||||
let pick = crate::FilePick { filters: vec![("Shortcut sets", &["json"])], ..crate::FilePick::named("VectorCraft Shortcuts.json") };
|
||||
let path = s("path").or_else(|| crate::picks::save(app, &pick));
|
||||
match path {
|
||||
Some(path) => match app.services.write.as_mut() {
|
||||
Some(w) => w(&path, &bytes).map(|_| json!({"path": path})),
|
||||
|
||||
@@ -34,6 +34,73 @@ fn last(w: &Written) -> String {
|
||||
w.borrow().last().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The file types each save panel was asked to show.
|
||||
type Shown = Rc<RefCell<Vec<Vec<(&'static str, &'static [&'static str])>>>>;
|
||||
|
||||
/// An app with a document whose save panel records the file types it shows and answers the
|
||||
/// suggested name in `/tmp`.
|
||||
fn panel_app() -> (VectorcraftApp, Shown) {
|
||||
let shown = Shown::default();
|
||||
let s = shown.clone();
|
||||
let services = Services {
|
||||
write: Some(Box::new(|_: &str, _: &[u8]| Ok(()))),
|
||||
pick_save: Some(Box::new(move |pick: &FilePick| {
|
||||
s.borrow_mut().push(pick.filters.clone());
|
||||
Some(format!("/tmp/{}", pick.name))
|
||||
})),
|
||||
..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, shown)
|
||||
}
|
||||
|
||||
/// The file types the last save panel showed.
|
||||
fn last_shown(s: &Shown) -> Vec<(&'static str, &'static [&'static str])> {
|
||||
s.borrow().last().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Document Info's, the libraries', the presets', the CSS, the PostScript and the shortcut saves
|
||||
/// show the one file type they write in the save panel, as Save and Export show theirs.
|
||||
#[test]
|
||||
fn save_panels_show_the_file_type_they_write() {
|
||||
use vectorcraft_engine::cmd::{flatten, pdfcmds, perspgrid, printpresets};
|
||||
let (mut app, shown) = panel_app();
|
||||
app.run("docInfo.save", json!({})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("Text", &["txt"][..])], "Document Info › Save");
|
||||
for format in ["vcswatches", "gpl", "ase", "css"] {
|
||||
app.run("ui.saveSwatchLibrary", json!({})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("format".into(), json!(format));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
let label = if format == "css" { "CSS" } else { "Swatch libraries" };
|
||||
assert_eq!(last_shown(&shown), [(label, &[format][..])], "Save Swatch Library as .{format}");
|
||||
}
|
||||
app.run("ui.saveGraphicStyleLibrary", json!({})).unwrap();
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("Graphic Style Libraries", &["vcstyles"][..])], "Save Graphic Style Library");
|
||||
let id = app.run("library.create", json!({"name": "Brand"})).unwrap()["id"].as_str().unwrap().to_string();
|
||||
io::save_command_output_named(&mut app, "library.export", "Brand.vclibrary", json!({"library": id})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("Libraries", &["vclibrary"][..])], "Libraries panel › Export Library…");
|
||||
app.run("css.exportFile", json!({"scope": "all"})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("CSS", &["css"][..])], "CSS Properties › Export All…");
|
||||
app.run("file.print", json!({"format": "postscript"})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("PostScript", &["ps"][..])], "Print to a PostScript file");
|
||||
app.run("shortcuts.export", json!({})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [("Shortcut sets", &["json"][..])], "Keyboard Shortcuts › Export…");
|
||||
let presets = [
|
||||
("pdf.preset.save", json!({"name": "Mine"}), "pdf.preset.export", "PDF presets", pdfcmds::PRESET_EXTS),
|
||||
("print.presets.save", json!({"name": "Mine"}), "print.presets.export", "Print presets", printpresets::PRESET_EXTS),
|
||||
("flattener.presets.save", json!({"name": "Mine"}), "flattener.presets.export", "Flattener presets", flatten::PRESET_EXTS),
|
||||
("perspective.presets.save", json!({"name": "Mine"}), "perspective.presets.export", "Perspective grid presets", perspgrid::PRESET_EXTS),
|
||||
];
|
||||
for (save, params, export, label, exts) in presets {
|
||||
app.run(save, params).unwrap();
|
||||
io::save_command_output(&mut app, export, exts[0], json!({"names": ["Mine"]})).unwrap();
|
||||
assert_eq!(last_shown(&shown), [(label, exts)], "{export}: the presets dialog's Export…");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saves_add_the_format_extension_to_a_bare_name() {
|
||||
let (mut app, w) = saving_app("/tmp/Untitled-1");
|
||||
|
||||
Reference in New Issue
Block a user