1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use super::*;
use crate::*;
use forky_core::*;
use js_sys::Function;
use js_sys::Promise;
use std::rc::Rc;
use wasm_bindgen::convert::FromWasmAbi;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::window;
use web_sys::EventTarget;

pub struct HtmlEventListenerInner<T> {
	closure: FnClosure<T>,
	target: EventTarget,
	name: &'static str,
}

impl<T> Drop for HtmlEventListenerInner<T> {
	fn drop(&mut self) {
		let closure: &Function = self.closure.as_ref().unchecked_ref();
		self.target
			.remove_event_listener_with_callback(self.name, closure)
			.anyhow()
			.ok_or(|e| web_sys::console::error_1(&format!("{:?}", e).into()));
	}
}

/// Event listener that unsubscribes on drop.
/// It stores an `Rc<Inner>` so can be safely cloned.
#[derive(Clone)]
pub struct HtmlEventListener<T>(pub Rc<HtmlEventListenerInner<T>>);

impl<T> HtmlEventListener<T> {
	#[must_use]
	pub fn new<F>(name: &'static str, f: F) -> Self
	where
		F: FnMut(T) + 'static,
		T: FromWasmAbi + 'static,
	{
		Self::new_with_target(name, f, window().unwrap().unchecked_into())
	}
	#[must_use]
	pub fn new_with_target<F>(
		name: &'static str,
		f: F,
		target: EventTarget,
	) -> Self
	where
		F: FnMut(T) + 'static,
		T: FromWasmAbi + 'static,
	{
		let closure = Closure::from_func(f);
		// let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut(_)>);
		target
			.add_event_listener_with_callback(
				name,
				closure.as_ref().unchecked_ref(),
			)
			.unwrap();
		Self(Rc::new(HtmlEventListenerInner {
			target,
			name,
			closure,
		}))
	}
	pub fn forget(self) { std::mem::forget(self); }
}


impl HtmlEventListener<JsValue> {
	pub async fn wait(name: &'static str) -> JsValue {
		Self::wait_with_target(name, window().unwrap().unchecked_into()).await
	}
	pub async fn wait_with_target(
		name: &'static str,
		target: EventTarget,
	) -> JsValue {
		let listener: RcCell<Option<HtmlEventListener<JsValue>>> = rccell(None);

		let listener2 = listener.clone();
		let promise = Promise::new(&mut move |resolve, _reject| {
			let target = target.clone();
			*listener2.borrow_mut() =
				Some(HtmlEventListener::<JsValue>::new_with_target(
					name,
					move |value| {
						resolve.call1(&JsValue::NULL, &value).unwrap();
					},
					target,
				));
		});
		JsFuture::from(promise).await.unwrap()
	}
	// pub async fn wait_with_target_and_while_listening(
	// 	name: &'static str,
	// 	target: EventTarget,
	// 	mut while_listening: impl FnMut() + 'static,
	// ) -> JsValue {
	// 	let listener: RcCell<Option<HtmlEventListener<JsValue>>> = rccell(None);

	// 	let listener2 = listener.clone();
	// 	let promise = Promise::new(&mut move |resolve, _reject| {
	// 		let target = target.clone();
	// 		*listener2.borrow_mut() =
	// 			Some(HtmlEventListener::<JsValue>::new_with_target(
	// 				name,
	// 				move |value| {
	// 					resolve.call1(&JsValue::NULL, &value).unwrap();
	// 				},
	// 				target,
	// 			));
	// 		while_listening();
	// 	});
	// 	let result = JsFuture::from(promise).await.unwrap();
	// 	drop(listener);
	// 	result
	// }
}