lumina-iced/src/core/service_items.rs
Chris Cochrun bec21239a5
Some checks failed
/ test (push) Has been cancelled
trying to make drag and drop work
2025-08-21 15:28:40 -05:00

447 lines
13 KiB
Rust

use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Deref;
use std::sync::Arc;
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use crisp::types::{Keyword, Symbol, Value};
use miette::Result;
use tracing::{debug, error};
use crate::Slide;
use super::images::Image;
use super::presentations::Presentation;
use super::songs::{lisp_to_song, Song};
use super::videos::Video;
use super::kinds::ServiceItemKind;
#[derive(Debug, PartialEq, Clone)]
pub struct ServiceItem {
pub id: i32,
pub title: String,
pub database_id: i32,
pub kind: ServiceItemKind,
pub slides: Arc<[Slide]>,
// pub item: Box<dyn ServiceTrait>,
}
impl Eq for ServiceItem {}
impl PartialOrd for ServiceItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.id.partial_cmp(&other.id)
}
}
impl Ord for ServiceItem {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl TryFrom<(Vec<u8>, String)> for ServiceItem {
type Error = miette::Error;
fn try_from(
value: (Vec<u8>, String),
) -> std::result::Result<Self, Self::Error> {
debug!(?value);
let val = Value::from(
String::from_utf8(value.0)
.expect("Value couldn't be made"),
);
Ok(Self::from(&val))
}
}
impl AllowedMimeTypes for ServiceItem {
fn allowed() -> Cow<'static, [String]> {
Cow::from(vec!["application/service-item".to_string()])
}
}
impl AsMimeTypes for ServiceItem {
fn available(&self) -> Cow<'static, [String]> {
debug!(?self);
Cow::from(vec!["application/service-item".to_string()])
}
fn as_bytes(
&self,
mime_type: &str,
) -> Option<std::borrow::Cow<'static, [u8]>> {
debug!(?self);
debug!(mime_type);
let val = Value::from(self);
let val = String::from(val);
Some(Cow::from(val.into_bytes()))
}
}
impl From<&ServiceItem> for Value {
fn from(value: &ServiceItem) -> Self {
match &value.kind {
ServiceItemKind::Song(song) => Value::from(song),
ServiceItemKind::Video(video) => Value::from(video),
ServiceItemKind::Image(image) => Value::from(image),
ServiceItemKind::Presentation(presentation) => {
Value::from(presentation)
}
ServiceItemKind::Content(slide) => Value::from(slide),
}
}
}
impl ServiceItem {
pub fn title(&self) -> String {
self.title.clone()
}
pub fn to_slides(&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()),
slides: Arc::new([]),
// 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(content) =
list.iter().position(|v| match v {
Value::List(list)
if list.iter().next()
== Some(&Value::Symbol(
Symbol("text".into()),
)) =>
{
list.iter().next().is_some()
}
_ => false,
})
{
let slide = Slide::from(value);
let title = slide.text();
Self {
id: 0,
title,
database_id: 0,
kind: ServiceItemKind::Content(
slide.clone(),
),
slides: Arc::new([slide]),
}
} else 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)
}
_ => ServiceItem::default(),
},
_ => ServiceItem::default(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Service {
items: Vec<ServiceItem>,
}
impl Deref for Service {
type Target = Vec<ServiceItem>;
fn deref(&self) -> &Self::Target {
&self.items
}
}
// impl Iterator for ServiceItemModel {
// type Item = ServiceItem;
// fn next(&mut self) -> Option<Self::Item> {
// *self.items.iter().next()
// }
// }
impl From<Vec<ServiceItem>> for Service {
fn from(items: Vec<ServiceItem>) -> Self {
Self { items }
}
}
impl From<&Song> for ServiceItem {
fn from(song: &Song) -> Self {
if let Ok(slides) = song.to_slides() {
Self {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
slides: slides.into(),
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Song(song.clone()),
database_id: song.id,
title: song.title.clone(),
..Default::default()
}
}
}
}
impl From<&Video> for ServiceItem {
fn from(video: &Video) -> Self {
if let Ok(slides) = video.to_slides() {
Self {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
slides: slides.into(),
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Video(video.clone()),
database_id: video.id,
title: video.title.clone(),
..Default::default()
}
}
}
}
impl From<&Image> for ServiceItem {
fn from(image: &Image) -> Self {
if let Ok(slides) = image.to_slides() {
Self {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
slides: slides.into(),
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Image(image.clone()),
database_id: image.id,
title: image.title.clone(),
..Default::default()
}
}
}
}
impl From<&Presentation> for ServiceItem {
fn from(presentation: &Presentation) -> Self {
if let Ok(slides) = presentation.to_slides() {
Self {
kind: ServiceItemKind::Presentation(
presentation.clone(),
),
database_id: presentation.id,
title: presentation.title.clone(),
slides: slides.into(),
..Default::default()
}
} else {
Self {
kind: ServiceItemKind::Presentation(
presentation.clone(),
),
database_id: presentation.id,
title: presentation.title.clone(),
..Default::default()
}
}
}
}
impl Service {
fn add_item(
&mut self,
item: impl Into<ServiceItem>,
) -> Result<()> {
let service_item: ServiceItem = item.into();
self.items.push(service_item);
Ok(())
}
pub fn to_slides(&self) -> Result<Vec<Slide>> {
let slides = self
.items
.iter()
.filter_map(|item| {
let slides = item.to_slides().ok();
debug!(?slides);
slides
})
.flatten()
.collect::<Vec<Slide>>();
let mut final_slides = vec![];
for (index, mut slide) in slides.into_iter().enumerate() {
slide.set_index(index as i32);
final_slides.push(slide);
}
Ok(final_slides)
}
}
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)]
mod test {
use std::path::PathBuf;
use crate::core::presentations::PresKind;
use super::*;
use pretty_assertions::assert_eq;
fn test_song() -> Song {
Song {
title: "Death Was Arrested".to_string(),
..Default::default()
}
}
fn test_presentation() -> Presentation {
Presentation {
id: 0,
title: "20240327T133649--12-isaiah-and-jesus__lesson_project_tfc".into(),
path: PathBuf::from(
"~/docs/notes/lessons/20240327T133649--12-isaiah-and-jesus__lesson_project_tfc.html",
),
kind: PresKind::Html,
}
}
#[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 = Service::default();
match service_model.add_item(&song) {
Ok(_) => {
assert_eq!(
ServiceItemKind::Song(song),
service_model.items[0].kind
);
assert_eq!(
ServiceItemKind::Presentation(pres),
pres_item.kind
);
assert_eq!(service_item, service_model.items[0]);
}
Err(e) => panic!("Problem adding item: {:?}", e),
}
}
}