use err ctx

This commit is contained in:
Asger Juul Brunshøj 2023-06-22 21:30:00 +02:00
parent 5b49586da3
commit d39596da77
9 changed files with 70 additions and 31 deletions

View File

@ -1,10 +1,13 @@
use crate::components::error::error_provider::ErrorContext;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use common::Achievement; use common::Achievement;
use common::ToggleAchievement; use common::ToggleAchievement;
use std::rc::Rc;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use yew::classes; use yew::classes;
use yew::function_component; use yew::function_component;
use yew::html; use yew::html;
use yew::use_context;
use yew::Callback; use yew::Callback;
use yew::Html; use yew::Html;
use yew::Properties; use yew::Properties;
@ -17,6 +20,8 @@ pub struct Props {
#[function_component] #[function_component]
pub fn AchievementComponent(props: &Props) -> Html { pub fn AchievementComponent(props: &Props) -> Html {
let err_ctx = Rc::new(use_context::<ErrorContext>());
let Achievement { let Achievement {
goal, goal,
completed, completed,
@ -28,10 +33,15 @@ pub fn AchievementComponent(props: &Props) -> Html {
let onclick_toggle = Callback::from(move |_| { let onclick_toggle = Callback::from(move |_| {
log::info!("button click, toggling achievement"); log::info!("button click, toggling achievement");
let err_ctx = Rc::clone(&err_ctx);
spawn_local(async move { spawn_local(async move {
match RestService::toggle_achievement(ToggleAchievement { uuid }).await { match RestService::toggle_achievement(ToggleAchievement { uuid }).await {
Ok(_response) => {} Ok(_response) => {}
Err(_err) => {} Err(err) => {
if let Some(err_ctx) = &*err_ctx {
err_ctx.dispatch(err.to_string());
}
}
} }
}); });
}); });

View File

@ -1,8 +1,10 @@
use crate::components::error::error_provider::ErrorContext;
use crate::services::confirm::ConfirmService; use crate::services::confirm::ConfirmService;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use common::DeleteAchievement; use common::DeleteAchievement;
use common::DeleteMilestone; use common::DeleteMilestone;
use common::UpdateAchievementTimeOfReveal; use common::UpdateAchievementTimeOfReveal;
use std::rc::Rc;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use yew::function_component; use yew::function_component;
@ -27,8 +29,9 @@ pub fn Admin() -> Html {
.enumerate() .enumerate()
.map(|(idx, a)| (idx + 1, a)) .map(|(idx, a)| (idx + 1, a))
.map(|(n, a)| { .map(|(n, a)| {
let uuid = a.uuid.to_string();
html! { html! {
<Achievement number={n} achievement={a} /> <Achievement key={uuid} number={n} achievement={a} />
} }
}) })
.collect::<Html>(); .collect::<Html>();
@ -48,7 +51,10 @@ pub fn Admin() -> Html {
milestones.sort_by_key(|m| m.goal); milestones.sort_by_key(|m| m.goal);
let milestones = milestones let milestones = milestones
.into_iter() .into_iter()
.map(|m| html! { <Milestone milestone={m}/> }) .map(|m| {
let uuid = m.uuid.to_string();
html! { <Milestone key={uuid} milestone={m}/> }
})
.collect::<Html>(); .collect::<Html>();
html! { html! {
@ -93,6 +99,7 @@ struct AchievementProps {
#[function_component] #[function_component]
fn Achievement(props: &AchievementProps) -> Html { fn Achievement(props: &AchievementProps) -> Html {
let err_ctx = Rc::new(use_context::<ErrorContext>());
let achievement = &props.achievement; let achievement = &props.achievement;
let uuid = achievement.uuid; let uuid = achievement.uuid;
@ -108,6 +115,7 @@ fn Achievement(props: &AchievementProps) -> Html {
let awaiting_response = awaiting_response.clone(); let awaiting_response = awaiting_response.clone();
let timed_reveal_enabled = timed_reveal_enabled.clone(); let timed_reveal_enabled = timed_reveal_enabled.clone();
let input_time = input_time.clone(); let input_time = input_time.clone();
let err_ctx = Rc::clone(&err_ctx);
Callback::from(move |e: web_sys::SubmitEvent| { Callback::from(move |e: web_sys::SubmitEvent| {
e.prevent_default(); e.prevent_default();
@ -129,8 +137,16 @@ fn Achievement(props: &AchievementProps) -> Html {
}; };
awaiting_response.set(true); awaiting_response.set(true);
let awaiting_response = awaiting_response.clone(); let awaiting_response = awaiting_response.clone();
let err_ctx = Rc::clone(&err_ctx);
spawn_local(async move { spawn_local(async move {
let res = RestService::update_time_of_reveal(payload).await; match RestService::update_time_of_reveal(payload).await {
Ok(_response) => {}
Err(err) => {
if let Some(err_ctx) = &*err_ctx {
err_ctx.dispatch(err.to_string());
}
}
}
awaiting_response.set(false); awaiting_response.set(false);
}); });
}) })
@ -163,10 +179,15 @@ fn Achievement(props: &AchievementProps) -> Html {
} }
log::info!("Delete achievement confirmed."); log::info!("Delete achievement confirmed.");
let err_ctx = Rc::clone(&err_ctx);
spawn_local(async move { spawn_local(async move {
match RestService::delete_achievement(DeleteAchievement { uuid }).await { match RestService::delete_achievement(DeleteAchievement { uuid }).await {
Ok(_response) => {} Ok(_response) => {}
Err(_err) => {} Err(err) => {
if let Some(err_ctx) = &*err_ctx {
err_ctx.dispatch(err.to_string());
}
}
} }
}); });
}); });
@ -192,7 +213,7 @@ fn Achievement(props: &AchievementProps) -> Html {
</div> </div>
// Timed reveal form // Timed reveal form
<form onsubmit={onsubmit_timed_reveal}> <form onsubmit={onsubmit_timed_reveal} style="margin-left: 30px">
// Timed reveal: Enable checkbox // Timed reveal: Enable checkbox
<label> <label>
<span class="label-body" style="margin-right: 0.5rem; font-weight: bold"><i class="fas fa-clock" style="padding-right: 0.5em"/>{"Enable Timed Reveal:"}</span> <span class="label-body" style="margin-right: 0.5rem; font-weight: bold"><i class="fas fa-clock" style="padding-right: 0.5em"/>{"Enable Timed Reveal:"}</span>
@ -215,23 +236,27 @@ fn Achievement(props: &AchievementProps) -> Html {
</div> </div>
</form> </form>
<hr />
</div> </div>
} }
} }
#[function_component] #[function_component]
fn Milestone(props: &MilestoneProps) -> Html { fn Milestone(props: &MilestoneProps) -> Html {
let err_ctx = Rc::new(use_context::<ErrorContext>());
let uuid = props.milestone.uuid; let uuid = props.milestone.uuid;
let onclick_delete = Callback::from(move |_| { let onclick_delete = Callback::from(move |_| {
if !ConfirmService::confirm("Are you sure you want to delete?") { if !ConfirmService::confirm("Are you sure you want to delete?") {
return; return;
} }
let err_ctx = Rc::clone(&err_ctx);
spawn_local(async move { spawn_local(async move {
match RestService::delete_milestone(DeleteMilestone { uuid }).await { match RestService::delete_milestone(DeleteMilestone { uuid }).await {
Ok(_response) => {} Ok(_response) => {}
Err(_err) => {} Err(err) => {
if let Some(err_ctx) = &*err_ctx {
err_ctx.dispatch(err.to_string());
}
}
} }
}); });
}); });

View File

@ -1,4 +1,4 @@
use super::error::error_provider::ErrorContext; use super::error::error_provider::get_error_context;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use crate::services::rest::RestServiceError; use crate::services::rest::RestServiceError;
use common::CreateAchievement; use common::CreateAchievement;
@ -66,14 +66,12 @@ impl Component for CreateAchievementComponent {
match result { match result {
Ok(_response) => { Ok(_response) => {
let nav = ctx.link().navigator().unwrap(); let nav = ctx.link().navigator().unwrap();
nav.push(&crate::Route::Root); nav.push(&crate::Route::Admin);
} }
Err(err) => { Err(err) => {
let (error_context, _context_listener) = ctx if let Some(err_ctx) = get_error_context(ctx) {
.link() err_ctx.dispatch(err.to_string());
.context(Callback::from(|_: ErrorContext| ())) }
.expect("No Error Context Provided");
error_context.dispatch(err.to_string());
} }
}; };
self.awaiting_response = false; self.awaiting_response = false;

View File

@ -1,4 +1,4 @@
use super::error::error_provider::ErrorContext; use super::error::error_provider::get_error_context;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use crate::services::rest::RestServiceError; use crate::services::rest::RestServiceError;
use common::CreateMilestone; use common::CreateMilestone;
@ -67,14 +67,12 @@ impl Component for CreateMilestoneComponent {
match result { match result {
Ok(_response) => { Ok(_response) => {
let nav = ctx.link().navigator().unwrap(); let nav = ctx.link().navigator().unwrap();
nav.push(&crate::Route::Root); nav.push(&crate::Route::Admin)
} }
Err(err) => { Err(err) => {
let (error_context, _context_listener) = ctx if let Some(err_ctx) = get_error_context(ctx) {
.link() err_ctx.dispatch(err.to_string());
.context(Callback::from(|_: ErrorContext| ())) }
.expect("No Error Context Provided");
error_context.dispatch(err.to_string());
} }
}; };
self.awaiting_response = false; self.awaiting_response = false;
@ -92,7 +90,7 @@ impl Component for CreateMilestoneComponent {
let nav = ctx.link().navigator().unwrap(); let nav = ctx.link().navigator().unwrap();
let onclick_go_back = Callback::from(move |_: web_sys::MouseEvent| { let onclick_go_back = Callback::from(move |_: web_sys::MouseEvent| {
nav.push(&crate::Route::Root); nav.push(&crate::Route::Admin);
}); });
let onsubmit = link.callback(|e: web_sys::SubmitEvent| { let onsubmit = link.callback(|e: web_sys::SubmitEvent| {

View File

@ -1,3 +1,5 @@
//! This is the UI component that subscribes to and displays errors.
use super::error_provider::ErrorContext; use super::error_provider::ErrorContext;
use yew::prelude::*; use yew::prelude::*;

View File

@ -41,3 +41,10 @@ pub fn ErrorProvider(props: &ErrorProviderProps) -> Html {
</ContextProvider<ErrorContext>> </ContextProvider<ErrorContext>>
} }
} }
/// Helper for struct components to get the error context
pub fn get_error_context(ctx: &Context<impl yew::Component>) -> Option<ErrorContext> {
ctx.link()
.context(Callback::from(|_: ErrorContext| ()))
.map(|(error_context, _context_handle)| error_context)
}

View File

@ -3,11 +3,9 @@ use crate::components::milestone::MilestoneComponent;
use crate::util::group_achievements::group_achievements; use crate::util::group_achievements::group_achievements;
use yew::functional::*; use yew::functional::*;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*;
#[function_component] #[function_component]
pub fn Root() -> Html { pub fn Root() -> Html {
let nav = use_navigator().expect("cannot get navigator");
let app_state = use_context::<crate::AppState>().expect("no app state ctx found"); let app_state = use_context::<crate::AppState>().expect("no app state ctx found");
let current_time: chrono::NaiveTime = chrono::Local::now().time(); let current_time: chrono::NaiveTime = chrono::Local::now().time();
@ -21,7 +19,7 @@ pub fn Root() -> Html {
if let Some(time_of_reveal) = time_of_reveal { if let Some(time_of_reveal) = time_of_reveal {
let s = if time_of_reveal > current_time { let s = if time_of_reveal > current_time {
format!( format!(
"{} more revealed at {}!", "Next {} revealed at {}!",
group.len(), group.len(),
time_of_reveal.format("%H:%M") time_of_reveal.format("%H:%M")
) )
@ -38,8 +36,9 @@ pub fn Root() -> Html {
} }
for a in group { for a in group {
n += 1; n += 1;
let uuid = a.uuid.to_string();
let html = html! { let html = html! {
<AchievementComponent number={n} achievement={a} /> <AchievementComponent key={uuid} number={n} achievement={a} />
}; };
achievements_html.push(html); achievements_html.push(html);
} }
@ -55,7 +54,7 @@ pub fn Root() -> Html {
let mut milestones = app_state.state.milestones.clone(); let mut milestones = app_state.state.milestones.clone();
milestones.sort_by_key(|m| m.goal); milestones.sort_by_key(|m| m.goal);
let milestones = milestones.into_iter().map(|m| html! { <MilestoneComponent milestone={m} completed_achievements={completed_achievements} /> }).collect::<Html>(); let milestones = milestones.into_iter().map(|m| {let uuid = m.uuid.to_string(); html! { <MilestoneComponent key={uuid} milestone={m} completed_achievements={completed_achievements} /> }}).collect::<Html>();
html! { html! {
<> <>

View File

@ -41,7 +41,7 @@ impl RestService {
})?; })?;
if let Ok(rest_response) = response.json::<RestResponse<T>>().await { if let Ok(rest_response) = response.json::<RestResponse<T>>().await {
return rest_response.map_err(Into::into); return rest_response.map_err(RestServiceError::RestError);
} }
let err = RestServiceError::UnexpectedResponse(response); let err = RestServiceError::UnexpectedResponse(response);

View File

@ -38,7 +38,7 @@ mod tests {
let time1 = NaiveTime::from_hms_opt(12, 0, 0).unwrap(); let time1 = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
let time2 = NaiveTime::from_hms_opt(13, 0, 0).unwrap(); let time2 = NaiveTime::from_hms_opt(13, 0, 0).unwrap();
let mut achievements = vec![ let achievements = vec![
Achievement { Achievement {
goal: "Goal 1".to_string(), goal: "Goal 1".to_string(),
completed: true, completed: true,