Merge pull request #1806 from helenkwok/save-keeps-host-file

figcraft-web: a Save of a host-opened document goes out under the host's file name (builds on #1358)
This commit is contained in:
FredApps authored and GitHub committed 2026-11-05 14:27:09 +01:00
commit 4558fd833b
6 files changed
+95 -6

No files matched your search

+5
View File
@@ -91,6 +91,11 @@ impl Bridge {
let _ = self.host.post_failed(error, None);
}
/// The document file the host opened or the last Save As made, if any.
pub fn host_file(&self) -> Option<String> {
self.host_file.borrow().clone()
}
/// Hand a file the app would otherwise download to the host; `false` when nothing was
/// delivered. The `saveAs` rule is this module's documentation's.
pub fn post_save(&self, path: &str, bytes: &[u8]) -> bool {
+3
View File
@@ -279,6 +279,9 @@ impl Services for WebServices {
}
}
}
fn host_file(&self) -> Option<String> {
self.host.as_ref()?.host_file()
}
fn clipboard_set_text(&mut self, s: &str) {
figcraft_ui_egui::egui_copy_text(&self.ctx, s);
}
+20 -6
View File
@@ -6,12 +6,7 @@ use super::{
impl FigApp {
pub fn save_as_dialog(&mut self) {
if self.services.downloads() {
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);
}
self.save_download(None);
return;
}
let Ok(name) = self.session.doc().map(|d| d.name.clone()) else { return };
@@ -43,6 +38,17 @@ impl FigApp {
});
}
/// Save by handing the file over as a download. `keep`: the name a Save of a host-opened
/// document keeps; `None` is a Save As, under the document's own name.
fn save_download(&mut self, keep: Option<&str>) {
if let Some(r) = self.run("file.saveAs", json!({"data": true}))
&& self.download(keep.unwrap_or_else(|| r["name"].as_str().unwrap_or_default()), &r)
{
self.run("file.markSaved", json!({}));
dialogs::file_saved(self, &r);
}
}
pub fn save(&mut self) {
// A branch tab saves its main file.
let tab = figcraft_engine::branches::file_tab(&self.session);
@@ -57,6 +63,9 @@ impl FigApp {
if self.run("file.save", json!({})).is_some() {
self.toast(i18n::tr("Saved"));
}
} else if let Some(name) = self.services.host_file().filter(|n| is_document_file(n)) {
// A host-opened document is saved as the file the host gave: the host updates that file.
self.save_download(Some(&name));
} else {
self.save_as_dialog();
}
@@ -299,3 +308,8 @@ impl FigApp {
failed
}
}
/// A `.figcraft` file name: the document format a Save writes (a `.fig` is an export copy).
fn is_document_file(name: &str) -> bool {
Path::new(name).extension().and_then(OsStr::to_str).is_some_and(|e| e.eq_ignore_ascii_case("figcraft"))
}
+60
View File
@@ -0,0 +1,60 @@
//! In a page host the browser hands files to the host: a Save of the document the host opened goes
//! out under the host's file name, a Save As under the document's own name.
use std::sync::{Arc, Mutex};
use figcraft_engine::Session;
use serde_json::json;
use crate::{FigApp, Services};
type Saved = Arc<Mutex<Vec<String>>>;
/// Services that save by downloads, with a document file the host opened.
struct Host {
got: Saved,
file: Option<&'static str>,
}
impl Services for Host {
fn downloads(&self) -> bool {
true
}
fn download(&mut self, name: &str, _bytes: &[u8]) -> bool {
self.got.lock().unwrap().push(name.to_string());
true
}
fn host_file(&self) -> Option<String> {
self.file.map(str::to_string)
}
}
fn app(file: Option<&'static str>) -> (FigApp, Saved) {
let got = Saved::default();
let mut app = FigApp::new(Session::empty(), Box::new(Host { got: got.clone(), file }));
app.session.execute("file.new", &json!({"name": "Original"})).unwrap();
(app, got)
}
#[test]
fn save_keeps_the_name_the_host_opened() {
let (mut app, got) = app(Some("client.figcraft"));
app.save();
assert_eq!(got.lock().unwrap().as_slice(), ["client.figcraft"]);
}
#[test]
fn save_as_uses_the_document_s_own_name() {
let (mut app, got) = app(Some("client.figcraft"));
app.save_as_dialog();
assert_eq!(got.lock().unwrap().as_slice(), ["Original.figcraft"]);
}
#[test]
fn without_a_host_file_or_for_a_copy_format_save_is_a_save_as() {
for file in [None, Some("design.json"), Some("export.fig")] {
let (mut app, got) = app(file);
app.save();
assert_eq!(got.lock().unwrap().as_slice(), ["Original.figcraft"], "{file:?}");
}
}
+2
View File
@@ -124,6 +124,8 @@ mod home_recents_tests;
#[cfg(test)]
mod home_tests;
#[cfg(test)]
mod host_save_tests;
#[cfg(test)]
mod idle_tests;
#[cfg(test)]
mod ime_tests;
+5
View File
@@ -24,6 +24,11 @@ pub trait Services {
fn download(&mut self, _name: &str, _bytes: &[u8]) -> bool {
false
}
/// The document file a host page opened, where the app runs inside one (the browser's host
/// mode): a Save of that document keeps this name instead of the document's own.
fn host_file(&self) -> Option<String> {
None
}
/// A folder to write several files into.
fn pick_folder(&mut self, _title: &str) -> DialogFuture {
Box::pin(std::future::ready(None))