feat: set timed reveals

This commit is contained in:
2023-06-16 21:14:53 +02:00
parent 17faadf7cd
commit 35d53d82c6
15 changed files with 350 additions and 25 deletions

View File

@@ -5,17 +5,19 @@ authors.workspace = true
edition.workspace = true
[dependencies]
log = "0.4.19"
wasm-logger = "0.2.0"
log.workspace = true
wasm-logger = "0.2"
yew = { version = "0.20", features = ["csr"] }
yew-router = "0.17.0"
yew-router = "0.17"
common.workspace = true
futures = "0.3.28"
wasm-bindgen-futures = "0.4.36"
reqwasm = "0.5.0"
yew-agent = "0.2.0"
futures.workspace = true
wasm-bindgen-futures = "0.4"
reqwasm = "0.5"
chrono.workspace = true
yew-agent = "0.2"
serde.workspace = true
serde_json = "1.0.96"
web-sys = "0.3.63"
wasm-bindgen = "0.2.86"
thiserror = "1.0.40"
serde_json.workspace = true
web-sys = "0.3"
wasm-bindgen = "0.2"
thiserror.workspace = true
uuid = { workspace = true, features = ["js"] }

View File

@@ -23,6 +23,7 @@ pub fn AchievementComponent(props: &Props) -> Html {
goal,
completed,
uuid,
time_of_reveal: _,
} = &props.achievement;
let uuid = *uuid;

View File

@@ -0,0 +1,130 @@
use crate::services::confirm::ConfirmService;
use crate::services::rest::RestService;
use common::Achievement;
use common::DeleteAchievement;
use common::ToggleAchievement;
use common::UpdateAchievementTimeOfReveal;
use std::ops::Deref;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use yew::classes;
use yew::function_component;
use yew::html;
use yew::use_state;
use yew::Callback;
use yew::Html;
use yew::Properties;
#[derive(Properties, PartialEq)]
pub struct Props {
pub achievement: Achievement,
pub number: usize,
}
#[function_component]
pub fn AchievementRevealTime(props: &Props) -> Html {
let achievement = &props.achievement;
let uuid = achievement.uuid;
let time_of_reveal: Option<String> = achievement
.time_of_reveal
.map(|naive_time| naive_time.format("%H:%M").to_string());
let timed_reveal_enabled = use_state(|| time_of_reveal.is_some());
let input_time = use_state(|| time_of_reveal.clone().unwrap_or("".to_string()));
let awaiting_response = use_state(|| false);
let onsubmit = {
let awaiting_response = awaiting_response.clone();
let timed_reveal_enabled = timed_reveal_enabled.clone();
let input_time = input_time.clone();
Callback::from(move |e: web_sys::SubmitEvent| {
e.prevent_default();
let new_time_of_reveal = if *timed_reveal_enabled {
if let Ok(naive_time) = chrono::NaiveTime::parse_from_str(&input_time, "%H:%M") {
Some(naive_time)
} else {
// TODO: show UI error
log::debug!("Could not parse time: {}", *input_time);
return;
}
} else {
None
};
let payload = UpdateAchievementTimeOfReveal {
time_of_reveal: new_time_of_reveal,
uuid,
};
awaiting_response.set(true);
let awaiting_response = awaiting_response.clone();
spawn_local(async move {
let res = RestService::update_time_of_reveal(payload).await;
awaiting_response.set(false);
});
})
};
let oninput_time = {
let input_time = input_time.clone();
Callback::from(move |e: web_sys::InputEvent| {
let Some(input) = e
.target()
.and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok()) else { unreachable!() };
log::debug!("{:?}", input.value());
input_time.set(input.value());
})
};
let oninput_timed_reveal_checkbox = {
let timed_reveal_enabled = timed_reveal_enabled.clone();
Callback::from(move |e: web_sys::InputEvent| {
let Some(input) = e
.target()
.and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok()) else { unreachable!() };
timed_reveal_enabled.set(input.checked());
})
};
let new_value: Option<&str> = timed_reveal_enabled.then_some(&**input_time);
let show_submit_button: bool = time_of_reveal.as_deref() != new_value;
html! {
<form {onsubmit}>
<div class="row flex">
// Achievement number
<div class="flex-intrinsic-size">
<p>{format!("{}.", props.number)}</p>
</div>
// Achievement text
<div class="flex-grow">
<p>{&achievement.goal}</p>
</div>
</div>
// Enable timed reveal checkbox
<label class="row">
<input oninput={oninput_timed_reveal_checkbox} checked={*timed_reveal_enabled} type="checkbox" />
<span class="label-body">{"Timed reveal"}</span>
</label>
{ if *timed_reveal_enabled { html! {
// Time input
<div>
<label for="revealTimeInput">{"Reveal time"}</label>
<input oninput={oninput_time} value={(*input_time).clone()} type="time" id="revealTimeInput" />
</div>
}} else { html! {}}}
// Submit button
{ if show_submit_button { html! {
<input class="button-primary" type="submit" value="Submit" disabled={ *awaiting_response } />
}} else { html! {}}}
<hr />
</form>
}
}

