regalade/app/src/full_context.rs

195 lines
4.7 KiB
Rust
Raw Normal View History

use std::{
cell::{Cell, Ref, RefCell},
collections::HashSet,
rc::Rc,
sync::Arc,
};
use dioxus::prelude::*;
2023-08-05 12:54:49 +02:00
use dioxus_router::prelude::*;
use gloo_storage::{errors::StorageError, LocalStorage, Storage};
use uuid::Uuid;
2023-08-05 12:54:49 +02:00
use crate::{HouseholdInfo, LoginInfo, Route};
#[derive(Props)]
pub struct RedirectorProps<'a> {
children: Element<'a>,
}
pub struct RefreshHandle {
run: Box<dyn FnOnce()>,
}
impl RefreshHandle {
pub fn refresh(self) {
(self.run)()
}
}
#[derive(Copy, Clone)]
pub struct FullContextState<'a> {
root: &'a ProvidedFullContext,
value: &'a Rc<RefCell<FullContext>>,
}
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<RefCell<FullContext>>,
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::<ProvidedFullContext>()
.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<RefCell<ProvidedFullContextInner>>;
struct ProvidedFullContextInner {
value: Rc<RefCell<FullContext>>,
notify_any: Arc<dyn Fn(ScopeId)>,
consumers: HashSet<ScopeId>,
needs_regen: Cell<bool>,
update_root: Arc<dyn Fn()>,
}
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::<LoginInfo>("token").expect("Not called in a full context");
let household =
LocalStorage::get::<HouseholdInfo>("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 {
2023-08-05 12:54:49 +02:00
let navigator = use_navigator(cx);
let check_token = match LocalStorage::get::<LoginInfo>("token") {
Ok(_) => true,
Err(StorageError::KeyNotFound(_)) => {
2023-08-05 12:54:49 +02:00
navigator.push(Route::Login);
false
}
Err(e) => unreachable!("Could not get token: {e:?}"),
};
let check_household = match LocalStorage::get::<HouseholdInfo>("household") {
Ok(_) => true,
Err(StorageError::KeyNotFound(_)) => {
2023-08-05 12:54:49 +02:00
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
}
}