host: change seat number for players
This commit is contained in:
parent
852973eddf
commit
14e8f369ea
|
|
@ -3064,6 +3064,12 @@ dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sorted-vec"
|
||||||
|
version = "0.8.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19f58d7b0190c7f12df7e8be6b79767a0836059159811b869d5ab55721fe14d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
|
@ -4053,6 +4059,7 @@ dependencies = [
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"reactive_stores",
|
"reactive_stores",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sorted-vec",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ ciborium = { version = "0.2" }
|
||||||
pretty_assertions = { version = "1.4" }
|
pretty_assertions = { version = "1.4" }
|
||||||
colored = { version = "3.1" }
|
colored = { version = "3.1" }
|
||||||
pretty_env_logger = { version = "0.5" }
|
pretty_env_logger = { version = "0.5" }
|
||||||
|
sorted-vec = { version = "0.8" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,10 @@ dialog::backdrop {
|
||||||
&.full {
|
&.full {
|
||||||
border-bottom: 1px solid white;
|
border-bottom: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,26 +285,7 @@ dialog::backdrop {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
|
||||||
font-size: 1em;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1ch;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.form-fields {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
// width: 100%;
|
|
||||||
gap: 1ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
label {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-settings-list {
|
.user-settings-list {
|
||||||
|
|
@ -659,13 +644,24 @@ dialog .tab-content {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host-seat-changer {
|
||||||
|
width: min-content;
|
||||||
|
|
||||||
|
input[type=submit] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.number-update:not([hidden]) {
|
.number-update:not([hidden]) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: 0.25ch;
|
gap: 0.25ch;
|
||||||
margin-top: 3ch;
|
margin-top: 3ch;
|
||||||
font-size: 1.5em;
|
|
||||||
|
.bigger {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
input::-webkit-outer-spin-button,
|
input::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
|
|
@ -756,3 +752,35 @@ dialog .tab-content {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
max-width: 80vw;
|
||||||
|
gap: 0.25ch;
|
||||||
|
min-width: 45vw;
|
||||||
|
|
||||||
|
&>* {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
font-size: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25ch;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use core::hash::Hash;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
|
|
@ -204,6 +205,24 @@ pub struct DeadChatMessage {
|
||||||
pub message: DeadChatContent,
|
pub message: DeadChatContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for DeadChatMessage {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for DeadChatMessage {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.id.cmp(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for DeadChatMessage {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DeadChatContent {
|
pub enum DeadChatContent {
|
||||||
PlayerMessage {
|
PlayerMessage {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ werewolves-macros.workspace = true
|
||||||
werewolves-proto.workspace = true
|
werewolves-proto.workspace = true
|
||||||
codee.workspace = true
|
codee.workspace = true
|
||||||
convert_case.workspace = true
|
convert_case.workspace = true
|
||||||
|
sorted-vec.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = [
|
hydrate = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
|
use leptos::{
|
||||||
|
ev::{Event, MouseEvent, SubmitEvent, Targeted},
|
||||||
|
html::Form,
|
||||||
|
prelude::*,
|
||||||
|
tachys::html::node_ref::NodeRefContainer,
|
||||||
|
web_sys::HtmlInputElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ConsoleLogError;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ChangePlayerNumber(
|
||||||
|
submitted_number: WriteSignal<Option<NonZeroU8>>,
|
||||||
|
error: WriteSignal<Option<String>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let number: RwSignal<Option<NonZeroU8>> = RwSignal::new(None);
|
||||||
|
let update = move |e: Targeted<Event, HtmlInputElement>| {
|
||||||
|
e.prevent_default();
|
||||||
|
let value = e.target().value();
|
||||||
|
if value.trim().is_empty() {
|
||||||
|
number.set(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let current = number.get_untracked();
|
||||||
|
let default = current.map(|c| c.get().to_string()).unwrap_or_default();
|
||||||
|
let value_u8 = match e.target().value().trim().parse::<u8>() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{err}");
|
||||||
|
e.target().set_value(default.as_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(nz) = NonZeroU8::new(value_u8) {
|
||||||
|
number.set(Some(nz));
|
||||||
|
} else {
|
||||||
|
e.target().set_value(default.as_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let submit = move |ev: SubmitEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
log::warn!("called submit with number: {:?}", number.get());
|
||||||
|
let Some(num) = number.get() else {
|
||||||
|
error.set(Some("please set a number".into()));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
submitted_number.set(Some(num));
|
||||||
|
};
|
||||||
|
let submit_click = move |ev: MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
log::warn!("called submit with number: {:?}", number.get());
|
||||||
|
let Some(num) = number.get() else {
|
||||||
|
error.set(Some("please set a number".into()));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
submitted_number.set(Some(num));
|
||||||
|
};
|
||||||
|
move || {
|
||||||
|
view! {
|
||||||
|
<form class="number-update" on:submit=submit>
|
||||||
|
<label for="player-number">"change seat number"</label>
|
||||||
|
<input
|
||||||
|
id="player-number"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
on:input:target=update
|
||||||
|
value=move || { number.get().map(|n| n.get().to_string()).unwrap_or_default() }
|
||||||
|
/>
|
||||||
|
<input value="submit" type="submit" on:click=submit_click/>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ pub enum DialogMode {
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn DialogModal(
|
pub fn DialogModal(
|
||||||
text: String,
|
button_content: impl IntoView + Clone,
|
||||||
#[prop(optional)] open: Option<RwSignal<bool>>,
|
#[prop(optional)] open: Option<RwSignal<bool>>,
|
||||||
#[prop(optional)] button_class: String,
|
#[prop(optional)] button_class: String,
|
||||||
#[prop(default = Box::new(|| ().into_any()))] mut children: ChildrenFnMut,
|
#[prop(default = Box::new(|| ().into_any()))] mut children: ChildrenFnMut,
|
||||||
|
|
@ -23,15 +23,13 @@ pub fn DialogModal(
|
||||||
// use generated id if not supplied
|
// use generated id if not supplied
|
||||||
let open = open.unwrap_or_else(|| RwSignal::new(false));
|
let open = open.unwrap_or_else(|| RwSignal::new(false));
|
||||||
|
|
||||||
let close_cb = {
|
let close_cb = move |ev: MouseEvent| {
|
||||||
move |ev: MouseEvent| {
|
ev.prevent_default();
|
||||||
ev.prevent_default();
|
open.set(false);
|
||||||
open.set(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let close = {
|
let close = {
|
||||||
view! {
|
view! {
|
||||||
<button on:click=close_cb.clone() class="close">
|
<button on:click=close_cb class="close">
|
||||||
"close"
|
"close"
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
@ -48,34 +46,31 @@ pub fn DialogModal(
|
||||||
.into_any(),
|
.into_any(),
|
||||||
};
|
};
|
||||||
let dialog_element: NodeRef<leptos::html::Dialog> = NodeRef::new();
|
let dialog_element: NodeRef<leptos::html::Dialog> = NodeRef::new();
|
||||||
let on_backdrop_click = {
|
let on_backdrop_click = move |ev: MouseEvent| {
|
||||||
let close_cb = close_cb.clone();
|
ev.prevent_default();
|
||||||
move |ev: MouseEvent| {
|
if !close_backdrop {
|
||||||
ev.prevent_default();
|
return;
|
||||||
if !close_backdrop {
|
}
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
{
|
||||||
|
let Some(dialog) = dialog_element.get() else {
|
||||||
|
log::error!("dialog_element is None");
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
#[cfg(feature = "hydrate")]
|
|
||||||
{
|
|
||||||
let Some(dialog) = dialog_element.get() else {
|
|
||||||
log::error!("dialog_element is None");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(Some(dialog_box)) = dialog.query_selector(".dialog-box") else {
|
let Ok(Some(dialog_box)) = dialog.query_selector(".dialog-box") else {
|
||||||
log::error!(".dialog-box is None");
|
log::error!(".dialog-box is None");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let rect: leptos::web_sys::DomRect = dialog_box.get_bounding_client_rect();
|
let rect: leptos::web_sys::DomRect = dialog_box.get_bounding_client_rect();
|
||||||
|
|
||||||
let is_in_dialog = rect.top() as i32 <= ev.client_y()
|
let is_in_dialog = rect.top() as i32 <= ev.client_y()
|
||||||
&& ev.client_y() <= rect.top() as i32 + rect.height() as i32
|
&& ev.client_y() <= rect.top() as i32 + rect.height() as i32
|
||||||
&& rect.left() as i32 <= ev.client_x()
|
&& rect.left() as i32 <= ev.client_x()
|
||||||
&& ev.client_x() <= rect.left() as i32 + rect.width() as i32;
|
&& ev.client_x() <= rect.left() as i32 + rect.width() as i32;
|
||||||
|
|
||||||
if !is_in_dialog {
|
if !is_in_dialog {
|
||||||
close_cb(ev);
|
close_cb(ev);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -97,11 +92,13 @@ pub fn DialogModal(
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
open.set(true);
|
open.set(true);
|
||||||
};
|
};
|
||||||
let button = text.is_empty().not().then_some(view! {
|
let button = {
|
||||||
<button class=button_class on:click=on_click>
|
view! {
|
||||||
{text.clone()}
|
<button class=button_class.clone() on:click=on_click>
|
||||||
</button>
|
{button_content.clone().into_view()}
|
||||||
});
|
</button>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view! { <div class="dialog-modal">{button} {modal}</div> }
|
view! { <div class="dialog-modal">{button} {modal}</div> }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ pub fn HostGamePage(
|
||||||
HostPage::None => ().into_any(),
|
HostPage::None => ().into_any(),
|
||||||
HostPage::Settings => view! {
|
HostPage::Settings => view! {
|
||||||
<Settings
|
<Settings
|
||||||
|
error=error
|
||||||
open_categories=open_categories
|
open_categories=open_categories
|
||||||
settings=settings.read_only()
|
settings=settings.read_only()
|
||||||
players=players.read_only()
|
players=players.read_only()
|
||||||
|
|
@ -122,7 +123,7 @@ fn CancelGame(
|
||||||
<div hidden=move || derive_hidden.get()>
|
<div hidden=move || derive_hidden.get()>
|
||||||
<DialogModal
|
<DialogModal
|
||||||
button_class="cancel-game-button".into()
|
button_class="cancel-game-button".into()
|
||||||
text="cancel game".into()
|
button_content="cancel game"
|
||||||
open=open
|
open=open
|
||||||
>
|
>
|
||||||
<h1>"this will cancel the game. are you sure?"</h1>
|
<h1>"this will cancel the game. are you sure?"</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,49 @@
|
||||||
use leptos::prelude::*;
|
use core::num::NonZeroU8;
|
||||||
use werewolves_proto::message::{Identification, PlayerState};
|
|
||||||
|
|
||||||
use crate::app::components::{Equals, IdentificationInline, Sample, TutorialBox};
|
use leptos::prelude::*;
|
||||||
|
use werewolves_proto::message::{
|
||||||
|
Identification, PlayerState,
|
||||||
|
host::{HostLobbyMessage, HostMessage},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::{
|
||||||
|
class::AsClasses,
|
||||||
|
components::{
|
||||||
|
DialogModal, DialogMode, Equals, IdentificationInline, Sample, TutorialBox,
|
||||||
|
input::ChangePlayerNumber,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn HostPlayerList(players: ReadSignal<Box<[PlayerState]>>) -> impl IntoView {
|
pub fn HostPlayerList(
|
||||||
|
players: ReadSignal<Box<[PlayerState]>>,
|
||||||
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
error: WriteSignal<Option<String>>,
|
||||||
|
) -> impl IntoView {
|
||||||
let players = move || {
|
let players = move || {
|
||||||
|
let mut players = players.get();
|
||||||
|
players.sort_by_key(|p| p.identification.public.number.unwrap_or(NonZeroU8::MAX));
|
||||||
players
|
players
|
||||||
.read()
|
.into_iter()
|
||||||
.iter()
|
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
|
let ident = p.identification.clone();
|
||||||
|
let button_content = move || {
|
||||||
|
view! { <IdentificationInline ident=ident.clone() /> }
|
||||||
|
};
|
||||||
|
let connected = if p.connected {
|
||||||
|
"connected"
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
view! {
|
view! {
|
||||||
<button class="player" class:connected=p.connected>
|
<DialogModal
|
||||||
<IdentificationInline ident=p.identification.clone() />
|
button_class=["player", connected].as_classes().to_string()
|
||||||
</button>
|
button_content=button_content
|
||||||
|
close_backdrop=true
|
||||||
|
mode=DialogMode::Box
|
||||||
|
>
|
||||||
|
<HostPlayerDialogBody error=error player=p.clone() reply=reply />
|
||||||
|
</DialogModal>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect_view()
|
.collect_view()
|
||||||
|
|
@ -53,3 +83,46 @@ pub fn HostPlayerList(players: ReadSignal<Box<[PlayerState]>>) -> impl IntoView
|
||||||
</TutorialBox>
|
</TutorialBox>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn HostPlayerDialogBody(
|
||||||
|
player: PlayerState,
|
||||||
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
error: WriteSignal<Option<String>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let pid = player.identification.player_id;
|
||||||
|
let kick = move |_| reply.set(Some(HostMessage::Lobby(HostLobbyMessage::Kick(pid))));
|
||||||
|
let set_number = RwSignal::new(None);
|
||||||
|
let num_change_open = RwSignal::new(false);
|
||||||
|
let num_change_button_text = move || {
|
||||||
|
if num_change_open.get() {
|
||||||
|
"close seat changer"
|
||||||
|
} else {
|
||||||
|
"change seat number"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Effect::new(move || {
|
||||||
|
if let Some(num) = set_number.get() {
|
||||||
|
log::info!("gunna set seat number to {num}");
|
||||||
|
reply.set(Some(HostMessage::Lobby(HostLobbyMessage::SetPlayerNumber(
|
||||||
|
pid, num,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let toggle_change_seat_open = move |_| num_change_open.set(!num_change_open.get());
|
||||||
|
move || {
|
||||||
|
view! {
|
||||||
|
<div class="headline">
|
||||||
|
<IdentificationInline ident=player.identification.clone() />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="player-options">
|
||||||
|
<button on:click=kick>"kick"</button>
|
||||||
|
<button on:click=toggle_change_seat_open>{num_change_button_text}</button>
|
||||||
|
<div class="host-seat-changer" hidden=move || !num_change_open.get()>
|
||||||
|
<ChangePlayerNumber submitted_number=set_number.write_only() error=error />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ pub fn Settings(
|
||||||
dialog_open: RwSignal<HashMap<SlotId, bool>>,
|
dialog_open: RwSignal<HashMap<SlotId, bool>>,
|
||||||
open_categories: RwSignal<HashMap<Category, bool>>,
|
open_categories: RwSignal<HashMap<Category, bool>>,
|
||||||
reply: WriteSignal<Option<HostMessage>>,
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
error: WriteSignal<Option<String>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let slots = move || {
|
let slots = move || {
|
||||||
settings
|
settings
|
||||||
|
|
@ -129,7 +130,7 @@ pub fn Settings(
|
||||||
<div class="role-add-list">{categories}</div>
|
<div class="role-add-list">{categories}</div>
|
||||||
<div class="setup-slots">{slots}</div>
|
<div class="setup-slots">{slots}</div>
|
||||||
</div>
|
</div>
|
||||||
<HostPlayerList players=players />
|
<HostPlayerList error=error players=players reply=reply />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[component]
|
#[component]
|
||||||
|
|
@ -211,7 +212,7 @@ fn SettingsSetupSlot(
|
||||||
]
|
]
|
||||||
.as_classes()
|
.as_classes()
|
||||||
.to_string()
|
.to_string()
|
||||||
text=setup_slot.read().role.title().to_string().to_case(Case::Title)
|
button_content=setup_slot.read().role.title().to_string().to_case(Case::Title)
|
||||||
close_backdrop=true
|
close_backdrop=true
|
||||||
>
|
>
|
||||||
<SlotSettingsDialogBody setup_slot=setup_slot players=players />
|
<SlotSettingsDialogBody setup_slot=setup_slot players=players />
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::collections::HashSet;
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
use sorted_vec::SortedSet;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
message::{
|
message::{
|
||||||
ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, dead::DeadChatMessage,
|
ClientDeadChat, ClientMessage, ServerToClientMessage as Srv2Client, dead::DeadChatMessage,
|
||||||
|
|
@ -35,8 +36,7 @@ pub fn PlayerGamePage(
|
||||||
reply: WriteSignal<Option<ClientMessage>>,
|
reply: WriteSignal<Option<ClientMessage>>,
|
||||||
disconnect: RwSignal<bool>,
|
disconnect: RwSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let error = RwSignal::new(None);
|
let dead_chat: RwSignal<Option<SortedSet<DeadChatMessage>>> = RwSignal::new(None);
|
||||||
let dead_chat: RwSignal<Option<HashSet<HashedDeadChatMessage>>> = RwSignal::new(None);
|
|
||||||
let page: RwSignal<Option<Page>> = RwSignal::new(None);
|
let page: RwSignal<Option<Page>> = RwSignal::new(None);
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
let Some(message) = message.get() else {
|
let Some(message) = message.get() else {
|
||||||
|
|
@ -64,17 +64,12 @@ pub fn PlayerGamePage(
|
||||||
Srv2Client::Story(game_story) => todo!("{game_story:#?}"),
|
Srv2Client::Story(game_story) => todo!("{game_story:#?}"),
|
||||||
Srv2Client::Update(_) => {}
|
Srv2Client::Update(_) => {}
|
||||||
Srv2Client::DeadChat(dead_chat_messages) => {
|
Srv2Client::DeadChat(dead_chat_messages) => {
|
||||||
dead_chat.set(Some(
|
dead_chat.set(Some(dead_chat_messages.into_iter().collect()));
|
||||||
dead_chat_messages
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::<HashedDeadChatMessage>::into)
|
|
||||||
.collect(),
|
|
||||||
));
|
|
||||||
page.set(Some(Page::DeadChat));
|
page.set(Some(Page::DeadChat));
|
||||||
}
|
}
|
||||||
Srv2Client::DeadChatMessage(dead_chat_message) => {
|
Srv2Client::DeadChatMessage(dead_chat_message) => {
|
||||||
if let Some(chat) = dead_chat.write().as_mut() {
|
if let Some(chat) = dead_chat.write().as_mut() {
|
||||||
chat.insert(dead_chat_message.into());
|
chat.push(dead_chat_message);
|
||||||
page.set(Some(Page::DeadChat));
|
page.set(Some(Page::DeadChat));
|
||||||
} else {
|
} else {
|
||||||
reply.set(Some(ClientMessage::DeadChat(ClientDeadChat::GetHistory)));
|
reply.set(Some(ClientMessage::DeadChat(ClientDeadChat::GetHistory)));
|
||||||
|
|
@ -134,45 +129,5 @@ pub fn PlayerGamePage(
|
||||||
.into_any(),
|
.into_any(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
view! {
|
view! { {content} }.into_any()
|
||||||
<ErrorBox msg=error />
|
|
||||||
{content}
|
|
||||||
}
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct HashedDeadChatMessage(DeadChatMessage);
|
|
||||||
impl Ord for HashedDeadChatMessage {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.timestamp.cmp(&other.timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PartialOrd for HashedDeadChatMessage {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Hash for HashedDeadChatMessage {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<DeadChatMessage> for HashedDeadChatMessage {
|
|
||||||
fn from(value: DeadChatMessage) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<HashedDeadChatMessage> for DeadChatMessage {
|
|
||||||
fn from(value: HashedDeadChatMessage) -> Self {
|
|
||||||
value.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Deref for HashedDeadChatMessage {
|
|
||||||
type Target = DeadChatMessage;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn PlayerLobby(
|
||||||
reply: WriteSignal<Option<ClientMessage>>,
|
reply: WriteSignal<Option<ClientMessage>>,
|
||||||
joined: ReadSignal<bool>,
|
joined: ReadSignal<bool>,
|
||||||
current_number: ReadSignal<Option<NonZeroU8>>,
|
current_number: ReadSignal<Option<NonZeroU8>>,
|
||||||
error: RwSignal<Option<String>>,
|
error: WriteSignal<Option<String>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let click = move |ev: MouseEvent| {
|
let click = move |ev: MouseEvent| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
|
|
@ -68,7 +68,7 @@ pub fn PlayerLobby(
|
||||||
form_hidden.set(true);
|
form_hidden.set(true);
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<form class="number-update" on:submit=submit hidden=move || form_hidden.get()>
|
<form class="number-update bigger" on:submit=submit hidden=move || form_hidden.get()>
|
||||||
<label for="player-number">"change seat number"</label>
|
<label for="player-number">"change seat number"</label>
|
||||||
<input
|
<input
|
||||||
id="player-number"
|
id="player-number"
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ pub fn ChangePasswordButton() -> impl IntoView {
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<DialogModal text="change password".into() close_backdrop=false>
|
<DialogModal button_content="change password" close_backdrop=false>
|
||||||
<ErrorBox msg=error />
|
<ErrorBox msg=error />
|
||||||
<form>
|
<form>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ pub fn UpdateProfileButton() -> impl IntoView {
|
||||||
.then_some(view! { <p>"profile updated"</p> })
|
.then_some(view! { <p>"profile updated"</p> })
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<DialogModal text="update profile".into() close_backdrop=false>
|
<DialogModal button_content="update profile" close_backdrop=false>
|
||||||
<ErrorBox msg=error />
|
<ErrorBox msg=error />
|
||||||
<form>
|
<form>
|
||||||
<label for="display-name">"display name"</label>
|
<label for="display-name">"display name"</label>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue