Implement release date for videos

This commit is contained in:
Simon Repp
2024-03-13 11:14:02 +01:00
parent af250f928e
commit 89e22993ad
5 changed files with 132 additions and 29 deletions
Generated
+68
View File
@@ -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"
+1
View File
@@ -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"
+19 -29
View File
@@ -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<String>
}
@@ -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#"<a href="/playlist/{id}">{title}</a>"#)
// })
// .collect::<Vec<String>>()
// .join(", ");
// formatdoc!("
// <br><br>
// <div>
// In playlists: {list}
// </div>"
// )
// }
// };
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#"<span class="feedback">Not Saved</span>"#,
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(
<form action="/video/{path}" method="post">
{input_title}
{input_description}
{input_release_date}
{input_unlisted}
<div style="align-items: center; column-gap: .5rem; display: flex;">
@@ -459,13 +446,15 @@ pub async fn update(
path: ActixPath<ContextPath>
) -> Either<Redirect, HttpResponse> {
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
};
+21
View File
@@ -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#"<span class="error">{message}</span>"#))
.unwrap_or(String::new());
let value = &field.value;
formatdoc!(r#"
<div class="form_field">
<label for="{key}">{label}</label>
<input id="{key}" name="{key}" type="date" value="{value}">
{error}
</div>
"#)
}
pub fn form_field_pattern(
field: &Field,
key: &str,
+23
View File
@@ -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<PosterFile>,
pub release_date: Option<NaiveDate>,
pub title: Option<String>,
/// 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::<String>() {
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));
}