//! Common Component module. //! This is used to factor out some common functionality that is recurrent in modules all over the //! application. In particular: //! - error handling //! - task handling //! - storing props //! //! The pattern used is the //! [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) pattern: The //! [`CommonComponent`] trait must be implemented with `Self` as the parameter, e.g. //! //! ```ignore //! struct MyComponent; //! impl CommonComponent for MyComponent { ... } //! ``` //! //! The component should also have a `CommonComponentParts` as a field, usually named //! `common`. //! //! Then the [`yew::prelude::Component::update`] method can delegate to //! [`CommonComponentParts::update`]. This will in turn call [`CommonComponent::handle_msg`] and //! take care of error and task handling. use crate::infra::api::HostService; use anyhow::{Error, Result}; use graphql_client::GraphQLQuery; use yew::{ prelude::*, services::{fetch::FetchTask, ConsoleService}, }; use yewtil::NeqAssign; /// Trait required for common components. pub trait CommonComponent>: Component { /// Handle the incoming message. If an error is returned here, any running task will be /// cancelled, the error will be written to the [`CommonComponentParts::error`] and the /// component will be refreshed. fn handle_msg(&mut self, msg: ::Message) -> Result; /// Get a mutable reference to the inner component parts, necessary for the CRTP. fn mut_common(&mut self) -> &mut CommonComponentParts; } /// Structure that contains the common parts needed by most components. /// The fields of [`props`] are directly accessible through a `Deref` implementation. pub struct CommonComponentParts> { link: ComponentLink, pub props: ::Properties, pub error: Option, task: Option, } impl> CommonComponentParts { /// Whether there is a currently running task in the background. pub fn is_task_running(&self) -> bool { self.task.is_some() } /// Cancel any background task. pub fn cancel_task(&mut self) { self.task = None; } pub fn create(props: ::Properties, link: ComponentLink) -> Self { Self { link, props, error: None, task: None, } } /// This should be called from the [`yew::prelude::Component::update`]: it will in turn call /// [`CommonComponent::handle_msg`] and handle any resulting error. pub fn update(com: &mut C, msg: ::Message) -> ShouldRender { com.mut_common().error = None; match com.handle_msg(msg) { Err(e) => { ConsoleService::error(&e.to_string()); com.mut_common().error = Some(e); com.mut_common().cancel_task(); true } Ok(b) => b, } } /// Same as above, but the resulting error is instead passed to the reporting function. pub fn update_and_report_error( com: &mut C, msg: ::Message, report_fn: Callback, ) -> ShouldRender { let should_render = Self::update(com, msg); com.mut_common() .error .take() .map(|e| { report_fn.emit(e); true }) .unwrap_or(should_render) } /// This can be called from [`yew::prelude::Component::update`]: it will check if the /// properties have changed and return whether the component should update. pub fn change(&mut self, props: ::Properties) -> ShouldRender where ::Properties: std::cmp::PartialEq, { self.props.neq_assign(props) } /// Create a callback from the link. pub fn callback(&self, function: F) -> Callback where M: Into, F: Fn(IN) -> M + 'static, { self.link.callback(function) } /// Call `method` from the backend with the given `request`, and pass the `callback` for the /// result. Returns whether _starting the call_ failed. pub fn call_backend( &mut self, method: M, req: Req, callback: Cb, ) -> Result<()> where M: Fn(Req, Callback) -> Result, Cb: FnOnce(Resp) -> ::Message + 'static, { self.task = Some(method(req, self.link.callback_once(callback))?); Ok(()) } /// Call the backend with a GraphQL query. /// /// `EnumCallback` should usually be left as `_`. pub fn call_graphql( &mut self, variables: QueryType::Variables, enum_callback: EnumCallback, error_message: &'static str, ) where QueryType: GraphQLQuery + 'static, EnumCallback: Fn(Result) -> ::Message + 'static, { self.task = HostService::graphql_query::( variables, self.link.callback(enum_callback), error_message, ) .map_err::<(), _>(|e| { ConsoleService::log(&e.to_string()); self.error = Some(e); }) .ok(); } } impl> std::ops::Deref for CommonComponentParts { type Target = ::Properties; fn deref(&self) -> &::Target { &self.props } } impl> std::ops::DerefMut for CommonComponentParts { fn deref_mut(&mut self) -> &mut ::Target { &mut self.props } }