Reactive experiments

This commit is contained in:
Daan Vanoverloop 2022-09-06 11:55:22 +02:00
parent cdc056dadc
commit 53cb2a22b1
Signed by: Danacus
GPG Key ID: F2272B50E129FC5C
5 changed files with 357 additions and 44 deletions

View File

@ -48,7 +48,7 @@ impl Event {
/// # EventSpec /// # EventSpec
/// ///
/// A specification of an event /// A specification of an event
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, 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 struct EventSpec { pub struct EventSpec {
@ -63,7 +63,7 @@ macro_rules! events {
/// # EventTypeSpec /// # EventTypeSpec
/// ///
/// A specification of an event type /// A specification of an event type
#[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 EventTypeSpec { pub enum EventTypeSpec {
@ -146,7 +146,7 @@ pub trait EventTrait {
} }
} }
mod test { pub mod test {
use super::*; use super::*;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -156,7 +156,7 @@ mod test {
pub num_players: i64, pub num_players: i64,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, 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 struct TestSpec { pub struct TestSpec {
@ -195,7 +195,7 @@ mod test {
} }
} }
mod team_game { pub mod team_game {
use super::{ use super::{
free_for_all_game::{ free_for_all_game::{
FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate, FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate,
@ -216,7 +216,7 @@ mod team_game {
pub ffa_game: FreeForAllGame, pub ffa_game: FreeForAllGame,
} }
#[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 struct TeamGameSpec { pub struct TeamGameSpec {
@ -339,7 +339,7 @@ mod team_game {
} }
} }
mod free_for_all_game { pub mod free_for_all_game {
use std::collections::HashSet; use std::collections::HashSet;
use super::*; use super::*;
@ -385,7 +385,7 @@ mod free_for_all_game {
vec![-3, -2, -1] vec![-3, -2, -1]
} }
#[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 struct FreeForAllGameSpec { pub struct FreeForAllGameSpec {
@ -554,3 +554,9 @@ mod free_for_all_game {
} }
events!(test => Test, team_game => TeamGame, free_for_all_game => FreeForAllGame); events!(test => Test, team_game => TeamGame, free_for_all_game => FreeForAllGame);
impl Default for EventTypeSpec {
fn default() -> Self {
Self::Test(test::TestSpec::default())
}
}

6
web/dist/index.html vendored
View File

@ -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-b24e51ea036df368_bg.wasm" as="fetch" type="application/wasm" crossorigin=""> <link rel="preload" href="/index-eca47f078afaab22_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
<link rel="modulepreload" href="/index-b24e51ea036df368.js"></head> <link rel="modulepreload" href="/index-eca47f078afaab22.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-b24e51ea036df368.js';init('/index-b24e51ea036df368_bg.wasm');</script></body></html> <script type="module">import init from '/index-eca47f078afaab22.js';init('/index-eca47f078afaab22_bg.wasm');</script></body></html>

View File

@ -1,31 +1,110 @@
use lan_party_core::event::*; use crate::components::*;
use std::collections::{HashMap, HashSet};
use lan_party_core::event::{
free_for_all_game::FreeForAllGame,
team_game::TeamGame,
test::{Test, TestSpec},
*,
};
use wasm_bindgen::JsValue;
use yew::prelude::*; use yew::prelude::*;
use crate::{bind, bind_change, bind_value, clone, clone_cb};
macro_rules! view_fields {
($(($name:expr, $prop:expr)),* $(,)?) => {
html! {
<>
$(
<p>
{ $name }
{ $prop.view() }
</p>
)*
</>
}
};
}
macro_rules! view_struct {
($struct:path: $title:expr => $(($name:expr, $prop:ident)),* $(,)?) => {
impl View for $struct {
fn view(&self) -> Html {
html! {
<Block title={$title}>
{ view_fields!(
$(($name, self.$prop),)*
)}
</Block>
}
}
}
};
($struct:path as $self:ident => $body:expr) => {
impl View for $struct {
fn view(&$self) -> Html {
$body
}
}
};
}
macro_rules! view_enum_simple {
($enum:path: $($variant:ident),* $(,)?) => {
impl View for $enum {
fn view(&self) -> Html {
html! {
{match self {
$(
Self::$variant(i) => html! {
<>
{ i.view() }
</>
},
)*
}}
}
}
}
};
}
#[derive(Debug, Clone, Properties, PartialEq)] #[derive(Debug, Clone, Properties, PartialEq)]
pub struct BlockProps { pub struct BlockProps {
title: String,
children: Children, children: Children,
} }
#[function_component(Block)] #[function_component(Block)]
pub fn block(props: &BlockProps) -> Html { pub fn block(props: &BlockProps) -> Html {
let open = use_state(|| false);
let mut class = classes!("overflow-hidden");
if !*open {
class.push("max-h-1");
}
html! { html! {
<>
<div class="px-3 py-3 rounded-lg border-solid border-gray-800 border-2 my-3"> <div class="px-3 py-3 rounded-lg border-solid border-gray-800 border-2 my-3">
<p class="cursor-pointer text-lg" onclick={clone_cb!(open => move |_| open.set(!*open))}>{ &props.title }</p>
<div {class}>
<br />
<div>
{for props.children.iter()} {for props.children.iter()}
</div> </div>
</div>
</div>
</>
} }
} }
pub trait RenderEvent { pub trait View {
fn view(&self) -> Html; fn view(&self) -> Html;
} }
impl RenderEvent for String { impl View for bool {
fn view(&self) -> Html {
html! { self }
}
}
impl RenderEvent for bool {
fn view(&self) -> Html { fn view(&self) -> Html {
html! { html! {
<input type="checkbox" value={self.to_string()} disabled={true} /> <input type="checkbox" value={self.to_string()} disabled={true} />
@ -33,33 +112,249 @@ impl RenderEvent for bool {
} }
} }
impl<T: RenderEvent> RenderEvent for Vec<T> { macro_rules! view_html {
($t:ty) => {
impl View for $t {
fn view(&self) -> Html {
html! { self }
}
}
};
}
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);
macro_rules! view_iter {
($t:ident => $type:ty) => {
impl<$t: View> View for $type {
fn view(&self) -> Html { fn view(&self) -> Html {
html! { html! {
self.iter().map(|x| x.view()).collect::<Html>() <Block title="List">
<ul>
{ self.iter().map(|x| html! { <li>{ x.view() }</li> }).collect::<Html>() }
</ul>
</Block>
}
}
}
};
}
view_iter!(T => Vec<T>);
view_iter!(T => HashSet<T>);
view_iter!(T => &[T]);
impl<T: View> View for Option<T> {
fn view(&self) -> Html {
match self {
Some(content) => content.view(),
None => html! { "None" },
} }
} }
} }
impl<T: RenderEvent> RenderEvent for &[T] { impl<K: View, V: View> View for HashMap<K, V> {
fn view(&self) -> Html { fn view(&self) -> Html {
html! { html! {
self.iter().map(|x| x.view()).collect::<Html>() <Block title={"Map"}>
} {self.iter().map(|(k, v)| {
} html! { <p><span>{k.view()}</span>{ ": " }<span>{v.view()}</span></p> }
} }).collect::<Html>()}
impl RenderEvent for lan_party_core::event::Event {
fn view(&self) -> Html {
html! {
<Block>
{ "Name: " }
{ self.name.view() }<br />
{ "Description: " }
{ self.description.view() }<br />
{ "Concluded: " }
{ self.concluded.view() }<br />
</Block> </Block>
} }
} }
} }
view_struct!(
lan_party_core::event::Event: "Event" =>
("Name: ", name),
("Description: ", description),
("Concluded: ", concluded),
("Event type: ", event_type),
);
view_enum_simple!(
lan_party_core::event::EventType: Test,
TeamGame,
FreeForAllGame
);
view_struct!(
lan_party_core::event::test::Test: "Test" =>
("Number of players: ", num_players)
);
view_struct!(FreeForAllGame as self =>
html! {
<Block title={"FreeForAllGame"}>
{ view_fields!(("Ranking: ", self.ranking)) }
{ view_fields!(
("Participants: ", self.spec.participants),
("Win rewards: ", self.spec.win_rewards),
("Lose rewards: ", self.spec.lose_rewards),
) }
</Block>
}
);
view_enum_simple!(
lan_party_core::event::free_for_all_game::FreeForAllGameRanking: Ranking,
Scores
);
view_struct!(TeamGame as self =>
html! {
<Block title={"TeamGame"}>
{ view_fields!(("Teams: ", self.teams)) }
{ view_fields!(("Ranking: ", self.ffa_game.ranking)) }
{ view_fields!(
("Participants: ", self.ffa_game.spec.participants),
("Win rewards: ", self.ffa_game.spec.win_rewards),
("Lose rewards: ", self.ffa_game.spec.lose_rewards),
) }
</Block>
}
);
#[derive(PartialEq, Properties)]
pub struct EditProps<T: PartialEq> {
pub state: UseStateHandle<T>,
}
pub trait Edit {
fn edit(&self) -> Html;
}
#[function_component(EventSpecEdit)]
pub fn event_spec_edit(props: &EditProps<EventSpec>) -> Html {
let state = props.state.clone();
let name = use_state(|| state.name.clone());
let description = use_state(|| state.description.clone());
use_effect_with_deps(
clone!(name, description, state =>
move |_| {
web_sys::console::log_1(&JsValue::from("Update EventSpec"));
state.set(EventSpec {
name: (*name).clone(),
description: (*description).clone(),
..Default::default()
});
|| { drop(state); drop(name); drop(description) }
}
),
(name.clone(), description.clone()),
);
html! {
<>
{ "Name: " }
{ name.edit() }
{ "Description: " }
{ description.edit() }
</>
}
}
impl Edit for UseStateHandle<EventSpec> {
fn edit(&self) -> Html {
html!(<EventSpecEdit state={self.clone()} />)
}
}
#[function_component(StringEdit)]
pub fn string_edit(props: &EditProps<String>) -> Html {
let string = props.state.clone();
html! {
<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"}
bind={bind!(string)}
/>
}
}
impl Edit for UseStateHandle<String> {
fn edit(&self) -> Html {
html!(<StringEdit state={self.clone()} />)
}
}
/*
pub trait Edit {
type Type;
fn to_edit(t: Self::Type) -> Self;
fn edit(&self) -> Html;
fn build(&self) -> Self::Type;
}
pub struct EventSpecEditHandle {
name: UseStateHandle<String>,
description: UseStateHandle<String>,
}
impl Edit for UseStateHandle<String> {
type Type = String;
fn to_edit(t: Self::Type) -> Self {
use_state(|| t)
}
fn edit(&self) -> Html {
let this = self.clone();
html! {
<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"}
bind={bind!(this)}
/>
}
}
fn build(&self) -> Self::Type {
(*self.clone()).clone()
}
}
impl Edit for EventSpecEditHandle {
type Type = EventSpec;
fn to_edit(t: Self::Type) -> Self {
Self {
name: use_state(|| t.name),
description: use_state(|| t.description),
}
}
fn edit(&self) -> Html {
html! {
<>
{ "Name: " }
{ self.name.edit() }
{ "Description: " }
{ self.description.edit() }
</>
}
}
fn build(&self) -> Self::Type {
let test_spec = TestSpec { num_players: 0 };
Self::Type {
name: self.name.build(),
description: self.description.build(),
event_type: EventTypeSpec::Test(test_spec),
}
}
}
*/

View File

@ -1,9 +1,9 @@
mod button; mod button;
mod event; pub mod event;
mod table; mod table;
pub use button::Button; pub use button::Button;
pub use event::RenderEvent; 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};

View File

@ -1,12 +1,17 @@
use crate::{ use crate::{
components::{Page, RenderEvent}, components::{
event::{Edit, EventSpecEdit},
Button, Page, View,
},
init, init,
}; };
use lan_party_core::event::EventSpec;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use yew::prelude::*; use yew::prelude::*;
use yew_hooks::*; use yew_hooks::*;
use crate::{clone, util::api_request}; use crate::{clone, clone_cb, util::api_request};
#[function_component(EventsPage)] #[function_component(EventsPage)]
pub fn events_page() -> Html { pub fn events_page() -> Html {
@ -19,9 +24,16 @@ pub fn events_page() -> Html {
.unwrap()) .unwrap())
}); });
//let edit_event = use_state(|| EventSpecEditHandle::to_edit(EventSpec::default()));
let event_spec = use_state(|| EventSpec::default());
html! { html! {
<Page> <Page>
{ events.view() } { events.view() }
{ event_spec.edit() }
<Button text="Create" onclick={clone_cb!(event_spec => move |_| web_sys::console::log_1(&JsValue::from_serde(&*event_spec).unwrap()))} />
</Page> </Page>
} }
} }