tangled
alpha
login
or
join now
ericwood.org
/
photos-site
1
fork
atom
A little app to serve my photography from my personal website
1
fork
atom
overview
issues
pulls
pipelines
messin with tha UI
ericwood.org
5 months ago
f54c6946
971b497f
+85
-10
6 changed files
expand all
collapse all
unified
split
assets
app.css
src
db.rs
models.rs
routes
photos.rs
templates
layout.jinja
photos
index.jinja
+21
assets/app.css
···
1
1
+
.photos {
2
2
+
display: flex;
3
3
+
flex-wrap: wrap;
4
4
+
justify-content: center;
5
5
+
gap: 20px;
6
6
+
max-width: 1200px;
7
7
+
}
8
8
+
9
9
+
.photos > * {
10
10
+
display: block;
11
11
+
}
12
12
+
13
13
+
.photos > * img {
14
14
+
max-height: 300px;
15
15
+
}
16
16
+
17
17
+
.pagination {
18
18
+
display: flex;
19
19
+
list-style-type: none;
20
20
+
gap: 10px;
21
21
+
}
+7
src/db.rs
···
60
60
pub tag: Option<String>,
61
61
}
62
62
63
63
+
pub async fn get_photo_count(pool: &SqlitePool) -> anyhow::Result<u32> {
64
64
+
let result: (u32,) = sqlx::query_as("SELECT COUNT(*) FROM photos")
65
65
+
.fetch_one(pool)
66
66
+
.await?;
67
67
+
Ok(result.0)
68
68
+
}
69
69
+
63
70
pub async fn get_photos(pool: &SqlitePool, pq: PhotoQuery) -> anyhow::Result<Vec<Photo>> {
64
71
let offset = pq.pagination.limit * (pq.pagination.page - 1);
65
72
+2
src/models.rs
···
8
8
pub id: String,
9
9
pub caption: String,
10
10
pub filename: String,
11
11
+
pub width: u32,
12
12
+
pub height: u32,
11
13
#[sqlx(try_from = "String")]
12
14
pub taken_at: DateTime,
13
15
#[sqlx(try_from = "String")]
+22
-4
src/routes/photos.rs
···
5
5
response::Html,
6
6
};
7
7
use minijinja::context;
8
8
-
use serde::{self, Deserialize};
8
8
+
use serde::{self, Deserialize, Serialize};
9
9
10
10
use crate::{
11
11
AppError, AppState,
12
12
-
db::{self, Pagination, PhotoQuery, Sort, SortDirection, SortField},
12
12
+
db::{self, Pagination as QueryPagination, PhotoQuery, Sort, SortDirection, SortField},
13
13
};
14
14
+
15
15
+
#[derive(Serialize)]
16
16
+
pub struct Pagination {
17
17
+
page: u32,
18
18
+
num_pages: u32,
19
19
+
}
14
20
15
21
#[derive(Deserialize)]
16
22
#[serde(default)]
···
47
53
field: query.sort,
48
54
direction: query.dir,
49
55
},
50
50
-
pagination: Pagination {
56
56
+
pagination: QueryPagination {
51
57
limit: query.limit,
52
58
page: query.page,
53
59
},
···
56
62
)
57
63
.await?;
58
64
65
65
+
let total_photos = db::get_photo_count(&state.pool).await?;
66
66
+
59
67
let current_tag = query.tag.clone();
60
60
-
let tags = db::get_tags(&state.pool).await?;
68
68
+
let mut tags = db::get_tags(&state.pool).await?;
69
69
+
if let Some(tag) = &query.tag {
70
70
+
tags.retain(|t| t.name != *tag);
71
71
+
}
72
72
+
61
73
let sort_dir = query.dir;
62
74
let sort_field = query.sort;
63
75
let template = state.template_env.get_template("photos/index")?;
76
76
+
let pagination = Pagination {
77
77
+
page: query.page,
78
78
+
num_pages: total_photos / query.limit,
79
79
+
};
64
80
let rendered = template.render(context! {
65
81
photos => photos,
66
82
tags => tags,
83
83
+
current_tag => current_tag,
67
84
current_tag => current_tag,
68
85
sort_dir => sort_dir,
69
86
sort_field => sort_field,
70
87
sort_fields => SORT_FIELDS,
88
88
+
pagination => pagination,
71
89
})?;
72
90
73
91
Ok(Html(rendered))
+1
templates/layout.jinja
···
2
2
<html>
3
3
<head>
4
4
<title>{% block title %}{% endblock %}</title>
5
5
+
<link rel="stylesheet" href="/assets/app.css" />
5
6
</head>
6
7
<body>
7
8
{% block body %}{% endblock %}
+32
-6
templates/photos/index.jinja
···
4
4
<nav>
5
5
{{ dir }}
6
6
<ul>
7
7
+
{% if current_tag %}
8
8
+
<li>{{ current_tag }} <a href="/">🅇</a></li>
9
9
+
{% endif %}
7
10
{% for tag in tags %}
8
11
<li>
9
12
{% if current_tag == tag.name -%}
···
31
34
</select>
32
35
</select>
33
36
</nav>
34
34
-
{% for photo in photos %}
35
35
-
<a href="/{{ photo.id }}">
36
36
-
<p><time datetime="{{ photo.taken_at }}">{{ photo.taken_at }}</time></p>
37
37
-
<img src="/thumbnails/{{ photo.id }}.webp">
38
38
-
</a>
39
39
-
{% endfor %}
37
37
+
<div class="photos">
38
38
+
{% for photo in photos %}
39
39
+
<a href="/{{ photo.id }}">
40
40
+
<p><time datetime="{{ photo.taken_at }}">{{ photo.taken_at }}</time></p>
41
41
+
<img
42
42
+
style="aspect-ratio: {{ photo.width }} / {{ photo.height }}"
43
43
+
src="/thumbnails/{{ photo.id }}.webp"
44
44
+
>
45
45
+
</a>
46
46
+
{% endfor %}
47
47
+
</div>
48
48
+
49
49
+
<ul class="pagination">
50
50
+
{% if pagination.page > 1 %}
51
51
+
<a href="?page={{ pagination.page - 1 }}">←</a>
52
52
+
{% endif %}
53
53
+
{% for i in range(1, pagination.num_pages+1) %}
54
54
+
<li>
55
55
+
{% if pagination.page == i %}
56
56
+
{{ i }}
57
57
+
{% else %}
58
58
+
<a href="?page={{ i }}">{{ i }}</a>
59
59
+
{% endif %}
60
60
+
</li>
61
61
+
{% endfor %}
62
62
+
{% if pagination.page != pagination.num_pages %}
63
63
+
<a href="?page={{ pagination.page + 1 }}">→</a>
64
64
+
{% endif %}
65
65
+
</ul>
40
66
{% endblock %}