Skip to main content

blockworx_canvas2d/
handoff.rs

1//! The two hand-offs a backend can perform without a shell around it: saving
2//! bytes to a file and putting text on the clipboard. Both are browser
3//! affordances rather than application decisions, which is why they live at
4//! this level rather than above it.
5
6use wasm_bindgen::{JsCast as _, JsValue};
7
8/// Save `bytes` as `file_name`: wrap them in a Blob, mint an object URL, and
9/// click a detached `<a download>`, so the browser saves the file straight
10/// away rather than offering a link.
11///
12/// # Errors
13///
14/// If the document is gone, or the browser refuses the Blob or the URL.
15pub fn download(file_name: &str, mime: &str, bytes: &[u8]) -> Result<(), JsValue> {
16    let parts = js_sys::Array::new();
17    parts.push(&js_sys::Uint8Array::from(bytes));
18    let options = web_sys::BlobPropertyBag::new();
19    options.set_type(mime);
20    let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &options)?;
21    let url = web_sys::Url::create_object_url_with_blob(&blob)?;
22
23    let document = web_sys::window()
24        .and_then(|window| window.document())
25        .ok_or_else(|| JsValue::from_str("no document"))?;
26    let anchor = document
27        .create_element("a")?
28        .dyn_into::<web_sys::HtmlAnchorElement>()?;
29    anchor.set_href(&url);
30    anchor.set_download(file_name);
31    anchor.click();
32
33    web_sys::Url::revoke_object_url(&url)?;
34    Ok(())
35}
36
37/// Put `text` on the system clipboard. The write is asynchronous and its
38/// promise is dropped: a clipboard the user denied is not an editing error.
39///
40/// # Errors
41///
42/// If there is no window to read the navigator from.
43pub fn clipboard_write(text: &str) -> Result<(), JsValue> {
44    let clipboard = web_sys::window()
45        .ok_or_else(|| JsValue::from_str("no window"))?
46        .navigator()
47        .clipboard();
48    let _ = clipboard.write_text(text);
49    Ok(())
50}