1use std::collections::HashMap;
4
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7
8use crate::error::Result;
9
10#[async_trait]
18pub trait Component: Send + Sync + 'static {
19 fn class_name() -> &'static str
20 where
21 Self: Sized;
22
23 fn view_path() -> &'static str
24 where
25 Self: Sized;
26
27 fn listeners() -> Vec<String>
28 where
29 Self: Sized,
30 {
31 Vec::new()
32 }
33
34 fn snapshot_data(&self) -> serde_json::Value;
35
36 fn load_snapshot(data: &serde_json::Value) -> Result<Self>
37 where
38 Self: Sized;
39
40 fn mount(props: MountProps) -> Self
41 where
42 Self: Sized + Default,
43 {
44 let _ = props;
45 Self::default()
46 }
47
48 async fn apply_writes(&mut self, writes: &[PropertyWrite], ctx: &mut Ctx) -> Result<()>;
49
50 async fn dispatch_call(
51 &mut self,
52 method: &str,
53 args: Vec<serde_json::Value>,
54 ctx: &mut Ctx,
55 ) -> Result<()>;
56
57 fn render(&self) -> Result<String>
58 where
59 Self: Sized,
60 {
61 let data = self.snapshot_data();
62 let view = Self::view_path();
63 crate::template::render(view, &data)
64 }
65}
66
67#[derive(Debug, Clone, Default)]
69pub struct MountProps {
70 pub raw: serde_json::Value,
71}
72
73impl MountProps {
74 pub fn new(v: serde_json::Value) -> Self {
75 Self { raw: v }
76 }
77
78 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
79 self.raw.get(key)
80 }
81
82 pub fn string(&self, key: &str) -> Option<String> {
83 self.get(key).and_then(|v| v.as_str()).map(String::from)
84 }
85
86 pub fn i32(&self, key: &str) -> Option<i32> {
87 self.get(key)
88 .and_then(|v| v.as_i64())
89 .and_then(|v| i32::try_from(v).ok())
90 }
91
92 pub fn i64(&self, key: &str) -> Option<i64> {
93 self.get(key).and_then(|v| v.as_i64())
94 }
95
96 pub fn bool(&self, key: &str) -> Option<bool> {
97 self.get(key).and_then(|v| v.as_bool())
98 }
99
100 pub fn parse<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
101 self.get(key)
102 .and_then(|v| serde_json::from_value(v.clone()).ok())
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct PropertyWrite {
109 pub name: String,
110 pub value: serde_json::Value,
111}
112
113pub struct Ctx {
115 pub container: Option<anvil_core::Container>,
116 pub dispatched: Vec<BrowserDispatch>,
117 pub emitted: Vec<ComponentEmit>,
118 pub redirect: Option<String>,
119 pub errors: HashMap<String, Vec<String>>,
120 pub island: Option<String>,
121}
122
123impl Default for Ctx {
124 fn default() -> Self {
125 Self {
126 container: None,
127 dispatched: Vec::new(),
128 emitted: Vec::new(),
129 redirect: None,
130 errors: HashMap::new(),
131 island: None,
132 }
133 }
134}
135
136impl Ctx {
137 pub fn new(container: Option<anvil_core::Container>) -> Self {
138 Self {
139 container,
140 ..Default::default()
141 }
142 }
143
144 pub fn dispatch_browser(&mut self, event: impl Into<String>, payload: serde_json::Value) {
145 self.dispatched.push(BrowserDispatch {
146 event: event.into(),
147 payload,
148 });
149 }
150
151 pub fn emit(&mut self, event: impl Into<String>, payload: serde_json::Value) {
152 self.emitted.push(ComponentEmit {
153 event: event.into(),
154 payload,
155 });
156 }
157
158 pub fn redirect(&mut self, to: impl Into<String>) {
159 self.redirect = Some(to.into());
160 }
161
162 pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
163 self.errors
164 .entry(field.into())
165 .or_default()
166 .push(message.into());
167 }
168
169 pub fn request_island(&mut self, name: impl Into<String>) {
170 self.island = Some(name.into());
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct BrowserDispatch {
176 pub event: String,
177 pub payload: serde_json::Value,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ComponentEmit {
182 pub event: String,
183 pub payload: serde_json::Value,
184}