Skip to content

Commit

Permalink
provide better docs; release 0.0.10
Browse files Browse the repository at this point in the history
  • Loading branch information
tiye committed Jun 5, 2022
1 parent f62a8ee commit 74465a9
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "respo"
version = "0.0.10-a2"
version = "0.0.10"
edition = "2021"
description = "a tiny virtual DOM library migrated from ClojureScript"
license = "Apache-2.0"
Expand Down
22 changes: 21 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
//! A tiny framework around a Virtual DOM library, migrated from [Respo.cljs](http://respo-mvc.org/) .
//! A tiny framework around a **virtual DOM** library, compiles to WebAssembly, runs in the browser, declarative UI for building interactive web apps.
//!
//! Original design was [Respo.cljs](http://respo-mvc.org/), which is heavily influenced by React.js and ClojureScript.
//! And this module is still "experimental" since lack of hot reloading in WebAssembly.
//!
//! It features:
//!
//! - virtual DOM(however simplified in list diffing)
//! - components declaration with functions
//! - globals states with Store and Actions dispatching
//! - states tree with nested states(inherited from Respo.cljs , might be akward)
//! - CSS in Rust macros
//! - basic component effects of `Mounted, WillUpdate, Updated, WillUnmount`
//! - macros to memoize component functions(although look clumsy)
//!
//! Meanwhile it does not include things like:
//!
//! - ❌ macros for JSX syntax. Respo prefer types over tags
//! - ❌ updating component states in lifecycle. Respo enforces "unidirectional data flow"
//! - ❌ React-like hooks. Respo uses functions without tricky side-effects
//! - ❌ Hot reloading. It does not work in WebAssembly so far
mod app;
mod memof1;
Expand Down
9 changes: 2 additions & 7 deletions src/memof1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};

use serde_json::Value;

/// dictionary of memoized functions
pub type MemoCache<T> = Rc<RefCell<HashMap<usize, HashMap<String, (Vec<Value>, T)>>>>;

pub fn init_memo_cache<T>() -> MemoCache<T>
where
T: Debug + Clone,
{
Rc::new(RefCell::new(HashMap::new()))
}

/// internal function for handling `memo1_call_by`
pub fn internal_memof1_call_by<F, T>(caches: MemoCache<T>, address: usize, key: String, args: Vec<Value>, f: F) -> Result<T, String>
where
T: Debug + Clone,
Expand Down
6 changes: 3 additions & 3 deletions src/respo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ where
let prev_tree = Rc::new(RefCell::new(tree0.clone()));

