Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ use {
video_format::VideoFormat,
video_info::VideoInfo,
video_type::VideoType,
view::View,
},
axum::{
body::Body,
Expand Down Expand Up @@ -368,6 +369,7 @@ mod video_error;
mod video_format;
mod video_info;
mod video_type;
mod view;

#[cfg(test)]
mod derive;
Expand Down
7 changes: 6 additions & 1 deletion src/subcommand/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
super::*,
axum::{
Router,
extract::{Extension, Path, Request},
extract::{Extension, Path, Query, Request},
http::{HeaderValue, Uri},
middleware::{self, Next},
response::{IntoResponse, Redirect, Response},
Expand Down Expand Up @@ -35,6 +35,11 @@ pub(crate) struct AuthConfig {
pub(crate) audience: Option<String>,
}

#[derive(Deserialize)]
pub(crate) struct PackagesQuery {
view: Option<View>,
}

pub(crate) struct RedirectConfig {
destination: Url,
domains: HashSet<String>,
Expand Down
6 changes: 5 additions & 1 deletion src/subcommand/serve/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,15 @@ pub(crate) async fn package_item(
})
}

pub(crate) async fn packages(server: ServerExtension) -> PageResult<PackagesHtml> {
pub(crate) async fn packages(
server: ServerExtension,
Query(query): Query<PackagesQuery>,
) -> PageResult<PackagesHtml> {
block_in_place(|| {
Ok(
PackagesHtml {
packages: server.packages()?,
view: query.view.unwrap_or_default(),
}
.into(),
)
Expand Down
41 changes: 40 additions & 1 deletion src/subcommand/serve/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,30 @@ fn packages_empty() {
.get("/packages")
.assert_page(PackagesHtml {
packages: Vec::new(),
view: View::List,
})
.send();
}

#[test]
fn packages_grid() {
let server = TestServer::new();

let metadata = Metadata {
artwork: Some("foo.png".parse().unwrap()),
..default()
};

let fingerprint = PackageBuilder::new()
.metadata(&metadata)
.file("foo.png", b"bar")
.upload(&server);

server
.get("/packages?view=grid")
.assert_page(PackagesHtml {
packages: vec![(fingerprint, Some(metadata))],
view: View::Grid,
})
.send();
}
Expand All @@ -1598,10 +1622,22 @@ fn packages_include_creators_and_titles() {
.get("/packages")
.assert_page(PackagesHtml {
packages: vec![(fingerprint, Some(metadata))],
view: View::List,
})
.send();
}

#[test]
fn packages_invalid_view() {
TestServer::new()
.get("/packages?view=foo")
.status(StatusCode::BAD_REQUEST)
.assert_body(
"Failed to deserialize query string: view: unknown variant `foo`, expected `grid` or `list`",
)
.send();
}

#[test]
fn packages_non_empty() {
let server = TestServer::new();
Expand All @@ -1622,7 +1658,10 @@ fn packages_non_empty() {

server
.get("/packages")
.assert_page(PackagesHtml { packages })
.assert_page(PackagesHtml {
packages,
view: View::List,
})
.send();
}

Expand Down
49 changes: 48 additions & 1 deletion src/templates/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ use super::*;
#[derive(Boilerplate)]
pub(crate) struct PackagesHtml {
pub(crate) packages: Vec<(Fingerprint, Option<Metadata>)>,
pub(crate) view: View,
}

impl PackagesHtml {
fn packages(
&self,
) -> impl Iterator<Item = (Fingerprint, Option<&Component>, Option<&Component>)> {
) -> impl Iterator<Item = (Fingerprint, bool, Option<&Component>, Option<&Component>)> {
self.packages.iter().map(|(fingerprint, metadata)| {
(
*fingerprint,
metadata
.as_ref()
.is_some_and(|metadata| metadata.artwork.is_some()),
metadata
.as_ref()
.and_then(|metadata| metadata.creator.as_deref()),
Expand All @@ -24,7 +28,50 @@ impl PackagesHtml {
}

impl Page for PackagesHtml {
fn stylesheet(&self) -> Option<&'static str> {
Some("/static/packages.css")
}

fn title(&self) -> String {
"packages · filepack".into()
}
}

#[cfg(test)]
mod tests {
use {super::*, pretty_assertions::assert_eq};

#[test]
fn grid() {
let fingerprint = test::FINGERPRINT.parse::<Fingerprint>().unwrap();

let metadata = Metadata {
artwork: Some("foo.png".parse().unwrap()),
..default()
};

assert_eq!(
PackagesHtml {
packages: vec![(fingerprint, Some(metadata)), (fingerprint, None)],
view: View::Grid,
}
.to_string(),
unindent(&format!(
"
<h1>Packages</h1>
<ul class=grid>
<li>
<a href=/package/{fingerprint}>
<img loading=lazy src=/artwork/{fingerprint} width=150 height=150>
</a>
</li>
<li>
<a href=/package/{fingerprint}></a>
</li>
</ul>
",
fingerprint = test::FINGERPRINT,
)),
);
}
}
9 changes: 9 additions & 0 deletions src/view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use super::*;

#[derive(Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum View {
Grid,
#[default]
List,
}
2 changes: 2 additions & 0 deletions static/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
:root {
--border: #222;
--border-hover: #444;
--primary: rgb(255 255 255 / 87%);
--secondary: rgb(255 255 255 / 60%);
}
Expand Down
4 changes: 2 additions & 2 deletions static/package.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ time {

a {
border-radius: 8px;
border: 1px solid #222;
border: 1px solid var(--border);
display: block;
overflow: hidden;
transition: border-color 150ms;

&:hover, &:focus-visible {
border-color: #444;
border-color: var(--border-hover);
}
}

Expand Down
27 changes: 27 additions & 0 deletions static/packages.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.grid {
display: grid;
gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
list-style: none;
padding-left: 0;

a {
aspect-ratio: 1 / 1;
border-radius: 8px;
border: 1px solid var(--border);
display: block;
overflow: hidden;
transition: border-color 150ms;

&:hover, &:focus-visible {
border-color: var(--border-hover);
}
}

img {
display: block;
height: 100%;
object-fit: contain;
width: 100%;
}
}
33 changes: 26 additions & 7 deletions templates/packages.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
<h1>Packages</h1>
%% match self.view {
%% View::Grid => {
<ul class=grid>
%% for (fingerprint, artwork, _creator, _title) in self.packages() {
<li>
%% if artwork {
<a href=/package/{{ fingerprint }}>
<img loading=lazy src=/artwork/{{ fingerprint }} width=150 height=150>
</a>
%% } else {
<a href=/package/{{ fingerprint }}></a>
%% }
</li>
%% }
</ul>
%% }
%% View::List => {
<ul>
%% for (fingerprint, creator, title) in self.packages() {
%% for (fingerprint, _artwork, creator, title) in self.packages() {
<li>
<a href=/package/{{ fingerprint }}>
%% if let Some(title) = title {
%% if let Some(creator) = creator {
%% if let Some(title) = title {
%% if let Some(creator) = creator {
{{ creator }} -
%% }
%% }
{{ title }}
%% } else {
%% } else {
<code>{{ fingerprint }}</code>
%% }
%% }
</a>
</li>
%% }
%% }
</ul>
%% }
%% }