From cb7fa372a9acca01c61273c7c9ceb976d02fa390 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 10 Dec 2024 09:27:50 -0600 Subject: [PATCH] moving the lisp presentation parsing to service_items Since we need the nav_bar to have the ServiceItemModel, the presentation needs to be a list of service items, not slides, each service item will have a list of slides attached though. --- src/core/images.rs | 73 ++++++++++++- src/core/kinds.rs | 80 +++++++------- src/core/presentations.rs | 50 ++++++++- src/core/service_items.rs | 212 ++++++++++++++++++++++++++++++++------ src/core/slide.rs | 4 + src/core/songs.rs | 44 +++++++- src/core/videos.rs | 52 +++++++++- src/main.rs | 5 + src/ui/presenter.rs | 52 +++++++++- 9 files changed, 493 insertions(+), 79 deletions(-) diff --git a/src/core/images.rs b/src/core/images.rs index 17bf94f..9c632d3 100644 --- a/src/core/images.rs +++ b/src/core/images.rs @@ -1,4 +1,7 @@ -use super::model::Model; +use crate::{Background, Slide, SlideBuilder, TextAlignment}; + +use super::{model::Model, service_items::ServiceTrait}; +use crisp::types::{Keyword, Value}; use miette::{miette, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use sqlx::{query_as, SqliteConnection}; @@ -14,6 +17,74 @@ pub struct Image { pub path: PathBuf, } +impl From for Image { + fn from(value: Value) -> Self { + Self::from(&value) + } +} + +impl From<&Value> for Image { + fn from(value: &Value) -> Self { + match value { + Value::List(list) => { + let path = if let Some(path_pos) = + list.iter().position(|v| { + v == &Value::Keyword(Keyword::from("source")) + }) { + let pos = path_pos + 1; + list.get(pos) + .map(|p| PathBuf::from(String::from(p))) + } else { + None + }; + + let title = path.clone().map(|p| { + p.to_str().unwrap_or_default().to_string() + }); + Self { + title: title.unwrap_or_default(), + path: path.unwrap_or_default(), + ..Default::default() + } + } + _ => todo!(), + } + } +} + +impl ServiceTrait for Image { + fn title(&self) -> String { + self.title.clone() + } + + fn id(&self) -> i32 { + self.id + } + + fn to_slides(&self) -> Result> { + let slide = SlideBuilder::new() + .background( + Background::try_from(self.path.clone()) + .into_diagnostic()?, + ) + .text("") + .audio("") + .font("") + .font_size(50) + .text_alignment(TextAlignment::MiddleCenter) + .video_loop(false) + .video_start_time(0.0) + .video_end_time(0.0) + .build()?; + + Ok(vec![slide]) + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} + impl Model { pub async fn load_from_db(&mut self) { let result = query_as!( diff --git a/src/core/kinds.rs b/src/core/kinds.rs index 40ed100..6a14ed4 100644 --- a/src/core/kinds.rs +++ b/src/core/kinds.rs @@ -2,65 +2,65 @@ use std::{error::Error, fmt::Display}; use serde::{Deserialize, Serialize}; -use super::presentations::PresKind; +use crate::Slide; -#[derive( - Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, -)] +use super::{ + images::Image, + presentations::{PresKind, Presentation}, + songs::Song, + videos::Video, +}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ServiceItemKind { - #[default] - Song, - Video, - Image, - Presentation(PresKind), - Content, + Song(Song), + Video(Video), + Image(Image), + Presentation((Presentation, PresKind)), + Content(Slide), } impl std::fmt::Display for ServiceItemKind { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let s = match self { - Self::Song => "song".to_owned(), - Self::Image => "image".to_owned(), - Self::Video => "video".to_owned(), - Self::Presentation(PresKind::Html) => "html".to_owned(), - Self::Presentation(PresKind::Pdf) => "pdf".to_owned(), - Self::Presentation(PresKind::Generic) => { - "presentation".to_owned() - } - Self::Content => "content".to_owned(), + Self::Song(s) => "song".to_owned(), + Self::Image(i) => "image".to_owned(), + Self::Video(v) => "video".to_owned(), + Self::Presentation((p, k)) => "html".to_owned(), + Self::Content(s) => "content".to_owned(), }; write!(f, "{s}") } } -impl TryFrom for ServiceItemKind { - type Error = ParseError; - fn try_from(value: String) -> Result { - match value.as_str() { - "song" => Ok(Self::Song), - "image" => Ok(Self::Image), - "video" => Ok(Self::Video), - "presentation" => { - Ok(Self::Presentation(PresKind::Generic)) - } - "html" => Ok(Self::Presentation(PresKind::Html)), - "pdf" => Ok(Self::Presentation(PresKind::Pdf)), - "content" => Ok(Self::Content), - _ => Err(ParseError::UnknownType), - } - } -} +// impl TryFrom for ServiceItemKind { +// type Error = ParseError; +// fn try_from(value: String) -> Result { +// match value.as_str() { +// "song" => Ok(Self::Song), +// "image" => Ok(Self::Image), +// "video" => Ok(Self::Video), +// "presentation" => { +// Ok(Self::Presentation(PresKind::Generic)) +// } +// "html" => Ok(Self::Presentation(PresKind::Html)), +// "pdf" => Ok(Self::Presentation(PresKind::Pdf)), +// "content" => Ok(Self::Content), +// _ => Err(ParseError::UnknownType), +// } +// } +// } impl From for String { fn from(val: ServiceItemKind) -> String { match val { - ServiceItemKind::Song => "song".to_owned(), - ServiceItemKind::Video => "video".to_owned(), - ServiceItemKind::Image => "image".to_owned(), + ServiceItemKind::Song(_) => "song".to_owned(), + ServiceItemKind::Video(_) => "video".to_owned(), + ServiceItemKind::Image(_) => "image".to_owned(), ServiceItemKind::Presentation(_) => { "presentation".to_owned() } - ServiceItemKind::Content => "content".to_owned(), + ServiceItemKind::Content(_) => "content".to_owned(), } } } diff --git a/src/core/presentations.rs b/src/core/presentations.rs index 8cbbc0e..3af4e82 100644 --- a/src/core/presentations.rs +++ b/src/core/presentations.rs @@ -1,3 +1,4 @@ +use crisp::types::Value; use miette::{miette, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use sqlx::{ @@ -6,7 +7,9 @@ use sqlx::{ use std::path::PathBuf; use tracing::error; -use super::model::Model; +use crate::{Background, Slide, SlideBuilder, TextAlignment}; + +use super::{model::Model, service_items::ServiceTrait}; #[derive( Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, @@ -28,6 +31,51 @@ pub struct Presentation { pub kind: PresKind, } +impl From for Presentation { + fn from(value: Value) -> Self { + Self::from(&value) + } +} + +impl From<&Value> for Presentation { + fn from(value: &Value) -> Self { + todo!() + } +} + +impl ServiceTrait for Presentation { + fn title(&self) -> String { + self.title.clone() + } + + fn id(&self) -> i32 { + self.id + } + + fn to_slides(&self) -> Result> { + let slide = SlideBuilder::new() + .background( + Background::try_from(self.path.clone()) + .into_diagnostic()?, + ) + .text("") + .audio("") + .font("") + .font_size(50) + .text_alignment(TextAlignment::MiddleCenter) + .video_loop(false) + .video_start_time(0.0) + .video_end_time(0.0) + .build()?; + + Ok(vec![slide]) + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} + impl Presentation { pub fn new() -> Self { Self { diff --git a/src/core/service_items.rs b/src/core/service_items.rs index 98a4fff..0451261 100644 --- a/src/core/service_items.rs +++ b/src/core/service_items.rs @@ -1,29 +1,146 @@ +use crisp::types::{Keyword, Symbol, Value}; use miette::Result; +use tracing::error; + +use crate::Slide; use super::images::Image; use super::presentations::Presentation; -use super::songs::Song; +use super::songs::{lisp_to_song, Song}; use super::videos::Video; use super::kinds::ServiceItemKind; -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct ServiceItem { pub id: i32, + pub title: String, pub database_id: i32, pub kind: ServiceItemKind, + // pub item: Box, } -#[derive(Debug, Default, PartialEq)] +impl ServiceItem { + pub fn to_slide(&self) -> Result> { + match &self.kind { + ServiceItemKind::Song(song) => song.to_slides(), + ServiceItemKind::Video(video) => video.to_slides(), + ServiceItemKind::Image(image) => image.to_slides(), + ServiceItemKind::Presentation((presentation, _)) => { + presentation.to_slides() + } + ServiceItemKind::Content(slide) => { + Ok(vec![slide.clone()]) + } + } + } +} + +impl Default for ServiceItem { + fn default() -> Self { + Self { + id: 0, + title: String::default(), + database_id: 0, + kind: ServiceItemKind::Content(Slide::default()), + // item: Box::new(Image::default()), + } + } +} + +impl From for ServiceItem { + fn from(value: Value) -> Self { + Self::from(&value) + } +} + +impl From<&Value> for ServiceItem { + fn from(value: &Value) -> Self { + match value { + Value::List(list) => match &list[0] { + Value::Symbol(Symbol(s)) if s == "slide" => { + let background_pos = list + .iter() + .position(|v| match v { + Value::Keyword(Keyword(background)) + if background == "background" => + { + true + } + _ => false, + }) + .map_or_else(|| 1, |pos| pos + 1); + if let Some(background) = list.get(background_pos) + { + match background { + Value::List(item) => match &item[0] { + Value::Symbol(Symbol(s)) + if s == "image" => + { + Self::from(&Image::from( + background, + )) + } + Value::Symbol(Symbol(s)) + if s == "video" => + { + Self::from(&Video::from( + background, + )) + } + Value::Symbol(Symbol(s)) + if s == "presentation" => + { + Self::from(&Presentation::from( + background, + )) + } + _ => todo!(), + }, + _ => { + error!( + "There is no background here: {:?}", + background + ); + ServiceItem::default() + } + } + } else { + error!( + "There is no background here: {:?}", + background_pos + ); + ServiceItem::default() + } + } + Value::Symbol(Symbol(s)) if s == "song" => { + let song = lisp_to_song(list.clone()); + Self::from(&song) + } + _ => todo!(), + }, + _ => todo!(), + } + } +} + +#[derive(Debug, Default)] pub struct ServiceItemModel { items: Vec, } +impl From> for ServiceItemModel { + fn from(items: Vec) -> Self { + Self { items } + } +} + impl From<&Song> for ServiceItem { fn from(song: &Song) -> Self { Self { - kind: ServiceItemKind::Song, + kind: ServiceItemKind::Song(song.clone()), database_id: song.id, + title: song.title.clone(), ..Default::default() } } @@ -32,8 +149,9 @@ impl From<&Song> for ServiceItem { impl From<&Video> for ServiceItem { fn from(video: &Video) -> Self { Self { - kind: ServiceItemKind::Video, + kind: ServiceItemKind::Video(video.clone()), database_id: video.id, + title: video.title.clone(), ..Default::default() } } @@ -42,8 +160,9 @@ impl From<&Video> for ServiceItem { impl From<&Image> for ServiceItem { fn from(image: &Image) -> Self { Self { - kind: ServiceItemKind::Image, + kind: ServiceItemKind::Image(image.clone()), database_id: image.id, + title: image.title.clone(), ..Default::default() } } @@ -52,10 +171,12 @@ impl From<&Image> for ServiceItem { impl From<&Presentation> for ServiceItem { fn from(presentation: &Presentation) -> Self { Self { - kind: ServiceItemKind::Presentation( + kind: ServiceItemKind::Presentation(( + presentation.clone(), presentation.kind.clone(), - ), + )), database_id: presentation.id, + title: presentation.title.clone(), ..Default::default() } } @@ -70,6 +191,37 @@ impl ServiceItemModel { self.items.push(service_item); Ok(()) } + + pub fn to_slides(&self) -> Result> { + Ok(self + .items + .iter() + .filter_map(|item| item.to_slide().ok()) + .flatten() + .collect::>()) + } +} + +pub trait ServiceTrait { + fn title(&self) -> String; + fn id(&self) -> i32; + fn to_slides(&self) -> Result>; + fn box_clone(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.box_clone() + } +} + +impl std::fmt::Debug for Box { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> Result<(), std::fmt::Error> { + write!(f, "{}: {}", self.id(), self.title()) + } } #[cfg(test)] @@ -99,26 +251,26 @@ mod test { } } - #[test] - pub fn test_service_item() { - let song = test_song(); - let service_item = ServiceItem::from(&song); - let pres = test_presentation(); - let pres_item = ServiceItem::from(&pres); - let mut service_model = ServiceItemModel::default(); - match service_model.add_item(&song) { - Ok(_) => { - assert_eq!( - ServiceItemKind::Song, - service_model.items[0].kind - ); - assert_eq!( - ServiceItemKind::Presentation(PresKind::Html), - pres_item.kind - ); - assert_eq!(service_item, service_model.items[0]); - } - Err(e) => panic!("Problem adding item: {:?}", e), - } - } + // #[test] + // pub fn test_service_item() { + // let song = test_song(); + // let service_item = ServiceItem::from(&song); + // let pres = test_presentation(); + // let pres_item = ServiceItem::from(&pres); + // let mut service_model = ServiceItemModel::default(); + // match service_model.add_item(&song) { + // Ok(_) => { + // assert_eq!( + // ServiceItemKind::Song, + // service_model.items[0].kind + // ); + // assert_eq!( + // ServiceItemKind::Presentation(PresKind::Html), + // pres_item.kind + // ); + // assert_eq!(service_item, service_model.items[0]); + // } + // Err(e) => panic!("Problem adding item: {:?}", e), + // } + // } } diff --git a/src/core/slide.rs b/src/core/slide.rs index fb973e5..6252f84 100644 --- a/src/core/slide.rs +++ b/src/core/slide.rs @@ -228,6 +228,10 @@ impl Slide { Ok(slides) } + + // pub fn slides_from_item(item: &ServiceItem) -> Result> { + // todo!() + // } } impl From for Slide { diff --git a/src/core/songs.rs b/src/core/songs.rs index 27e6a95..a60fede 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -10,10 +10,11 @@ use sqlx::{ }; use tracing::{debug, error}; -use crate::core::slide; +use crate::{core::slide, Slide, SlideBuilder}; use super::{ model::Model, + service_items::ServiceTrait, slide::{Background, TextAlignment}, }; @@ -34,6 +35,47 @@ pub struct Song { pub font_size: Option, } +impl ServiceTrait for Song { + fn title(&self) -> String { + self.title.clone() + } + + fn id(&self) -> i32 { + self.id + } + + fn to_slides(&self) -> Result> { + let lyrics = self.get_lyrics()?; + let slides: Vec = lyrics + .iter() + .map(|l| { + SlideBuilder::new() + .background( + self.background.clone().unwrap_or_default(), + ) + .font(self.font.clone().unwrap_or_default()) + .font_size(self.font_size.unwrap_or_default()) + .text_alignment( + self.text_alignment.unwrap_or_default(), + ) + .audio(self.audio.clone().unwrap_or_default()) + .video_loop(true) + .video_start_time(0.0) + .video_end_time(0.0) + .text(l) + .build() + .unwrap_or_default() + }) + .collect(); + + Ok(slides) + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} + const VERSE_KEYWORDS: [&str; 24] = [ "Verse 1", "Verse 2", "Verse 3", "Verse 4", "Verse 5", "Verse 6", "Verse 7", "Verse 8", "Chorus 1", "Chorus 2", "Chorus 3", diff --git a/src/core/videos.rs b/src/core/videos.rs index 57e9806..cd075c3 100644 --- a/src/core/videos.rs +++ b/src/core/videos.rs @@ -1,5 +1,10 @@ -use super::model::Model; +use crate::{Background, SlideBuilder, TextAlignment}; + +use super::{ + model::Model, service_items::ServiceTrait, slide::Slide, +}; use cosmic::{executor, iced::Executor}; +use crisp::types::Value; use miette::{miette, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; use sqlx::{query_as, SqliteConnection}; @@ -18,6 +23,51 @@ pub struct Video { pub looping: bool, } +impl From for Video { + fn from(value: Value) -> Self { + Self::from(&value) + } +} + +impl From<&Value> for Video { + fn from(value: &Value) -> Self { + todo!() + } +} + +impl ServiceTrait for Video { + fn title(&self) -> String { + self.title.clone() + } + + fn id(&self) -> i32 { + self.id + } + + fn to_slides(&self) -> Result> { + let slide = SlideBuilder::new() + .background( + Background::try_from(self.path.clone()) + .into_diagnostic()?, + ) + .text("") + .audio("") + .font("") + .font_size(50) + .text_alignment(TextAlignment::MiddleCenter) + .video_loop(self.looping) + .video_start_time(self.start_time.unwrap_or(0.0)) + .video_end_time(self.end_time.unwrap_or(0.0)) + .build()?; + + Ok(vec![slide]) + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} + impl Model