finish ui

This commit is contained in:
JMARyA 2025-01-17 17:52:28 +01:00
parent 36128864aa
commit 6c1ca1e8c0
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
4 changed files with 421 additions and 262 deletions

10
Cargo.lock generated
View file

@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "based" name = "based"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#78e3d6b798eb32d85ab58dd8891457f7ab6f75aa" source = "git+https://git.hydrar.de/jmarya/based?branch=ui#79f08fd202abcfbc52cbab09be7dccc02f4c7c01"
dependencies = [ dependencies = [
"bcrypt", "bcrypt",
"chrono", "chrono",
@ -313,9 +313,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.9" version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -3364,9 +3364,9 @@ dependencies = [
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"

View file

@ -2,7 +2,7 @@ use based::auth::MaybeUser;
use based::request::{RawResponse, RequestContext, StringResponse, respond_with}; use based::request::{RawResponse, RequestContext, StringResponse, respond_with};
use based::ui::components::Shell; use based::ui::components::Shell;
use based::ui::{prelude::*, render_page}; use based::ui::{prelude::*, render_page};
use maud::{PreEscaped, html}; use maud::{PreEscaped, Render, html};
use pacco::pkg::mirror::MirrorRepository; use pacco::pkg::mirror::MirrorRepository;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::{State, get}; use rocket::{State, get};
@ -24,35 +24,39 @@ pub async fn index_page(
) -> StringResponse { ) -> StringResponse {
let repos: Vec<String> = Repository::list(); let repos: Vec<String> = Repository::list();
let content = html!( let content = Div().vanish()
(Flex(html! { .push(
h1 class="text-4xl font-bold pb-6" { "Repositories" }; Flex(
@if let Some(user) = user.take_user() { Div().vanish()
a class="text-lg" href="/account" { (user.username) }; .push(Padding(Text("Repositories")._4xl().bold()).bottom(ScreenValue::_6))
}; .push_some(user.take_user().as_ref(), |user: &based::auth::User| Link("/account", Text(&user.username).large()))
}).justify(Justify::Between)) ).justify(Justify::Between)
).push(
div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" { html!(
@for repo in repos { div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" {
// TODO : Implement class transition @for repo in repos {
(Link(&format!("/{repo}"), (Animated(
Rounded( Rounded(
Padding( Padding(
Shadow::medium( Shadow::medium(
Flex(Hover( Flex(Hover(
Background(Gray::_600, Nothing()), Background(Gray::_600, Nothing())).on(
Background(Gray::_700, Background(Gray::_700,
Div().vanish().push(Text(&repo).medium().color(&Gray::_100)) Link(
.push(html! { &format!("/{repo}"),
@if config.is_mirrored_repo(&repo) { Div().vanish().push(Text(&repo).medium().color(&Gray::_100))
(Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)) .push_some(
}; if config.is_mirrored_repo(&repo) {
}) Some(&())
))).items_center().gap(4))).all(ScreenValue::_4)) } else { None },
.size(Size::Large))) |_: &()| Context(Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)))).use_htmx()
))).items_center().gap(ScreenValue::_4)
)).all(ScreenValue::_4))
.size(Size::Large)))
}
} }
} )
); ).render();
render(content, "Repositories", ctx).await render(content, "Repositories", ctx).await
} }

View file

