use std::{cell::RefCell, marker::PhantomData, str::FromStr}; use crate::components::Block; use lan_party_core::event::{ free_for_all_game::FreeForAllGameSpec, team_game::TeamGameSpec, test::TestSpec, EventSpec, EventTypeSpec, }; use log::debug; use paste::paste; use sycamore::{builder::prelude::*, component::Prop, prelude::*}; macro_rules! editable { ($type:ty => $editor:ty) => { impl<'s: 'a, 'a, G: Html> Editable<'s, 'a, G> for $type { type Editor = $editor; } }; } macro_rules! edit_fields { ($cx:ident, $(($name:expr, $prop:expr)),* $(,)?) => { view! { $cx, $( p { label { ($name) } ($prop.edit($cx)) } )* } }; } macro_rules! link_fields { ($cx:ident, $($field:ident),* $(,)? => $state:ident as $t:ident) => { $(let $field = create_signal($cx, $state.get_untracked().$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<'s: 'a, 'a, G: Html> Editor<'s, '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>]); } }; } macro_rules! link_variants { ($cx:ident, $selected:ident => $($index:literal: $var_name:ident = $variant:ident: $var_type:ty),* $(,)? => $state:ident as $t:ident) => { let $selected = create_signal($cx, String::from("0")); $(let $var_name = if let $t::$variant(v) = $state.get_untracked().as_ref().clone() { create_signal($cx, v.clone()) } else { create_signal($cx, <$var_type>::default()) };)* create_effect($cx, || { debug!("{:#?}", $selected.get()); match $selected.get().as_str() { $(stringify!($index) => $state.set($t::$variant($var_name.get().as_ref().clone())),)* _ => unreachable!() } }); }; } macro_rules! edit_enum { ($enum:ident => $selected:ident => $($index:literal: $var_name:ident = $variant:ident: $var_type:ty),* $(,)?) => { paste! { pub struct [<$enum Edit>]; impl<'s: 'a, 'a, G: Html> Editor<'s, 'a, G, $enum> for [<$enum Edit>] { fn edit(cx: Scope<'a>, props: EditProps<'a, $enum>) -> View { let state = props.state; link_variants!(cx, $selected => $($index: $var_name = $variant: $var_type,)* => state as $enum ); view! { cx, Block(title=stringify!($enum).to_string()) { select(bind:value=$selected) { $(option(value={stringify!($index)}, selected=$index==0) { (stringify!($variant)) })* } (match $selected.get().as_str() { $(stringify!($index) => $var_name.edit(cx),)* _ => unreachable!() }) } } } } editable!($enum => [<$enum 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<'s: 'a, 'a, G: Html> { fn edit(self, cx: BoundedScope<'a, 's>) -> View; } impl<'s: 'a, 'a, G: Html, T: Editable<'s, 'a, G> + 'a> IntoEdit<'s, 'a, G> for &'a Signal where EditProps<'a, T>: From<&'a Signal>, { fn edit(self, cx: BoundedScope<'a, 's>) -> View { T::edit(cx, self.into()) } } pub trait Edit<'s: 'a, 'a, G: Html>: Sized { fn edit(cx: BoundedScope<'a, 's>, props: EditProps<'a, Self>) -> View; } impl<'s: 'a, 'a, G, E, Type> Edit<'s, 'a, G> for Type where G: Html, E: Editor<'s, 'a, G, Type>, Type: Editable<'s, 'a, G, Editor = E> + 'a, { fn edit(cx: BoundedScope<'a, 's>, props: EditProps<'a, Self>) -> View { E::edit(cx, props) } } pub trait Editor<'s: 'a, 'a, G: Html, Type: 'a>: Sized { fn edit(cx: BoundedScope<'a, 's>, props: EditProps<'a, Type>) -> View; } pub trait Editable<'s: 'a, 'a, G: Html>: Sized + 'a { type Editor: Editor<'s, 'a, G, Self>; } edit_struct!(EventSpec => ("Name", name), ("Description", description), ("Event type", event_type)); edit_enum!(EventTypeSpec => selected => 0: test = Test: TestSpec, 1: team_game = TeamGame: TeamGameSpec, 2: free_for_all_game = FreeForAllGame: FreeForAllGameSpec ); edit_struct!(TestSpec => ("Number of players", num_players)); edit_struct!(TeamGameSpec => ("Win rewards", win_rewards)); edit_struct!(FreeForAllGameSpec => ); pub struct StringEdit; impl<'s: 'a, 'a, G: Html> Editor<'s, 'a, G, String> for StringEdit { fn edit(cx: Scope<'a>, props: EditProps<'a, String>) -> View { view! { cx, input(bind:value=props.state) } } } editable!(String => StringEdit); pub struct StubEdit; impl<'s: 'a, 'a, G: Html, T> Editor<'s, 'a, G, T> for StubEdit where T: Editable<'s, 'a, G, Editor = StubEdit>, { fn edit(cx: Scope<'a>, _props: EditProps<'a, T>) -> View { view! { cx, i { "Editor Unimplemented" } } } } pub struct InputEdit; impl<'s: 'a, 'a, G: Html, T> Editor<'s, 'a, G, T> for InputEdit where T: Editable<'s, '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_untracked().to_string()); create_effect(cx, || { props .state .set((*value.get()).parse().unwrap_or(T::default())) }); view! { cx, input(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<'s: 'a, 'a, G: Html> Editor<'s, '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); pub struct VecEdit; impl<'s: 'a, 'a, G, T, I> Editor<'s, 'a, G, I> for VecEdit where G: Html, T: Editable<'s, 'a, G> + Clone + PartialEq + 'a, I: IntoIterator + FromIterator + Clone + 'a, { fn edit(cx: BoundedScope<'a, 's>, props: EditProps<'a, I>) -> View { let vec: &'a Signal>> = create_signal( cx, props .state .get_untracked() .as_ref() .clone() .into_iter() .map(|x| create_signal(cx, x)) .collect::>(), ); //let signal = create_signal(cx, 0); //let vec2 = vec.get().as_ref().clone(); //let signal = create_ref(cx, vec.get(0).unwrap()); create_effect(cx, || { props.state.set( vec.get() .iter() .cloned() .map(|x| x.get().as_ref().clone()) .collect(), ) }); let test = Indexed( cx, IndexedProps::builder() .iterable(vec) .view(|cx: BoundedScope<'_, '_>, x: &'a Signal| { //view! { cx, (T::edit(cx, EditProps { state: x })) } T::edit(cx, EditProps { state: x }) }) .build(), ); test /* //view! { cx, "Vec" } view! { cx, Indexed( iterable=vec, view=|cx: BoundedScope<'_, 'a>, x: &'a Signal| { view! { cx, //(T::edit(cx, EditProps { state: x })) (something_with_signal(cx, EditProps { state: x })) } }, ) } */ /* view! { cx, (View::new_fragment((vec.get().try_borrow().unwrap().clone().into_iter().map(|s: &'a Signal| { view! { cx, (T::edit(cx, s.into())) } })).collect())) } */ } } /* #[component] pub fn VecTest< 'a, G: Html, T: 'a + PartialEq + Clone + Editable<'a, G, Editor = E>, E: Editor<'a, G, T>, >( cx: Scope<'a>, props: EditProps<'a, Vec>, ) -> View { /* let signals: &'a Signal>> = create_signal( cx, vec![ create_signal(cx, String::new()), create_signal(cx, String::new()), ], ); */ let signals = create_signal( cx, props .state .get() .iter() .cloned() .map(|s| create_signal(cx, s)) .collect::>(), ); view! { cx, Indexed( iterable=signals, view=|cx: BoundedScope<'_, 'a>, signal: &'a Signal| { view! { cx, //(something_with_signal(cx, SignalWrapper(signal))) //(E::edit(cx, EditProps { state: signal })) //(T::edit(cx, EditProps { state: signal })) //(signal.edit(cx)) } }, ) } } */ pub struct SignalWrapper<'a, T>(&'a Signal); #[component] pub fn something_with_signal<'a, G: Html, T: 'a>( cx: Scope<'a>, signal: EditProps<'a, T>, ) -> View { drop(signal); view! { cx, "Something" } } impl<'s: 'a, 'a, G, T> Editable<'s, 'a, G> for Vec where G: Html, T: Editable<'s, 'a, G> + Clone + PartialEq + 'a, { type Editor = VecEdit; } #[derive(Default, Clone, Debug)] pub struct Test { inner: TestInner, inner2: TestInner, } edit_struct!(Test => ("Inner", inner), ("Inner 2", inner2)); #[derive(Default, Clone, Debug)] pub struct TestInner { some_text: String, some_number: usize, } edit_struct!(TestInner => ("Text", some_text), ("Number", some_number));