diff --git a/source/main.rs b/source/main.rs index f0ea9f3..706a16b 100644 --- a/source/main.rs +++ b/source/main.rs @@ -8,6 +8,7 @@ use color_eyre::{install, Result}; mod copy; mod scss; mod templates; +mod video; fn main() -> Result<()> { install()?; @@ -20,6 +21,7 @@ fn main() -> Result<()> { templates::Index::write(&public_dir)?; scss::Scss::write(&public_dir, &source_dir)?; copy::Copy::write(&build_dir, &public_dir, &source_dir)?; + video::write_all(&public_dir)?; Ok(()) } diff --git a/source/scss/mod.rs b/source/scss/mod.rs index a662c11..caae318 100644 --- a/source/scss/mod.rs +++ b/source/scss/mod.rs @@ -17,7 +17,7 @@ impl Scss { create_dir_all(&css_dir)?; let scss_dir = source_dir.join("scss"); - let scss_filenames = vec!["index", "modern-normalize"]; + let scss_filenames = vec!["index", "modern-normalize", "video"]; let format = Format { precision: 5, diff --git a/source/scss/video.scss b/source/scss/video.scss new file mode 100644 index 0000000..c8a0f5c --- /dev/null +++ b/source/scss/video.scss @@ -0,0 +1,63 @@ +@use "mixins"; +@use "reset"; + +body { + // Colors. + --foreground: #f1ebff; + --background: #1f003e; + --anchor: #89c3ff; + + // Spacing constants. + --spacing-small: 4px; + --spacing-medium: 8px; + --spacing-large: 16px; + + background-color: var(--background); + color: var(--foreground); + font-size: 1rem; +} + +a { + color: var(--anchor); + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +ul { + margin-left: 1rem; +} + +iframe, +h2 { + margin-top: var(--spacing-large); +} + +iframe { + aspect-ratio: 16 / 9; + border: 2px dashed; + display: block; + width: 100%; + margin-bottom: var(--spacing-large); + max-width: 1280px; + padding: var(--spacing-medium); +} + +.page-header { + border-bottom: 2px dashed; + padding: var(--spacing-large); + + h1 { + @include mixins.responsive-container; + } +} + +.page-main { + @include mixins.responsive-container; +} + +.timestamp-link { + font-family: monospace; +} diff --git a/source/templates/video.html b/source/templates/video.html new file mode 100644 index 0000000..4d92d2d --- /dev/null +++ b/source/templates/video.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% block head %} + +{% endblock %} + +{% block body %} + + +
+ {{ rendered_markdown|safe }} + + {% if let Some(speedrun) = speedrun %} +

Speedrun

+ + + {% if let Some(mods) = speedrun.mods %} +

Mods

+ + {% endif %} + + {% if let Some(chapters) = speedrun.chapters %} +

Chapters

+ + {% endif %} + + + {% endif %} + +
+{% endblock %} diff --git a/source/video/filters.rs b/source/video/filters.rs new file mode 100644 index 0000000..1954e61 --- /dev/null +++ b/source/video/filters.rs @@ -0,0 +1,19 @@ +/*! +Filters for Askama templates. +*/ + +/** +Turn a timestamp with format `mm:ss` into its total seconds. + +## Examples + +- `00:30` -> 30 seconds +- `01:00` -> 60 seconds +- `01:30` -> 90 seconds +*/ +pub fn timestamp_to_seconds(timestamp: &str) -> askama::Result { + let mut split = timestamp.split(":"); + let minutes = split.next().map(str::parse::).unwrap().unwrap(); + let seconds = split.next().map(str::parse::).unwrap().unwrap(); + Ok(minutes * 60 + seconds) +} diff --git a/source/video/mod.rs b/source/video/mod.rs new file mode 100644 index 0000000..1fc378b --- /dev/null +++ b/source/video/mod.rs @@ -0,0 +1,69 @@ +use std::{fs, path::Path}; + +use {askama::Template, color_eyre::Result, serde::Deserialize}; + +mod filters; + +#[derive(Debug, Template)] +#[template(path = "video.html")] +pub struct VideoTemplate { + pub page_title: String, + pub rendered_markdown: String, + pub speedrun: Option, + pub video_id: String, +} + +#[derive(Debug, Deserialize)] +struct VideoData { + pub id: String, + pub page_title: String, + pub speedrun: Option, +} + +#[derive(Debug, Deserialize)] +pub struct SpeedrunData { + pub chapters: Option>, + pub entry: String, + pub leaderboard: String, + pub mods: Option>, +} + +pub fn write_all(public_dir: &Path) -> Result<()> { + let video_datas = { + let mut data = vec![]; + + for dir in ["2022"] { + for file in fs::read_dir(format!("source/video/{dir}"))? { + let file_path = file?.path(); + if file_path.extension().unwrap() != "md" { + continue; + } + + let file_contents = fs::read_to_string(file_path)?; + let (video_data, markdown) = + toml_frontmatter::parse::(&file_contents).unwrap(); + data.push((video_data, markdown.to_string())); + } + } + + data + }; + + for (video_data, markdown) in video_datas { + let video_dir = public_dir.join("v").join(&video_data.id); + fs::create_dir_all(&video_dir)?; + + let template = VideoTemplate { + page_title: video_data.page_title, + rendered_markdown: comrak::markdown_to_html( + &markdown, + &Default::default(), + ), + speedrun: video_data.speedrun, + video_id: video_data.id, + }; + fs::write(video_dir.join("index.html"), template.render()?)?; + } + + Ok(()) +}