A little app to serve my photography from my personal website

map index page functionality to the UI

+80 -9
+35
assets/app.js
··· 1 + const initTimestamps = () => { 2 + document.querySelectorAll('time').forEach((el) => { 3 + const timeStr = el.attributes.datetime; 4 + if (!timeStr || !timeStr.value) { 5 + return; 6 + } 7 + 8 + const date = new Date(timeStr.value); 9 + const formatted = new Intl.DateTimeFormat(undefined, { 10 + month: "long", 11 + day: "numeric", 12 + year: "numeric" 13 + }).format(date); 14 + 15 + el.innerText = formatted; 16 + }) 17 + }; 18 + 19 + const initSort = () => { 20 + const select = document.getElementById("sort"); 21 + if (!select) { 22 + return; 23 + } 24 + 25 + select.addEventListener("change", (event) => { 26 + const url = new URL(window.location); 27 + url.searchParams.set('sort', event.target.value); 28 + window.location = url.search; 29 + }); 30 + } 31 + 32 + document.addEventListener("DOMContentLoaded", () => { 33 + initTimestamps(); 34 + initSort(); 35 + });
+3 -3
src/db.rs
··· 1 1 use crate::{Photo, Tag}; 2 - use serde::Deserialize; 2 + use serde::{Deserialize, Serialize}; 3 3 use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs}; 4 4 5 - #[derive(Deserialize, Clone, Copy)] 5 + #[derive(Deserialize, Serialize, Clone, Copy)] 6 6 #[serde(rename_all = "snake_case")] 7 7 pub enum SortDirection { 8 8 Asc, ··· 19 19 } 20 20 } 21 21 22 - #[derive(Deserialize, Clone, Copy)] 22 + #[derive(Deserialize, Serialize, Clone, Copy)] 23 23 #[serde(rename_all = "snake_case")] 24 24 pub enum SortField { 25 25 TakenAt,
+1
src/main.rs
··· 32 32 .route("/", get(routes::photos::index)) 33 33 .route("/{id}", get(routes::photos::show)) 34 34 .with_state(app_state) 35 + .nest_service("/assets", ServeDir::new(&env::var("ASSETS_PATH")?)) 35 36 .nest_service("/thumbnails", ServeDir::new(&env::var("THUMBNAIL_PATH")?)) 36 37 .nest_service("/images", ServeDir::new(&env::var("IMAGE_PATH")?)); 37 38 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
+9
src/routes/photos.rs
··· 34 34 } 35 35 } 36 36 37 + static SORT_FIELDS: [SortField; 2] = [SortField::TakenAt, SortField::CreatedAt]; 38 + 37 39 pub async fn index( 38 40 query: Query<IndexParams>, 39 41 State(state): State<Arc<AppState>>, ··· 54 56 ) 55 57 .await?; 56 58 59 + let current_tag = query.tag.clone(); 57 60 let tags = db::get_tags(&state.pool).await?; 61 + let sort_dir = query.dir; 62 + let sort_field = query.sort; 58 63 let template = state.template_env.get_template("photos/index")?; 59 64 let rendered = template.render(context! { 60 65 photos => photos, 61 66 tags => tags, 67 + current_tag => current_tag, 68 + sort_dir => sort_dir, 69 + sort_field => sort_field, 70 + sort_fields => SORT_FIELDS, 62 71 })?; 63 72 64 73 Ok(Html(rendered))
+1
templates/layout.jinja
··· 5 5 </head> 6 6 <body> 7 7 {% block body %}{% endblock %} 8 + <script src="/assets/app.js" type="text/javascript"></script> 8 9 </body> 9 10 </html>
+31 -6
templates/photos/index.jinja
··· 1 1 {% extends "layout" %} 2 2 {% block title %}Photos{% endblock %} 3 3 {% block body %} 4 - <ul> 5 - {% for tag in tags %} 6 - <li>{{ tag.name }} ({{ tag.count }})</li> 7 - {% endfor %} 8 - </ul> 4 + <nav> 5 + {{ dir }} 6 + <ul> 7 + {% for tag in tags %} 8 + <li> 9 + {% if current_tag == tag.name -%} 10 + {{ tag.name }} ({{ tag.count }}) 11 + {% else -%} 12 + <a href="?tag={{ tag.name }}"> 13 + {{ tag.name }} ({{ tag.count }}) 14 + </a> 15 + {% endif %} 16 + </li> 17 + {% endfor %} 18 + </ul> 19 + <p> 20 + Sort: 21 + {% if sort_dir == "asc" -%} 22 + <a href="/?dir=desc">↑</a> 23 + {% else -%} 24 + <a href="/?dir=asc">↓</a> 25 + {% endif %} 26 + </p> 27 + <select id="sort" name="sort"> 28 + {% for field in sort_fields %} 29 + <option value="{{ field }}" {% if field == sort_field -%}selected="true"{% endif %}>{{ field }}</option> 30 + {% endfor %} 31 + </select> 32 + </select> 33 + </nav> 9 34 {% for photo in photos %} 10 35 <a href="/{{ photo.id }}"> 11 - <p>{{ photo.taken_at }}</p> 36 + <p><time datetime="{{ photo.taken_at }}">{{ photo.taken_at }}</time></p> 12 37 <img src="/thumbnails/{{ photo.id }}.webp"> 13 38 </a> 14 39 {% endfor %}