1use js_sys::Object;
4use leptos::html::Video;
5use leptos::*;
6use std::sync::Arc;
7
8use wasm_bindgen::prelude::*;
9
10#[wasm_bindgen(module = "/public/qr-scanner-worker.min.js")]
11extern "C" {
12 #[derive(Clone, Debug)]
13 type QrWorker;
14
15 #[wasm_bindgen(method, js_name = createWorker)]
16 fn createWorker(this: &QrWorker);
17}
18
19#[wasm_bindgen(module = "/public/qr-scanner-wrapper.min.js")]
20extern "C" {
21 #[derive(Clone, Debug)]
22 type QrScanner;
23
24 #[wasm_bindgen(constructor, js_name = new)]
25 fn qr_new(
26 video_elem: &web_sys::HtmlVideoElement,
27 callback: &js_sys::Function,
28 options: &JsValue,
29 ) -> QrScanner;
30
31 #[wasm_bindgen(method, js_name = start)]
32 fn qr_start(this: &QrScanner);
33 #[wasm_bindgen(method, js_name = stop)]
34 fn qr_stop(this: &QrScanner);
35 #[wasm_bindgen(method, js_name = destroy)]
36 fn qr_destroy(this: &QrScanner);
37}
38
39#[wasm_bindgen]
40pub fn process_js_value_with_cast(js_value: JsValue) -> Result<String, JsValue> {
41 if let Ok(obj) = js_value.dyn_into::<Object>() {
43 if let Ok(data) = js_sys::Reflect::get(&obj, &JsValue::from_str("data")) {
45 if let Some(data_string) = data.as_string() {
47 return Ok(data_string);
48 }
49 }
50 }
51
52 Err(JsValue::from_str("Failed to extract the data properly"))
54}
55
56#[component]
57pub fn Scan<A, F>(
58 active: A,
59 on_scan: F,
60 class: &'static str,
61 video_class: &'static str,
62) -> impl IntoView
63where
64 A: SignalGet<Value = bool> + 'static,
65 F: Fn(String) + 'static,
66{
67 let video_ref = create_node_ref::<Video>();
68 let (error, set_error) = create_signal(None);
69
70 let o_scanner: StoredValue<Option<QrScanner>> = store_value(None);
71
72 let on_scan = Arc::new(on_scan);
73 let scan = move || {
74 if let Some(video) = video_ref.get() {
75 let on_scan_inner = on_scan.clone();
76 let callback = Closure::wrap(Box::new(move |result: JsValue| {
77 match process_js_value_with_cast(result) {
78 Ok(data) => {
79 on_scan_inner(data);
80 }
81 Err(e) => {
82 let error_message = format!("Error: {:?}", e);
83 set_error.set(Some(error_message));
84 }
85 };
86 }) as Box<dyn Fn(JsValue)>);
87
88 let options = js_sys::Object::new();
92 js_sys::Reflect::set(
93 &options,
94 &JsValue::from_str("returnDetailedScanResult"),
95 &JsValue::from_bool(true),
96 )
97 .unwrap();
98
99 let scanner = QrScanner::qr_new(&video, callback.as_ref().unchecked_ref(), &options);
100 scanner.qr_start();
101 callback.forget();
102
103 o_scanner.set_value(Some(scanner));
104 }
105 };
106
107 let cancel = move || {
108 if let Some(scanner) = o_scanner.get_value() {
109 scanner.qr_stop();
110 scanner.qr_destroy();
111 o_scanner.set_value(None);
112 }
113 };
114
115 create_effect(move |_| {
116 if active.get() {
117 scan();
118 } else {
119 cancel();
120 }
121 });
122
123 view! {
124 <div class=class>
125 <video _ref=video_ref class=video_class style="object-fit: cover;"></video>
126 <Show
127 when=move || error.get().is_some()
128 fallback=|| {
129 view! { "" }
130 }
131 >
132 <p>{error.get()}</p>
133 </Show>
134 </div>
135 }
136}