mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 05:16:56 -06:00
Cause: in the web build no file has a path, so Save goes to Save as, and Save as and every Export go through Services::pick_save / pick_folder. WebServices keeps the trait's defaults there, which return no path, so the callback never ran: no download, no message. Only Variables ▸ Export used the platform's downloads (#368). Fix: where services.downloads() is true, those routes run their command without a path and hand the bytes to services.download(): - engine: file.saveAs takes `data: true` and returns the .figcraft file as base64 `data` with its `name`, marking it saved (no path, nothing remembered). Programmatic Save as without either still asks for a path. - ui: FigApp::download decodes a result's `data` and says when nothing arrived; Save as, one-off export (PNG/JPG/WebP/SVG/PDF), .fig export, frames to PDF and multi-layer export (one download per file) use it. Tests: engine save_as_data_hands_back_the_file_and_marks_it_saved; ui-egui web_save_tests (save, export, refused download) with the fake browser Services from variables_export_ui_tests. The UI tests fail without the fix.
94 lines
3.2 KiB
Rust
94 lines
3.2 KiB
Rust
//! Variables ▸ Export… where the platform saves by downloads (the browser, which has no save
|
|
//! dialogs): the export arrives as a download instead of doing nothing.
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use figcraft_engine::Session;
|
|
use serde_json::{Value, json};
|
|
|
|
use crate::variables_panel::export_dialog;
|
|
use crate::{FigApp, NoServices, Services};
|
|
|
|
pub(crate) type Downloads = Arc<Mutex<Vec<(String, Vec<u8>)>>>;
|
|
|
|
/// Services that save by downloads and keep them (`deliver` false: the browser refused).
|
|
pub(crate) struct Browser {
|
|
pub(crate) got: Downloads,
|
|
pub(crate) deliver: bool,
|
|
}
|
|
|
|
impl Services for Browser {
|
|
fn downloads(&self) -> bool {
|
|
true
|
|
}
|
|
fn download(&mut self, name: &str, bytes: &[u8]) -> bool {
|
|
self.got.lock().unwrap().push((name.to_string(), bytes.to_vec()));
|
|
self.deliver
|
|
}
|
|
}
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
for (cmd, p) in [
|
|
("variable.createCollection", json!({"name": "Theme", "modes": ["Light", "Dark"]})),
|
|
(
|
|
"variable.create",
|
|
json!({"collection": "Theme", "name": "color/primary", "type": "color", "values": {"Light": "#0000ff", "Dark": "#ffffff"}}),
|
|
),
|
|
("variable.create", json!({"collection": "Theme", "name": "radius", "type": "number", "value": 4})),
|
|
] {
|
|
s.execute(cmd, &p).unwrap();
|
|
}
|
|
s
|
|
}
|
|
|
|
fn browser(deliver: bool) -> (FigApp, Downloads) {
|
|
let got = Downloads::default();
|
|
(FigApp::new(session(), Box::new(Browser { got: got.clone(), deliver })), got)
|
|
}
|
|
|
|
fn toast(app: &FigApp) -> String {
|
|
app.ui.toast.as_ref().map(|t| t.0.clone()).unwrap_or_default()
|
|
}
|
|
|
|
#[test]
|
|
fn json_export_downloads_what_variable_export_writes() {
|
|
let (mut app, got) = browser(true);
|
|
let expected = app.session.execute("variable.export", &json!({"format": "json"})).unwrap()["text"].as_str().unwrap().to_string();
|
|
export_dialog(&mut app, "json");
|
|
let got = got.lock().unwrap();
|
|
let [(name, bytes)] = got.as_slice() else { panic!("one download, got {}", got.len()) };
|
|
assert!(name.ends_with(".variables.json"), "{name}");
|
|
assert_eq!(String::from_utf8(bytes.clone()).unwrap(), expected);
|
|
let parsed: Value = serde_json::from_slice(bytes).unwrap();
|
|
assert!(parsed.is_object());
|
|
assert!(toast(&app).contains(name.as_str()), "{}", toast(&app));
|
|
}
|
|
|
|
#[test]
|
|
fn css_export_downloads_custom_properties() {
|
|
let (mut app, got) = browser(true);
|
|
export_dialog(&mut app, "css");
|
|
let got = got.lock().unwrap();
|
|
let [(name, bytes)] = got.as_slice() else { panic!("one download, got {}", got.len()) };
|
|
assert!(name.ends_with(".variables.css"), "{name}");
|
|
let css = String::from_utf8(bytes.clone()).unwrap();
|
|
assert!(css.contains(":root") && css.contains("--"), "{css}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_refused_download_says_so() {
|
|
let (mut app, got) = browser(false);
|
|
export_dialog(&mut app, "json");
|
|
assert_eq!(got.lock().unwrap().len(), 1);
|
|
assert_eq!(toast(&app), "Couldn't download the variables");
|
|
}
|
|
|
|
#[test]
|
|
fn platforms_with_save_dialogs_keep_them() {
|
|
// No dialog answer (cancelled) and no downloads: nothing happens, as before.
|
|
let mut app = FigApp::new(session(), Box::new(NoServices));
|
|
export_dialog(&mut app, "json");
|
|
assert!(app.ui.toast.is_none());
|
|
}
|