mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Iniitial work on transitions
This commit is contained in:
parent
4da115b52c
commit
ef032092f1
9 changed files with 154 additions and 41 deletions
4
TODO.md
4
TODO.md
|
@ -2,6 +2,10 @@
|
|||
- [x] Resource
|
||||
- [x] Suspense
|
||||
- [ ] Transitions
|
||||
- [ ] Tests for Suspense/Transitions
|
||||
- [ ] Bugs in Suspense/Transitions
|
||||
- [ ] infinite loop if reading resource in effect
|
||||
- [ ] rendering bugs if `Suspense` doesn't have a parent
|
||||
- [ ] Router
|
||||
- [ ] Docs (and clippy warning to insist on docs)
|
||||
- [ ] Read through + understand...
|
||||
|
|
|
@ -31,7 +31,7 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
|
|||
|
||||
pub fn fetch_example(cx: Scope) -> web_sys::Element {
|
||||
let (cat_count, set_cat_count) = cx.create_signal::<u32>(3);
|
||||
let cats = cx.create_ref(cx.create_resource(cat_count.clone(), |count| fetch_cats(*count)));
|
||||
let cats = cx.create_ref(cx.create_resource(cat_count, |count| fetch_cats(*count)));
|
||||
|
||||
view! {
|
||||
<div>
|
||||
|
@ -47,7 +47,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
|
|||
/>
|
||||
</label>
|
||||
<div>
|
||||
//<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
|
||||
<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
|
||||
{move || match &*cats.read() {
|
||||
ResourceState::Idle => view! { <p>"(no data)"</p> },
|
||||
ResourceState::Pending { .. } => view! { <p>"Loading..."</p> },
|
||||
|
@ -64,7 +64,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
|
|||
}</div>
|
||||
}
|
||||
}}
|
||||
//</Suspense>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -123,19 +123,6 @@ pub fn insert_expression<'a>(
|
|||
mut current: Child<'a>,
|
||||
before: Option<&web_sys::Node>,
|
||||
) -> Child<'a> {
|
||||
crate::warn!(
|
||||
"insert {:?} on {} to replace {:?}",
|
||||
new_value,
|
||||
parent.node_name(),
|
||||
current
|
||||
);
|
||||
if let Child::Node(node) = ¤t {
|
||||
crate::log!(
|
||||
"current's parent = {}",
|
||||
node.parent_node().unwrap().node_name()
|
||||
);
|
||||
}
|
||||
|
||||
if new_value == ¤t {
|
||||
current
|
||||
} else {
|
||||
|
@ -180,11 +167,11 @@ pub fn insert_expression<'a>(
|
|||
}
|
||||
}
|
||||
Child::Node(old_node) => {
|
||||
crate::warn!(
|
||||
/* crate::warn!(
|
||||
"replacing old node with new node\n\nparents are {} and {}",
|
||||
old_node.parent_node().unwrap().node_name(),
|
||||
node.parent_node().unwrap().node_name()
|
||||
);
|
||||
); */
|
||||
replace_with(old_node.unchecked_ref(), node);
|
||||
Child::Node(node.clone())
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ mod scope_arena;
|
|||
mod signal;
|
||||
mod spawn;
|
||||
mod suspense;
|
||||
mod transition;
|
||||
|
||||
pub use effect::*;
|
||||
pub use resource::*;
|
||||
|
@ -20,6 +21,7 @@ pub use scope::*;
|
|||
pub use signal::*;
|
||||
pub use spawn::*;
|
||||
pub use suspense::*;
|
||||
pub use transition::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -10,6 +10,22 @@ pub enum ResourceState<T> {
|
|||
Ready { data: T },
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for ResourceState<T>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Idle => write!(f, "Idle"),
|
||||
Self::Pending { abort_handle } => f
|
||||
.debug_struct("Pending")
|
||||
.field("abort_handle", abort_handle)
|
||||
.finish(),
|
||||
Self::Ready { data } => f.debug_struct("Ready").field("data", data).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Resource<'a, S, T, Fu>
|
||||
where
|
||||
S: 'static + Clone,
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
use std::{cell::RefCell, rc::Weak};
|
||||
|
||||
use crate::EffectInner;
|
||||
use crate::{EffectInner, ReadSignal, TransitionState, WriteSignal};
|
||||
|
||||
pub struct RootContext {
|
||||
pub(crate) stack: RefCell<Vec<Weak<EffectInner>>>,
|
||||
pub(crate) transition_pending: RefCell<Option<(ReadSignal<bool>, WriteSignal<bool>)>>,
|
||||
pub(crate) transition: RefCell<Option<TransitionState>>,
|
||||
}
|
||||
|
||||
impl RootContext {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: RefCell::new(Vec::new()),
|
||||
stack: Default::default(),
|
||||
transition_pending: Default::default(),
|
||||
transition: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::scope_arena::ScopeArena;
|
||||
use crate::{EffectInner, Resource, SignalState};
|
||||
use crate::{signal_from_root_context, EffectInner, Resource};
|
||||
|
||||
use super::{root_context::RootContext, Effect, ReadSignal, WriteSignal};
|
||||
use std::future::Future;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
};
|
||||
|
@ -18,6 +18,14 @@ pub fn create_scope<'disposer>(
|
|||
root_context: &'static RootContext,
|
||||
f: impl for<'a> FnOnce(Scope<'a>),
|
||||
) -> ScopeDisposer<'disposer> {
|
||||
// create global transition tracking if this is the first scope
|
||||
{
|
||||
let mut transition = root_context.transition_pending.borrow_mut();
|
||||
if transition.is_none() {
|
||||
*transition = Some(signal_from_root_context(root_context, false));
|
||||
}
|
||||
}
|
||||
|
||||
let inner = ScopeInner::new(&root_context);
|
||||
let boxed_inner = Box::new(inner);
|
||||
let inner_ptr = Box::into_raw(boxed_inner);
|
||||
|
@ -36,7 +44,7 @@ pub fn create_scope<'disposer>(
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BoundedScope<'a, 'b: 'a> {
|
||||
inner: &'a ScopeInner<'a>,
|
||||
pub(crate) inner: &'a ScopeInner<'a>,
|
||||
/// `&'b` for covariance!
|
||||
_phantom: PhantomData<&'b ()>,
|
||||
}
|
||||
|
@ -87,7 +95,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
|
|||
|
||||
pub fn create_resource<S, T, Fu>(
|
||||
self,
|
||||
source: ReadSignal<S>,
|
||||
source: &ReadSignal<S>,
|
||||
fetcher: impl Fn(&S) -> Fu + 'static,
|
||||
) -> Resource<'a, S, T, Fu>
|
||||
where
|
||||
|
@ -95,7 +103,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
|
|||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
Resource::new(self, source, fetcher)
|
||||
Resource::new(self, source.clone(), fetcher)
|
||||
}
|
||||
|
||||
pub fn child_scope<F>(self, f: F) -> ScopeDisposer<'a>
|
||||
|
@ -134,7 +142,7 @@ impl<'a, 'b> BoundedScope<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
struct ScopeInner<'a> {
|
||||
pub(crate) struct ScopeInner<'a> {
|
||||
pub(crate) root_context: &'static RootContext,
|
||||
|
||||
pub(crate) parent: Option<&'a ScopeInner<'a>>,
|
||||
|
@ -164,21 +172,7 @@ impl<'a> ScopeInner<'a> {
|
|||
}
|
||||
|
||||
pub fn signal<T>(&self, value: T) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
let state = Rc::new(SignalState {
|
||||
value: RefCell::new(value),
|
||||
subscriptions: RefCell::new(HashSet::new()),
|
||||
});
|
||||
|
||||
let writer = WriteSignal {
|
||||
inner: Rc::downgrade(&state),
|
||||
};
|
||||
|
||||
let reader = ReadSignal {
|
||||
stack: self.root_context,
|
||||
inner: state,
|
||||
};
|
||||
|
||||
(reader, writer)
|
||||
signal_from_root_context(self.root_context, value)
|
||||
}
|
||||
|
||||
pub fn untrack<T>(&self, f: impl Fn() -> T) -> T {
|
||||
|
|
|
@ -9,6 +9,27 @@ use crate::EffectInner;
|
|||
|
||||
use super::{root_context::RootContext, EffectDependency};
|
||||
|
||||
pub(crate) fn signal_from_root_context<T>(
|
||||
root_context: &'static RootContext,
|
||||
value: T,
|
||||
) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
let state = Rc::new(SignalState {
|
||||
value: RefCell::new(value),
|
||||
subscriptions: RefCell::new(HashSet::new()),
|
||||
});
|
||||
|
||||
let writer = WriteSignal {
|
||||
inner: Rc::downgrade(&state),
|
||||
};
|
||||
|
||||
let reader = ReadSignal {
|
||||
stack: root_context,
|
||||
inner: state,
|
||||
};
|
||||
|
||||
(reader, writer)
|
||||
}
|
||||
|
||||
pub struct ReadSignal<T: 'static> {
|
||||
pub(crate) stack: &'static RootContext,
|
||||
pub(crate) inner: Rc<SignalState<T>>,
|
||||
|
|
85
leptos_reactive/src/transition.rs
Normal file
85
leptos_reactive/src/transition.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use std::{cell::RefCell, collections::HashSet, pin::Pin, rc::Rc};
|
||||
|
||||
use futures::{
|
||||
channel::oneshot::{Canceled, Sender},
|
||||
Future,
|
||||
};
|
||||
|
||||
use crate::{BoundedScope, Effect, EffectDependency, ReadSignal, SuspenseContext};
|
||||
|
||||
impl<'a, 'b> BoundedScope<'a, 'b> {
|
||||
pub fn use_transition<F>(self) -> (ReadSignal<bool>, impl Fn())
|
||||
where
|
||||
F: Fn(),
|
||||
{
|
||||
if let Some(transition) = self.inner.root_context.transition {
|
||||
transition
|
||||
}
|
||||
}
|
||||
|
||||
/* pub fn start_transition(self, f: impl Fn()) {
|
||||
// If a transition is already running, run this function
|
||||
// and then return when the existing transition is done
|
||||
if let Some(transition) = self.inner.root_context.transition.take() {
|
||||
if transition.running {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, if we're inside a Suspense context, create a transition and run it
|
||||
if self.use_context::<SuspenseContext>().is_some() {
|
||||
let t = TransitionState {
|
||||
running: true,
|
||||
sources: Default::default(),
|
||||
effects: Default::default(),
|
||||
queue: Default::default(),
|
||||
pending_resources: Default::default(),
|
||||
};
|
||||
*self.inner.root_context.transition.borrow_mut() = Some(t);
|
||||
f();
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
pub struct TransitionState {
|
||||
inner: Rc<RefCell<TransitionStateInner>>,
|
||||
/* sources: Set<SignalState<any>>;
|
||||
effects: Computation<any>[];
|
||||
promises: Set<Promise<any>>;
|
||||
disposed: Set<Computation<any>>;
|
||||
queue: Set<Computation<any>>;
|
||||
scheduler?: (fn: () => void) => unknown;
|
||||
running: boolean;
|
||||
done?: Promise<void>;
|
||||
resolve?: () => void; */
|
||||
}
|
||||
|
||||
pub(crate) struct TransitionStateInner {
|
||||
pub(crate) running: bool,
|
||||
pub(crate) sources: HashSet<Box<dyn EffectDependency>>,
|
||||
pub(crate) effects: Vec<Effect>,
|
||||
pub(crate) queue: HashSet<Effect>,
|
||||
pub(crate) pending_resources: Option<HashSet<ReadSignal<usize>>>,
|
||||
}
|
||||
|
||||
impl TransitionState {
|
||||
pub fn pending(&self) -> bool {
|
||||
match &self.inner.borrow().pending_resources {
|
||||
Some(suspenses) => {
|
||||
suspenses
|
||||
.iter()
|
||||
.map(|pending| *pending.get())
|
||||
.sum::<usize>()
|
||||
> 0
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn complete(&self) -> bool {
|
||||
match &self.pending_resources {
|
||||
Some(_) => !self.pending(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue