yew_nested_router/
history.rs

1use gloo_events::EventListener;
2use std::cell::RefCell;
3use std::rc::{Rc, Weak};
4use wasm_bindgen::JsValue;
5
6thread_local! {
7    static INSTANCE: RefCell<InnerHistory> = RefCell::new(InnerHistory::new());
8}
9
10/// Handle to a history listener.
11///
12/// Disposes the listener when dropped.
13pub struct HistoryListener {
14    _callback: Rc<CallbackFn>,
15}
16
17pub struct History;
18
19impl History {
20    /// Subscribe to events of the browser history.
21    ///
22    /// This will receive events when popping items from the stack, as well as changes triggered by calling
23    /// [`History::push_state`].
24    #[must_use = "The listener will only be active for as long as the returned instance exists."]
25    pub fn listener<F: Fn() + 'static>(f: F) -> HistoryListener {
26        INSTANCE.with(|instance| instance.borrow_mut().listener(f))
27    }
28
29    /// Push a new state to the history stack.
30    ///
31    /// This will send out events and update the browser's history. Ultimately calling
32    /// [`web_sys::History::push_state_with_url`].
33    pub fn push_state(state: JsValue, url: &str) -> Result<(), JsValue> {
34        INSTANCE.with(|instance| instance.borrow_mut().push_state(state, url))
35    }
36    /// Replace current state in the history stack
37    ///
38    /// This will send out events and update the browser's history. Ultimately calling
39    /// [`web_sys::History::replace_state_with_url`].
40    pub fn replace_state(state: JsValue, url: &str) -> Result<(), JsValue> {
41        INSTANCE.with(|instance| instance.borrow_mut().replace_state(state, url))
42    }
43}
44
45type CallbackFn = dyn Fn() + 'static;
46
47#[derive(Default)]
48struct Listeners {
49    listeners: Vec<Weak<CallbackFn>>,
50}
51
52impl Listeners {
53    fn add(&mut self, listener: Weak<CallbackFn>) {
54        self.listeners.push(listener);
55    }
56
57    fn notify(&mut self) {
58        log::info!("Notify listeners");
59
60        let mut new = vec![];
61
62        for listener in &mut self.listeners {
63            if let Some(cb) = listener.upgrade() {
64                (*cb)();
65                new.push(listener.clone());
66            }
67        }
68
69        self.listeners = new;
70    }
71}
72
73struct InnerHistory {
74    _event: EventListener,
75    listeners: Rc<RefCell<Listeners>>,
76}
77
78impl InnerHistory {
79    fn new() -> Self {
80        let listeners = Rc::new(RefCell::new(Listeners::default()));
81        let _event = {
82            let listeners = listeners.clone();
83            EventListener::new(&gloo_utils::window(), "popstate", move |_| {
84                listeners.borrow_mut().notify();
85            })
86        };
87
88        Self { listeners, _event }
89    }
90
91    fn push_state(&mut self, state: JsValue, url: &str) -> Result<(), JsValue> {
92        let result = gloo_utils::history().push_state_with_url(&state, "", Some(url));
93        self.listeners.borrow_mut().notify();
94        result
95    }
96
97    fn replace_state(&mut self, state: JsValue, url: &str) -> Result<(), JsValue> {
98        let result = gloo_utils::history().replace_state_with_url(&state, "", Some(url));
99        self.listeners.borrow_mut().notify();
100        result
101    }
102
103    fn listener<F: Fn() + 'static>(&mut self, f: F) -> HistoryListener {
104        let callback = Rc::new(f) as Rc<CallbackFn>;
105        self.listeners.borrow_mut().add(Rc::downgrade(&callback));
106        HistoryListener {
107            _callback: callback,
108        }
109    }
110}