fixing some database and adding issues

This commit is contained in:
Chris Cochrun 2025-10-04 13:59:36 -05:00
parent 7f30b4395f
commit 375cea2408
11 changed files with 709 additions and 392 deletions

View file

@ -201,6 +201,27 @@ pub async fn remove_from_db(
.map(|_| ())
}
pub async fn add_image_to_db(
image: Image,
db: PoolConnection<Sqlite>,
) -> 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<Sqlite>,
@ -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"#,

View file

@ -310,7 +310,6 @@ pub async fn remove_from_db(
pub async fn add_presentation_to_db(
presentation: Presentation,
db: PoolConnection<Sqlite>,
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(())
}

View file

@ -420,6 +420,51 @@ pub async fn remove_from_db(
.map(|_| ())
}
pub async fn add_song_to_db(
song: Song,
db: PoolConnection<Sqlite>,
) -> 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::<String>()
} 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<Sqlite>,

View file

@ -236,6 +236,30 @@ pub async fn remove_from_db(
.map(|_| ())
}
pub async fn add_video_to_db(
video: Video,
db: PoolConnection<Sqlite>,
) -> 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<Sqlite>,
@ -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"#,

View file

@ -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<usize>,
current_item: (usize, usize),
hovered_item: Option<usize>,
hovered_dnd: Option<usize>,
presentation_open: bool,
cli_mode: bool,
library: Option<Library>,
@ -166,6 +171,7 @@ enum Message {
SelectServiceItem(usize),
AddSelectServiceItem(usize),
HoveredServiceItem(Option<usize>),
HoveredServiceDrop(Option<usize>),
AddServiceItem(usize, KindWrapper),
AddServiceItemsFiles(usize, Vec<ServiceItem>),
RemoveServiceItem(usize),
@ -183,8 +189,9 @@ enum Message {
New,
Open,
OpenFile(PathBuf),
Save(Option<PathBuf>),
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
}
Message::SaveAs => {
debug!("saving as a file");
Task::none()
Err(e) => {
error!(?e, "There was a problem saving");
cosmic::Action::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<PathBuf> {
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"))
}
}
})?
}

View file

