Skip to main content

scalar_cms/
editor_field.rs

1use chrono::{DateTime, NaiveDate, TimeZone};
2use serde::Serialize;
3use ts_rs::TS;
4
5use crate::{
6    types::{Markdown, MultiLine, Slug, Toggle},
7    EditorType,
8};
9
10#[derive(Serialize, TS)]
11#[ts(export)]
12pub struct EditorField {
13    pub name: &'static str,
14    pub title: &'static str,
15    pub placeholder: Option<&'static str>,
16    pub validator: Option<&'static str>,
17    pub required: bool,
18    pub field_type: EditorType,
19}
20
21/// Convert an input type into a `scalar_cms::EditorField`
22pub trait ToEditorField {
23    fn to_editor_field(
24        default: Option<impl Into<Self>>,
25        name: &'static str,
26        title: &'static str,
27        placeholder: Option<&'static str>,
28        validator: Option<&'static str>,
29        component_key: Option<&'static str>,
30    ) -> EditorField
31    where
32        Self: std::marker::Sized;
33}
34
35impl ToEditorField for () {
36    fn to_editor_field(
37        _default: Option<impl Into<Self>>,
38        name: &'static str,
39        title: &'static str,
40        placeholder: Option<&'static str>,
41        validator: Option<&'static str>,
42        component_key: Option<&'static str>,
43    ) -> EditorField
44    where
45        Self: std::marker::Sized,
46    {
47        EditorField {
48            name,
49            title,
50            placeholder,
51            required: true,
52            validator,
53            field_type: crate::EditorType::Null {
54                component_key: component_key.map(Into::into),
55            },
56        }
57    }
58}
59
60impl ToEditorField for bool {
61    fn to_editor_field(
62        default: Option<impl Into<bool>>,
63        name: &'static str,
64        title: &'static str,
65        placeholder: Option<&'static str>,
66        validator: Option<&'static str>,
67        component_key: Option<&'static str>,
68    ) -> EditorField
69    where
70        Self: std::marker::Sized,
71    {
72        EditorField {
73            name,
74            title,
75            placeholder,
76            required: true,
77            validator,
78            field_type: crate::EditorType::Bool {
79                default: default.map(Into::into),
80                component_key: component_key.map(Into::into),
81            },
82        }
83    }
84}
85
86impl ToEditorField for i32 {
87    fn to_editor_field(
88        default: Option<impl Into<Self>>,
89        name: &'static str,
90        title: &'static str,
91        placeholder: Option<&'static str>,
92        validator: Option<&'static str>,
93        component_key: Option<&'static str>,
94    ) -> EditorField
95    where
96        Self: std::marker::Sized,
97    {
98        EditorField {
99            name,
100            title,
101            placeholder,
102            required: true,
103            validator,
104            field_type: crate::EditorType::Integer {
105                default: default.map(Into::into),
106                component_key: component_key.map(Into::into),
107            },
108        }
109    }
110}
111
112impl ToEditorField for f32 {
113    fn to_editor_field(
114        default: Option<impl Into<Self>>,
115        name: &'static str,
116        title: &'static str,
117        placeholder: Option<&'static str>,
118        validator: Option<&'static str>,
119        component_key: Option<&'static str>,
120    ) -> EditorField
121    where
122        Self: std::marker::Sized,
123    {
124        EditorField {
125            name,
126            title,
127            placeholder,
128            required: true,
129            validator,
130            field_type: crate::EditorType::Float {
131                default: default.map(Into::into),
132                component_key: component_key.map(Into::into),
133            },
134        }
135    }
136}
137
138impl ToEditorField for String {
139    fn to_editor_field(
140        default: Option<impl Into<Self>>,
141        name: &'static str,
142        title: &'static str,
143        placeholder: Option<&'static str>,
144        validator: Option<&'static str>,
145        component_key: Option<&'static str>,
146    ) -> EditorField
147    where
148        Self: std::marker::Sized,
149    {
150        EditorField {
151            name,
152            title,
153            placeholder,
154            required: true,
155            validator,
156            field_type: crate::EditorType::SingleLine {
157                default: default.map(Into::into),
158                component_key: component_key.map(Into::into),
159            },
160        }
161    }
162}
163
164impl ToEditorField for Slug {
165    fn to_editor_field(
166        default: Option<impl Into<Self>>,
167        name: &'static str,
168        title: &'static str,
169        placeholder: Option<&'static str>,
170        validator: Option<&'static str>,
171        component_key: Option<&'static str>,
172    ) -> EditorField
173    where
174        Self: std::marker::Sized,
175    {
176        EditorField {
177            name,
178            title,
179            placeholder,
180            required: true,
181            validator,
182            field_type: crate::EditorType::SingleLine {
183                default: default.map(Into::into).map(|v| v.0),
184                component_key: component_key.map(Into::into),
185            },
186        }
187    }
188}
189
190impl ToEditorField for MultiLine {
191    fn to_editor_field(
192        default: Option<impl Into<Self>>,
193        name: &'static str,
194        title: &'static str,
195        placeholder: Option<&'static str>,
196        validator: Option<&'static str>,
197        component_key: Option<&'static str>,
198    ) -> EditorField
199    where
200        Self: std::marker::Sized,
201    {
202        EditorField {
203            name,
204            title,
205            placeholder,
206            required: true,
207            validator,
208            field_type: crate::EditorType::MultiLine {
209                default: default.map(Into::into).map(|v| v.0),
210                component_key: component_key.map(Into::into),
211            },
212        }
213    }
214}
215
216impl ToEditorField for Markdown {
217    fn to_editor_field(
218        default: Option<impl Into<Self>>,
219        name: &'static str,
220        title: &'static str,
221        placeholder: Option<&'static str>,
222        validator: Option<&'static str>,
223        component_key: Option<&'static str>,
224    ) -> EditorField
225    where
226        Self: std::marker::Sized,
227    {
228        EditorField {
229            name,
230            title,
231            placeholder,
232            required: true,
233            validator,
234            field_type: crate::EditorType::Markdown {
235                default: default.map(Into::into).map(|v| v.0),
236                component_key: component_key.map(Into::into),
237            },
238        }
239    }
240}
241
242impl<Z: TimeZone> ToEditorField for DateTime<Z> {
243    fn to_editor_field(
244        default: Option<impl Into<DateTime<Z>>>,
245        name: &'static str,
246        title: &'static str,
247        placeholder: Option<&'static str>,
248        validator: Option<&'static str>,
249        component_key: Option<&'static str>,
250    ) -> EditorField
251    where
252        Self: std::marker::Sized,
253    {
254        EditorField {
255            name,
256            title,
257            placeholder,
258            validator,
259            required: true,
260            field_type: EditorType::DateTime {
261                default: default.map(Into::into).as_ref().map(DateTime::to_utc),
262                component_key: component_key.map(Into::into),
263            },
264        }
265    }
266}
267
268impl ToEditorField for NaiveDate {
269    fn to_editor_field(
270        default: Option<impl Into<NaiveDate>>,
271        name: &'static str,
272        title: &'static str,
273        placeholder: Option<&'static str>,
274        validator: Option<&'static str>,
275        component_key: Option<&'static str>,
276    ) -> EditorField
277    where
278        Self: std::marker::Sized,
279    {
280        EditorField {
281            name,
282            title,
283            placeholder,
284            validator,
285            required: true,
286            field_type: EditorType::Date {
287                default: default.map(Into::into).as_ref().map(|d| {
288                    d.and_hms_opt(0, 0, 0)
289                        .expect("should always be valid")
290                        .and_utc()
291                }),
292                component_key: component_key.map(Into::into),
293            },
294        }
295    }
296}
297
298impl<T: ToEditorField + Serialize> ToEditorField for Toggle<T> {
299    fn to_editor_field(
300        default: Option<impl Into<Self>>,
301        name: &'static str,
302        title: &'static str,
303        placeholder: Option<&'static str>,
304        validator: Option<&'static str>,
305        component_key: Option<&'static str>,
306    ) -> EditorField
307    where
308        Self: std::marker::Sized,
309    {
310        let dummy_field = T::to_editor_field(
311            None::<T>,
312            name,
313            title,
314            placeholder,
315            validator,
316            component_key,
317        );
318        let field_type = dummy_field.field_type;
319
320        EditorField {
321            name,
322            title,
323            placeholder,
324            validator,
325            required: true,
326            field_type: EditorType::Toggle {
327                component_key: component_key.map(Into::into),
328                default: default
329                    .map(|v| serde_json::to_value(v.into()).expect("this should never fail")),
330                value: Box::new(field_type),
331            },
332        }
333    }
334}
335
336impl<T> ToEditorField for Option<T>
337where
338    T: ToEditorField + Serialize,
339{
340    fn to_editor_field(
341        default: Option<impl Into<Option<T>>>,
342        name: &'static str,
343        title: &'static str,
344        placeholder: Option<&'static str>,
345        validator: Option<&'static str>,
346        component_key: Option<&'static str>,
347    ) -> EditorField
348    where
349        Self: std::marker::Sized,
350    {
351        let test = default.and_then(Into::into);
352
353        let mut field =
354            T::to_editor_field(test, name, title, placeholder, validator, component_key);
355        field.required = false;
356
357        field
358    }
359}
360
361impl<T> ToEditorField for Vec<T>
362where
363    T: ToEditorField + Serialize,
364{
365    fn to_editor_field(
366        default: Option<impl Into<Vec<T>>>,
367        name: &'static str,
368        title: &'static str,
369        placeholder: Option<&'static str>,
370        validator: Option<&'static str>,
371        component_key: Option<&'static str>,
372    ) -> EditorField
373    where
374        Self: std::marker::Sized,
375    {
376        let dummy_field = T::to_editor_field(
377            None::<T>,
378            name,
379            title,
380            placeholder,
381            validator,
382            component_key,
383        );
384        let field_type = dummy_field.field_type;
385
386        EditorField {
387            name,
388            title,
389            placeholder,
390            required: true,
391            validator,
392            field_type: EditorType::Array {
393                default: default
394                    .map(|v| serde_json::to_value(v.into()).expect("this should never fail")),
395                component_key: component_key.map(Into::into),
396                of: Box::new(field_type),
397            },
398        }
399    }
400}
401
402#[cfg(feature = "url")]
403impl ToEditorField for url::Url {
404    fn to_editor_field(
405        default: Option<impl Into<Self>>,
406        name: &'static str,
407        title: &'static str,
408        placeholder: Option<&'static str>,
409        validator: Option<&'static str>,
410        component_key: Option<&'static str>,
411    ) -> EditorField
412    where
413        Self: std::marker::Sized,
414    {
415        EditorField {
416            name,
417            title,
418            placeholder,
419            required: true,
420            validator,
421            field_type: crate::EditorType::SingleLine {
422                default: default.map(Into::into).map(Into::into),
423                component_key: component_key.map(Into::into).or_else(|| Some("url".into())),
424            },
425        }
426    }
427}
428
429#[cfg(feature = "rgb")]
430impl ToEditorField for rgb::RGB8 {
431    fn to_editor_field(
432        default: Option<impl Into<Self>>,
433        name: &'static str,
434        title: &'static str,
435        placeholder: Option<&'static str>,
436        validator: Option<&'static str>,
437        component_key: Option<&'static str>,
438    ) -> EditorField
439    where
440        Self: std::marker::Sized,
441    {
442        let default = default.map(Into::into);
443
444        super::EditorField {
445            name,
446            title,
447            placeholder,
448            validator,
449            required: true,
450            field_type: crate::EditorType::Struct {
451                component_key: component_key.map(Into::into).or(Some("color-input".into())),
452                default: default
453                    .map(serde_json::to_value)
454                    .map(|v| v.expect("rgb values should always serialize correctly")),
455                fields: vec![
456                    i32::to_editor_field(
457                        default.map(|c| i32::from(c.r)),
458                        "r",
459                        "",
460                        None,
461                        None,
462                        None,
463                    ),
464                    i32::to_editor_field(
465                        default.map(|c| i32::from(c.g)),
466                        "g",
467                        "",
468                        None,
469                        None,
470                        None,
471                    ),
472                    i32::to_editor_field(
473                        default.map(|c| i32::from(c.b)),
474                        "b",
475                        "",
476                        None,
477                        None,
478                        None,
479                    ),
480                ],
481            },
482        }
483    }
484}
485
486#[cfg(feature = "rgb")]
487impl ToEditorField for rgb::RGBA8 {
488    fn to_editor_field(
489        default: Option<impl Into<Self>>,
490        name: &'static str,
491        title: &'static str,
492        placeholder: Option<&'static str>,
493        validator: Option<&'static str>,
494        component_key: Option<&'static str>,
495    ) -> EditorField
496    where
497        Self: std::marker::Sized,
498    {
499        let default = default.map(Into::into);
500
501        super::EditorField {
502            name,
503            title,
504            placeholder,
505            validator,
506            required: true,
507            field_type: crate::EditorType::Struct {
508                component_key: component_key.map(Into::into).or(Some("color-input".into())),
509                default: default
510                    .map(serde_json::to_value)
511                    .map(|v| v.expect("rgb values should always serialize correctly")),
512                fields: vec![
513                    i32::to_editor_field(
514                        default.map(|c| i32::from(c.r)),
515                        "r",
516                        "",
517                        None,
518                        None,
519                        None,
520                    ),
521                    i32::to_editor_field(
522                        default.map(|c| i32::from(c.g)),
523                        "g",
524                        "",
525                        None,
526                        None,
527                        None,
528                    ),
529                    i32::to_editor_field(
530                        default.map(|c| i32::from(c.b)),
531                        "b",
532                        "",
533                        None,
534                        None,
535                        None,
536                    ),
537                    i32::to_editor_field(
538                        default.map(|c| i32::from(c.a)),
539                        "a",
540                        "",
541                        None,
542                        None,
543                        None,
544                    ),
545                ],
546            },
547        }
548    }
549}