Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add use_clipboard hook #10

Merged
merged 1 commit into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ fn counter() -> Html {
- `use_debounce_effect` - debounces an effect.
- `use_throttle` - throttles a function.
- `use_throttle_effect` - throttles an effect.
- `use_clipboard` - reads from or writes to clipboard for text/bytes.

### Lifecycles

Expand Down
3 changes: 2 additions & 1 deletion crates/yew-hooks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "yew-hooks"
version = "0.1.53"
version = "0.1.54"
edition = "2018"
authors = ["Jet Li <jing.i.qin@icloud.com>"]
categories = ["gui", "wasm", "web-programming"]
Expand Down Expand Up @@ -50,6 +50,7 @@ features = [
"TouchList",
"HtmlLinkElement",
"HtmlCollection",
"Blob",
]

[dev-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions crates/yew-hooks/src/hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod use_async;
mod use_before_unload;
mod use_click_away;
mod use_clipboard;
mod use_counter;
mod use_debounce;
mod use_debounce_effect;
Expand Down Expand Up @@ -54,6 +55,7 @@ mod use_window_size;
pub use use_async::*;
pub use use_before_unload::*;
pub use use_click_away::*;
pub use use_clipboard::*;
pub use use_counter::*;
pub use use_debounce::*;
pub use use_debounce_effect::*;
Expand Down
355 changes: 355 additions & 0 deletions crates/yew-hooks/src/hooks/use_clipboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
use std::rc::Rc;

use gloo::file::Blob as GlooBlob;
use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array};
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::Blob;
use yew::prelude::*;

use super::{use_state_ptr_eq, UseStatePtrEqHandle};
use crate::web_sys_ext::{window, ClipboardItem};

/// State handle for the [`use_clipboard`] hook.
pub struct UseClipboardHandle {
/// The text that is read from or written to clipboard.
pub text: UseStatePtrEqHandle<Option<String>>,
/// The bytes that is read from or written to clipboard.
pub bytes: UseStatePtrEqHandle<Option<Vec<u8>>>,
/// The mime type of the bytes that is read from or written to clipboard.
pub bytes_mime_type: UseStatePtrEqHandle<Option<String>>,
/// If the content is already copied.
pub copied: UseStatePtrEqHandle<bool>,
/// If the clipboard is supported.
pub is_supported: Rc<bool>,

write_text: Rc<dyn Fn(String)>,
write: Rc<dyn Fn(Vec<u8>, Option<String>)>,
read_text: Rc<dyn Fn()>,
read: Rc<dyn Fn()>,
}

impl UseClipboardHandle {
/// Read bytes from clipboard.
pub fn read(&self) {
(self.read)()
}

/// Read text from clipboard.
pub fn read_text(&self) {
(self.read_text)()
}

/// Write bytes with mime type to clipboard.
pub fn write(&self, data: Vec<u8>, mime_type: Option<String>) {
(self.write)(data, mime_type)
}

/// Write text to clipboard.
pub fn write_text(&self, data: String) {
(self.write_text)(data)
}
}

impl Clone for UseClipboardHandle {
fn clone(&self) -> Self {
Self {
text: self.text.clone(),
bytes: self.bytes.clone(),
bytes_mime_type: self.bytes_mime_type.clone(),
is_supported: self.is_supported.clone(),
copied: self.copied.clone(),

write_text: self.write_text.clone(),
write: self.write.clone(),
read_text: self.read_text.clone(),
read: self.read.clone(),
}
}
}

/// This hook is used to read from or write to clipboard for text or bytes.
/// e.g. copy plain text or copy `image/png` file to clipboard.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::use_clipboard;
///
/// #[function_component(UseClipboard)]
/// fn clipboard() -> Html {
/// let clipboard = use_clipboard();
///
/// let onclick_write_text = {
/// let clipboard = clipboard.clone();
/// Callback::from(move |_| {
/// clipboard.write_text("hello world!".to_owned());
/// })
/// };
/// let onclick_read_text = {
/// let clipboard = clipboard.clone();
/// Callback::from(move |_| {
/// clipboard.read_text();
/// })
/// };
/// let onclick_write_bytes = {
/// let clipboard = clipboard.clone();
/// Callback::from(move |_| {
/// clipboard.write(vec![], Some("image/png".to_owned()));
/// })
/// };
/// let onclick_read_bytes = {
/// let clipboard = clipboard.clone();
/// Callback::from(move |_| {
/// clipboard.read();
/// })
/// };
///
/// html! {
/// <div>
/// <button onclick={onclick_write_text}>{ "Write text to clipboard" }</button>
/// <button onclick={onclick_read_text}>{ "Read text from clipboard" }</button>
/// <button onclick={onclick_write_bytes}>{ "Write bytes to clipboard" }</button>
/// <button onclick={onclick_read_bytes}>{ "Read bytes from clipboard" }</button>
/// <p>{ format!("Current text: {:?}", *clipboard.text) }</p>
/// <p>{ format!("Copied: {:?}", *clipboard.copied) }</p>
/// <p>{ format!("Is supported: {:?}", *clipboard.is_supported) }</p>
/// <p>{ format!("Current bytes: {:?}", *clipboard.bytes) }</p>
/// <p>{ format!("Current bytes mime type: {:?}", *clipboard.bytes_mime_type) }</p>
/// </div>
/// }
/// }
/// ```
pub fn use_clipboard() -> UseClipboardHandle {
let text = use_state_ptr_eq(|| None);
let bytes = use_state_ptr_eq(|| None);
let bytes_mime_type = use_state_ptr_eq(|| None);
let is_supported = use_ref(|| {
window()
.expect_throw("Can't find the global Window")
.navigator()
.clipboard()
.is_some()
});
let copied = use_state_ptr_eq(|| false);

let clipboard = use_ref(|| {
window()
.expect_throw("Can't find the global Window")
.navigator()
.clipboard()
});

let write_text = {
let clipboard = clipboard.clone();
let text = text.clone();
let copied = copied.clone();
Rc::new(move |data: String| {
if let Some(clipboard) = &*clipboard {
let text = text.clone();
let text2 = text.clone();
let copied = copied.clone();
let copied2 = copied.clone();
let data2 = data.clone();
let resolve_closure = Closure::wrap(Box::new(move |_| {
text.set(Some(data.clone()));
copied.set(true);
}) as Box<dyn FnMut(JsValue)>);
let reject_closure = Closure::wrap(Box::new(move |_| {
text2.set(None);
copied2.set(false);
}) as Box<dyn FnMut(JsValue)>);
let _ = clipboard
.write_text(&data2)
.then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
}
})
};

let write = {
let clipboard = clipboard.clone();
let bytes = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
let copied = copied.clone();
Rc::new(move |data: Vec<u8>, mime_type: Option<String>| {
if let Some(clipboard) = &*clipboard {
let blob = GlooBlob::new_with_options(&*data, mime_type.as_deref());
let object = Object::new();
if Reflect::set(
&object,
&JsValue::from(mime_type.as_deref()),
&JsValue::from(blob),
)
.is_ok()
{
if let Ok(item) = ClipboardItem::new(&object) {
let items = Array::new();
items.push(&item);
let bytes = bytes.clone();
let bytes2 = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
let bytes_mime_type2 = bytes_mime_type.clone();
let copied = copied.clone();
let copied2 = copied.clone();
let resolve_closure = Closure::wrap(Box::new(move |_| {
bytes.set(Some(data.clone()));
bytes_mime_type.set(mime_type.clone());
copied.set(true)
})
as Box<dyn FnMut(JsValue)>);
let reject_closure = Closure::wrap(Box::new(move |_| {
bytes2.set(None);
bytes_mime_type2.set(None);
copied2.set(false);
})
as Box<dyn FnMut(JsValue)>);
let _ = clipboard
.write(&items)
.then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
}
}
}
})
};

let read_text = {
let clipboard = clipboard.clone();
let text = text.clone();
Rc::new(move || {
if let Some(clipboard) = &*clipboard {
let text = text.clone();
let text2 = text.clone();
let resolve_closure = Closure::wrap(Box::new(move |data: JsValue| {
if let Some(data) = data.as_string() {
if data.is_empty() {
text.set(None);
} else {
text.set(Some(data));
}
} else {
text.set(None);
}
}) as Box<dyn FnMut(JsValue)>);
let reject_closure = Closure::wrap(Box::new(move |_| {
text2.set(None);
}) as Box<dyn FnMut(JsValue)>);
let _ = clipboard
.read_text()
.then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
}
})
};

let read = {
let bytes = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
Rc::new(move || {
if let Some(clipboard) = &*clipboard {
let bytes = bytes.clone();
let bytes2 = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
let bytes_mime_type2 = bytes_mime_type.clone();
let resolve_closure = Closure::wrap(Box::new(move |items| {
let items = Array::from(&items);
let bytes = bytes.clone();
for item in items.iter() {
if let Ok(item) = item.dyn_into::<ClipboardItem>() {
for t in item.types().iter() {
if let Some(t) = t.as_string() {
let bytes = bytes.clone();
let bytes2 = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
let bytes_mime_type2 = bytes_mime_type.clone();
let t2 = t.clone();
let resolve_closure =
Closure::wrap(Box::new(move |blob: JsValue| {
if let Ok(blob) = blob.dyn_into::<Blob>() {
let bytes = bytes.clone();
let bytes2 = bytes.clone();
let bytes_mime_type = bytes_mime_type.clone();
let bytes_mime_type2 = bytes_mime_type.clone();
let t = t.clone();
let resolve_closure = Closure::wrap(Box::new(
move |buffer: JsValue| {
if let Ok(buffer) =
buffer.dyn_into::<ArrayBuffer>()
{
let data =
Uint8Array::new(&buffer).to_vec();
bytes.set(Some(data));
bytes_mime_type.set(Some(t.clone()));
} else {
bytes.set(None);
bytes_mime_type.set(None);
}
},
)
as Box<dyn FnMut(JsValue)>);
let reject_closure =
Closure::wrap(Box::new(move |_| {
bytes2.set(None);
bytes_mime_type2.set(None);
})
as Box<dyn FnMut(JsValue)>);
let _ = blob
.array_buffer()
.then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
} else {
bytes.set(None);
bytes_mime_type.set(None);
}
})
as Box<dyn FnMut(JsValue)>);
let reject_closure = Closure::wrap(Box::new(move |_| {
bytes2.set(None);
bytes_mime_type2.set(None);
})
as Box<dyn FnMut(JsValue)>);
let _ =
item.get_type(&t2).then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
} else {
bytes.set(None);
bytes_mime_type.set(None);
}
}
} else {
bytes.set(None);
bytes_mime_type.set(None);
}
}
}) as Box<dyn FnMut(JsValue)>);
let reject_closure = Closure::wrap(Box::new(move |_| {
bytes2.set(None);
bytes_mime_type2.set(None);
}) as Box<dyn FnMut(JsValue)>);
let _ = clipboard.read().then2(&resolve_closure, &reject_closure);
resolve_closure.forget();
reject_closure.forget();
}
})
};

UseClipboardHandle {
text,
bytes,
bytes_mime_type,
is_supported,
copied,
write_text,
write,
read_text,
read,
}
}
Loading