pacco/src/routes/ui/repo.rs
JMARyA 48476d5cf4
Some checks failed
ci/woodpecker/push/pkgbuild/2 Pipeline is pending
ci/woodpecker/push/pkgbuild/1 Pipeline was successful
ci/woodpecker/push/container Pipeline failed
error handling
2025-06-28 04:08:12 +02:00

193 lines
6.2 KiB
Rust

use based::request::{RequestContext, StringResponse};
use based::ui::components::prelude::Shell;
use based::ui::prelude::*;
use maud::{PreEscaped, Render, html};
use pacco::pkg::package::PackageMetaInfo;
use rocket::{State, get};
use pacco::pkg::{Repository, arch::Architecture};
use serde_json::json;
use crate::routes::no_such_repo_error;
use crate::routes::ui::arch_card;
use pacco::config::Config;
/// Repository API Endpoint
#[get("/json/<repo>/<arch>")]
pub async fn repo_arch_json(
repo: &str,
ctx: RequestContext,
arch: &str,
config: &State<Config>,
) -> Result<serde_json::Value, serde_json::Value> {
let arch = Architecture::parse(arch).unwrap_or(Architecture::any);
let repo_name = repo;
let repo = Repository::new(repo_name).ok_or_else(no_such_repo_error)?;
let packages = repo.list_pkg_arch(arch.clone());
Ok(json!({
"repo": repo_name,
"arch": arch.to_string(),
"packages": packages
}))
}
/// Repository Overview UI
#[get("/<repo>?<arch>&<sort>")]
pub async fn repo_ui(
repo: &str,
ctx: RequestContext,
arch: Option<&str>,
sort: Option<&str>,
config: &State<Config>,
shell: &State<Shell>,
) -> StringResponse {
let arch = arch.map(|x| Architecture::parse(x).unwrap_or(Architecture::any));
let repo = Repository::new(repo).unwrap();
let architectures: Vec<_> = repo.arch().into_iter().map(|x| x.to_string()).collect();
let packages = if let Some(arch) = arch.clone() {
repo.list_pkg_arch(arch)
} else {
repo.list_pkg()
};
// TODO : sortable pkg list
// Repository name and architectures
let repo_info = Margin(
Flex(
Div()
.vanish()
.push(Text(&repo.name)._3xl().bold().color(&Gray::_100))
.push_if(config.is_mirrored_repo(&repo.name), || {
Background(
Rounded(Margin(
Padding(Text("Mirrored").sm().medium().white())
.x(ScreenValue::_3)
.y(ScreenValue::_1),
))
.size(Size::Full),
)
.color(Blue::_500)
})
.push(
Screen::medium(Margin(Nothing()).top(ScreenValue::_0)).on(Margin(
Flex(Div().vanish().push_for_each(&architectures, |a: &String| {
html! {
@if let Some(arch) = arch.as_ref() {
@if arch.to_string() == *a {
(arch_card(&a, &repo.name, true))
} @else {
(arch_card(&a, &repo.name, false))
}
} @else {
(arch_card(&a, &repo.name, false))
}
}
}))
.group()
.gap(ScreenValue::_2),
)
.top(ScreenValue::_2)),
)
.push(
Text(&format!("{} packages", packages.len()))
.sm()
.color(&Gray::_100),
),
)
.gap(ScreenValue::_3)
.wrap(Wrap::Wrap)
.full_center()
.group(),
)
.bottom(ScreenValue::_6);
let package_list = SpaceBetween(Div().vanish().push_for_each(&packages, |pkg| {
Animated(
Flex(
Padding(
Hover(Background(Nothing()).color(Gray::_600)).on(Background(
Rounded(Shadow::medium(
Link(
&format!("/{}/{pkg}", repo.name),
Div()
.vanish()
.push(FirstLetterCircle(pkg))
.push(Context(Width(
ScreenValue::fit,
Text(&pkg).medium().color(&Gray::_100),
)))
.push_if(
repo.get_pkg_by_name(&pkg)
.map(|x| x.sig_content())
.is_some(),
|| SignedInfo(),
),
)
.use_htmx(),
))
.size(Size::Large),
)
.color(Gray::_700)),
)
.all(ScreenValue::_4),
)
.items_center()
.gap(ScreenValue::_4),
)
}))
.y(ScreenValue::_4);
let content = Div().vanish().push(repo_info).push(package_list).render();
shell.render_page(content, &repo.name, ctx).await
}
#[allow(non_snake_case)]
pub fn FirstLetterCircle(name: &str) -> PreEscaped<String> {
Sized(
ScreenValue::_10,
ScreenValue::_10,
Rounded(
Background(
Flex(
Text(
&name
.chars()
.next()
.unwrap_or_default()
.to_uppercase()
.to_string(),
)
.white()
.semibold(),
)
.full_center()
.group(),
)
.color(Blue::_500),
)
.size(Size::Full),
)
.render()
}
#[allow(non_snake_case)]
pub fn SignedInfo() -> PreEscaped<String> {
Padding(
Flex(
Div()
.vanish()
.push(Span("")._2xl().bold().color(&Slate::_300))
.push(Span("Signed").sm().medium().color(&Slate::_300)),
)
.items_center()
.gap(ScreenValue::_2)
.group(),
)
.right(ScreenValue::_4)
.render()
}