Skip to content

Instantly share code, notes, and snippets.

@alilee
Created October 20, 2022 22:16
Show Gist options
  • Save alilee/caa66e1f05e0ef499b3eae07dbba36aa to your computer and use it in GitHub Desktop.
Save alilee/caa66e1f05e0ef499b3eae07dbba36aa to your computer and use it in GitHub Desktop.
yewdux relation
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