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
113#[derive(Default)]
115pub struct Ctx {
116 pub container: Option<anvil_core::Container>,
117 pub dispatched: Vec<BrowserDispatch>,
118 pub emitted: Vec<ComponentEmit>,
119 pub redirect: Option<String>,
120 pub errors: HashMap<String, Vec<String>>,
121 pub island: Option<String>,
122}
123
124impl Ctx {
125 pub fn new(container: Option<anvil_core::Container>) -> Self {
126 Self {
127 container,
128 ..Default::default()
129 }
130 }
131
132 pub fn dispatch_browser(&mut self, event: impl Into<String>, payload: serde_json::Value) {
133 self.dispatched.push(BrowserDispatch {
134 event: event.into(),
135 payload,
136 });
137 }
138
139 pub fn emit(&mut self, event: impl Into<String>, payload: serde_json::Value) {
140 self.emitted.push(ComponentEmit {
141 event: event.into(),
142 payload,
143 });
144 }
145
146 pub fn redirect(&mut self, to: impl Into<String>) {
147 self.redirect = Some(to.into());
148 }
149
150 pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
151 self.errors
152 .entry(field.into())
153 .or_default()
154 .push(message.into());
155 }
156
157 pub fn request_island(&mut self, name: impl Into<String>) {
158 self.island = Some(name.into());
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct BrowserDispatch {
164 pub event: String,
165 pub payload: serde_json::Value,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct ComponentEmit {
170 pub event: String,
171 pub payload: serde_json::Value,
172}