shared/domain/module/body/_groups/
design.rs

1use crate::domain::module::body::{
2    Audio, Background, HoverAnimation, Image, ModuleAssist, StickerHidden, ThemeId, Transform,
3};
4use derive_setters::Setters;
5use serde::{Deserialize, Serialize};
6
7/// Default text for `Text`
8pub const DEFAULT_TEXT_VALUE: &str = "Shalom שָׁלוֹם";
9
10/// The base content for design modules that don't need custom Sticker wrappers
11#[derive(Clone, Serialize, Deserialize, Debug)]
12pub struct BaseContent {
13    /// The instructions for the module.
14    pub instructions: ModuleAssist,
15
16    /// The feedback for the module.
17    #[serde(default)]
18    pub feedback: ModuleAssist,
19
20    /// The module's theme.
21    pub theme: ThemeId,
22
23    /// Backgrounds
24    pub backgrounds: Backgrounds,
25
26    /// Stickers
27    pub stickers: Vec<Sticker>,
28}
29
30impl Default for BaseContent {
31    fn default() -> Self {
32        Self {
33            instructions: Default::default(),
34            feedback: Default::default(),
35            theme: Default::default(),
36            backgrounds: Default::default(),
37            stickers: vec![Sticker::Text(Default::default())],
38        }
39    }
40}
41
42#[derive(Clone, Default, Serialize, Deserialize, Debug)]
43/// Background
44/// although it's simply a list of layers
45/// the number of layers is predefined
46/// and has special meaning from a UI perspective
47pub struct Backgrounds {
48    /// Layer 1
49    pub layer_1: Option<Background>,
50    /// Layer 2
51    pub layer_2: Option<Background>,
52}
53
54#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
55/// Stickers are things that can be rendered and transforme
56pub enum Sticker {
57    /// Sprites
58    #[serde(alias = "sprite")]
59    Sprite(Sprite),
60    /// Text
61    #[serde(alias = "text")]
62    Text(Text),
63    /// Embed
64    #[serde(alias = "embed")]
65    Embed(Embed),
66}
67
68impl Sticker {
69    /// Get the inner transform of a sticker
70    pub fn transform(&self) -> &Transform {
71        match self {
72            Self::Sprite(sprite) => &sprite.transform,
73            Self::Text(text) => &text.transform,
74            Self::Embed(embed) => &embed.transform,
75        }
76    }
77}
78
79#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
80/// Text are serialized text things
81pub struct Text {
82    /// the raw text
83    pub value: String,
84    /// The Transform
85    pub transform: Transform,
86    /// Animation to run on hover
87    #[serde(default)]
88    pub hover_animation: Option<HoverAnimation>,
89    /// Hide on click
90    #[serde(default)]
91    pub hidden: Option<StickerHidden>,
92}
93
94impl Default for Text {
95    fn default() -> Self {
96        Self::from_str(DEFAULT_TEXT_VALUE)
97    }
98}
99
100impl Text {
101    // value is the ready slate json and str is just a string of text
102
103    /// Create a new Text from a str
104    pub fn from_str(text: &str) -> Self {
105        Self::from_value(Self::value_from_str(text))
106    }
107
108    /// Create a new Text from value
109    pub fn from_value(value: String) -> Self {
110        Self {
111            value,
112            transform: Transform::identity(),
113            hover_animation: None,
114            hidden: None,
115        }
116    }
117
118    /// Get a value string from a str
119    pub fn value_from_str(text: &str) -> String {
120        format!(
121            r#"{{"version":"0.1.0","content":[{{"children":[{{"text":"{}","element":"H1"}}]}}]}}"#,
122            text
123        )
124    }
125}
126
127#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
128/// Sprites are a combo of image + transform
129pub struct Sprite {
130    /// The Image
131    pub image: Image,
132    /// The Transform
133    pub transform: Transform,
134    /// Effects
135    pub effects: Vec<SpriteEffect>,
136
137    /// Flip horizontal
138    pub flip_horizontal: bool,
139
140    /// Flip vertical
141    pub flip_vertical: bool,
142
143    /// Animation to run on hover
144    #[serde(default)]
145    pub hover_animation: Option<HoverAnimation>,
146    /// Hide on click
147    #[serde(default)]
148    pub hidden: Option<StickerHidden>,
149}
150
151#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
152#[serde(rename_all = "snake_case")]
153/// Sprite Effects
154pub enum SpriteEffect {
155    /// Remove White
156    RemoveWhite,
157}
158
159#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
160/// Embed
161/// Text are serialized text things
162pub struct Embed {
163    /// The embed host
164    pub host: EmbedHost,
165
166    /// Transforms
167    pub transform: Transform,
168}
169
170/// what to do when done
171#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
172pub enum DoneAction {
173    /// loop
174    Loop,
175
176    /// move on to next activity
177    Next,
178}
179
180#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
181/// Host of embed
182pub enum EmbedHost {
183    /// YouTube
184    #[serde(alias = "youtube")]
185    Youtube(YoutubeEmbed),
186
187    /// Vimeo
188    Vimeo(VimeoEmbed),
189
190    /// Google doc
191    GoogleDoc(GoogleDocsEmbed),
192
193    /// Google form
194    GoogleForm(GoogleFormsEmbed),
195
196    /// Google sheets
197    GoogleSheet(GoogleSheetsEmbed),
198
199    /// Google slide
200    GoogleSlide(GoogleSlidesEmbed),
201
202    /// Edpuzzle
203    Edpuzzle(EdpuzzleEmbed),
204
205    /// Puzzel
206    Puzzel(PuzzelEmbed),
207
208    /// Quizlet
209    Quizlet(QuizletEmbed),
210
211    /// Thinglink
212    Thinglink(ThinglinkEmbed),
213
214    /// Sutori
215    Sutori(SutoriEmbed),
216}
217
218#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Setters)]
219/// YouTube host embed
220pub struct YoutubeEmbed {
221    /// url of the YouTube embed
222    pub url: YoutubeUrl,
223
224    /// start at second
225    pub start_at: Option<u32>,
226
227    /// end at second
228    pub end_at: Option<u32>,
229
230    /// show captions
231    pub captions: bool,
232
233    /// play with sound
234    pub muted: bool,
235
236    /// autoplay
237    pub autoplay: bool,
238
239    /// what to do when done
240    pub done_action: Option<DoneAction>,
241}
242
243impl YoutubeEmbed {
244    /// creates a new YoutubeEmbed
245    pub fn new(url: YoutubeUrl) -> Self {
246        Self {
247            url,
248            start_at: None,
249            end_at: None,
250            captions: false,
251            muted: false,
252            autoplay: false,
253            done_action: None,
254        }
255    }
256}
257
258#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
259/// YouTube host embed url
260pub struct YoutubeUrl(pub String);
261
262#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Setters)]
263/// Vimeo host embed
264pub struct VimeoEmbed {
265    /// url of the Vimeo embed
266    pub url: VimeoUrl,
267
268    /// start at second
269    pub start_at: Option<u32>,
270
271    /// end at second
272    pub end_at: Option<u32>,
273
274    /// show captions
275    pub captions: bool,
276
277    /// play with sound
278    pub muted: bool,
279
280    /// autoplay
281    pub autoplay: bool,
282
283    /// what to do when done
284    pub done_action: Option<DoneAction>,
285}
286
287impl VimeoEmbed {
288    /// creates a new VimeoEmbed
289    pub fn new(url: VimeoUrl) -> Self {
290        Self {
291            url,
292            start_at: None,
293            end_at: None,
294            captions: false,
295            muted: false,
296            autoplay: false,
297            done_action: None,
298        }
299    }
300}
301
302#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
303/// Vimeo host embed url
304pub struct VimeoUrl(pub String);
305
306impl EmbedHost {
307    /// Convert youtube host url to a string
308    pub fn get_url_string(&self) -> String {
309        match &self {
310            EmbedHost::Youtube(youtube_video) => {
311                let YoutubeUrl(url) = &youtube_video.url;
312                url.to_string()
313            }
314            EmbedHost::Vimeo(vimeo_video) => {
315                let VimeoUrl(url) = &vimeo_video.url;
316                url.to_string()
317            }
318            EmbedHost::GoogleDoc(google_doc) => {
319                let GoogleDocId(url) = &google_doc.url;
320                url.to_string()
321            }
322            EmbedHost::GoogleForm(google_form) => {
323                let GoogleFormId(url) = &google_form.url;
324                url.to_string()
325            }
326            EmbedHost::GoogleSheet(google_sheet) => {
327                let GoogleSheetId(url) = &google_sheet.url;
328                url.to_string()
329            }
330            EmbedHost::GoogleSlide(google_slide) => {
331                let GoogleSlideId(url) = &google_slide.url;
332                url.to_string()
333            }
334            EmbedHost::Edpuzzle(_) => todo!(),
335            EmbedHost::Puzzel(_) => todo!(),
336            EmbedHost::Quizlet(quizlet) => {
337                let QuizletId(url) = &quizlet.url;
338                url.to_string()
339            }
340            EmbedHost::Thinglink(thinglink) => {
341                let ThinglinkId(url) = &thinglink.url;
342                url.to_string()
343            }
344            EmbedHost::Sutori(sutori) => {
345                let SutoriId(url) = &sutori.url;
346                url.to_string()
347            }
348        }
349    }
350}
351
352#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
353/// Google docs host embed url
354pub struct GoogleDocsEmbed {
355    /// url of the YouTube video
356    pub url: GoogleDocId,
357}
358
359#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
360/// GoogleDoc host google doc url
361pub struct GoogleDocId(pub String);
362
363#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
364/// Google forms host embed url
365pub struct GoogleFormsEmbed {
366    /// url of the YouTube video
367    pub url: GoogleFormId,
368}
369
370#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
371/// GoogleForm host google form url
372pub struct GoogleFormId(pub String);
373
374#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
375/// Google sheets host embed url
376pub struct GoogleSheetsEmbed {
377    /// url of the YouTube video
378    pub url: GoogleSheetId,
379}
380
381#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
382/// GoogleSheet host google sheet url
383pub struct GoogleSheetId(pub String);
384
385#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
386/// Google slides host embed url
387pub struct GoogleSlidesEmbed {
388    /// url of the YouTube video
389    pub url: GoogleSlideId,
390}
391
392#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
393/// GoogleSlide host google slide url
394pub struct GoogleSlideId(pub String);
395
396#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
397/// Edpuzzle host embed url
398pub struct EdpuzzleEmbed {
399    /// url of the Edpuzzle video
400    pub url: EdpuzzleId,
401}
402
403#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
404/// Edpuzzle host google sheet url
405pub struct EdpuzzleId(pub String);
406
407#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
408/// Puzzel host embed url
409pub struct PuzzelEmbed {
410    /// url of the Puzzel video
411    pub url: PuzzelId,
412}
413
414#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
415/// Puzzel host google sheet url
416pub struct PuzzelId(pub String);
417
418#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
419/// Quizlet host embed url
420pub struct QuizletEmbed {
421    /// url of the Quizlet video
422    pub url: QuizletId,
423}
424
425#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
426/// Quizlet host google sheet url
427pub struct QuizletId(pub String);
428
429#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
430/// Thinglink host embed url
431pub struct ThinglinkEmbed {
432    /// url of the Thinglink video
433    pub url: ThinglinkId,
434}
435
436#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
437/// Thinglink host google sheet url
438pub struct ThinglinkId(pub String);
439
440#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
441/// Sutori host embed url
442pub struct SutoriEmbed {
443    /// url of the Sutori video
444    pub url: SutoriId,
445}
446
447#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
448/// Sutori host google sheet url
449pub struct SutoriId(pub String);
450
451#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
452/// Trace
453pub struct Trace {
454    /// The Transform
455    pub transform: Transform,
456    /// The Shape
457    pub shape: TraceShape,
458    /// The Kind
459    pub kind: TraceKind,
460    /// Optional audio associated with this trace
461    pub audio: Option<Audio>,
462    /// Optional text associated with this trace
463    pub text: Option<String>,
464}
465
466#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
467/// Trace kind
468pub enum TraceKind {
469    /// Wrong (red color)
470    #[serde(alias = "wrong")]
471    Wrong,
472    /// Correct (green color)
473    #[serde(alias = "correct")]
474    Correct,
475    /// Regular (blue color)
476    #[serde(alias = "regular")]
477    Regular,
478}
479
480impl AsRef<Trace> for Trace {
481    fn as_ref(&self) -> &Trace {
482        self
483    }
484}
485
486#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
487/// Trace shape
488pub enum TraceShape {
489    /// width and height
490    #[serde(alias = "rect")]
491    Rect(f64, f64),
492    /// radius
493    #[serde(alias = "ellipse")]
494    Ellipse(f64, f64),
495    /// points
496    #[serde(alias = "path")]
497    Path(Vec<(f64, f64)>),
498    /// explicit path commands
499    /// corresponds to SVG spec: https://svgwg.org/svg2-draft/paths.html#TheDProperty
500    /// the second parameter indicates whether it's absolute (true) or relative (false)
501    #[serde(alias = "pathCommands")]
502    PathCommands(Vec<(PathCommand, bool)>),
503}
504
505#[allow(missing_docs)]
506#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
507pub enum PathCommand {
508    /// https://svgwg.org/svg2-draft/paths.html#PathDataMovetoCommands
509    MoveTo(f64, f64),
510    /// https://svgwg.org/svg2-draft/paths.html#PathDataLinetoCommands
511    ClosePath,
512    /// https://svgwg.org/svg2-draft/paths.html#PathDataLinetoCommands
513    LineTo(f64, f64),
514    /// https://svgwg.org/svg2-draft/paths.html#PathDataLinetoCommands
515    HorizontalLineTo(f64),
516    /// https://svgwg.org/svg2-draft/paths.html#PathDataLinetoCommands
517    VerticalLineTo(f64),
518    /// https://svgwg.org/svg2-draft/paths.html#PathDataCubicBezierCommands
519    CurveTo(f64, f64, f64, f64, f64, f64),
520    /// https://svgwg.org/svg2-draft/paths.html#PathDataCubicBezierCommands
521    SmoothCurveTo(f64, f64, f64, f64),
522    /// https://svgwg.org/svg2-draft/paths.html#PathDataQuadraticBezierCommands
523    QuadCurveTo(f64, f64, f64, f64),
524    /// https://svgwg.org/svg2-draft/paths.html#PathDataQuadraticBezierCommands
525    SmoothQuadCurveTo(f64, f64),
526    /// https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands
527    ArcTo(f64, f64, f64, f64, f64, f64, f64),
528}