From 067a0860abd6cc27dcf8b5fc04d5893336b84fab Mon Sep 17 00:00:00 2001 From: xeovalyte Date: Mon, 8 Apr 2024 15:58:58 +0200 Subject: [PATCH] Added add time system --- application/Cargo.toml | 3 + application/src/pages/add_time.rs | 225 ++++++++++++++++++++-- application/src/util/surrealdb/schemas.rs | 10 +- application/style/forms.scss | 44 +++++ 4 files changed, 262 insertions(+), 20 deletions(-) diff --git a/application/Cargo.toml b/application/Cargo.toml index 3fee6c2..a308637 100644 --- a/application/Cargo.toml +++ b/application/Cargo.toml @@ -28,6 +28,8 @@ once_cell = "1.19.0" futures = "0.3.30" uuid = "1.8.0" leptos-use = "0.10.6" +strsim = "0.11.1" +web-sys = { version = "0.3.69", features = ["Document", "Window", "Element", "ScrollIntoViewOptions", "ScrollLogicalPosition", "ScrollBehavior" ] } [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] @@ -39,6 +41,7 @@ ssr = [ "dep:tower-http", "dep:leptos_axum", "leptos/ssr", + "leptos-use/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:tracing", diff --git a/application/src/pages/add_time.rs b/application/src/pages/add_time.rs index 56d8d1d..03433ea 100644 --- a/application/src/pages/add_time.rs +++ b/application/src/pages/add_time.rs @@ -1,32 +1,53 @@ use crate::util::surrealdb::schemas; -use leptos::*; -use leptos_router::ActionForm; +use leptos::{ev::keydown, *}; +use leptos_router::{ActionForm, FromFormData}; +use leptos_use::*; +use strsim::normalized_damerau_levenshtein; +use web_sys::ScrollIntoViewOptions; cfg_if::cfg_if! { if #[cfg(feature = "ssr")] { use crate::util::surrealdb::{DB}; + use surrealdb::opt::PatchOp; use leptos::logging; } } +#[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)] -async fn add_time(name: String, group: String) -> Result<(), ServerFnError> { - let created: Vec = DB - .create("participant") - .content(schemas::NewParticipant { name, group }) +async fn add_time( + mut participant: schemas::Participant, + event: String, + time: u32, +) -> Result<(), ServerFnError> { + let updated: Option = DB + .update(("participant", participant.id)) + .patch(PatchOp::replace(&("/events/".to_owned() + &event), time)) .await?; - match created.first() { + match updated { Some(participant) => { logging::log!( - "Created participant: {} ({})", + "Updated participant: {} ({})", participant.name, participant.group ); Ok(()) } None => Err(ServerFnError::ServerError(String::from( - "Could not create participant", + "Could not update participant", ))), } } @@ -34,17 +55,191 @@ async fn add_time(name: String, group: String) -> Result<(), ServerFnError> { /// Renders the home page of your application. #[component] pub fn AddTime() -> impl IntoView { - let participants = use_context::>>(); + let participants = use_context::>>().unwrap(); - let form_submit = create_server_action::(); + let container_ref: NodeRef = create_node_ref(); + let name_input_ref: NodeRef = create_node_ref(); + + let event = create_rw_signal("lifesaver".to_string()); + let name = create_rw_signal("".to_string()); + let time = create_rw_signal(Time { + minutes: 0, + seconds: 0, + milliseconds: 0, + }); + let selected_index = create_rw_signal::(0); + + let participants_sorted = + create_memo(move |_| sort_participants(participants.get(), name.get())); + + let add_time_action = create_action(|input: &(schemas::Participant, String, u32)| { + let input = input.to_owned(); + async move { add_time(input.0, input.1, input.2).await } + }); + + let form_submit = move |ev: ev::SubmitEvent| { + ev.prevent_default(); + + let participant = &participants_sorted.get()[selected_index.get()]; + add_time_action.dispatch(( + participant.clone(), + String::from("lifesaver"), + time.get().as_milliseconds(), + )); + + name.set("".to_string()); + time.set(Time { + minutes: 0, + seconds: 0, + milliseconds: 0, + }); + let _ = name_input_ref.get().unwrap().focus(); + }; + + let _ = use_event_listener(name_input_ref, keydown, move |evt| { + match evt.key().as_str() { + "ArrowDown" => selected_index.update(|x| { + let len = participants.get_untracked().len(); + if *x != len { + *x += 1; + } + }), + "ArrowUp" => selected_index.update(|x| { + if *x != 0 { + *x -= 1; + } + }), + "Enter" => evt.prevent_default(), + _ => (), + } + let el: web_sys::Element = container_ref + .get_untracked() + .unwrap() + .children() + .item(selected_index.get_untracked().try_into().unwrap()) + .unwrap(); + el.scroll_into_view_with_scroll_into_view_options( + &ScrollIntoViewOptions::new().block(web_sys::ScrollLogicalPosition::Center), + ); + }); view! {

"Tijd toevoegen"

- +
+ + - +
+ +
    + {move || participants_sorted.get().into_iter().enumerate().map(|(i, participant)| view! { +
  • {participant.name + " " + "(" + &participant.group + ")" }
  • + }).collect_view()} +
+
+ +
+ () { + Ok(x) => x, + Err(_) => 0, + }); + } + prop:value=move || time.get().minutes + /> + () { + Ok(x) => x, + Err(_) => 0, + }); + } + prop:value=move || time.get().seconds + /> + () { + Ok(x) => x, + Err(_) => 0, + }); + } + prop:value=move || time.get().milliseconds + /> +
- -

