forked from 1b-agent/opencadstudio-upstream
feat(web): return saved drawings to an embedding page
The web control channel gains a save operation that serializes the active drawing in the requested format and returns it as base64 in the reply, clearing the dirty flag, the counterpart of opening from bytes. A host page can also define window.openCadStudioSaveFile(name, bytes) to receive every saved or exported file in place of a browser download; while it is set no file picker opens. Fixes #1704 Fixes #1705 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d1a2e9db64
commit
85627ebc8a
4 files changed
+91
-2
No files matched your search
@@ -58,6 +58,33 @@ earlier drawing is still opening the request is answered with `busy`; retry it o
|
||||
rather than a disk, so use `data_base64` for a file the page has not seen.
|
||||
`data_base64` is rejected on native builds; use `path` there.
|
||||
|
||||
## Getting the drawing back
|
||||
|
||||
`save` serializes the active drawing and returns it in the reply, without a download:
|
||||
|
||||
```json
|
||||
{"op":"save","name":"plan.dwg","target_format":"dwg","target_version":"AC1032"}
|
||||
```
|
||||
|
||||
The reply carries `name`, `target_format`, `target_version`, `dropped_on_save`, `bytes`
|
||||
and `data_base64`, and the tab is marked clean. `target_format` and `target_version`
|
||||
default to the drawing's own; a conversion that would drop unsupported records needs
|
||||
`allow_lossy: true`, as on native.
|
||||
|
||||
## Receiving saved files in the host page
|
||||
|
||||
A page that embeds the app can take every saved or exported file itself instead of a
|
||||
browser download by defining a function on the app's window before saving:
|
||||
|
||||
```js
|
||||
window.openCadStudioSaveFile = (name, bytes) => {
|
||||
// name: file name, bytes: Uint8Array
|
||||
};
|
||||
```
|
||||
|
||||
While it is defined, Save, Save As, the save-before-close prompt and the exports call it
|
||||
with the file name and the bytes, and no file picker opens. Without it nothing changes.
|
||||
|
||||
## Startup dialogs
|
||||
|
||||
A dialog can be open when the page is ready. The donation prompt appears once per
|
||||
|
||||
@@ -1147,6 +1147,49 @@ impl OpenCADStudio {
|
||||
)
|
||||
}
|
||||
}
|
||||
// Web: no filesystem path; the drawing comes back in the reply.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
"save" => {
|
||||
use base64::Engine as _;
|
||||
let default_is_dxf = crate::io::source_is_dxf(None, &self.tabs[i].scene.document);
|
||||
let (version, is_dxf) = super::automation::requested_save_target(
|
||||
req,
|
||||
self.tabs[i].scene.document.version,
|
||||
default_is_dxf,
|
||||
None,
|
||||
)
|
||||
.map_err(|error| failure("invalid_target", error))?;
|
||||
let dropped =
|
||||
crate::io::dropped_on_save_count(&self.tabs[i].scene.document, version, is_dxf);
|
||||
if dropped > 0 && req["allow_lossy"].as_bool() != Some(true) {
|
||||
return Err(failure(
|
||||
"lossy_conversion_not_acknowledged",
|
||||
format!(
|
||||
"Conversion would drop {dropped} unsupported record(s); set allow_lossy=true to acknowledge"
|
||||
),
|
||||
));
|
||||
}
|
||||
let ext = if is_dxf { "dxf" } else { "dwg" };
|
||||
self.sync_view_state_for_save(i);
|
||||
super::update::util::sync_annotation_scale_header(&mut self.tabs[i].scene);
|
||||
self.stamp_header_sysvars(i);
|
||||
self.sync_solid_models_for_save(i);
|
||||
self.tabs[i].scene.sync_native_parametric_graph();
|
||||
let bytes = crate::io::save_to_bytes(&self.tabs[i].scene.document, ext, version)
|
||||
.map_err(|error| failure("save_failed", error))?;
|
||||
self.tabs[i].dirty = false;
|
||||
let name = req["name"].as_str().filter(|name| !name.is_empty());
|
||||
let name = name.map_or_else(|| format!("drawing.{ext}"), str::to_string);
|
||||
self.set_control_result(json!({
|
||||
"name": name,
|
||||
"target_format": ext,
|
||||
"target_version": format!("{version:?}"),
|
||||
"dropped_on_save": dropped,
|
||||
"bytes": bytes.len(),
|
||||
"data_base64": base64::engine::general_purpose::STANDARD.encode(&bytes),
|
||||
}));
|
||||
Task::none()
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
"save_verified" => {
|
||||
let result = self.save_verified_request(req)?;
|
||||
|
||||
@@ -3882,8 +3882,10 @@ impl OpenCADStudio {
|
||||
// handle is secured at this gesture moment (or reused from a
|
||||
// previous pick — retained handles need no activation to write)
|
||||
// and only the write happens later.
|
||||
// A registered host save hook takes the bytes through the
|
||||
// download path, so no file picker opens.
|
||||
let route = crate::io::fs_access::save_route(
|
||||
crate::io::fs_access::supported(),
|
||||
crate::io::fs_access::supported() && crate::sys::save_hook().is_none(),
|
||||
self.save_plain_reuse_fs_handle,
|
||||
crate::io::fs_access::handle_ext(tab_id).as_deref(),
|
||||
ext,
|
||||
|
||||
+18
-1
@@ -597,10 +597,15 @@ pub fn download_bytes(name: &str, bytes: &[u8]) {
|
||||
let Some(window) = web_sys::window() else {
|
||||
return;
|
||||
};
|
||||
let array = js_sys::Uint8Array::from(bytes);
|
||||
// A host page that embeds the app takes the file instead of a download.
|
||||
if let Some(hook) = save_hook() {
|
||||
let _ = hook.call2(&window, &wasm_bindgen::JsValue::from_str(name), &array);
|
||||
return;
|
||||
}
|
||||
let Some(document) = window.document() else {
|
||||
return;
|
||||
};
|
||||
let array = js_sys::Uint8Array::from(bytes);
|
||||
let parts = js_sys::Array::new();
|
||||
parts.push(&array.buffer());
|
||||
let Ok(blob) = web_sys::Blob::new_with_u8_array_sequence(&parts) else {
|
||||
@@ -618,6 +623,18 @@ pub fn download_bytes(name: &str, bytes: &[u8]) {
|
||||
let _ = web_sys::Url::revoke_object_url(&url);
|
||||
}
|
||||
|
||||
/// The `window.openCadStudioSaveFile(name, bytes)` function an embedding page
|
||||
/// registers to receive saved and exported files, if any.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn save_hook() -> Option<js_sys::Function> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let window = web_sys::window()?;
|
||||
js_sys::Reflect::get(window.as_ref(), &wasm_bindgen::JsValue::from_str("openCadStudioSaveFile"))
|
||||
.ok()?
|
||||
.dyn_into::<js_sys::Function>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Show a plot preview as an in-page overlay. The plotted PDF arrives already
|
||||
/// rasterised to PNG pages: inline `<img>` elements render in every browser,
|
||||
/// with no popup permission and no PDF viewer plugin — both of which embedded
|
||||
|
||||
Reference in new issue
Block a user