squark 0.7.1

Virtual DOM implemention and application definition inspired from HyperApp
Documentation
use rand::prelude::*;
use std::cell::{Cell, RefCell};
use std::fmt::Debug;
use rustc_hash::FxHashMap;
use std::rc::Rc;
use futures::Future;
use serde::Serialize;

mod vdom;

pub use crate::vdom::{Node, Element, Diff, View, HandlerArg, AttributeValue, Child};
use crate::vdom::{HandlerFunction, HandlerMap};

thread_local! {
    static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
}

pub trait App: 'static + Clone + Default {
    type State: Clone + Debug + PartialEq + 'static;
    type Action: Clone + Debug + 'static;

    fn reducer(&self, state: Self::State, action: Self::Action) -> (Self::State, Task<Self::Action>);

    fn view(&self, state: Self::State) -> View<Self::Action>;
}

pub fn handler<A, F>(f: F) -> (String, HandlerFunction<A>)
where
    F: Fn(HandlerArg) -> Option<A> + 'static,
{
    (uuid(), Box::new(f))
}

#[derive(Clone)]
pub struct Env<A: App> {
    app: A,
    state: Rc<RefCell<A::State>>,
    node: Rc<RefCell<Node>>,
    handler_map: Rc<RefCell<HandlerMap<A::Action>>>,
    scheduled: Rc<Cell<bool>>,
}

impl<A: App> Env<A> {
    pub fn new(state: A::State) -> Env<A> {
        Env {
            app: A::default(),
            state: Rc::new(RefCell::new(state)),
            node: Rc::new(RefCell::new(Node::Null)),
            handler_map: Rc::new(RefCell::new(FxHashMap::default())),
            scheduled: Rc::new(Cell::new(false)),
        }
    }

    fn get_state(&self) -> A::State {
        self.state.borrow().to_owned()
    }

    fn set_state(&self, state: A::State) {
        *self.state.borrow_mut() = state;
    }

    fn get_node(&self) -> Node {
        self.node.borrow().to_owned()
    }

    fn set_node(&self, node: Node) {
        *self.node.borrow_mut() = node;
    }

    fn pop_handler(&self, id: &str) -> Option<HandlerFunction<A::Action>> {
        self.handler_map.borrow_mut().remove(id)
    }
}

pub struct Task<A>(Vec<Box<Future<Item = A, Error = ()>>>);

impl<A> Default for Task<A> {
    fn default() -> Self {
        Task(vec![])
    }
}

impl<A> Task<A> {
    pub fn empty() -> Self {
        Self::default()
    }

    pub fn into_futures(self) -> Vec<Box<Future<Item = A, Error = ()>>> {
        self.0
    }

    pub fn push(&mut self, future: Box<Future<Item = A, Error = ()>>) {
        self.0.push(future);
    }
}

pub trait Runtime<A: App>: Clone + 'static {
    fn get_env<'a>(&'a self) -> &'a Env<A>;

    fn handle_diff(&self, diff: Diff);

    fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, future: Box<Future<Item = T, Error = E>>);

    fn schedule_render(&self);

    fn run(&self) {
        self.run_with_task(Task::empty());
    }

    fn run_with_task(&self, task: Task<A::Action>) {
        for future in task.into_futures() {
            self.emit_future(future);
        }

        let env = self.get_env();
        env.scheduled.set(false);
        let mut old_node = env.get_node();
        let view = env.app.view(env.get_state());
        *env.handler_map.borrow_mut() = view.handler_map;
        if let Some(diff) = Node::diff(&mut old_node, &view.node, &mut 0) {
            env.set_node(view.node);
            self.handle_diff(diff);
        }
    }

    fn on_action(&self, action: A::Action) {
        let env = self.get_env();

        let old_state = env.get_state();
        let (new_state, task) = env.app.reducer(old_state.to_owned(), action);
        for future in task.into_futures() {
            self.emit_future(future);
        }
        self.set_state(new_state);
    }

    fn set_state(&self, new_state: A::State) {
        let env = self.get_env();
        let old_state = env.get_state();
        if old_state == new_state {
            return;
        }
        env.set_state(new_state);
        if env.scheduled.get() {
            return;
        }
        env.scheduled.set(true);
        self.schedule_render();
    }

    fn emit_future(&self, task: Box<Future<Item = A::Action, Error = ()>>) {
        let this = self.clone();
        self.handle_future(Box::new(task.map(move |a| {
            this.on_action(a);
        })));
    }

    fn pop_handler(&self, id: &str) -> Option<Box<Fn(HandlerArg)>> {
        let env = self.get_env();
        let handler = env.pop_handler(id)?;
        let this = self.to_owned();
        let f = move |arg: HandlerArg| {
            match handler(arg) {
                Some(a) => this.on_action(a),
                None => return,
            };
        };
        Some(Box::new(f))
    }
}

pub fn uuid() -> String {
    RNG.with(|rng| uuid::Builder::from_bytes(rng.borrow_mut().gen()))
        .set_variant(uuid::Variant::RFC4122)
        .set_version(uuid::Version::Random)
        .build()
        .to_string()
}