diff --git a/src/core/images.rs b/src/core/images.rs index 9a08a0b..9656f4e 100644 --- a/src/core/images.rs +++ b/src/core/images.rs @@ -14,7 +14,7 @@ use sqlx::{ SqlitePool, }; use std::path::{Path, PathBuf}; -use tracing::error; +use tracing::{debug, error}; #[derive( Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, @@ -210,17 +210,65 @@ pub async fn update_image_in_db( .to_str() .map(std::string::ToString::to_string) .unwrap_or_default(); - query!( + let mut db = db.detach(); + let id = image.id.clone(); + 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"#, image.id, image.title, path, ) - .execute(&mut db.detach()) - .await - .into_diagnostic()?; + .execute(&mut db) + .await.into_diagnostic(); - Ok(()) + match result { + Ok(_) => { + debug!("should have been updated"); + Ok(()) + } + Err(e) => { + error! {?e}; + Err(e) + } + } } pub async fn get_image_from_db( diff --git a/src/main.rs b/src/main.rs index 088eb1d..bc0f3d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use core::service_items::ServiceItem; use core::slide::{ Background, BackgroundKind, Slide, SlideBuilder, TextAlignment, }; -use core::songs::Song; use cosmic::app::context_drawer::ContextDrawer; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::alignment::Vertical; @@ -49,6 +48,7 @@ use ui::song_editor::{self, SongEditor}; use ui::EditorMode; use crate::core::kinds::ServiceItemKind; +use crate::ui::image_editor::{self, ImageEditor}; use crate::ui::text_svg::{self}; use crate::ui::video_editor::{self, VideoEditor}; use crate::ui::widgets::draggable; @@ -126,6 +126,7 @@ struct App { editor_mode: Option, song_editor: SongEditor, video_editor: VideoEditor, + image_editor: ImageEditor, searching: bool, search_query: String, search_results: Vec, @@ -142,6 +143,7 @@ enum Message { Library(library::Message), SongEditor(song_editor::Message), VideoEditor(video_editor::Message), + ImageEditor(image_editor::Message), File(PathBuf), OpenWindow, CloseWindow(Option), @@ -328,6 +330,7 @@ impl cosmic::Application for App { editor_mode: None, song_editor, video_editor: VideoEditor::new(), + image_editor: ImageEditor::new(), searching: false, search_results: vec![], search_query: String::new(), @@ -767,6 +770,27 @@ impl cosmic::Application for App { song_editor::Action::None => Task::none(), } } + Message::ImageEditor(message) => { + match self.image_editor.update(message) { + image_editor::Action::Task(task) => { + task.map(|m| { + cosmic::Action::App(Message::ImageEditor( + m, + )) + }) + } + image_editor::Action::UpdateImage(image) => { + if let Some(_) = &mut self.library { + self.update(Message::Library( + library::Message::UpdateImage(image), + )) + } else { + Task::none() + } + } + image_editor::Action::None => Task::none(), + } + } Message::VideoEditor(message) => { match self.video_editor.update(message) { video_editor::Action::Task(task) => { @@ -975,7 +999,14 @@ impl cosmic::Application for App { let video = lib_video.to_owned(); return self.update(Message::VideoEditor(video_editor::Message::ChangeVideo(video))); }, - core::model::LibraryKind::Image => todo!(), + core::model::LibraryKind::Image => { + let Some(lib_image) = library.get_image(index) else { + return Task::none(); + }; + self.editor_mode = Some(kind.into()); + let image = lib_image.to_owned(); + return self.update(Message::ImageEditor(image_editor::Message::ChangeImage(image))); + }, core::model::LibraryKind::Presentation => todo!(), } } @@ -1314,7 +1345,9 @@ impl cosmic::Application for App { EditorMode::Song => { self.song_editor.view().map(Message::SongEditor) } - EditorMode::Image => todo!(), + EditorMode::Image => { + self.image_editor.view().map(Message::ImageEditor) + } EditorMode::Video => { self.video_editor.view().map(Message::VideoEditor) } diff --git a/src/ui/image_editor.rs b/src/ui/image_editor.rs new file mode 100644 index 0000000..ba3eb03 --- /dev/null +++ b/src/ui/image_editor.rs @@ -0,0 +1,182 @@ +use std::{io, path::PathBuf}; + +use crate::core::images::Image; +use cosmic::{ + dialog::file_chooser::{open::Dialog, FileFilter}, + iced::{alignment::Vertical, Length}, + iced_widget::{column, row}, + theme, + widget::{ + self, button, container, horizontal_space, icon, text, + text_input, Space, + }, + Element, Task, +}; +use tracing::{debug, error, warn}; + +#[derive(Debug)] +pub struct ImageEditor { + pub image: Option, + title: String, + editing: bool, +} + +pub enum Action { + Task(Task), + UpdateImage(Image), + None, +} + +#[derive(Debug, Clone)] +pub enum Message { + ChangeImage(Image), + Update(Image), + ChangeTitle(String), + PickImage, + Edit(bool), + None, +} + +impl ImageEditor { + pub fn new() -> Self { + Self { + image: None, + title: "Death was Arrested".to_string(), + editing: false, + } + } + 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)); + } + Message::ChangeTitle(title) => { + self.title = title.clone(); + if let Some(image) = &self.image { + let mut image = image.clone(); + image.title = title; + return self.update(Message::Update(image)); + } + } + Message::Edit(edit) => { + debug!(edit); + self.editing = edit; + } + Message::Update(image) => { + warn!(?image); + return Action::UpdateImage(image); + } + Message::PickImage => { + let image_id = self + .image + .as_ref() + .map(|v| v.id) + .unwrap_or_default() + .clone(); + let task = Task::perform( + pick_image(), + move |image_result| { + if let Ok(image) = image_result { + let mut image = Image::from(image); + image.id = image_id; + Message::ChangeImage(image) + } else { + Message::None + } + }, + ); + return Action::Task(task); + } + Message::None => (), + } + Action::None + } + + pub fn view(&self) -> Element { + let container = if let Some(pic) = &self.image { + let image = widget::image(pic.path.clone()); + container(image) + } else { + container(Space::new(0, 0)) + }; + let column = column![ + self.toolbar(), + container.center_x(Length::FillPortion(2)) + ] + .spacing(theme::active().cosmic().space_l()); + column.into() + } + + fn toolbar(&self) -> Element { + let title_box = text_input("Title...", &self.title) + .on_input(Message::ChangeTitle); + + let image_selector = button::icon( + icon::from_name("folder-images-symbolic").scale(2), + ) + .label("Image") + .tooltip("Select a image") + .on_press(Message::PickImage) + .padding(10); + + row![ + text::body("Title:"), + title_box, + horizontal_space(), + image_selector + ] + .align_y(Vertical::Center) + .spacing(10) + .into() + } + + pub const fn editing(&self) -> bool { + self.editing + } +} + +impl Default for ImageEditor { + fn default() -> Self { + Self::new() + } +} + +async fn pick_image() -> Result { + let dialog = Dialog::new().title("Choose a image..."); + let bg_filter = FileFilter::new("Images") + .extension("png") + .extension("jpeg") + .extension("gif") + .extension("heic") + .extension("webp") + .extension("jpg"); + dialog + .filter(bg_filter) + .directory(dirs::home_dir().expect("oops")) + .open_file() + .await + .map_err(|e| { + error!(?e); + ImageError::DialogClosed + }) + .map(|file| file.url().to_file_path().unwrap()) + // rfd::AsyncFileDialog::new() + // .set_title("Choose a background...") + // .add_filter( + // "Images and Images", + // &["png", "jpeg", "mp4", "webm", "mkv", "jpg", "mpeg"], + // ) + // .set_directory(dirs::home_dir().unwrap()) + // .pick_file() + // .await + // .ok_or(ImageError::BackgroundDialogClosed) + // .map(|file| file.path().to_owned()) +} + +#[derive(Debug, Clone)] +pub enum ImageError { + DialogClosed, + IOError(io::ErrorKind), +} diff --git a/src/ui/library.rs b/src/ui/library.rs index 5c589df..e8168d4 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -439,27 +439,28 @@ impl<'a> Library { return Action::None; }; - match self + if self .image_library .update_item(image.clone(), index) + .is_err() { - Ok(()) => { - return Action::Task( - Task::future(self.db.acquire()).and_then( - move |conn| { - Task::perform( - update_image_in_db( - image.clone(), - conn, - ), - |_| Message::ImageChanged, - ) - }, - ), - ); - } - Err(_) => todo!(), - } + error!("Couldn't update image in model"); + return Action::None; + }; + + return Action::Task( + Task::future(self.db.acquire()).and_then( + move |conn| { + Task::perform( + update_image_in_db( + image.to_owned(), + conn, + ), + |_| Message::ImageChanged, + ) + }, + ), + ); } Message::ImageChanged => (), Message::UpdateVideo(video) => { @@ -960,6 +961,10 @@ impl<'a> Library { pub fn get_video(&self, index: i32) -> Option<&Video> { self.video_library.get_item(index) } + + pub fn get_image(&self, index: i32) -> Option<&Image> { + self.image_library.get_item(index) + } } async fn add_images() -> Option> { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d73fab9..e69efc6 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,7 @@ use crate::core::model::LibraryKind; pub mod double_ended_slider; +pub mod image_editor; pub mod library; pub mod presenter; pub mod service;