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
#![recursion_limit = "1024"]

use filter::Visibility;
use header::input_header;
use main_section::main_section;

use illicit::AsContext;
use mox::mox;
use moxie_dom::{
    elements::sectioning::{section, Section},
    interfaces::element::Element,
    prelude::*,
};
use std::sync::atomic::{AtomicU32, Ordering};
use tracing::*;
use wasm_bindgen::prelude::*;

pub mod filter;
pub mod footer;
pub mod header;
pub mod input;
pub mod item;
pub mod main_section;

#[topo::nested]
fn todo_app() -> Section {
    mox! {
        <section class="todoapp">
            { input_header() }
            { main_section() }
        </section>
    }
}

pub(crate) struct App {
    pub todos: Key<Vec<Todo>>,
    pub visibility: Key<Visibility>,
}

impl App {
    #[topo::nested]
    pub fn current() -> Self {
        let (_, visibility) = state(Visibility::default);
        let (_, todos) =
            // we allow the default empty to be overridden for testing
            // TODO support localStorage
            state(|| illicit::get::<Vec<Todo>>().map(|d| d.clone()).unwrap_or_default());

        Self { todos, visibility }
    }

    pub fn enter<T>(self, f: impl FnMut() -> T) -> T {
        illicit::Layer::new().offer(self.todos).offer(self.visibility).enter(f)
    }

    pub fn boot(node: impl Into<moxie_dom::raw::Node>) {
        Self::boot_fn(&[], node, todo_app)
    }

    fn boot_fn<Root: Element + 'static>(
        default_todos: &[Todo],
        node: impl Into<moxie_dom::raw::Node>,
        mut root: impl FnMut() -> Root + 'static,
    ) {
        let defaults = default_todos.to_vec();
        moxie_dom::boot(node, move || defaults.clone().offer(|| App::current().enter(&mut root)));
        info!("running");
    }
}

#[derive(Clone, Debug)]
pub struct Todo {
    id: u32,
    title: String,
    completed: bool,
}

impl Todo {
    fn new(s: impl Into<String>) -> Self {
        static NEXT_ID: AtomicU32 = AtomicU32::new(0);
        Self { id: NEXT_ID.fetch_add(1, Ordering::SeqCst), title: s.into(), completed: false }
    }
}

#[wasm_bindgen(start)]
pub fn setup_tracing() {
    static SETUP: std::sync::Once = std::sync::Once::new();
    SETUP.call_once(|| {
        let config = tracing_wasm::WASMLayerConfigBuilder::new()
            .set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor)
            .build();
        tracing_wasm::set_as_global_default_with_config(config);
        std::panic::set_hook(Box::new(|info| {
            error!(?info, "crashed");
        }));
        info!("tracing initialized");
    });
    console_error_panic_hook::set_once();
}

#[wasm_bindgen]
pub fn boot(root: moxie_dom::raw::sys::Node) {
    App::boot(root);
}

/// Included as a module within the crate rather than a separate file because
/// cargo is grumpy about resolving the crate-under-test.
#[cfg(test)]
mod integration_tests;

#[cfg(test)]
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);