use std::{ cell::{Cell, Ref, RefCell}, collections::HashSet, rc::Rc, sync::Arc, }; use dioxus::prelude::*; use dioxus_router::prelude::*; use gloo_storage::{errors::StorageError, LocalStorage, Storage}; use uuid::Uuid; use crate::{HouseholdInfo, LoginInfo, Route}; #[derive(Props)] pub struct RedirectorProps<'a> { children: Element<'a>, } pub struct RefreshHandle { run: Box, } impl RefreshHandle { pub fn refresh(self) { (self.run)() } } #[derive(Copy, Clone)] pub struct FullContextState<'a> { root: &'a ProvidedFullContext, value: &'a Rc>, } impl<'a> FullContextState<'a> { pub fn read(&self) -> Ref<'_, FullContext> { self.value.borrow() } pub fn refresh(&self) { let r = self.root.borrow(); r.needs_regen.set(true); (r.update_root)(); } pub fn refresh_handle(&self) -> RefreshHandle { let r = self.root.clone(); RefreshHandle { run: Box::new(move || { let root = r.borrow(); root.needs_regen.set(true); (root.update_root)(); }), } } } struct FullContextStateInner { root: ProvidedFullContext, value: Rc>, scope_id: ScopeId, } impl Drop for FullContextStateInner { fn drop(&mut self) { let mut root = self.root.borrow_mut(); root.consumers.remove(&self.scope_id); } } pub fn use_full_context(cx: &ScopeState) -> FullContextState { let state = cx.use_hook(|| { let scope_id = cx.scope_id(); let root = cx .consume_context::() .expect("Called use_full_context not in a full context scope"); let mut r = root.borrow_mut(); r.consumers.insert(scope_id); let value = r.value.clone(); drop(r); FullContextStateInner { root, value, scope_id, } }); FullContextState { root: &state.root, value: &state.value, } } pub fn use_trimmed_context(cx: &ScopeState) -> (String, Uuid) { let binding = use_full_context(cx); let ctx = binding.read(); (ctx.login.token.clone(), ctx.household.id) } #[derive(Clone)] pub struct FullContext { pub login: LoginInfo, pub household: HouseholdInfo, } type ProvidedFullContext = Rc>; struct ProvidedFullContextInner { value: Rc>, notify_any: Arc, consumers: HashSet, needs_regen: Cell, update_root: Arc, } impl ProvidedFullContextInner { fn notify_consumers(&mut self) { for &consumer in &self.consumers { (self.notify_any)(consumer) } } } fn use_full_context_setter(cx: &ScopeState) { let gen = || { let login = LocalStorage::get::("token").expect("Not called in a full context"); let household = LocalStorage::get::("household").expect("Not called in a full context"); FullContext { login, household } }; let hook = cx.use_hook(move || { let state = Rc::new(RefCell::new(ProvidedFullContextInner { value: Rc::new(RefCell::new(gen())), consumers: HashSet::new(), notify_any: cx.schedule_update_any(), update_root: cx.schedule_update(), needs_regen: Cell::new(false), })); cx.provide_context(state.clone()); state }); if hook.borrow().needs_regen.get() { let mut hook = (**hook).borrow_mut(); *(*hook.value).borrow_mut() = gen(); hook.notify_consumers(); } } fn FullContextRedirectInner<'a>(cx: Scope<'a, RedirectorProps<'a>>) -> Element { use_full_context_setter(cx); cx.render(rsx! {&cx.props.children}) } pub fn FullContextRedirect<'a>(cx: Scope<'a, RedirectorProps<'a>>) -> Element { let navigator = use_navigator(cx); let check_token = match LocalStorage::get::("token") { Ok(_) => true, Err(StorageError::KeyNotFound(_)) => { navigator.push(Route::Login); false } Err(e) => unreachable!("Could not get token: {e:?}"), }; let check_household = match LocalStorage::get::("household") { Ok(_) => true, Err(StorageError::KeyNotFound(_)) => { navigator.push(Route::HouseholdSelection); false } Err(e) => unreachable!("Could not get household: {e:?}"), }; if check_token && check_household { cx.render(rsx! { FullContextRedirectInner { &cx.props.children } }) } else { None } }