From cbeee94113822429000ee855accbedef3c3b3ccb Mon Sep 17 00:00:00 2001 From: emilis Date: Fri, 3 Oct 2025 03:49:05 +0100 Subject: [PATCH] player list styling --- werewolves-server/src/client.rs | 20 +-- werewolves-server/src/communication/lobby.rs | 9 +- werewolves/index.scss | 136 ++++++++++++++----- werewolves/src/clients/host/host.rs | 13 +- werewolves/src/components/action/target.rs | 96 ++----------- werewolves/src/components/action/wolves.rs | 2 +- werewolves/src/components/button.rs | 4 +- werewolves/src/components/host/daytime.rs | 54 ++++---- werewolves/src/components/lobby_player.rs | 19 ++- werewolves/src/components/reveal.rs | 37 ++--- werewolves/src/main.rs | 15 +- 11 files changed, 198 insertions(+), 207 deletions(-) diff --git a/werewolves-server/src/client.rs b/werewolves-server/src/client.rs index a252078..6539488 100644 --- a/werewolves-server/src/client.rs +++ b/werewolves-server/src/client.rs @@ -28,12 +28,12 @@ pub async fn handler( .map(|x| x.to_string()) .unwrap_or_else(|| addr.to_string()) .italic(); - log::info!( - "{who}{} connected.", - user_agent - .map(|agent| format!(" (User-Agent: {})", agent.as_str())) - .unwrap_or_default(), - ); + // log::debug!( + // "{who}{} connected.", + // user_agent + // .map(|agent| format!(" (User-Agent: {})", agent.as_str())) + // .unwrap_or_default(), + // ); let player_list = state.joined_players; // finalize the upgrade process by returning upgrade callback. @@ -46,7 +46,7 @@ pub async fn handler( return; } }; - log::info!("connected {who} as {ident}"); + // log::debug!("connected {who} as {ident}"); let connection_id = ConnectionId::new(ident.player_id.clone()); let recv = { let (send, recv) = tokio::sync::broadcast::channel(100); @@ -76,7 +76,7 @@ pub async fn handler( .run() .await; - log::info!("ending connection with {who}"); + // log::debug!("ending connection with {who}"); player_list.disconnect(&connection_id).await; }) } @@ -178,11 +178,11 @@ impl Client { } Message::Pong(_) => return Ok(()), Message::Close(Some(close_frame)) => { - log::debug!("sent close frame: {close_frame:?}"); + // log::debug!("sent close frame: {close_frame:?}"); return Ok(()); } Message::Close(None) => { - log::debug!("host closed connection"); + // log::debug!("host closed connection"); return Ok(()); } }; diff --git a/werewolves-server/src/communication/lobby.rs b/werewolves-server/src/communication/lobby.rs index 7f4a94e..abe3108 100644 --- a/werewolves-server/src/communication/lobby.rs +++ b/werewolves-server/src/communication/lobby.rs @@ -7,6 +7,7 @@ use super::{HostComms, player::PlayerIdComms}; pub struct LobbyComms { comms: Comms, + // TODO: move this to not use a receiver connect_recv: Receiver<(PlayerId, bool)>, } @@ -34,13 +35,17 @@ impl LobbyComms { pub async fn next_message(&mut self) -> Result { tokio::select! { r = self.comms.message() => { - r + match r { + Ok(val) => Ok(val), + Err(GameError::GenericError(err)) => Err(GameError::GenericError(format!("comms message: {err}"))), + Err(err) => Err(err), + } } r = self.connect_recv.recv() => { match r { Ok((player_id, true)) => Ok(Message::Connect(player_id)), Ok((player_id, false)) => Ok(Message::Disconnect(player_id)), - Err(err) => Err(GameError::GenericError(err.to_string())), + Err(err) => Err(GameError::GenericError(format!("connect recv: {err}"))), } } } diff --git a/werewolves/index.scss b/werewolves/index.scss index f40b65c..e2c39a7 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -1,3 +1,5 @@ +@use 'sass:color'; + $wolves_color: rgba(255, 0, 0, 0.7); $village_color: rgba(0, 0, 255, 0.7); $connected_color: hsl(120, 68%, 50%); @@ -94,6 +96,8 @@ nav.debug-nav { background-color: black; color: #cccccc; cursor: pointer; + width: fit-content; + text-align: center; &:hover { background-color: white; @@ -104,9 +108,6 @@ nav.debug-nav { .player { margin: 0px; - // padding-left: 5px; - // padding-right: 5px; - // padding-bottom: 5px; min-width: 10rem; max-width: 10vw; max-height: 4rem; @@ -116,7 +117,6 @@ nav.debug-nav { font-family: 'Cute Font'; &.marked { - // background-color: brighten($village_color, 100%); filter: hue-rotate(90deg); } @@ -219,18 +219,17 @@ button { background-color: #000; &:disabled { - filter: grayscale(80%); - } - - &:disabled:hover { - filter: sepia(100%); + background-color: rgba(128, 128, 128, 0.5); + color: rgb(128, 128, 128); + cursor: not-allowed; } &:disabled:hover::after { content: attr(reason); position: absolute; margin-top: 10px; - top: 90%; + // top: 90%; + // left: 0; font: 'Cute Font'; // color: #000; // background-color: #fff; @@ -258,11 +257,21 @@ button { } -.wolves-list { - flex-wrap: wrap; - flex-direction: row; - justify-content: space-evenly; - flex: 1 1 0; +.wolves-intro { + @extend .column-list; + align-content: center; + width: 100%; + + .wolves-list { + flex-wrap: wrap; + flex-direction: row; + justify-content: space-evenly; + flex: 1 1 0; + } + + & button { + align-self: center; + } } .character { @@ -391,9 +400,12 @@ bool_role { } .error-container { - width: 70vw; - margin-left: 10vw; - margin-right: 10vw; + position: fixed; + top: 10vh; + width: 100vw; + display: flex; + flex-direction: row; + align-content: center; } .error-container button { @@ -412,14 +424,15 @@ bool_role { display: flex; flex-direction: row; align-items: center; - width: 100%; + width: 80%; margin: 30px; text-align: center; // gap: 20px; justify-content: center; gap: 30px; background-color: $error_color; - filter: $error_filter; + border: 1px solid color.change($error_color, $alpha: 1.0); + backdrop-filter: grayscale(100%); padding-left: 5vw; @@ -507,9 +520,17 @@ clients { } .role-reveal-card { - border: 3px solid rgba(0, 0, 0, 0.5); - background-color: rgba(255, 0, 0, 0.7); + min-width: 5cm; + display: flex; + align-items: center; + align-content: center; + flex-direction: column; + gap: 10px; + padding: 10px; + border: 1px solid $wolves_color; + background-color: color.change($wolves_color, $alpha: 0.1); min-width: 100px; + color: white; & p.number { font-size: 2rem; @@ -519,12 +540,22 @@ clients { text-align: center; } -} + &>button { + border: 1px solid $wolves_color; + $bg: color.change($wolves_color, $alpha: 0.2); + background-color: $bg; -.role-reveal-card.ready { - background-color: rgba(0, 255, 0, 0.7); -} + &:hover { + background-color: white; + color: color.change($wolves_color, $alpha: 1.0); + } + } + &.ready { + border: 1px solid $village_color; + background-color: color.change($village_color, $alpha: 0.2); + } +} .pronouns { font-size: 70%; @@ -541,6 +572,8 @@ clients { font-size: 2rem; justify-content: center; + align-content: center; + align-items: center; &.margin-20 { margin-left: 20px; @@ -562,6 +595,8 @@ clients { .column-list { list-style: none; justify-content: center; + align-content: center; + align-items: center; font-family: 'Cute Font'; @@ -607,6 +642,12 @@ clients { flex-direction: column; justify-content: center; text-align: center; + + & button { + width: fit-content; + text-align: center; + align-self: center; + } } .small { @@ -767,11 +808,6 @@ input { } } -.zoom { - zoom: 200%; -} - - .game-start-role { @extend .column-list; text-align: center; @@ -826,3 +862,41 @@ input { } } + +.character-picker { + display: flex; + flex-direction: column; + width: 100%; + align-items: center; + color: white; + $marked_bg: color.change($wolves_color, $alpha: 0.3); + $marked_border: color.change($wolves_color, $alpha: 1.0); + $village_bg: color.change($village_color, $alpha: 0.3); + $village_border: color.change($village_color, $alpha: 1.0); + + .character { + padding: 0.5cm; + + & * { + font-size: 1.5rem; + } + + &.marked { + background-color: $marked_bg; + border: 1px solid $marked_border; + + &:hover { + color: white; + background-color: $marked_border; + } + } + + background-color: $village_bg; + border: 1px solid $village_border; + + &:hover { + color: white; + background-color: $village_border; + } + } +} diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index 12821f4..947161e 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -391,9 +391,8 @@ impl Component for Host { self.send.clone(), ); html! { - <> -

{format!("Day {}", day.get())}

- } } HostState::RoleReveal { ackd, waiting } => { @@ -582,15 +580,16 @@ impl Component for Host { HostEvent::SetBigScreenState(state) => { self.big_screen = state; if self.big_screen - && let Ok(Some(root)) = gloo::utils::document().query_selector("app") - && let Err(err) = root.set_attribute("style", "zoom: 200%;") + && let Some(root) = gloo::utils::document().document_element() + && let Err(err) = root.set_attribute("style", "font-size: 3rem;") { log::error!("setting zoom: {err:?}"); } if state { - let (discard_send, mut discard_recv) = futures::channel::mpsc::channel(10); - self.send = discard_send; + let (mut discard_send, mut discard_recv) = futures::channel::mpsc::channel(10); + core::mem::swap(&mut discard_send, &mut self.send); + Box::leak(Box::new(discard_send)); yew::platform::spawn_local(async move { while discard_recv.next().await.is_some() {} }); diff --git a/werewolves/src/components/action/target.rs b/werewolves/src/components/action/target.rs index dd0573e..d36897a 100644 --- a/werewolves/src/components/action/target.rs +++ b/werewolves/src/components/action/target.rs @@ -290,26 +290,24 @@ impl Component for SingleTarget { .then(|| html!(

{headline}

)); let submit = target_selection.as_ref().map(|target_selection| { - let disabled = self.selected.is_none(); + let disabled = self.selected.is_none().then_some("pick a target"); let target_selection = target_selection.clone(); let on_click = self .selected .clone() - .map(|t| move |_| target_selection.emit(t.clone())); + .map(|t| Callback::from(move |_| target_selection.emit(t.clone()))) + .unwrap_or_default(); html! {
- +
} }); html! { -
+
{headline} {children.clone()}
@@ -344,81 +342,15 @@ pub struct TargetCardProps { #[function_component] fn TargetCard(props: &TargetCardProps) -> Html { - let submenu = { - let button_text = if props.selected { "unpick" } else { "pick" }; - let character_id = props.target.character_id.clone(); - let on_select = props.on_select.clone(); - let on_click = Callback::from(move |_| on_select.emit(character_id.clone())); - html! { - - } - }; + let character_id = props.target.character_id.clone(); + let on_select = props.on_select.clone(); + let on_click = Callback::from(move |_| on_select.emit(character_id.clone())); + let marked = props.selected.then_some("marked"); + let ident: PublicIdentity = props.target.clone().into(); html! { -
-
- ::into(&props.target)} /> - {submenu} -
-
- } -} - -#[derive(Debug, Clone, PartialEq, Properties)] -pub struct CustomTargetCardProps { - pub target: CharacterIdentity, - pub options: Arc<[String]>, - pub on_select: Option>, - #[prop_or_default] - pub class: String, - #[prop_or(true)] - pub hide_submenu: bool, -} - -#[function_component] -pub fn CustomTargetCard( - CustomTargetCardProps { - target, - options, - on_select, - class, - hide_submenu, - }: &CustomTargetCardProps, -) -> Html { - let submenu = options.is_empty().not().then(|| { - let buttons = options - .iter() - .cloned() - .map(|option| { - let on_select = on_select.clone(); - let button_text = option.clone(); - let character_id = target.character_id.clone(); - let on_click = on_select - .map(|on_select| { - Callback::from(move |_| { - on_select.emit((character_id.clone(), option.clone())) - }) - }) - .unwrap_or_default(); - html! { - - } - }) - .collect::(); - html! { - - } - }); - html! { -
-
- ::into(target)} /> - {submenu} -
-
+ } } diff --git a/werewolves/src/components/action/wolves.rs b/werewolves/src/components/action/wolves.rs index b4af1a0..c673ef0 100644 --- a/werewolves/src/components/action/wolves.rs +++ b/werewolves/src/components/action/wolves.rs @@ -20,7 +20,7 @@ pub fn WolvesIntro(props: &WolvesIntroProps) -> Html { let on_complete = props.on_complete.clone(); let on_complete = Callback::from(move |_| on_complete.emit(())); html! { -
+

{"these are the wolves:"}

{ diff --git a/werewolves/src/components/button.rs b/werewolves/src/components/button.rs index 5764702..fb4f163 100644 --- a/werewolves/src/components/button.rs +++ b/werewolves/src/components/button.rs @@ -7,6 +7,8 @@ pub struct ButtonProperties { pub disabled_reason: Option, #[prop_or_default] pub children: Html, + #[prop_or_default] + pub classes: yew::Classes, } #[function_component] @@ -15,7 +17,7 @@ pub fn Button(props: &ButtonProperties) -> Html { let on_click = Callback::from(move |_| on_click.emit(())); html! { + } }); html! { -
-
+
+

{"day "}{day.to_string()}

+
{chars}
{button} @@ -87,26 +89,16 @@ pub fn DaytimePlayer( }: &DaytimePlayerProps, ) -> Html { let dead = died_to.is_some().then_some("dead"); - let button_text = if *on_the_block { "unmark" } else { "mark" }; - let on_the_block = on_the_block.then_some("marked"); - let submenu = died_to.is_none().then_some(()).and_then(|_| { - on_select.as_ref().map(|on_select| { - let character_id = identity.character_id.clone(); - let on_select = on_select.clone(); - let on_click = Callback::from(move |_| on_select.emit(character_id.clone())); - html! { - - } - }) - }); - + let marked = on_the_block.then_some("marked"); + let character_id = identity.character_id.clone(); + let on_click: Callback<_> = on_select + .clone() + .map(|on_select| Callback::from(move |_| on_select.emit(character_id.clone()))) + .unwrap_or_default(); let identity: PublicIdentity = identity.into(); html! { -
+
+ } } diff --git a/werewolves/src/components/lobby_player.rs b/werewolves/src/components/lobby_player.rs index 07ad702..2eb83a1 100644 --- a/werewolves/src/components/lobby_player.rs +++ b/werewolves/src/components/lobby_player.rs @@ -67,16 +67,13 @@ pub fn LobbyPlayer(LobbyPlayerProps { player, on_action }: &LobbyPlayerProps) -> }); html! { - //
- - - - // {submenu} - //
+ + + } } diff --git a/werewolves/src/components/reveal.rs b/werewolves/src/components/reveal.rs index 855ada9..8e82efd 100644 --- a/werewolves/src/components/reveal.rs +++ b/werewolves/src/components/reveal.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; +use core::ops::Not; -use werewolves_proto::message::CharacterIdentity; +use werewolves_proto::message::{CharacterIdentity, PublicIdentity}; use yew::prelude::*; -use crate::components::{Button, action::CustomTargetCard}; +use crate::components::{Button, Identity}; #[derive(Debug, PartialEq, Properties)] pub struct RoleRevealProps { @@ -65,11 +65,9 @@ impl Component for RoleReveal { } }); html! { -
+
{force_all} -
- {cards} -
+ {cards}
} } @@ -87,25 +85,20 @@ pub fn RoleRevealCard(props: &RoleRevealCardProps) -> Html { let class = props.is_ready.then_some("ready"); let target = props.target.clone(); let on_force_ready = props.on_force_ready.clone(); - let on_click = on_force_ready.map(|on_force_ready| { - Callback::from(move |_| { - on_force_ready.emit(target.clone()); + let on_click = props.is_ready.not().then_some(()).and_then(|_| { + on_force_ready.map(|on_force_ready| { + let on_click = Callback::from(move |_| { + on_force_ready.emit(target.clone()); + }); + html! {} }) }); - let options: Arc<[String]> = if props.is_ready || props.on_force_ready.is_none() { - Arc::new([]) - } else { - Arc::new([String::from("ready")]) - }; + + let ident: PublicIdentity = props.target.clone().into(); html! {
- + + {on_click}
} } diff --git a/werewolves/src/main.rs b/werewolves/src/main.rs index 87d84e2..d0aac83 100644 --- a/werewolves/src/main.rs +++ b/werewolves/src/main.rs @@ -18,6 +18,8 @@ mod pages { } mod callback; +use core::num::NonZeroU8; + use pages::{ErrorComponent, WerewolfError}; use web_sys::Url; use werewolves_proto::{ @@ -53,19 +55,14 @@ fn main() { } } else if path.starts_with("/many-client") { let clients = document.query_selector("clients").unwrap().unwrap(); - for (player_id, name, dupe) in [( - PlayerId::from_u128(1), - "player 1".to_string(), - document.query_selector("app").unwrap().unwrap(), - )] - .into_iter() - .chain((1..=2).map(|num| { + for (player_id, name, num, dupe) in (1..=16).map(|num| { ( PlayerId::from_u128(num as u128), format!("player {num}"), + NonZeroU8::new(num).unwrap(), document.create_element("autoclient").unwrap(), ) - })) { + }) { if dupe.tag_name() == "AUTOCLIENT" { clients.append_child(&dupe).unwrap(); } @@ -80,7 +77,7 @@ fn main() { public: PublicIdentity { name: name.to_string(), pronouns: Some(String::from("he/him")), - number: None, + number: Some(num), }, }), },