1use std::collections::HashMap;
9use std::pin::Pin;
10
11use futures::Future;
12use once_cell::sync::Lazy;
13use parking_lot::RwLock;
14
15use crate::component::{Ctx, MountProps, PropertyWrite};
16use crate::error::{Error, Result};
17
18pub trait DynComponent: Send + Sync {
22 fn as_any_mut(&mut self) -> &mut (dyn std::any::Any + Send + Sync);
23 fn snapshot_data(&self) -> serde_json::Value;
24 fn render(&self) -> Result<String>;
25 fn apply_writes<'a>(
26 &'a mut self,
27 writes: &'a [PropertyWrite],
28 ctx: &'a mut Ctx,
29 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
30 fn dispatch_call<'a>(
31 &'a mut self,
32 method: &'a str,
33 args: Vec<serde_json::Value>,
34 ctx: &'a mut Ctx,
35 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
36}
37
38pub struct BoxedComponent {
41 pub class: &'static str,
42 pub view: &'static str,
43 pub state: Box<dyn DynComponent>,
44}
45
46pub struct ComponentEntry {
49 pub class: &'static str,
50 pub view: &'static str,
51 pub listeners: fn() -> Vec<String>,
52 pub mount: fn(MountProps) -> BoxedComponent,
53 pub load: fn(&serde_json::Value) -> Result<BoxedComponent>,
54}
55
56inventory::collect!(ComponentEntry);
57
58pub struct DispatchEntry {
62 pub class: &'static str,
63 pub dispatch: for<'a> fn(
64 component: &'a mut (dyn std::any::Any + Send + Sync),
65 method: &'a str,
66 args: Vec<serde_json::Value>,
67 ctx: &'a mut crate::component::Ctx,
68 ) -> Pin<Box<dyn futures::Future<Output = Result<()>> + Send + 'a>>,
69}
70
71inventory::collect!(DispatchEntry);
72
73pub struct ListenerEntry {
76 pub class: &'static str,
77 pub events: fn() -> Vec<String>,
78}
79
80inventory::collect!(ListenerEntry);
81
82pub struct MountEntry {
90 pub class: &'static str,
91 pub mount: fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>,
92}
93
94inventory::collect!(MountEntry);
95
96pub fn dispatcher_for(class: &str) -> Option<&'static DispatchEntry> {
98 inventory::iter::<DispatchEntry>
99 .into_iter()
100 .find(|e| e.class == class)
101}
102
103pub fn listeners_for(class: &str) -> Vec<String> {
105 for entry in inventory::iter::<ListenerEntry> {
106 if entry.class == class {
107 return (entry.events)();
108 }
109 }
110 Vec::new()
111}
112
113pub fn mount_factory_for(
115 class: &str,
116) -> Option<fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>> {
117 for entry in inventory::iter::<MountEntry> {
118 if entry.class == class {
119 return Some(entry.mount);
120 }
121 }
122 None
123}
124
125static REGISTRY: Lazy<RwLock<HashMap<&'static str, &'static ComponentEntry>>> = Lazy::new(|| {
126 let mut map = HashMap::new();
127 for entry in inventory::iter::<ComponentEntry> {
128 map.insert(entry.class, entry);
129 }
130 RwLock::new(map)
131});
132
133pub fn lookup(class: &str) -> Option<&'static ComponentEntry> {
135 REGISTRY.read().get(class).copied()
136}
137
138pub fn lookup_by_short_name(short: &str) -> Option<&'static ComponentEntry> {
141 let map = REGISTRY.read();
142
143 let suffix = format!("::{}", short);
144 if let Some(entry) = map.values().find(|e| e.class.ends_with(&suffix)) {
145 return Some(*entry);
146 }
147
148 if let Some(entry) = map.values().find(|e| e.class == short) {
149 return Some(*entry);
150 }
151
152 let lower = short.to_ascii_lowercase();
153 map.values()
154 .find(|e| e.class.to_ascii_lowercase().ends_with(&lower))
155 .copied()
156}
157
158pub fn resolve(name: &str) -> Result<&'static ComponentEntry> {
160 lookup(name)
161 .or_else(|| lookup_by_short_name(name))
162 .ok_or_else(|| Error::UnknownComponent(name.to_string()))
163}
164
165pub fn classes() -> Vec<&'static str> {
167 REGISTRY.read().keys().copied().collect()
168}