diff --git a/werewolves/index.scss b/werewolves/index.scss index 2041b93..f5e3b63 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -2,6 +2,9 @@ $wolves_color: rgba(255, 0, 0, 0.7); $village_color: rgba(0, 0, 255, 0.7); $connected_color: hsl(120, 68%, 50%); $disconnected_color: hsl(0, 68%, 50%); +$client_shadow_color: hsl(260, 55%, 61%); +$client_shadow_color_2: hsl(240, 55%, 61%); +$client_filter: drop-shadow(5px 5px 0 $client_shadow_color) drop-shadow(5px 5px 0 $client_shadow_color_2); html, @@ -10,44 +13,13 @@ body { } body { - background: - linear-gradient(145deg, rgba(133, 0, 153, 1) 0%, rgba(57, 0, 153, 1) 100%); min-height: 100vh; font-size: 1.5rem; max-width: 100vw; + min-width: 100vw; user-select: none; -} - -main { - color: #fff6d5; - font-family: sans-serif; - text-align: center; - padding-bottom: 80px; - // min-height: 50vh; - // background: - // linear-gradient(145deg, rgba(133, 0, 153, 1) 0%, rgba(57, 0, 153, 1) 100%); -} - -.logo { - height: 20em; -} - -.burger { - background-color: transparent; - border: none; -} - -h1.navbar-item { - font-size: 32px; - align-self: flex-start; - padding-left: 30px; -} - -.navbar-start .navbar-item { - background-color: #fff6d5; - margin-left: 20px; - padding-left: 5px; - padding-right: 5px; + color: rgba(255, 255, 255, 1); + background: black; } $link_color: #432054; @@ -64,300 +36,57 @@ $error_shadow_color: hsla(340, 95%, 61%, 0.7); $error_shadow_color_2: hsla(0, 95%, 61%, 0.7); $error_filter: drop-shadow(5px 5px 0 $error_shadow_color) drop-shadow(5px 5px 0 $error_shadow_color_2); -.navbar-item:hover { - filter: $link_select_filter; -} -.navbar-menu { - justify-items: center; -} +nav.debug-nav { -.navbar a, -.link-container a, -.pagination-item a { - text-decoration: none; - color: $link_color; -} -.out-of-order { - filter: $link_select_filter; -} - -.navbar { - font-family: 'Cute Font'; + position: sticky; + backdrop-filter: brightness(70%); display: flex; - align-items: baseline; - - gap: 30px; - filter: $link_filter; -} - -.navbar-icon img { - width: 32px; - margin-left: 15px; -} - -.post-back button { - padding-top: 10px; + align-items: flex-start; + flex-direction: row; padding-bottom: 10px; -} - -[title] { - position: relative; - display: inline-flex; - justify-content: center; -} - -[title]:focus::after { - content: attr(title); - position: absolute; - top: 90%; - font: 'Cute Font'; - color: #000; - background-color: #fff; - border: 1px solid; - width: fit-content; - padding: 3px; - z-index: 3; -} - -@media only screen and (min-width : 872px) { - .footer { - display: inline-block; - left: 0; - right: 0; - bottom: 0; - font-size: 12px; - width: 100vw; - user-select: none; - height: auto; - position: fixed; - backdrop-filter: blur(1px); - } - - .post-back { - position: absolute; - left: 25px; - } - - .post-back button { - padding-top: 20px; - padding-bottom: 20px; - } - - .post-container { - display: flex; - flex-direction: column; - text-wrap: wrap; - width: 80vw; - margin-left: 10vw; - margin-right: 10vw; - justify-content: center; - } - - .smaller video { - width: 360px; - height: 360px; - } -} - -@media only screen and (max-width : 871px) { - .footer { - display: inline-block; - left: 0; - right: 0; - bottom: 0; - font-size: 12px; - width: 100vw; - user-select: none; - height: auto; - position: inherit; - } - - .post-container { - display: flex; - flex-direction: column; - text-wrap: wrap; - width: 90vw; - margin-left: 5vw; - margin-right: 5vw; - justify-content: center; - } - - .post-back { - margin-bottom: 20px; - position: relative; - left: 5vw; - width: 90vw; - - } - - .post-back button { - width: 100% - } -} - -.post-details { - gap: 0px; - display: inline-block; -} - -.post-details h3 { - font-size: 0.7rem; - font-weight: lighter; - font-style: italic; -} - -.footer .content { - filter: drop-shadow(0px 0px 6px #000000); -} - -.badge-list { - display: flex; - flex-wrap: wrap; - height: 100%; - max-width: 100vw; - - align-items: center; - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 8px; + padding-top: 10px; + padding-left: 5vw; + padding-right: 5vw; gap: 10px; -} -.badge { - filter: drop-shadow(5px 5px 0px #000000); - image-rendering: pixelated; -} - -.badge:hover { - filter: brightness(120%) drop-shadow(5px 5px 0px hsl(0, 0%, 30%)); - transform: scale(1.1) skew(-10deg, 10deg); -} - -.container .posts-list { - padding-bottom: 100px; -} - -.link-list { - font-family: 'Cute Font'; - display: inline-flex; - flex-direction: column; - - gap: 30px; - filter: $link_filter; -} - -.pagination-list { - list-style: none; - - font-family: 'Cute Font'; - display: inline-flex; - flex-direction: row; - - gap: 30px; - filter: $link_filter; } -.link-container { - background-color: #fff6d5; - width: 100%; - padding-left: 3px; - padding-right: 3px; +.button-container { + button { + font-size: 1.3rem; + border: 1px solid rgba(255, 255, 255, 1); + padding: 5px; + background-color: rgba(0, 0, 0, 0.3); + color: #cccccc; + cursor: pointer; + } + + button:hover { + filter: $link_select_filter; + } + + // button { + // color: #fff; + // background: transparent; + // background-repeat: no-repeat; + // cursor: pointer; + // overflow: hidden; + // outline: none; + // padding: 0px; + + // &:hover { + // background-color: rgba(0, 0, 0, 0.5); + // } + // } } -.link-container:hover { - filter: $link_select_filter; -} - -.home-body { - display: flex; - columns: 2; - flex-wrap: wrap; - width: 80%; - margin-left: 5%; - margin-right: 5%; - justify-content: space-evenly; -} - -.home-column { - margin-left: 5%; - margin-right: 5%; -} - - - -.post-body { - background-color: $link_bg_color; - font-family: 'Cute Font'; - filter: $link_filter; - color: $link_color; - - padding-left: 10px; - padding-right: 10px; -} - -.pagination { - display: flex; - justify-content: center; - gap: 20px; - width: 100vw; -} - -.pagination-list { - display: flex; - justify-content: left; - flex-direction: row; - gap: 15px; -} - -.pagination-list .pagination-item { - background-color: #fff6d5; - padding-left: 3px; - padding-right: 3px; -} - -.pagination-item:hover { - filter: $link_select_filter; -} - -.scratchpad-tile { - width: 10px; - height: 10px; - background: #fff6d5; -} - -.scratchpad-active-tile { - filter: invert(100%); -} - -.scratchpad-grid { - display: grid; - - grid-template-columns: repeat(32, 1fr); - grid-template-rows: repeat(32, 1fr); - - filter: $link_filter; -} - -.subline { - font-size: 1rem; - text-align: center; - opacity: 80%; - filter: brightness(80%); - font-style: italic; -} - - - - .player .number { padding-top: 3px; margin: 0px; - // color: white; } .player:hover { @@ -370,51 +99,35 @@ $error_filter: drop-shadow(5px 5px 0 $error_shadow_color) drop-shadow(5px 5px 0 margin-right: 10vw; } -.player-title, -.player-description { - padding-left: 10px; - padding-right: 10px; - filter: $link_filter; - background-color: $link_bg_color; -} - -.player-description { - font-size: 0.8rem; - text-align: left; - padding-left: 20px; -} - -.video-player { - border: solid; - border-width: 5px; - border-color: $border_color; - background-color: $border_color; -} - -video { - max-width: 100%; - max-height: 100%; - - object-fit: scale-down; -} - -.video-container { - border: none; - background: none; - -} - - .start-game { align-self: center; margin-bottom: 30px; font-size: 2rem; - background-color: hsl(283, 100%, 80%); + // background-color: hsl(283, 100%, 80%); + border: 1px solid rgba(0, 255, 0, 0.7); + background-color: black; + color: rgba(0, 255, 0, 0.7); + cursor: pointer; + position: relative; display: inline-flex; justify-content: center; -} + &:hover { + background-color: rgba(0, 255, 0, 0.3); + } + + &:disabled { + border: 1px solid rgba(255, 0, 0, 1); + color: rgba(255, 0, 0, 1); + filter: none; + + &:hover { + background-color: rgba(255, 0, 0, 0.3); + filter: none; + } + } +} button { font-size: 1rem; @@ -422,7 +135,6 @@ button { padding-top: 2px; padding-bottom: 2px; - color: inherit; border: none; width: fit-content; @@ -450,132 +162,52 @@ button:disabled:hover::after { margin-top: 10px; top: 90%; font: 'Cute Font'; - color: #000; - background-color: #fff; - border: 1px solid; + // color: #000; + // background-color: #fff; + color: rgba(255, 0, 0, 1); + background-color: rgba(255, 0, 0, 0.3); + border: 1px solid rgba(255, 0, 0, 0.3); min-width: 50vw; width: fit-content; padding: 3px; z-index: 3; } -.player-title { - font-size: 1.2rem; - justify-self: center; - flex-grow: 10; - font-style: italic; - padding-top: 2px; - padding-bottom: 2px; -} - -.player-header { - display: flex; - flex-wrap: nowrap; - flex-direction: row; - justify-content: left; - height: min-content; - align-items: stretch; - margin-bottom: 20px; -} - -.player-header>.button-container>button { - height: 100%; -} - -.embed-link-hint { - font-size: 0.8rem; - font-style: italic; - text-align: center; -} - -.post-content { - padding-left: 30px; - padding-right: 30px; -} - -.post-content p { - text-align: left; - font-size: 1.1rem; -} - -.definition { - text-decoration: underline dotted; - cursor: help; -} - -.intentionally-left-blank { - font-size: 0.8rem; - text-align: center; - font-style: italic; - user-select: none; - opacity: 70%; -} - -blockquote { - margin: 1.5em 10px; - padding: 0.5em 10px; - quotes: "\201C" "\201D" "\2018" "\2019"; - font-style: italic; -} - -blockquote p { - display: inline; -} - -.sic { - position: relative; - bottom: 1.5ex; - font-size: 60%; -} - .settings { list-style: none; font-family: 'Cute Font'; - // font-size: 0.7rem; - background-color: white; display: flex; - // flex-wrap: wrap; flex-direction: column; - // width: 100%; margin-left: 20px; margin-right: 20px; gap: 30px; - // filter: $link_filter; + } -.settings h2 { - text-align: center; -} - -stack { - list-style: none; - - font-family: 'Cute Font'; - background-color: white; - display: flex; - // flex-wrap: wrap; - flex-direction: column; - padding: 20px; - - gap: 10px; -} - -stack>ul { - list-style: none; - display: flex; - flex-direction: row; +.wolves-list { flex-wrap: wrap; - justify-content: space-around; + flex-direction: row; + justify-content: space-evenly; + flex: 1 1 0; } -stack>ul>li { - display: inline-block; - // margin: 10px; +.character { + text-align: center; + border: 3px solid rgba(0, 0, 0, 0.4); + + .role { + font-size: 2rem; + } + + } -h2 { +h1, +h2, +h3, +h4 { text-align: center; } @@ -589,7 +221,6 @@ button.confirm { list-style: none; font-family: 'Cute Font'; - background-color: white; display: flex; flex-wrap: wrap; flex-direction: row; @@ -611,14 +242,20 @@ button.confirm { .role-card button { min-width: 25px; min-height: 25px; - background-color: rgba(255, 255, 255, 0.5); + background-color: rgba(255, 255, 255, 0.3); margin: 0px; margin-left: 10px; margin-right: 10px; + cursor: pointer; + + &:hover { + background-color: rgba(255, 255, 255, 0.7); + } } .role-card.village { background-color: $village_color; + color: rgba(255, 255, 255, 1); } rolecard { @@ -631,7 +268,7 @@ rolecard { justify-content: space-between; } -.role-card.wolves { +.role.wolves { background-color: $wolves_color; } @@ -694,7 +331,6 @@ bool_role { display: inline-block; &:hover { - // color: white; filter: invert(20%); font-size: 3rem; } @@ -727,6 +363,10 @@ bool_role { padding-top: 5px; padding-bottom: 5px; margin: 10px; + + &.wolves { + background-color: $wolves_color; + } } @@ -750,9 +390,6 @@ player { color: rgba(255, 255, 255, 0.9); } -$client_shadow_color: hsl(260, 55%, 61%); -$client_shadow_color_2: hsl(240, 55%, 61%); -$client_filter: drop-shadow(5px 5px 0 $client_shadow_color) drop-shadow(5px 5px 0 $client_shadow_color_2); client { list-style: none; @@ -792,33 +429,39 @@ clients { .role-reveal-cards { list-style: none; + // max-width: 80vw; font-family: 'Cute Font'; - // font-size: 0.7rem; - background-color: hsl(280, 55%, 61%); display: flex; - // flex-wrap: wrap; - flex-direction: column; - margin-left: 20px; - margin-right: 20px; - padding: 30px; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-evenly; + color: black; + // align-content: stretch; + // flex: 1 1 0px; - gap: 30px; - filter: $client_filter; - border: 2px solid black; + gap: 10px; } .role-reveal-card { - background-color: purple; + border: 3px solid rgba(0, 0, 0, 0.5); + background-color: rgba(255, 0, 0, 0.7); + min-width: 100px; + + & p.number { + font-size: 2rem; + } + + & p { + text-align: center; + } + } .role-reveal-card.ready { - background-color: green; + background-color: rgba(0, 255, 0, 0.7); } -.role-reveal-card.not-ready { - background-color: red; -} .pronouns { font-size: 70%; @@ -829,9 +472,6 @@ clients { list-style: none; font-family: 'Cute Font'; - // font-size: 0.7rem; - // background-color: white; - // max-width: 90vw; display: flex; flex-wrap: wrap; flex-direction: row; @@ -861,11 +501,8 @@ clients { justify-content: center; font-family: 'Cute Font'; - // font-size: 0.7rem; - // max-width: 90vw; - // background-color: white; + display: flex; - // flex-wrap: wrap; flex-direction: column; font-size: 2rem; margin-left: 20px; @@ -883,7 +520,6 @@ clients { } .content { - background-color: white; margin-left: 5vw; margin-right: 5vw; margin-top: 30px; @@ -920,19 +556,34 @@ clients { padding: 0px; .submenu { - visibility: collapse; + margin-top: 10px; + margin-bottom: 10px; + display: flex; + justify-content: center; + // visibility: collapse; + display: none; .button-container { display: flex; align-items: stretch; } + + &.shown { + // visibility: visible; + display: flex; + } + + button { + font-size: 1rem; + } } &:active, &:focus, &:hover { .submenu { - visibility: visible; + // visibility: visible; + display: flex; } } } @@ -942,19 +593,6 @@ clients { // justify-content: space-evenly; } -nav.debug-nav { - position: sticky; - backdrop-filter: brightness(70%); - // background-color: $link_bg_color; - // filter: $link_filter; - display: flex; - align-items: flex-start; - - button { - color: #cccccc; - } -} - error { position: absolute; top: 0; @@ -979,23 +617,7 @@ error { } } -.button-container { - background-color: inherit; - button { - background: transparent; - background-repeat: no-repeat; - border: none; - cursor: pointer; - overflow: hidden; - outline: none; - padding: 0px; - - &:hover { - background-color: rgba(0, 0, 0, 0.5); - } - } -} .player { diff --git a/werewolves/src/components/action/prompt.rs b/werewolves/src/components/action/prompt.rs index c522cb2..d94098c 100644 --- a/werewolves/src/components/action/prompt.rs +++ b/werewolves/src/components/action/prompt.rs @@ -12,7 +12,7 @@ use werewolves_proto::{ use yew::prelude::*; use crate::components::{ - CoverOfDarkness, Identity, + Button, CoverOfDarkness, Identity, action::{BinaryChoice, OptionalSingleTarget, SingleTarget, TwoTarget, WolvesIntro}, }; @@ -24,12 +24,18 @@ pub struct ActionPromptProps { pub on_complete: Callback, } -fn identity_html(ident: Option<&PublicIdentity>) -> Option { - ident.map(|ident| { - html! { - - } - }) +fn identity_html(props: &ActionPromptProps, ident: Option<&PublicIdentity>) -> Option { + props + .big_screen + .not() + .then(|| { + ident.map(|ident| { + html! { + + } + }) + }) + .flatten() } #[function_component] @@ -40,7 +46,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { let next = props.big_screen.not().then(|| { Callback::from(move |_| { on_complete.emit(HostMessage::InGame(HostGameMessage::Night( - HostNightMessage::Next, + HostNightMessage::ActionResponse(ActionResponse::ClearCoverOfDarkness), ))) }) }); @@ -58,7 +64,11 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { ))) }); html! { - + } } ActionPrompt::Seer { @@ -75,7 +85,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { }); let cont = props.big_screen.not().then(|| { html! { - + } }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))}

{"your role has changed"}

{new_role.to_string()}

{cont} @@ -122,7 +132,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { }); html! {
- {identity_html(props.big_screen.then_some(&character_id.public))} + {identity_html(props, Some(&character_id.public))} Html { } } ActionPrompt::MapleWolf { - character_id: _, + character_id, kill_or_die, living_players, } => { @@ -250,6 +260,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }); html! {
+ {identity_html(props, Some(&character_id.public))} Html { } } ActionPrompt::Guardian { - character_id: _, + character_id, previous, living_players, } => { @@ -291,13 +302,16 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }); html! { - - {last_protect} - +
+ {identity_html(props, Some(&character_id.public))} + + {last_protect} + +
} } ActionPrompt::WolfPackKill { living_villagers } => { @@ -317,7 +331,7 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { /> } } - ActionPrompt::Shapeshifter { character_id: _ } => { + ActionPrompt::Shapeshifter { character_id } => { let on_complete = props.on_complete.clone(); let on_select = props.big_screen.not().then_some({ move |shift| { @@ -327,13 +341,16 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { } }); html! { - -

{"shapeshift?"}

-
+
+ {identity_html(props, Some(&character_id.public))} + +

{"shapeshift?"}

+
+
} } ActionPrompt::AlphaWolf { - character_id: _, + character_id, living_villagers, } => { let on_complete = props.on_complete.clone(); @@ -345,15 +362,18 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }) }); html! { - +
+ {identity_html(props, Some(&character_id.public))} + +
} } ActionPrompt::DireWolf { - character_id: _, + character_id, living_players, } => { let on_complete = props.on_complete.clone(); @@ -365,11 +385,14 @@ pub fn Prompt(props: &ActionPromptProps) -> Html { }) }); html! { - +
+ {identity_html(props, Some(&character_id.public))} + +
} } } diff --git a/werewolves/src/components/action/target.rs b/werewolves/src/components/action/target.rs index 0e2c992..279e3e8 100644 --- a/werewolves/src/components/action/target.rs +++ b/werewolves/src/components/action/target.rs @@ -1,4 +1,5 @@ use core::{fmt::Debug, ops::Not}; +use std::sync::Arc; use werewolves_macros::ChecksAs; use werewolves_proto::{message::Target, player::CharacterId}; @@ -50,6 +51,8 @@ impl Component for TwoTarget { headline, target_selection, } = ctx.props(); + let mut targets = targets.clone(); + targets.sort_by(|l, r| l.public.number.cmp(&r.public.number)); let target_selection = target_selection.clone(); let scope = ctx.link().clone(); @@ -160,6 +163,8 @@ impl Component for OptionalSingleTarget { target_selection, children, } = ctx.props(); + let mut targets = targets.clone(); + targets.sort_by(|l, r| l.public.number.cmp(&r.public.number)); let target_selection = target_selection.clone(); let scope = ctx.link().clone(); @@ -257,6 +262,8 @@ impl Component for SingleTarget { target_selection, children, } = ctx.props(); + let mut targets = targets.clone(); + targets.sort_by(|l, r| l.public.number.cmp(&r.public.number)); let target_selection = target_selection.clone(); let scope = ctx.link().clone(); let card_select = Callback::from(move |target| { @@ -356,3 +363,60 @@ fn TargetCard(props: &TargetCardProps) -> Html {
} } + +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct CustomTargetCardProps { + pub target: Target, + 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! { +
+
+ + {submenu} +
+
+ } +} diff --git a/werewolves/src/components/action/wolves.rs b/werewolves/src/components/action/wolves.rs index 4e2c1d8..97ae7f6 100644 --- a/werewolves/src/components/action/wolves.rs +++ b/werewolves/src/components/action/wolves.rs @@ -1,6 +1,10 @@ +use core::ops::Not; + use werewolves_proto::{message::Target, role::RoleTitle}; use yew::prelude::*; +use crate::components::{Button, Identity}; + #[derive(Debug, Clone, PartialEq, Properties)] pub struct WolvesIntroProps { pub wolves: Box<[(Target, RoleTitle)]>, @@ -13,30 +17,23 @@ 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:"}

- { - if props.big_screen { - html!() - } else { - html!{ - - } +
+ { + props.wolves.iter().map(|w| html!{ +
+

{w.1.to_string()}

+ +
+ }).collect::() } - } +
{ - props.wolves.iter().map(|w| html!{ -
-

{w.1.to_string()}

-

{w.0.public.name.clone()}

- { - w.0.public.pronouns.as_ref().map(|p| html!{ -

{"("}{p.as_str()}{")"}

- }).unwrap_or(html!()) - } -
- }).collect::() - } + props.big_screen.not().then_some(html!{ + + }) + }
} } diff --git a/werewolves/src/components/host/mod.rs b/werewolves/src/components/host/mod.rs index 5bd4f81..4d72f6f 100644 --- a/werewolves/src/components/host/mod.rs +++ b/werewolves/src/components/host/mod.rs @@ -25,6 +25,8 @@ pub fn DaytimePlayerList( }: &DaytimePlayerListProps, ) -> Html { let on_select = big_screen.not().then(|| on_mark.clone()); + let mut characters = characters.clone(); + characters.sort_by(|l, r| l.public_identity.number.cmp(&r.public_identity.number)); let chars = characters .iter() .map(|c| { diff --git a/werewolves/src/components/identity.rs b/werewolves/src/components/identity.rs index 38de330..66dc2f9 100644 --- a/werewolves/src/components/identity.rs +++ b/werewolves/src/components/identity.rs @@ -4,6 +4,8 @@ use yew::prelude::*; #[derive(Debug, Clone, PartialEq, Properties)] pub struct IdentityProps { pub ident: PublicIdentity, + #[prop_or_default] + pub class: Option, } #[function_component] @@ -15,6 +17,7 @@ pub fn Identity(props: &IdentityProps) -> Html { pronouns, number, }, + class, } = props; let pronouns = pronouns.as_ref().map(|p| { html! { @@ -22,7 +25,7 @@ pub fn Identity(props: &IdentityProps) -> Html { } }); html! { -
+

{number.get()}

{name}

{pronouns} diff --git a/werewolves/src/components/reveal.rs b/werewolves/src/components/reveal.rs index 06f7671..e15fd46 100644 --- a/werewolves/src/components/reveal.rs +++ b/werewolves/src/components/reveal.rs @@ -1,11 +1,15 @@ +use std::sync::Arc; + use werewolves_proto::message::Target; use yew::prelude::*; +use crate::components::{Button, action::CustomTargetCard}; + #[derive(Debug, PartialEq, Properties)] pub struct RoleRevealProps { pub ackd: Box<[Target]>, pub waiting: Box<[Target]>, - pub on_force_ready: Callback, + pub on_force_ready: Option>, } pub struct RoleReveal {} @@ -25,11 +29,16 @@ impl Component for RoleReveal { waiting, on_force_ready, } = ctx.props(); - let on_force_ready = on_force_ready.clone(); - let cards = ackd + let mut chars = ackd .iter() .map(|t| (t, true)) .chain(waiting.iter().map(|t| (t, false))) + .collect::>(); + chars.sort_by(|(l, _), (r, _)| l.public.number.cmp(&r.public.number)); + + let on_force_ready = on_force_ready.clone(); + let cards = chars + .into_iter() .map(|(t, ready)| { html! { (); let nack = waiting.clone(); - let on_force_all = Callback::from(move |_| { - for t in nack.clone() { - on_force_ready.emit(t); - } - }); + let force_all = on_force_ready + .map(|on_force_ready| { + Callback::from(move |_| { + for t in nack.clone() { + on_force_ready.emit(t); + } + }) + }) + .map(|on_force_all| { + html! { + + } + }); html! { -
- - {cards} +
+ {force_all} +
+ {cards} +
} } @@ -60,27 +79,41 @@ impl Component for RoleReveal { pub struct RoleRevealCardProps { pub target: Target, pub is_ready: bool, - pub on_force_ready: Callback, + pub on_force_ready: Option>, } #[function_component] pub fn RoleRevealCard(props: &RoleRevealCardProps) -> Html { - let class = if props.is_ready { "ready" } else { "not-ready" }; + 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 = Callback::from(move |_| { - on_force_ready.emit(target.clone()); + let on_click = on_force_ready.map(|on_force_ready| { + Callback::from(move |_| { + on_force_ready.emit(target.clone()); + }) }); + let options: Arc<[String]> = if props.is_ready || props.on_force_ready.is_none() { + Arc::new([]) + } else { + Arc::new([String::from("ready")]) + }; html! {
-

{props.target.public.name.as_str()}

- { - if !props.is_ready { - html! {} - } else { - html!{} - } - } + + //

{props.target.public.name.as_str()}

+ // { + // if !props.is_ready { + // html! {} + // } else { + // html!{} + // } + // }
} } diff --git a/werewolves/src/components/settings.rs b/werewolves/src/components/settings.rs index fe7180f..f109e36 100644 --- a/werewolves/src/components/settings.rs +++ b/werewolves/src/components/settings.rs @@ -1,10 +1,6 @@ use convert_case::{Case, Casing}; use web_sys::HtmlInputElement; -use werewolves_proto::{ - error::GameError, - game::GameSettings, - role::{Alignment, RoleTitle}, -}; +use werewolves_proto::{error::GameError, game::GameSettings, role::RoleTitle}; use yew::prelude::*; const ALIGN_VILLAGE: &str = "village"; diff --git a/werewolves/src/pages/error.rs b/werewolves/src/pages/error.rs index 16291d2..e376862 100644 --- a/werewolves/src/pages/error.rs +++ b/werewolves/src/pages/error.rs @@ -13,6 +13,8 @@ pub enum WerewolfError { InvalidTarget, #[error("send error: {0}")] SendError(#[from] futures::channel::mpsc::SendError), + #[error("[{0}] {1}")] + NamedGameError(&'static str, GameError), } impl From for WerewolfError { diff --git a/werewolves/src/pages/host.rs b/werewolves/src/pages/host.rs index 9341f31..e8ed05e 100644 --- a/werewolves/src/pages/host.rs +++ b/werewolves/src/pages/host.rs @@ -71,7 +71,7 @@ async fn worker(mut recv: Receiver, scope: Scope) { log::info!("connected to {url}"); if let Err(err) = ws.send(encode_message(&HostMessage::GetState)).await { - log::error!("sending request for player list: {err}"); + log::error!("sending request for state: {err}"); continue 'outer; } @@ -136,7 +136,10 @@ async fn worker(mut recv: Receiver, scope: Scope) { } }; match parse { - Ok(msg) => scope.send_message::(msg.into()), + Ok(msg) => { + log::debug!("got message: {msg:?}"); + scope.send_message::(msg.into()) + } Err(err) => { log::error!("parsing server message: {err}; ignoring.") } @@ -149,6 +152,7 @@ pub enum HostEvent { SetErrorCallback(Callback>), SetBigScreenState(bool), SetState(HostState), + Continue, PlayerList(Box<[PlayerState]>), Settings(GameSettings), Error(GameError), @@ -198,6 +202,7 @@ impl From for HostEvent { ServerToHostMessage::ActionPrompt(prompt) => { HostEvent::SetState(HostState::Prompt(prompt)) } + ServerToHostMessage::ActionResult(_, ActionResult::Continue) => HostEvent::Continue, ServerToHostMessage::ActionResult(ident, result) => { HostEvent::SetState(HostState::Result(ident, result)) } @@ -235,10 +240,7 @@ impl Component for Host { yew::platform::spawn_local(async move { worker(recv, scope).await }); Self { send, - state: HostState::Lobby { - players: Box::new([]), - settings: GameSettings::default(), - }, + state: HostState::Disconnected, debug: option_env!("DEBUG").is_some(), big_screen: false, error_callback: Callback::from(|err| { @@ -250,7 +252,7 @@ impl Component for Host { } fn view(&self, _ctx: &Context) -> Html { - log::info!("state: {:?}", self.state); + log::trace!("state: {:?}", self.state); let content = match self.state.clone() { HostState::GameOver { result } => { let new_lobby = self.big_screen.not().then(|| { @@ -378,17 +380,19 @@ impl Component for Host { } HostState::RoleReveal { ackd, waiting } => { let send = self.send.clone(); - let on_force_ready = Callback::from(move |target: Target| { - let send = send.clone(); - yew::platform::spawn_local(async move { - if let Err(err) = send - .clone() - .send(HostMessage::ForceRoleAckFor(target.character_id.clone())) - .await - { - log::error!("force role ack for [{target}]: {err}"); - } - }); + let on_force_ready = self.big_screen.not().then(|| { + Callback::from(move |target: Target| { + let send = send.clone(); + yew::platform::spawn_local(async move { + if let Err(err) = send + .clone() + .send(HostMessage::ForceRoleAckFor(target.character_id.clone())) + .await + { + log::error!("force role ack for [{target}]: {err}"); + } + }); + }) }); html! { @@ -445,10 +449,8 @@ impl Component for Host { ); html! { } }); @@ -470,15 +472,25 @@ impl Component for Host { players: p, settings: _, } => *p = players, - HostState::GameOver { result: _ } => { + HostState::Disconnected | HostState::GameOver { result: _ } => { + let mut send = self.send.clone(); + let on_err = self.error_callback.clone(); self.state = HostState::Lobby { players, - settings: GameSettings::default(), - } + settings: Default::default(), + }; + yew::platform::spawn_local(async move { + if let Err(err) = send + .send(HostMessage::Lobby(HostLobbyMessage::GetGameSettings)) + .await + { + on_err.emit(Some(err.into())) + } + }); } + HostState::Prompt(_) | HostState::Result(_, _) - | HostState::Disconnected | HostState::RoleReveal { ackd: _, waiting: _, @@ -533,10 +545,37 @@ 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%;") + { + log::error!("setting zoom: {err:?}"); + } + + if state { + self.send.close_channel(); + } self.debug = false; self.error_callback = Callback::noop(); false } + HostEvent::Continue => { + if self.big_screen { + return false; + } + let mut send = self.send.clone(); + yew::platform::spawn_local(async move { + if let Err(err) = send + .send(HostMessage::InGame(HostGameMessage::Night( + HostNightMessage::Next, + ))) + .await + { + log::error!("sending next: {err:?}") + } + }); + false + } } } }