use std::collections::HashMap;
use std::pin::Pin;
use futures::Future;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use crate::component::{Ctx, MountProps, PropertyWrite};
use crate::error::{Error, Result};
pub trait DynComponent: Send + Sync {
fn as_any_mut(&mut self) -> &mut (dyn std::any::Any + Send + Sync);
fn snapshot_data(&self) -> serde_json::Value;
fn render(&self) -> Result<String>;
fn apply_writes<'a>(
&'a mut self,
writes: &'a [PropertyWrite],
ctx: &'a mut Ctx,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
fn dispatch_call<'a>(
&'a mut self,
method: &'a str,
args: Vec<serde_json::Value>,
ctx: &'a mut Ctx,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
}
pub struct BoxedComponent {
pub class: &'static str,
pub view: &'static str,
pub state: Box<dyn DynComponent>,
}
pub struct ComponentEntry {
pub class: &'static str,
pub view: &'static str,
pub listeners: fn() -> Vec<String>,
pub mount: fn(MountProps) -> BoxedComponent,
pub load: fn(&serde_json::Value) -> Result<BoxedComponent>,
}
inventory::collect!(ComponentEntry);
pub struct DispatchEntry {
pub class: &'static str,
pub dispatch: for<'a> fn(
component: &'a mut (dyn std::any::Any + Send + Sync),
method: &'a str,
args: Vec<serde_json::Value>,
ctx: &'a mut crate::component::Ctx,
) -> Pin<Box<dyn futures::Future<Output = Result<()>> + Send + 'a>>,
}
inventory::collect!(DispatchEntry);
pub struct ListenerEntry {
pub class: &'static str,
pub events: fn() -> Vec<String>,
}
inventory::collect!(ListenerEntry);
pub struct MountEntry {
pub class: &'static str,
pub mount: fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>,
}
inventory::collect!(MountEntry);
pub fn dispatcher_for(class: &str) -> Option<&'static DispatchEntry> {
inventory::iter::<DispatchEntry>
.into_iter()
.find(|e| e.class == class)
}
pub fn listeners_for(class: &str) -> Vec<String> {
for entry in inventory::iter::<ListenerEntry> {
if entry.class == class {
return (entry.events)();
}
}
Vec::new()
}
pub fn mount_factory_for(
class: &str,
) -> Option<fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>> {
for entry in inventory::iter::<MountEntry> {
if entry.class == class {
return Some(entry.mount);
}
}
None
}
static REGISTRY: Lazy<RwLock<HashMap<&'static str, &'static ComponentEntry>>> = Lazy::new(|| {
let mut map = HashMap::new();
for entry in inventory::iter::<ComponentEntry> {
map.insert(entry.class, entry);
}
RwLock::new(map)
});
pub fn lookup(class: &str) -> Option<&'static ComponentEntry> {
REGISTRY.read().get(class).copied()
}
pub fn lookup_by_short_name(short: &str) -> Option<&'static ComponentEntry> {
let map = REGISTRY.read();
let suffix = format!("::{}", short);
if let Some(entry) = map.values().find(|e| e.class.ends_with(&suffix)) {
return Some(*entry);
}
if let Some(entry) = map.values().find(|e| e.class == short) {
return Some(*entry);
}
let lower = short.to_ascii_lowercase();
map.values()
.find(|e| e.class.to_ascii_lowercase().ends_with(&lower))
.copied()
}
pub fn resolve(name: &str) -> Result<&'static ComponentEntry> {
lookup(name)
.or_else(|| lookup_by_short_name(name))
.ok_or_else(|| Error::UnknownComponent(name.to_string()))
}
pub fn classes() -> Vec<&'static str> {
REGISTRY.read().keys().copied().collect()
}