Created
October 20, 2022 22:16
-
-
Save alilee/caa66e1f05e0ef499b3eae07dbba36aa to your computer and use it in GitHub Desktop.
yewdux relation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::HashMap; | |
use std::fmt::Debug; | |
use std::marker::PhantomData; | |
use std::rc::Rc; | |
use serde::{Deserialize, Serialize}; | |
use reqwasm::http::Request; | |
use yew::prelude::*; | |
use yew::platform::spawn_local; | |
use yewdux::prelude::*; | |
use yewdux::mrc::Mrc; | |
use crate::record::{Mutation, Record, State}; | |
use super::{APIEndpoint, APIDispatch}; | |
#[derive(Default, Clone)] | |
pub struct Relation<R: Record, A: 'static> { | |
map: Mrc<HashMap<R::Id, (Rc<R>, State)>>, | |
preserved: Mrc<HashMap<R::Id, (Rc<R>, State)>>, | |
next_temp: R::Id, | |
_a: PhantomData<A>, | |
} | |
impl<R, A> Relation<R, A> | |
where R: Record + Debug + Clone + Serialize + for<'de> Deserialize<'de> + APIEndpoint + 'static, | |
A: APIDispatch + Clone + 'static { | |
pub fn get(&self, id: R::Id) -> (Rc<R>, State) { | |
let mut map = self.map.borrow_mut(); | |
match map.get(&id) { | |
None => { | |
Self::forward(id, &Mutation::<R>::Load(id)); | |
let result = Rc::new(R::new(id)); | |
map.insert(id, (Rc::clone(&result), State::Loading)); | |
(result, State::Loading) | |
} | |
Some((result, state)) => { | |
(Rc::clone(result), *state) | |
} | |
} | |
} | |
pub fn create(&mut self) -> R::Id { | |
let result = R::new(R::next_temp(self.next_temp)); | |
self.next_temp = result.id(); | |
self.map.borrow_mut().insert(self.next_temp, (Rc::new(result), State::Dirty)); | |
self.next_temp | |
} | |
pub fn all_ids(&self) -> Vec<R::Id> { | |
self.map.borrow().keys().map(|id| *id).collect() | |
} | |
pub fn _preserve(&mut self, id: R::Id) { | |
let map = self.map.borrow(); | |
let (r, state) = map.get(&id).unwrap(); | |
self.preserved.borrow_mut().insert(id, (r.clone(), *state)); | |
} | |
pub fn update(&mut self, r: Rc<R>) { | |
self.map.borrow_mut().insert(r.id(), (r, State::Dirty)); | |
} | |
pub fn _undo(&mut self, id: R::Id) { | |
let (r, state) = self.preserved.borrow_mut().remove(&id).unwrap(); | |
self.map.borrow_mut().insert(id, (r, state)); | |
} | |
pub fn forward<M: Serialize>(id: R::Id, msg: &M) { | |
let body = serde_json::to_string(msg).unwrap(); | |
spawn_local(async move { | |
let resp = Request::post(R::url()) | |
.header("Content-Type", "application/json") | |
.body(body) | |
.send() | |
.await | |
.unwrap(); | |
let dispatch = Dispatch::<Self>::new(); | |
if !resp.ok() { | |
error!("Error fetching data {} ({})", resp.status(), resp.status_text()); | |
dispatch.reduce_mut(|rel| rel.error(id)); | |
}; | |
let resp = resp.text().await.unwrap(); | |
if !A::dispatch(resp) { | |
dispatch.reduce_mut(|rel| rel.error(id)); | |
}; | |
}); | |
} | |
pub fn dispatch(id: R::Id, state: State, r: Option<R>, old_id: Option<R::Id>, error: Option<String>) { | |
info!("dispatch {:?}, {:?}, {:?}, {:?}, {:?}", id, state, r, old_id, error); | |
let dispatch = Dispatch::<Self>::new(); | |
match state { | |
State::Current => dispatch.reduce_mut(|rel| rel.load(Rc::new(r.unwrap()))), | |
State::Deleting => dispatch.reduce_mut(|rel| rel.map.borrow_mut().remove(&id)), | |
State::Error => dispatch.reduce_mut(|rel| rel.error(id)), | |
_ => unreachable!(), | |
}; | |
} | |
fn loading(&mut self, id: R::Id) { | |
let mut map = self.map.borrow_mut(); | |
let v = map.get_mut(&id).unwrap(); | |
v.1 = State::Loading; | |
} | |
fn load(&mut self, r: Rc<R>) { | |
let mut map = self.map.borrow_mut(); | |
map.insert(r.id(), (r, State::Current)); | |
} | |
fn delete(&mut self, id: R::Id) { | |
self.map.borrow_mut().insert(id, (Rc::new(R::new(id)), State::Deleting)); | |
} | |
fn save(&mut self, id: R::Id) { | |
let mut map = self.map.borrow_mut(); | |
let v = map.get_mut(&id).unwrap(); | |
v.1 = State::Dirty; | |
self.preserved.borrow_mut().remove(&id); | |
} | |
fn error(&mut self, id: R::Id) { | |
let mut map = self.map.borrow_mut(); | |
let v = map.get_mut(&id).unwrap(); | |
v.1 = State::Error; | |
self.preserved.borrow_mut().remove(&id); | |
} | |
} | |
impl<R: Record + 'static, A> Store for Relation<R, A> { | |
fn new() -> Self { | |
Self { | |
map: Mrc::new(HashMap::new()), | |
preserved: Mrc::new(HashMap::new()), | |
next_temp: R::init_temp(), | |
_a: Default::default(), | |
} | |
} | |
fn should_notify(&self, old: &Self) -> bool { | |
self.map != old.map // not changed just because new temp or records preserved | |
} | |
} | |
impl<R, A> Reducer<Relation<R, A>> for Mutation<R> | |
where R: Record + Clone + Debug + Serialize + APIEndpoint + for<'d> Deserialize<'d> + 'static, | |
A: Clone + APIDispatch { | |
fn apply(&self, mut state: Rc<Relation<R, A>>) -> Rc<Relation<R, A>> { | |
let rel = Rc::make_mut(&mut state); | |
let _x = match self { | |
Self::Load(id) => { | |
rel.loading(*id); | |
Relation::<R, A>::forward(*id, &self); | |
} | |
Self::Find(r) => { | |
Relation::<R, A>::forward(r.id(), &self); | |
} | |
Self::Delete(id) => { | |
rel.delete(*id); | |
Relation::<R, A>::forward(*id, &self); | |
} | |
Self::Save(r) => { | |
rel.save(r.id()); | |
Relation::<R, A>::forward(r.id(), &self); | |
} | |
}; | |
state | |
} | |
} | |
#[function_component(DebugRelationView)] | |
pub fn debug_relation<T, A>() -> Html | |
where T: Record + Clone + Debug + Serialize + for<'de> Deserialize<'de> + APIEndpoint + 'static, | |
A: APIDispatch + Clone + 'static { | |
let relation = use_store_value::<Relation<T, A>>(); | |
html! { | |
<div class="content"> | |
<h4> { format!("Relation<{}>", std::any::type_name::<T>()) } </h4> | |
<dl> | |
{ | |
relation.all_ids().iter().map(|id| { | |
let (record, state) = relation.get(*id); | |
html! { | |
<> | |
<dt> { format!("{:?} ({:?})", record.id(), state) } </dt> | |
<dd> <code> { format!("{:?}", record) } </code> </dd> | |
</> | |
} | |
}).collect::<Html>() | |
} | |
</dl> | |
</div> | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment