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
122
123
124
125
126
127
128
129
130
131
132
133
//! The browser History API.

//!

//! Navigate back and forth through the user's history, and manipulate the contents of the history stack

//!

//! [Read more](https://developer.mozilla.org/en-US/docs/Web/API/History).

//!

//! Notes: https://yoshuawuyts-old.netlify.com/history


use std::pin::Pin;
use std::task::{Context, Poll};

use crate::prelude::*;

use async_std::prelude::Stream;
use pin_project::pin_project;
use wasm_bindgen::JsValue;

/// The Web History API.

///

/// The History API represents the URL and forward/backward buttons in the

/// browser. It's modeled as a stack where URLs can be pushed/popped, and a

/// cursor can be moved forward/backward along the stack.

///

/// `History` also implements `Stream` which returns a url every time the

/// cursor is moved along the stack. The only time this event is not triggered is

/// when the cursor moves because of a new entry being pushed onto the stack.

#[pin_project]
#[derive(Debug)]
pub struct History {
    inner: web_sys::History,
    #[pin]
    listener: Option<crate::events::EventStream>,
}

impl History {
    /// Create a new instance of `History`.

    pub fn new() -> Self {
        Self {
            inner: crate::utils::window().history().unwrap_throw(),
            listener: None,
        }
    }

    /// Returns the number of urls in the History stack.

    pub fn len(&self) -> usize {
        self.inner.length().unwrap_throw() as usize
    }

    /// Get the current url from the History stack.

    pub fn current(&self) -> String {
        crate::utils::document()
            .location()
            .unwrap_throw()
            .href()
            .unwrap_throw()
    }

    /// Push a new url onto the history stack.

    pub fn push(&self, url: &str) {
        let null = JsValue::null();
        self.inner
            .push_state_with_url(&null, "", Some(url))
            .unwrap_throw();
    }

    /// Pop a url from the history stack.

    pub fn pop(&self) {
        self.inner.back().unwrap_throw();
    }

    /// Replace the url currently on the stack with another url.

    pub fn replace(&self, url: &str) {
        let null = JsValue::null();
        self.inner
            .replace_state_with_url(&null, "", Some(url))
            .unwrap_throw();
    }

    /// Move the cursor forwards and backwards through the History stack

    /// depending on the value of the parameter.

    ///

    /// Passing a value of `0` will reload the page.

    pub async fn move_by(&self, delta: i32) {
        let fut = crate::utils::window().once("popstate");
        self.inner.go_with_delta(delta).unwrap_throw();
        fut.await;
    }

    /// Moves the cursor to the next url in the History stack.

    pub async fn move_next(&self) {
        let fut = crate::utils::window().once("popstate");
        self.inner.forward().unwrap_throw();
        fut.await;
    }

    /// Moves the cursor to the previous url in the History stack.

    pub async fn move_prev(&self) {
        let fut = crate::utils::window().once("popstate");
        self.inner.back().unwrap_throw();
        fut.await;
    }

    /// Reload the current url, like the Refresh button.

    pub async fn reload(&self) {
        let fut = crate::utils::window().once("popstate");
        self.inner.go().unwrap_throw();
        fut.await;
    }
}

impl Stream for History {
    type Item = String;

    /// Return URLs generated by moving the cursor on the stack. This is

    /// equivalent to listening for the `"popstate"` event.

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        if self.listener.is_none() {
            self.listener = Some(crate::utils::window().on("popstate"));
        }

        let listener = self.project().listener.get_mut().as_mut().unwrap_throw();
        match Pin::new(listener).poll_next(cx) {
            Poll::Ready(_) => Poll::Ready(Some(
                crate::utils::document()
                    .location()
                    .unwrap_throw()
                    .href()
                    .unwrap_throw(),
            )),
            Poll::Pending => Poll::Pending,
        }
    }
}