use super::{
lifecycle::{
ComponentState, CreateRunner, DestroyRunner, RenderRunner, RenderedRunner, UpdateEvent,
UpdateRunner,
},
Component,
};
use crate::callback::Callback;
use crate::context::{ContextHandle, ContextProvider};
use crate::html::NodeRef;
use crate::scheduler::{self, Shared};
use crate::virtual_dom::{insert_node, VNode};
use gloo_utils::document;
use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell};
use std::future::Future;
use std::ops::Deref;
use std::rc::Rc;
use std::{fmt, iter};
use wasm_bindgen_futures::spawn_local;
use web_sys::{Element, Node};
#[derive(Debug, Clone)]
pub struct AnyScope {
type_id: TypeId,
parent: Option<Rc<AnyScope>>,
state: Rc<dyn Any>,
#[cfg(debug_assertions)]
pub(crate) vcomp_id: u64,
}
impl<COMP: Component> From<Scope<COMP>> for AnyScope {
fn from(scope: Scope<COMP>) -> Self {
AnyScope {
type_id: TypeId::of::<COMP>(),
parent: scope.parent,
state: scope.state,
#[cfg(debug_assertions)]
vcomp_id: scope.vcomp_id,
}
}
}
impl AnyScope {
#[cfg(test)]
pub(crate) fn test() -> Self {
Self {
type_id: TypeId::of::<()>(),
parent: None,
state: Rc::new(()),
#[cfg(debug_assertions)]
vcomp_id: 0,
}
}
pub fn get_parent(&self) -> Option<&AnyScope> {
self.parent.as_deref()
}
pub fn get_type_id(&self) -> &TypeId {
&self.type_id
}
pub fn downcast<COMP: Component>(self) -> Scope<COMP> {
let state = self
.state
.downcast::<RefCell<Option<ComponentState<COMP>>>>()
.expect("unexpected component type");
#[cfg(debug_assertions)]
let vcomp_id = state
.borrow()
.as_ref()
.map(|s| s.vcomp_id)
.unwrap_or_default();
Scope {
parent: self.parent,
state,
#[cfg(debug_assertions)]
vcomp_id,
}
}
fn find_parent_scope<C: Component>(&self) -> Option<Scope<C>> {
let expected_type_id = TypeId::of::<C>();
iter::successors(Some(self), |scope| scope.get_parent())
.filter(|scope| scope.get_type_id() == &expected_type_id)
.cloned()
.map(AnyScope::downcast::<C>)
.next()
}
pub fn context<T: Clone + PartialEq + 'static>(
&self,
callback: Callback<T>,
) -> Option<(T, ContextHandle<T>)> {
let scope = self.find_parent_scope::<ContextProvider<T>>()?;
let scope_clone = scope.clone();
let component = scope.get_component()?;
Some(component.subscribe_consumer(callback, scope_clone))
}
}
pub(crate) trait Scoped {
fn to_any(&self) -> AnyScope;
fn root_vnode(&self) -> Option<Ref<'_, VNode>>;
fn destroy(&mut self);
}
impl<COMP: Component> Scoped for Scope<COMP> {
fn to_any(&self) -> AnyScope {
self.clone().into()
}
fn root_vnode(&self) -> Option<Ref<'_, VNode>> {
let state_ref = self.state.borrow();
state_ref.as_ref()?;
Some(Ref::map(state_ref, |state_ref| {
&state_ref.as_ref().unwrap().root_node
}))
}
fn destroy(&mut self) {
scheduler::push_component_destroy(DestroyRunner {
state: self.state.clone(),
});
scheduler::start();
}
}
pub struct Scope<COMP: Component> {
parent: Option<Rc<AnyScope>>,
pub(crate) state: Shared<Option<ComponentState<COMP>>>,
#[cfg(debug_assertions)]
pub(crate) vcomp_id: u64,
}
impl<COMP: Component> fmt::Debug for Scope<COMP> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Scope<_>")
}
}
impl<COMP: Component> Clone for Scope<COMP> {
fn clone(&self) -> Self {
Scope {
parent: self.parent.clone(),
state: self.state.clone(),
#[cfg(debug_assertions)]
vcomp_id: self.vcomp_id,
}
}
}
impl<COMP: Component> Scope<COMP> {
pub fn get_parent(&self) -> Option<&AnyScope> {
self.parent.as_deref()
}
pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
self.state.try_borrow().ok().and_then(|state_ref| {
state_ref.as_ref()?;
Some(Ref::map(state_ref, |state| {
state.as_ref().unwrap().component.as_ref()
}))
})
}
pub(crate) fn new(parent: Option<AnyScope>) -> Self {
let parent = parent.map(Rc::new);
let state = Rc::new(RefCell::new(None));
#[cfg(debug_assertions)]
let vcomp_id = parent.as_ref().map(|p| p.vcomp_id).unwrap_or_default();
Scope {
state,
parent,
#[cfg(debug_assertions)]
vcomp_id,
}
}
pub(crate) fn mount_in_place(
&self,
parent: Element,
next_sibling: NodeRef,
node_ref: NodeRef,
props: Rc<COMP::Properties>,
) {
#[cfg(debug_assertions)]
crate::virtual_dom::vcomp::log_event(self.vcomp_id, "create placeholder");
let placeholder = {
let placeholder: Node = document().create_text_node("").into();
insert_node(&placeholder, &parent, next_sibling.get().as_ref());
node_ref.set(Some(placeholder.clone()));
VNode::VRef(placeholder)
};
scheduler::push_component_create(
CreateRunner {
parent,
next_sibling,
placeholder,
node_ref,
props,
scope: self.clone(),
},
RenderRunner {
state: self.state.clone(),
},
RenderedRunner {
state: self.state.clone(),
},
);
scheduler::start();
}
pub(crate) fn reuse(
&self,
props: Rc<COMP::Properties>,
node_ref: NodeRef,
next_sibling: NodeRef,
) {
#[cfg(debug_assertions)]
crate::virtual_dom::vcomp::log_event(self.vcomp_id, "reuse");
self.push_update(UpdateEvent::Properties(props, node_ref, next_sibling));
}
fn push_update(&self, event: UpdateEvent<COMP>) {
scheduler::push_component_update(UpdateRunner {
state: self.state.clone(),
event,
});
scheduler::start();
}
pub fn send_message<T>(&self, msg: T)
where
T: Into<COMP::Message>,
{
self.push_update(UpdateEvent::Message(msg.into()));
}
pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
if messages.is_empty() {
return;
}
self.push_update(UpdateEvent::MessageBatch(messages));
}
pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
where
M: Into<COMP::Message>,
F: Fn(IN) -> M + 'static,
{
self.callback_with_passive(None, function)
}
pub fn callback_with_passive<F, IN, M>(
&self,
passive: impl Into<Option<bool>>,
function: F,
) -> Callback<IN>
where
M: Into<COMP::Message>,
F: Fn(IN) -> M + 'static,
{
let scope = self.clone();
let closure = move |input| {
let output = function(input);
scope.send_message(output);
};
Callback::Callback {
passive: passive.into(),
cb: Rc::new(closure),
}
}
pub fn callback_once<F, IN, M>(&self, function: F) -> Callback<IN>
where
M: Into<COMP::Message>,
F: FnOnce(IN) -> M + 'static,
{
let scope = self.clone();
let closure = move |input| {
let output = function(input);
scope.send_message(output);
};
Callback::once(closure)
}
pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
where
F: Fn(IN) -> OUT + 'static,
OUT: SendAsMessage<COMP>,
{
let scope = self.clone();
let closure = move |input| {
let messages = function(input);
messages.send(&scope);
};
closure.into()
}
pub fn batch_callback_once<F, IN, OUT>(&self, function: F) -> Callback<IN>
where
F: FnOnce(IN) -> OUT + 'static,
OUT: SendAsMessage<COMP>,
{
let scope = self.clone();
let closure = move |input| {
let messages = function(input);
messages.send(&scope);
};
Callback::once(closure)
}
pub fn callback_future<FN, FU, IN, M>(&self, function: FN) -> Callback<IN>
where
M: Into<COMP::Message>,
FU: Future<Output = M> + 'static,
FN: Fn(IN) -> FU + 'static,
{
let link = self.clone();
let closure = move |input: IN| {
let future: FU = function(input);
link.send_future(future);
};
closure.into()
}
pub fn callback_future_once<FN, FU, IN, M>(&self, function: FN) -> Callback<IN>
where
M: Into<COMP::Message>,
FU: Future<Output = M> + 'static,
FN: FnOnce(IN) -> FU + 'static,
{
let link = self.clone();
let closure = move |input: IN| {
let future: FU = function(input);
link.send_future(future);
};
Callback::once(closure)
}
pub fn send_future<F, M>(&self, future: F)
where
M: Into<COMP::Message>,
F: Future<Output = M> + 'static,
{
let link = self.clone();
let js_future = async move {
let message: COMP::Message = future.await.into();
link.send_message(message);
};
spawn_local(js_future);
}
pub fn send_future_batch<F>(&self, future: F)
where
F: Future<Output = Vec<COMP::Message>> + 'static,
{
let link = self.clone();
let js_future = async move {
let messages: Vec<COMP::Message> = future.await;
link.send_message_batch(messages);
};
spawn_local(js_future);
}
pub fn context<T: Clone + PartialEq + 'static>(
&self,
callback: Callback<T>,
) -> Option<(T, ContextHandle<T>)> {
self.to_any().context(callback)
}
}
pub trait SendAsMessage<COMP: Component> {
fn send(self, scope: &Scope<COMP>);
}
impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
where
COMP: Component,
{
fn send(self, scope: &Scope<COMP>) {
if let Some(msg) = self {
scope.send_message(msg);
}
}
}
impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
where
COMP: Component,
{
fn send(self, scope: &Scope<COMP>) {
scope.send_message_batch(self);
}
}