mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 05:46:46 -06:00
M1: a refused browser download keeps the unsaved changes (#969)
`file.saveAs {data: true}` marked the file saved before the UI handed the
bytes to `Services::download`. When the download was refused, the toast
said "Couldn't download" but the document was clean, so closing it no
longer warned and the edits could be lost.
The data result now leaves the save state alone. A new `file.markSaved`
(the bookkeeping a native save already did: the file and its branch tabs)
runs from `save_as_dialog` only once the download was delivered. It ends a
version preview like the other commands that act on the document itself.
Tests: the engine save test checks the file stays dirty until
`file.markSaved`; `a_refused_download_says_so` checks the document is
still dirty.
This commit is contained in:
@@ -55,6 +55,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
false,
|
||||
save_as
|
||||
),
|
||||
spec!(
|
||||
"file.markSaved",
|
||||
"Mark saved",
|
||||
"{} clears the unsaved changes of the active tab's file and its branch tabs: run it once the file from `file.saveAs {data: true}` was delivered (a refused download keeps them)",
|
||||
false,
|
||||
mark_saved
|
||||
),
|
||||
spec!(
|
||||
"file.export",
|
||||
"Export",
|
||||
@@ -869,24 +876,30 @@ fn save_to(s: &mut Session, path: Option<String>) -> Result<Value> {
|
||||
Arc::make_mut(&mut snapshot.doc).name = file_stem(path);
|
||||
}
|
||||
let bytes = snapshot.encode()?;
|
||||
if let Some(path) = &path {
|
||||
write(path, &bytes)?;
|
||||
}
|
||||
ds.doc = snapshot.doc;
|
||||
if path.is_some() {
|
||||
ds.path.clone_from(&path);
|
||||
}
|
||||
ds.mark_saved();
|
||||
ds.revision += 1;
|
||||
let (uid, name) = (ds.uid, format!("{}.figcraft", ds.doc.name));
|
||||
branch_tabs_saved(s, uid);
|
||||
let Some(path) = path else {
|
||||
// The download can still be refused: the caller marks the file saved once it was delivered.
|
||||
let name = format!("{}.figcraft", ds.doc.name);
|
||||
return Ok(json!({"name": name, "bytes": bytes.len(), "data": base64::engine::general_purpose::STANDARD.encode(&bytes)}));
|
||||
};
|
||||
write(&path, &bytes)?;
|
||||
ds.doc = snapshot.doc;
|
||||
ds.path = Some(path.clone());
|
||||
mark_saved(s, &json!({}))?;
|
||||
remember(s, &path);
|
||||
Ok(json!({"path": path, "bytes": bytes.len()}))
|
||||
}
|
||||
|
||||
/// The file of the active tab (with its branch tabs) now matches what was written or delivered.
|
||||
fn mark_saved(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let i = crate::branches::file_tab(s);
|
||||
let ds = s.docs.get_mut(i).ok_or(EngineError::NoDocument)?;
|
||||
ds.mark_saved();
|
||||
ds.revision += 1;
|
||||
let uid = ds.uid;
|
||||
branch_tabs_saved(s, uid);
|
||||
Ok(json!({"saved": true}))
|
||||
}
|
||||
|
||||
fn save(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(path) = string(p, "path") {
|
||||
return save_to(s, Some(path));
|
||||
@@ -1590,7 +1603,7 @@ mod rest_import_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_as_data_hands_back_the_file_and_marks_it_saved() {
|
||||
fn save_as_data_hands_back_the_file_and_marks_it_saved_once_delivered() {
|
||||
// The browser has no paths: Save as returns the file for a download (#969).
|
||||
let mut s = Session::new();
|
||||
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
|
||||
@@ -1598,6 +1611,8 @@ mod rest_import_tests {
|
||||
let recent = s.prefs.recent_files.clone();
|
||||
let r = s.execute("file.saveAs", &json!({"data": true})).unwrap();
|
||||
assert_eq!(r["name"], "Untitled.figcraft");
|
||||
assert!(s.ds().unwrap().is_dirty(), "not delivered yet: a refused download must not lose the edits");
|
||||
s.execute("file.markSaved", &json!({})).unwrap();
|
||||
assert!(!s.ds().unwrap().is_dirty());
|
||||
assert_eq!(s.ds().unwrap().path, None, "still no path to save over");
|
||||
assert_eq!(s.prefs.recent_files, recent, "nothing on disk to remember");
|
||||
|
||||
@@ -402,7 +402,8 @@ pub const PREVIEW_READ_ONLY: &str = "you're viewing an earlier version: restore
|
||||
|
||||
/// Commands that first end a version preview: they act on the document itself. Saving does so
|
||||
/// only with a native destination; export copies and pending format choices keep the preview.
|
||||
const ENDS_PREVIEW: &[&str] = &["file.save", "file.saveAs", "file.close", "file.revert", "file.saveVersion", "file.duplicate", "version.restore"];
|
||||
const ENDS_PREVIEW: &[&str] =
|
||||
&["file.save", "file.saveAs", "file.markSaved", "file.close", "file.revert", "file.saveVersion", "file.duplicate", "version.restore"];
|
||||
|
||||
static NEXT_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
|
||||
|
||||
@@ -1920,6 +1920,7 @@ status = "draft — needs native review"
|
||||
"file.revert" = "Volver a lo guardado"
|
||||
"file.save" = "Guardar"
|
||||
"file.saveAs" = "Guardar como"
|
||||
"file.markSaved" = "Marcar como guardado"
|
||||
"file.saveVersion" = "Guardar en el historial de versiones"
|
||||
"file.versionHistory" = "Mostrar historial de versiones"
|
||||
"fill.add" = "Añadir relleno"
|
||||
|
||||
@@ -1146,6 +1146,7 @@ impl FigApp {
|
||||
if let Some(r) = self.run("file.saveAs", json!({"data": true}))
|
||||
&& self.download(r["name"].as_str().unwrap_or_default(), &r)
|
||||
{
|
||||
self.run("file.markSaved", json!({}));
|
||||
dialogs::file_saved(self, &r);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -52,6 +52,7 @@ fn a_refused_download_says_so() {
|
||||
app.save_as_dialog();
|
||||
assert_eq!(got.lock().unwrap().len(), 1);
|
||||
assert_eq!(toast(&app), "Couldn't download Untitled.figcraft");
|
||||
assert!(app.session.ds().unwrap().is_dirty(), "no file arrived: closing must still warn about the edits");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user