Cleanup
This commit is contained in:
parent
2ddbfc2fdb
commit
667f2c5bbf
139
web/src/'
139
web/src/'
|
@ -1,139 +0,0 @@
|
||||||
use lan_party_core::user::User;
|
|
||||||
use log::debug;
|
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
|
||||||
use web_sys::Event;
|
|
||||||
|
|
||||||
use crate::util::api_request;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn UsersPage<'a, G: Html>(cx: Scope<'a>) -> View<G> {
|
|
||||||
let users = create_signal(cx, Vec::<User>::new());
|
|
||||||
|
|
||||||
spawn_local_scoped(cx, async move {
|
|
||||||
users.set(
|
|
||||||
api_request::<_, Vec<User>>(reqwasm::http::Method::GET, "/user", Option::<()>::None)
|
|
||||||
.await
|
|
||||||
.map(|inner| inner.unwrap())
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let handle_click = move |event: Event| {
|
|
||||||
debug!("{:#?}", event);
|
|
||||||
};
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
Page {
|
|
||||||
ul {
|
|
||||||
Keyed(
|
|
||||||
iterable=users,
|
|
||||||
view=|cx, user| view! { cx,
|
|
||||||
li { (user.name) }
|
|
||||||
},
|
|
||||||
key=|user| user.name.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(onclick=handle_click,text="Click me".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temp
|
|
||||||
|
|
||||||
#[derive(Prop)]
|
|
||||||
pub struct PageProps<'a, G: Html> {
|
|
||||||
pub children: Children<'a, G>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Page<'a, G: Html>(cx: Scope<'a>, props: PageProps<'a, G>) -> View<G> {
|
|
||||||
let children = props.children.call(cx);
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
div(class="max-w-7xl mx-auto") { (children) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Prop)]
|
|
||||||
pub struct Props<F: FnMut(Event)> {
|
|
||||||
pub onclick: F,
|
|
||||||
#[builder(default)]
|
|
||||||
pub text: String,
|
|
||||||
#[builder(default)]
|
|
||||||
pub icon: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Button<'a, G: Html, F: 'a + FnMut(Event)>(cx: Scope<'a>, props: Props<F>) -> View<G> {
|
|
||||||
let mut icon_class = String::from("mdi text-lg ");
|
|
||||||
|
|
||||||
if !props.icon.is_empty() {
|
|
||||||
icon_class.push_str(&props.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
button(class="bg-gray-700 hover:bg-gray-800 text-gray-400 font-bold py-2 px-2 rounded inline-flex items-center",on:click=props.onclick) {
|
|
||||||
span(class=icon_class)
|
|
||||||
span { (props.text) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Prop)]
|
|
||||||
pub struct TableProps<'a, G: Html> {
|
|
||||||
pub headers: Vec<String>,
|
|
||||||
pub rows: Vec<Vec<String>>,
|
|
||||||
pub loading: bool,
|
|
||||||
|
|
||||||
#[builder(default)]
|
|
||||||
pub children: Children<'a, G>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn table<'a, G: Html>(props: TableProps<'a, G>) -> View<G> {
|
|
||||||
let children = props.children.call(cx);
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
div(class="inline-block min-w-full py-2 align-middle") {
|
|
||||||
div(class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg") {
|
|
||||||
table(class="min-w-full divide-y divide-gray-600") {
|
|
||||||
thead(class="bg-gray-800") {
|
|
||||||
tr {
|
|
||||||
(props.headers.iter().map(|header| view! { cx,
|
|
||||||
th(
|
|
||||||
scope="col",
|
|
||||||
class="px-3 py-3.5 text-left
|
|
||||||
text-sm font-semibold text-gray-400"
|
|
||||||
) {
|
|
||||||
(header)
|
|
||||||
}
|
|
||||||
}).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody(class="divide-y divide-gray-600 bg-gray-700") {
|
|
||||||
(props.rows.iter().map(|row| view! { cx,
|
|
||||||
tr {
|
|
||||||
{row.iter().map(|value| html! {
|
|
||||||
td(class="whitespace-nowrap px-3 py-4 text-sm text-gray-400"){
|
|
||||||
(value)
|
|
||||||
}
|
|
||||||
}).collect()}
|
|
||||||
}
|
|
||||||
}).collect())
|
|
||||||
(children)
|
|
||||||
(if props.loading == true { view! {
|
|
||||||
tr {
|
|
||||||
td(colspan={(props.headers.len() + 1).to_string()} class="py-3") {
|
|
||||||
div(class="grid place-items-center") {
|
|
||||||
"Loading ..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}} else { view! {} })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,448 +0,0 @@
|
||||||
use crate::components::*;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use lan_party_core::event::{
|
|
||||||
free_for_all_game::{FreeForAllGame, FreeForAllGameRanking, FreeForAllGameSpec},
|
|
||||||
team_game::{TeamGame, TeamGameSpec},
|
|
||||||
test::{Test, TestSpec},
|
|
||||||
*,
|
|
||||||
};
|
|
||||||
use paste::paste;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
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)]
|
|
||||||
pub struct BlockProps {
|
|
||||||
title: String,
|
|
||||||
children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(Block)]
|
|
||||||
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! {
|
|
||||||
<>
|
|
||||||
<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()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait View {
|
|
||||||
fn view(&self) -> Html;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for bool {
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<input type="checkbox" value={self.to_string()} disabled={true} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ViewPlain: Into<Html> + std::fmt::Display {}
|
|
||||||
|
|
||||||
impl<T> View for T
|
|
||||||
where
|
|
||||||
T: ViewPlain,
|
|
||||||
{
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
impl<$t: View> View for $type {
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
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<K: View, V: View> View for HashMap<K, V> {
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<Block title={"Map"}>
|
|
||||||
{self.iter().map(|(k, v)| {
|
|
||||||
html! { <p><span>{k.view()}</span>{ ": " }<span>{v.view()}</span></p> }
|
|
||||||
}).collect::<Html>()}
|
|
||||||
</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>
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
macro_rules! edit_fields {
|
|
||||||
($(($name:expr, $prop:expr)),* $(,)?) => {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
$(
|
|
||||||
<p>
|
|
||||||
{ $name }
|
|
||||||
{ $prop.edit() }
|
|
||||||
</p>
|
|
||||||
)*
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! link_fields {
|
|
||||||
($($field:ident),* $(,)? => $state:ident as $t:ident) => {
|
|
||||||
$(let $field = use_state(|| $state.$field.clone());)*
|
|
||||||
|
|
||||||
use_effect_with_deps(
|
|
||||||
clone!($($field,)* $state =>
|
|
||||||
move |_| {
|
|
||||||
$state.set($t {
|
|
||||||
$($field: (*$field).clone(),)*
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|| { $(drop($field);)* drop($state); }
|
|
||||||
}
|
|
||||||
),
|
|
||||||
($($field.clone(),)*),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 [<$struct:lower _edit>](props: &EditProps<$struct>) -> Html {
|
|
||||||
let state = props.state.clone();
|
|
||||||
link_fields!($($prop,)* => state as $struct);
|
|
||||||
html! {
|
|
||||||
<Block title={stringify!($struct)}>
|
|
||||||
{ edit_fields!($(($name, $prop)),*) }
|
|
||||||
</Block>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Editable for $struct {
|
|
||||||
type Edit = [<$struct Edit>];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
|
||||||
pub struct EditProps<T: PartialEq> {
|
|
||||||
pub state: UseStateHandle<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Edit {
|
|
||||||
fn edit(&self) -> Html;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Comp: Component<Properties = EditProps<Type>>, Type: Editable<Edit = Comp> + PartialEq> Edit
|
|
||||||
for UseStateHandle<Type>
|
|
||||||
{
|
|
||||||
fn edit(&self) -> Html {
|
|
||||||
html!(<Comp state={self.clone()} />)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Editable {
|
|
||||||
type Edit: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(InputEdit)]
|
|
||||||
pub fn input_edit<T>(props: &EditProps<T>) -> Html
|
|
||||||
where
|
|
||||||
T: PartialEq + Clone + 'static,
|
|
||||||
Binding<String>: From<Binding<T>>,
|
|
||||||
{
|
|
||||||
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-50 leading-tight focus:outline-none focus:ring-1 focus:ring-gray-500 focus:border-gray-500"}
|
|
||||||
bind={bind!(string)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Editable for T
|
|
||||||
where
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -1,39 +0,0 @@
|
||||||
use crate::{
|
|
||||||
components::{
|
|
||||||
event::{Edit, EventSpecEdit},
|
|
||||||
Button, Page, View,
|
|
||||||
},
|
|
||||||
init,
|
|
||||||
};
|
|
||||||
use lan_party_core::event::EventSpec;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
use wasm_bindgen_futures::spawn_local;
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew_hooks::*;
|
|
||||||
|
|
||||||
use crate::{clone, clone_cb, util::api_request};
|
|
||||||
|
|
||||||
#[function_component(EventsPage)]
|
|
||||||
pub fn events_page() -> Html {
|
|
||||||
let events = use_state(|| Vec::new());
|
|
||||||
|
|
||||||
init!(events => {
|
|
||||||
events.set(api_request::<_, Vec<lan_party_core::event::Event>>(reqwasm::http::Method::GET, "/event", Option::<()>::None)
|
|
||||||
.await
|
|
||||||
.map(|inner| inner.unwrap())
|
|
||||||
.unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
//let edit_event = use_state(|| EventSpecEditHandle::to_edit(EventSpec::default()));
|
|
||||||
let event_spec = use_state(|| EventSpec::default());
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<Page>
|
|
||||||
{ 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>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
use crate::{
|
|
||||||
bind, bind_change, bind_value, clone, clone_cb, clone_cb_spawn,
|
|
||||||
components::{Binding, Button, Loading, Page, Table, TextInput},
|
|
||||||
init,
|
|
||||||
util::api_request,
|
|
||||||
};
|
|
||||||
use lan_party_core::user::User;
|
|
||||||
use wasm_bindgen_futures::spawn_local;
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew_hooks::*;
|
|
||||||
|
|
||||||
#[function_component(UsersPage)]
|
|
||||||
pub fn users_page() -> Html {
|
|
||||||
let headers = vec!["Username".into(), "Score".into(), "".into()];
|
|
||||||
|
|
||||||
let new_username = use_state(|| String::new());
|
|
||||||
let score_edit: UseStateHandle<Option<usize>> = use_state(|| Option::None);
|
|
||||||
let current_score = use_state(|| String::new());
|
|
||||||
let users = use_state(|| Vec::new());
|
|
||||||
|
|
||||||
init!(users => {
|
|
||||||
users.set(api_request::<_, Vec<User>>(reqwasm::http::Method::GET, "/user", Option::<()>::None)
|
|
||||||
.await
|
|
||||||
.map(|inner| inner.unwrap())
|
|
||||||
.unwrap());
|
|
||||||
});
|
|
||||||
|
|
||||||
let oncheck = clone_cb_spawn!(score_edit, current_score, users => {
|
|
||||||
if let (Some(score_edit), Ok(score)) = (*score_edit, current_score.parse()) {
|
|
||||||
let user: &User = &users[score_edit];
|
|
||||||
api_request::<_, ()>(reqwasm::http::Method::POST, &format!("/user/{}/score", user.name), Some(score))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let mut cloned = (*users).clone();
|
|
||||||
cloned[score_edit].score = score;
|
|
||||||
users.set(cloned);
|
|
||||||
}
|
|
||||||
score_edit.set(None);
|
|
||||||
});
|
|
||||||
|
|
||||||
let onedit = clone_cb!(current_score, score_edit, users => i => move |_| {
|
|
||||||
let user: &User = &users[i];
|
|
||||||
current_score.set(user.score.to_string());
|
|
||||||
score_edit.set(Some(i));
|
|
||||||
});
|
|
||||||
|
|
||||||
let ondelete = clone_cb_spawn!(users => i => {
|
|
||||||
let user: &User = &users[i];
|
|
||||||
api_request::<_, ()>(reqwasm::http::Method::DELETE, &format!("/user/{}", user.name), Option::<()>::None).await.unwrap();
|
|
||||||
let cloned = users.iter().cloned().filter(|u| u.name != user.name).collect();
|
|
||||||
users.set(cloned);
|
|
||||||
});
|
|
||||||
|
|
||||||
let onadd = clone_cb_spawn!(new_username, users => {
|
|
||||||
let user = api_request::<String, User>(reqwasm::http::Method::POST, "/user", Some((*new_username).clone())).await.unwrap();
|
|
||||||
let mut cloned = (*users).clone();
|
|
||||||
cloned.push(user.unwrap());
|
|
||||||
users.set(cloned);
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<Page>
|
|
||||||
<Table headers={headers.clone()} loading=false rows={vec![]}>
|
|
||||||
{users.iter().enumerate().map(move |(i, user)| html! {
|
|
||||||
<tr>
|
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-slate-400">{&user.name}</td>
|
|
||||||
<td class="whitespace-nowrap px-3 text-sm text-slate-400">
|
|
||||||
{if Some(i) == *score_edit.clone() { html! {
|
|
||||||
<>
|
|
||||||
<span class="inline-block">
|
|
||||||
<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!(current_score)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<Button icon={"mdi-check"} onclick={oncheck.clone()} />
|
|
||||||
</>
|
|
||||||
}} else { html! {
|
|
||||||
<>
|
|
||||||
<span class="my-3 w-20">
|
|
||||||
{user.score}
|
|
||||||
</span>
|
|
||||||
<Button icon={"mdi-pencil"} onclick={onedit.clone()(i)} />
|
|
||||||
</>
|
|
||||||
}}}
|
|
||||||
</td>
|
|
||||||
<td class="whitespace-nowrap py-4 text-sm text-slate-400">
|
|
||||||
<Button icon={"mdi-delete"} onclick={ondelete.clone()(i)} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}).collect::<Html>()}
|
|
||||||
<tr>
|
|
||||||
<td class="whitespace-nowrap px-3 text-sm text-slate-400">
|
|
||||||
<span class="inline-block">
|
|
||||||
<TextInput
|
|
||||||
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!(new_username)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-slate-400"></td>
|
|
||||||
<td class="whitespace-nowrap py-4 text-sm text-slate-400">
|
|
||||||
<Button icon={"mdi-plus"} onclick={onadd} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</Table>
|
|
||||||
</Page>
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue