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 type DispatchFn = for<'a> fn(
62 component: &'a mut (dyn std::any::Any + Send + Sync),
63 method: &'a str,
64 args: Vec<serde_json::Value>,
65 ctx: &'a mut crate::component::Ctx,
66) -> Pin<Box<dyn futures::Future<Output = Result<()>> + Send + 'a>>;
67
68pub struct DispatchEntry {
72 pub class: &'static str,
73 pub dispatch: DispatchFn,
74}
75
76inventory::collect!(DispatchEntry);
77
78pub struct ListenerEntry {
81 pub class: &'static str,
82 pub events: fn() -> Vec<String>,
83}
84
85inventory::collect!(ListenerEntry);
86
87pub struct MountEntry {
95 pub class: &'static str,
96 pub mount: fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>,
97}
98
99inventory::collect!(MountEntry);
100
101pub fn dispatcher_for(class: &str) -> Option<&'static DispatchEntry> {
103 inventory::iter::<DispatchEntry>
104 .into_iter()
105 .find(|e| e.class == class)
106}
107
108pub fn listeners_for(class: &str) -> Vec<String> {
110 for entry in inventory::iter::<ListenerEntry> {
111 if entry.class == class {
112 return (entry.events)();
113 }
114 }
115 Vec::new()
116}
117
118pub fn mount_factory_for(
120 class: &str,
121) -> Option<fn(crate::component::MountProps) -> Box<dyn std::any::Any + Send>> {
122 for entry in inventory::iter::<MountEntry> {
123 if entry.class == class {
124 return Some(entry.mount);
125 }
126 }
127 None
128}
129
130static REGISTRY: Lazy<RwLock<HashMap<&'static str, &'static ComponentEntry>>> = Lazy::new(|| {
131 let mut map = HashMap::new();
132 for entry in inventory::iter::<ComponentEntry> {
133 map.insert(entry.class, entry);
134 }
135 RwLock::new(map)
136});
137
138pub fn lookup(class: &str) -> Option<&'static ComponentEntry> {
140 REGISTRY.read().get(class).copied()
141}
142
143pub fn lookup_by_short_name(short: &str) -> Option<&'static ComponentEntry> {
146 let map = REGISTRY.read();
147
148 let suffix = format!("::{}", short);
149 if let Some(entry) = map.values().find(|e| e.class.ends_with(&suffix)) {
150 return Some(*entry);
151 }
152
153 if let Some(entry) = map.values().find(|e| e.class == short) {
154 return Some(*entry);
155 }
156
157 let lower = short.to_ascii_lowercase();
158 map.values()
159 .find(|e| e.class.to_ascii_lowercase().ends_with(&lower))
160 .copied()
161}
162
163pub fn resolve(name: &str) -> Result<&'static ComponentEntry> {
165 lookup(name)
166 .or_else(|| lookup_by_short_name(name))
167 .ok_or_else(|| Error::UnknownComponent(name.to_string()))
168}
169
170pub fn classes() -> Vec<&'static str> {
172 REGISTRY.read().keys().copied().collect()
173}