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 }