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 T, } impl<'a, T> From<&'a T> for ViewProps<'a, T> { fn from(state: &'a T) -> 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 T where ViewProps<'a, T>: From<&'a T>, { 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.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 { view! { cx, input(type="checkbox", checked=*props.state, 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.clone().into_iter().map(|x| create_ref(cx, x)).map(|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 { view! { cx, Block(title="Tuple".into()) { (props.state.0.view(cx)) (props.state.1.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 { Some(x) => view! { cx, (x.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; }