@ -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 {

View file

@ -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<LibraryKind>),
OpenLibrary(Option<LibraryKind>),
HoverItem(Option<(LibraryKind, i32)>),
@ -180,27 +179,61 @@ 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((
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 {
@ -218,7 +251,6 @@ impl<'a> Library {
add_presentation_to_db(
presentation.to_owned(),
db,
index as i32,
),
move |res| {
debug!(
@ -244,53 +276,89 @@ impl<'a> Library {
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((
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)) {
} else {
items.push((kind, index));
}
debug!(index, "should context contained");
self.selected_items = Some(items.to_vec());
} else {
debug!(index, "should context not contained");
self.selected_items = vec![(kind, index)].into();
}
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(mouse_area)
})
);
Element::from(context_menu)
} else {
Element::from(mouse_area)
}
} else {
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,8 +1274,7 @@ impl<'a> Library {
return Action::None;
};
items.sort_by(|(_, index), (_, other)| index.cmp(other));
let tasks: Vec<Task<Message>> =
items
let tasks: Vec<Task<Message>> = items
.iter()
.rev()
.map(|(kind, index)| match kind {
@ -1095,8 +1289,8 @@ impl<'a> Library {
error!(?e);
Task::none()
} else {
Task::future(self.db.acquire())
.and_then(move |db| {
Task::future(self.db.acquire()).and_then(
move |db| {
Task::perform(
songs::remove_from_db(
db, song.id,
@ -1108,7 +1302,8 @@ impl<'a> Library {
Message::None
},
)
})
},
)
}
} else {
Task::none()
@ -1125,8 +1320,8 @@ impl<'a> Library {
error!(?e);
Task::none()
} else {
Task::future(self.db.acquire())
.and_then(move |db| {
Task::future(self.db.acquire()).and_then(
move |db| {
Task::perform(
videos::remove_from_db(
db, video.id,
@ -1138,7 +1333,8 @@ impl<'a> Library {
Message::None
},
)
})
},
)
}
} else {
Task::none()
@ -1155,8 +1351,10 @@ impl<'a> Library {
error!(?e);
Task::none()
} else {
Task::future(self.db.acquire())
.and_then(move |db| {
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,
@ -1168,7 +1366,8 @@ impl<'a> Library {
Message::None
},
)
})
},
)
}
} else {
Task::none()
@ -1186,8 +1385,8 @@ impl<'a> Library {
error!(?e);
Task::none()
} else {
Task::future(self.db.acquire())
.and_then(move |db| {
Task::future(self.db.acquire()).and_then(
move |db| {
Task::perform(
presentations::remove_from_db(
db,
@ -1200,7 +1399,8 @@ impl<'a> Library {
Message::None
},
)
})
},
)
}
} else {
Task::none()

View file

@ -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 {

View file

@ -62,6 +62,7 @@ pub enum Message {
ChangeVerseOrder(String),
ChangeLyrics(text_editor::Action),
ChangeBackground(Result<PathBuf, SongError>),
UpdateSlides(Vec<Slide>),
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::<Vec<Slide>>()
});
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::<Vec<Slide>>()
})
.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| {
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(|mut s| {
.map(move |mut s| {
text_svg::text_svg_generator(
&mut s,
Arc::clone(&self.font_db),
Arc::clone(&font_db),
);
s
})
.collect::<Vec<Slide>>()
});
Action::UpdateSong(song)
})
.unwrap_or_default()
},
|slides| Message::UpdateSlides(slides),
);
Action::Task(task.chain(update_task))
}
fn background_video(&mut self, background: &Option<Background>) {

View file

@ -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<Video>,
core_video: Option<crate::core::videos::Video>,
core_video: Option<videos::Video>,
title: String,
editing: bool,
}
pub enum Action {
Task(Task<Message>),
UpdateVideo(crate::core::videos::Video),
UpdateVideo(videos::Video),
None,
}
#[derive(Debug, Clone)]
pub enum Message {
ChangeVideo(crate::core::videos::Video),
Update(crate::core::videos::Video),
ChangeVideo(videos::Video),
Update(videos::Video),
ChangeTitle(String),
PickVideo,
Edit(bool),
None,
PauseVideo,
UpdateVideoFile(videos::Video),
}
impl VideoEditor {
@ -52,20 +55,7 @@ impl VideoEditor {
pub fn update(&mut self, message: Message) -> Action {
match message {
Message::ChangeVideo(video) => {
let Ok(mut player_video) = Url::from_file_path(
video.path.clone(),
)
.map(|url| Video::new(&url).expect("Should be here")) else {
self.video = None;
self.title = video.title.clone();
self.core_video = Some(video);
return Action::None;
};
player_video.set_paused(true);
self.video = Some(player_video);
self.title = video.title.clone();
self.core_video = Some(video.clone());
return self.update(Message::Update(video));
self.update_entire_video(&video);
}
Message::ChangeTitle(title) => {
self.title = title.clone();
@ -100,11 +90,9 @@ impl VideoEditor {
move |video_result| {
if let Ok(video) = video_result {
let mut video =
crate::core::videos::Video::from(
video,
);
videos::Video::from(video);
video.id = video_id;
Message::ChangeVideo(video)
Message::UpdateVideoFile(video)
} else {
Message::None
}
@ -112,6 +100,10 @@ impl VideoEditor {
);
return Action::Task(task);
}
Message::UpdateVideoFile(video) => {
self.update_entire_video(&video);
return Action::UpdateVideo(video);
}
Message::None => (),
}
Action::None
@ -185,6 +177,22 @@ impl VideoEditor {
pub const fn editing(&self) -> bool {
self.editing
}
fn update_entire_video(&mut self, video: &videos::Video) {
let Ok(mut player_video) =
Url::from_file_path(video.path.clone())
.map(|url| Video::new(&url).expect("Should be here"))
else {
self.video = None;
self.title = video.title.clone();
self.core_video = Some(video.clone());
return;
};
player_video.set_paused(true);
self.video = Some(player_video);
self.title = video.title.clone();
self.core_video = Some(video.clone());
}
}
impl Default for VideoEditor {

View file

@ -3,7 +3,7 @@
* TODO Add OBS integration
This will be based on each slide having the ability to activate an OBS scene when it is active.
* TODO Move text_generation function to be asynchronous so that UI doesn't lock up during song editing.
* DONE Move text_generation function to be asynchronous so that UI doesn't lock up during song editing.
* DONE Build a presentation editor
* TODO [#A] Need to fix tests now that the basic app is working