diff --git a/.gitignore b/.gitignore index 6524883..dc17db2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,6 @@ dist /target # Project specific files -test_album +test_album* DESIGN.md TODO.md diff --git a/Cargo.lock b/Cargo.lock index 457a247..e896f62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,7 @@ version = "0.2.0" dependencies = [ "anyhow", "clap", + "thiserror", ] [[package]] @@ -165,6 +166,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index 4286a33..2fa3932 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ edition = "2024" [dependencies] anyhow = "^1.0" clap = { version = "^4.5", features = ["derive"] } +thiserror = "^2.0" diff --git a/resources/skel/_templates/album.html b/resources/skel/_templates/album.html new file mode 100644 index 0000000..cf4a112 --- /dev/null +++ b/resources/skel/_templates/album.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} + +{% block content %} + {% if not album_dir.is_root %} +

+ Home + {% for href, name in breadcrumbs %} + / {{name}} + {% endfor %} + / {{album_dir.path.name}} +

+
+ {% endif %} + + {% if album_dir.description %} +
+ {{ album_dir.description | safe }} +
+ {% endif %} + + {% if album_dir.children %} +

Albums

+
+ {% for child in album_dir.children %} +
+ +
+ {% if child.cover_path %} + + {% endif %} +
+
+ {{child.path.name}} +
+
+
+ {% endfor %} +
+ {% endif %} + {% if album_dir.images %} + {% if album_dir.children %} +

Photos

+ {% endif %} +
+ {% for image in album_dir.images %} +
+ + + +
+ {% endfor %} +
+ {% endif %} +{% endblock %} diff --git a/resources/skel/_templates/base.html b/resources/skel/_templates/base.html new file mode 100644 index 0000000..b122456 --- /dev/null +++ b/resources/skel/_templates/base.html @@ -0,0 +1,24 @@ + + + + + + My Photos + + + + +
+ {% block content %} + {% endblock %} +
+ + + diff --git a/resources/skel/_templates/photo.html b/resources/skel/_templates/photo.html new file mode 100644 index 0000000..9f4198e --- /dev/null +++ b/resources/skel/_templates/photo.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block js %} + // Do left/right navigation on keypresses + document.onkeydown = function(event) { + if (event.key == "ArrowLeft") { + {% if prev_image %} + location.href = "{{prev_image.html_filename()}}"; + {% endif %} + } else if (event.key == "ArrowRight") { + {% if next_image %} + location.href = "{{next_image.html_filename()}}"; + {% endif %} + } + } +{% endblock %} + +{% block content %} +
+ +
+ + + +
+ {% if image_path.description %} + {{ image_path.description | safe }} + {% endif %} +
+ +
+ view full size +
+{% endblock %} diff --git a/resources/skel/photojawn.conf.yml b/resources/skel/photojawn.conf.yml new file mode 100644 index 0000000..91b8b0b --- /dev/null +++ b/resources/skel/photojawn.conf.yml @@ -0,0 +1,9 @@ +# Max size of thumbnails when viewing an album +thumbnail_size: [256, 256] + +# Max size of images when viewing a single one on screen +view_size: [1024, 768] + +# Directory where the generated site will be created in. All original images will be +# copied in to here with the same directory structure. +output_dir: "site" diff --git a/resources/skel/static/index.css b/resources/skel/static/index.css new file mode 100644 index 0000000..f476fe1 --- /dev/null +++ b/resources/skel/static/index.css @@ -0,0 +1,112 @@ +body { + margin: 0; +} + +a { + color: #ba1200; +} + +#header { + background-color: #eee; +} + +#header * { + margin-top: 0; + text-decoration: inherit; + color: inherit; +} + +#header h1 { + padding: 0.5em; +} + +#content { + text-align: center; + max-width: 1200px; + margin: 0.5em; + margin-left: auto; + margin-right: auto; +} + +#content > * { + margin-top: 1em; +} + +ul { + padding-left: 1.5em; +} + +#album-children { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +#album-children > * { + margin: 1em; + padding: 0.75em; + background-color: lightgrey; + height: min-content; + border: thin solid black; + box-shadow: 0.25em 0.25em #ccc; +} + +#album-photos { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.thumbnail { + margin: 1em; + display: flex; + align-items: center; +} + +.thumbnail * { + max-width: 100%; +} + +#nav { + width: 150px; + margin: auto; + display: flex; + padding: 0.5em; +} + +#nav div { + width: 50px; +} + +#photo img { + max-width: 100%; + height: auto; +} + +.caption { + max-width: 700px; + margin-left: auto; + margin-right: auto; +} + +.arrow { + border: solid black; + border-width: 0 3px 3px 0; + display: inline-block; + padding: 3px; +} + +.arrow-right { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.arrow-left { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.arrow-up { + transform: rotate(-135deg); + -webkit-transform: rotate(-135deg); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e8c41c1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod skel; diff --git a/src/main.rs b/src/main.rs index 2461394..d73956b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,22 @@ use clap::{Parser, Subcommand}; +use photojawn::skel::make_skeleton; +use std::path::Path; -fn main() { +fn main() -> anyhow::Result<()> { let cli = Cli::parse(); + + let album_path = Path::new(&cli.album_path); + + match cli.subcommand { + Commands::Init {} => make_skeleton(album_path)?, + Commands::Generate { quick } => { + println!("Generate, quick: {quick}"); + todo!() + } + Commands::Clean {} => todo!(), + } + + Ok(()) } #[derive(Parser)] @@ -12,7 +27,7 @@ struct Cli { album_path: String, #[command(subcommand)] - command: Commands, + subcommand: Commands, } #[derive(Subcommand)] diff --git a/src/skel.rs b/src/skel.rs new file mode 100644 index 0000000..bb81ca6 --- /dev/null +++ b/src/skel.rs @@ -0,0 +1,40 @@ +use std::fs; +use std::io; +use std::path::Path; +use thiserror::Error; + +const BASE_TMPL: &'static [u8; 653] = include_bytes!("../resources/skel/_templates/base.html"); +const ALBUM_TMPL: &'static [u8; 1682] = include_bytes!("../resources/skel/_templates/album.html"); +const PHOTO_TMPL: &'static [u8; 1333] = include_bytes!("../resources/skel/_templates/photo.html"); +const INDEX_CSS: &'static [u8; 1421] = include_bytes!("../resources/skel/static/index.css"); +const BASE_CONFIG: &'static [u8; 315] = include_bytes!("../resources/skel/photojawn.conf.yml"); + +#[derive(Error, Debug)] +pub enum InitError { + #[error("Album directory already initialized - contains a photojawn.conf.yml")] + AlreadyInitialized, + #[error(transparent)] + IoError(#[from] io::Error), +} + +pub fn make_skeleton(album_path: &Path) -> Result<(), InitError> { + let cfg_path = album_path.join("photojawn.conf.yml"); + if cfg_path.exists() { + return Err(InitError::AlreadyInitialized); + } + + fs::create_dir_all(album_path)?; + fs::write(cfg_path, BASE_CONFIG)?; + + let static_path = album_path.join("static"); + fs::create_dir_all(&static_path)?; + fs::write(static_path.join("index.css"), INDEX_CSS)?; + + let tmpl_path = album_path.join("_templates"); + fs::create_dir_all(tmpl_path)?; + fs::write(static_path.join("base.html"), BASE_TMPL)?; + fs::write(static_path.join("album.html"), ALBUM_TMPL)?; + fs::write(static_path.join("photo.html"), PHOTO_TMPL)?; + + Ok(()) +}