{ move || format!("{:?}", participants.unwrap().get()) }

+
} } + +#[component] +pub fn SelectOption(is: &'static str, value: ReadSignal) -> impl IntoView { + view! { + + } +} +fn sort_participants( + participants: Vec, + search: String, +) -> Vec { + let mut filtered_sorted_list: Vec<(schemas::Participant, f64)> = participants + .into_iter() + .map(|participant| { + ( + participant.clone(), + normalized_damerau_levenshtein( + &participant.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() +} diff --git a/application/src/util/surrealdb/schemas.rs b/application/src/util/surrealdb/schemas.rs index 2fab564..997c4dc 100644 --- a/application/src/util/surrealdb/schemas.rs +++ b/application/src/util/surrealdb/schemas.rs @@ -6,11 +6,11 @@ cfg_if::cfg_if! { use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Events { - lifesaver: String, - hindernis: String, - popduiken: String, + lifesaver: Option, + hindernis: Option, + popduiken: Option, } #[cfg(feature = "ssr")] @@ -22,7 +22,7 @@ pub struct ParticipantRecord { pub events: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Participant { pub id: String, pub name: String, diff --git a/application/style/forms.scss b/application/style/forms.scss index addfc40..c9bb692 100644 --- a/application/style/forms.scss +++ b/application/style/forms.scss @@ -12,6 +12,32 @@ form label { margin-bottom: 3px; } +.autocomplete { + display: flex; + flex-direction: column; +} + +.autocomplete ul { + background-color: $secondary-bg-color-lighter; + list-style: none; + text-align: left; + padding: 5px; + margin-top: -15px; + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; + border-radius: 5px; +} + +.autocomplete ul .selected { + background-color: $secondary-color; +} + +.autocomplete ul li { + padding: 3px 5px; + border-radius: 3px; +} + input,select { background-color: $secondary-bg-color-light; border: none; @@ -26,3 +52,21 @@ input[type=submit]:hover { background-color: $secondary-bg-color-lighter; cursor: pointer; } + +form .time { + display: flex; + gap: 6px; +} + +form .time input { + display: flex; + width: 30px; + text-align: center; + -moz-appearance: textfield; +} + +form .time input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} +