use std::str::FromStr; use crate::components::Block; use lan_party_core::event::{ free_for_all_game::FreeForAllGameSpec, team_game::TeamGameSpec, test::TestSpec, EventSpec, EventTypeSpec, }; use paste::paste; use sycamore::prelude::*; use super::input_classes; macro_rules! editable { ($type:ty => $editor:ty) => { impl<'a, G: Html> Editable<'a, G> for $type { type Editor = $editor; } }; } macro_rules! edit_fields { ($cx:ident, $(($name:expr, $prop:expr)),* $(,)?) => { view! { $cx, $( p { ($name) ": " ($prop.edit($cx)) } )* } }; } macro_rules! link_fields { ($cx:ident, $($field:ident),* $(,)? => $state:ident as $t:ident) => { $(let $field = create_signal($cx, $state.get().$field.clone());)* create_effect($cx, || { $state.set($t { $($field: $field.get().as_ref().clone(),)* ..Default::default() }); }); }; } macro_rules! edit_struct { ($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => { paste! { pub struct [<$struct Edit>]; impl<'a, G: Html> Editor<'a, G, $struct> for [<$struct Edit>] { fn edit(cx: Scope<'a>, props: EditProps<'a, $struct>) -> View { let state = props.state; link_fields!(cx, $($prop,)* => state as $struct); view! { cx, Block(title=stringify!($struct).into()) { (edit_fields!(cx, $(($name, $prop),)*)) } } } } editable!($struct => [<$struct Edit>]); } }; } #[derive(Prop)] pub struct EditProps<'a, T> { pub state: &'a Signal, } impl<'a, T> From<&'a Signal> for EditProps<'a, T> { fn from(state: &'a Signal) -> Self { EditProps { state } } } pub trait IntoEdit<'a, G: Html> { fn edit(self, cx: Scope<'a>) -> View; } impl<'a, G: Html, T: Editable<'a, G>> IntoEdit<'a, G> for &'a Signal where EditProps<'a, T>: From<&'a Signal>, { fn edit(self, cx: Scope<'a>) -> View { T::edit(cx, self.into()) } } pub trait Edit<'a, G: Html>: Sized { fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View; } impl<'a, G: Html, E: Editor<'a, G, Type>, Type: Editable<'a, G, Editor = E>> Edit<'a, G> for Type { fn edit(cx: Scope<'a>, props: EditProps<'a, Self>) -> View { E::edit(cx, props) } } pub trait Editor<'a, G: Html, Type>: Sized { fn edit(cx: Scope<'a>, props: EditProps<'a, Type>) -> View; } pub trait Editable<'a, G: Html>: Sized { type Editor: Editor<'a, G, Self>; } edit_struct!(EventSpec => ("Name", name), ("Description", description), ("Event type", event_type)); edit_struct!(TestSpec => ("Number of players", num_players)); edit_struct!(TeamGameSpec => ); edit_struct!(FreeForAllGameSpec => ); pub struct StringEdit; impl<'a, G: Html> Editor<'a, G, String> for StringEdit { fn edit(cx: Scope<'a>, props: EditProps<'a, String>) -> View { view! { cx, input(class=input_classes(), bind:value=props.state) } } } editable!(String => StringEdit); pub struct StubEdit; impl<'a, G: Html, T> Editor<'a, G, T> for StubEdit where T: Editable<'a, G, Editor = StubEdit>, { fn edit(cx: Scope<'a>, _props: EditProps<'a, T>) -> View { view! { cx, "Unimplemented" } } } editable!(EventTypeSpec => StubEdit); pub struct InputEdit; impl<'a, G: Html, T> Editor<'a, G, T> for InputEdit where T: Editable<'a, G, Editor = InputEdit> + FromStr + ToString + Default, { fn edit(cx: Scope<'a>, props: EditProps<'a, T>) -> View { let value = create_signal(cx, props.state.get().to_string()); create_memo(cx, || { props .state .set((*value.get()).parse().unwrap_or(T::default())) }); view! { cx, input(class=input_classes(), bind:value=value) } } } editable!(i64 => InputEdit); editable!(i32 => InputEdit); editable!(isize => InputEdit); editable!(u64 => InputEdit); editable!(u32 => InputEdit); editable!(usize => InputEdit); editable!(f64 => InputEdit); editable!(f32 => InputEdit); pub struct BoolEdit; impl<'a, G: Html> Editor<'a, G, bool> for BoolEdit { fn edit(cx: Scope<'a>, props: EditProps<'a, bool>) -> View { view! { cx, input(type="checkbox", bind:checked=props.state) } } } editable!(bool => BoolEdit);