StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use crate::{clipboard::ClipboardContent, Clipboard}; |
| 2 | use js_sys::{Array, Object}; |
| 3 | use wasm_bindgen::{self, prelude::*, JsCast}; |
| 4 | use web_sys::{Blob, BlobPropertyBag}; |
| 5 | |
| 6 | pub struct WebClipboard { |
| 7 | inner: web_sys::Clipboard, |
| 8 | saved_content: ClipboardContent, |
| 9 | } |
| 10 | |
| 11 | impl WebClipboard { |
| 12 | pub fn new() -> Self { |
| 13 | Self { |
| 14 | inner: gloo::utils::window().navigator().clipboard(), |
| 15 | saved_content: Default::default(), |
| 16 | } |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | impl Default for WebClipboard { |
| 21 | fn default() -> Self { |
| 22 | Self::new() |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | impl Clipboard for WebClipboard { |
| 27 | fn write(&mut self, contents: ClipboardContent) { |
| 28 | match create_item_list(&contents) { |
| 29 | Ok(item_list) => { |
| 30 | // This returns a Promise, which succeeds iff the copy succeeds. There's nothing we can do |
| 31 | // if the copy fails, though, and this API doesn't support async, so we just ignore the |
| 32 | // promise. It's not necessary to hold a reference to the promise for the copy to succeed. |
| 33 | let _ = self.inner.write(&item_list); |
| 34 | } |
| 35 | Err(error) => { |
| 36 | // Fall back to just writing plain text. |
| 37 | // ClipboardItems are not supported in Firefox yet. |
| 38 | log::warn!("Failed to construct clipboard data: {error:?}"); |
| 39 | let _ = self.inner.write_text(&contents.plain_text); |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | fn read(&mut self) -> ClipboardContent { |
| 45 | std::mem::take(&mut self.saved_content) |
| 46 | } |
| 47 | |
| 48 | fn save(&mut self, content: ClipboardContent) { |
| 49 | self.saved_content = content; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | fn create_item_list(contents: &ClipboardContent) -> Result<Array, JsValue> { |
| 54 | // The Clipboard.write method |
| 55 | // (https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write) |
| 56 | // requires an array of ClipboardItem objects |
| 57 | // (https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem). |
| 58 | // This function constructs a single element array containing a ClipboardItem, which contains |
| 59 | // both plain text and html data that's being copied. |
| 60 | |
| 61 | let items = Object::new(); |
| 62 | |
| 63 | // We always have plain text data. |
| 64 | let text_blob = create_blob(&contents.plain_text, "text/plain")?; |
| 65 | js_sys::Reflect::set(&items, &JsValue::from_str("text/plain"), &text_blob)?; |
| 66 | |
| 67 | // We sometimes have html data. |
| 68 | if let Some(html) = &contents.html { |
| 69 | let html_blob = create_blob(html, "text/html")?; |
| 70 | js_sys::Reflect::set(&items, &JsValue::from_str("text/html"), &html_blob)?; |
| 71 | } |
| 72 | |
| 73 | // web_sys doesn't have this constructor, so we have to do things the hard way. |
| 74 | let clipboard_item_constructor: js_sys::Function = js_sys::Reflect::get( |
| 75 | &JsValue::from(gloo::utils::window()), |
| 76 | &JsValue::from_str("ClipboardItem"), |
| 77 | )? |
| 78 | .dyn_into()?; |
| 79 | let clipboard_item = |
| 80 | js_sys::Reflect::construct(&clipboard_item_constructor, &Array::of1(&items))?; |
| 81 | |
| 82 | // Write the ClipboardItem to the clipboard |
| 83 | let item_list = Array::new(); |
| 84 | item_list.push(&clipboard_item); |
| 85 | |
| 86 | Ok(item_list) |
| 87 | } |
| 88 | |
| 89 | fn create_blob(contents: &str, type_: &str) -> Result<Blob, JsValue> { |
| 90 | // See the JS Blob constructor docs for more info: |
| 91 | // https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob |
| 92 | let blob_parts = Array::new(); |
| 93 | blob_parts.push(&JsValue::from_str(contents)); |
| 94 | let blob_opts = BlobPropertyBag::new(); |
| 95 | blob_opts.set_type(type_); |
| 96 | Blob::new_with_str_sequence_and_options(&blob_parts, &blob_opts) |
| 97 | } |
| 98 |