@ -1,5 +1,8 @@
use based::request::{RequestContext, StringResponse}; use based::request::{RequestContext, StringResponse};
use based::ui::prelude::*; 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 maud::{PreEscaped, Render, html};
use rocket::{State, get}; use rocket::{State, get};
@ -35,150 +38,212 @@ pub async fn pkg_ui(
let binaries = pkg.binaries(); let binaries = pkg.binaries();
let mut pkginfo = pkg.pkginfo(); let mut pkginfo = pkg.pkginfo();
let content = html! { let content = Div().vanish()
// Package Name .push(
div class="flex flex-wrap gap-2 justify-center items-center" { Flex(
(Link(&format!("/{}", repo.name), // Package Name
Div().vanish()
.push(
Link(&format!("/{}", repo.name),
Text(&repo.name).bold().color(&Gray::_100)._3xl() Text(&repo.name).bold().color(&Gray::_100)._3xl()
).use_htmx()) ).use_htmx()
)
p class="font-bold p-2 text-gray-400" { "/" }; .push(Padding(Text("/").bold().color(&Gray::_400)).all(ScreenValue::_2))
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-2" { .push(
(pkg.name) Padding(Text(&pkg.name)._3xl().bold().color(&Gray::_100)).right(ScreenValue::_2)
}; )
.push_if(pkg.is_signed(), || {
@if pkg.is_signed() { Flex(
div class="flex items-center gap-2 text-slate-300 pr-4" { Padding(
(Span("")._2xl().bold()) Div().vanish()
(Span("Signed").sm().medium()) .push(Span("")._2xl().bold().color(&Slate::_300))
.push(Span("Signed").sm().medium().color(&Slate::_300))
// TODO : Add more info: Who signed? + Public Key recv // TODO : Add more info: Who signed? + Public Key recv
} ).right(ScreenValue::_4)
} ).items_center().gap(ScreenValue::_2)
})
@for arch in arch { .push_for_each(&arch, |arch: &Architecture| {
(Rounded( Rounded(
Padding( Padding(
Background(Gray::_700, Background(Gray::_700,
Span(&arch.to_string()).medium().sm()) Span(&arch.to_string()).medium().sm())
).x(ScreenValue::_3).y(ScreenValue::_1) ).x(ScreenValue::_3).y(ScreenValue::_1)
).size(Size::Full) ).size(Size::Full)
) })
} .push(Margin(
Flex(
a href=(format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name())) class="ml-4 inline-flex items-center px-2 py-2 bg-gray-200 text-black font-xs rounded-lg shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" { Padding(
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" { Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200,
path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; Rounded(Shadow::medium(
}; Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()),
p class="ml-2" { "Download" }; 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" {};
};
div class="flex flex-wrap pt-6" { p class="ml-2 text-black" { "Download" };
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition flex-1 m-2 grow" { }
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 underline" { "Info" }; ).black().xs()))).size(Size::Large)
))
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1 { ).x(ScreenValue::_2).y(ScreenValue::_2)
(build_info(desc.0, desc.1)) ).items_center()
} ).left(ScreenValue::_4)
)
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "packager" }).1 { ).wrap(Wrap::Wrap).gap(ScreenValue::_2).full_center().group()
(build_info(desc.0, desc.1)) )
} .push(
Padding(
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "url" }).1 { Flex(
(build_info(desc.0, desc.1)) Div().vanish().push(
} Context(
FlexGrow(Strategy::Grow,
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "size" }).1 { Animated(Background(Gray::_800, Shadow::large(Rounded(Margin(Padding(SpaceBetween(
(build_info(desc.0, desc.1)) Div()
} .push(
Context(Text("Info").xl().semibold().color(&Gray::_300).underlined())
@for (key, val) in pkginfo { )
(build_info(key, val)) .push_some(
}; take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1,
}; |x: (String, String)| build_info(x.0, x.1)
)
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow md:grow-0 md:shrink" { .push_some(
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { "Versions" } take_out(&mut pkginfo, |x| { x.0 == "packager" }).1,
ul class="space-y-1" { |x: (String, String)| build_info(x.0, x.1)
@for version in versions { )
li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" { .push_some(
(Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), take_out(&mut pkginfo, |x| { x.0 == "url" }).1,
if pkg.version.as_ref() |x: (String, String)| build_info(x.0, x.1)
.map(|x| *x == Package::version(&version).0).unwrap_or_default() )
{ Text(&version).color(&Blue::_500) } else { Text(&version) } .push_some(
).use_htmx()) 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(
div class="flex flex-wrap pt-6" { Padding(
div class="space-y-2" { Background(Gray::_800,
h2 class="text-xl font-bold text-gray-700 dark:text-gray-300" { "Content" } Shadow::large(
Rounded(
div class="flex flex-wrap" { Margin(
@if !systemd_units.is_empty() { Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on(
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { FlexGrow(Strategy::Grow,
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Systemd Units" } Div().vanish()
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { .push(
@for unit in systemd_units { Text("Versions").xl().semibold().color(&Gray::_300)
li { (unit) } )
} .push(
} SpaceBetween(
} Div().vanish()
} .push_for_each(&versions, |version: &String| {
Animated(Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name),
@if !pacman_hooks.is_empty() { if pkg.version.as_ref()
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { .map(|x| *x == Package::version(&version).0).unwrap_or_default()
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Pacman Hooks" } { Text(&version).color(&Blue::_500).render() } else {
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { Hover(Text("").color(&Blue::_400)).on(
@for hook in pacman_hooks { Text(&version).color(&Gray::_100)).render()
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { (hook.0) } }
).use_htmx())
pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { })
(hook.1) ).y(ScreenValue::_1)
} )
} ))
} ).all(ScreenValue::_2)
} ).size(Size::Large)
} )
)
@if !binaries.is_empty() { ).all(ScreenValue::_4)
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { ).y(ScreenValue::_2)
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Binaries" } )
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { ).wrap(Wrap::Wrap).group()
@for binary in binaries { ).top(ScreenValue::_6)
li { (binary) } ).push(
} Padding(Flex(
} SpaceBetween(
} Div().vanish()
} .push(
Text("Content").xl().bold().color(&Gray::_300)
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { )
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Package Files" } .push(
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { Flex(
@for file in pkg.file_list() { Div().vanish()
li { (file) } .push_if(!systemd_units.is_empty(), || {
} InfoCard(
} Div().vanish()
} .push(
} CardTitle("Systemd Units")
}; )
}; .push(
ListElements(&systemd_units)
// Install Script )
@if let Some(install_script) = install_script { )
div class="space-y-4 pt-6" { })
h2 class="text-3xl font-semibold text-gray-700 dark:text-gray-300" { "Install Script" } .push_if(!pacman_hooks.is_empty(), || {
pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { InfoCard(
(install_script) 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)
)
})
.push(
html! {
@for unit in &systemd_units {
li { (unit) }
}
})
).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(
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) Some(render(content, pkg_name, ctx).await)
} }
@ -222,73 +287,126 @@ pub async fn repo_ui(
repo.list_pkg() repo.list_pkg()
}; };
let content = html! { let repo_info = Margin(
// Repository name and architectures // Repository name and architectures
div class="flex flex-wrap items-center justify-center mb-6" { Flex(
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100" { Div()
(repo.name) .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),
)
.left(ScreenValue::_4),
)
.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),
)
.left(ScreenValue::_4)
.top(ScreenValue::_2)),
),
)
.wrap(Wrap::Wrap)
.full_center()
.group(),
)
.bottom(ScreenValue::_6);
@if config.is_mirrored_repo(&repo.name) { let package_list = SpaceBetween(Div().vanish().push_for_each(&packages, |pkg| {
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full ml-4" { "Mirrored" }; Animated(
}; Flex(
Padding(
div class="flex gap-2 mt-2 md:mt-0 ml-4" { Hover(Background(Gray::_600, Nothing())).on(Background(
@for a in architectures { Gray::_700,
@if let Some(arch) = arch.as_ref() { Rounded(Shadow::medium(
@if arch.to_string() == a { Link(
(arch_card(&a, &repo.name, true)) &format!("/{}/{pkg}", repo.name),
} @else { Div()
(arch_card(&a, &repo.name, false)) .vanish()
} .push(Context(Sized(
} @else { ScreenValue::_10,
(arch_card(&a, &repo.name, false)) ScreenValue::_10,
} Rounded(Background(
} Blue::_500,
} Flex(
}; Text(
&pkg.chars()
// Package list .next()
ul class="space-y-4" { .unwrap_or_default()
@for pkg in packages { .to_uppercase()
( .to_string(),
Link( )
&format!("/{}/{pkg}", repo.name), .white()
// TODO : Implement transition class .semibold(),
Flex(
Padding(
Hover(
Background(Gray::_600, Nothing()),
Background(Gray::_700,
Rounded(
Shadow::medium(
Div().vanish()
.push(
html! { (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(html! { (Width(ScreenValue::fit, Text(&pkg).medium().color(&Gray::_100))) })
.push(html! {
(
Flex(
Padding(
Div().vanish()
.push(Span("")._2xl().bold().color(&Slate::_300))
.push(Span("Signed").sm().medium().color(&Slate::_300))
).right(ScreenValue::_4)
).items_center().gap(2).group()
) )
}) .full_center()
) .group(),
).size(Size::Large)))).all(ScreenValue::_4)).items_center().gap(4) ))
).use_htmx() .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 render(content, &repo.name, ctx).await
} }
@ -307,12 +425,16 @@ pub fn build_info(key: String, value: String) -> PreEscaped<String> {
return key_value("Packager".to_string(), value); return key_value("Packager".to_string(), value);
} }
"url" => { "url" => {
return html! { return Flex(
div class="flex" { Div()
span class="font-bold w-32" { (format!("Website: ")) }; .vanish()
a class="ml-2 text-blue-400" href=(value) { (value) }; .push(Width(ScreenValue::_32, Span("Website: ").bold()))
}; .push(
}; Margin(Link(&value, Span(&value).color(&Blue::_400))).left(ScreenValue::_6),
),
)
.group()
.render();
} }
"builddate" => { "builddate" => {
let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap(); let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap();
@ -349,7 +471,7 @@ pub fn key_value(key: String, value: String) -> PreEscaped<String> {
Div() Div()
.vanish() .vanish()
.push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold()))
.push(Margin(Span(&value)).left(ScreenValue::_2)), .push(Margin(Span(&value)).left(ScreenValue::_6)),
) )
.items_center() .items_center()
.group() .group()
@ -384,24 +506,57 @@ pub fn find_pkg_url(pkg: &str) -> Option<String> {
pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> { pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> {
let pkgs = value.split_whitespace().map(|pkg| { let pkgs = value.split_whitespace().map(|pkg| {
if let Some(pkg_url) = find_pkg_url(pkg) { if let Some(pkg_url) = find_pkg_url(pkg) {
html! { Margin(Link(&pkg_url, Text(&pkg).color(&Blue::_400)).use_htmx()).left(ScreenValue::_6)
a href=(pkg_url) class="ml-2 text-blue-400" { (pkg) };
}
} else { } else {
html! { Margin(Span(&pkg)).left(ScreenValue::_6)
span class="ml-2" { (pkg) };
}
} }
}); });
html! { html! {
(Flex(html! { (Flex(
span class="font-bold w-32" { (format!("{key}: ")) }; Div().vanish()
div class="flex flex-wrap" { .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold()))
@for pkg in pkgs { .push(Flex(
(pkg) html! {
}; @for pkg in pkgs {
}; (pkg)
}).items_center().group()) }
}
).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<String> {
Context(Text(title).large().medium().color(&Gray::_100).underlined())
}
#[allow(non_snake_case)]
pub fn InfoCard<T: UIWidget + 'static>(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)
}

View file

@ -128,7 +128,7 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
(Link("/passwd", Margin( (Link("/passwd", Margin(
Rounded(Padding( Rounded(Padding(
Hover( Hover(
Background(Green::_600, Nothing()), Background(Green::_600, Nothing())).on(
Background(Green::_500, Text("Change Password").white()) Background(Green::_500, Text("Change Password").white())
) )
).x(ScreenValue::_6).y(ScreenValue::_2)) ).x(ScreenValue::_6).y(ScreenValue::_2))