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
//! Savory is library for building user interface.
//!
//! [![master docs](https://img.shields.io/badge/docs-master-blue.svg)](https://malrusayni.gitlab.io/savory/savory-core/)
//! ·
//! [![crate info](https://img.shields.io/crates/v/savory.svg)](https://crates.io/crates/savory-core)
//! ·
//! [![pipeline](https://gitlab.com/MAlrusayni/savory/badges/master/pipeline.svg)](https://gitlab.com/MAlrusayni/savory/pipelines)
//! ·
//! [![rustc version](https://img.shields.io/badge/rustc-stable-green.svg)](https://crates.io/crates/savory)
//! ·
//! [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
//!
//!
//! # Features
//!
//! - **Views**: Views can be any type implement `View` trait or any standalone
//!   function that returns `Node`, views can be trait object which make them very
//!   composable.
//! - **Elements**: Savory uses elements as core building unit when building
//!   stateful UI. Elements owns thier state and handle user inputs via messages.
//! - **Collection of UI elements**: Savory ships with collection of resuable and
//!   themeable UI elements.
//! - **Theme**: UI elements can be themed by any type that implement `ThemeImpl`
//!   trait, themes have full control on the element appearance.
//! - **Typed HTML**: Use typed CSS and HTML attributes, Savory try hard not to rely
//!   on strings when creating CSS and HTML attributes since these can produce hard
//!   to debug bugs.
//! - **Enhance Seed API**: Enhancement on Seed API that makes working with `Node`,
//!   `Orders` fun.
//!
//! Savory tries to make writing UI elements fun and boilerplate free.
//!
//! Savory crates:
//! - `savory`: savory CLI
//! - [`savory-core`]: Library for building user interface (this crate)
//! - [`savory-html`]: Typed HTML for Savory
//! - [`savory-elements`]: UI Elements based on Savory
//! - [`savory-derive`]: Helper derives
//!
//! # Core Concept
//!
//! Savory have two main types **View** and **Element**, View types produce
//! static HTML, while Element types produce interactive HTML, as simple as
//! that.
//!
//! Elements types must implemente [`Element`] and [`View`] traits, which would
//! make them interactive.
//!
//! View types must implemente [`View`] trait, that would produce the static
//! HTML.
//!
//! # Counter Example
//!
//! Here is very simple counter, that doesn't use all Savory features, but it's
//! good as starting point for newcomers.
//!
//! ``` rust
//! use savory_core::prelude::*;
//! use savory_html::prelude::*;
//! use wasm_bindgen::prelude::*;
//!
//! // app element (the model)
//! pub struct Counter(i32);
//!
//! // app message
//! pub enum Msg {
//!     Increment,
//!     Decrement,
//! }
//!
//! impl Element for Counter {
//!     type Message = Msg;
//!     type Config = Url;
//!
//!     // initialize the app in this function
//!     fn init(_: Url, _: &mut impl Orders<Msg>) -> Self {
//!         Self(0)
//!     }
//!
//!     // handle app messages
//!     fn update(&mut self, msg: Msg, _: &mut impl Orders<Msg>) {
//!         match msg {
//!             Msg::Increment => self.0 += 1,
//!             Msg::Decrement => self.0 -= 1,
//!         }
//!     }
//! }
//!
//! impl View<Node<Msg>> for Counter {
//!     // view the app
//!     fn view(&self) -> Node<Msg> {
//!         let inc_btn = html::button().add("Increment").on_click(|_| Msg::Increment);
//!         let dec_btn = html::button().add("Decrement").on_click(|_| Msg::Decrement);
//!
//!         html::div()
//!             .add(inc_btn)
//!             .add(self.0.to_string())
//!             .add(dec_btn)
//!     }
//! }
//!
//! #[wasm_bindgen(start)]
//! pub fn view() {
//!     // mount and start the app at `app` element
//!     Counter::start();
//! }
//! ```
//!
//! [`View`]: crate::prelude::View
//! [`Element`]: crate::prelude::Element
//! [`savory-core`]: https://gitlab.com/MAlrusayni/savory/tree/master/core
//! [`savory-html`]: https://gitlab.com/MAlrusayni/savory/tree/master/html
//! [`savory-elements`]: https://gitlab.com/MAlrusayni/savory/tree/master/elements
//! [`savory-derive`]: https://gitlab.com/MAlrusayni/savory/tree/master

#![forbid(unsafe_code)]

pub mod element;
pub mod orders_ext;
pub mod view;

/// savory prelude.
pub mod prelude {
    pub use crate::{
        element::{AppElementExt, Element},
        orders_ext::OrdersExt,
        view::View,
    };
    pub use seed::prelude::{MessageMapper, Node, Orders, Url};
}