Progress
This commit is contained in:
parent
d5fc504821
commit
0e17e870c4
|
@ -216,7 +216,7 @@ pub mod team_game {
|
||||||
pub ffa_game: FreeForAllGame,
|
pub ffa_game: FreeForAllGame,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct TeamGameSpec {
|
pub struct TeamGameSpec {
|
||||||
|
@ -344,7 +344,7 @@ pub mod free_for_all_game {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum FreeForAllGameRanking {
|
pub enum FreeForAllGameRanking {
|
||||||
|
@ -385,7 +385,7 @@ pub mod free_for_all_game {
|
||||||
vec![-3, -2, -1]
|
vec![-3, -2, -1]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct FreeForAllGameSpec {
|
pub struct FreeForAllGameSpec {
|
||||||
|
|
|
@ -8,7 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yew = "0.19"
|
yew = "0.19"
|
||||||
yew-router = "0.16"
|
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"] }
|
lan_party_core = { path = "../core", features = ["serde"] }
|
||||||
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
|
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
|
||||||
<title>Yew App</title>
|
<title>Yew App</title>
|
||||||
|
|
||||||
<link rel="preload" href="/index-f3d26bdebe032292_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
<link rel="preload" href="/index-a37beaf9d3547068_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||||
<link rel="modulepreload" href="/index-f3d26bdebe032292.js"></head>
|
<link rel="modulepreload" href="/index-a37beaf9d3547068.js"></head>
|
||||||
<body class="base theme-dark bg-gray-900 text-gray-400">
|
<body class="base theme-dark bg-gray-900 text-gray-400">
|
||||||
|
|
||||||
|
|
||||||
<script type="module">import init from '/index-f3d26bdebe032292.js';init('/index-f3d26bdebe032292_bg.wasm');</script></body></html>
|
<script type="module">import init from '/index-a37beaf9d3547068.js';init('/index-a37beaf9d3547068_bg.wasm');</script></body></html>
|
|
@ -2,8 +2,8 @@ use crate::components::*;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use lan_party_core::event::{
|
use lan_party_core::event::{
|
||||||
free_for_all_game::FreeForAllGame,
|
free_for_all_game::{FreeForAllGame, FreeForAllGameRanking, FreeForAllGameSpec},
|
||||||
team_game::TeamGame,
|
team_game::{TeamGame, TeamGameSpec},
|
||||||
test::{Test, TestSpec},
|
test::{Test, TestSpec},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
@ -113,26 +113,30 @@ impl View for bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! view_html {
|
pub trait ViewPlain: Into<Html> + std::fmt::Display {}
|
||||||
($t:ty) => {
|
|
||||||
impl View for $t {
|
impl<T> View for T
|
||||||
|
where
|
||||||
|
T: ViewPlain,
|
||||||
|
{
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
html! { self }
|
self.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
view_html!(i64);
|
impl ViewPlain for i64 {}
|
||||||
view_html!(i32);
|
impl ViewPlain for i32 {}
|
||||||
view_html!(isize);
|
impl ViewPlain for isize {}
|
||||||
view_html!(u64);
|
|
||||||
view_html!(u32);
|
impl ViewPlain for u64 {}
|
||||||
view_html!(usize);
|
impl ViewPlain for u32 {}
|
||||||
view_html!(f64);
|
impl ViewPlain for usize {}
|
||||||
view_html!(f32);
|
|
||||||
view_html!(String);
|
impl ViewPlain for f64 {}
|
||||||
view_html!(&'static str);
|
impl ViewPlain for f32 {}
|
||||||
|
|
||||||
|
impl ViewPlain for String {}
|
||||||
|
impl<'a> ViewPlain for &'a str {}
|
||||||
|
|
||||||
macro_rules! view_iter {
|
macro_rules! view_iter {
|
||||||
($t:ident => $type:ty) => {
|
($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 {
|
macro_rules! edit_struct {
|
||||||
($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => {
|
($struct:ident => $(($name:expr, $prop:ident)),* $(,)?) => {
|
||||||
paste! {
|
paste! {
|
||||||
#[function_component([<$struct Edit>])]
|
#[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();
|
let state = props.state.clone();
|
||||||
link_fields!(name, description => state as $struct);
|
link_fields!($($prop,)* => state as $struct);
|
||||||
html! {
|
html! {
|
||||||
<Block title={stringify!($struct)}>
|
<Block title={stringify!($struct)}>
|
||||||
{ edit_fields!(("Name: ", name), ("Description: ", description)) }
|
{ edit_fields!($(($name, $prop)),*) }
|
||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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! {
|
||||||
|
<Block title={stringify!($enum)}>
|
||||||
|
<Select class="" bind={bind!($selected)}>
|
||||||
|
$(<option value={stringify!($index)}>{ stringify!($variant) }</option>)*
|
||||||
|
</Select>
|
||||||
|
{ match &*state {
|
||||||
|
$($enum::$variant(_) => $var_name.edit(),)*
|
||||||
|
}}
|
||||||
|
</Block>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editable for $enum {
|
||||||
|
type Edit = [<$enum Edit>];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Properties)]
|
#[derive(PartialEq, Properties)]
|
||||||
pub struct EditProps<T: PartialEq> {
|
pub struct EditProps<T: PartialEq> {
|
||||||
pub state: UseStateHandle<T>,
|
pub state: UseStateHandle<T>,
|
||||||
|
@ -302,22 +361,88 @@ pub trait Editable {
|
||||||
type Edit;
|
type Edit;
|
||||||
}
|
}
|
||||||
|
|
||||||
edit_struct!(EventSpec => ("Name: ", name), ("Description: ", description));
|
#[function_component(InputEdit)]
|
||||||
|
pub fn input_edit<T>(props: &EditProps<T>) -> Html
|
||||||
#[function_component(StringEdit)]
|
where
|
||||||
pub fn string_edit(props: &EditProps<String>) -> Html {
|
T: PartialEq + Clone + 'static,
|
||||||
|
Binding<String>: From<Binding<T>>,
|
||||||
|
{
|
||||||
let string = props.state.clone();
|
let string = props.state.clone();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<TextInput
|
<TextInput
|
||||||
class={"mx-2 appearance-none block bg-gray-700 text-slate-400 border border-gray-600 rounded py-2 px-2 w-20 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500"}
|
class={"mx-2 appearance-none block bg-gray-700 text-slate-400 border border-gray-600 rounded py-2 px-2 w-50 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500"}
|
||||||
bind={bind!(string)}
|
bind={bind!(string)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Edit for UseStateHandle<String> {
|
impl<T> Editable for T
|
||||||
fn edit(&self) -> Html {
|
where
|
||||||
html!(<StringEdit state={self.clone()} />)
|
T: PartialEq + Clone + 'static,
|
||||||
|
Binding<String>: From<Binding<T>>,
|
||||||
|
{
|
||||||
|
type Edit = InputEdit<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(Stub)]
|
||||||
|
pub fn stub<T: PartialEq>(props: &EditProps<T>) -> Html {
|
||||||
|
html! { "stub" }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editable for Vec<String> {
|
||||||
|
type Edit = Stub<Vec<String>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editable for HashMap<String, i64> {
|
||||||
|
type Edit = Stub<HashMap<String, i64>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
1: scores = Scores: HashMap<String, i64>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[function_component(EventTypeSpecEdit)]
|
||||||
|
pub fn event_type_spec_edit(props: &EditProps<EventTypeSpec>) -> 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! {
|
||||||
|
<Block title={"EventTypeSpec"}>
|
||||||
|
<Select class="" bind={bind!(selected)}>
|
||||||
|
<option value="0">{ "Test" }</option>
|
||||||
|
<option value="1">{ "TeamGame" }</option>
|
||||||
|
<option value="2">{ "FreeForAllGame" }</option>
|
||||||
|
</Select>
|
||||||
|
{ match &*state {
|
||||||
|
EventTypeSpec::Test(_) => test.edit(),
|
||||||
|
EventTypeSpec::TeamGame(_) => team_game.edit(),
|
||||||
|
EventTypeSpec::FreeForAllGame(_) => free_for_all_game.edit(),
|
||||||
|
}}
|
||||||
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Editable for EventTypeSpec {
|
||||||
|
type Edit = EventTypeSpecEdit;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -7,8 +7,8 @@ pub use event::View;
|
||||||
pub use table::Table;
|
pub use table::Table;
|
||||||
use yew::{function_component, html, Children, Properties};
|
use yew::{function_component, html, Children, Properties};
|
||||||
|
|
||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
|
||||||
use web_sys::{Event, HtmlInputElement, InputEvent};
|
use web_sys::{Event, HtmlInputElement, HtmlSelectElement, InputEvent};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
|
@ -23,6 +23,24 @@ impl<T> Binding<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Binding<i64>> for Binding<String> {
|
||||||
|
fn from(val: Binding<i64>) -> Self {
|
||||||
|
Binding {
|
||||||
|
onchange: val.onchange.reform(|s: String| s.parse().unwrap_or(0)),
|
||||||
|
value: val.value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Binding<usize>> for Binding<String> {
|
||||||
|
fn from(val: Binding<usize>) -> Self {
|
||||||
|
Binding {
|
||||||
|
onchange: val.onchange.reform(|s: String| s.parse().unwrap_or(0)),
|
||||||
|
value: val.value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
pub struct InputProps {
|
pub struct InputProps {
|
||||||
pub bind: Binding<String>,
|
pub bind: Binding<String>,
|
||||||
|
@ -36,7 +54,6 @@ fn get_value_from_input_event(e: InputEvent) -> String {
|
||||||
target.value()
|
target.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controlled Text Input Component
|
|
||||||
#[function_component(TextInput)]
|
#[function_component(TextInput)]
|
||||||
pub fn text_input(props: &InputProps) -> Html {
|
pub fn text_input(props: &InputProps) -> Html {
|
||||||
let InputProps {
|
let InputProps {
|
||||||
|
@ -53,6 +70,40 @@ pub fn text_input(props: &InputProps) -> Html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct SelectProps {
|
||||||
|
pub bind: Binding<String>,
|
||||||
|
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! {
|
||||||
|
<select {class} {value} {oninput}>
|
||||||
|
{for children.iter()}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq, Default)]
|
#[derive(Properties, PartialEq, Default)]
|
||||||
pub struct PageProps {
|
pub struct PageProps {
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
|
|
|
@ -137,6 +137,6 @@ macro_rules! bind_change {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bind {
|
macro_rules! bind {
|
||||||
($value:ident) => {
|
($value:ident) => {
|
||||||
Binding::new(bind_value!($value), bind_change!($value))
|
Binding::<String>::from(Binding::new(bind_value!($value), bind_change!($value)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue