diff --git a/src/core/model.rs b/src/core/model.rs index a778a14..53f6162 100644 --- a/src/core/model.rs +++ b/src/core/model.rs @@ -1,7 +1,10 @@ -use std::mem::replace; +use std::{borrow::Cow, mem::replace}; -use miette::{miette, Result}; +use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; +use miette::{miette, IntoDiagnostic, Result}; +use serde::{Deserialize, Serialize}; use sqlx::{Connection, SqliteConnection}; +use tracing::debug; #[derive(Debug, Clone)] pub struct Model { @@ -9,7 +12,9 @@ pub struct Model { pub kind: LibraryKind, } -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive( + Debug, Clone, PartialEq, Eq, Copy, Hash, Serialize, Deserialize, +)] pub enum LibraryKind { Song, Video, @@ -17,6 +22,46 @@ pub enum LibraryKind { Presentation, } +#[derive( + Debug, Clone, PartialEq, Eq, Copy, Hash, Serialize, Deserialize, +)] +pub struct KindWrapper(pub (LibraryKind, i32)); + +impl TryFrom<(Vec, String)> for KindWrapper { + type Error = miette::Error; + + fn try_from( + value: (Vec, String), + ) -> std::result::Result { + debug!(?value); + ron::de::from_bytes(&value.0).into_diagnostic() + } +} + +impl AllowedMimeTypes for KindWrapper { + fn allowed() -> Cow<'static, [String]> { + Cow::from(vec!["application/service-item".to_string()]) + } +} + +impl AsMimeTypes for KindWrapper { + fn available(&self) -> Cow<'static, [String]> { + debug!(?self); + Cow::from(vec!["application/service-item".to_string()]) + } + + fn as_bytes( + &self, + mime_type: &str, + ) -> Option> { + debug!(?self); + debug!(mime_type); + let ron = ron::ser::to_string(self).ok()?; + debug!(ron); + Some(Cow::from(ron.into_bytes())) + } +} + impl Model { pub fn add_item(&mut self, item: T) -> Result<()> { self.items.push(item); diff --git a/src/core/presentations.rs b/src/core/presentations.rs index 4ada88e..a1d8b2a 100644 --- a/src/core/presentations.rs +++ b/src/core/presentations.rs @@ -82,7 +82,7 @@ impl From<&Path> for Presentation { } impl From<&Presentation> for Value { - fn from(value: &Presentation) -> Self { + fn from(_value: &Presentation) -> Self { Self::List(vec![Self::Symbol(Symbol("presentation".into()))]) } } @@ -307,6 +307,38 @@ pub async fn remove_from_db( .map(|_| ()) } +pub async fn add_presentation_to_db( + presentation: Presentation, + db: PoolConnection, + id: i32, +) -> Result<()> { + let path = presentation + .path + .to_str() + .map(std::string::ToString::to_string) + .unwrap_or_default(); + let html = presentation.kind == PresKind::Html; + let mut db = db.detach(); + let result = query!( + r#"INSERT into presentations VALUES($1, $2, $3, $4)"#, + id, + presentation.title, + path, + html, + ) + .execute(&mut db) + .await + .into_diagnostic()?; + if result.last_insert_rowid() != id as i64 { + let rowid = result.last_insert_rowid(); + error!( + rowid, + id, "It appears that rowid and id aren't the same" + ); + } + Ok(()) +} + pub async fn update_presentation_in_db( presentation: Presentation, db: PoolConnection, diff --git a/src/main.rs b/src/main.rs index 4114475..4fa4b1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,8 +47,11 @@ use ui::presenter::{self, Presenter}; use ui::song_editor::{self, SongEditor}; use ui::EditorMode; +use crate::core::content::Content; use crate::core::kinds::ServiceItemKind; +use crate::core::model::KindWrapper; use crate::ui::image_editor::{self, ImageEditor}; +use crate::ui::presentation_editor::{self, PresentationEditor}; use crate::ui::text_svg::{self}; use crate::ui::video_editor::{self, VideoEditor}; use crate::ui::widgets::draggable; @@ -127,6 +130,7 @@ struct App { song_editor: SongEditor, video_editor: VideoEditor, image_editor: ImageEditor, + presentation_editor: PresentationEditor, searching: bool, search_query: String, search_results: Vec, @@ -145,6 +149,7 @@ enum Message { SongEditor(song_editor::Message), VideoEditor(video_editor::Message), ImageEditor(image_editor::Message), + PresentationEditor(presentation_editor::Message), File(PathBuf), OpenWindow, CloseWindow(Option), @@ -160,7 +165,7 @@ enum Message { SelectServiceItem(usize), AddSelectServiceItem(usize), HoveredServiceItem(Option), - AddServiceItem(usize, ServiceItem), + AddServiceItem(usize, KindWrapper), RemoveServiceItem(usize), AddServiceItemDrop(usize), AppendServiceItem(ServiceItem), @@ -333,6 +338,7 @@ impl cosmic::Application for App { song_editor, video_editor: VideoEditor::new(), image_editor: ImageEditor::new(), + presentation_editor: PresentationEditor::new(), searching: false, search_results: vec![], search_query: String::new(), @@ -818,6 +824,27 @@ impl cosmic::Application for App { video_editor::Action::None => Task::none(), } } + Message::PresentationEditor(message) => { + match self.presentation_editor.update(message) { + presentation_editor::Action::Task(task) => { + task.map(|m| { + cosmic::Action::App(Message::PresentationEditor( + m, + )) + }) + } + presentation_editor::Action::UpdatePresentation(presentation) => { + if self.library.is_some() { + self.update(Message::Library( + library::Message::UpdatePresentation(presentation), + )) + } else { + Task::none() + } + } + presentation_editor::Action::None => Task::none(), + } + } Message::Present(message) => { // debug!(?message); if self.presentation_open @@ -854,7 +881,7 @@ impl cosmic::Application for App { ) })); } - _ => todo!(), + _ => (), } self.current_item = (item_index, slide_index + 1); @@ -882,7 +909,7 @@ impl cosmic::Application for App { ) })); } - _ => todo!(), + _ => (), } } Task::batch(tasks) @@ -914,7 +941,7 @@ impl cosmic::Application for App { ) })); } - _ => todo!(), + _ => (), } self.current_item = (item_index, slide_index - 1); @@ -957,7 +984,7 @@ impl cosmic::Application for App { ) })); } - _ => todo!(), + _ => (), } } Task::batch(tasks) @@ -1013,7 +1040,14 @@ impl cosmic::Application for App { let image = lib_image.to_owned(); return self.update(Message::ImageEditor(image_editor::Message::ChangeImage(image))); }, - core::model::LibraryKind::Presentation => todo!(), + core::model::LibraryKind::Presentation => { + let Some(lib_presentation) = library.get_presentation(index) else { + return Task::none(); + }; + self.editor_mode = Some(kind.into()); + let presentation = lib_presentation.to_owned(); + return self.update(Message::PresentationEditor(presentation_editor::Message::ChangePresentation(presentation))); + }, } } library::Action::DraggedItem( @@ -1145,20 +1179,40 @@ impl cosmic::Application for App { } Task::none() } - Message::AddServiceItem(index, mut item) => { - item.slides = item - .slides - .into_par_iter() - .map(|mut slide| { - let fontdb = Arc::clone(&self.fontdb); - text_svg::text_svg_generator( - &mut slide, fontdb, - ); - slide - }) - .collect(); - self.service.insert(index, item); - self.presenter.update_items(self.service.clone()); + Message::AddServiceItem(index, item) => { + let item_index = item.0 .1; + let kind = item.0 .0; + match kind { + core::model::LibraryKind::Song => todo!(), + core::model::LibraryKind::Video => todo!(), + core::model::LibraryKind::Image => todo!(), + core::model::LibraryKind::Presentation => { + let Some(library) = self.library.as_mut() + else { + return Task::none(); + }; + let Some(presentation) = + library.get_presentation(item_index) + else { + return Task::none(); + }; + let mut item = presentation.to_service_item(); + item.slides = item + .slides + .into_par_iter() + .map(|mut slide| { + let fontdb = Arc::clone(&self.fontdb); + text_svg::text_svg_generator( + &mut slide, fontdb, + ); + slide + }) + .collect(); + self.service.insert(index, item); + self.presenter + .update_items(self.service.clone()); + } + } Task::none() } Message::RemoveServiceItem(index) => { @@ -1221,10 +1275,24 @@ impl cosmic::Application for App { song_editor::Message::ChangeSong(song), )) } - ServiceItemKind::Video(_video) => todo!(), - ServiceItemKind::Image(_image) => todo!(), - ServiceItemKind::Presentation(_presentation) => { - todo!() + ServiceItemKind::Video(video) => { + self.editor_mode = Some(EditorMode::Video); + self.update(Message::VideoEditor( + video_editor::Message::ChangeVideo(video), + )) + } + ServiceItemKind::Image(image) => { + self.editor_mode = Some(EditorMode::Image); + self.update(Message::ImageEditor( + image_editor::Message::ChangeImage(image), + )) + } + ServiceItemKind::Presentation(presentation) => { + self.editor_mode = + Some(EditorMode::Presentation); + self.update(Message::PresentationEditor( + presentation_editor::Message::ChangePresentation(presentation), + )) } ServiceItemKind::Content(_slide) => todo!(), } @@ -1371,7 +1439,10 @@ impl cosmic::Application for App { EditorMode::Video => { self.video_editor.view().map(Message::VideoEditor) } - EditorMode::Presentation => todo!(), + EditorMode::Presentation => self + .presentation_editor + .view() + .map(Message::PresentationEditor), EditorMode::Slide => todo!(), }, ); @@ -1692,7 +1763,7 @@ where ) .on_finish(move |mime, data, _, _, _| { let Ok(item) = - ServiceItem::try_from((data, mime)) + KindWrapper::try_from((data, mime)) else { error!("couldn't drag in Service item"); return Message::None; diff --git a/src/ui/library.rs b/src/ui/library.rs index 80c9fe7..a43196e 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -26,8 +26,11 @@ use tracing::{debug, error, warn}; use crate::core::{ content::Content, images::{self, update_image_in_db, Image}, - model::{LibraryKind, Model}, - presentations::{self, update_presentation_in_db, Presentation}, + model::{KindWrapper, LibraryKind, Model}, + presentations::{ + self, add_presentation_to_db, update_presentation_in_db, + Presentation, + }, service_items::ServiceItem, songs::{self, update_song_in_db, Song}, videos::{self, update_video_in_db, Video}, @@ -99,6 +102,7 @@ pub enum Message { None, AddImages(Option>), AddVideos(Option>), + AddPresentations(Option>), } impl<'a> Library { @@ -143,11 +147,10 @@ impl<'a> Library { let item = match kind { LibraryKind::Song => { let song = Song::default(); + let index = self.song_library.items.len(); self.song_library .add_item(song) .map(|_| { - let index = - self.song_library.items.len(); (LibraryKind::Song, index as i32) }) .ok() @@ -165,26 +168,17 @@ impl<'a> Library { )); } LibraryKind::Presentation => { - let presentation = Presentation::default(); - self.presentation_library - .add_item(presentation) - .map(|_| { - let index = self - .presentation_library - .items - .len(); - ( - LibraryKind::Presentation, - index as i32, - ) - }) - .ok() + return Action::Task(Task::perform( + add_presentations(), + Message::AddPresentations, + )); } }; return self.update(Message::OpenItem(item)); } Message::AddVideos(videos) => { debug!(?videos); + let mut index = self.video_library.items.len(); if let Some(videos) = videos { for video in videos { if let Err(e) = @@ -192,6 +186,7 @@ impl<'a> Library { { error!(?e); } + index += 1; } } return self.update(Message::OpenItem(Some(( @@ -199,8 +194,78 @@ impl<'a> Library { self.video_library.items.len() as i32 - 1, )))); } + Message::AddPresentations(presentations) => { + debug!(?presentations); + let mut index = self.presentation_library.items.len(); + // Check if empty + let mut tasks = Vec::new(); + if index == 0 { + if let Some(presentations) = presentations { + let len = presentations.len(); + for presentation in presentations { + if let Err(e) = self + .presentation_library + .add_item(presentation.clone()) + { + error!(?e); + } + let task = Task::future( + self.db.acquire(), + ) + .and_then(move |db| { + Task::perform( + add_presentation_to_db( + presentation.to_owned(), + db, + index as i32, + ), + move |res| { + debug!( + len, + index, "added to db" + ); + if let Err(e) = res { + error!(?e); + } + if len == index { + debug!("open the pres"); + Message::OpenItem(Some(( + LibraryKind::Presentation, + index as i32, + ))) + } else { + Message::None + } + }, + ) + }); + tasks.push(task); + index += 1; + } + } + return Action::Task(Task::batch(tasks)); + } else { + if let Some(presentations) = presentations { + for presentation in presentations { + if let Err(e) = self + .presentation_library + .add_item(presentation) + { + error!(?e); + } + index += 1; + } + } + return self.update(Message::OpenItem(Some(( + LibraryKind::Presentation, + self.presentation_library.items.len() as i32 + - 1, + )))); + } + } Message::AddImages(images) => { debug!(?images); + let mut index = self.image_library.items.len(); if let Some(images) = images { for image in images { if let Err(e) = @@ -208,6 +273,7 @@ impl<'a> Library { { error!(?e); } + index += 1; } } return self.update(Message::OpenItem(Some(( @@ -479,6 +545,21 @@ impl<'a> Library { Message::PresentationChanged => (), Message::Error(_) => (), Message::OpenContext(index) => { + let Some(kind) = self.library_open else { + return Action::None; + }; + let Some(items) = self.selected_items.as_mut() else { + self.selected_items = vec![(kind, index)].into(); + self.context_menu = Some(index); + return Action::None; + }; + + if items.contains(&(kind, index)) { + () + } else { + items.push((kind, index)); + } + self.selected_items = Some(items.to_vec()); self.context_menu = Some(index); } } @@ -597,13 +678,12 @@ impl<'a> Library { column({ model.items.iter().enumerate().map( |(index, item)| { - - let service_item = item.to_service_item(); + let kind = model.kind; let visual_item = self .single_item(index, item, model) .map(|()| Message::None); - DndSource::::new({ + DndSource::::new({ let mouse_area = mouse_area(visual_item); let mouse_area = mouse_area.on_enter(Message::HoverItem( Some(( @@ -669,7 +749,7 @@ impl<'a> Library { ) }}) .drag_content(move || { - service_item.clone() + KindWrapper((kind, index as i32)) }) .into() }, @@ -908,6 +988,13 @@ impl<'a> Library { self.image_library.get_item(index) } + pub fn get_presentation( + &self, + index: i32, + ) -> Option<&Presentation> { + self.presentation_library.get_item(index) + } + pub fn set_modifiers(&mut self, modifiers: Option) { self.modifiers_pressed = modifiers; } @@ -1083,6 +1170,23 @@ async fn add_videos() -> Option> { ) } +async fn add_presentations() -> Option> { + let paths = Dialog::new() + .title("pick presentation") + .open_files() + .await + .ok()?; + Some( + paths + .urls() + .iter() + .map(|path| { + Presentation::from(path.to_file_path().expect("oops")) + }) + .collect(), + ) +} + async fn add_db() -> Result { let mut data = dirs::data_local_dir().unwrap(); data.push("lumina"); diff --git a/src/ui/presentation_editor.rs b/src/ui/presentation_editor.rs index 7b181b0..b124665 100644 --- a/src/ui/presentation_editor.rs +++ b/src/ui/presentation_editor.rs @@ -6,12 +6,12 @@ use crate::core::{ }; use cosmic::{ dialog::file_chooser::{open::Dialog, FileFilter}, - iced::{alignment::Vertical, Length}, + iced::{alignment::Vertical, ContentFit, Length}, iced_widget::{column, row}, theme, widget::{ - button, container, horizontal_space, icon, text, text_input, - Space, + button, container, horizontal_space, icon, image, text, + text_input, Space, }, Element, Task, }; @@ -23,6 +23,7 @@ pub struct PresentationEditor { slides: Option>, title: String, editing: bool, + current_slide: i16, } pub enum Action { @@ -48,6 +49,7 @@ impl PresentationEditor { slides: None, title: "Death was Arrested".to_string(), editing: false, + current_slide: 0, } } pub fn update(&mut self, message: Message) -> Action { @@ -55,10 +57,12 @@ impl PresentationEditor { Message::ChangePresentation(presentation) => { self.presentation = Some(presentation.clone()); self.title = presentation.title.clone(); + warn!("changing presentation"); let Ok(slides) = presentation.to_slides() else { return Action::None; }; self.slides = Some(slides); + self.current_slide = 0; return self.update(Message::Update(presentation)); } Message::ChangeTitle(title) => { @@ -107,8 +111,18 @@ impl PresentationEditor { pub fn view(&self) -> Element { let container = if let Some(slides) = &self.slides { - todo!(); - // container(presentation) + if let Some(slide) = + slides.get(self.current_slide as usize) + { + container( + image(slide.pdf_page().unwrap_or( + image::Handle::from_path("res/chad.png"), + )) + .content_fit(ContentFit::Cover), + ) + } else { + container(Space::new(0, 0)) + } } else { container(Space::new(0, 0)) };