It's a buggy mess, but it works
This commit is contained in:
parent
3ef2c282b0
commit
f2d6d3a20f
|
@ -376,8 +376,18 @@ version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.13.4",
|
||||||
"darling_macro",
|
"darling_macro 0.13.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.14.1",
|
||||||
|
"darling_macro 0.14.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -394,13 +404,38 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.13.4",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.14.1",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
@ -1173,6 +1208,7 @@ name = "lan_party_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
|
"darling 0.14.1",
|
||||||
"paste",
|
"paste",
|
||||||
"quote",
|
"quote",
|
||||||
"sycamore",
|
"sycamore",
|
||||||
|
@ -1184,7 +1220,9 @@ name = "lan_party_web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"console_log",
|
"console_log",
|
||||||
|
"futures",
|
||||||
"gloo-timers",
|
"gloo-timers",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lan_party_core",
|
"lan_party_core",
|
||||||
|
@ -1965,7 +2003,7 @@ version = "0.8.0-rc.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54f94d1ffe41472e08463d7a2674f1db04dc4df745285e8369b33d3cfd6b0308"
|
checksum = "54f94d1ffe41472e08463d7a2674f1db04dc4df745285e8369b33d3cfd6b0308"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.13.4",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rocket_http",
|
"rocket_http",
|
||||||
|
@ -2213,7 +2251,7 @@ version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.13.4",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub async fn apply_outcome(outcome: &EventOutcome, db: &Connection<Db>) -> Resul
|
||||||
for (player, reward) in outcome.points.iter() {
|
for (player, reward) in outcome.points.iter() {
|
||||||
db.users()
|
db.users()
|
||||||
.update_one(
|
.update_one(
|
||||||
doc! { "id": player },
|
doc! { "id": player.to_string() },
|
||||||
doc! { "$inc": { "score": reward } },
|
doc! { "$inc": { "score": reward } },
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub fn Block<'a, G: Html>(cx: Scope<'a>, props: BlockProps<'a, G>) -> View<G> {
|
||||||
let children = props.children.call(cx);
|
let children = props.children.call(cx);
|
||||||
|
|
||||||
view! { cx,
|
view! { cx,
|
||||||
details {
|
details(open=true) {
|
||||||
summary { (props.title) }
|
summary { (props.title) }
|
||||||
p { (children) }
|
p { (children) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,11 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::components::Block;
|
use crate::{
|
||||||
|
components::Block,
|
||||||
|
util::{ContextOptions, WithContext},
|
||||||
|
view::Viewable,
|
||||||
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
@ -263,7 +267,7 @@ pub struct VecEdit;
|
||||||
impl<'a, G, T, I> Editor<'a, G, I> for VecEdit
|
impl<'a, G, T, I> Editor<'a, G, I> for VecEdit
|
||||||
where
|
where
|
||||||
G: Html,
|
G: Html,
|
||||||
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + 'a,
|
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + std::fmt::Debug + 'a,
|
||||||
I: IntoIterator<Item = T> + FromIterator<T> + Clone,
|
I: IntoIterator<Item = T> + FromIterator<T> + Clone,
|
||||||
{
|
{
|
||||||
fn edit(cx: Scope<'a>, props: EditProps<'a, I>) -> View<G> {
|
fn edit(cx: Scope<'a>, props: EditProps<'a, I>) -> View<G> {
|
||||||
|
@ -294,6 +298,7 @@ where
|
||||||
let onremove = move |item: &'a Signal<T>| {
|
let onremove = move |item: &'a Signal<T>| {
|
||||||
move |_| {
|
move |_| {
|
||||||
let cloned = vec.get().as_ref().clone();
|
let cloned = vec.get().as_ref().clone();
|
||||||
|
debug!("{:#?}", item.get());
|
||||||
vec.set(
|
vec.set(
|
||||||
cloned
|
cloned
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -332,7 +337,7 @@ where
|
||||||
impl<'a, G, T> Editable<'a, G> for Vec<T>
|
impl<'a, G, T> Editable<'a, G> for Vec<T>
|
||||||
where
|
where
|
||||||
G: Html,
|
G: Html,
|
||||||
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + 'a,
|
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + std::fmt::Debug + 'a,
|
||||||
{
|
{
|
||||||
type Editor = VecEdit;
|
type Editor = VecEdit;
|
||||||
}
|
}
|
||||||
|
@ -340,7 +345,7 @@ where
|
||||||
impl<'a, G, T> Editable<'a, G> for HashSet<T>
|
impl<'a, G, T> Editable<'a, G> for HashSet<T>
|
||||||
where
|
where
|
||||||
G: Html,
|
G: Html,
|
||||||
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + 'a,
|
T: for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + std::fmt::Debug + 'a,
|
||||||
{
|
{
|
||||||
type Editor = VecEdit;
|
type Editor = VecEdit;
|
||||||
}
|
}
|
||||||
|
@ -350,7 +355,8 @@ where
|
||||||
G: Html,
|
G: Html,
|
||||||
K: Clone + Hash + Eq,
|
K: Clone + Hash + Eq,
|
||||||
V: Clone,
|
V: Clone,
|
||||||
(K, V): for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + 'a,
|
(K, V):
|
||||||
|
for<'b> Editable<'b, G> + Clone + PartialEq + Default + Hash + Eq + std::fmt::Debug + 'a,
|
||||||
{
|
{
|
||||||
type Editor = VecEdit;
|
type Editor = VecEdit;
|
||||||
}
|
}
|
||||||
|
@ -458,6 +464,58 @@ impl From<User> for WithLabel<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ContextEdit;
|
||||||
|
|
||||||
|
impl<'a, G, Ctx, T> Editor<'a, G, WithContext<Ctx, T>> for ContextEdit
|
||||||
|
where
|
||||||
|
G: Html,
|
||||||
|
Ctx: ContextOptions<T> + 'static,
|
||||||
|
T: FromStr + ToString + Default + Clone + PartialEq,
|
||||||
|
{
|
||||||
|
fn edit(cx: Scope<'a>, props: EditProps<'a, WithContext<Ctx, T>>) -> View<G> {
|
||||||
|
let value = create_signal(cx, props.state.get_untracked().to_string());
|
||||||
|
|
||||||
|
let ctx = use_context::<Ctx>(cx);
|
||||||
|
|
||||||
|
create_effect(cx, || {
|
||||||
|
debug!(
|
||||||
|
"{}",
|
||||||
|
(*value.get())
|
||||||
|
.parse::<T>()
|
||||||
|
.unwrap_or(T::default())
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
props
|
||||||
|
.state
|
||||||
|
.set((*value.get()).parse::<T>().unwrap_or(T::default()).into())
|
||||||
|
});
|
||||||
|
|
||||||
|
view! { cx,
|
||||||
|
select(bind:value=value) {
|
||||||
|
Indexed(
|
||||||
|
iterable=ctx.options(cx),
|
||||||
|
view=move |cx: BoundedScope<'_, 'a>, x| {
|
||||||
|
let x = create_ref(cx, x);
|
||||||
|
view! { cx,
|
||||||
|
option(value=x.to_string()) { (x.to_string()) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
" "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, G, Ctx, T> Editable<'a, G> for WithContext<Ctx, T>
|
||||||
|
where
|
||||||
|
G: Html,
|
||||||
|
Ctx: ContextOptions<T> + 'static,
|
||||||
|
T: FromStr + ToString + Default + Clone + PartialEq,
|
||||||
|
{
|
||||||
|
type Editor = ContextEdit;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct Test {
|
pub struct Test {
|
||||||
inner: TestInner,
|
inner: TestInner,
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#[cfg(feature = "sycamore")]
|
#[cfg(feature = "sycamore")]
|
||||||
use crate::edit::prelude::*;
|
use crate::edit::prelude::*;
|
||||||
use crate::util::PartyError;
|
|
||||||
#[cfg(feature = "sycamore")]
|
#[cfg(feature = "sycamore")]
|
||||||
use crate::view::prelude::*;
|
use crate::view::prelude::*;
|
||||||
|
use crate::{
|
||||||
|
state::Users,
|
||||||
|
util::{PartyError, WithContext},
|
||||||
|
};
|
||||||
#[cfg(feature = "sycamore")]
|
#[cfg(feature = "sycamore")]
|
||||||
use lan_party_macros::{web_view_attr, WebEdit, WebView};
|
use lan_party_macros::{WebEdit, WebView};
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
@ -15,11 +18,14 @@ use std::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type User = WithContext<Users, String>;
|
||||||
|
type Team = String;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[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))]
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub enum Ranking<T: Hash + PartialEq + Eq + Clone + Default> {
|
pub enum Ranking<T: Hash + PartialEq + Eq + Clone + Default + std::fmt::Debug> {
|
||||||
/// Ranking of participants by user name or team name (first element is first place, second element is second
|
/// Ranking of participants by user name or team name (first element is first place, second element is second
|
||||||
/// place, etc.)
|
/// place, etc.)
|
||||||
Ranking(Vec<T>),
|
Ranking(Vec<T>),
|
||||||
|
@ -27,13 +33,13 @@ pub enum Ranking<T: Hash + PartialEq + Eq + Clone + Default> {
|
||||||
Scores(HashMap<T, i64>),
|
Scores(HashMap<T, i64>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Hash + PartialEq + Eq + Clone + Default> Default for Ranking<T> {
|
impl<T: Hash + PartialEq + Eq + Clone + Default + std::fmt::Debug> Default for Ranking<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Ranking(Vec::default())
|
Self::Ranking(Vec::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Hash + PartialEq + Eq + Clone + Default> Ranking<T> {
|
impl<T: Hash + PartialEq + Eq + Clone + Default + std::fmt::Debug> Ranking<T> {
|
||||||
pub fn is_valid(&self, participants: &HashSet<T>) -> bool {
|
pub fn is_valid(&self, participants: &HashSet<T>) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Ranking(v) => v.iter().all(|p| participants.contains(p)),
|
Self::Ranking(v) => v.iter().all(|p| participants.contains(p)),
|
||||||
|
@ -42,11 +48,19 @@ impl<T: Hash + PartialEq + Eq + Clone + Default> Ranking<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug)]
|
||||||
#[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 EventOutcome {
|
pub struct EventOutcome {
|
||||||
pub points: HashMap<String, i64>,
|
pub points: HashMap<User, i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EventOutcome {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
points: HashMap::<User, _>::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Event
|
/// # Event
|
||||||
|
@ -56,7 +70,7 @@ pub struct EventOutcome {
|
||||||
#[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))]
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebView))]
|
||||||
#[cfg_attr(feature = "sycamore", web_view_attr(title = "name"))]
|
#[cfg_attr(feature = "sycamore", view(title = "name"))]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
/// Has this event concluded?
|
/// Has this event concluded?
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
@ -239,13 +253,15 @@ pub mod test {
|
||||||
|
|
||||||
fn outcome(&self) -> EventOutcome {
|
fn outcome(&self) -> EventOutcome {
|
||||||
let mut points = HashMap::new();
|
let mut points = HashMap::new();
|
||||||
points.insert("420".into(), self.num_players);
|
points.insert("420".to_string().into(), self.num_players);
|
||||||
EventOutcome { points }
|
EventOutcome { points }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod team_game {
|
pub mod team_game {
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
free_for_all_game::{FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate},
|
free_for_all_game::{FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate},
|
||||||
*,
|
*,
|
||||||
|
@ -257,7 +273,7 @@ pub mod team_game {
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub struct TeamGame {
|
pub struct TeamGame {
|
||||||
/// Map of teams with a name as key and an array of players as value
|
/// Map of teams with a name as key and an array of players as value
|
||||||
pub teams: HashMap<String, Vec<String>>,
|
pub teams: HashMap<Team, Vec<User>>,
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", serde(flatten))]
|
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||||
pub ffa_game: FreeForAllGame,
|
pub ffa_game: FreeForAllGame,
|
||||||
|
@ -269,7 +285,7 @@ pub mod team_game {
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub struct TeamGameSpec {
|
pub struct TeamGameSpec {
|
||||||
/// Map of teams with a name as key and an array of players as value
|
/// Map of teams with a name as key and an array of players as value
|
||||||
pub teams: HashMap<String, Vec<String>>,
|
pub teams: HashMap<Team, Vec<User>>,
|
||||||
|
|
||||||
/// Rewards for winning the game (first element for first place, second element for second
|
/// Rewards for winning the game (first element for first place, second element for second
|
||||||
/// place, etc.)
|
/// place, etc.)
|
||||||
|
@ -292,16 +308,8 @@ pub mod team_game {
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub struct TeamGameUpdateSetTeam {
|
pub struct TeamGameUpdateSetTeam {
|
||||||
pub team: String,
|
pub team: Team,
|
||||||
pub members: Vec<String>,
|
pub members: Vec<User>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
|
||||||
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
|
||||||
pub struct Team {
|
|
||||||
name: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -313,11 +321,11 @@ pub mod team_game {
|
||||||
/// Add or replace a team with the given name and array of members
|
/// Add or replace a team with the given name and array of members
|
||||||
SetTeam(TeamGameUpdateSetTeam),
|
SetTeam(TeamGameUpdateSetTeam),
|
||||||
/// Remove team with given name
|
/// Remove team with given name
|
||||||
RemoveTeam(String),
|
RemoveTeam(Team),
|
||||||
/// Replace the current ranking with the given ranking
|
/// Replace the current ranking with the given ranking
|
||||||
SetRanking(Ranking<String>),
|
SetRanking(Ranking<Team>),
|
||||||
/// If the current ranking is of type `Scores`, apply the given score deltas
|
/// If the current ranking is of type `Scores`, apply the given score deltas
|
||||||
ScoreDelta(HashMap<String, i64>),
|
ScoreDelta(HashMap<Team, i64>),
|
||||||
/// Set rewards for winning the game
|
/// Set rewards for winning the game
|
||||||
SetWinRewards(Vec<i64>),
|
SetWinRewards(Vec<i64>),
|
||||||
/// Set rewards for losing the game
|
/// Set rewards for losing the game
|
||||||
|
@ -330,13 +338,24 @@ pub mod team_game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Ranking<Team>> for Ranking<User> {
|
||||||
|
fn from(r: Ranking<Team>) -> Self {
|
||||||
|
match r {
|
||||||
|
Ranking::Scores(s) => {
|
||||||
|
Ranking::Scores(s.into_iter().map(|(k, v)| (k.into(), v)).collect())
|
||||||
|
}
|
||||||
|
Ranking::Ranking(s) => Ranking::Ranking(s.into_iter().map(|k| k.into()).collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EventTrait for TeamGame {
|
impl EventTrait for TeamGame {
|
||||||
type Spec = TeamGameSpec;
|
type Spec = TeamGameSpec;
|
||||||
type Update = TeamGameUpdate;
|
type Update = TeamGameUpdate;
|
||||||
|
|
||||||
fn from_spec(spec: TeamGameSpec) -> Self {
|
fn from_spec(spec: TeamGameSpec) -> Self {
|
||||||
let ffa_game_spec = FreeForAllGameSpec {
|
let ffa_game_spec = FreeForAllGameSpec {
|
||||||
participants: spec.teams.keys().cloned().collect(),
|
participants: spec.teams.keys().map(|k| User::from(k.clone())).collect(),
|
||||||
win_rewards: spec.win_rewards,
|
win_rewards: spec.win_rewards,
|
||||||
lose_rewards: spec.lose_rewards,
|
lose_rewards: spec.lose_rewards,
|
||||||
};
|
};
|
||||||
|
@ -351,10 +370,12 @@ pub mod team_game {
|
||||||
match update {
|
match update {
|
||||||
TeamGameUpdate::SetRanking(x) => self
|
TeamGameUpdate::SetRanking(x) => self
|
||||||
.ffa_game
|
.ffa_game
|
||||||
.apply_update(FreeForAllGameUpdate::SetRanking(x)),
|
.apply_update(FreeForAllGameUpdate::SetRanking(x.into())),
|
||||||
TeamGameUpdate::ScoreDelta(x) => self
|
TeamGameUpdate::ScoreDelta(x) => {
|
||||||
.ffa_game
|
self.ffa_game.apply_update(FreeForAllGameUpdate::ScoreDelta(
|
||||||
.apply_update(FreeForAllGameUpdate::ScoreDelta(x)),
|
x.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
TeamGameUpdate::SetWinRewards(x) => self
|
TeamGameUpdate::SetWinRewards(x) => self
|
||||||
.ffa_game
|
.ffa_game
|
||||||
.apply_update(FreeForAllGameUpdate::SetWinRewards(x)),
|
.apply_update(FreeForAllGameUpdate::SetWinRewards(x)),
|
||||||
|
@ -363,13 +384,23 @@ pub mod team_game {
|
||||||
.apply_update(FreeForAllGameUpdate::SetLoseRewards(x)),
|
.apply_update(FreeForAllGameUpdate::SetLoseRewards(x)),
|
||||||
TeamGameUpdate::SetTeam(u) => {
|
TeamGameUpdate::SetTeam(u) => {
|
||||||
self.ffa_game
|
self.ffa_game
|
||||||
.apply_update(FreeForAllGameUpdate::AddParticipant(u.team.clone()))?;
|
.apply_update(FreeForAllGameUpdate::AddParticipant(
|
||||||
self.teams.insert(u.team, u.members);
|
u.team.clone().into(),
|
||||||
|
))?;
|
||||||
|
self.teams.insert(
|
||||||
|
u.team,
|
||||||
|
u.members
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| (t.clone()).deref().clone().into())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
TeamGameUpdate::RemoveTeam(team) => {
|
TeamGameUpdate::RemoveTeam(team) => {
|
||||||
self.ffa_game
|
self.ffa_game
|
||||||
.apply_update(FreeForAllGameUpdate::RemoveParticipant(team.clone()))?;
|
.apply_update(FreeForAllGameUpdate::RemoveParticipant(
|
||||||
|
team.clone().into(),
|
||||||
|
))?;
|
||||||
self.teams.remove(&team);
|
self.teams.remove(&team);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -382,10 +413,10 @@ pub mod team_game {
|
||||||
let mut points = HashMap::new();
|
let mut points = HashMap::new();
|
||||||
|
|
||||||
for (team, reward) in ffa_outcome.points.iter() {
|
for (team, reward) in ffa_outcome.points.iter() {
|
||||||
if let Some(team) = self.teams.get(team) {
|
if let Some(team) = self.teams.get(&*team.to_string()) {
|
||||||
for player in team {
|
for player in team {
|
||||||
let score = points.get(player).unwrap_or(&0);
|
let score = points.get(&player.clone().into()).unwrap_or(&0);
|
||||||
points.insert(player.clone(), score + reward);
|
points.insert(User::from(player.clone()), score + reward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,6 +431,7 @@ pub mod free_for_all_game {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/*
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
#[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))]
|
||||||
|
@ -407,6 +439,7 @@ pub mod free_for_all_game {
|
||||||
pub struct User {
|
pub struct User {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
#[cfg_attr(feature = "openapi", derive(JsonSchema))]
|
||||||
|
@ -415,7 +448,7 @@ pub mod free_for_all_game {
|
||||||
pub struct FreeForAllGame {
|
pub struct FreeForAllGame {
|
||||||
/// Ranking of participants by user name or team name (first element is first place, second element is second
|
/// Ranking of participants by user name or team name (first element is first place, second element is second
|
||||||
/// place, etc.)
|
/// place, etc.)
|
||||||
pub ranking: Option<Ranking<String>>,
|
pub ranking: Option<Ranking<User>>,
|
||||||
|
|
||||||
/// Specification of the game
|
/// Specification of the game
|
||||||
#[cfg_attr(feature = "serde", serde(flatten))]
|
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||||
|
@ -436,7 +469,7 @@ pub mod free_for_all_game {
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub struct FreeForAllGameSpec {
|
pub struct FreeForAllGameSpec {
|
||||||
/// Array of user ids that participate in the game
|
/// Array of user ids that participate in the game
|
||||||
pub participants: HashSet<String>,
|
pub participants: HashSet<User>,
|
||||||
|
|
||||||
/// Rewards for winning the game (first element for first place, second element for second
|
/// Rewards for winning the game (first element for first place, second element for second
|
||||||
/// place, etc.)
|
/// place, etc.)
|
||||||
|
@ -455,24 +488,24 @@ pub mod free_for_all_game {
|
||||||
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
#[cfg_attr(feature = "sycamore", derive(WebEdit, WebView))]
|
||||||
pub enum FreeForAllGameUpdate {
|
pub enum FreeForAllGameUpdate {
|
||||||
/// Replace the current ranking with the given ranking
|
/// Replace the current ranking with the given ranking
|
||||||
SetRanking(Ranking<String>),
|
SetRanking(Ranking<User>),
|
||||||
/// If the current ranking is of type `Scores`, apply the given score deltas
|
/// If the current ranking is of type `Scores`, apply the given score deltas
|
||||||
ScoreDelta(HashMap<String, i64>),
|
ScoreDelta(HashMap<User, i64>),
|
||||||
/// Set rewards for winning the game
|
/// Set rewards for winning the game
|
||||||
SetWinRewards(Vec<i64>),
|
SetWinRewards(Vec<i64>),
|
||||||
/// Set rewards for losing the game
|
/// Set rewards for losing the game
|
||||||
SetLoseRewards(Vec<i64>),
|
SetLoseRewards(Vec<i64>),
|
||||||
/// Set list of participants participating in the game
|
/// Set list of participants participating in the game
|
||||||
SetParticipants(HashSet<String>),
|
SetParticipants(HashSet<User>),
|
||||||
/// Add participant by name
|
/// Add participant by name
|
||||||
AddParticipant(String),
|
AddParticipant(User),
|
||||||
/// Remove participant by name
|
/// Remove participant by name
|
||||||
RemoveParticipant(String),
|
RemoveParticipant(User),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FreeForAllGameUpdate {
|
impl Default for FreeForAllGameUpdate {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::AddParticipant(String::new())
|
Self::AddParticipant(String::new().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod state;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
use crate::{event::Event, user::User};
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
use crate::util::ContextOptions;
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
#[derive(Clone, PartialEq, Default)]
|
||||||
|
pub struct Users(pub RcSignal<Vec<User>>);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sycamore"))]
|
||||||
|
#[derive(Clone, PartialEq, Default)]
|
||||||
|
pub struct Users;
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
impl Users {
|
||||||
|
pub fn get(&self) -> &RcSignal<Vec<User>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
impl ContextOptions<String> for Users {
|
||||||
|
fn options<'a>(&'a self, cx: Scope<'a>) -> &'a ReadSignal<Vec<String>> {
|
||||||
|
self.get()
|
||||||
|
.map(cx, |v| v.iter().map(|u| u.name.clone()).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
#[derive(Clone, PartialEq, Default)]
|
||||||
|
pub struct Events(pub RcSignal<Vec<Event>>);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "sycamore"))]
|
||||||
|
#[derive(Clone, PartialEq, Default)]
|
||||||
|
pub struct Events;
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
impl Events {
|
||||||
|
pub fn get(&self) -> &RcSignal<Vec<Event>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
impl ContextOptions<String> for Events {
|
||||||
|
fn options<'a>(&'a self, cx: Scope<'a>) -> &'a ReadSignal<Vec<String>> {
|
||||||
|
self.get()
|
||||||
|
.map(cx, |v| v.iter().map(|u| u.name.clone()).collect())
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,12 @@ use rocket::FromFormField;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
hash::Hash,
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -42,3 +48,86 @@ impl ToString for Ordering {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct WithContext<Ctx, T> {
|
||||||
|
#[serde(skip)]
|
||||||
|
_ctx: PhantomData<Ctx>,
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "openapi")]
|
||||||
|
impl<Ctx, T> JsonSchema for WithContext<Ctx, T>
|
||||||
|
where
|
||||||
|
T: JsonSchema,
|
||||||
|
{
|
||||||
|
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
|
T::json_schema(gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_name() -> String {
|
||||||
|
T::schema_name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> Debug for WithContext<Ctx, T>
|
||||||
|
where
|
||||||
|
T: Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> Hash for WithContext<Ctx, T>
|
||||||
|
where
|
||||||
|
T: Hash,
|
||||||
|
{
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.inner.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> PartialEq for WithContext<Ctx, T>
|
||||||
|
where
|
||||||
|
T: PartialEq,
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.inner == other.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> Eq for WithContext<Ctx, T> where T: Eq {}
|
||||||
|
|
||||||
|
impl<Ctx, T> Deref for WithContext<Ctx, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> DerefMut for WithContext<Ctx, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, T> From<T> for WithContext<Ctx, T> {
|
||||||
|
fn from(inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
_ctx: PhantomData,
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
use sycamore::reactive::{ReadSignal, Scope};
|
||||||
|
|
||||||
|
#[cfg(feature = "sycamore")]
|
||||||
|
pub trait ContextOptions<T> {
|
||||||
|
fn options<'a>(&'a self, cx: Scope<'a>) -> &'a ReadSignal<Vec<T>>;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
ops::Deref,
|
||||||
};
|
};
|
||||||
use sycamore::view::IntoView as _;
|
use sycamore::view::IntoView as _;
|
||||||
|
|
||||||
|
@ -14,7 +15,10 @@ pub mod prelude {
|
||||||
pub use sycamore::prelude::*;
|
pub use sycamore::prelude::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::components::{Block, BlockProps};
|
use crate::{
|
||||||
|
components::{Block, BlockProps},
|
||||||
|
util::WithContext,
|
||||||
|
};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! viewable {
|
macro_rules! viewable {
|
||||||
|
@ -204,3 +208,17 @@ impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewer<'a, G, Option<T>> f
|
||||||
impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewable<'a, G> for Option<T> {
|
impl<'a, G: Html, T: for<'b> Viewable<'b, G> + Clone> Viewable<'a, G> for Option<T> {
|
||||||
type Viewer = OptionView;
|
type Viewer = OptionView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ContextView;
|
||||||
|
|
||||||
|
impl<'a, G: Html, Ctx, T: for<'b> Viewable<'b, G> + Clone> Viewer<'a, G, WithContext<Ctx, T>>
|
||||||
|
for ContextView
|
||||||
|
{
|
||||||
|
fn view(cx: Scope<'a>, props: ViewProps<'a, WithContext<Ctx, T>>) -> View<G> {
|
||||||
|
props.state.deref().view(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, G: Html, Ctx, T: for<'b> Viewable<'b, G> + Clone> Viewable<'a, G> for WithContext<Ctx, T> {
|
||||||
|
type Viewer = ContextView;
|
||||||
|
}
|
||||||
|
|
|
@ -14,4 +14,5 @@ quote = "1.0"
|
||||||
sycamore = { version = "0.8.1", features = ["serde", "suspense"] }
|
sycamore = { version = "0.8.1", features = ["serde", "suspense"] }
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
convert_case = "0.6"
|
convert_case = "0.6"
|
||||||
|
darling = "0.14"
|
||||||
#lan_party_core = { path = "../core", features = ["sycamore"] }
|
#lan_party_core = { path = "../core", features = ["sycamore"] }
|
||||||
|
|
|
@ -6,6 +6,9 @@ use syn::{
|
||||||
LifetimeDef, Lit, MetaNameValue, Path, PredicateType, Type, TypeParam,
|
LifetimeDef, Lit, MetaNameValue, Path, PredicateType, Type, TypeParam,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate darling;
|
||||||
|
|
||||||
enum ParsedAttribute {
|
enum ParsedAttribute {
|
||||||
Documentation(Documentation),
|
Documentation(Documentation),
|
||||||
View(ViewAttribute),
|
View(ViewAttribute),
|
||||||
|
@ -50,7 +53,7 @@ impl Documentation {
|
||||||
|
|
||||||
impl ViewAttribute {
|
impl ViewAttribute {
|
||||||
fn parse(attr: &Attribute) -> ViewAttribute {
|
fn parse(attr: &Attribute) -> ViewAttribute {
|
||||||
if !attr.path.is_ident("web_view_attr") {
|
if !attr.path.is_ident("view") {
|
||||||
return Self::None;
|
return Self::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +99,7 @@ impl ParsedAttribute {
|
||||||
fn parse(attr: &Attribute) -> ParsedAttribute {
|
fn parse(attr: &Attribute) -> ParsedAttribute {
|
||||||
match attr.path.get_ident() {
|
match attr.path.get_ident() {
|
||||||
Some(i) if i.to_string() == "doc" => Self::Documentation(Documentation::parse(attr)),
|
Some(i) if i.to_string() == "doc" => Self::Documentation(Documentation::parse(attr)),
|
||||||
Some(i) if i.to_string() == "web_view_attr" => Self::View(ViewAttribute::parse(attr)),
|
Some(i) if i.to_string() == "view" => Self::View(ViewAttribute::parse(attr)),
|
||||||
Some(i) if i.to_string() == "serde" => Self::Serde(SerdeAttribute::parse(attr)),
|
Some(i) if i.to_string() == "serde" => Self::Serde(SerdeAttribute::parse(attr)),
|
||||||
_ => Self::None,
|
_ => Self::None,
|
||||||
}
|
}
|
||||||
|
@ -154,11 +157,6 @@ impl Attributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn web_view_attr(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
||||||
item
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ItemProps {
|
struct ItemProps {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
|
@ -494,32 +492,6 @@ fn struct_view(props: &ItemProps, s: DataStruct) -> TokenStream2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enum_fields<'a>(e: &'a DataEnum) -> impl Iterator<Item = EnumVariant> + 'a {
|
|
||||||
e.variants.iter().map(|v| {
|
|
||||||
let variant = v.ident.clone();
|
|
||||||
|
|
||||||
let inner = match &v.fields {
|
|
||||||
Fields::Unnamed(u) => u.unnamed.first().expect("the should be a field").ty.clone(),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let Attributes {
|
|
||||||
title,
|
|
||||||
title_field: _,
|
|
||||||
description,
|
|
||||||
..
|
|
||||||
} = Attributes::parse(&v.attrs);
|
|
||||||
let variant_lower = format_ident!("{}", variant.to_string().to_case(Case::Snake));
|
|
||||||
EnumVariant {
|
|
||||||
variant_lower,
|
|
||||||
variant,
|
|
||||||
inner,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enum_view(props: &ItemProps, e: DataEnum) -> TokenStream2 {
|
fn enum_view(props: &ItemProps, e: DataEnum) -> TokenStream2 {
|
||||||
let ItemProps {
|
let ItemProps {
|
||||||
name,
|
name,
|
||||||
|
@ -667,7 +639,7 @@ pub fn web_edit(tokens: TokenStream) -> TokenStream {
|
||||||
TokenStream::from(res)
|
TokenStream::from(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(WebView)]
|
#[proc_macro_derive(WebView, attributes(view))]
|
||||||
pub fn web_view(tokens: TokenStream) -> TokenStream {
|
pub fn web_view(tokens: TokenStream) -> TokenStream {
|
||||||
let input: syn::DeriveInput = syn::parse(tokens).unwrap();
|
let input: syn::DeriveInput = syn::parse(tokens).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
podman run -d --rm --name lan_party_db \
|
podman run -d --rm --name lan_party_db \
|
||||||
-v lan-party-db:/data/db:z \
|
-v lan-party-db-2:/data/db:z \
|
||||||
-p "27017:27017" \
|
-p "27017:27017" \
|
||||||
-e MONGO_INITDB_ROOT_USERNAME=root \
|
-e MONGO_INITDB_ROOT_USERNAME=root \
|
||||||
-e MONGO_INITDB_ROOT_PASSWORD=example \
|
-e MONGO_INITDB_ROOT_PASSWORD=example \
|
||||||
|
|
|
@ -28,5 +28,5 @@ reqwasm = "0.5"
|
||||||
console_log = "0.2"
|
console_log = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
gloo-timers = "0.2"
|
gloo-timers = "0.2"
|
||||||
|
async-trait = "0.1"
|
||||||
|
futures = "0.3"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<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>LAN Party</title>
|
<title>LAN Party</title>
|
||||||
|
|
||||||
<link rel="preload" href="/index-45b72451f5518e3c_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
<link rel="preload" href="/index-bca49fe60bf0b94f_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||||
<link rel="modulepreload" href="/index-45b72451f5518e3c.js"></head>
|
<link rel="modulepreload" href="/index-bca49fe60bf0b94f.js"></head>
|
||||||
<body>
|
<body>
|
||||||
<script type="module">import init from '/index-45b72451f5518e3c.js';init('/index-45b72451f5518e3c_bg.wasm');</script></body></html>
|
<script type="module">import init from '/index-bca49fe60bf0b94f.js';init('/index-bca49fe60bf0b94f_bg.wasm');</script></body></html>
|
|
@ -4,9 +4,11 @@ pub mod state;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
use components::messages::{Messages, Messenger};
|
use components::messages::{Messages, Messenger};
|
||||||
|
use futures::join;
|
||||||
|
use lan_party_core::state::{Events, Users};
|
||||||
use pages::{EventsPage, UsersPage};
|
use pages::{EventsPage, UsersPage};
|
||||||
use state::{Events, Users};
|
use state::{EventsExt, UsersExt};
|
||||||
use sycamore::prelude::*;
|
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
use sycamore_router::{HistoryIntegration, Route, Router};
|
use sycamore_router::{HistoryIntegration, Route, Router};
|
||||||
|
|
||||||
#[derive(Route)]
|
#[derive(Route)]
|
||||||
|
@ -74,6 +76,23 @@ fn main() {
|
||||||
let events = Events::default();
|
let events = Events::default();
|
||||||
provide_context(cx, events);
|
provide_context(cx, events);
|
||||||
|
|
||||||
|
spawn_local_scoped(cx, async move {
|
||||||
|
let (users_res, events_res) = join!(
|
||||||
|
use_context::<Users>(cx).load(),
|
||||||
|
use_context::<Events>(cx).load(),
|
||||||
|
);
|
||||||
|
use_context::<Messenger>(cx).add_result(
|
||||||
|
users_res,
|
||||||
|
Option::<String>::None,
|
||||||
|
Some("failed to load users"),
|
||||||
|
);
|
||||||
|
use_context::<Messenger>(cx).add_result(
|
||||||
|
events_res,
|
||||||
|
Option::<String>::None,
|
||||||
|
Some("failed to load events"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let messages = use_context::<MessagesState>(cx);
|
let messages = use_context::<MessagesState>(cx);
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{messages::Messenger, Button, Modal, Table},
|
components::{messages::Messenger, Button, Modal, Table},
|
||||||
state::Events,
|
state::{EventsExt, UsersExt},
|
||||||
util::api_request,
|
util::api_request,
|
||||||
};
|
};
|
||||||
|
use lan_party_core::state::{Events, Users};
|
||||||
|
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
Reload,
|
Reload,
|
||||||
|
@ -113,7 +114,7 @@ pub fn EventsPage<'a, G: Html>(cx: Scope<'a>) -> View<G> {
|
||||||
view=move |cx, (k, v)| {
|
view=move |cx, (k, v)| {
|
||||||
view! { cx,
|
view! { cx,
|
||||||
tr {
|
tr {
|
||||||
td { (k.clone()) }
|
td { (*k.clone()) }
|
||||||
td { (v.clone()) }
|
td { (v.clone()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{messages::Messenger, Button, Table},
|
components::{messages::Messenger, Button, Table},
|
||||||
state::Users,
|
state::{EventsExt, UsersExt},
|
||||||
};
|
};
|
||||||
|
use lan_party_core::state::{Events, Users};
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
use lan_party_core::{
|
use lan_party_core::{
|
||||||
event::{Event, EventSpec, EventUpdate},
|
event::{Event, EventSpec, EventUpdate},
|
||||||
|
state::{Events, Users},
|
||||||
user::User,
|
user::User,
|
||||||
};
|
};
|
||||||
use reqwasm::http::Method;
|
use reqwasm::http::Method;
|
||||||
|
@ -8,15 +10,20 @@ use sycamore::prelude::*;
|
||||||
|
|
||||||
use crate::util::api_request;
|
use crate::util::api_request;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Default)]
|
#[async_trait(?Send)]
|
||||||
pub struct Users(RcSignal<Vec<User>>);
|
pub trait UsersExt {
|
||||||
|
async fn load(&self) -> Result<()>;
|
||||||
|
|
||||||
impl Users {
|
async fn delete(&self, name: &str) -> Result<()>;
|
||||||
pub fn get(&self) -> &RcSignal<Vec<User>> {
|
|
||||||
&self.0
|
async fn update_score(&self, name: &str, score: i64) -> Result<()>;
|
||||||
|
|
||||||
|
async fn add(&self, name: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load(&self) -> Result<()> {
|
#[async_trait(?Send)]
|
||||||
|
impl UsersExt for Users {
|
||||||
|
async fn load(&self) -> Result<()> {
|
||||||
self.0.set(
|
self.0.set(
|
||||||
api_request::<_, Vec<User>>(
|
api_request::<_, Vec<User>>(
|
||||||
Method::GET,
|
Method::GET,
|
||||||
|
@ -29,7 +36,7 @@ impl Users {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, name: &str) -> Result<()> {
|
async fn delete(&self, name: &str) -> Result<()> {
|
||||||
let users_ref = self.0.get();
|
let users_ref = self.0.get();
|
||||||
let user: &User = users_ref.iter().find(|user| user.name == name).unwrap();
|
let user: &User = users_ref.iter().find(|user| user.name == name).unwrap();
|
||||||
api_request::<_, ()>(
|
api_request::<_, ()>(
|
||||||
|
@ -48,7 +55,7 @@ impl Users {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_score(&self, name: &str, score: i64) -> Result<()> {
|
async fn update_score(&self, name: &str, score: i64) -> Result<()> {
|
||||||
let users_ref = self.0.get();
|
let users_ref = self.0.get();
|
||||||
let user: &User = users_ref.iter().find(|user| &user.name == name).unwrap();
|
let user: &User = users_ref.iter().find(|user| &user.name == name).unwrap();
|
||||||
api_request::<_, ()>(
|
api_request::<_, ()>(
|
||||||
|
@ -71,22 +78,27 @@ impl Users {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&self, name: &str) -> Result<()> {
|
async fn add(&self, name: &str) -> Result<()> {
|
||||||
let user = api_request::<&str, User>(Method::POST, "/user", Some(name)).await?;
|
let user = api_request::<&str, User>(Method::POST, "/user", Some(name)).await?;
|
||||||
self.0.modify().push(user.ok_or(anyhow!("missing body"))?);
|
self.0.modify().push(user.ok_or(anyhow!("missing body"))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Default)]
|
#[async_trait(?Send)]
|
||||||
pub struct Events(RcSignal<Vec<Event>>);
|
pub trait EventsExt {
|
||||||
|
async fn load(&self) -> Result<()>;
|
||||||
|
|
||||||
impl Events {
|
async fn add(&self, event_spec: EventSpec) -> Result<()>;
|
||||||
pub fn get(&self) -> &RcSignal<Vec<Event>> {
|
|
||||||
&self.0
|
async fn update_event(&self, event_name: &str, event_update: EventUpdate) -> Result<()>;
|
||||||
|
|
||||||
|
async fn delete(&self, event_name: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load(&self) -> Result<()> {
|
#[async_trait(?Send)]
|
||||||
|
impl EventsExt for Events {
|
||||||
|
async fn load(&self) -> Result<()> {
|
||||||
self.0.set(
|
self.0.set(
|
||||||
api_request::<_, Vec<Event>>(Method::GET, "/event", Option::<()>::None)
|
api_request::<_, Vec<Event>>(Method::GET, "/event", Option::<()>::None)
|
||||||
.await
|
.await
|
||||||
|
@ -95,7 +107,7 @@ impl Events {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&self, event_spec: EventSpec) -> Result<()> {
|
async fn add(&self, event_spec: EventSpec) -> Result<()> {
|
||||||
let new_event =
|
let new_event =
|
||||||
api_request::<EventSpec, Event>(Method::POST, "/event", Some(event_spec)).await?;
|
api_request::<EventSpec, Event>(Method::POST, "/event", Some(event_spec)).await?;
|
||||||
|
|
||||||
|
@ -105,7 +117,7 @@ impl Events {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_event(&self, event_name: &str, event_update: EventUpdate) -> Result<()> {
|
async fn update_event(&self, event_name: &str, event_update: EventUpdate) -> Result<()> {
|
||||||
api_request::<EventUpdate, ()>(
|
api_request::<EventUpdate, ()>(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("/event/{}", event_name),
|
&format!("/event/{}", event_name),
|
||||||
|
@ -118,7 +130,7 @@ impl Events {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, event_name: &str) -> Result<()> {
|
async fn delete(&self, event_name: &str) -> Result<()> {
|
||||||
api_request::<(), ()>(Method::DELETE, &format!("/event/{}", event_name), None).await?;
|
api_request::<(), ()>(Method::DELETE, &format!("/event/{}", event_name), None).await?;
|
||||||
|
|
||||||
self.load().await?;
|
self.load().await?;
|
||||||
|
|
Loading…
Reference in New Issue