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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Middleware library that roost-client and roost depend upon in order
//! to create instances of Roost applications.
//!
//! Ultimately, managing the states, routes, and views of a Roost application.

#![feature(proc_macro_hygiene)]
// The compiler shows the route handlers within this file as unreachable
#![allow(unreachable_code)]

use std::rc::Rc;
use std::cell::RefCell;

use wasm_bindgen;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen::prelude::*;

use router_rs::prelude::*;
use virtual_dom_rs::prelude::*;
use virtual_dom_rs::VirtualNode;

mod store;
mod views;
pub mod state;
pub use crate::state::*;
pub use crate::store::*;
use crate::views::EntryView;

#[wasm_bindgen]
extern "C" {
    /// index.html's JS downloadJson function imported as download_json in Rust
    #[wasm_bindgen(js_name = "downloadJson")]
    pub fn download_json(path: &str, callback: &js_sys::Function);
}

/// Used for creating an instance of a Roost application
pub struct App {
    /// Manages the state wrapper and routing of the application.
    ///
    /// Having a this field as public allows for borrowing the application
    /// state directly from the client and server.
    pub store: Rc<RefCell<Store>>,
    /// Handles the different routes of Roost
    router: Rc<Router>,
}

/// The included functions of a Roost application instance
impl App {
    /// Returns an application with a path that is set based on the provided
    /// string and initial state values found in `State::new()`
    pub fn new(path: String) -> App {
        // Initial state is created
        let state = State::new();
        let store = Rc::new(RefCell::new(Store::new(state)));

        // Path is set
        store.borrow_mut().msg(&Msg::SetPath(path));

        // Router is created and set
        let router = make_router(Rc::clone(&store));
        store.borrow_mut().set_router(Rc::clone(&router));

        App { store, router }
    }

    /// Returns an application based on the provided JSON string.
    /// The provided JSON string should be a serialized State.
    pub fn from_state_json(json: &str) -> App {
        // State is set based on the given JSON
        let state = State::from_json(json);
        let store = Rc::new(RefCell::new(Store::new(state)));

        // Router is created and set
        let router = make_router(Rc::clone(&store));
        store.borrow_mut().set_router(Rc::clone(&router));

        App { store, router }
    }

    /// Retruns a virtual dom based on the current state of our application
    pub fn render(&self) -> VirtualNode {
        self.router
            .view(self.store.borrow().path())
            .expect("No route found")
    }
}

// These route handlers are reachable and are passed into the create_routes! macro

/// The route handler of the root repository path without a specified patch.
/// The selected patch will be the current state of the repository or the latest patch.
#[route(
    path = "/",
    on_visit = download_root_json
)]
fn root_route(store: Provided<Rc<RefCell<Store>>>) -> VirtualNode {
    EntryView::new(Rc::clone(&store)).render()
}

/// The route handler of a specifed repository path without a specific patch.
/// The selected patch will be the current state of the repository or the latest patch.
#[route(
    path = "/path/:_encoded_path",
    on_visit = download_entry_json
)]
fn entry_route(store: Provided<Rc<RefCell<Store>>>, _encoded_path: String) -> VirtualNode {
    EntryView::new(Rc::clone(&store)).render()
}

/// The route handler of the root repository path with a specific patch.
#[route(
    path = "/patch/:_selected_patch",
    on_visit = download_root_patch_json
)]
fn root_patch_route(store: Provided<Rc<RefCell<Store>>>, _selected_patch: String) -> VirtualNode {
    EntryView::new(Rc::clone(&store)).render()
}

/// The route handler of a specified repository path with a specific patch.
#[route(
    path = "/patch/:_selected_patch/:_encoded_path",
    on_visit = download_patch_json
)]
fn patch_route(
    store: Provided<Rc<RefCell<Store>>>,
    _selected_patch: String,
    _encoded_path: String,
) -> VirtualNode {
    EntryView::new(Rc::clone(&store)).render()
}

/// The `on_visit` value of the `root_route`, which fetches the JSON content of the application
/// state for that specific route path.
fn download_root_json(store: Provided<Rc<RefCell<Store>>>) {
    download_patch_json(store, String::new(), String::new());
}
/// The `on_visit` value of the `entry_route`, which fetches the JSON content of the application
/// state for that specific route path.
fn download_entry_json(store: Provided<Rc<RefCell<Store>>>, _encoded_path: String) {
    download_patch_json(store, _encoded_path, String::new());
}
/// The `on_visit` value of the `root_patch_route`, which fetches the JSON content of the application
/// state for that specific route path.
fn download_root_patch_json(store: Provided<Rc<RefCell<Store>>>, _selected_patch: String) {
    download_patch_json(store, String::new(), _selected_patch);
}
/// The `on_visit` value of the `patch_route`, which fetches the JSON content of the application
/// state for that specific route path.
fn download_patch_json(
    store: Provided<Rc<RefCell<Store>>>,
    _selected_patch: String,
    _encoded_path: String,
) {
    // An animation frame is requested due to store already being mutably
    // borrowed, since this method will be called from the `Store.msg` function.
    //
    // TODO: Do this in `Store.msg` instead of needing to do it in every on_visit callback
    let raf_closure = Closure::wrap(Box::new(move || {
        let store = Rc::clone(&store);
        let mut path = store.borrow().path().to_string();
        if path.ends_with("/") {
            path.push_str("json");
        } else {
            path.push_str("/json");
        }

        let callback = Closure::wrap(Box::new(move |json: JsValue| {
            store.borrow_mut().msg(&Msg::SetEntryContentsJson(json));
        }) as Box<FnMut(JsValue)>);

        download_json(&path, callback.as_ref().unchecked_ref());

        // TODO: Store and drop the callback instead of leaking memory.
        callback.forget();
    }) as Box<FnMut()>);

    web_sys::window()
        .unwrap()
        .request_animation_frame(raf_closure.as_ref().unchecked_ref())
        .unwrap();

    // TODO: We don't want to repeatedly forget this closure and should instead figure out a place
    // to store it.
    // Maybe make our `Store`'s msg handler for Msg::SetPath call `on_visit` inside of a RAF..
    raf_closure.forget();
}

/// Creates the router for application instances from route handlers
fn make_router(store: Rc<RefCell<Store>>) -> Rc<Router> {
    // Create new router from given store
    let mut router = Router::default();
    router.provide(store);

    // Set the route handlers
    router.set_route_handlers(create_routes![
        root_route,
        entry_route,
        root_patch_route,
        patch_route
    ]);
    Rc::new(router)
}