use std::{ collections::{HashMap, HashSet}, hash::Hash, marker::PhantomData, }; use sycamore::view::IntoView as _; use sycamore::{builder::prelude::*, prelude::*}; pub mod prelude { pub use super::{IntoView, ViewProps, Viewable, Viewer, WebView}; pub use crate::components::Block; pub use paste::paste; pub use sycamore::prelude::*; } use crate::components::{Block, BlockProps}; #[macro_export] macro_rules! viewable { ($type:ty => $viewer:ty) => { impl<'a, G: Html> Viewable<'a, G> for $type { type Viewer = $viewer; } }; } #[derive(Prop)] pub struct ViewProps<'a, T> { pub state: &'a Signal, } impl<'a, T> From<&'a Signal> for ViewProps<'a, T> { fn from(state: &'a Signal) -> Self { ViewProps { state } } } pub trait IntoView<'a, G: Html> { fn view(self, cx: Scope<'a>) -> View; } impl<'a, G: Html, T: Viewable<'a, G>> IntoView<'a, G> for &'a Signal where ViewProps<'a, T>: From<&'a Signal>, { fn view(self, cx: Scope<'a>) -> View { T::view(cx, self.into()) } } pub trait WebView<'a, G: Html>: Sized { fn view(cx: Scope<'a>, props: ViewProps<'a, Self>) -> View; } impl<'a, G, E, Type> WebView<'a, G> for Type where G: Html, E: Viewer<'a, G, Type>, Type: Viewable<'a, G, Viewer = E>, { fn view(cx: Scope<'a>, props: ViewProps<'a, Self>) -> View { E::view(cx, props) } } pub trait Viewer<'a, G: Html, Type>: Sized { fn view(cx: Scope<'a>, props: ViewProps<'a, Type>) -> View; } pub trait Viewable<'a, G: Html>: Sized { type Viewer: Viewer<'a, G, Self>; } pub struct StringView; impl<'a, G: Html, T: 'a> Viewer<'a, G, T> for StringView where T: sycamore::view::IntoView, { fn view(cx: Scope<'a>, props: ViewProps<'a, T>) -> View { view! { cx, (props.state.get().clone().create()) } } } viewable!(String => StringView); viewable!(i64 => StringView); viewable!(i32 => StringView); viewable!(isize => StringView); viewable!(u64 => StringView); viewable!(u32 => StringView); viewable!(usize => StringView); viewable!(f64 => StringView); viewable!(f32 => StringView); pub struct BoolView; impl<'a, G: Html> Viewer<'a, G, bool> for BoolView { fn view(cx: Scope<'a>, props: ViewProps<'a, bool>) -> View { let signal = create_signal(cx, props.state.get()); view! { cx, input(type="checkbox", checked=*signal.get().as_ref().clone(), disabled=true) } } } viewable!(bool => BoolView); pub struct VecView; impl<'a, G, T, I> Viewer<'a, G, I> for VecView where G: Html, T: for<'b> Viewable<'b, G> + Clone + PartialEq + 'a, I: IntoIterator + FromIterator + Clone, { fn view(cx: Scope<'a>, props: ViewProps<'a, I>) -> View { Block( cx, BlockProps { title: "List".into(), children: Children::new(cx, move |_| { view! { cx, //Block(title="List".into()) { div { (View::new_fragment(props.state.get().as_ref().clone().into_iter().map(|x| { let x = create_signal(cx, x); div().c(x.view(cx)).c(br()).view(cx) }).collect())) } //} } }), }, ) } } impl<'a, G, T> Viewable<'a, G> for Vec where G: Html, T: for<'b> Viewable<'b, G> + Clone + PartialEq + 'a, { type Viewer = VecView; } impl<'a, G, T> Viewable<'a, G> for HashSet where G: Html, T: for<'b> Viewable<'b, G> + Clone + PartialEq + Hash + Eq + 'a, { type Viewer = VecView; } impl<'a, G, K, V> Viewable<'a, G> for HashMap where G: Html, K: Clone + Hash + Eq, V: Clone, (K, V): for<'b> Viewable<'b, G> + Clone + PartialEq + Hash + Eq + 'a, { type Viewer = VecView; } pub struct TupleView; impl<'a, G: Html, A: for<'b> Viewable<'b, G> + Clone, B: for<'b> Viewable<'b, G> + Clone> Viewer<'a, G, (A, B)> for TupleView { fn view(cx: Scope<'a>, props: ViewProps<'a, (A, B)>) -> View { let a = create_signal(cx, props.state.get().as_ref().clone().0); let b = create_signal(cx, props.state.get().as_ref().clone().1); view! { cx, Block(title="Tuple".into()) { (a.view(cx)) (b.view(cx)) } } } } impl<'a, G, A, B> Viewable<'a, G> for (A, B) where G: Html, A: for<'b> Viewable<'b, G> + Clone, B: for<'b> Viewable<'b, G> + Clone, { type Viewer = TupleView; } pub struct OptionView; impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewer<'a, G, Option> for OptionView { fn view(cx: Scope<'a>, props: ViewProps<'a, Option>) -> View { match props.state.get().as_ref().clone() { Some(x) => view! { cx, (create_signal(cx, x.clone()).view(cx)) }, None => view! { cx, "None" }, } } } impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewable<'a, G> for Option { type Viewer = OptionView; }