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 %}
+
+ {% 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 prev_image %}
+
+
+
+ {% endif %}
+
+
+
+ {% if next_image %}
+
+
+
+ {% endif %}
+
+
+
+
+ {% if image_path.description %}
+ {{ image_path.description | safe }}
+ {% endif %}
+
+
+
+{% 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(())
+}