This commit is contained in:
parent
1896b9380d
commit
51c184fe37
4 changed files with 284 additions and 6 deletions
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- Add migration script here
|
||||||
|
ALTER TABLE presentations
|
||||||
|
DROP COLUMN pageCount;
|
||||||
|
|
@ -7,7 +7,7 @@ use sqlx::{
|
||||||
pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow,
|
pool::PoolConnection, prelude::FromRow, query, sqlite::SqliteRow,
|
||||||
Row, Sqlite, SqliteConnection, SqlitePool,
|
Row, Sqlite, SqliteConnection, SqlitePool,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{Background, Slide, SlideBuilder, TextAlignment};
|
use crate::{Background, Slide, SlideBuilder, TextAlignment};
|
||||||
|
|
@ -48,6 +48,39 @@ impl PartialEq for Presentation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PathBuf> for Presentation {
|
||||||
|
fn from(value: PathBuf) -> Self {
|
||||||
|
let kind = match value
|
||||||
|
.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
"pdf" => PresKind::Pdf,
|
||||||
|
"html" => PresKind::Html,
|
||||||
|
_ => PresKind::Generic,
|
||||||
|
};
|
||||||
|
let title = value
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
title,
|
||||||
|
path: value.canonicalize().unwrap_or(value),
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Path> for Presentation {
|
||||||
|
fn from(value: &Path) -> Self {
|
||||||
|
Self::from(value.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Presentation> for Value {
|
impl From<&Presentation> for Value {
|
||||||
fn from(value: &Presentation) -> Self {
|
fn from(value: &Presentation) -> Self {
|
||||||
Self::List(vec![Self::Symbol(Symbol("presentation".into()))])
|
Self::List(vec![Self::Symbol(Symbol("presentation".into()))])
|
||||||
|
|
@ -284,18 +317,69 @@ pub async fn update_presentation_in_db(
|
||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let html = presentation.kind == PresKind::Html;
|
let html = presentation.kind == PresKind::Html;
|
||||||
query!(
|
let mut db = db.detach();
|
||||||
|
let id = presentation.id.clone();
|
||||||
|
if let Err(e) =
|
||||||
|
query!("SELECT id FROM presentations where id = $1", id)
|
||||||
|
.fetch_one(&mut db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if let Ok(ids) = query!("SELECT id FROM presentations")
|
||||||
|
.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, "Presentation not found");
|
||||||
|
max += 1;
|
||||||
|
let result = query!(
|
||||||
|
r#"INSERT into presentations VALUES($1, $2, $3, $4)"#,
|
||||||
|
max,
|
||||||
|
presentation.title,
|
||||||
|
path,
|
||||||
|
html,
|
||||||
|
)
|
||||||
|
.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!(?presentation, "should be been updated");
|
||||||
|
let result = query!(
|
||||||
r#"UPDATE presentations SET title = $2, file_path = $3, html = $4 WHERE id = $1"#,
|
r#"UPDATE presentations SET title = $2, file_path = $3, html = $4 WHERE id = $1"#,
|
||||||
presentation.id,
|
presentation.id,
|
||||||
presentation.title,
|
presentation.title,
|
||||||
path,
|
path,
|
||||||
html
|
html
|
||||||
)
|
)
|
||||||
.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_presentation_from_db(
|
pub async fn get_presentation_from_db(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::core::model::LibraryKind;
|
||||||
pub mod double_ended_slider;
|
pub mod double_ended_slider;
|
||||||
pub mod image_editor;
|
pub mod image_editor;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
|
pub mod presentation_editor;
|
||||||
pub mod presenter;
|
pub mod presenter;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod slide_editor;
|
pub mod slide_editor;
|
||||||
|
|
|
||||||
190
src/ui/presentation_editor.rs
Normal file
190
src/ui/presentation_editor.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use std::{io, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::core::{
|
||||||
|
presentations::Presentation, service_items::ServiceTrait,
|
||||||
|
slide::Slide,
|
||||||
|
};
|
||||||
|
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 PresentationEditor {
|
||||||
|
pub presentation: Option<Presentation>,
|
||||||
|
slides: Option<Vec<Slide>>,
|
||||||
|
title: String,
|
||||||
|
editing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
Task(Task<Message>),
|
||||||
|
UpdatePresentation(Presentation),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
ChangePresentation(Presentation),
|
||||||
|
Update(Presentation),
|
||||||
|
ChangeTitle(String),
|
||||||
|
PickPresentation,
|
||||||
|
Edit(bool),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresentationEditor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
presentation: None,
|
||||||
|
slides: None,
|
||||||
|
title: "Death was Arrested".to_string(),
|
||||||
|
editing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update(&mut self, message: Message) -> Action {
|
||||||
|
match message {
|
||||||
|
Message::ChangePresentation(presentation) => {
|
||||||
|
self.presentation = Some(presentation.clone());
|
||||||
|
self.title = presentation.title.clone();
|
||||||
|
let Ok(slides) = presentation.to_slides() else {
|
||||||
|
return Action::None;
|
||||||
|
};
|
||||||
|
self.slides = Some(slides);
|
||||||
|
return self.update(Message::Update(presentation));
|
||||||
|
}
|
||||||
|
Message::ChangeTitle(title) => {
|
||||||
|
self.title = title.clone();
|
||||||
|
if let Some(presentation) = &self.presentation {
|
||||||
|
let mut presentation = presentation.clone();
|
||||||
|
presentation.title = title;
|
||||||
|
return self
|
||||||
|
.update(Message::Update(presentation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Edit(edit) => {
|
||||||
|
debug!(edit);
|
||||||
|
self.editing = edit;
|
||||||
|
}
|
||||||
|
Message::Update(presentation) => {
|
||||||
|
warn!(?presentation);
|
||||||
|
return Action::UpdatePresentation(presentation);
|
||||||
|
}
|
||||||
|
Message::PickPresentation => {
|
||||||
|
let presentation_id = self
|
||||||
|
.presentation
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.clone();
|
||||||
|
let task = Task::perform(
|
||||||
|
pick_presentation(),
|
||||||
|
move |presentation_result| {
|
||||||
|
if let Ok(presentation) = presentation_result
|
||||||
|
{
|
||||||
|
let mut presentation =
|
||||||
|
Presentation::from(presentation);
|
||||||
|
presentation.id = presentation_id;
|
||||||
|
Message::ChangePresentation(presentation)
|
||||||
|
} else {
|
||||||
|
Message::None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return Action::Task(task);
|
||||||
|
}
|
||||||
|
Message::None => (),
|
||||||
|
}
|
||||||
|
Action::None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> Element<Message> {
|
||||||
|
let container = if let Some(slides) = &self.slides {
|
||||||
|
todo!();
|
||||||
|
// container(presentation)
|
||||||
|
} 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 presentation_selector = button::icon(
|
||||||
|
icon::from_name("folder-presentations-symbolic").scale(2),
|
||||||
|
)
|
||||||
|
.label("Presentation")
|
||||||
|
.tooltip("Select a presentation")
|
||||||
|
.on_press(Message::PickPresentation)
|
||||||
|
.padding(10);
|
||||||
|
|
||||||
|
row![
|
||||||
|
text::body("Title:"),
|
||||||
|
title_box,
|
||||||
|
horizontal_space(),
|
||||||
|
presentation_selector
|
||||||
|
]
|
||||||
|
.align_y(Vertical::Center)
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn editing(&self) -> bool {
|
||||||
|
self.editing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PresentationEditor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn pick_presentation() -> Result<PathBuf, PresentationError> {
|
||||||
|
let dialog = Dialog::new().title("Choose a presentation...");
|
||||||
|
let bg_filter = FileFilter::new("Presentations")
|
||||||
|
.extension("pdf")
|
||||||
|
.extension("html");
|
||||||
|
dialog
|
||||||
|
.filter(bg_filter)
|
||||||
|
.directory(dirs::home_dir().expect("oops"))
|
||||||
|
.open_file()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(?e);
|
||||||
|
PresentationError::DialogClosed
|
||||||
|
})
|
||||||
|
.map(|file| file.url().to_file_path().unwrap())
|
||||||
|
// rfd::AsyncFileDialog::new()
|
||||||
|
// .set_title("Choose a background...")
|
||||||
|
// .add_filter(
|
||||||
|
// "Presentations and Presentations",
|
||||||
|
// &["png", "jpeg", "mp4", "webm", "mkv", "jpg", "mpeg"],
|
||||||
|
// )
|
||||||
|
// .set_directory(dirs::home_dir().unwrap())
|
||||||
|
// .pick_file()
|
||||||
|
// .await
|
||||||
|
// .ok_or(PresentationError::BackgroundDialogClosed)
|
||||||
|
// .map(|file| file.path().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PresentationError {
|
||||||
|
DialogClosed,
|
||||||
|
IOError(io::ErrorKind),
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue