re-write create_milestone as functional component

This commit is contained in:
Asger Juul Brunshøj 2023-06-22 22:37:28 +02:00
parent d39596da77
commit 7793f6a5dc
2 changed files with 83 additions and 99 deletions

View File

@ -1,127 +1,113 @@
use super::error::error_provider::get_error_context; use crate::components::error::error_provider::ErrorContext;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use crate::services::rest::RestServiceError;
use common::CreateMilestone; use common::CreateMilestone;
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 web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
use yew::html; use yew::html;
use yew::prelude::*;
use yew::Callback; use yew::Callback;
use yew::Component;
use yew::Html; use yew::Html;
use yew::NodeRef; use yew_router::prelude::use_navigator;
use yew_router::scope_ext::RouterScopeExt;
#[derive(Debug)] #[derive(Properties, PartialEq)]
pub enum Msg { pub struct Props;
Submit,
SubmitResult(Result<(), RestServiceError>),
UpdateInput(String),
}
#[derive(Default)] #[function_component]
pub struct CreateMilestoneComponent { pub fn CreateMilestoneComponent(_: &Props) -> Html {
input_value: String, let nav = use_navigator().expect("cannot get navigator");
input_ref: NodeRef, let err_ctx = Rc::new(use_context::<ErrorContext>());
/// Submitted and awaiting response let input_ref = use_node_ref();
awaiting_response: bool, // Focus input after it is rendered
} {
impl Component for CreateMilestoneComponent { let input_ref = input_ref.clone();
type Message = Msg; use_effect(move || {
type Properties = (); if let Some(input_element) = input_ref.get() {
fn create(_ctx: &yew::Context<Self>) -> Self {
Self::default()
}
fn rendered(&mut self, _ctx: &yew::Context<Self>, first_render: bool) {
if first_render {
if let Some(input_element) = self.input_ref.get() {
let input_element = input_element let input_element = input_element
.dyn_into::<HtmlInputElement>() .dyn_into::<HtmlInputElement>()
.expect("Failed to cast input element"); .expect("Failed to cast input element");
input_element.focus().expect("Failed to focus input"); input_element.focus().expect("Failed to focus input");
} }
}
// effect destructor
|| {}
});
} }
fn update(&mut self, ctx: &yew::Context<Self>, msg: Self::Message) -> bool { let input_value = use_state(String::default);
match msg { let awaiting_response = use_state(|| false);
Msg::Submit => {
log::info!("Creating milestone");
let Ok(goal) = self.input_value.parse::<f64>() else { return false; };
let goal = goal as usize; let onclick_go_back = {
let payload = CreateMilestone { goal }; let nav = nav.clone();
let link = ctx.link().clone(); Callback::from(move |_: web_sys::MouseEvent| {
spawn_local(async move {
let res = RestService::create_milestone(payload).await;
link.send_message(Msg::SubmitResult(res));
});
self.awaiting_response = true;
true
}
Msg::SubmitResult(result) => {
match result {
Ok(_response) => {
let nav = ctx.link().navigator().unwrap();
nav.push(&crate::Route::Admin)
}
Err(err) => {
if let Some(err_ctx) = get_error_context(ctx) {
err_ctx.dispatch(err.to_string());
}
}
};
self.awaiting_response = false;
true
}
Msg::UpdateInput(value) => {
self.input_value = value;
true
}
}
}
fn view(&self, ctx: &yew::Context<Self>) -> Html {
let link = ctx.link().clone();
let nav = ctx.link().navigator().unwrap();
let onclick_go_back = Callback::from(move |_: web_sys::MouseEvent| {
nav.push(&crate::Route::Admin); nav.push(&crate::Route::Admin);
}); })
};
let onsubmit = link.callback(|e: web_sys::SubmitEvent| { let onsubmit = {
let input_value = input_value.clone();
let awaiting_response = awaiting_response.clone();
Callback::from(move |e: yew::SubmitEvent| {
e.prevent_default(); e.prevent_default();
Msg::Submit
});
let oninput = link.callback(|e: web_sys::InputEvent| { log::info!("Creating milestone");
let Ok(goal) = input_value.parse::<f64>() else {
log::error!("Input parse error");
return;
};
let goal = goal as usize;
let payload = CreateMilestone { goal };
{
let nav = nav.clone();
let err_ctx = Rc::clone(&err_ctx);
awaiting_response.set(true);
let awaiting_response = awaiting_response.clone();
spawn_local(async move {
match RestService::create_milestone(payload).await {
Ok(_response) => nav.push(&crate::Route::Admin),
Err(err) => {
if let Some(err_ctx) = &*err_ctx {
err_ctx.dispatch(err.to_string());
}
}
};
awaiting_response.set(false);
});
}
})
};
let oninput = {
let input_value = input_value.clone();
Callback::from(move |e: web_sys::InputEvent| {
let Some(input) = e let Some(input) = e
.target() .target()
.and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok()) else { unreachable!() }; .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok()) else { unreachable!() };
Msg::UpdateInput(input.value()) input_value.set(input.value());
}); })
};
html! { html! {
<> <>
<div class="row"> <div class="row">
<button onclick={onclick_go_back} class="button">{"Back"}</button> <button onclick={onclick_go_back} class="button">{"Back"}</button>
</div>
<form {onsubmit} >
<hr />
<div class="row">
<div class="twelve columns">
<label for="milestoneInput">{"New milestone"}</label>
<input ref={input_ref.clone()} {oninput} value={input_value.to_string()} class="u-full-width" type="number" id="milestoneInput" />
</div> </div>
</div>
<form {onsubmit} > <input class="button-primary" type="submit" value="Submit" disabled={*awaiting_response} />
<hr /> </form>
<div class="row"> </>
<div class="twelve columns">
<label for="milestoneInput">{"New milestone"}</label>
<input ref={self.input_ref.clone()} {oninput} value={self.input_value.to_string()} class="u-full-width" type="number" id="milestoneInput" />
</div>
</div>
<input class="button-primary" type="submit" value="Submit" disabled={self.awaiting_response} />
</form>
</>
}
} }
} }

View File

@ -1,3 +1 @@
- UI errors from failed requests
- websocket reconnect - websocket reconnect
- make it an admin interface, and move the new + remove buttons there, as well as milestone management