//! Ergonomic interface with shared state. use std::rc::Rc; use yew::{Callback, Properties}; use crate::handler::{ HandlerLink, Reduction, ReductionOnce, SharedHandler, StateHandler, StorageHandler, }; /// Handle for basic shared state. pub type SharedHandle = StateHandle>; /// Handle for shared state with persistent storage. pub type StorageHandle = StateHandle>; type Model = ::Model; pub trait Handle { type Handler: StateHandler; } impl Handle for StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone + 'static, { type Handler = HANDLER; } pub trait SharedState { type Handle: Handle; fn handle(&mut self) -> &mut Self::Handle; } /// Provides mutable access for wrapper component to update pub trait WrapperHandle: Handle { fn set_state(&mut self, state: Rc>); fn set_callbacks( &mut self, callback: Callback>>, callback_once: Callback>>, ); fn set_link(&mut self, _link: HandlerLink) {} } impl WrapperHandle for StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone + 'static, { fn set_state(&mut self, state: Rc>) { self.state = Some(state); } fn set_link(&mut self, link: HandlerLink) { self.link = Some(link); } fn set_callbacks( &mut self, callback: Callback>>, callback_once: Callback>>, ) { self.callback = callback; self.callback_once = callback_once; } } /// Interface to shared state #[derive(Properties)] pub struct StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone + 'static, { #[prop_or_default] state: Option>>, #[prop_or_default] callback: Callback>>, #[prop_or_default] callback_once: Callback>>, #[prop_or_default] link: Option>, } impl StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone + 'static, { pub fn link(&self) -> &HandlerLink { self.link.as_ref().expect( "Link accessed prematurely. Is your component wrapped in a SharedStateComponent?", ) } pub fn state(&self) -> &Model { self.state.as_ref().expect( "State accessed prematurely. Is your component wrapped in a SharedStateComponent?", ) } /// Apply a function that may mutate shared state. /// Changes are not immediate, and must be handled in `Component::change`. pub fn reduce(&self, f: impl FnOnce(&mut Model) + 'static) { self.callback_once.emit(Box::new(f)) } /// Convenience method for modifying shared state directly from a `Callback`. /// The callback event is ignored here, see `reduce_callback_with` for the alternative. pub fn reduce_callback( &self, f: impl Fn(&mut Model) + 'static, ) -> Callback where Model: 'static, { let f = Rc::new(f); self.callback.reform(move |_| f.clone()) } /// Convenience method for modifying shared state directly from a `CallbackOnce`. /// The callback event is ignored here, see `reduce_callback_once_with` for the alternative. pub fn reduce_callback_once( &self, f: impl FnOnce(&mut Model) + 'static, ) -> Callback where Model: 'static, { let f = Box::new(f); let cb = self.callback_once.clone(); Callback::once(move |_| cb.emit(f)) } /// Convenience method for modifying shared state directly from a `Callback`. /// Similar to `reduce_callback` but it also accepts the fired event. pub fn reduce_callback_with( &self, f: impl Fn(&mut Model, E) + 'static, ) -> Callback where Model: 'static, E: Clone, { let f = Rc::new(f); self.callback.reform(move |e: E| { let f = f.clone(); Rc::new(move |state| f.clone()(state, e.clone())) }) } /// Convenience method for modifying shared state directly from a `CallbackOnce`. /// Similar to `reduce_callback` but it also accepts the fired event. pub fn reduce_callback_once_with( &self, f: impl FnOnce(&mut Model, E) + 'static, ) -> Callback where Model: 'static, { let cb = self.callback_once.clone(); Callback::once(move |e| cb.emit(Box::new(move |state| f(state, e)))) } } impl SharedState for StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone, { type Handle = Self; fn handle(&mut self) -> &mut Self::Handle { self } } impl Default for StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone, { fn default() -> Self { Self { state: Default::default(), callback: Default::default(), callback_once: Default::default(), link: Default::default(), } } } impl Clone for StateHandle where HANDLER: StateHandler, HandlerLink: Clone, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone, { fn clone(&self) -> Self { Self { state: self.state.clone(), callback: self.callback.clone(), callback_once: self.callback_once.clone(), link: self.link.clone(), } } } impl PartialEq for StateHandle where HANDLER: StateHandler, ::Message: Clone, ::Output: Clone, ::Input: Clone, Model: Clone, { fn eq(&self, other: &Self) -> bool { self.state .as_ref() .zip(other.state.as_ref()) .map(|(a, b)| Rc::ptr_eq(a, b)) .unwrap_or(false) && self.callback == other.callback && self.callback_once == other.callback_once } }