diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-27 14:40:15 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-27 14:40:15 +0100 |
| commit | c05bfcc2775f0e11db6e856bfcf06d0419c35d54 (patch) | |
| tree | ffd0e9fcf6b476a6198287085a514cfa7940c200 /ui/src | |
| parent | 4ba86694e393c61107e27c4127efc0455b329524 (diff) | |
| download | jellything-c05bfcc2775f0e11db6e856bfcf06d0419c35d54.tar jellything-c05bfcc2775f0e11db6e856bfcf06d0419c35d54.tar.bz2 jellything-c05bfcc2775f0e11db6e856bfcf06d0419c35d54.tar.zst | |
ui changed before object slices
Diffstat (limited to 'ui/src')
| -rw-r--r-- | ui/src/components/admin.rs | 37 | ||||
| -rw-r--r-- | ui/src/components/admin_log.rs | 6 | ||||
| -rw-r--r-- | ui/src/components/home.rs | 38 | ||||
| -rw-r--r-- | ui/src/components/items.rs | 22 | ||||
| -rw-r--r-- | ui/src/components/login.rs | 15 | ||||
| -rw-r--r-- | ui/src/components/message.rs | 6 | ||||
| -rw-r--r-- | ui/src/components/mod.rs | 61 | ||||
| -rw-r--r-- | ui/src/components/node_card.rs | 18 | ||||
| -rw-r--r-- | ui/src/components/node_list.rs | 47 | ||||
| -rw-r--r-- | ui/src/components/node_page.rs | 29 | ||||
| -rw-r--r-- | ui/src/components/props.rs | 9 | ||||
| -rw-r--r-- | ui/src/components/stats.rs | 36 | ||||
| -rw-r--r-- | ui/src/components/user.rs | 7 | ||||
| -rw-r--r-- | ui/src/lib.rs | 54 | ||||
| -rw-r--r-- | ui/src/scaffold.rs | 26 |
15 files changed, 198 insertions, 213 deletions
diff --git a/ui/src/components/admin.rs b/ui/src/components/admin.rs index 3cb45d6..76dfe6a 100644 --- a/ui/src/components/admin.rs +++ b/ui/src/components/admin.rs @@ -4,9 +4,8 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::RenderInfo; +use crate::{RenderInfo, page}; use jellycommon::{ - jellyobject::Object, routes::{ u_admin_import, u_admin_import_post, u_admin_log, u_admin_new_user, u_admin_user, u_admin_user_remove, u_admin_users, @@ -15,6 +14,16 @@ use jellycommon::{ }; use jellyui_locale::tr; +page!(AdminDashboard<'_>, |x| tr(x.ri.lang, "admin.dashboard")); +page!(AdminImport<'_>, |x| tr(x.ri.lang, "admin.import")); +page!(AdminUserList<'_>, |x| tr(x.ri.lang, "admin.users")); +page!(AdminUser<'_>, |x| x + .user + .get(USER_NAME) + .unwrap_or("nameless user") + .to_string() + .into()); + markup::define!( AdminDashboard<'a>(ri: &'a RenderInfo<'a>) { h1 { @tr(ri.lang, "admin.dashboard") } @@ -22,23 +31,22 @@ markup::define!( li{a[href=u_admin_log(true)] { @tr(ri.lang, "admin.log.warnonly") }} li{a[href=u_admin_log(false)] { @tr(ri.lang, "admin.log.full") }} } - a[href=u_admin_import()] { h2 { @tr(ri.lang, "admin.import") }} a[href=u_admin_users()] { h2 { @tr(ri.lang, "admin.users") }} } - AdminImport<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) { - @if data.has(ADMIN_IMPORT_BUSY.0) { + AdminImport<'a>(ri: &'a RenderInfo<'a>, errors: &'a [&'a str], busy: bool) { + @if *busy { h1 { @tr(ri.lang, "admin.import.running") } noscript { "Live import progress needs javascript." } div[id="admin_import"] {} } else { h1 { @tr(ri.lang, "admin.import") } - @if data.has(ADMIN_IMPORT_ERROR.0) { + @if !errors.is_empty() { section.message.error { details { - summary { p.error { @tr(ri.lang, "admin.import_errors").replace("{n}", &data.iter(ADMIN_IMPORT_ERROR).count().to_string()) } } - ol { @for e in data.iter(ADMIN_IMPORT_ERROR) { + summary { p.error { @tr(ri.lang, "admin.import_errors").replace("{n}", &errors.len().to_string()) } } + ol { @for e in *errors { li.error { pre.error { @e } } }} } @@ -53,24 +61,19 @@ markup::define!( } } - AdminInfo<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) { - @let _ = ri; - h2 { @data.get(ADMIN_INFO_TITLE) } - pre { @data.get(ADMIN_INFO_TEXT) } - } - - AdminUserList<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) { + AdminUserList<'a>(ri: &'a RenderInfo<'a>, users: &'a [User<'a>]) { h1 { @tr(ri.lang, "admin.users") } form[method="POST", action=u_admin_new_user()] { label[for="login"] { "Login: " } input[type="text", id="login", name="login"]; br; input[type="submit", value="Create new user"]; } - ul { @for u in data.iter(ADMIN_USER_LIST_ITEM) { + ul { @for u in *users { li { a[href=u_admin_user(u.get(USER_LOGIN).unwrap_or_default())] { @u.get(USER_LOGIN) } } }} } - AdminUser<'a>(ri: &'a RenderInfo<'a>, user: Object<'a>) { + + AdminUser<'a>(ri: &'a RenderInfo<'a>, user: User<'a>) { h2 { @user.get(USER_NAME).unwrap_or("nameless user") } p { @tr(ri.lang, "tag.Ulgn") ": " @user.get(USER_LOGIN) } form[method="POST", action=u_admin_user_remove(user.get(USER_LOGIN).unwrap())] { diff --git a/ui/src/components/admin_log.rs b/ui/src/components/admin_log.rs index abec7fe..f521aae 100644 --- a/ui/src/components/admin_log.rs +++ b/ui/src/components/admin_log.rs @@ -4,6 +4,7 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ +use crate::{RenderInfo, page}; use jellycommon::{ internal::{LogLevel, LogLine}, routes::u_admin_log, @@ -11,8 +12,11 @@ use jellycommon::{ use markup::raw; use std::fmt::Write; +page!(ServerLogPage<'_>, |_| "Server Log".into()); + markup::define! { - ServerLogPage<'a>(warnonly: bool, messages: &'a [String]) { + ServerLogPage<'a>(ri: &'a RenderInfo<'a>, warnonly: bool, messages: &'a [String]) { + @let _ = ri; h1 { "Server Log" } a[href=u_admin_log(!warnonly)] { @if *warnonly { "Show everything" } else { "Show only warnings" }} code.log[id="log"] { diff --git a/ui/src/components/home.rs b/ui/src/components/home.rs new file mode 100644 index 0000000..5ae6a66 --- /dev/null +++ b/ui/src/components/home.rs @@ -0,0 +1,38 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{ + RenderInfo, + components::node_card::{NodeCard, NodeCardHightlight}, + page, +}; +use jellycommon::Nku; +use jellyui_locale::tr; + +pub enum HomeRow { + Inline(Vec<Nku<'a>>), + Highlight(Nku<'a>), +} + +page!(Home<'_>, |x| tr(x.ri.lang, "home")); + +markup::define! { + Home<'a>(ri: &'a RenderInfo<'a>, rows: &'a [(&'a str, HomeRow<'a>)]) { + @for (title, row) in *rows { + h2 { @tr(ri.lang, title) } + @match row { + HomeRow::Inline(nkus) => { + ul.nl.inline { @for nku in nkus { + li { @NodeCard { ri, nku } } + }} + } + HomeRow::Highlight(nku) => { + @NodeCardHightlight { ri, nku } + } + } + } + } +} diff --git a/ui/src/components/items.rs b/ui/src/components/items.rs new file mode 100644 index 0000000..c9e0d41 --- /dev/null +++ b/ui/src/components/items.rs @@ -0,0 +1,22 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{RenderInfo, components::node_card::NodeCard, page}; +use jellycommon::{Nku, routes::u_items_cont}; +use jellyui_locale::tr; + +page!(Items<'_>, |x| tr(x.ri.lang, "items")); + +markup::define! { + Items<'a>(ri: &'a RenderInfo<'a>, items: &'a [Nku<'a>], cont: Option<String>) { + ul.nl.grid { @for nku in *items { + li { @NodeCard { ri, nku } } + }} + @if let Some(cont) = cont { + a.next_page[href=u_items_cont(cont)] { button { "Show more" } } + } + } +} diff --git a/ui/src/components/login.rs b/ui/src/components/login.rs index d291165..7d19a38 100644 --- a/ui/src/components/login.rs +++ b/ui/src/components/login.rs @@ -4,19 +4,24 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use jellycommon::{SETPW_PASSWORD, SETPW_USERNAME, jellyobject::Object}; +use crate::{RenderInfo, page}; use jellyui_locale::tr; -use crate::RenderInfo; +page!(AccountLogin<'_>, |x| tr(x.ri.lang, "account.login")); +page!(AccountLogout<'_>, |x| tr(x.ri.lang, "account.logout")); +page!(AccountSetPassword<'_>, |x| tr( + x.ri.lang, + "account.login.set_password" +)); markup::define! { - AccountSetPassword<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) { + AccountSetPassword<'a>(ri: &'a RenderInfo<'a>, username: &'a str, password: &'a str) { form.account[method="POST", action=""] { h1 { @tr(ri.lang, "account.login.set_password") } p { @tr(ri.lang, "account.login.set_password.par") } - input[type="hidden", name="username", value=data.get(SETPW_USERNAME)]; - input[type="hidden", name="password", value=data.get(SETPW_PASSWORD)]; + input[type="hidden", name="username", value=username]; + input[type="hidden", name="password", value=password]; label[for="password"] { @tr(ri.lang, "account.new_password") } input[type="password", id="password", name="new_password"]; br; diff --git a/ui/src/components/message.rs b/ui/src/components/message.rs index a271d40..b4499ed 100644 --- a/ui/src/components/message.rs +++ b/ui/src/components/message.rs @@ -4,14 +4,12 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use crate::RenderInfo; -use jellycommon::{MESSAGE_KIND, MESSAGE_TEXT, jellyobject::Object}; use markup::define; define! { - Message<'a>(ri: &'a RenderInfo<'a>, message: Object<'a>) { + Message<'a>(ri: &'a RenderInfo<'a>, kind: &'a str, text: &'a str) { @let _ = ri; - @let text = message.get(MESSAGE_TEXT).unwrap_or_default(); - @match message.get(MESSAGE_KIND).unwrap_or("neutral") { + @match *kind { "success" => { section.message { p.success { @text } } } "error" => { section.message { p.error { @text } } } "neutral" | _ => { section.message { p { @text } } } diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index d87efab..6c4cc9d 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -9,66 +9,9 @@ pub mod admin_log; pub mod login; pub mod message; pub mod node_card; -pub mod node_list; pub mod node_page; pub mod props; pub mod stats; pub mod user; - -use crate::{ - RenderInfo, - components::{ - admin::{AdminDashboard, AdminImport, AdminInfo, AdminUser, AdminUserList}, - login::{AccountLogin, AccountLogout, AccountSetPassword}, - message::Message, - node_list::NodeList, - node_page::{NodePage, Player}, - user::UserSettings, - }, -}; -use jellycommon::{jellyobject::Object, *}; -use markup::define; - -define! { - View<'a>(ri: &'a RenderInfo<'a>, view: Object<'a>) { - @if let Some(message) = view.get(VIEW_MESSAGE) { - @Message { ri, message } - } - @if let Some(nku) = view.get(VIEW_NODE_PAGE) { - @NodePage { ri, nku } - } - @if let Some(nku) = view.get(VIEW_PLAYER) { - @Player { ri, nku } - } - @for nl in view.iter(VIEW_NODE_LIST) { - @NodeList { ri, nl } - } - @if let Some(()) = view.get(VIEW_ACCOUNT_LOGIN) { - @AccountLogin { ri } - } - @if let Some(()) = view.get(VIEW_ACCOUNT_LOGOUT) { - @AccountLogout{ ri } - } - @if let Some(data) = view.get(VIEW_ACCOUNT_SET_PASSWORD) { - @AccountSetPassword { ri, data } - } - @if let Some(()) = view.get(VIEW_ADMIN_DASHBOARD) { - @AdminDashboard { ri } - } - @if let Some(data) = view.get(VIEW_ADMIN_IMPORT) { - @AdminImport { ri, data } - } - @if let Some(data) = view.get(VIEW_ADMIN_INFO) { - @AdminInfo { ri, data } - } - @if let Some(user) = view.get(VIEW_USER_SETTINGS) { - @UserSettings { ri, user } - } - @if let Some(data) = view.get(VIEW_ADMIN_USER_LIST) { - @AdminUserList { ri, data } - } - @if let Some(user) = view.get(VIEW_ADMIN_USER) { - @AdminUser { ri, user } - } - } -} +pub mod items; +pub mod home; diff --git a/ui/src/components/node_card.rs b/ui/src/components/node_card.rs index d93825b..e1baec1 100644 --- a/ui/src/components/node_card.rs +++ b/ui/src/components/node_card.rs @@ -15,8 +15,8 @@ use jellycommon::{ }; markup::define! { - NodeCard<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { - @let node = nku.get(NKU_NODE).unwrap_or_default(); + NodeCard<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>) { + @let node = nku.node; @let slug = node.get(NO_SLUG).unwrap_or_default(); div[class=&format!("card {}", aspect_class(node))] { .poster { @@ -27,7 +27,7 @@ markup::define! { @if node.has(NO_TRACK.0) { a.play.icon[href=u_node_slug_player(&slug)] { "play_arrow" } } - @Props { ri, nku: *nku, full: false } + @Props { ri, nku, full: false } } } div.title { @@ -37,14 +37,14 @@ markup::define! { } div.subtitle { span { - @nku.get(NKU_ROLE).or(node.get(NO_SUBTITLE)) + @nku.role.or(node.get(NO_SUBTITLE)) } } } } - NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { - @let node = nku.get(NKU_NODE).unwrap_or_default(); + NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { + @let node = nku.node; @let slug = node.get(NO_SLUG).unwrap_or_default(); div[class="card wide"] { div[class=&format!("poster {}", aspect_class(node))] { @@ -59,14 +59,14 @@ markup::define! { } div.details { a.title[href=u_node_slug(&slug)] { @node.get(NO_TITLE) } - @Props { ri, nku: *nku ,full: false } + @Props { ri, nku ,full: false } span.overview { @node.get(NO_DESCRIPTION) } } } } - NodeCardHightlight<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { - @let node = nku.get(NKU_NODE).unwrap_or_default(); + NodeCardHightlight<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>) { + @let node = nku.node; @let slug = node.get(NO_SLUG).unwrap_or_default(); @let backdrop = u_image(node.get(NO_PICTURES).unwrap_or_default().get(PICT_BACKDROP).unwrap_or_default(), 2048); div[class="card highlight", style=format!("background-image: url(\"{backdrop}\")")] { diff --git a/ui/src/components/node_list.rs b/ui/src/components/node_list.rs deleted file mode 100644 index df405ea..0000000 --- a/ui/src/components/node_list.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of jellything (https://codeberg.org/metamuffin/jellything) - which is licensed under the GNU Affero General Public License (version 3); see /COPYING. - Copyright (C) 2026 metamuffin <metamuffin.org> -*/ - -use crate::{ - RenderInfo, - components::node_card::{NodeCard, NodeCardHightlight, NodeCardWide}, -}; -use jellycommon::{jellyobject::Object, routes::u_items_cont, *}; -use jellyui_locale::tr; - -markup::define! { - NodeList<'a>(ri: &'a RenderInfo<'a>, nl: Object<'a>) { - @let ds = nl.get(NODELIST_DISPLAYSTYLE).unwrap_or(NLSTYLE_GRID); - @if let Some(title) = nl.get(NODELIST_TITLE) { - h2 { @tr(ri.lang, title) } - } - @match ds { - NLSTYLE_GRID => { - ul.nl.grid { @for nku in nl.iter(NODELIST_ITEM) { - li { @NodeCard { ri, nku } } - }} - } - NLSTYLE_INLINE => { - ul.nl.inline { @for nku in nl.iter(NODELIST_ITEM) { - li { @NodeCard { ri, nku } } - }} - } - NLSTYLE_LIST => { - ol.nl.list { @for nku in nl.iter(NODELIST_ITEM) { - li { @NodeCardWide { ri, nku } } - }} - } - NLSTYLE_HIGHLIGHT => { - @if let Some(nku) = nl.get(NODELIST_ITEM) { - @NodeCardHightlight { ri, nku } - } - } - _ => {} - } - @if let Some(cont) = nl.get(NODELIST_CONTINUATION) { - a[href=u_items_cont(cont)] { button { "Show more" } } - } - } -} diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs index f40aa73..5cd71ce 100644 --- a/ui/src/components/node_page.rs +++ b/ui/src/components/node_page.rs @@ -4,7 +4,7 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{RenderInfo, components::props::Props}; +use crate::{RenderInfo, components::props::Props, page}; use jellycommon::{ jellyobject::{Object, Tag, TypedTag}, routes::{u_image, u_node_slug_player}, @@ -13,9 +13,24 @@ use jellycommon::{ use jellyui_locale::tr; use std::marker::PhantomData; +page!(NodePage<'_>, |x| x + .nku + .node + .get(NO_TITLE) + .unwrap_or_default() + .to_string() + .into()); +page!(Player<'_>, |x| x + .nku + .node + .get(NO_TITLE) + .unwrap_or_default() + .to_string() + .into()); + markup::define! { - NodePage<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { - @let node = nku.get(NKU_NODE).unwrap_or_default(); + NodePage<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { + @let node = nku.node; @let slug = node.get(NO_SLUG).unwrap_or_default(); @let pics = node.get(NO_PICTURES).unwrap_or_default(); @if let Some(path) = pics.get(PICT_BACKDROP) { @@ -59,7 +74,7 @@ markup::define! { } } .details { - @Props { ri, nku: *nku, full: true } + @Props { ri, nku, full: true } h3 { @node.get(NO_TAGLINE).unwrap_or_default() } @if let Some(description) = &node.get(NO_DESCRIPTION) { p { @for line in description.lines() { @line br; } } @@ -141,13 +156,11 @@ markup::define! { // } } - Player<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { + Player<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { @let _ = ri; - @let node = nku.get(NKU_NODE).unwrap_or_default(); - @let pics = node.get(NO_PICTURES).unwrap_or_default(); + @let pics = nku.node.get(NO_PICTURES).unwrap_or_default(); video[id="player", poster=pics.get(PICT_COVER).map(|p| u_image(p, 2048))] {} } - } // fn chapter_key_time(c: Object, dur: f64) -> f64 { diff --git a/ui/src/components/props.rs b/ui/src/components/props.rs index 5fa9d3e..e672919 100644 --- a/ui/src/components/props.rs +++ b/ui/src/components/props.rs @@ -9,16 +9,13 @@ use crate::{ format::{format_count, format_duration}, }; use chrono::DateTime; -use jellycommon::{ - jellyobject::{Object, TypedTag}, - *, -}; +use jellycommon::{jellyobject::TypedTag, *}; use jellyui_locale::tr; use std::marker::PhantomData; markup::define! { - Props<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>, full: bool) { - @let node = nku.get(NKU_NODE).unwrap_or_default(); + Props<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>, full: bool) { + @let node = nku.node; .props { @if let Some(dur) = node.get(NO_DURATION) { p { @format_duration(dur) } diff --git a/ui/src/components/stats.rs b/ui/src/components/stats.rs index 698430b..ce06843 100644 --- a/ui/src/components/stats.rs +++ b/ui/src/components/stats.rs @@ -8,27 +8,25 @@ use crate::{ RenderInfo, format::{format_duration, format_duration_long, format_size}, }; -use jellycommon::{jellyobject::Object, *}; +use jellycommon::*; use jellyui_locale::tr; use markup::raw; markup::define! { - StatText<'a>(ri: &'a RenderInfo<'a>, stat: Object<'a>) { + StatText<'a>(ri: &'a RenderInfo<'a>, stats: &'a Stats) { h1 { @tr(ri.lang, "stats.title") } p { @raw(tr(ri.lang, "stats.count") - .replace("{count}", &format!("<b>{}</b>", stat.get(STAT_COUNT).unwrap_or_default())) + .replace("{count}", &format!("<b>{}</b>", stats.all.count)) )} p { @raw(tr(ri.lang, "stats.runtime") - .replace("{dur}", &format!("<b>{}</b>", format_duration_long(ri.lang, stat.get(STAT_TOTAL_DURATION).unwrap_or_default()))) - .replace("{size}", &format!("<b>{}</b>", format_size(stat.get(STAT_TOTAL_SIZE).unwrap_or_default()))) + .replace("{dur}", &format!("<b>{}</b>", format_duration_long(ri.lang, stats.all.sum_duration))) + .replace("{size}", &format!("<b>{}</b>", format_size(stats.all.sum_size))) )} p { @raw(tr(ri.lang, "stats.average") - .replace("{dur}", &format!("<b>{}</b>", format_duration(stat.get(STAT_TOTAL_DURATION).unwrap_or_default() / stat.get(STAT_COUNT).unwrap_or_default() as f64))) - .replace("{size}", &format!("<b>{}</b>", format_size(stat.get(STAT_TOTAL_SIZE).unwrap_or_default() / stat.get(STAT_COUNT).unwrap_or_default()))) + .replace("{dur}", &format!("<b>{}</b>", format_duration(stats.all.sum_duration / stats.all.count as f64))) + .replace("{size}", &format!("<b>{}</b>", format_size(stats.all.sum_size / stats.all.count as u64))) )} - } - StatGroup<'a>(ri: &'a RenderInfo<'a>, statgroup: Object<'a>) { - h2 { @tr(ri.lang, statgroup.get(STATGROUP_TITLE).unwrap_or_default()) } + h2 { @tr(ri.lang, "stats.by_kind") } table.striped { tr { th { @tr(ri.lang, "stats.by_kind.kind") } @@ -40,15 +38,15 @@ markup::define! { th { @tr(ri.lang, "stats.by_kind.max_size") } th { @tr(ri.lang, "stats.by_kind.max_runtime") } } - @for stat in statgroup.iter(STATGROUP_BIN) { tr { - td { @tr(ri.lang, stat.get(STAT_NAME).unwrap_or_default()) } - td { @stat.get(STAT_COUNT).unwrap_or_default() } - td { @format_size(stat.get(STAT_TOTAL_SIZE).unwrap_or_default()) } - td { @format_duration(stat.get(STAT_TOTAL_DURATION).unwrap_or_default()) } - td { @format_size(stat.get(STAT_TOTAL_SIZE).unwrap_or_default() / stat.get(STAT_COUNT).unwrap_or_default()) } - td { @format_duration(stat.get(STAT_TOTAL_DURATION).unwrap_or_default() / stat.get(STAT_COUNT).unwrap_or_default() as f64) } - td { @format_size(stat.get(STAT_MAX_SIZE).unwrap_or_default()) } - td { @format_duration(stat.get(STAT_MAX_DURATION).unwrap_or_default()) } + @for (kind, stat) in &stats.by_kind { tr { + td { @tr(ri.lang, &format!("tag.kind.{kind}")) } + td { @stat.count } + td { @format_size(stat.sum_size) } + td { @format_duration(stat.sum_duration) } + td { @format_size(stat.sum_size / stat.count as u64) } + td { @format_duration(stat.sum_duration / stat.count as f64) } + td { @format_size(stat.max_size) } + td { @format_duration(stat.max_duration) } }} } } diff --git a/ui/src/components/user.rs b/ui/src/components/user.rs index 22b296e..9dabffc 100644 --- a/ui/src/components/user.rs +++ b/ui/src/components/user.rs @@ -4,16 +4,17 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::RenderInfo; +use crate::{RenderInfo, page}; use jellycommon::{ - jellyobject::Object, routes::{u_account_login, u_account_logout, u_account_settings}, *, }; use jellyui_locale::tr; +page!(UserSettings<'_>, |x| tr(x.ri.lang, "settings")); + markup::define! { - UserSettings<'a>(ri: &'a RenderInfo<'a>, user: Object<'a>) { + UserSettings<'a>(ri: &'a RenderInfo<'a>, user: User<'a>) { h1 { @tr(ri.lang, "settings") } h2 { @tr(ri.lang, "settings.account") } diff --git a/ui/src/lib.rs b/ui/src/lib.rs index d74df51..93dd38a 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -3,20 +3,19 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -mod components; +pub mod components; pub(crate) mod format; mod scaffold; +use std::borrow::Cow; + pub use jellyui_client_scripts::*; pub use jellyui_client_style::*; - -pub use components::admin_log::ServerLogPage; -pub use components::admin_log::render_log_line; pub use jellyui_locale::tr; -pub use scaffold::Scaffold; -use crate::components::View; -use jellycommon::{jellyobject::Object, *}; +use jellycommon::jellyobject::Object; +use markup::DynRender; +pub use scaffold::Scaffold; use serde::{Deserialize, Serialize}; #[rustfmt::skip] @@ -27,29 +26,34 @@ pub struct Config { pub logo: bool, } +pub trait Page { + fn title(&self) -> Cow<'static, str>; + fn ri(&self) -> &RenderInfo<'_>; + fn render(&self) -> DynRender<'_>; +} + pub struct RenderInfo<'a> { pub user: Option<Object<'a>>, + pub message: Option<(&'a str, &'a str)>, pub lang: &'a str, pub status_message: Option<&'a str>, pub config: &'a Config, } -pub fn render_view(ri: RenderInfo<'_>, view: Object<'_>) -> String { - let theme = ri - .user - .and_then(|u| u.get(USER_THEME_PRESET)) - .unwrap_or(THEME_DARK); - Scaffold { - ri: &ri, - main: View { ri: &ri, view }, - title: view.get(VIEW_TITLE).unwrap_or_default(), - class: match (theme, view.has(VIEW_PLAYER.0)) { - (THEME_DARK, true) => "theme-dark player", - (THEME_DARK, false) => "theme-dark", - (THEME_LIGHT, true) => "theme-light player", - (THEME_LIGHT, false) => "theme-light", - _ => "theme-dark", - }, - } - .to_string() +#[macro_export] +macro_rules! page { + ($t:ty, $title:stmt) => { + impl $crate::Page for $t { + fn title(&self) -> std::borrow::Cow<'static, str> { + let x: fn(&$t) -> std::borrow::Cow<'static, str> = {$title}; + x(self) + } + fn ri(&self) -> &$crate::RenderInfo<'_> { + self.ri + } + fn render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } + } + }; } diff --git a/ui/src/scaffold.rs b/ui/src/scaffold.rs index c563ee4..61e497a 100644 --- a/ui/src/scaffold.rs +++ b/ui/src/scaffold.rs @@ -4,9 +4,9 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::RenderInfo; +use crate::{Page, RenderInfo, components::message::Message}; use jellycommon::{ - USER_THEME_ACCENT, + THEME_DARK, USER_THEME_ACCENT, USER_THEME_PRESET, routes::{ u_account_login, u_account_settings, u_admin_dashboard, u_home, u_items, u_node_slug, u_search, u_stats, @@ -14,21 +14,27 @@ use jellycommon::{ user::{USER_ADMIN, USER_NAME}, }; use jellyui_locale::{escape, tr}; -use markup::{Render, raw}; +use markup::raw; markup::define! { - Scaffold<'a, Main: Render>(ri: &'a RenderInfo<'a>, title: &'a str, main: Main, class: &'a str) { + Scaffold<'a>(page: &'a dyn Page) { + @let ri = page.ri(); @markup::doctype() html { head { - title { @title " - " @ri.config.brand } + title { @page.title() " - " @ri.config.brand } meta[name="viewport", content="width=device-width, initial-scale=1.0"]; link[rel="stylesheet", href="/assets/bundle.css"]; script[src="/assets/bundle.js"] {} } - body[class=class, style=format!("--accent-hue: {}", ri.user.and_then(|u|u.get(USER_THEME_ACCENT)).unwrap_or(277))] { + @let theme = ri.user.and_then(|u| u.get(USER_THEME_PRESET)).unwrap_or(THEME_DARK); + @let hue = ri.user.and_then(|u| u.get(USER_THEME_ACCENT)).unwrap_or(277); + body[class=format!("theme-{theme}"), style=format!("--accent-hue: {hue}")] { @Navbar { ri } - #main { @main } + @if let Some((kind, text)) = ri.message { + @Message { ri, kind, text } + } + #main { @page.render() } footer { p { @ri.config.brand " - " @ri.config.slogan " | powered by " a[href="https://codeberg.org/metamuffin/jellything"]{"Jellything"} } } @@ -50,11 +56,11 @@ markup::define! { @if let Some(user) = &ri.user { span { @raw(tr(ri.lang, "nav.username").replace("{name}", &format!("<b class=\"username\">{}</b>", escape(user.get(USER_NAME).unwrap_or("nameless user"))))) } " " @if user.has(USER_ADMIN.0) { - a.admin.hybrid_button[href=u_admin_dashboard()] { p {@tr(ri.lang, "nav.admin")} } " " + a.admin[href=u_admin_dashboard()] { p {@tr(ri.lang, "nav.admin")} } " " } - a.settings.hybrid_button[href=u_account_settings()] { p {@tr(ri.lang, "nav.settings")} } " " + a.settings[href=u_account_settings()] { p {@tr(ri.lang, "nav.settings")} } " " } else { - a.login.hybrid_button[href=u_account_login()] { p {@tr(ri.lang, "nav.login")} } + a.login[href=u_account_login()] { p {@tr(ri.lang, "nav.login")} } } } } |