1use serde::{Deserialize, Serialize};
6
7use crate::common::{
8 AccessibilityAttributes, Action, Checkable, ChildList, ComponentId, DynamicBoolean,
9 DynamicNumber, DynamicString, DynamicStringList,
10};
11
12pub const STANDARD_CATALOG_ID: &str = "https://a2ui.org/specification/v0_10/standard_catalog.json";
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
21#[serde(rename_all = "camelCase")]
22pub struct ComponentCommon {
23 pub id: ComponentId,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub accessibility: Option<AccessibilityAttributes>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub weight: Option<f64>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(tag = "component")]
40pub enum Component {
41 Text(TextComponent),
42 Image(ImageComponent),
43 Icon(IconComponent),
44 Video(VideoComponent),
45 AudioPlayer(AudioPlayerComponent),
46 Row(RowComponent),
47 Column(ColumnComponent),
48 List(ListComponent),
49 Card(CardComponent),
50 Tabs(TabsComponent),
51 Modal(ModalComponent),
52 Divider(DividerComponent),
53 Button(ButtonComponent),
54 TextField(TextFieldComponent),
55 CheckBox(CheckBoxComponent),
56 ChoicePicker(ChoicePickerComponent),
57 Slider(SliderComponent),
58 DateTimeInput(DateTimeInputComponent),
59}
60
61impl Component {
62 pub fn id(&self) -> &str {
64 match self {
65 Component::Text(c) => &c.common.id,
66 Component::Image(c) => &c.common.id,
67 Component::Icon(c) => &c.common.id,
68 Component::Video(c) => &c.common.id,
69 Component::AudioPlayer(c) => &c.common.id,
70 Component::Row(c) => &c.common.id,
71 Component::Column(c) => &c.common.id,
72 Component::List(c) => &c.common.id,
73 Component::Card(c) => &c.common.id,
74 Component::Tabs(c) => &c.common.id,
75 Component::Modal(c) => &c.common.id,
76 Component::Divider(c) => &c.common.id,
77 Component::Button(c) => &c.common.id,
78 Component::TextField(c) => &c.common.id,
79 Component::CheckBox(c) => &c.common.id,
80 Component::ChoicePicker(c) => &c.common.id,
81 Component::Slider(c) => &c.common.id,
82 Component::DateTimeInput(c) => &c.common.id,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93#[serde(rename_all = "camelCase")]
94pub struct TextComponent {
95 #[serde(flatten)]
96 pub common: ComponentCommon,
97 pub text: DynamicString,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub variant: Option<TextVariant>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106#[serde(rename_all = "lowercase")]
107pub enum TextVariant {
108 H1,
109 H2,
110 H3,
111 H4,
112 H5,
113 Caption,
114 Body,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119#[serde(rename_all = "camelCase")]
120pub struct ImageComponent {
121 #[serde(flatten)]
122 pub common: ComponentCommon,
123 pub url: DynamicString,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub fit: Option<ImageFit>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub variant: Option<ImageVariant>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135#[serde(rename_all = "kebab-case")]
136pub enum ImageFit {
137 Contain,
138 Cover,
139 Fill,
140 None,
141 ScaleDown,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146#[serde(rename_all = "camelCase")]
147pub enum ImageVariant {
148 Icon,
149 Avatar,
150 SmallFeature,
151 MediumFeature,
152 LargeFeature,
153 Header,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
158#[serde(rename_all = "camelCase")]
159pub struct IconComponent {
160 #[serde(flatten)]
161 pub common: ComponentCommon,
162 pub name: IconName,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
168#[serde(untagged)]
169pub enum IconName {
170 Preset(PresetIcon),
172 Custom { path: String },
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
178#[serde(rename_all = "camelCase")]
179pub enum PresetIcon {
180 AccountCircle,
181 Add,
182 ArrowBack,
183 ArrowForward,
184 AttachFile,
185 CalendarToday,
186 Call,
187 Camera,
188 Check,
189 Close,
190 Delete,
191 Download,
192 Edit,
193 Event,
194 Error,
195 FastForward,
196 Favorite,
197 FavoriteOff,
198 Folder,
199 Help,
200 Home,
201 Info,
202 LocationOn,
203 Lock,
204 LockOpen,
205 Mail,
206 Menu,
207 MoreVert,
208 MoreHoriz,
209 NotificationsOff,
210 Notifications,
211 Pause,
212 Payment,
213 Person,
214 Phone,
215 Photo,
216 Play,
217 Print,
218 Refresh,
219 Rewind,
220 Search,
221 Send,
222 Settings,
223 Share,
224 ShoppingCart,
225 SkipNext,
226 SkipPrevious,
227 Star,
228 StarHalf,
229 StarOff,
230 Stop,
231 Upload,
232 Visibility,
233 VisibilityOff,
234 VolumeDown,
235 VolumeMute,
236 VolumeOff,
237 VolumeUp,
238 Warning,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
243#[serde(rename_all = "camelCase")]
244pub struct VideoComponent {
245 #[serde(flatten)]
246 pub common: ComponentCommon,
247 pub url: DynamicString,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
253#[serde(rename_all = "camelCase")]
254pub struct AudioPlayerComponent {
255 #[serde(flatten)]
256 pub common: ComponentCommon,
257 pub url: DynamicString,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub description: Option<DynamicString>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
270#[serde(rename_all = "camelCase")]
271pub struct RowComponent {
272 #[serde(flatten)]
273 pub common: ComponentCommon,
274 pub children: ChildList,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub justify: Option<JustifyContent>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub align: Option<AlignItems>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
286#[serde(rename_all = "camelCase")]
287pub struct ColumnComponent {
288 #[serde(flatten)]
289 pub common: ComponentCommon,
290 pub children: ChildList,
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub justify: Option<JustifyContent>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub align: Option<AlignItems>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
302#[serde(rename_all = "camelCase")]
303pub enum JustifyContent {
304 Start,
305 Center,
306 End,
307 SpaceBetween,
308 SpaceAround,
309 SpaceEvenly,
310 Stretch,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
315#[serde(rename_all = "camelCase")]
316pub enum AlignItems {
317 Start,
318 Center,
319 End,
320 Stretch,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
325#[serde(rename_all = "camelCase")]
326pub struct ListComponent {
327 #[serde(flatten)]
328 pub common: ComponentCommon,
329 pub children: ChildList,
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub direction: Option<ListDirection>,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub align: Option<AlignItems>,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
341#[serde(rename_all = "camelCase")]
342pub enum ListDirection {
343 Vertical,
344 Horizontal,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349#[serde(rename_all = "camelCase")]
350pub struct CardComponent {
351 #[serde(flatten)]
352 pub common: ComponentCommon,
353 pub child: ComponentId,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
359#[serde(rename_all = "camelCase")]
360pub struct TabsComponent {
361 #[serde(flatten)]
362 pub common: ComponentCommon,
363 pub tabs: Vec<TabItem>,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
369#[serde(rename_all = "camelCase")]
370pub struct TabItem {
371 pub title: DynamicString,
373 pub child: ComponentId,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
379#[serde(rename_all = "camelCase")]
380pub struct ModalComponent {
381 #[serde(flatten)]
382 pub common: ComponentCommon,
383 pub trigger: ComponentId,
385 pub content: ComponentId,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
391#[serde(rename_all = "camelCase")]
392pub struct DividerComponent {
393 #[serde(flatten)]
394 pub common: ComponentCommon,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub axis: Option<DividerAxis>,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
402#[serde(rename_all = "camelCase")]
403pub enum DividerAxis {
404 Horizontal,
405 Vertical,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
414#[serde(rename_all = "camelCase")]
415pub struct ButtonComponent {
416 #[serde(flatten)]
417 pub common: ComponentCommon,
418 pub child: ComponentId,
420 pub action: Action,
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub variant: Option<ButtonVariant>,
425 #[serde(flatten, skip_serializing_if = "Option::is_none")]
427 pub checkable: Option<Checkable>,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
432#[serde(rename_all = "camelCase")]
433pub enum ButtonVariant {
434 Primary,
435 Borderless,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
440#[serde(rename_all = "camelCase")]
441pub struct TextFieldComponent {
442 #[serde(flatten)]
443 pub common: ComponentCommon,
444 pub label: DynamicString,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub value: Option<DynamicString>,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub variant: Option<TextFieldVariant>,
452 #[serde(flatten, skip_serializing_if = "Option::is_none")]
454 pub checkable: Option<Checkable>,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
459#[serde(rename_all = "camelCase")]
460pub enum TextFieldVariant {
461 ShortText,
462 LongText,
463 Number,
464 Obscured,
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
469#[serde(rename_all = "camelCase")]
470pub struct CheckBoxComponent {
471 #[serde(flatten)]
472 pub common: ComponentCommon,
473 pub label: DynamicString,
475 pub value: DynamicBoolean,
477 #[serde(flatten, skip_serializing_if = "Option::is_none")]
479 pub checkable: Option<Checkable>,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484#[serde(rename_all = "camelCase")]
485pub struct ChoicePickerComponent {
486 #[serde(flatten)]
487 pub common: ComponentCommon,
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub label: Option<DynamicString>,
491 pub options: Vec<ChoiceOption>,
493 pub value: DynamicStringList,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub variant: Option<ChoicePickerVariant>,
498 #[serde(flatten, skip_serializing_if = "Option::is_none")]
500 pub checkable: Option<Checkable>,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
505#[serde(rename_all = "camelCase")]
506pub struct ChoiceOption {
507 pub label: DynamicString,
509 pub value: String,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
515#[serde(rename_all = "camelCase")]
516pub enum ChoicePickerVariant {
517 MultipleSelection,
518 MutuallyExclusive,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
523#[serde(rename_all = "camelCase")]
524pub struct SliderComponent {
525 #[serde(flatten)]
526 pub common: ComponentCommon,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub label: Option<DynamicString>,
530 pub min: f64,
532 pub max: f64,
534 pub value: DynamicNumber,
536 #[serde(flatten, skip_serializing_if = "Option::is_none")]
538 pub checkable: Option<Checkable>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
543#[serde(rename_all = "camelCase")]
544pub struct DateTimeInputComponent {
545 #[serde(flatten)]
546 pub common: ComponentCommon,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub label: Option<DynamicString>,
550 pub value: DynamicString,
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub enable_date: Option<bool>,
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub enable_time: Option<bool>,
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub min: Option<DynamicString>,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub max: Option<DynamicString>,
564 #[serde(flatten, skip_serializing_if = "Option::is_none")]
566 pub checkable: Option<Checkable>,
567}