Added live updating timings

This commit is contained in:
xeovalyte 2024-04-11 08:48:11 +02:00
parent 36c23f3740
commit 1f96cbcba6
No known key found for this signature in database
8 changed files with 151 additions and 102 deletions

View File

@ -1,4 +1,4 @@
use crate::util::surrealdb::schemas; use crate::util::surrealdb::{client::sort_participants, client::Time, schemas};
use leptos::*; use leptos::*;
/// Renders the home page of your application. /// Renders the home page of your application.
@ -6,9 +6,23 @@ use leptos::*;
pub fn Participants() -> impl IntoView { pub fn Participants() -> impl IntoView {
let participants_context = use_context::<schemas::ParticipantsContext>().unwrap(); let participants_context = use_context::<schemas::ParticipantsContext>().unwrap();
let name = create_rw_signal("".to_string());
let participants_sorted =
create_memo(move |_| sort_participants(participants_context.get(), name.get()));
view! { view! {
<div class="participants-container"> <input type="search"
<table> class="participants-search"
placeholder="Search"
autocomplete="off"
autofocus=true
on:input=move |ev| {
name.set(event_target_value(&ev));
}
prop:value=name
/>
<table class="participants-table">
<tr> <tr>
<th>"Naam"</th> <th>"Naam"</th>
<th>"Groep"</th> <th>"Groep"</th>
@ -17,7 +31,7 @@ pub fn Participants() -> impl IntoView {
<th>"Popduiken"</th> <th>"Popduiken"</th>
</tr> </tr>
<For <For
each=move || participants_context.get() each=move || participants_sorted.get()
key=|state| state.id.clone() key=|state| state.id.clone()
let:child let:child
> >
@ -26,9 +40,9 @@ pub fn Participants() -> impl IntoView {
<td>{ move || child.value.get().group }</td> <td>{ move || child.value.get().group }</td>
{ move || match child.value.get().events { { move || match child.value.get().events {
Some(events) => view! { Some(events) => view! {
<td>{ events.lifesaver.unwrap_or(0) }</td> <td>{ Time::from_milliseconds_to_string(events.lifesaver.unwrap_or(0)) }</td>
<td>{ events.hindernis.unwrap_or(0) }</td> <td>{ Time::from_milliseconds_to_string(events.hindernis.unwrap_or(0)) }</td>
<td>{ events.popduiken.unwrap_or(0) }</td> <td>{ Time::from_milliseconds_to_string(events.popduiken.unwrap_or(0)) }</td>
}, },
None => view! { None => view! {
<td>"0"</td> <td>"0"</td>
@ -40,6 +54,5 @@ pub fn Participants() -> impl IntoView {
</tr> </tr>
</For> </For>
</table> </table>
</div>
} }
} }

View File

@ -30,7 +30,7 @@ async fn add_participant(name: String, group: String) -> Result<(), ServerFnErro
participant: schemas::Participant { participant: schemas::Participant {
name: participant.name.clone(), name: participant.name.clone(),
group: participant.group.clone(), group: participant.group.clone(),
id: participant.id.to_string(), id: participant.id.id.to_string(),
events: participant.events.clone(), events: participant.events.clone(),
}, },
}; };

View File

@ -1,7 +1,6 @@
use crate::util::surrealdb::schemas; use crate::util::surrealdb::{client::sort_participants, client::Time, schemas};
use leptos::{ev::keydown, *}; use leptos::{ev::keydown, *};
use leptos_use::*; use leptos_use::*;
use strsim::normalized_damerau_levenshtein;
use web_sys::ScrollIntoViewOptions; use web_sys::ScrollIntoViewOptions;
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -13,19 +12,6 @@ cfg_if::cfg_if! {
} }
} }
#[derive(Clone)]
struct Time {
minutes: u32,
seconds: u32,
milliseconds: u32,
}
impl Time {
fn as_milliseconds(&self) -> u32 {
self.minutes * 60 * 1000 + self.seconds * 1000 + self.milliseconds * 10
}
}
#[server(AddTime)] #[server(AddTime)]
async fn add_time( async fn add_time(
mut participant: schemas::Participant, mut participant: schemas::Participant,
@ -96,7 +82,7 @@ pub fn AddTime() -> impl IntoView {
let participant = &participants_sorted.get()[selected_index.get()]; let participant = &participants_sorted.get()[selected_index.get()];
add_time_action.dispatch(( add_time_action.dispatch((
participant.clone(), participant.value.get(),
event.get(), event.get(),
time.get().as_milliseconds(), time.get().as_milliseconds(),
)); ));
@ -165,7 +151,7 @@ pub fn AddTime() -> impl IntoView {
/> />
<ul node_ref=container_ref tabindex=-1> <ul node_ref=container_ref tabindex=-1>
{move || participants_sorted.get().into_iter().enumerate().map(|(i, participant)| view! { {move || participants_sorted.get().into_iter().enumerate().map(|(i, participant)| view! {
<li on:click=move |_| selected_index.set(i) class:selected=move || selected_index.get() == i>{participant.name + " " + "(" + &participant.group + ")" }</li> <li on:click=move |_| selected_index.set(i) class:selected=move || selected_index.get() == i>{participant.value.get().name + " " + "(" + &participant.value.get().group + ")" }</li>
}).collect_view()} }).collect_view()}
</ul> </ul>
</div> </div>
@ -227,33 +213,3 @@ pub fn SelectOption(is: &'static str, value: ReadSignal<String>) -> impl IntoVie
</option> </option>
} }
} }
fn sort_participants(
participants: Vec<schemas::ParticipantSignal>,
search: String,
) -> Vec<schemas::Participant> {
let mut filtered_sorted_list: Vec<(schemas::Participant, f64)> = participants
.into_iter()
.map(|participant| {
(
participant.value.get(),
normalized_damerau_levenshtein(
&participant.value.get().name.to_lowercase(),
&search.to_lowercase(),
),
)
})
.collect();
filtered_sorted_list.sort_by(|a, b| {
let (_, sim_score_a) = a;
let (_, sim_score_b) = b;
sim_score_b
.partial_cmp(sim_score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
filtered_sorted_list
.into_iter()
.map(|(item, _)| item)
.collect()
}

View File

@ -1,5 +1,6 @@
use leptos::*; use leptos::*;
pub mod client;
pub mod schemas; pub mod schemas;
cfg_if::cfg_if! { cfg_if::cfg_if! {

View File

@ -0,0 +1,57 @@
use crate::util::surrealdb::schemas;
use leptos::*;
use strsim::normalized_damerau_levenshtein;
#[derive(Clone)]
pub struct Time {
pub minutes: u32,
pub seconds: u32,
pub milliseconds: u32,
}
impl Time {
pub fn as_milliseconds(&self) -> u32 {
self.minutes * 60 * 1000 + self.seconds * 1000 + self.milliseconds * 10
}
pub fn from_milliseconds_to_string(mut time: u32) -> String {
let milliseconds = (time % 1000) / 10;
time /= 1000;
let seconds = time % 60;
time /= 60;
let minutes = time;
format!("{minutes}:{seconds}:{milliseconds}")
}
}
pub fn sort_participants(
participants: Vec<schemas::ParticipantSignal>,
search: String,
) -> Vec<schemas::ParticipantSignal> {
let mut filtered_sorted_list: Vec<(schemas::ParticipantSignal, f64)> = participants
.into_iter()
.map(|participant| {
(
participant.clone(),
normalized_damerau_levenshtein(
&participant.value.get().name.to_lowercase(),
&search.to_lowercase(),
),
)
})
.collect();
filtered_sorted_list.sort_by(|a, b| {
let (_, sim_score_a) = a;
let (_, sim_score_b) = b;
sim_score_b
.partial_cmp(sim_score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
filtered_sorted_list
.into_iter()
.map(|(item, _)| item)
.collect()
}

View File

@ -31,7 +31,7 @@ pub struct Participant {
pub events: Option<Events>, pub events: Option<Events>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ParticipantSignal { pub struct ParticipantSignal {
pub id: String, pub id: String,
pub value: RwSignal<Participant>, pub value: RwSignal<Participant>,

View File

@ -12,22 +12,17 @@ pub fn init_websocket() {
} = use_websocket("ws://localhost:3000/ws"); } = use_websocket("ws://localhost:3000/ws");
provide_context(ready_state); provide_context(ready_state);
let participants_context = use_context::<schemas::ParticipantsContext>().unwrap();
let owner = Owner::current().unwrap();
create_effect(move |prev_value| { create_effect(move |_| {
let msg = message.get(); if let Some(m) = message.get() {
with_owner(owner, || handle_message(&participants_context, &m));
if prev_value != Some(msg.clone()) {
match msg {
Some(ref text) => handle_message(text),
None => (),
} }
}
msg
}); });
} }
fn handle_message(message: &str) { fn handle_message(&participants_context: &schemas::ParticipantsContext, message: &str) {
let action: ParticipantsAction = match serde_json::from_str(message) { let action: ParticipantsAction = match serde_json::from_str(message) {
Ok(res) => res, Ok(res) => res,
Err(_err) => { Err(_err) => {
@ -37,8 +32,6 @@ fn handle_message(message: &str) {
logging::log!("Recieved action: {:?}", action); logging::log!("Recieved action: {:?}", action);
let participants_context = use_context::<schemas::ParticipantsContext>().unwrap();
match action { match action {
ParticipantsAction::Add { participant } => participants_context.update(|participants| { ParticipantsAction::Add { participant } => participants_context.update(|participants| {
participants.push(schemas::ParticipantSignal { participants.push(schemas::ParticipantSignal {
@ -49,15 +42,13 @@ fn handle_message(message: &str) {
ParticipantsAction::Remove { id } => participants_context ParticipantsAction::Remove { id } => participants_context
.update(|participants| participants.retain(|participant| participant.id != id)), .update(|participants| participants.retain(|participant| participant.id != id)),
ParticipantsAction::Replace { participant } => { ParticipantsAction::Replace { participant } => {
participants_context.with(|participants| { let participants = participants_context.get();
for participant_signal in participants { for participant_signal in participants {
if participant_signal.id == participant.id { if participant_signal.id == participant.id {
participant_signal.value.update(|value| { participant_signal.value.set(participant);
*value = participant.clone(); break;
})
} }
} }
});
} }
} }
} }

View File

@ -24,3 +24,34 @@ a {
background-color: $secondary-bg-color-light; background-color: $secondary-bg-color-light;
} }
.participants-search {
margin-top: 40px;
width: 100%;
}
.participants-table {
margin-top: 0px;
width: 100%;
border-collapse: collapse;
}
.participants-table th {
position: sticky;
background-color: $secondary-bg-color-lighter;
z-index: 100;
top: 0;
}
.participants-table th,td {
text-align: left;
padding: 8px 8px;
}
.participants-table tr:hover {
background-color: $secondary-color !important;
cursor: pointer;
}
.participants-table tr:nth-child(odd) {
background-color: $secondary-bg-color-light;
}