View File

@@ -0,0 +1,32 @@
use crate::components::achievement_reveal_time::AchievementRevealTime;
use yew::functional::*;
use yew::prelude::*;
use yew_router::prelude::*;
#[function_component]
pub fn AchievementRevealTimes() -> Html {
let nav = use_navigator().expect("cannot get navigator");
let app_state = use_context::<crate::AppState>().expect("no app state ctx found");
let achievements = app_state
.state
.achievements
.iter()
.cloned()
.enumerate()
.map(|(idx, a)| (idx + 1, a))
.map(|(n, a)| {
html! {
<AchievementRevealTime number={n} achievement={a} />
}
})
.collect::<Html>();
html! {
<>
<h1>{"Achievement Timed Reveals"}</h1>
<hr />
{achievements}
</>
}
}

View File

@@ -51,6 +51,7 @@ impl Component for CreateAchievementComponent {
log::info!("Creating achievement");
let payload = CreateAchievement {
goal: self.input_value.clone(),
time_of_reveal: None,
};
let link = ctx.link().clone();
spawn_local(async move {

View File

@@ -1,4 +1,6 @@
pub mod achievement;
pub mod achievement_reveal_time;
pub mod achievement_reveal_times;
pub mod admin;
pub mod create_achievement;
pub mod create_milestone;

View File

@@ -2,6 +2,7 @@ use crate::components::error::error_component::ErrorComponent;
use crate::components::error::error_provider::ErrorProvider;
use crate::event_bus::EventBus;
use crate::services::websocket::WebsocketService;
use components::achievement_reveal_times::AchievementRevealTimes;
use components::admin::Admin;
use components::create_achievement::CreateAchievementComponent;
use components::create_milestone::CreateMilestoneComponent;
@@ -27,6 +28,8 @@ enum Route {
CreateAchievement,
#[at("/create-milestone")]
CreateMilestone,
#[at("/reveal-times")]
RevealTimes,
#[not_found]
#[at("/404")]
NotFound,
@@ -39,6 +42,7 @@ fn switch(selected_route: Route) -> Html {
Route::CreateAchievement => html! {<CreateAchievementComponent/>},
Route::CreateMilestone => html! {<CreateMilestoneComponent/>},
Route::NotFound => html! {<h1>{"404 not found"}</h1>},
Route::RevealTimes => html! {<AchievementRevealTimes/>},
}
}

View File

@@ -4,6 +4,7 @@ use common::DeleteAchievement;
use common::DeleteMilestone;
use common::RestResponse;
use common::ToggleAchievement;
use common::UpdateAchievementTimeOfReveal;
use reqwasm::http::Request;
use serde::de::DeserializeOwned;
use serde::Serialize;
@@ -67,4 +68,10 @@ impl RestService {
pub async fn delete_milestone(payload: DeleteMilestone) -> Result<(), RestServiceError> {
Self::post_json(payload, "/api/v1/delete-milestone").await
}
pub async fn update_time_of_reveal(
payload: UpdateAchievementTimeOfReveal,
) -> Result<(), RestServiceError> {
Self::post_json(payload, "/api/v1/update-time-of-reveal").await
}
}