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.
This commit is contained in:
Chris Cochrun 2024-12-10 09:27:50 -06:00
parent 9eb5bec320
commit cb7fa372a9
9 changed files with 493 additions and 79 deletions

View file

@ -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<Value> 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<Vec<Slide>> {
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<dyn ServiceTrait> {
Box::new((*self).clone())
}
}
impl Model<Image> {
pub async fn load_from_db(&mut self) {
let result = query_as!(

View file

@ -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<String> for ServiceItemKind {
type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
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<String> for ServiceItemKind {
// type Error = ParseError;
// fn try_from(value: String) -> Result<Self, Self::Error> {
// 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<ServiceItemKind> 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(),
}
}
}

View file

@ -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<Value> 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<Vec<Slide>> {
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<dyn ServiceTrait> {
Box::new((*self).clone())
}
}
impl Presentation {
pub fn new() -> Self {
Self {

View file

@ -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<dyn ServiceTrait>,
}
#[derive(Debug, Default, PartialEq)]
impl ServiceItem {
pub fn to_slide(&self) -> Result<Vec<Slide>> {
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<Value> 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<ServiceItem>,
}
impl From<Vec<ServiceItem>> for ServiceItemModel {
fn from(items: Vec<ServiceItem>) -> 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<Vec<Slide>> {
Ok(self
.items
.iter()
.filter_map(|item| item.to_slide().ok())
.flatten()
.collect::<Vec<Slide>>())
}
}
pub trait ServiceTrait {
fn title(&self) -> String;
fn id(&self) -> i32;
fn to_slides(&self) -> Result<Vec<Slide>>;
fn box_clone(&self) -> Box<dyn ServiceTrait>;
}
impl Clone for Box<dyn ServiceTrait> {
fn clone(&self) -> Self {
self.box_clone()
}
}
impl std::fmt::Debug for Box<dyn ServiceTrait> {
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),
// }
// }
}

View file

@ -228,6 +228,10 @@ impl Slide {
Ok(slides)
}
// pub fn slides_from_item(item: &ServiceItem) -> Result<Vec<Self>> {
// todo!()
// }
}
impl From<Value> for Slide {

View file

@ -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<i32>,
}
impl ServiceTrait for Song {
fn title(&self) -> String {
self.title.clone()
}
fn id(&self) -> i32 {
self.id
}
fn to_slides(&self) -> Result<Vec<Slide>> {
let lyrics = self.get_lyrics()?;
let slides: Vec<Slide> = 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<dyn ServiceTrait> {
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",

View file

@ -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<Value> 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<Vec<Slide>> {
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<dyn ServiceTrait> {
Box::new((*self).clone())
}
}
impl Model<Video> {
pub async fn load_from_db(&mut self) {
let result = query_as!(Video, r#"SELECT title as "title!", file_path as "path!", start_time as "start_time!: f32", end_time as "end_time!: f32", loop as "looping!", id as "id: i32" from videos"#).fetch_all(&mut self.db).await;

View file

@ -1,4 +1,5 @@
use clap::{command, Parser};
use core::service_items::ServiceItem;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::keyboard::Key;
use cosmic::iced::window::{Mode, Position};
@ -93,6 +94,8 @@ struct App {
enum Message {
Present(presenter::Message),
File(PathBuf),
DndEnter(ServiceItem),
DndDrop(ServiceItem),
OpenWindow,
CloseWindow(Option<window::Id>),
WindowOpened(window::Id, Option<Point>),
@ -429,6 +432,8 @@ impl cosmic::Application for App {
}
}
Message::Quit => cosmic::iced::exit(),
Message::DndEnter(service_item) => todo!(),
Message::DndDrop(service_item) => todo!(),
}
}

View file

@ -1,4 +1,4 @@
use std::time::Duration;
use std::{rc::Rc, time::Duration};
use cosmic::{
dialog::ashpd::url::Url,
@ -13,7 +13,6 @@ use cosmic::{
stack,
},
prelude::*,
theme::CosmicTheme,
widget::{
container, image, mouse_area, responsive, scrollable,
Container, Responsive, Row, Space,
@ -25,14 +24,17 @@ use miette::{Context, IntoDiagnostic, Result};
use tracing::{debug, error, info};
use crate::{
core::{service_items::ServiceItem, slide::Slide},
core::{
service_items::{ServiceItem, ServiceItemModel},
slide::Slide,
},
BackgroundKind,
};
// #[derive(Default, Clone, Debug)]
pub(crate) struct Presenter {
pub slides: Vec<Slide>,
pub items: Vec<ServiceItem>,
pub items: ServiceItemModel,
pub current_slide: Slide,
pub current_slide_index: u16,
pub video: Option<Video>,
@ -56,7 +58,47 @@ impl Presenter {
pub fn with_slides(slides: Vec<Slide>) -> Self {
Self {
slides: slides.clone(),
items: vec![],
items: ServiceItemModel::default(),
current_slide: slides[0].clone(),
current_slide_index: 0,
video: {
if let Some(slide) = slides.get(0) {
let path = slide.background().path.clone();
if path.exists() {
let url = Url::from_file_path(path).unwrap();
let result = Video::new(&url);
match result {
Ok(mut v) => {
v.set_paused(true);
Some(v)
}
Err(e) => {
error!("Had an error creating the video object: {e}");
None
}
}
} else {
None
}
} else {
None
}
},
video_position: 0.0,
hovered_slide: -1,
}
}
pub fn with_items(items: Vec<ServiceItem>) -> Self {
let items = ServiceItemModel::from(items);
let slides = if let Ok(slides) = items.to_slides() {
slides
} else {
vec![]
};
Self {
slides: slides.clone(),
items: items.into(),
current_slide: slides[0].clone(),
current_slide_index: 0,
video: {