yew_hooks/hooks/use_clipboard.rs
1use std::rc::Rc;
2
3use gloo::file::Blob as GlooBlob;
4use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array};
5use wasm_bindgen::UnwrapThrowExt;
6use wasm_bindgen::{prelude::*, JsCast, JsValue};
7use web_sys::Blob;
8use yew::prelude::*;
9
10use super::{use_state_ptr_eq, UseStatePtrEqHandle};
11use crate::web_sys_ext::{window, ClipboardItem};
12
13/// State handle for the [`use_clipboard`] hook.
14pub struct UseClipboardHandle {
15 /// The text that is read from or written to clipboard.
16 pub text: UseStatePtrEqHandle<Option<String>>,
17 /// The bytes that is read from or written to clipboard.
18 pub bytes: UseStatePtrEqHandle<Option<Vec<u8>>>,
19 /// The mime type of the bytes that is read from or written to clipboard.
20 pub bytes_mime_type: UseStatePtrEqHandle<Option<String>>,
21 /// If the content is already copied.
22 pub copied: UseStatePtrEqHandle<bool>,
23 /// If the clipboard is supported.
24 pub is_supported: Rc<bool>,
25
26 write_text: Rc<dyn Fn(String)>,
27 write: Rc<dyn Fn(Vec<u8>, Option<String>)>,
28 read_text: Rc<dyn Fn()>,
29 read: Rc<dyn Fn()>,
30}
31
32impl UseClipboardHandle {
33 /// Read bytes from clipboard.
34 pub fn read(&self) {
35 (self.read)();
36 }
37
38 /// Read text from clipboard.
39 pub fn read_text(&self) {
40 (self.read_text)();
41 }
42
43 /// Write bytes with mime type to clipboard.
44 pub fn write(&self, data: Vec<u8>, mime_type: Option<String>) {
45 (self.write)(data, mime_type);
46 }
47
48 /// Write text to clipboard.
49 pub fn write_text(&self, data: String) {
50 (self.write_text)(data);
51 }
52}
53
54impl Clone for UseClipboardHandle {
55 fn clone(&self) -> Self {
56 Self {
57 text: self.text.clone(),
58 bytes: self.bytes.clone(),
59 bytes_mime_type: self.bytes_mime_type.clone(),
60 is_supported: self.is_supported.clone(),
61 copied: self.copied.clone(),
62
63 write_text: self.write_text.clone(),
64 write: self.write.clone(),
65 read_text: self.read_text.clone(),
66 read: self.read.clone(),
67 }
68 }
69}
70
71/// This hook is used to read from or write to clipboard for text or bytes.
72/// e.g. copy plain text or copy `image/png` file to clipboard.
73///
74/// # Example
75///
76/// ```rust
77/// # use yew::prelude::*;
78/// #
79/// use yew_hooks::prelude::*;
80///
81/// #[function_component(UseClipboard)]
82/// fn clipboard() -> Html {
83/// let clipboard = use_clipboard();
84///
85/// let onclick_write_text = {
86/// let clipboard = clipboard.clone();
87/// Callback::from(move |_| {
88/// clipboard.write_text("hello world!".to_owned());
89/// })
90/// };
91/// let onclick_read_text = {
92/// let clipboard = clipboard.clone();
93/// Callback::from(move |_| {
94/// clipboard.read_text();
95/// })
96/// };
97/// let onclick_write_bytes = {
98/// let clipboard = clipboard.clone();
99/// Callback::from(move |_| {
100/// clipboard.write(vec![], Some("image/png".to_owned()));
101/// })
102/// };
103/// let onclick_read_bytes = {
104/// let clipboard = clipboard.clone();
105/// Callback::from(move |_| {
106/// clipboard.read();
107/// })
108/// };
109///
110/// html! {
111/// <div>
112/// <button onclick={onclick_write_text}>{ "Write text to clipboard" }</button>
113/// <button onclick={onclick_read_text}>{ "Read text from clipboard" }</button>
114/// <button onclick={onclick_write_bytes}>{ "Write bytes to clipboard" }</button>
115/// <button onclick={onclick_read_bytes}>{ "Read bytes from clipboard" }</button>
116/// <p>{ format!("Current text: {:?}", *clipboard.text) }</p>
117/// <p>{ format!("Copied: {:?}", *clipboard.copied) }</p>
118/// <p>{ format!("Is supported: {:?}", *clipboard.is_supported) }</p>
119/// <p>{ format!("Current bytes: {:?}", *clipboard.bytes) }</p>
120/// <p>{ format!("Current bytes mime type: {:?}", *clipboard.bytes_mime_type) }</p>
121/// </div>
122/// }
123/// }
124/// ```
125#[hook]
126pub fn use_clipboard() -> UseClipboardHandle {
127 let text = use_state_ptr_eq(|| None);
128 let bytes = use_state_ptr_eq(|| None);
129 let bytes_mime_type = use_state_ptr_eq(|| None);
130 let is_supported = use_memo((), |_| {
131 window()
132 .expect_throw("Can't find the global Window")
133 .navigator()
134 .clipboard()
135 .is_some()
136 });
137 let copied = use_state_ptr_eq(|| false);
138
139 let clipboard = use_memo((), |_| {
140 window()
141 .expect_throw("Can't find the global Window")
142 .navigator()
143 .clipboard()
144 });
145
146 let write_text = {
147 let clipboard = clipboard.clone();
148 let text = text.clone();
149 let copied = copied.clone();
150 Rc::new(move |data: String| {
151 if let Some(clipboard) = &*clipboard {
152 let text = text.clone();
153 let text2 = text.clone();
154 let copied = copied.clone();
155 let copied2 = copied.clone();
156 let data2 = data.clone();
157 let resolve_closure = Closure::wrap(Box::new(move |_| {
158 text.set(Some(data.clone()));
159 copied.set(true);
160 }) as Box<dyn FnMut(JsValue)>);
161 let reject_closure = Closure::wrap(Box::new(move |_| {
162 text2.set(None);
163 copied2.set(false);
164 }) as Box<dyn FnMut(JsValue)>);
165 let _ = clipboard
166 .write_text(&data2)
167 .then2(&resolve_closure, &reject_closure);
168 resolve_closure.forget();
169 reject_closure.forget();
170 }
171 })
172 };
173
174 let write = {
175 let clipboard = clipboard.clone();
176 let bytes = bytes.clone();
177 let bytes_mime_type = bytes_mime_type.clone();
178 let copied = copied.clone();
179 Rc::new(move |data: Vec<u8>, mime_type: Option<String>| {
180 if let Some(clipboard) = &*clipboard {
181 let blob = GlooBlob::new_with_options(&*data, mime_type.as_deref());
182 let object = Object::new();
183 if Reflect::set(
184 &object,
185 &JsValue::from(mime_type.as_deref()),
186 &JsValue::from(blob),
187 )
188 .is_ok()
189 {
190 if let Ok(item) = ClipboardItem::new(&object) {
191 let items = Array::new();
192 items.push(&item);
193 let bytes = bytes.clone();
194 let bytes2 = bytes.clone();
195 let bytes_mime_type = bytes_mime_type.clone();
196 let bytes_mime_type2 = bytes_mime_type.clone();
197 let copied = copied.clone();
198 let copied2 = copied.clone();
199 let resolve_closure = Closure::wrap(Box::new(move |_| {
200 bytes.set(Some(data.clone()));
201 bytes_mime_type.set(mime_type.clone());
202 copied.set(true);
203 })
204 as Box<dyn FnMut(JsValue)>);
205 let reject_closure = Closure::wrap(Box::new(move |_| {
206 bytes2.set(None);
207 bytes_mime_type2.set(None);
208 copied2.set(false);
209 })
210 as Box<dyn FnMut(JsValue)>);
211 let _ = clipboard
212 .write(&items)
213 .then2(&resolve_closure, &reject_closure);
214 resolve_closure.forget();
215 reject_closure.forget();
216 }
217 }
218 }
219 })
220 };
221
222 let read_text = {
223 let clipboard = clipboard.clone();
224 let text = text.clone();
225 Rc::new(move || {
226 if let Some(clipboard) = &*clipboard {
227 let text = text.clone();
228 let text2 = text.clone();
229 let resolve_closure = Closure::wrap(Box::new(move |data: JsValue| {
230 data.as_string().map_or_else(
231 || {
232 text.set(None);
233 },
234 |data| {
235 if data.is_empty() {
236 text.set(None);
237 } else {
238 text.set(Some(data));
239 }
240 },
241 );
242 }) as Box<dyn FnMut(JsValue)>);
243 let reject_closure = Closure::wrap(Box::new(move |_| {
244 text2.set(None);
245 }) as Box<dyn FnMut(JsValue)>);
246 let _ = clipboard
247 .read_text()
248 .then2(&resolve_closure, &reject_closure);
249 resolve_closure.forget();
250 reject_closure.forget();
251 }
252 })
253 };
254
255 let read = {
256 let bytes = bytes.clone();
257 let bytes_mime_type = bytes_mime_type.clone();
258 Rc::new(move || {
259 if let Some(clipboard) = &*clipboard {
260 let bytes = bytes.clone();
261 let bytes2 = bytes.clone();
262 let bytes_mime_type = bytes_mime_type.clone();
263 let bytes_mime_type2 = bytes_mime_type.clone();
264 let resolve_closure = Closure::wrap(Box::new(move |items| {
265 let items = Array::from(&items);
266 let bytes = bytes.clone();
267 for item in items.iter() {
268 item.dyn_into::<ClipboardItem>().map_or_else(
269 |_| {
270 bytes.set(None);
271 bytes_mime_type.set(None);
272 },
273 |item| {
274 for t in item.types().iter() {
275 t.as_string().map_or_else(
276 || {
277 bytes.set(None);
278 bytes_mime_type.set(None);
279 },
280 |t| {
281 let bytes = bytes.clone();
282 let bytes2 = bytes.clone();
283 let bytes_mime_type = bytes_mime_type.clone();
284 let bytes_mime_type2 = bytes_mime_type.clone();
285 let t2 = t.clone();
286 let resolve_closure =
287 Closure::wrap(Box::new(move |blob: JsValue| {
288 blob.dyn_into::<Blob>().map_or_else(
289 |_| {
290 bytes.set(None);
291 bytes_mime_type.set(None);
292 },
293 |blob| {
294 let bytes = bytes.clone();
295 let bytes2 = bytes.clone();
296 let bytes_mime_type =
297 bytes_mime_type.clone();
298 let bytes_mime_type2 =
299 bytes_mime_type.clone();
300 let t = t.clone();
301 let resolve_closure = Closure::wrap(
302 Box::new(move |buffer: JsValue| {
303 buffer
304 .dyn_into::<ArrayBuffer>()
305 .map_or_else(
306 |_| {
307 bytes.set(None);
308 bytes_mime_type.set(None);
309 },
310 |buffer| {
311 let data = Uint8Array::new(
312 &buffer,
313 )
314 .to_vec();
315 bytes.set(Some(data));
316 bytes_mime_type
317 .set(Some(t.clone()));
318 },
319 );
320 })
321 as Box<dyn FnMut(JsValue)>,
322 );
323 let reject_closure =
324 Closure::wrap(Box::new(move |_| {
325 bytes2.set(None);
326 bytes_mime_type2.set(None);
327 })
328 as Box<dyn FnMut(JsValue)>);
329 let _ = blob.array_buffer().then2(
330 &resolve_closure,
331 &reject_closure,
332 );
333 resolve_closure.forget();
334 reject_closure.forget();
335 },
336 );
337 })
338 as Box<dyn FnMut(JsValue)>);
339 let reject_closure = Closure::wrap(Box::new(move |_| {
340 bytes2.set(None);
341 bytes_mime_type2.set(None);
342 })
343 as Box<dyn FnMut(JsValue)>);
344 let _ = item
345 .get_type(&t2)
346 .then2(&resolve_closure, &reject_closure);
347 resolve_closure.forget();
348 reject_closure.forget();
349 },
350 );
351 }
352 },
353 );
354 }
355 }) as Box<dyn FnMut(JsValue)>);
356 let reject_closure = Closure::wrap(Box::new(move |_| {
357 bytes2.set(None);
358 bytes_mime_type2.set(None);
359 }) as Box<dyn FnMut(JsValue)>);
360 let _ = clipboard.read().then2(&resolve_closure, &reject_closure);
361 resolve_closure.forget();
362 reject_closure.forget();
363 }
364 })
365 };
366
367 UseClipboardHandle {
368 text,
369 bytes,
370 bytes_mime_type,
371 copied,
372 is_supported,
373 write_text,
374 write,
375 read_text,
376 read,
377 }
378}