let to_prev_tree = prev_tree.clone();
let handle_event = EventHandlerFn::new(move |mark: RespoEventMark| -> Result<(), String> {
let handle_event = RespoEventMarkFn::new(move |mark: RespoEventMark| -> Result<(), String> {
match request_for_target_handler(&to_prev_tree.borrow(), &mark.name, &mark.coord) {
Ok(handler) => match handler.run(mark.event_info, dispatch_action.clone()) {
Ok(()) => {
Expand Down Expand Up @@ -154,7 +154,7 @@ where
}
}

fn request_for_target_handler<T>(tree: &RespoNode<T>, event_name: &str, coord: &[RespoCoord]) -> Result<RespoEventHandler<T>, String>
fn request_for_target_handler<T>(tree: &RespoNode<T>, event_name: &str, coord: &[RespoCoord]) -> Result<RespoListenerFn<T>, String>
where
T: Debug + Clone,
{
Expand All @@ -171,7 +171,7 @@ where
}

/// creates a DOM tree from virtual DOM with proxied event handler attached
pub fn build_dom_tree<T>(tree: &RespoNode<T>, coord: &[RespoCoord], handle_event: EventHandlerFn) -> Result<Node, JsValue>
pub fn build_dom_tree<T>(tree: &RespoNode<T>, coord: &[RespoCoord], handle_event: RespoEventMarkFn) -> Result<Node, JsValue>
where
T: Debug + Clone,
{
Expand Down
40 changes: 21 additions & 19 deletions src/respo/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use super::primes::RespoNode;
/// ```
#[macro_export]
macro_rules! declare_tag {
( $name:ident ) => {
( $name:ident, $doc: expr) => {
#[doc=$doc]
#[allow(dead_code)]
pub fn $name<T>() -> RespoNode<T>
where
Expand All @@ -24,25 +25,26 @@ macro_rules! declare_tag {
};
}

declare_tag!(div);
declare_tag!(header);
declare_tag!(section);
declare_tag!(footer);
declare_tag!(span);
declare_tag!(input);
declare_tag!(link);
declare_tag!(button);
declare_tag!(pre);
declare_tag!(img);
declare_tag!(video);
declare_tag!(code);
declare_tag!(a);
declare_tag!(h1);
declare_tag!(h2);
declare_tag!(h3);
declare_tag!(h4);
declare_tag!(blockquote);
declare_tag!(div, "`<div/>`");
declare_tag!(header, "`<header/>`");
declare_tag!(section, "`<section/>`");
declare_tag!(footer, "`<footer/>`");
declare_tag!(span, "`<span/>`");
declare_tag!(input, "`<input/>`");
declare_tag!(link, "`<link/>`");
declare_tag!(button, "`<button/>`");
declare_tag!(pre, "`<pre/>`");
declare_tag!(img, "`<img/>`");
declare_tag!(video, "`<video/>`");
declare_tag!(code, "`<code/>`");
declare_tag!(a, "`<a/>`");
declare_tag!(h1, "`<h1/>`");
declare_tag!(h2, "`<h2/>`");
declare_tag!(h3, "`<h3/>`");
declare_tag!(h4, "`<h4/>`");
declare_tag!(blockquote, "`<blockquote/>`");

/// special function to return `<div/>` with width/height that can be used as a space
pub fn space<T>(w: Option<i32>, h: Option<i32>) -> RespoNode<T>
where
T: Clone + Debug,
Expand Down
6 changes: 3 additions & 3 deletions src/respo/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ use web_sys::console::warn_1;
use crate::RespoEffectType;

use super::{
build_dom_tree, load_coord_target_tree, ChildDomOp, DomChange, EventHandlerFn, RespoCoord, RespoEvent, RespoEventMark, RespoNode,
build_dom_tree, load_coord_target_tree, ChildDomOp, DomChange, RespoCoord, RespoEvent, RespoEventMark, RespoEventMarkFn, RespoNode,
};

pub fn patch_tree<T>(
tree: &RespoNode<T>,
old_tree: &RespoNode<T>,
mount_target: &Node,
changes: &[DomChange<T>],
handle_event: EventHandlerFn,
handle_event: RespoEventMarkFn,
) -> Result<(), String>
where
T: Debug + Clone,
Expand Down Expand Up @@ -225,7 +225,7 @@ fn find_coord_dom_target(mount_target: &Node, coord: &[u32]) -> Result<Node, Str
Ok(target)
}

pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_event: EventHandlerFn) -> Result<(), String> {
pub fn attach_event(element: &Element, key: &str, coord: &[RespoCoord], handle_event: RespoEventMarkFn) -> Result<(), String> {
let coord = coord.to_owned();
// util::log!("attach event {}", key);
match key {
Expand Down
49 changes: 27 additions & 22 deletions src/respo/primes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ where
/// tagName
name: String,
attrs: HashMap<String, String>,
event: HashMap<String, RespoEventHandler<T>>,
event: HashMap<String, RespoListenerFn<T>>,
/// inlines styles, partially typed.
/// there's also a macro called `static_styles` for inserting CSS rules
style: RespoStyle,
Expand Down Expand Up @@ -70,6 +70,7 @@ where
}
}

/// a key for referencing a child node, use a value that can be converted to string
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct RespoIndexKey(String);

Expand Down Expand Up @@ -167,7 +168,7 @@ where
node.on_click(handler);
}
RespoNode::Element { ref mut event, .. } => {
event.insert("click".into(), RespoEventHandler::new(handler));
event.insert("click".into(), RespoListenerFn::new(handler));
}
RespoNode::Referenced(_) => {
unreachable!("should not be called on a referenced node");
Expand All @@ -184,7 +185,7 @@ where
node.on_input(handler);
}
RespoNode::Element { ref mut event, .. } => {
event.insert("input".into(), RespoEventHandler::new(handler));
event.insert("input".into(), RespoListenerFn::new(handler));
}
RespoNode::Referenced(_) => {
unreachable!("should not be called on a referenced node");
Expand All @@ -194,7 +195,7 @@ where
}
pub fn add_event<U, V>(&mut self, more: U) -> &mut Self
where
U: IntoIterator<Item = (V, RespoEventHandler<T>)>,
U: IntoIterator<Item = (V, RespoListenerFn<T>)>,
V: Into<String> + ToOwned,
{
match self {
Expand Down Expand Up @@ -310,12 +311,13 @@ where

pub(crate) type StrDict = HashMap<String, String>;

/// (internal) struct to store event handler function on the tree
#[derive(Clone)]
pub struct RespoEventHandler<T>(Rc<dyn Fn(RespoEvent, DispatchFn<T>) -> Result<(), String>>)
pub struct RespoListenerFn<T>(Rc<dyn Fn(RespoEvent, DispatchFn<T>) -> Result<(), String>>)
where
T: Debug + Clone;

impl<T> PartialEq for RespoEventHandler<T>
impl<T> PartialEq for RespoListenerFn<T>
where
T: Debug + Clone,
{
Expand All @@ -325,9 +327,9 @@ where
}
}

impl<T> Eq for RespoEventHandler<T> where T: Debug + Clone {}
impl<T> Eq for RespoListenerFn<T> where T: Debug + Clone {}

impl<T> Debug for RespoEventHandler<T>
impl<T> Debug for RespoListenerFn<T>
where
T: Debug + Clone,
{
Expand All @@ -336,7 +338,7 @@ where
}
}

impl<T> RespoEventHandler<T>
impl<T> RespoListenerFn<T>
where
T: Debug + Clone,
{
Expand Down Expand Up @@ -399,11 +401,13 @@ pub enum RespoEvent {
/// effects that attached to components
#[derive(Clone)]
pub struct RespoEffect {
pub args: Vec<EffectArg>,
/// arguments passed to this effect.
/// the events `WillUpdate` and `Updated` are triggered when these arguments are changed
pub args: Vec<RespoEffectArg>,
handler: Rc<RespoEffectHandler>,
}

type RespoEffectHandler = dyn Fn(Vec<EffectArg>, RespoEffectType, &Node) -> Result<(), String>;
type RespoEffectHandler = dyn Fn(Vec<RespoEffectArg>, RespoEffectType, &Node) -> Result<(), String>;

impl PartialEq for RespoEffect {
/// closure are not compared, changes happen in and passed via args
Expand All @@ -420,13 +424,13 @@ impl RespoEffect {
}
pub fn new<U, V>(args: Vec<&V>, handler: U) -> Self
where
U: Fn(Vec<EffectArg>, RespoEffectType, &Node) -> Result<(), String> + 'static,
U: Fn(Vec<RespoEffectArg>, RespoEffectType, &Node) -> Result<(), String> + 'static,
V: Serialize,
{
Self {
args: args
.iter()
.map(|v| EffectArg::new(serde_json::to_value(v).expect("to json")))
.map(|v| RespoEffectArg::new(serde_json::to_value(v).expect("to json")))
.collect(),
handler: Rc::new(handler),
}
Expand All @@ -435,7 +439,7 @@ impl RespoEffect {
/// no need to have args, only handler
pub fn new_insular<U>(handler: U) -> Self
where
U: Fn(Vec<EffectArg>, RespoEffectType, &Node) -> Result<(), String> + 'static,
U: Fn(Vec<RespoEffectArg>, RespoEffectType, &Node) -> Result<(), String> + 'static,
{
Self {
args: vec![],
Expand Down Expand Up @@ -609,16 +613,17 @@ where
}
}

/// (internal) function to handle event marks at first phase of event handling
#[derive(Clone)]
pub struct EventHandlerFn(Rc<dyn Fn(RespoEventMark) -> Result<(), String>>);
pub struct RespoEventMarkFn(Rc<dyn Fn(RespoEventMark) -> Result<(), String>>);

impl Debug for EventHandlerFn {
impl Debug for RespoEventMarkFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("[EventHandlerFn]")
f.write_str("[EventMarkFn ...]")
}
}

impl EventHandlerFn {
impl RespoEventMarkFn {
pub fn run(&self, e: RespoEventMark) -> Result<(), String> {
(self.0)(e)
}
Expand All @@ -630,17 +635,17 @@ impl EventHandlerFn {
}
}

impl From<Rc<dyn Fn(RespoEventMark) -> Result<(), String>>> for EventHandlerFn {
impl From<Rc<dyn Fn(RespoEventMark) -> Result<(), String>>> for RespoEventMarkFn {
fn from(f: Rc<dyn Fn(RespoEventMark) -> Result<(), String>>) -> Self {
Self(f)
}
}

// abstraction on efffect argument
/// (internal) abstraction on effect argument
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectArg(Value);
pub struct RespoEffectArg(Value);

impl EffectArg {
impl RespoEffectArg {
pub fn new(v: Value) -> Self {
Self(v)
}
Expand Down
5 changes: 4 additions & 1 deletion src/respo/states_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use std::fmt::Debug;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;

/// Respo maintains states in a tree structure, where the keys are strings,
/// each child component "picks" a key to attach its own state to the tree,
/// and it dispatches events to global store to update the state.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StatesTree {
/// local data
Expand Down Expand Up @@ -60,7 +63,7 @@ impl StatesTree {
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
/// local state in component could be None according to the tree structure
/// local state in component could be `None` according to the tree structure
pub struct MaybeState(Option<Value>);

impl MaybeState {
Expand Down
13 changes: 13 additions & 0 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//! Some pre-defined styles for layout fonts, and form elements.
//! Highly coupled with styles from <http://ui.respo-mvc.org/> .
//!
//! - Layouts: flexbox rows and columns, also with centering
//! - Elements: button, input, textarea, and link from Respo UI.
//! - Fonts: fancy(Josefin Sans), normal(Hind), code(monospace from system).
//!
//! Since this is CSS rules, you can combine styles with:
//!
//! ```ignore
//! format!("{} {} {}", ui_input(), your_shared(), your_own())
//! ```
use crate::respo::{CssBoxSizing, CssColor, CssDisplay, CssLineHeight, CssSize, CssTextAlign, CssVerticalAlign, RespoStyle, *};

const DEFAULT_FONTS: &str = "Hind,Verdana,'Hiragino Sans GB','WenQuanYi Micro Hei','Microsoft Yahei',sans-serif";
Expand Down

0 comments on commit 74465a9

Please sign in to comment.