Skip to content

Commit

Permalink
Start work on v0.19 (#49)
Browse files Browse the repository at this point in the history
* Try out new design

* Impl View for tuples

* Create use_ref hook

* Re-import web code and refactor

* Clean up
  • Loading branch information
matthunz authored Jan 25, 2024
1 parent eb9fab6 commit 61838cf
Show file tree
Hide file tree
Showing 25 changed files with 324 additions and 983 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ resolver = "2"
members = [
"crates/concoct",
"crates/concoct-web",
"crates/concoct-menu",
# "crates/concoct-menu",
"web_examples/counter"
]
1 change: 1 addition & 0 deletions crates/concoct-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ repository = "https://github.com/concoct-rs/concoct"
# concoct = "0.17.0-alpha.2"
concoct = { path = "../concoct" }
web-sys = { version = "0.3.66", features = ["Document", "Event", "HtmlElement", "HtmlCollection", "Text", "Window"] }
rustc-hash = "1.1.0"
50 changes: 29 additions & 21 deletions crates/concoct-web/src/html.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use super::WebContext;
use concoct::{
hook::{use_context, use_on_drop, use_provider, use_ref},
view::ViewCell,
View, ViewBuilder,
hook::{use_context, use_provider, use_ref},
View,
};
use std::{borrow::Cow, cell::RefCell, rc::Rc};
use std::{borrow::Cow, cell::RefCell, marker::PhantomData, rc::Rc};
use web_sys::{
wasm_bindgen::{closure::Closure, JsCast},
Element, Event,
};

use crate::WebContext;

macro_rules! make_tag_fns {
($($name:tt),*) => {
$(
pub fn $name<C: View>(content: C) -> Html<C> {
pub fn $name<T, A, C: View<T, A>>(content: C) -> Html<C, T, A> {
Html::new(stringify!($name), content)
}
)*
Expand All @@ -40,11 +40,12 @@ struct Data {
)>,
}

pub struct Html<C> {
pub struct Html<C, T, A> {
tag: Cow<'static, str>,
attrs: Vec<(Cow<'static, str>, Cow<'static, str>)>,
handlers: Vec<(Cow<'static, str>, Rc<RefCell<dyn FnMut(Event)>>)>,
content: ViewCell<C>,
content: C,
_marker: PhantomData<(T, A)>,
}

macro_rules! impl_attr_methods {
Expand All @@ -67,16 +68,17 @@ macro_rules! impl_handler_methods {
};
}

impl<C> Html<C> {
pub fn new(tag: impl Into<Cow<'static, str>>, content: C) -> Html<C>
impl<C, T, A> Html<C, T, A> {
pub fn new(tag: impl Into<Cow<'static, str>>, content: C) -> Html<C, T, A>
where
C: View,
C: View<T, A>,
{
Html {
tag: tag.into(),
attrs: Vec::new(),
handlers: Vec::new(),
content: ViewCell::new(content),
content,
_marker: PhantomData,
}
}

Expand Down Expand Up @@ -111,19 +113,24 @@ impl<C> Html<C> {
);
}

impl<C: View> ViewBuilder for Html<C> {
fn build(&self) -> impl View {
let data = use_ref(|| RefCell::new(Data::default()));
impl<T, A, C> View<T, A> for Html<C, T, A>
where
C: View<T, A>,
{
fn body(&mut self, cx: &concoct::Scope<T, A>) -> impl View<T, A> {
let data = use_ref(cx, || Rc::new(RefCell::new(Data::default())));
let mut data_ref = data.borrow_mut();

let web_cx = use_context::<WebContext>().unwrap();
let data_clone = data.clone();
let web_cx: Rc<WebContext> = use_context(cx);
let _data_clone = data.clone();

use_on_drop(move || {
/*
use_on_drop(move || {
if let Some(element) = &data_clone.borrow_mut().element {
element.remove();
}
});
*/

if data_ref.element.is_none() {
let elem = web_cx.document.create_element(&self.tag).unwrap();
Expand Down Expand Up @@ -155,12 +162,13 @@ impl<C: View> ViewBuilder for Html<C> {
}
}

use_provider(WebContext {
use_provider(cx, || WebContext {
window: web_cx.window.clone(),
document: web_cx.document.clone(),
parent: data_ref.element.as_ref().unwrap().clone().into(),
body: web_cx.body.clone(),
parent: data_ref.element.as_ref().unwrap().clone().unchecked_into(),
});

self.content.clone()
&mut self.content
}
}
98 changes: 53 additions & 45 deletions crates/concoct-web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,74 @@
use concoct::{
hook::{use_context, use_on_drop, use_provider, use_ref},
TextViewContext, View, ViewBuilder,
hook::{use_context, use_provider, use_ref},
Scope, TextViewContext, View,
};
use std::{cell::RefCell, rc::Rc};
use web_sys::{Document, Node, Text, Window};
use rustc_hash::FxHasher;
use std::{
cell::Cell,
hash::{Hash, Hasher},
rc::Rc,
};
use web_sys::{Document, HtmlElement, Window};

pub mod html;

struct WebContext {
window: Window,
document: Document,
parent: Node,
body: HtmlElement,
parent: HtmlElement,
}

struct WebRoot<VB> {
pub body: Rc<VB>,
pub struct HtmlRoot<C> {
content: C,
}

impl<VB: ViewBuilder> ViewBuilder for WebRoot<VB> {
fn build(&self) -> impl View {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();

use_provider(WebContext {
window,
document,
parent: body.into(),
impl<T: 'static, A: 'static, C: View<T, A>> View<T, A> for HtmlRoot<C> {
fn body(&mut self, cx: &Scope<T, A>) -> impl View<T, A> {
use_provider(cx, || {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
WebContext {
window,
document,
body: body.clone(),
parent: body,
}
});

use_provider(TextViewContext::new(|s| {
let web_cx = use_context::<WebContext>().unwrap();
use_provider(cx, || {
TextViewContext::new(|cx: &Scope<T, A>, s| {
let web_cx: Rc<WebContext> = use_context(cx);

let data = use_ref(|| RefCell::new((s.clone(), None::<Text>)));
let (last, node_cell) = &mut *data.borrow_mut();
let mut is_init = false;
let (hash_cell, node) = use_ref(cx, || {
let elem = web_cx.document.create_text_node(s);
web_cx.parent.append_child(&elem).unwrap();

let data_clone = data.clone();
use_on_drop(move || {
if let Some(node) = &data_clone.borrow_mut().1 {
node.remove();
}
});
let mut hasher = FxHasher::default();
s.hash(&mut hasher);
let hash = hasher.finish();

is_init = true;
(Cell::new(hash), elem)
});

if !is_init {
let mut hasher = FxHasher::default();
s.hash(&mut hasher);
let hash = hasher.finish();

if let Some(node) = node_cell {
if s != *last {
node.set_text_content(Some(&s));
*last = s.clone();
let last_hash = hash_cell.get();
hash_cell.set(hash);

if hash != last_hash {
node.set_text_content(Some(s));
}
}
} else {
let node = web_cx.document.create_text_node(&s);
web_cx.parent.append_child(&node).unwrap();
*node_cell = Some(node);
}
}));
})
});

self.body.clone()
&mut self.content
}
}

pub async fn run(view: impl ViewBuilder) {
concoct::run(WebRoot {
body: Rc::new(view),
})
.await;
}
24 changes: 24 additions & 0 deletions crates/concoct/examples/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use concoct::{Scope, View, VirtualDom};

struct Child;

impl View<Counter> for Child {
fn body(&mut self, cx: &Scope<Counter>) -> impl View<Counter> {
dbg!(cx.key);
}
}

struct Counter;

impl View<Self> for Counter {
fn body(&mut self, _cx: &Scope<Self>) -> impl View<Self> {
dbg!("view");
(Child, Child)
}
}

fn main() {
let mut app = VirtualDom::new(Counter);
app.build();
app.rebuild();
}
16 changes: 4 additions & 12 deletions crates/concoct/src/hook/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
//! Hooks to access render context.

mod use_context;
pub use use_context::{use_context, use_provider};
pub use self::use_context::use_context;

mod use_effect;
pub use self::use_effect::use_effect;
mod use_provider;
pub use self::use_provider::use_provider;

mod use_ref;
pub use use_ref::use_ref;

mod use_state;
pub use use_state::use_state;

mod use_on_drop;
pub use use_on_drop::use_on_drop;
pub use self::use_ref::use_ref;
28 changes: 5 additions & 23 deletions crates/concoct/src/hook/use_context.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
use crate::Runtime;
use crate::Scope;
use std::{any::TypeId, rc::Rc};

/// Hook to provide a context.
pub fn use_provider<T: 'static>(value: T) {
let cx = Runtime::current();
let cx_ref = cx.inner.borrow();
let mut scope = cx_ref.scope.as_ref().unwrap().inner.borrow_mut();

scope.contexts.insert(TypeId::of::<T>(), Rc::new(value));
}

/// Hook to get a context from its type.
pub fn use_context<T: 'static>() -> Option<Rc<T>> {
Runtime::current()
.inner
.borrow()
.scope
.as_ref()
.unwrap()
.inner
.borrow()
.contexts
.get(&TypeId::of::<T>())
.map(|rc| Rc::downcast(rc.clone()).unwrap())
pub fn use_context<T, A, R: 'static>(cx: &Scope<T, A>) -> Rc<R> {
let contexts = cx.contexts.borrow();
let rc = contexts.get(&TypeId::of::<R>()).unwrap();
Rc::downcast(rc.clone()).unwrap()
}
24 changes: 0 additions & 24 deletions crates/concoct/src/hook/use_effect.rs

This file was deleted.

18 changes: 0 additions & 18 deletions crates/concoct/src/hook/use_on_drop.rs

This file was deleted.

12 changes: 12 additions & 0 deletions crates/concoct/src/hook/use_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::Scope;
use std::{any::TypeId, rc::Rc};

use super::use_ref;

pub fn use_provider<T, A, R: 'static>(cx: &Scope<T, A>, make_initial: impl FnOnce() -> R) {
use_ref(cx, || {
cx.contexts
.borrow_mut()
.insert(TypeId::of::<R>(), Rc::new(make_initial()))
});
}
Loading

0 comments on commit 61838cf

Please sign in to comment.