diff --git a/core/src/event.rs b/core/src/event.rs index e8a4b51..60756ba 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -216,7 +216,7 @@ pub mod team_game { pub ffa_game: FreeForAllGame, } - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Debug, PartialEq, Default)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TeamGameSpec { @@ -344,7 +344,7 @@ pub mod free_for_all_game { use super::*; - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FreeForAllGameRanking { @@ -385,7 +385,7 @@ pub mod free_for_all_game { vec![-3, -2, -1] } - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Debug, PartialEq, Default)] #[cfg_attr(feature = "openapi", derive(JsonSchema))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FreeForAllGameSpec { diff --git a/web/Cargo.toml b/web/Cargo.toml index 5bcc4f9..dad1bc1 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] yew = "0.19" yew-router = "0.16" -web-sys = { version = "0.3", features = ["Request", "RequestInit", "RequestMode", "Response", "Headers"] } +web-sys = { version = "0.3", features = ["Request", "RequestInit", "RequestMode", "Response", "Headers", "HtmlSelectElement"] } lan_party_core = { path = "../core", features = ["serde"] } wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" diff --git a/web/dist/index.html b/web/dist/index.html index f279540..b091628 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -4,9 +4,9 @@ Yew App - - + + - \ No newline at end of file + \ No newline at end of file diff --git a/web/src/components/event.rs b/web/src/components/event.rs index 8eaef7f..a8bfb4b 100644 --- a/web/src/components/event.rs +++ b/web/src/components/event.rs @@ -2,8 +2,8 @@ use crate::components::*; use std::collections::{HashMap, HashSet}; use lan_party_core::event::{ - free_for_all_game::FreeForAllGame, - team_game::TeamGame, + free_for_all_game::{FreeForAllGame, FreeForAllGameRanking, FreeForAllGameSpec}, + team_game::{TeamGame, TeamGameSpec}, test::{Test, TestSpec}, *, }; @@ -113,26 +113,30 @@ impl View for bool { } } -macro_rules! view_html { - ($t:ty) => { - impl View for $t { - fn view(&self) -> Html { - html! { self } - } - } - }; +pub trait ViewPlain: Into + std::fmt::Display {} + +impl View for T +where + T: ViewPlain, +{ + fn view(&self) -> Html { + self.into() + } } -view_html!(i64); -view_html!(i32); -view_html!(isize); -view_html!(u64); -view_html!(u32); -view_html!(usize); -view_html!(f64); -view_html!(f32); -view_html!(String); -view_html!(&'static str); +impl ViewPlain for i64 {} +impl ViewPlain for i32 {} +impl ViewPlain for isize {} + +impl ViewPlain for u64 {} +impl ViewPlain for u32 {} +impl ViewPlain for usize {} + +impl ViewPlain for f64 {} +impl ViewPlain for f32 {} + +impl ViewPlain for String {} +impl<'a> ViewPlain for &'a str {} macro_rules! view_iter { ($t:ident => $type:ty) => { @@ -260,16 +264,40 @@ macro_rules! link_fields { }; } +macro_rules! link_variants { + ($selected:ident => $($index:literal: $var_name:ident = $variant:ident: $var_type:ty),* $(,)? => $state:ident as $t:ident) => { + let $selected = use_state(|| 0 as usize); + $(let $var_name = if let $t::$variant(v) = &*$state { + use_state(|| v.clone()) + } else { + use_state(|| <$var_type>::default()) + };)* + + use_effect_with_deps( + clone!($($var_name,)* $state, $selected => + move |_| { + match *$selected { + $($index => $state.set($t::$variant((*$var_name).clone())),)* + _ => unreachable!() + } + || { $(drop($var_name);)* drop($selected); drop($state); } + } + ), + ($($var_name.clone(),)* $selected.clone()), + ); + }; +} + macro_rules! edit_struct { ($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => { paste! { #[function_component([<$struct Edit>])] - pub fn event_spec_edit(props: &EditProps<$struct>) -> Html { + pub fn [<$struct:lower _edit>](props: &EditProps<$struct>) -> Html { let state = props.state.clone(); - link_fields!(name, description => state as $struct); + link_fields!($($prop,)* => state as $struct); html! { - { edit_fields!(("Name: ", name), ("Description: ", description)) } + { edit_fields!($(($name, $prop)),*) } } } @@ -281,6 +309,37 @@ macro_rules! edit_struct { }; } +macro_rules! edit_enum { + ($enum:ident => $selected:ident => $($index:literal: $var_name:ident = $variant:ident: $var_type:ty),* $(,)?) => { + paste! { + #[function_component([<$enum Edit>])] + pub fn [<$enum:lower _edit>](props: &EditProps<$enum>) -> Html { + let state = props.state.clone(); + + link_variants!($selected => + $($index: $var_name = $variant: $var_type,)* + => state as $enum + ); + + html! { + + + { match &*state { + $($enum::$variant(_) => $var_name.edit(),)* + }} + + } + } + + impl Editable for $enum { + type Edit = [<$enum Edit>]; + } + } + }; +} + #[derive(PartialEq, Properties)] pub struct EditProps { pub state: UseStateHandle, @@ -302,22 +361,88 @@ pub trait Editable { type Edit; } -edit_struct!(EventSpec => ("Name: ", name), ("Description: ", description)); - -#[function_component(StringEdit)] -pub fn string_edit(props: &EditProps) -> Html { +#[function_component(InputEdit)] +pub fn input_edit(props: &EditProps) -> Html +where + T: PartialEq + Clone + 'static, + Binding: From>, +{ let string = props.state.clone(); html! { } } -impl Edit for UseStateHandle { - fn edit(&self) -> Html { - html!() +impl Editable for T +where + T: PartialEq + Clone + 'static, + Binding: From>, +{ + type Edit = InputEdit; +} + +#[function_component(Stub)] +pub fn stub(props: &EditProps) -> Html { + html! { "stub" } +} + +impl Editable for Vec { + type Edit = Stub>; +} + +impl Editable for HashMap { + type Edit = Stub>; +} + +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 => ); + +edit_enum!(EventTypeSpec => selected => + 0: test = Test: TestSpec, + 1: team_game = TeamGame: TeamGameSpec, + 2: free_for_all_game = FreeForAllGame: FreeForAllGameSpec +); + +edit_enum!(FreeForAllGameRanking => selected => + 0: ranking = Ranking: Vec, + 1: scores = Scores: HashMap, +); + +/* +#[function_component(EventTypeSpecEdit)] +pub fn event_type_spec_edit(props: &EditProps) -> Html { + let state = props.state.clone(); + + link_variants!(selected => + 0: test = Test: TestSpec, + 1: team_game = TeamGame: TeamGameSpec, + 2: free_for_all_game = FreeForAllGame: FreeForAllGameSpec + => state as EventTypeSpec + ); + + html! { + + + { match &*state { + EventTypeSpec::Test(_) => test.edit(), + EventTypeSpec::TeamGame(_) => team_game.edit(), + EventTypeSpec::FreeForAllGame(_) => free_for_all_game.edit(), + }} + } } + +impl Editable for EventTypeSpec { + type Edit = EventTypeSpecEdit; +} +*/ diff --git a/web/src/components/mod.rs b/web/src/components/mod.rs index 1202e50..0dd3837 100644 --- a/web/src/components/mod.rs +++ b/web/src/components/mod.rs @@ -7,8 +7,8 @@ pub use event::View; pub use table::Table; use yew::{function_component, html, Children, Properties}; -use wasm_bindgen::{JsCast, UnwrapThrowExt}; -use web_sys::{Event, HtmlInputElement, InputEvent}; +use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt}; +use web_sys::{Event, HtmlInputElement, HtmlSelectElement, InputEvent}; use yew::prelude::*; #[derive(Clone, PartialEq)] @@ -23,6 +23,24 @@ impl Binding { } } +impl From> for Binding { + fn from(val: Binding) -> Self { + Binding { + onchange: val.onchange.reform(|s: String| s.parse().unwrap_or(0)), + value: val.value.to_string(), + } + } +} + +impl From> for Binding { + fn from(val: Binding) -> Self { + Binding { + onchange: val.onchange.reform(|s: String| s.parse().unwrap_or(0)), + value: val.value.to_string(), + } + } +} + #[derive(Clone, PartialEq, Properties)] pub struct InputProps { pub bind: Binding, @@ -36,7 +54,6 @@ fn get_value_from_input_event(e: InputEvent) -> String { target.value() } -/// Controlled Text Input Component #[function_component(TextInput)] pub fn text_input(props: &InputProps) -> Html { let InputProps { @@ -53,6 +70,40 @@ pub fn text_input(props: &InputProps) -> Html { } } +#[derive(Clone, PartialEq, Properties)] +pub struct SelectProps { + pub bind: Binding, + pub class: Classes, + pub children: Children, +} + +fn get_value_from_select_event(e: InputEvent) -> String { + let event: Event = e.dyn_into().unwrap_throw(); + let event_target = event.target().unwrap_throw(); + let target: HtmlSelectElement = event_target.dyn_into().unwrap_throw(); + target.value() +} + +#[function_component(Select)] +pub fn select(props: &SelectProps) -> Html { + let SelectProps { + class, + children, + bind: Binding { onchange, value }, + } = props.clone(); + + let oninput = Callback::from(move |event: InputEvent| { + web_sys::console::log_1(&JsValue::from("select")); + onchange.emit(get_value_from_select_event(event)); + }); + + html! { + + } +} + #[derive(Properties, PartialEq, Default)] pub struct PageProps { #[prop_or_default] diff --git a/web/src/util.rs b/web/src/util.rs index 8f55a0d..a616da0 100644 --- a/web/src/util.rs +++ b/web/src/util.rs @@ -137,6 +137,6 @@ macro_rules! bind_change { #[macro_export] macro_rules! bind { ($value:ident) => { - Binding::new(bind_value!($value), bind_change!($value)) + Binding::::from(Binding::new(bind_value!($value), bind_change!($value))) }; }