#![allow(missing_docs)]
use super::ModuleKind;
use crate::{
domain::{audio::AudioId, image::ImageId},
media::MediaLibrary,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
collections::HashSet,
fmt::{self, Debug},
hash::Hash,
};
use strum_macros::{EnumIs, EnumIter, IntoStaticStr};
pub mod memory;
pub mod poster;
pub mod video;
pub mod embed;
pub mod tapping_board;
pub mod drag_drop;
pub mod cover;
pub mod resource_cover;
pub mod flashcards;
pub mod card_quiz;
pub mod matching;
pub mod find_answer;
pub mod legacy;
pub mod _groups;
#[derive(Clone, Serialize, Deserialize, Debug, strum_macros::EnumTryAs)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum Body {
MemoryGame(memory::ModuleData),
Matching(matching::ModuleData),
Flashcards(flashcards::ModuleData),
CardQuiz(card_quiz::ModuleData),
Poster(poster::ModuleData),
Video(video::ModuleData),
Embed(embed::ModuleData),
TappingBoard(tapping_board::ModuleData),
DragDrop(drag_drop::ModuleData),
Cover(cover::ModuleData),
ResourceCover(resource_cover::ModuleData),
FindAnswer(find_answer::ModuleData),
Legacy(legacy::ModuleData),
}
impl Body {
pub fn new(kind: super::ModuleKind) -> Self {
match kind {
super::ModuleKind::Cover => Self::Cover(Default::default()),
super::ModuleKind::ResourceCover => Self::ResourceCover(Default::default()),
super::ModuleKind::Memory => Self::MemoryGame(Default::default()),
super::ModuleKind::CardQuiz => Self::CardQuiz(Default::default()),
super::ModuleKind::Flashcards => Self::Flashcards(Default::default()),
super::ModuleKind::Matching => Self::Matching(Default::default()),
super::ModuleKind::Poster => Self::Poster(Default::default()),
super::ModuleKind::Video => Self::Video(Default::default()),
super::ModuleKind::Embed => Self::Embed(Default::default()),
super::ModuleKind::TappingBoard => Self::TappingBoard(Default::default()),
super::ModuleKind::DragDrop => Self::DragDrop(Default::default()),
super::ModuleKind::FindAnswer => Self::FindAnswer(Default::default()),
super::ModuleKind::Legacy => Self::Legacy(Default::default()),
super::ModuleKind::Tracing => unimplemented!("TODO!"),
}
}
pub fn convert_to_body(&self, kind: ModuleKind) -> Result<Self, &'static str> {
match self {
Self::MemoryGame(data) => data.convert_to_body(kind),
Self::Matching(data) => data.convert_to_body(kind),
Self::Flashcards(data) => data.convert_to_body(kind),
Self::CardQuiz(data) => data.convert_to_body(kind),
Self::Poster(data) => data.convert_to_body(kind),
Self::Video(data) => data.convert_to_body(kind),
Self::Embed(data) => data.convert_to_body(kind),
Self::TappingBoard(data) => data.convert_to_body(kind),
Self::DragDrop(data) => data.convert_to_body(kind),
Self::Cover(data) => data.convert_to_body(kind),
Self::ResourceCover(data) => data.convert_to_body(kind),
Self::FindAnswer(data) => data.convert_to_body(kind),
Self::Legacy(data) => data.convert_to_body(kind),
}
}
pub fn is_complete(&self) -> bool {
match self {
Self::MemoryGame(data) => data.is_complete(),
Self::Matching(data) => data.is_complete(),
Self::Flashcards(data) => data.is_complete(),
Self::CardQuiz(data) => data.is_complete(),
Self::Poster(data) => data.is_complete(),
Self::Video(data) => data.is_complete(),
Self::Embed(data) => data.is_complete(),
Self::TappingBoard(data) => data.is_complete(),
Self::DragDrop(data) => data.is_complete(),
Self::Cover(data) => data.is_complete(),
Self::ResourceCover(data) => data.is_complete(),
Self::FindAnswer(data) => data.is_complete(),
Self::Legacy(data) => data.is_complete(),
}
}
}
pub trait BodyExt<Mode: ModeExt, Step: StepExt>:
BodyConvert + TryFrom<Body> + Serialize + DeserializeOwned + Clone + Debug
{
fn choose_mode_list() -> Vec<Mode> {
Mode::get_list()
}
fn as_body(&self) -> Body;
fn is_complete(&self) -> bool;
fn is_legacy() -> bool {
false
}
fn has_preload() -> bool {
false
}
fn kind() -> super::ModuleKind;
fn new_with_mode_and_theme(mode: Mode, theme_id: ThemeId) -> Self;
fn mode(&self) -> Option<Mode>;
fn requires_choose_mode(&self) -> bool;
fn get_theme(&self) -> Option<ThemeId>;
fn set_theme(&mut self, theme_id: ThemeId);
fn set_editor_state_step(&mut self, step: Step);
fn set_editor_state_steps_completed(&mut self, steps_completed: HashSet<Step>);
fn get_editor_state_step(&self) -> Option<Step>;
fn get_editor_state_steps_completed(&self) -> Option<HashSet<Step>>;
fn insert_editor_state_step_completed(&mut self, step: Step) {
if let Some(mut steps_completed) = self.get_editor_state_steps_completed() {
steps_completed.insert(step);
self.set_editor_state_steps_completed(steps_completed);
}
}
fn convert_to_body(&self, kind: ModuleKind) -> Result<Body, &'static str> {
match kind {
ModuleKind::Memory => Ok(Body::MemoryGame(self.convert_to_memory()?)),
ModuleKind::Matching => Ok(Body::Matching(self.convert_to_matching()?)),
ModuleKind::Flashcards => Ok(Body::Flashcards(self.convert_to_flashcards()?)),
ModuleKind::CardQuiz => Ok(Body::CardQuiz(self.convert_to_card_quiz()?)),
ModuleKind::Poster => Ok(Body::Poster(self.convert_to_poster()?)),
ModuleKind::Video => Ok(Body::Video(self.convert_to_video()?)),
ModuleKind::Embed => Ok(Body::Embed(self.convert_to_embed()?)),
ModuleKind::TappingBoard => Ok(Body::TappingBoard(self.convert_to_tapping_board()?)),
ModuleKind::DragDrop => Ok(Body::DragDrop(self.convert_to_drag_drop()?)),
ModuleKind::Cover => Ok(Body::Cover(self.convert_to_cover()?)),
ModuleKind::ResourceCover => Ok(Body::ResourceCover(self.convert_to_resource_cover()?)),
ModuleKind::FindAnswer => Ok(Body::FindAnswer(self.convert_to_find_answer()?)),
ModuleKind::Legacy => Ok(Body::Legacy(self.convert_to_legacy()?)),
_ => unimplemented!(
"cannot convert from {} to {}",
Self::kind().as_str(),
kind.as_str()
),
}
}
}
pub trait BodyConvert {
fn convertable_list() -> Vec<ModuleKind> {
Vec::new()
}
fn convert_to_memory(&self) -> Result<memory::ModuleData, &'static str> {
Err("cannot convert to memory game!")
}
fn convert_to_matching(&self) -> Result<matching::ModuleData, &'static str> {
Err("cannot convert to matching!")
}
fn convert_to_flashcards(&self) -> Result<flashcards::ModuleData, &'static str> {
Err("cannot convert to matching!")
}
fn convert_to_card_quiz(&self) -> Result<card_quiz::ModuleData, &'static str> {
Err("cannot convert to quiz game!")
}
fn convert_to_poster(&self) -> Result<poster::ModuleData, &'static str> {
Err("cannot convert to talking poster!")
}
fn convert_to_tapping_board(&self) -> Result<tapping_board::ModuleData, &'static str> {
Err("cannot convert to Listen & Learn!")
}
fn convert_to_drag_drop(&self) -> Result<drag_drop::ModuleData, &'static str> {
Err("cannot convert to drag & drop!")
}
fn convert_to_cover(&self) -> Result<cover::ModuleData, &'static str> {
Err("cannot convert to cover!")
}
fn convert_to_resource_cover(&self) -> Result<resource_cover::ModuleData, &'static str> {
Err("cannot convert to resource cover!")
}
fn convert_to_find_answer(&self) -> Result<find_answer::ModuleData, &'static str> {
Err("cannot convert to answer this!")
}
fn convert_to_video(&self) -> Result<video::ModuleData, &'static str> {
Err("cannot convert to video!")
}
fn convert_to_embed(&self) -> Result<embed::ModuleData, &'static str> {
Err("cannot convert to embed!")
}
fn convert_to_legacy(&self) -> Result<legacy::ModuleData, &'static str> {
Err("cannot convert to legacy!")
}
}
pub trait ModeExt: Copy + Default + PartialEq + Eq + Hash {
fn get_list() -> Vec<Self>;
fn as_str_id(&self) -> &'static str;
fn label(&self) -> &'static str;
fn image_tag_filters(&self) -> Option<Vec<i16>> {
None
}
fn image_tag_priorities(&self) -> Option<Vec<i16>> {
None
}
}
impl ModeExt for () {
fn get_list() -> Vec<Self> {
vec![]
}
fn as_str_id(&self) -> &'static str {
""
}
fn label(&self) -> &'static str {
""
}
}
impl Body {
pub fn kind(&self) -> super::ModuleKind {
match self {
Self::Cover(_) => super::ModuleKind::Cover,
Self::ResourceCover(_) => super::ModuleKind::ResourceCover,
Self::MemoryGame(_) => super::ModuleKind::Memory,
Self::Flashcards(_) => super::ModuleKind::Flashcards,
Self::CardQuiz(_) => super::ModuleKind::CardQuiz,
Self::Matching(_) => super::ModuleKind::Matching,
Self::Poster(_) => super::ModuleKind::Poster,
Self::Video(_) => super::ModuleKind::Video,
Self::Embed(_) => super::ModuleKind::Embed,
Self::TappingBoard(_) => super::ModuleKind::TappingBoard,
Self::DragDrop(_) => super::ModuleKind::DragDrop,
Self::FindAnswer(_) => super::ModuleKind::FindAnswer,
Self::Legacy(_) => super::ModuleKind::Legacy,
}
}
}
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct EditorState<STEP>
where
STEP: StepExt,
{
pub step: STEP,
pub steps_completed: HashSet<STEP>,
}
pub trait StepExt: Clone + Copy + Default + PartialEq + Eq + Hash + Debug + Unpin {
fn next(&self) -> Option<Self>;
fn as_number(&self) -> usize;
fn label(&self) -> &'static str;
fn get_list() -> Vec<Self>;
fn get_preview() -> Self;
fn is_preview(&self) -> bool {
*self == Self::get_preview()
}
}
impl StepExt for () {
fn next(&self) -> Option<Self> {
None
}
fn as_number(&self) -> usize {
0
}
fn label(&self) -> &'static str {
""
}
fn get_list() -> Vec<Self> {
Vec::new()
}
fn get_preview() -> Self {
()
}
}
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct Audio {
pub id: AudioId,
pub lib: MediaLibrary,
}
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct ModuleAssist {
pub text: Option<String>,
pub audio: Option<Audio>,
#[serde(default)]
#[cfg_attr(feature = "backend", serde(skip))]
pub always_show: bool,
}
impl ModuleAssist {
pub fn new(text: Option<String>, audio: Option<Audio>) -> Self {
Self {
text,
audio,
..Default::default()
}
}
pub fn always_show(mut self) -> Self {
self.always_show = true;
self
}
pub fn has_content(&self) -> bool {
self.text.is_some() || self.audio.is_some()
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum ModuleAssistType {
Instructions,
Feedback,
InActivity,
}
impl ModuleAssistType {
pub fn is_instructions(&self) -> bool {
matches!(self, Self::Instructions)
}
pub fn is_feedback(&self) -> bool {
matches!(self, Self::Feedback)
}
pub fn is_in_activity(&self) -> bool {
matches!(self, Self::InActivity)
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum Background {
Color(Option<rgb::RGBA8>),
Image(Image),
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Image {
pub id: ImageId,
pub lib: MediaLibrary,
}
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct Vec2(pub [f64; 2]);
impl From<(f64, f64)> for Vec2 {
fn from((x, y): (f64, f64)) -> Self {
Self([x, y])
}
}
impl From<Vec2> for (f64, f64) {
fn from(v: Vec2) -> Self {
(v.0[0], v.0[1])
}
}
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct Vec3(pub [f64; 3]);
impl From<(f64, f64, f64)> for Vec3 {
fn from((x, y, z): (f64, f64, f64)) -> Self {
Self([x, y, z])
}
}
impl From<Vec3> for (f64, f64, f64) {
fn from(v: Vec3) -> Self {
(v.0[0], v.0[1], v.0[2])
}
}
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct Vec4(pub [f64; 4]);
impl From<(f64, f64, f64, f64)> for Vec4 {
fn from((x, y, z, w): (f64, f64, f64, f64)) -> Self {
Self([x, y, z, w])
}
}
impl From<Vec4> for (f64, f64, f64, f64) {
fn from(v: Vec4) -> Self {
(v.0[0], v.0[1], v.0[2], v.0[3])
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Vec4,
pub scale: Vec3,
pub origin: Vec3,
}
impl Transform {
pub fn identity() -> Self {
Self {
translation: Vec3([0.0, 0.0, 0.0]),
rotation: Vec4([0.0, 0.0, 0.0, 1.0]),
scale: Vec3([1.0, 1.0, 1.0]),
origin: Vec3([0.0, 0.0, 0.0]),
}
}
}
impl Default for Transform {
fn default() -> Self {
Self::identity()
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, EnumIs)]
pub enum HoverAnimation {
Grow,
Tilt,
Buzz,
}
impl fmt::Display for HoverAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str = match self {
HoverAnimation::Grow => "Grow",
HoverAnimation::Tilt => "Tilt",
HoverAnimation::Buzz => "Buzz",
};
write!(f, "{str}")
}
}
impl HoverAnimation {
pub fn as_str(&self) -> &'static str {
match self {
HoverAnimation::Grow => "grow",
HoverAnimation::Tilt => "tilt",
HoverAnimation::Buzz => "buzz",
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, EnumIs)]
pub enum StickerHidden {
OnClick(ShowHideAnimation),
UntilClick(ShowHideAnimation),
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, EnumIs, EnumIter, Default)]
pub enum ShowHideAnimation {
#[default]
Appear,
FadeInTop,
FadeInBottom,
FadeInLeft,
FadeInRight,
}
impl fmt::Display for ShowHideAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
ShowHideAnimation::Appear => "Appear",
ShowHideAnimation::FadeInTop => "Top",
ShowHideAnimation::FadeInBottom => "Bottom",
ShowHideAnimation::FadeInLeft => "Left",
ShowHideAnimation::FadeInRight => "Right",
}
)
}
}
impl ShowHideAnimation {
pub fn as_str(&self) -> &'static str {
match self {
ShowHideAnimation::Appear => "Appear",
ShowHideAnimation::FadeInTop => "fade-in-top",
ShowHideAnimation::FadeInBottom => "fade-in-bottom",
ShowHideAnimation::FadeInLeft => "fade-in-left",
ShowHideAnimation::FadeInRight => "fade-in-right",
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug, EnumIter, IntoStaticStr)]
#[repr(i16)]
#[cfg_attr(feature = "backend", derive(sqlx::Type))]
#[allow(missing_docs)]
#[strum(serialize_all = "kebab-case")]
pub enum ThemeId {
Blank,
Jigzi,
JigziGreen,
JigziBlue,
JigziRed,
Chalkboard,
Iml,
HebrewReading,
MyNotebook,
BackToSchool,
BackToSchoolYouth,
MyWorkspace,
Comix,
Surreal,
Abstract,
Denim,
HappyBrush,
Graffiti,
JewishText,
NumberGames,
WonderLab,
ShabbatShalom,
RoshHashana,
RoshHashanah,
AppleWithHoney,
Pomegranate,
YomKippur,
HappySukkot,
Sukkot,
#[strum(serialize = "tubishvat")]
TuBishvat,
IlluminatingHanukkah,
Chanukah,
ChanukahLights,
Purim,
PurimFeast,
PurimSweets,
HappyPassover,
#[strum(serialize = "passover-matza")]
PassoveMatza,
PassoverSeder,
LagBaOmer,
HappyShavuot,
ShavuotDishes,
ShavuotFields,
OurIsrael,
Israel,
JerusalemCity,
JerusalemWall,
NoahsArk,
GardenOfEden,
JewishStories,
LovelySpring,
Spring,
Flowers,
Nature,
SillyMonsters,
DinoGames,
WatermelonSummer,
SummerPool,
ExcitingFall,
Autumn,
WinterSnow,
IceAge,
LostInSpace,
Space,
Camping,
HappyBirthday,
#[strum(serialize = "valentine_s-day")]
Valentine,
Jungle,
OurPlanet,
Theater,
Sport,
Travel,
}
impl Default for ThemeId {
fn default() -> Self {
Self::Blank
}
}