From 5ae9b80302fd7dcd2e1985c437fb1c5e9a793deb Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sat, 18 Jan 2025 22:14:02 +0100 Subject: [PATCH] refactor --- src/routes/ui.rs | 644 ------------------------------------------ src/routes/ui/mod.rs | 50 ++++ src/routes/ui/pkg.rs | 567 +++++++++++++++++++++++++++++++++++++ src/routes/ui/repo.rs | 163 +++++++++++ 4 files changed, 780 insertions(+), 644 deletions(-) delete mode 100644 src/routes/ui.rs create mode 100644 src/routes/ui/mod.rs create mode 100644 src/routes/ui/pkg.rs create mode 100644 src/routes/ui/repo.rs diff --git a/src/routes/ui.rs b/src/routes/ui.rs deleted file mode 100644 index 09f7160..0000000 --- a/src/routes/ui.rs +++ /dev/null @@ -1,644 +0,0 @@ -use based::request::{RequestContext, StringResponse}; -use based::ui::primitives::flex::Strategy; -use based::ui::primitives::space::SpaceBetweenWidget; -use based::ui::primitives::text::{Code, TextWidget}; -use based::ui::{UIWidget, prelude::*}; -use maud::{PreEscaped, Render, html}; -use rocket::{State, get}; - -use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name}; - -use crate::config::Config; - -use super::render; - -// TODO : API - -#[get("//?")] -pub async fn pkg_ui( - repo: &str, - pkg_name: &str, - ctx: RequestContext, - ver: Option<&str>, -) -> Option { - let repo = Repository::new(repo).unwrap(); - let mut pkg = repo.get_pkg_by_name(pkg_name)?; - - if let Some(ver) = ver { - let (version, rel) = Package::version(ver); - pkg = pkg.get_version(&version); - pkg.rel = rel; - } - - let versions = pkg.versions(); - let arch = pkg.arch(); - let install_script = pkg.install_script(); - let systemd_units = pkg.systemd_units(); - let pacman_hooks = pkg.pacman_hooks(); - let binaries = pkg.binaries(); - let man_entries = pkg.man_entries(); - let etc_entries = pkg.etc_entries(); - let kernel_modules = pkg.kernel_modules(); - let firmware = pkg.firmware(); - let keyrings = pkg.keyrings(); - let shared_objects = pkg.shared_objects(); - let icons = pkg.icons(); - let readmes = pkg.readmes(); - let mut pkginfo = pkg.pkginfo(); - - let content = Div().vanish() - .push( - Flex( - // Package Name - Div().vanish() - .push( - Link(&format!("/{}", repo.name), - Text(&repo.name).bold().color(&Gray::_100)._3xl() - ).use_htmx() - ) - .push(Padding(Text("/").bold().color(&Gray::_400)).all(ScreenValue::_2)) - .push( - Padding(Text(&pkg.name)._3xl().bold().color(&Gray::_100)).right(ScreenValue::_2) - ) - .push_if(pkg.is_signed(), || { - Flex( - Padding( - Div().vanish() - .push(Span("✓")._2xl().bold().color(&Slate::_300)) - .push(Span("Signed").sm().medium().color(&Slate::_300)) - // TODO : Add more info: Who signed? + Public Key recv - ).right(ScreenValue::_4) - ).items_center().gap(ScreenValue::_2) - }) - .push_for_each(&arch, |arch: &Architecture| { - Rounded( - Padding( - Background(Gray::_700, - Span(&arch.to_string()).medium().sm()) - ).x(ScreenValue::_3).y(ScreenValue::_1) - ).size(Size::Full) - }) - .push(Margin( - Flex( - Padding( - Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200, - Rounded(Shadow::medium( - Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()), - Paragraph( - html! { - svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" { - path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; - }; - p class="ml-2 text-black" { "Download" }; - } - ).black().xs()))).size(Size::Large) - )) - ).x(ScreenValue::_2).y(ScreenValue::_2) - ).items_center() - ).left(ScreenValue::_4) - ) - ).wrap(Wrap::Wrap).gap(ScreenValue::_2).full_center().group() - ) - .push( - Padding( - Flex( - Div().vanish().push( - Context( - FlexGrow(Strategy::Grow, - Animated(Background(Gray::_800, Shadow::large(Rounded(Margin(Padding(SpaceBetween( - Div() - .push( - Context(Text("Info").xl().semibold().color(&Gray::_300).underlined()) - ) - .push_some( - take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1, - |x: (String, String)| build_info(x.0, x.1) - ) - .push_some( - take_out(&mut pkginfo, |x| { x.0 == "packager" }).1, - |x: (String, String)| build_info(x.0, x.1) - ) - .push_some( - take_out(&mut pkginfo, |x| { x.0 == "url" }).1, - |x: (String, String)| build_info(x.0, x.1) - ) - .push_some( - take_out(&mut pkginfo, |x| { x.0 == "size" }).1, - |x: (String, String)| build_info(x.0, x.1) - ) - .push_for_each(&pkginfo, |(key, val)| build_info(key.clone(), val.clone())) - ).y(ScreenValue::_2)).all(ScreenValue::_4)).all(ScreenValue::_2)).size(Size::Large))))) - )).push( - SpaceBetween( - Padding( - Background(Gray::_800, - Shadow::large( - Rounded( - Margin( - Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on( - FlexGrow(Strategy::Grow, - Div().vanish() - .push( - Text("Versions").xl().semibold().color(&Gray::_300) - ) - .push( - SpaceBetween( - Div().vanish() - .push_for_each(&versions, |version: &String| { - Animated(Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), - if pkg.version.as_ref() - .map(|x| *x == Package::version(&version).0).unwrap_or_default() - { Text(&version).color(&Blue::_500).render() } else { - Hover(Text("").color(&Blue::_400)).on( - Text(&version).color(&Gray::_100)).render() - } - ).use_htmx()) - }) - ).y(ScreenValue::_1) - ) - )) - ).all(ScreenValue::_2) - ).size(Size::Large) - ) - ) - ).all(ScreenValue::_4) - ).y(ScreenValue::_2) - ) - ).wrap(Wrap::Wrap).group() - ).top(ScreenValue::_6) - ).push( - Padding(Flex( - SpaceBetween( - Div().vanish() - .push( - Text("Content").xl().bold().color(&Gray::_300) - ) - .push( - Flex( - Div().vanish() - .push_if(!systemd_units.is_empty(), || { - InfoCard( - Div().vanish() - .push( - CardTitle("Systemd Units") - ) - .push( - ListElements(&systemd_units) - ) - ) - }) - .push_if(!pacman_hooks.is_empty(), || { - InfoCard( - Div().vanish() - .push(CardTitle("Pacman Hooks")) - .push( - Paragraph( - SpaceBetween( - Div().vanish() - .push_for_each(&pacman_hooks, |hook: &(String, String)| { - Div().vanish() - .push( - Text(&hook.0).xl().semibold().color(&Gray::_300) - ) - .push( - Padding(Rounded(Background(Gray::_700, Code(&hook.1).sm().color(&Gray::_100))).size(Size::Large)).all(ScreenValue::_4) - ) - }) - ).y(ScreenValue::_1) - ).list_style(ListStyle::Disc).color(&Gray::_300) - ) - ) - }) - .push_if(!readmes.is_empty(), - || InfoCard( - Div().vanish().push(CardTitle("READMEs")).push( - Paragraph( - SpaceBetween( - Div().vanish() - .push_for_each(&readmes, |readme: &(String, String)| { - Div().vanish() - .push( - Text(&readme.0).xl().semibold().color(&Gray::_300) - ) - .push( - Padding(Rounded(Background(Gray::_700, Code(&readme.1).sm().color(&Gray::_100))).size(Size::Large)).all(ScreenValue::_4) - ) - }) - ).y(ScreenValue::_1) - ).list_style(ListStyle::Disc).color(&Gray::_300) - ) - ) - ) - .push_if(!binaries.is_empty(), - || InfoCard( - Div().vanish() - .push( - CardTitle("Binaries") - ).push( - ListElements(&binaries) - ) - ) - ) .push_if(!kernel_modules.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Kernel Modules")) - .push( - ListElements(&kernel_modules) - ) - ) - ).push_if(!shared_objects.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Shared Objects (Libraries)")) - .push( - ListElements(&shared_objects) - ) - ) - ).push_if(!firmware.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Firmware")) - .push( - ListElements(&firmware) - ) - ) - ).push_if(!keyrings.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Pacman Keyrings")) - .push( - ListElements(&keyrings) - ) - ) - ).push_if(!icons.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Icons")) - .push( - ListElements(&icons) - ) - ) - ) - .push_if(!man_entries.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Man Entries")) - .push( - ListElements(&man_entries) - ) - ) - ) - .push_if(!etc_entries.is_empty(), - || InfoCard( - Div().vanish() - .push(CardTitle("Config Files")) - .push( - ListElements(&etc_entries) - ) - ) - ) - .push( - InfoCard( - Div().vanish().push( - CardTitle("Package Files") - ) - .push( - ListElements(&pkg.file_list()) - ) - ) - ) - ).group().wrap(Wrap::Wrap) - ) - ).y(ScreenValue::_2) - ).wrap(Wrap::Wrap).group()).top(ScreenValue::_6) - ).push_some(install_script.as_ref(), |install_script: &String| { - SpaceBetween( - Margin( - Div() - .push(Text("Install Script")._3xl().semibold().color(&Gray::_300).indentation(ScreenValue::_2)) - .push(Rounded(Padding(Background(Gray::_700, - Code(install_script.trim()) - .color(&Gray::_100).sm() - .indentation(ScreenValue::_0) - )).all(ScreenValue::_2)).size(Size::Large)) - ).top(ScreenValue::_6) - ).y(ScreenValue::_4) - }).render(); - - Some(render(content, pkg_name, ctx).await) -} - -pub fn arch_card(a: &str, repo_name: &str, current: bool) -> PreEscaped { - let url = if current { - &format!("/{repo_name}") - } else { - &format!("/{repo_name}?arch={a}") - }; - - let link = Link(&url, Text(&a).sm().medium().color(&Gray::_300)).use_htmx(); - - Rounded( - Padding(if current { - Background(Blue::_500, link) - } else { - Background(Gray::_700, link) - }) - .x(ScreenValue::_3) - .y(ScreenValue::_1), - ) - .size(Size::Full) - .render() -} - -#[get("/?")] -pub async fn repo_ui( - repo: &str, - ctx: RequestContext, - arch: Option<&str>, - config: &State, -) -> 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() - }; - - let repo_info = Margin( - // Repository name and architectures - Flex( - Div() - .vanish() - .push(Text(&repo.name)._3xl().bold().color(&Gray::_100)) - .push_if(config.is_mirrored_repo(&repo.name), || { - Background( - Blue::_500, - Rounded(Margin( - Padding(Text("Mirrored").sm().medium().white()) - .x(ScreenValue::_3) - .y(ScreenValue::_1), - )) - .size(Size::Full), - ) - }) - .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(Gray::_600, Nothing())).on(Background( - Gray::_700, - Rounded(Shadow::medium( - Link( - &format!("/{}/{pkg}", repo.name), - Div() - .vanish() - .push(Context(Sized( - ScreenValue::_10, - ScreenValue::_10, - Rounded(Background( - Blue::_500, - Flex( - Text( - &pkg.chars() - .next() - .unwrap_or_default() - .to_uppercase() - .to_string(), - ) - .white() - .semibold(), - ) - .full_center() - .group(), - )) - .size(Size::Full), - ))) - .push(Context(Width( - ScreenValue::fit, - Text(&pkg).medium().color(&Gray::_100), - ))) - .push(Context( - 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), - )), - ) - .use_htmx(), - )) - .size(Size::Large), - )), - ) - .all(ScreenValue::_4), - ) - .items_center() - .gap(ScreenValue::_4), - ) - })) - .y(ScreenValue::_4); - - let content = Div().vanish().push(repo_info).push(package_list).render(); - - render(content, &repo.name, ctx).await -} - -pub fn build_info(key: String, value: String) -> PreEscaped { - match key.as_str() { - "pkgname" => {} - "xdata" => {} - "arch" => {} - "pkbase" => {} - "pkgver" => {} - "pkgdesc" => { - return key_value("Description".to_string(), value); - } - "packager" => { - return key_value("Packager".to_string(), value); - } - "url" => { - return Flex( - Div() - .vanish() - .push(Width(ScreenValue::_32, Span("Website: ").bold())) - .push( - Margin(Link(&value, Span(&value).color(&Blue::_400))).left(ScreenValue::_6), - ), - ) - .group() - .render(); - } - "builddate" => { - let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap(); - return key_value( - "Build at".to_string(), - date.format("%d.%m.%Y %H:%M").to_string(), - ); - } - "depend" => { - return pkg_list_info("Depends on", &value); - } - "makedepend" => { - return pkg_list_info("Build Dependencies", &value); - } - "license" => { - return key_value("License".to_string(), value); - } - "size" => { - return key_value( - "Size".to_string(), - bytesize::to_string(value.parse().unwrap(), false), - ); - } - _ => { - log::warn!("Unhandled PKGINFO {key} = {value}"); - } - } - - html! {} -} - -pub fn key_value(key: String, value: String) -> PreEscaped { - Flex( - Div() - .vanish() - .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) - .push(Margin(Span(&value)).left(ScreenValue::_6)), - ) - .items_center() - .group() - .render() -} - -pub fn take_out(v: &mut Vec, f: impl Fn(&T) -> bool) -> (&mut Vec, Option) { - let mut index = -1; - - for (i, e) in v.iter().enumerate() { - if f(e) { - index = i as i64; - } - } - - if index != -1 { - let e = v.remove(index as usize); - return (v, Some(e)); - } - - (v, None) -} - -pub fn find_pkg_url(pkg: &str) -> Option { - if let Some(pkg) = find_package_by_name(pkg) { - return Some(format!("/{}/{}", pkg.repo, pkg.name)); - } - - None -} - -pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped { - let pkgs = value.split_whitespace().map(|pkg| { - if let Some(pkg_url) = find_pkg_url(pkg) { - Margin(Link(&pkg_url, Text(&pkg).color(&Blue::_400)).use_htmx()).left(ScreenValue::_6) - } else { - Margin(Span(&pkg)).left(ScreenValue::_6) - } - }); - - html! { - (Flex( - Div().vanish() - .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) - .push(Flex( - html! { - @for pkg in pkgs { - (pkg) - } - } - ).group().wrap(Wrap::Wrap)) - ).items_center().group()) - } -} - -#[allow(non_snake_case)] -pub fn ListElements(el: &[String]) -> TextWidget { - Paragraph( - SpaceBetween(html! { - @for unit in el { - li { (unit) } - } - }) - .y(ScreenValue::_1), - ) - .list_style(ListStyle::Disc) - .color(&Gray::_300) -} - -#[allow(non_snake_case)] -pub fn CardTitle(title: &str) -> PreEscaped { - Context(Text(title).large().medium().color(&Gray::_100).underlined()) -} - -#[allow(non_snake_case)] -pub fn InfoCard(inner: T) -> SpaceBetweenWidget { - SpaceBetween( - Padding(Background( - Gray::_800, - Shadow::large( - Rounded(Margin(FlexGrow(Strategy::Grow, inner)).all(ScreenValue::_2)) - .size(Size::Large), - ), - )) - .all(ScreenValue::_4), - ) - .y(ScreenValue::_2) -} diff --git a/src/routes/ui/mod.rs b/src/routes/ui/mod.rs new file mode 100644 index 0000000..c12f915 --- /dev/null +++ b/src/routes/ui/mod.rs @@ -0,0 +1,50 @@ +use based::ui::prelude::*; +use maud::{PreEscaped, Render}; + +use super::render; + +// TODO : API + +pub mod pkg; +pub use pkg::pkg_ui; +pub mod repo; +pub use repo::repo_ui; + +pub fn arch_card(a: &str, repo_name: &str, current: bool) -> PreEscaped { + let url = if current { + &format!("/{repo_name}") + } else { + &format!("/{repo_name}?arch={a}") + }; + + let link = Link(&url, Text(&a).sm().medium().color(&Gray::_300)).use_htmx(); + + Rounded( + Padding(if current { + Background(Blue::_500, link) + } else { + Background(Gray::_700, link) + }) + .x(ScreenValue::_3) + .y(ScreenValue::_1), + ) + .size(Size::Full) + .render() +} + +pub fn take_out(v: &mut Vec, f: impl Fn(&T) -> bool) -> (&mut Vec, Option) { + let mut index = -1; + + for (i, e) in v.iter().enumerate() { + if f(e) { + index = i as i64; + } + } + + if index != -1 { + let e = v.remove(index as usize); + return (v, Some(e)); + } + + (v, None) +} diff --git a/src/routes/ui/pkg.rs b/src/routes/ui/pkg.rs new file mode 100644 index 0000000..4c922f3 --- /dev/null +++ b/src/routes/ui/pkg.rs @@ -0,0 +1,567 @@ +use based::request::{RequestContext, StringResponse}; +use based::ui::primitives::flex::Strategy; +use based::ui::primitives::space::SpaceBetweenWidget; +use based::ui::primitives::text::{Code, TextWidget}; +use based::ui::{UIWidget, prelude::*}; +use maud::{PreEscaped, Render, html}; +use rocket::get; + +use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name}; + +use crate::routes::render; + +use super::take_out; + +#[get("//?")] +pub async fn pkg_ui( + repo: &str, + pkg_name: &str, + ctx: RequestContext, + ver: Option<&str>, +) -> Option { + let repo = Repository::new(repo).unwrap(); + let mut pkg = repo.get_pkg_by_name(pkg_name)?; + + if let Some(ver) = ver { + let (version, rel) = Package::version(ver); + pkg = pkg.get_version(&version); + pkg.rel = rel; + } + + let versions = pkg.versions(); + let arch = pkg.arch(); + let install_script = pkg.install_script(); + let systemd_units = pkg.systemd_units(); + let pacman_hooks = pkg.pacman_hooks(); + let binaries = pkg.binaries(); + let man_entries = pkg.man_entries(); + let etc_entries = pkg.etc_entries(); + let kernel_modules = pkg.kernel_modules(); + let firmware = pkg.firmware(); + let keyrings = pkg.keyrings(); + let shared_objects = pkg.shared_objects(); + let icons = pkg.icons(); + let readmes = pkg.readmes(); + let mut pkginfo = pkg.pkginfo(); + + let pkg_info_header = Flex( + // Package Name + Div().vanish() + .push( + Link(&format!("/{}", repo.name), + Text(&repo.name).bold().color(&Gray::_100)._3xl() + ).use_htmx() + ) + .push(Padding(Text("/").bold().color(&Gray::_400)).all(ScreenValue::_2)) + .push( + Padding(Text(&pkg.name)._3xl().bold().color(&Gray::_100)).right(ScreenValue::_2) + ) + .push_if(pkg.is_signed(), || { + Flex( + Padding( + Div().vanish() + .push(Span("✓")._2xl().bold().color(&Slate::_300)) + .push(Span("Signed").sm().medium().color(&Slate::_300)) + // TODO : Add more info: Who signed? + Public Key recv + ).right(ScreenValue::_4) + ).items_center().gap(ScreenValue::_2) + }) + .push_for_each(&arch, |arch: &Architecture| { + Rounded( + Padding( + Background(Gray::_700, + Span(&arch.to_string()).medium().sm()) + ).x(ScreenValue::_3).y(ScreenValue::_1) + ).size(Size::Full) + }) + .push(Margin( + Flex( + Padding( + Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200, + Rounded(Shadow::medium( + Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()), + Paragraph( + html! { + svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" { + path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; + }; + p class="ml-2 text-black" { "Download" }; + } + ).black().xs()))).size(Size::Large) + )) + ).x(ScreenValue::_2).y(ScreenValue::_2) + ).items_center() + ).left(ScreenValue::_4) + ) + ).wrap(Wrap::Wrap).gap(ScreenValue::_2).full_center().group(); + + let pkg_info_table = Padding( + Flex( + Div() + .vanish() + .push(Context(FlexGrow( + Strategy::Grow, + Animated(Background( + Gray::_800, + Shadow::large( + Rounded( + Margin( + Padding( + SpaceBetween( + Div() + .push(Context( + Text("Info") + .xl() + .semibold() + .color(&Gray::_300) + .underlined(), + )) + .push_some( + take_out(&mut pkginfo, |x| x.0 == "pkgdesc").1, + |x: (String, String)| build_info(x.0, x.1), + ) + .push_some( + take_out(&mut pkginfo, |x| x.0 == "packager").1, + |x: (String, String)| build_info(x.0, x.1), + ) + .push_some( + take_out(&mut pkginfo, |x| x.0 == "url").1, + |x: (String, String)| build_info(x.0, x.1), + ) + .push_some( + take_out(&mut pkginfo, |x| x.0 == "size").1, + |x: (String, String)| build_info(x.0, x.1), + ) + .push_for_each(&pkginfo, |(key, val)| { + build_info(key.clone(), val.clone()) + }), + ) + .y(ScreenValue::_2), + ) + .all(ScreenValue::_4), + ) + .all(ScreenValue::_2), + ) + .size(Size::Large), + ), + )), + ))) + .push( + SpaceBetween( + Padding(Background( + Gray::_800, + Shadow::large( + Rounded( + Margin( + Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on( + FlexGrow( + Strategy::Grow, + Div() + .vanish() + .push( + Text("Versions") + .xl() + .semibold() + .color(&Gray::_300), + ) + .push( + SpaceBetween(Div().vanish().push_for_each( + &versions, + |version: &String| { + Animated( + Link( + &format!( + "/{}/{}?ver={version}", + repo.name, &pkg.name + ), + if pkg + .version + .as_ref() + .map(|x| { + *x == Package::version( + &version, + ) + .0 + }) + .unwrap_or_default() + { + Text(&version) + .color(&Blue::_500) + .render() + } else { + Hover( + Text("") + .color(&Blue::_400), + ) + .on(Text(&version) + .color(&Gray::_100)) + .render() + }, + ) + .use_htmx(), + ) + }, + )) + .y(ScreenValue::_1), + ), + ), + ), + ) + .all(ScreenValue::_2), + ) + .size(Size::Large), + ), + )) + .all(ScreenValue::_4), + ) + .y(ScreenValue::_2), + ), + ) + .wrap(Wrap::Wrap) + .group(), + ) + .top(ScreenValue::_6); + + let pkg_content = Padding( + Flex( + SpaceBetween( + Div() + .vanish() + .push(Text("Content").xl().bold().color(&Gray::_300)) + .push( + Flex( + Div() + .vanish() + .push_if(!systemd_units.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Systemd Units")) + .push(ListElements(&systemd_units)), + ) + }) + .push_if(!pacman_hooks.is_empty(), || { + InfoCard( + Div().vanish().push(CardTitle("Pacman Hooks")).push( + Paragraph( + SpaceBetween(Div().vanish().push_for_each( + &pacman_hooks, + |hook: &(String, String)| { + Div() + .vanish() + .push( + Text(&hook.0) + .xl() + .semibold() + .color(&Gray::_300), + ) + .push( + Padding( + Rounded(Background( + Gray::_700, + Code(&hook.1) + .sm() + .color(&Gray::_100), + )) + .size(Size::Large), + ) + .all(ScreenValue::_4), + ) + }, + )) + .y(ScreenValue::_1), + ) + .list_style(ListStyle::Disc) + .color(&Gray::_300), + ), + ) + }) + .push_if(!readmes.is_empty(), || { + InfoCard( + Div().vanish().push(CardTitle("READMEs")).push( + Paragraph( + SpaceBetween(Div().vanish().push_for_each( + &readmes, + |readme: &(String, String)| { + Div() + .vanish() + .push( + Text(&readme.0) + .xl() + .semibold() + .color(&Gray::_300), + ) + .push( + Padding( + Rounded(Background( + Gray::_700, + Code(&readme.1) + .sm() + .color(&Gray::_100), + )) + .size(Size::Large), + ) + .all(ScreenValue::_4), + ) + }, + )) + .y(ScreenValue::_1), + ) + .list_style(ListStyle::Disc) + .color(&Gray::_300), + ), + ) + }) + .push_if(!binaries.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Binaries")) + .push(ListElements(&binaries)), + ) + }) + .push_if(!kernel_modules.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Kernel Modules")) + .push(ListElements(&kernel_modules)), + ) + }) + .push_if(!shared_objects.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Shared Objects (Libraries)")) + .push(ListElements(&shared_objects)), + ) + }) + .push_if(!firmware.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Firmware")) + .push(ListElements(&firmware)), + ) + }) + .push_if(!keyrings.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Pacman Keyrings")) + .push(ListElements(&keyrings)), + ) + }) + .push_if(!icons.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Icons")) + .push(ListElements(&icons)), + ) + }) + .push_if(!man_entries.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Man Entries")) + .push(ListElements(&man_entries)), + ) + }) + .push_if(!etc_entries.is_empty(), || { + InfoCard( + Div() + .vanish() + .push(CardTitle("Config Files")) + .push(ListElements(&etc_entries)), + ) + }) + .push(InfoCard( + Div() + .vanish() + .push(CardTitle("Package Files")) + .push(ListElements(&pkg.file_list())), + )), + ) + .group() + .wrap(Wrap::Wrap), + ), + ) + .y(ScreenValue::_2), + ) + .wrap(Wrap::Wrap) + .group(), + ) + .top(ScreenValue::_6); + + let content = Div() + .vanish() + .push(pkg_info_header) + .push(pkg_info_table) + .push(pkg_content) + .push_some(install_script.as_ref(), |install_script: &String| { + SpaceBetween( + Margin( + Div() + .push( + Text("Install Script") + ._3xl() + .semibold() + .color(&Gray::_300) + .indentation(ScreenValue::_2), + ) + .push( + Rounded( + Padding(Background( + Gray::_700, + Code(install_script.trim()) + .color(&Gray::_100) + .sm() + .indentation(ScreenValue::_0), + )) + .all(ScreenValue::_2), + ) + .size(Size::Large), + ), + ) + .top(ScreenValue::_6), + ) + .y(ScreenValue::_4) + }) + .render(); + + Some(render(content, pkg_name, ctx).await) +} + +pub fn build_info(key: String, value: String) -> PreEscaped { + match key.as_str() { + "pkgname" => {} + "xdata" => {} + "arch" => {} + "pkbase" => {} + "pkgver" => {} + "pkgdesc" => { + return key_value("Description".to_string(), value); + } + "packager" => { + return key_value("Packager".to_string(), value); + } + "url" => { + return Flex( + Div() + .vanish() + .push(Width(ScreenValue::_32, Span("Website: ").bold())) + .push( + Margin(Link(&value, Span(&value).color(&Blue::_400))).left(ScreenValue::_6), + ), + ) + .group() + .render(); + } + "builddate" => { + let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap(); + return key_value( + "Build at".to_string(), + date.format("%d.%m.%Y %H:%M").to_string(), + ); + } + "depend" => { + return pkg_list_info("Depends on", &value); + } + "makedepend" => { + return pkg_list_info("Build Dependencies", &value); + } + "license" => { + return key_value("License".to_string(), value); + } + "size" => { + return key_value( + "Size".to_string(), + bytesize::to_string(value.parse().unwrap(), false), + ); + } + _ => { + log::warn!("Unhandled PKGINFO {key} = {value}"); + } + } + + html! {} +} + +pub fn key_value(key: String, value: String) -> PreEscaped { + Flex( + Div() + .vanish() + .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) + .push(Margin(Span(&value)).left(ScreenValue::_6)), + ) + .items_center() + .group() + .render() +} + +#[allow(non_snake_case)] +pub fn ListElements(el: &[String]) -> TextWidget { + Paragraph( + SpaceBetween(html! { + @for unit in el { + li { (unit) } + } + }) + .y(ScreenValue::_1), + ) + .list_style(ListStyle::Disc) + .color(&Gray::_300) +} + +#[allow(non_snake_case)] +pub fn CardTitle(title: &str) -> PreEscaped { + Context(Text(title).large().medium().color(&Gray::_100).underlined()) +} + +#[allow(non_snake_case)] +pub fn InfoCard(inner: T) -> SpaceBetweenWidget { + SpaceBetween( + Padding(Background( + Gray::_800, + Shadow::large( + Rounded(Margin(FlexGrow(Strategy::Grow, inner)).all(ScreenValue::_2)) + .size(Size::Large), + ), + )) + .all(ScreenValue::_4), + ) + .y(ScreenValue::_2) +} + +pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped { + let pkgs = value.split_whitespace().map(|pkg| { + if let Some(pkg_url) = find_pkg_url(pkg) { + Margin(Link(&pkg_url, Text(&pkg).color(&Blue::_400)).use_htmx()).left(ScreenValue::_6) + } else { + Margin(Span(&pkg)).left(ScreenValue::_6) + } + }); + + html! { + (Flex( + Div().vanish() + .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) + .push(Flex( + html! { + @for pkg in pkgs { + (pkg) + } + } + ).group().wrap(Wrap::Wrap)) + ).items_center().group()) + } +} + +pub fn find_pkg_url(pkg: &str) -> Option { + if let Some(pkg) = find_package_by_name(pkg) { + return Some(format!("/{}/{}", pkg.repo, pkg.name)); + } + + None +} diff --git a/src/routes/ui/repo.rs b/src/routes/ui/repo.rs new file mode 100644 index 0000000..28630a6 --- /dev/null +++ b/src/routes/ui/repo.rs @@ -0,0 +1,163 @@ +use based::request::{RequestContext, StringResponse}; +use based::ui::prelude::*; +use maud::{PreEscaped, Render, html}; +use rocket::{State, get}; + +use pacco::pkg::{Repository, arch::Architecture}; + +use crate::config::Config; +use crate::routes::ui::arch_card; + +use super::render; + +#[get("/?")] +pub async fn repo_ui( + repo: &str, + ctx: RequestContext, + arch: Option<&str>, + config: &State, +) -> 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() + }; + + // 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( + Blue::_500, + Rounded(Margin( + Padding(Text("Mirrored").sm().medium().white()) + .x(ScreenValue::_3) + .y(ScreenValue::_1), + )) + .size(Size::Full), + ) + }) + .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(Gray::_600, Nothing())).on(Background( + Gray::_700, + 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), + )), + ) + .all(ScreenValue::_4), + ) + .items_center() + .gap(ScreenValue::_4), + ) + })) + .y(ScreenValue::_4); + + let content = Div().vanish().push(repo_info).push(package_list).render(); + + render(content, &repo.name, ctx).await +} + +#[allow(non_snake_case)] +pub fn FirstLetterCircle(name: &str) -> PreEscaped { + Sized( + ScreenValue::_10, + ScreenValue::_10, + Rounded(Background( + Blue::_500, + Flex( + Text( + &name + .chars() + .next() + .unwrap_or_default() + .to_uppercase() + .to_string(), + ) + .white() + .semibold(), + ) + .full_center() + .group(), + )) + .size(Size::Full), + ) + .render() +} + +#[allow(non_snake_case)] +pub fn SignedInfo() -> PreEscaped { + 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() +}