From 375cea2408f299f2c832d3c0fd00b07dd8e7fa16 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Sat, 4 Oct 2025 13:59:36 -0500 Subject: [PATCH] fixing some database and adding issues --- src/core/images.rs | 59 ++-- src/core/presentations.rs | 13 +- src/core/songs.rs | 45 +++ src/core/videos.rs | 65 ++-- src/main.rs | 113 +++++-- src/ui/image_editor.rs | 12 +- src/ui/library.rs | 578 +++++++++++++++++++++++----------- src/ui/presentation_editor.rs | 83 ++--- src/ui/song_editor.rs | 79 +++-- src/ui/video_editor.rs | 52 +-- todo.org | 2 +- 11 files changed, 709 insertions(+), 392 deletions(-) diff --git a/src/core/images.rs b/src/core/images.rs index cd9b2e7..7e37607 100644 --- a/src/core/images.rs +++ b/src/core/images.rs @@ -201,6 +201,27 @@ pub async fn remove_from_db( .map(|_| ()) } +pub async fn add_image_to_db( + image: Image, + db: PoolConnection, +) -> Result<()> { + let path = image + .path + .to_str() + .map(std::string::ToString::to_string) + .unwrap_or_default(); + let mut db = db.detach(); + query!( + r#"INSERT INTO images (title, file_path) VALUES ($1, $2)"#, + image.title, + path, + ) + .execute(&mut db) + .await + .into_diagnostic()?; + Ok(()) +} + pub async fn update_image_in_db( image: Image, db: PoolConnection, @@ -211,44 +232,6 @@ pub async fn update_image_in_db( .map(std::string::ToString::to_string) .unwrap_or_default(); let mut db = db.detach(); - let id = image.id; - if let Err(e) = query!("SELECT id FROM images where id = $1", id) - .fetch_one(&mut db) - .await - { - if let Ok(ids) = - query!("SELECT id FROM images").fetch_all(&mut db).await - { - let Some(mut max) = ids.iter().map(|r| r.id).max() else { - return Err(miette::miette!("cannot find max id")); - }; - debug!(?e, "Image not found"); - max += 1; - let result = query!( - r#"INSERT into images VALUES($1, $2, $3)"#, - max, - image.title, - path, - ) - .execute(&mut db) - .await - .into_diagnostic(); - - return match result { - Ok(_) => { - debug!("should have been updated"); - Ok(()) - } - Err(e) => { - error! {?e}; - Err(e) - } - }; - } else { - return Err(miette::miette!("cannot find ids")); - } - }; - debug!(?image, "should be been updated"); let result = query!( r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#, diff --git a/src/core/presentations.rs b/src/core/presentations.rs index 3572edc..06ded09 100644 --- a/src/core/presentations.rs +++ b/src/core/presentations.rs @@ -310,7 +310,6 @@ pub async fn remove_from_db( pub async fn add_presentation_to_db( presentation: Presentation, db: PoolConnection, - id: i32, ) -> Result<()> { let path = presentation .path @@ -319,9 +318,8 @@ pub async fn add_presentation_to_db( .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, + query!( + r#"INSERT INTO presentations (title, file_path, html) VALUES ($1, $2, $3)"#, presentation.title, path, html, @@ -329,13 +327,6 @@ pub async fn add_presentation_to_db( .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(()) } diff --git a/src/core/songs.rs b/src/core/songs.rs index 2be3a66..31ec39c 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -420,6 +420,51 @@ pub async fn remove_from_db( .map(|_| ()) } +pub async fn add_song_to_db( + song: Song, + db: PoolConnection, +) -> Result<()> { + let mut db = db.detach(); + + let verse_order = { + if let Some(vo) = song.verse_order { + vo.into_iter() + .map(|mut s| { + s.push(' '); + s + }) + .collect::() + } else { + String::new() + } + }; + + let audio = song + .audio + .map(|a| a.to_str().unwrap_or_default().to_string()); + + let background = song + .background + .map(|b| b.path.to_str().unwrap_or_default().to_string()); + + query!( + r#"INSERT INTO songs (title, lyrics, author, ccli, verse_order, audio, font, font_size, background) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"#, + song.title, + song.lyrics, + song.author, + song.ccli, + verse_order, + audio, + song.font, + song.font_size, + background + ) + .execute(&mut db) + .await + .into_diagnostic()?; + Ok(()) +} + pub async fn update_song_in_db( item: Song, db: PoolConnection, diff --git a/src/core/videos.rs b/src/core/videos.rs index c881572..e80f9b2 100644 --- a/src/core/videos.rs +++ b/src/core/videos.rs @@ -236,6 +236,30 @@ pub async fn remove_from_db( .map(|_| ()) } +pub async fn add_video_to_db( + video: Video, + db: PoolConnection, +) -> Result<()> { + let path = video + .path + .to_str() + .map(std::string::ToString::to_string) + .unwrap_or_default(); + let mut db = db.detach(); + query!( + r#"INSERT INTO videos (title, file_path, start_time, end_time, loop) VALUES ($1, $2, $3, $4, $5)"#, + video.title, + path, + video.start_time, + video.end_time, + video.looping + ) + .execute(&mut db) + .await + .into_diagnostic()?; + Ok(()) +} + pub async fn update_video_in_db( video: Video, db: PoolConnection, @@ -246,47 +270,6 @@ pub async fn update_video_in_db( .map(std::string::ToString::to_string) .unwrap_or_default(); let mut db = db.detach(); - let id = video.id; - if let Err(e) = query!("SELECT id FROM videos where id = $1", id) - .fetch_one(&mut db) - .await - { - if let Ok(ids) = - query!("SELECT id FROM videos").fetch_all(&mut db).await - { - let Some(mut max) = ids.iter().map(|r| r.id).max() else { - return Err(miette::miette!("cannot find max id")); - }; - debug!(?e, "Video not found"); - max += 1; - let result = query!( - r#"INSERT into videos VALUES($1, $2, $3, $4, $5, $6)"#, - max, - video.title, - path, - video.start_time, - video.end_time, - video.looping, - ) - .execute(&mut db) - .await - .into_diagnostic(); - - return match result { - Ok(_) => { - debug!("should have been updated"); - Ok(()) - } - Err(e) => { - error! {?e}; - Err(e) - } - }; - } else { - return Err(miette::miette!("cannot find ids")); - } - }; - debug!(?video, "should be been updated"); let result = query!( r#"UPDATE videos SET title = $2, file_path = $3, start_time = $4, end_time = $5, loop = $6 WHERE id = $1"#, diff --git a/src/main.rs b/src/main.rs index 7858a52..5c6cc1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use core::slide::{ }; use cosmic::app::context_drawer::ContextDrawer; use cosmic::app::{Core, Settings, Task}; +use cosmic::dialog::file_chooser::{self, save}; use cosmic::iced::alignment::Vertical; use cosmic::iced::keyboard::{Key, Modifiers}; use cosmic::iced::window::{Mode, Position}; @@ -14,15 +15,14 @@ use cosmic::iced::{ }; use cosmic::iced_core::text::Wrapping; use cosmic::iced_futures::Subscription; -use cosmic::iced_widget::{column, row, stack}; -use cosmic::theme; +use cosmic::iced_widget::{column, horizontal_rule, row, stack}; use cosmic::widget::button::Catalog; use cosmic::widget::dnd_destination::dnd_destination; use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::{ItemWidth, KeyBind}; use cosmic::widget::nav_bar::nav_bar_style; use cosmic::widget::tooltip::Position as TPosition; -use cosmic::widget::{Container, menu}; +use cosmic::widget::{Container, divider, menu}; use cosmic::widget::{ Space, button, context_menu, horizontal_space, mouse_area, nav_bar, nav_bar_toggle, responsive, scrollable, search_input, @@ -30,10 +30,13 @@ use cosmic::widget::{ }; use cosmic::widget::{container, text}; use cosmic::widget::{icon, slider}; -use cosmic::{Application, ApplicationExt, Element, executor}; +use cosmic::{ + Application, ApplicationExt, Element, dialog, executor, +}; +use cosmic::{theme, widget}; use crisp::types::Value; use lisp::parse_lisp; -use miette::{Result, miette}; +use miette::{IntoDiagnostic, Result, miette}; use rayon::prelude::*; use resvg::usvg::fontdb; use std::collections::HashMap; @@ -49,6 +52,7 @@ use ui::presenter::{self, Presenter}; use ui::song_editor::{self, SongEditor}; use crate::core::content::Content; +use crate::core::file; use crate::core::kinds::ServiceItemKind; use crate::core::model::KindWrapper; use crate::ui::image_editor::{self, ImageEditor}; @@ -123,6 +127,7 @@ struct App { selected_items: Vec, current_item: (usize, usize), hovered_item: Option, + hovered_dnd: Option, presentation_open: bool, cli_mode: bool, library: Option, @@ -166,6 +171,7 @@ enum Message { SelectServiceItem(usize), AddSelectServiceItem(usize), HoveredServiceItem(Option), + HoveredServiceDrop(Option), AddServiceItem(usize, KindWrapper), AddServiceItemsFiles(usize, Vec), RemoveServiceItem(usize), @@ -183,8 +189,9 @@ enum Message { New, Open, OpenFile(PathBuf), - Save(Option), - SaveAs, + Save, + SaveAsDialog, + SaveAs(PathBuf), OpenSettings, ModifiersPressed(Modifiers), } @@ -205,8 +212,8 @@ impl menu::Action for MenuAction { fn message(&self) -> Self::Message { match self { MenuAction::New => Message::New, - MenuAction::Save => Message::Save(None), - MenuAction::SaveAs => Message::SaveAs, + MenuAction::Save => Message::Save, + MenuAction::SaveAs => Message::SaveAsDialog, MenuAction::Open => Message::Open, MenuAction::OpenSettings => Message::OpenSettings, MenuAction::DeleteItem(index) => { @@ -352,6 +359,7 @@ impl cosmic::Application for App { fontdb: Arc::clone(&fontdb), menu_keys, hovered_item: None, + hovered_dnd: None, context_menu: None, modifiers_pressed: None, }; @@ -620,8 +628,8 @@ impl cosmic::Application for App { } iced::Event::Touch(_touch) => None, iced::Event::A11y(_id, _action_request) => None, - iced::Event::Dnd(dnd_event) => { - debug!(?dnd_event); + iced::Event::Dnd(_dnd_event) => { + // debug!(?dnd_event); None } iced::Event::PlatformSpecific(_platform_specific) => { @@ -1139,6 +1147,10 @@ impl cosmic::Application for App { self.hovered_item = index; Task::none() } + Message::HoveredServiceDrop(index) => { + self.hovered_dnd = index; + Task::none() + } Message::SelectServiceItem(index) => { self.selected_items = vec![index]; Task::none() @@ -1234,6 +1246,7 @@ impl cosmic::Application for App { Task::none() } Message::AddServiceItemsFiles(index, items) => { + self.hovered_dnd = None; for item in items { self.service.insert(index, item); } @@ -1345,17 +1358,44 @@ impl cosmic::Application for App { debug!(?file, "opening file"); Task::none() } - Message::Save(file) => { - let Some(file) = file else { - debug!("saving current"); - return Task::none(); - }; - debug!(?file, "saving new file"); - Task::none() + Message::Save => { + let service = self.service.clone(); + let file = self.file.clone(); + Task::perform( + file::save(service, file.clone()), + move |res| match res { + Ok(_) => { + tracing::info!( + "saving file to: {:?}", + file + ); + cosmic::Action::None + } + Err(e) => { + error!(?e, "There was a problem saving"); + cosmic::Action::None + } + }, + ) } - Message::SaveAs => { - debug!("saving as a file"); - Task::none() + Message::SaveAs(file) => { + debug!(?file, "saving as a file"); + self.file = file; + return self.update(Message::Save); + } + Message::SaveAsDialog => { + Task::perform(save_as_dialog(), |file| match file { + Ok(file) => { + cosmic::Action::App(Message::SaveAs(file)) + } + Err(e) => { + error!( + ?e, + "There was an error during saving" + ); + cosmic::Action::None + } + }) } Message::OpenSettings => { debug!("Opening settings"); @@ -1635,7 +1675,7 @@ where } match (key, modifiers) { (Key::Character(k), Modifiers::CTRL) if k == *"s" => { - self.update(Message::Save(None)) + self.update(Message::Save) } (Key::Character(k), Modifiers::CTRL) if k == *"o" => { self.update(Message::Open) @@ -1743,7 +1783,20 @@ where )) }) .width(Length::Fill); - let mouse_area = mouse_area(container) + let visual_item = if self.hovered_dnd.is_some_and(|h| h == index) { + let divider = divider::horizontal::default().class(theme::Rule::custom(|t| { + let color = t.cosmic().accent_color(); + let style = cosmic::iced_widget::rule::Style { + color: color.into(), + width: 2, + radius: t.cosmic().corner_radii.radius_xs.into(), + fill_mode: cosmic::iced_widget::rule::FillMode::Full, + }; + style + } )); + Container::new(column![divider, container].spacing(theme::spacing().space_s)) + } else { container }; + let mouse_area = mouse_area(visual_item) .on_enter(Message::HoveredServiceItem(Some( index, ))) @@ -1797,6 +1850,8 @@ where tooltip, vec!["application/service-item".into(), "text/uri-list".into(), "x-special/gnome-copied-files".into()], ) + .on_enter(move |_, _, _| Message::HoveredServiceDrop(Some(index))) + .on_leave(move || Message::HoveredServiceDrop(None)) .on_finish(move |mime, data, _, _, _| { match mime.as_str() { @@ -1905,3 +1960,15 @@ where container.center(Length::FillPortion(2)).into() } } + +async fn save_as_dialog() -> Result { + let dialog = save::Dialog::new(); + save::file(dialog).await.into_diagnostic().map(|response| { + match response.url() { + Some(url) => Ok(url.to_file_path().unwrap()), + None => { + Err(miette!("Can't convert url of file to a path")) + } + } + })? +} diff --git a/src/ui/image_editor.rs b/src/ui/image_editor.rs index 194484f..b2be3f6 100644 --- a/src/ui/image_editor.rs +++ b/src/ui/image_editor.rs @@ -48,9 +48,7 @@ impl ImageEditor { pub fn update(&mut self, message: Message) -> Action { match message { Message::ChangeImage(image) => { - self.image = Some(image.clone()); - self.title = image.title.clone(); - return self.update(Message::Update(image)); + self.update_entire_image(&image); } Message::ChangeTitle(title) => { self.title = title.clone(); @@ -66,6 +64,7 @@ impl ImageEditor { } Message::Update(image) => { warn!(?image); + self.update_entire_image(&image); return Action::UpdateImage(image); } Message::PickImage => { @@ -80,7 +79,7 @@ impl ImageEditor { if let Ok(image) = image_result { let mut image = Image::from(image); image.id = image_id; - Message::ChangeImage(image) + Message::Update(image) } else { Message::None } @@ -134,6 +133,11 @@ impl ImageEditor { pub const fn editing(&self) -> bool { self.editing } + + fn update_entire_image(&mut self, image: &Image) { + self.image = Some(image.clone()); + self.title = image.title.clone(); + } } impl Default for ImageEditor { diff --git a/src/ui/library.rs b/src/ui/library.rs index d531463..fcdceb0 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -24,7 +24,7 @@ use tracing::{debug, error, warn}; use crate::core::{ content::Content, - images::{self, Image, update_image_in_db}, + images::{self, Image, add_image_to_db, update_image_in_db}, kinds::ServiceItemKind, model::{KindWrapper, LibraryKind, Model}, presentations::{ @@ -32,8 +32,8 @@ use crate::core::{ update_presentation_in_db, }, service_items::ServiceItem, - songs::{self, Song, update_song_in_db}, - videos::{self, Video, update_video_in_db}, + songs::{self, Song, add_song_to_db, update_song_in_db}, + videos::{self, Video, add_video_to_db, update_video_in_db}, }; #[derive(Debug, Clone)] @@ -56,7 +56,7 @@ pub struct Library { #[derive(Debug, Clone, Eq, PartialEq, Copy)] enum MenuMessage { Delete, - Open((LibraryKind, i32)), + Open, } impl MenuAction for MenuMessage { @@ -65,9 +65,7 @@ impl MenuAction for MenuMessage { fn message(&self) -> Self::Message { match self { MenuMessage::Delete => Message::DeleteItem, - MenuMessage::Open((kind, index)) => { - Message::OpenItem(Some((*kind, *index))) - } + MenuMessage::Open => Message::OpenContextItem, } } } @@ -84,6 +82,7 @@ pub enum Message { AddItem, DeleteItem, OpenItem(Option<(LibraryKind, i32)>), + OpenContextItem, HoverLibrary(Option), OpenLibrary(Option), HoverItem(Option<(LibraryKind, i32)>), @@ -180,37 +179,71 @@ impl<'a> Library { Message::AddVideos(videos) => { debug!(?videos); let mut index = self.video_library.items.len(); + // Check if empty + let mut tasks = Vec::new(); if let Some(videos) = videos { + let len = videos.len(); for video in videos { if let Err(e) = - self.video_library.add_item(video) + self.video_library.add_item(video.clone()) { error!(?e); } + let task = Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + add_video_to_db( + video.to_owned(), + db, + ), + 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::Video, + index as i32, + ))) + } else { + Message::None + } + }, + ) + }); + tasks.push(task); index += 1; } } - return self.update(Message::OpenItem(Some(( - LibraryKind::Video, - self.video_library.items.len() as i32 - 1, - )))); + let after_task = + Task::done(Message::OpenItem(Some(( + LibraryKind::Video, + self.video_library.items.len() as i32 - 1, + )))); + return Action::Task( + Task::batch(tasks).chain(after_task), + ); } 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( + 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| { @@ -218,7 +251,6 @@ impl<'a> Library { add_presentation_to_db( presentation.to_owned(), db, - index as i32, ), move |res| { debug!( @@ -240,57 +272,93 @@ impl<'a> Library { }, ) }); - tasks.push(task); - index += 1; - } + 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(( + } + let after_task = + Task::done(Message::OpenItem(Some(( LibraryKind::Presentation, self.presentation_library.items.len() as i32 - 1, )))); - } + return Action::Task( + Task::batch(tasks).chain(after_task), + ); } Message::AddImages(images) => { debug!(?images); let mut index = self.image_library.items.len(); + // Check if empty + let mut tasks = Vec::new(); if let Some(images) = images { + let len = images.len(); for image in images { if let Err(e) = - self.image_library.add_item(image) + self.image_library.add_item(image.clone()) { error!(?e); } + let task = Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + add_image_to_db( + image.to_owned(), + db, + ), + 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::Image, + index as i32, + ))) + } else { + Message::None + } + }, + ) + }); + tasks.push(task); index += 1; } } - return self.update(Message::OpenItem(Some(( - LibraryKind::Image, - self.image_library.items.len() as i32 - 1, - )))); + let after_task = + Task::done(Message::OpenItem(Some(( + LibraryKind::Image, + self.image_library.items.len() as i32 - 1, + )))); + return Action::Task( + Task::batch(tasks).chain(after_task), + ); } Message::OpenItem(item) => { debug!(?item); self.editing_item = item; return Action::OpenItem(item); } + Message::OpenContextItem => { + let Some(kind) = self.library_open else { + return Action::None; + }; + let Some(index) = self.context_menu else { + return Action::None; + }; + return self + .update(Message::OpenItem(Some((kind, index)))); + } Message::HoverLibrary(library_kind) => { self.library_hovered = library_kind; } Message::OpenLibrary(library_kind) => { + self.selected_items = None; self.library_open = library_kind; } Message::HoverItem(item) => { @@ -549,6 +617,7 @@ impl<'a> Library { let Some(kind) = self.library_open else { return Action::None; }; + debug!(index, "should context"); let Some(items) = self.selected_items.as_mut() else { self.selected_items = vec![(kind, index)].into(); self.context_menu = Some(index); @@ -556,21 +625,59 @@ impl<'a> Library { }; if items.contains(&(kind, index)) { + debug!(index, "should context contained"); + self.selected_items = Some(items.to_vec()); } else { - items.push((kind, index)); + debug!(index, "should context not contained"); + self.selected_items = vec![(kind, index)].into(); } - self.selected_items = Some(items.to_vec()); self.context_menu = Some(index); } Message::AddFiles(items) => { + let mut tasks = Vec::new(); + let last_item = &items.last(); + let after_task = match last_item { + Some(ServiceItemKind::Image(image)) => { + Task::done(Message::OpenItem(Some(( + LibraryKind::Image, + self.image_library.items.len() as i32 - 1, + )))) + } + _ => Task::none(), + }; for item in items { match item { ServiceItemKind::Song(song) => { let Some(e) = self .song_library - .add_item(song) + .add_item(song.clone()) .err() else { + let task = Task::future( + self.db.acquire(), + ) + .and_then(move |db| { + Task::perform( + add_song_to_db( + song.clone(), + db, + ), + { + let song = song.clone(); + move |res| { + debug!( + ?song, + "added to db" + ); + if let Err(e) = res { + error!(?e); + } + Message::None + } + }, + ) + }); + tasks.push(task); continue; }; error!(?e); @@ -578,9 +685,34 @@ impl<'a> Library { ServiceItemKind::Video(video) => { let Some(e) = self .video_library - .add_item(video) + .add_item(video.clone()) .err() else { + let task = Task::future( + self.db.acquire(), + ) + .and_then(move |db| { + Task::perform( + add_video_to_db( + video.clone(), + db, + ), + { + let video = video.clone(); + move |res| { + debug!( + ?video, + "added to db" + ); + if let Err(e) = res { + error!(?e); + } + Message::None + } + }, + ) + }); + tasks.push(task); continue; }; error!(?e); @@ -588,9 +720,34 @@ impl<'a> Library { ServiceItemKind::Image(image) => { let Some(e) = self .image_library - .add_item(image) + .add_item(image.clone()) .err() else { + let task = Task::future( + self.db.acquire(), + ) + .and_then(move |db| { + Task::perform( + add_image_to_db( + image.clone(), + db, + ), + { + let image = image.clone(); + move |res| { + debug!( + ?image, + "added to db" + ); + if let Err(e) = res { + error!(?e); + } + Message::None + } + }, + ) + }); + tasks.push(task); continue; }; error!(?e); @@ -600,9 +757,35 @@ impl<'a> Library { ) => { let Some(e) = self .presentation_library - .add_item(presentation) + .add_item(presentation.clone()) .err() else { + let task = + Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + add_presentation_to_db( + presentation.clone(), + db, + ), + { + let presentation = + presentation.clone(); + move |res| { + debug!( + ?presentation, + "added to db" + ); + if let Err(e) = res { + error!(?e); + } + Message::None + } + }, + ) + }); + tasks.push(task); + continue; }; error!(?e); @@ -610,6 +793,9 @@ impl<'a> Library { ServiceItemKind::Content(slide) => todo!(), } } + return Action::Task( + Task::batch(tasks).chain(after_task), + ); } } Action::None @@ -814,28 +1000,8 @@ impl<'a> Library { )), )); - if let Some(context_id) = self.context_menu { - if index == context_id as usize { - let menu_items = vec![ - menu::Item::Button("Open", None, MenuMessage::Open((model.kind, index as i32))), - menu::Item::Button("Delete", None, MenuMessage::Delete) - ]; - let context_menu = context_menu( - mouse_area, - self.context_menu.map_or_else(|| None, |_| { - Some(menu::items(&self.menu_keys, - menu_items)) - }) - ); - Element::from(context_menu) - } else { - Element::from(mouse_area) - } - } else { - Element::from(mouse_area) - } - } - ) + Element::from(mouse_area) + }) .action(DndAction::Copy) .drag_icon({ let model = model.kind; @@ -874,8 +1040,9 @@ impl<'a> Library { button::icon(icon::from_name("add")) .on_press(Message::AddItem) ); + let context_menu = self.context_menu(items.into()); let library_column = - column![library_toolbar, items].spacing(3); + column![library_toolbar, context_menu].spacing(3); Container::new(library_column).padding(5) } else { Container::new(Space::new(0, 0)) @@ -993,6 +1160,34 @@ impl<'a> Library { .into() } + fn context_menu<'b>( + &self, + items: Element<'b, Message>, + ) -> Element<'b, Message> { + if self.context_menu.is_some() { + let menu_items = vec![ + menu::Item::Button("Open", None, MenuMessage::Open), + menu::Item::Button( + "Delete", + None, + MenuMessage::Delete, + ), + ]; + let context_menu = context_menu( + items, + self.context_menu.map_or_else( + || None, + |_| { + Some(menu::items(&self.menu_keys, menu_items)) + }, + ), + ); + Element::from(context_menu) + } else { + items + } + } + #[allow(clippy::unused_async)] pub async fn search_items( &self, @@ -1079,135 +1274,140 @@ impl<'a> Library { return Action::None; }; items.sort_by(|(_, index), (_, other)| index.cmp(other)); - let tasks: Vec> = - items - .iter() - .rev() - .map(|(kind, index)| match kind { - LibraryKind::Song => { - if let Some(song) = - self.song_library.get_item(*index) + let tasks: Vec> = items + .iter() + .rev() + .map(|(kind, index)| match kind { + LibraryKind::Song => { + if let Some(song) = + self.song_library.get_item(*index) + { + let song = song.clone(); + if let Err(e) = + self.song_library.remove_item(*index) { - let song = song.clone(); - if let Err(e) = - self.song_library.remove_item(*index) - { - error!(?e); - Task::none() - } else { - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - songs::remove_from_db( - db, song.id, - ), - |r| { - if let Err(e) = r { - error!(?e) - } - Message::None - }, - ) - }) - } - } else { + error!(?e); Task::none() + } else { + Task::future(self.db.acquire()).and_then( + move |db| { + Task::perform( + songs::remove_from_db( + db, song.id, + ), + |r| { + if let Err(e) = r { + error!(?e) + } + Message::None + }, + ) + }, + ) } + } else { + Task::none() } - LibraryKind::Video => { - if let Some(video) = - self.video_library.get_item(*index) + } + LibraryKind::Video => { + if let Some(video) = + self.video_library.get_item(*index) + { + let video = video.clone(); + if let Err(e) = + self.video_library.remove_item(*index) { - let video = video.clone(); - if let Err(e) = - self.video_library.remove_item(*index) - { - error!(?e); - Task::none() - } else { - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - videos::remove_from_db( - db, video.id, - ), - |r| { - if let Err(e) = r { - error!(?e) - } - Message::None - }, - ) - }) - } - } else { + error!(?e); Task::none() + } else { + Task::future(self.db.acquire()).and_then( + move |db| { + Task::perform( + videos::remove_from_db( + db, video.id, + ), + |r| { + if let Err(e) = r { + error!(?e) + } + Message::None + }, + ) + }, + ) } + } else { + Task::none() } - LibraryKind::Image => { - if let Some(image) = - self.image_library.get_item(*index) + } + LibraryKind::Image => { + if let Some(image) = + self.image_library.get_item(*index) + { + let image = image.clone(); + if let Err(e) = + self.image_library.remove_item(*index) { - let image = image.clone(); - if let Err(e) = - self.image_library.remove_item(*index) - { - error!(?e); - Task::none() - } else { - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - images::remove_from_db( - db, image.id, - ), - |r| { - if let Err(e) = r { - error!(?e) - } - Message::None - }, - ) - }) - } - } else { + error!(?e); Task::none() + } else { + debug!("let's remove {0}", image.id); + debug!("let's remove {0}", image.title); + Task::future(self.db.acquire()).and_then( + move |db| { + Task::perform( + images::remove_from_db( + db, image.id, + ), + |r| { + if let Err(e) = r { + error!(?e) + } + Message::None + }, + ) + }, + ) } + } else { + Task::none() } - LibraryKind::Presentation => { - if let Some(presentation) = - self.presentation_library.get_item(*index) + } + LibraryKind::Presentation => { + if let Some(presentation) = + self.presentation_library.get_item(*index) + { + let presentation = presentation.clone(); + if let Err(e) = self + .presentation_library + .remove_item(*index) { - let presentation = presentation.clone(); - if let Err(e) = self - .presentation_library - .remove_item(*index) - { - error!(?e); - Task::none() - } else { - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - presentations::remove_from_db( - db, - presentation.id, - ), - |r| { - if let Err(e) = r { - error!(?e) - } - Message::None - }, - ) - }) - } - } else { + error!(?e); Task::none() + } else { + Task::future(self.db.acquire()).and_then( + move |db| { + Task::perform( + presentations::remove_from_db( + db, + presentation.id, + ), + |r| { + if let Err(e) = r { + error!(?e) + } + Message::None + }, + ) + }, + ) } + } else { + Task::none() } - }) - .collect(); + } + }) + .collect(); if !tasks.is_empty() { self.selected_items = None; } diff --git a/src/ui/presentation_editor.rs b/src/ui/presentation_editor.rs index 8132e9c..0f426ea 100644 --- a/src/ui/presentation_editor.rs +++ b/src/ui/presentation_editor.rs @@ -43,6 +43,7 @@ pub enum Message { NextPage, PrevPage, None, + ChangePresentationFile(Presentation), } impl PresentationEditor { @@ -60,43 +61,7 @@ impl PresentationEditor { pub fn update(&mut self, message: Message) -> Action { match message { Message::ChangePresentation(presentation) => { - self.presentation = Some(presentation.clone()); - self.title = presentation.title.clone(); - self.document = - Document::open(&presentation.path.as_path()).ok(); - self.page_count = self - .document - .as_ref() - .and_then(|doc| doc.page_count().ok()); - warn!("changing presentation"); - self.current_slide = - self.document.as_ref().and_then(|doc| { - let page = doc.load_page(0).ok()?; - let matrix = Matrix::IDENTITY; - let colorspace = Colorspace::device_rgb(); - let Ok(pixmap) = page - .to_pixmap( - &matrix, - &colorspace, - true, - true, - ) - .into_diagnostic() - else { - error!( - "Can't turn this page into pixmap" - ); - return None; - }; - debug!(?pixmap); - Some(Handle::from_rgba( - pixmap.width(), - pixmap.height(), - pixmap.samples().to_vec(), - )) - }); - self.current_slide_index = Some(0); - return self.update(Message::Update(presentation)); + self.update_entire_presentation(&presentation); } Message::ChangeTitle(title) => { self.title = title.clone(); @@ -112,7 +77,7 @@ impl PresentationEditor { self.editing = edit; } Message::Update(presentation) => { - warn!(?presentation); + warn!(?presentation, "about to update"); return Action::UpdatePresentation(presentation); } Message::PickPresentation => { @@ -129,7 +94,9 @@ impl PresentationEditor { let mut presentation = Presentation::from(presentation); presentation.id = presentation_id; - Message::ChangePresentation(presentation) + Message::ChangePresentationFile( + presentation, + ) } else { Message::None } @@ -137,6 +104,10 @@ impl PresentationEditor { ); return Action::Task(task); } + Message::ChangePresentationFile(presentation) => { + self.update_entire_presentation(&presentation); + return self.update(Message::Update(presentation)); + } Message::None => (), Message::NextPage => { let next_index = @@ -261,6 +232,40 @@ impl PresentationEditor { pub const fn editing(&self) -> bool { self.editing } + + fn update_entire_presentation( + &mut self, + presentation: &Presentation, + ) { + self.presentation = Some(presentation.clone()); + self.title = presentation.title.clone(); + self.document = + Document::open(&presentation.path.as_path()).ok(); + self.page_count = self + .document + .as_ref() + .and_then(|doc| doc.page_count().ok()); + warn!("changing presentation"); + self.current_slide = self.document.as_ref().and_then(|doc| { + let page = doc.load_page(0).ok()?; + let matrix = Matrix::IDENTITY; + let colorspace = Colorspace::device_rgb(); + let Ok(pixmap) = page + .to_pixmap(&matrix, &colorspace, true, true) + .into_diagnostic() + else { + error!("Can't turn this page into pixmap"); + return None; + }; + debug!(?pixmap); + Some(Handle::from_rgba( + pixmap.width(), + pixmap.height(), + pixmap.samples().to_vec(), + )) + }); + self.current_slide_index = Some(0); + } } impl Default for PresentationEditor { diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 309e86c..fffa4a7 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -62,6 +62,7 @@ pub enum Message { ChangeVerseOrder(String), ChangeLyrics(text_editor::Action), ChangeBackground(Result), + UpdateSlides(Vec), PickBackground, Edit(bool), None, @@ -134,17 +135,7 @@ impl SongEditor { match message { Message::ChangeSong(song) => { self.song = Some(song.clone()); - self.song_slides = song.to_slides().ok().map(|v| { - v.into_par_iter() - .map(|mut s| { - text_svg::text_svg_generator( - &mut s, - Arc::clone(&self.font_db), - ); - s - }) - .collect::>() - }); + let song_slides = song.clone().to_slides(); self.title = song.title; if let Some(font) = song.font { self.font = font; @@ -175,7 +166,28 @@ impl SongEditor { text_editor::Content::with_text(&lyrics); } self.background_video(&song.background); - self.background = song.background; + self.background = song.background.clone(); + let font_db = Arc::clone(&self.font_db); + let task = Task::perform( + async move { + song_slides + .ok() + .map(move |v| { + v.into_par_iter() + .map(move |mut s| { + text_svg::text_svg_generator( + &mut s, + Arc::clone(&font_db), + ); + s + }) + .collect::>() + }) + .unwrap_or_default() + }, + |slides| Message::UpdateSlides(slides), + ); + return Action::Task(task); } Message::ChangeFont(font) => { self.font = font.clone(); @@ -258,6 +270,13 @@ impl SongEditor { video.set_paused(!paused); }; } + Message::UpdateSlides(slides) => { + self.song_slides = Some(slides); + } + Message::UpdateSong(song) => { + self.song = Some(song.clone()); + return Action::UpdateSong(song); + } _ => (), } Action::None @@ -431,18 +450,30 @@ order", fn update_song(&mut self, song: Song) -> Action { self.song = Some(song.clone()); - self.song_slides = song.to_slides().ok().map(|v| { - v.into_par_iter() - .map(|mut s| { - text_svg::text_svg_generator( - &mut s, - Arc::clone(&self.font_db), - ); - s - }) - .collect::>() - }); - Action::UpdateSong(song) + + let font_db = Arc::clone(&self.font_db); + let update_task = + Task::done(Message::UpdateSong(song.clone())); + let task = Task::perform( + async move { + song.to_slides() + .ok() + .map(move |v| { + v.into_par_iter() + .map(move |mut s| { + text_svg::text_svg_generator( + &mut s, + Arc::clone(&font_db), + ); + s + }) + .collect::>() + }) + .unwrap_or_default() + }, + |slides| Message::UpdateSlides(slides), + ); + Action::Task(task.chain(update_task)) } fn background_video(&mut self, background: &Option) { diff --git a/src/ui/video_editor.rs b/src/ui/video_editor.rs index c57baa9..76c4dea 100644 --- a/src/ui/video_editor.rs +++ b/src/ui/video_editor.rs @@ -15,29 +15,32 @@ use iced_video_player::{Video, VideoPlayer}; use tracing::{debug, error, warn}; use url::Url; +use crate::core::videos; + #[derive(Debug)] pub struct VideoEditor { pub video: Option