This commit is contained in:
parent
b26fdd980b
commit
1896b9380d
5 changed files with 296 additions and 27 deletions
|
|
@ -14,7 +14,7 @@ use sqlx::{
|
||||||
SqlitePool,
|
SqlitePool,
|
||||||
};
|
};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tracing::error;
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
|
||||||
|
|
@ -210,18 +210,66 @@ pub async fn update_image_in_db(
|
||||||
.to_str()
|
.to_str()
|
||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
.unwrap_or_default();
|
.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"#,
|
r#"UPDATE images SET title = $2, file_path = $3 WHERE id = $1"#,
|
||||||
image.id,
|
image.id,
|
||||||
image.title,
|
image.title,
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
.execute(&mut db.detach())
|
.execute(&mut db)
|
||||||
.await
|
.await.into_diagnostic();
|
||||||
.into_diagnostic()?;
|
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("should have been updated");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error! {?e};
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_image_from_db(
|
pub async fn get_image_from_db(
|
||||||
database_id: i32,
|
database_id: i32,
|
||||||
|
|
|
||||||
39
src/main.rs
39
src/main.rs
|
|
@ -3,7 +3,6 @@ use core::service_items::ServiceItem;
|
||||||
use core::slide::{
|
use core::slide::{
|
||||||
Background, BackgroundKind, Slide, SlideBuilder, TextAlignment,
|
Background, BackgroundKind, Slide, SlideBuilder, TextAlignment,
|
||||||
};
|
};
|
||||||
use core::songs::Song;
|
|
||||||
use cosmic::app::context_drawer::ContextDrawer;
|
use cosmic::app::context_drawer::ContextDrawer;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced::alignment::Vertical;
|
use cosmic::iced::alignment::Vertical;
|
||||||
|
|
@ -49,6 +48,7 @@ use ui::song_editor::{self, SongEditor};
|
||||||
use ui::EditorMode;
|
use ui::EditorMode;
|
||||||
|
|
||||||
use crate::core::kinds::ServiceItemKind;
|
use crate::core::kinds::ServiceItemKind;
|
||||||
|
use crate::ui::image_editor::{self, ImageEditor};
|
||||||
use crate::ui::text_svg::{self};
|
use crate::ui::text_svg::{self};
|
||||||
use crate::ui::video_editor::{self, VideoEditor};
|
use crate::ui::video_editor::{self, VideoEditor};
|
||||||
use crate::ui::widgets::draggable;
|
use crate::ui::widgets::draggable;
|
||||||
|
|
@ -126,6 +126,7 @@ struct App {
|
||||||
editor_mode: Option<EditorMode>,
|
editor_mode: Option<EditorMode>,
|
||||||
song_editor: SongEditor,
|
song_editor: SongEditor,
|
||||||
video_editor: VideoEditor,
|
video_editor: VideoEditor,
|
||||||
|
image_editor: ImageEditor,
|
||||||
searching: bool,
|
searching: bool,
|
||||||
search_query: String,
|
search_query: String,
|
||||||
search_results: Vec<ServiceItem>,
|
search_results: Vec<ServiceItem>,
|
||||||
|
|
@ -142,6 +143,7 @@ enum Message {
|
||||||
Library(library::Message),
|
Library(library::Message),
|
||||||
SongEditor(song_editor::Message),
|
SongEditor(song_editor::Message),
|
||||||
VideoEditor(video_editor::Message),
|
VideoEditor(video_editor::Message),
|
||||||
|
ImageEditor(image_editor::Message),
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
OpenWindow,
|
OpenWindow,
|
||||||
CloseWindow(Option<window::Id>),
|
CloseWindow(Option<window::Id>),
|
||||||
|
|
@ -328,6 +330,7 @@ impl cosmic::Application for App {
|
||||||
editor_mode: None,
|
editor_mode: None,
|
||||||
song_editor,
|
song_editor,
|
||||||
video_editor: VideoEditor::new(),
|
video_editor: VideoEditor::new(),
|
||||||
|
image_editor: ImageEditor::new(),
|
||||||
searching: false,
|
searching: false,
|
||||||
search_results: vec![],
|
search_results: vec![],
|
||||||
search_query: String::new(),
|
search_query: String::new(),
|
||||||
|
|
@ -767,6 +770,27 @@ impl cosmic::Application for App {
|
||||||
song_editor::Action::None => Task::none(),
|
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) => {
|
Message::VideoEditor(message) => {
|
||||||
match self.video_editor.update(message) {
|
match self.video_editor.update(message) {
|
||||||
video_editor::Action::Task(task) => {
|
video_editor::Action::Task(task) => {
|
||||||
|
|
@ -975,7 +999,14 @@ impl cosmic::Application for App {
|
||||||
let video = lib_video.to_owned();
|
let video = lib_video.to_owned();
|
||||||
return self.update(Message::VideoEditor(video_editor::Message::ChangeVideo(video)));
|
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!(),
|
core::model::LibraryKind::Presentation => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1314,7 +1345,9 @@ impl cosmic::Application for App {
|
||||||
EditorMode::Song => {
|
EditorMode::Song => {
|
||||||
self.song_editor.view().map(Message::SongEditor)
|
self.song_editor.view().map(Message::SongEditor)
|
||||||
}
|
}
|
||||||
EditorMode::Image => todo!(),
|
EditorMode::Image => {
|
||||||
|
self.image_editor.view().map(Message::ImageEditor)
|
||||||
|
}
|
||||||
EditorMode::Video => {
|
EditorMode::Video => {
|
||||||
self.video_editor.view().map(Message::VideoEditor)
|
self.video_editor.view().map(Message::VideoEditor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
182
src/ui/image_editor.rs
Normal file
182
src/ui/image_editor.rs
Normal file
|
|
@ -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<Image>,
|
||||||
|
title: String,
|
||||||
|
editing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
Task(Task<Message>),
|
||||||
|
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<Message> {
|
||||||
|
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<Message> {
|
||||||
|
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<PathBuf, ImageError> {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
@ -439,17 +439,21 @@ impl<'a> Library {
|
||||||
return Action::None;
|
return Action::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self
|
if self
|
||||||
.image_library
|
.image_library
|
||||||
.update_item(image.clone(), index)
|
.update_item(image.clone(), index)
|
||||||
|
.is_err()
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
error!("Couldn't update image in model");
|
||||||
|
return Action::None;
|
||||||
|
};
|
||||||
|
|
||||||
return Action::Task(
|
return Action::Task(
|
||||||
Task::future(self.db.acquire()).and_then(
|
Task::future(self.db.acquire()).and_then(
|
||||||
move |conn| {
|
move |conn| {
|
||||||
Task::perform(
|
Task::perform(
|
||||||
update_image_in_db(
|
update_image_in_db(
|
||||||
image.clone(),
|
image.to_owned(),
|
||||||
conn,
|
conn,
|
||||||
),
|
),
|
||||||
|_| Message::ImageChanged,
|
|_| Message::ImageChanged,
|
||||||
|
|
@ -458,9 +462,6 @@ impl<'a> Library {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::ImageChanged => (),
|
Message::ImageChanged => (),
|
||||||
Message::UpdateVideo(video) => {
|
Message::UpdateVideo(video) => {
|
||||||
let Some((kind, index)) = self.editing_item else {
|
let Some((kind, index)) = self.editing_item else {
|
||||||
|
|
@ -960,6 +961,10 @@ impl<'a> Library {
|
||||||
pub fn get_video(&self, index: i32) -> Option<&Video> {
|
pub fn get_video(&self, index: i32) -> Option<&Video> {
|
||||||
self.video_library.get_item(index)
|
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<Vec<Image>> {
|
async fn add_images() -> Option<Vec<Image>> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::core::model::LibraryKind;
|
use crate::core::model::LibraryKind;
|
||||||
|
|
||||||
pub mod double_ended_slider;
|
pub mod double_ended_slider;
|
||||||
|
pub mod image_editor;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod presenter;
|
pub mod presenter;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue