From 89e22993adca1769be4a26bf0e99f9d387ab45ed Mon Sep 17 00:00:00 2001 From: Simon Repp Date: Wed, 13 Mar 2024 11:14:02 +0100 Subject: [PATCH] Implement release date for videos --- Cargo.lock | 68 +++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/editor/endpoints/video.rs | 48 ++++++++++--------------- src/editor/widgets.rs | 21 +++++++++++ src/video.rs | 23 ++++++++++++ 5 files changed, 132 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 828aaab..3912867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.11" @@ -457,6 +472,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", +] + [[package]] name = "clap" version = "4.5.1" @@ -526,6 +555,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -947,6 +982,7 @@ dependencies = [ "actix-web", "base64 0.22.0", "bincode", + "chrono", "clap", "enolib", "form_urlencoded", @@ -963,6 +999,29 @@ dependencies = [ "url", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2004,6 +2063,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index 9d3621d..fada331 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ actix-multipart = "0.6.1" actix-web = "4.5.1" base64 = "0.22.0" bincode = "1.3.3" +chrono = "0.4.35" clap = { features = ["derive"], version = "4.5.1" } enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.4.2" } form_urlencoded = "1.2.1" diff --git a/src/editor/endpoints/video.rs b/src/editor/endpoints/video.rs index dfe2f09..cd5b610 100644 --- a/src/editor/endpoints/video.rs +++ b/src/editor/endpoints/video.rs @@ -7,6 +7,7 @@ use actix_multipart::Multipart; use actix_web::{Either, Error, HttpResponse}; use actix_web::http::header::ContentType; use actix_web::web::{self, Data, Form, Path as ActixPath, Redirect}; +use chrono::NaiveDate; use futures_util::TryStreamExt as _; use indoc::formatdoc; use serde_derive::Deserialize; @@ -31,6 +32,7 @@ use crate::{ use crate::editor::endpoints; use crate::editor::widgets::{ form_checkbox, + form_field_date, form_field, form_field_pattern, form_textarea, @@ -52,6 +54,7 @@ pub enum VideoFormFeedback<'a> { Site(endpoints::site::SiteFormFeedback), Update { description: &'a str, + release_date: Field, title: Field, unlisted: bool } @@ -65,6 +68,7 @@ pub struct VideoMoveForm { #[derive(Deserialize)] pub struct VideoUpdateForm { description: String, + release_date: String, title: String, unlisted: Option } @@ -217,32 +221,6 @@ pub fn edit_page( "#) }; - // let in_playlists = { - // let playlists = context.collection.get_playlists_for_video(&video); - - // if playlists.is_empty() { - // String::new() - // } else { - // let list = playlists - // .into_iter() - // .map(|playlist| { - // let id = &playlist.id; - // let title = &playlist.title; - - // format!(r#"{title}"#) - // }) - // .collect::>() - // .join(", "); - - // formatdoc!(" - //

- //
- // In playlists: {list} - //
" - // ) - // } - // }; - let poster = video.poster .as_ref() .map(|poster| { @@ -261,19 +239,27 @@ pub fn edit_page( let ( feedback_update, input_description, + input_release_date, input_title, input_unlisted, - ) = if let Some(VideoFormFeedback::Update { description, title, unlisted }) = &form_feedback { + ) = if let Some(VideoFormFeedback::Update { description, title, release_date, unlisted }) = &form_feedback { ( r#""#, form_textarea("description", "Description", "Description", Some(description)), + form_field_date(release_date, "release_date", "Release Date"), form_field(title, "title", "Title", "Title"), form_checkbox(*unlisted, "unlisted", "Unlisted") ) } else { + let release_date = video.release_date + .as_ref() + .map(|date| date.to_string()) + .unwrap_or(String::new()); + ( "", form_textarea("description", "Description", "Description", video.description.as_deref()), + form_field_date(&Field::valid(release_date), "release_date", "Release Date"), form_field(&Field::valid(video.title.as_deref().unwrap_or("")), "title", "Title", "Title"), form_checkbox(video.unlisted, "unlisted", "Unlisted") ) @@ -330,6 +316,7 @@ pub fn edit_page(
{input_title} {input_description} + {input_release_date} {input_unlisted}
@@ -459,13 +446,15 @@ pub async fn update( path: ActixPath ) -> Either { let description_trimmed = form.description.trim(); + let release_date = NaiveDate::parse_from_str(&form.release_date, "%Y-%m-%d").map_err(|err| err.to_string()); let title_trimmed = form.title.trim(); let unlisted = form.unlisted.is_some(); - // TODO: We're keeping this around for now because we might need to re-introduce some actual validation? (description/title length e.g.?) - if true { + + if release_date.is_ok() { let mutation = |video: &mut Video| { video.description = if description_trimmed.is_empty() { None } else { Some(description_trimmed.to_owned()) }; + video.release_date = release_date.ok(); video.title = if title_trimmed.is_empty() { None } else { Some(title_trimmed.to_owned()) }; video.unlisted = unlisted; video.write_manifest(&context); @@ -490,6 +479,7 @@ pub async fn update( let form_feedback = VideoFormFeedback::Update { description: description_trimmed, + release_date: Field::validated(&form.release_date, release_date.err()), title: Field::valid(title_trimmed), unlisted }; diff --git a/src/editor/widgets.rs b/src/editor/widgets.rs index fc44e48..60eda45 100644 --- a/src/editor/widgets.rs +++ b/src/editor/widgets.rs @@ -222,6 +222,27 @@ pub fn form_field( "#) } + +pub fn form_field_date( + field: &Field, + key: &str, + label: &str +) -> String { + let error = field.error + .as_deref() + .map(|message| format!(r#"{message}"#)) + .unwrap_or(String::new()); + let value = &field.value; + + formatdoc!(r#" +
+ + + {error} +
+ "#) +} + pub fn form_field_pattern( field: &Field, key: &str, diff --git a/src/video.rs b/src/video.rs index b18b060..d8e14b7 100644 --- a/src/video.rs +++ b/src/video.rs @@ -2,6 +2,7 @@ use std::fs; use std::path::Path; use std::sync::Arc; +use chrono::NaiveDate; use enolib::SectionElement; use serde_derive::{Deserialize, Serialize}; @@ -24,6 +25,7 @@ pub struct Video { /// Relative to site dir, always without leading/trailing slashes pub path: String, pub poster: Option, + pub release_date: Option, pub title: Option, /// Unlisted videos are not linked from the site anywhere, /// one needs to know the link to access them. @@ -119,6 +121,22 @@ impl Video { _ => () } + match document.optional_field("release_date") { + Ok(Some(field)) => { + match field.required_value::() { + Ok(value) => match NaiveDate::parse_from_str(&value, "%Y-%m-%d") { + Ok(date) => { + self.release_date = Some(date); + } + Err(err) => self.errors.push(format!("Error in {} ({})", manifest_path.display(), err)) + } + Err(err) => self.errors.push(format!("Error in {}:{} ({})", manifest_path.display(), err.line, err)) + } + } + Err(err) => self.errors.push(format!("Error in {}:{} ({})", manifest_path.display(), err.line, err)), + _ => () + } + match document.optional_field("title") { Ok(Some(field)) => { match field.required_value() { @@ -182,6 +200,7 @@ impl Video { errors: Vec::new(), path, poster: None, + release_date: None, title: None, unlisted: false, versions: Vec::new() @@ -311,6 +330,10 @@ impl Video { eno.push_str(&format!("poster: {}\n", poster.file_name)); } + if let Some(release_date) = &self.release_date { + eno.push_str(&format!("release_date: {}\n", release_date)); + } + if let Some(title) = &self.title { eno.push_str(&format!("title: {}\n", title)); }