diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-28 21:50:51 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-28 21:50:51 +0200 |
commit | 73d2d5eb01fceae9e0b1c58afb648822000c878a (patch) | |
tree | 8fd0279949251245e2086ad28e99b114eac1bf14 /ui | |
parent | 51761cbdefa39107b9e1f931f1aa8df6aebb2a94 (diff) | |
download | jellything-73d2d5eb01fceae9e0b1c58afb648822000c878a.tar jellything-73d2d5eb01fceae9e0b1c58afb648822000c878a.tar.bz2 jellything-73d2d5eb01fceae9e0b1c58afb648822000c878a.tar.zst |
yes
Diffstat (limited to 'ui')
-rw-r--r-- | ui/src/filter_sort.rs | 6 | ||||
-rw-r--r-- | ui/src/lib.rs | 15 | ||||
-rw-r--r-- | ui/src/node_card.rs | 22 | ||||
-rw-r--r-- | ui/src/node_page.rs | 34 | ||||
-rw-r--r-- | ui/src/scaffold.rs | 44 | ||||
-rw-r--r-- | ui/src/settings.rs | 31 |
6 files changed, 80 insertions, 72 deletions
diff --git a/ui/src/filter_sort.rs b/ui/src/filter_sort.rs index ec83f6f..4d1a1ad 100644 --- a/ui/src/filter_sort.rs +++ b/ui/src/filter_sort.rs @@ -119,17 +119,17 @@ markup::define! { struct A<T>(pub T); impl markup::Render for A<SortProperty> { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_fmt(format_args!("{}", self as &dyn UriDisplay<Query>)) + writer.write_str(self.0.to_str()) } } impl markup::Render for A<SortOrder> { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_fmt(format_args!("{}", self as &dyn UriDisplay<Query>)) + writer.write_str(self.0.to_str()) } } impl markup::Render for A<FilterProperty> { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_fmt(format_args!("{}", self as &dyn UriDisplay<Query>)) + writer.write_str(self.0.to_str()) } } impl RenderAttributeValue for A<SortOrder> {} diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 4298623..67dc067 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,5 +1,3 @@ -use markup::DynRender; - /* 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. @@ -28,7 +26,16 @@ pub trait Page { } } +use markup::DynRender; +use scaffold::Scaffold; + pub fn render_page(page: &dyn Page) -> String { - // page.render() - "a".to_string() + Scaffold { + lang, + context, + class: page.class().unwrap_or("aaaa"), + title: page.title(), + main: page.to_render(), + } + .to_string() } diff --git a/ui/src/node_card.rs b/ui/src/node_card.rs index cedb81e..b4481b7 100644 --- a/ui/src/node_card.rs +++ b/ui/src/node_card.rs @@ -5,25 +5,29 @@ */ use crate::{locale::Language, node_page::aspect_class, props::Props}; -use jellycommon::{Node, user::NodeUserData}; +use jellycommon::{ + Node, + routes::{u_node_slug, u_node_slug_player, u_node_slug_poster}, + user::NodeUserData, +}; markup::define! { NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) { @let cls = format!("node card poster {}", aspect_class(node.kind)); div[class=cls] { .poster { - a[href=uri!(r_library_node(&node.slug))] { - img[src=uri!(r_item_poster(&node.slug, Some(1024))), loading="lazy"]; + a[href=u_node_slug(&node.slug)] { + img[src=u_node_slug_poster(&node.slug, 1024), loading="lazy"]; } .cardhover.item { @if node.media.is_some() { - a.play.icon[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "play_arrow" } + a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } } @Props { node, udata, full: false, lang } } } div.title { - a[href=uri!(r_library_node(&node.slug))] { + a[href=u_node_slug(&node.slug)] { @node.title } } @@ -37,17 +41,17 @@ markup::define! { NodeCardWide<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) { div[class="node card widecard poster"] { div[class=&format!("poster {}", aspect_class(node.kind))] { - a[href=uri!(r_library_node(&node.slug))] { - img[src=uri!(r_item_poster(&node.slug, Some(1024))), loading="lazy"]; + a[href=u_node_slug(&node.slug)] { + img[src=u_node_slug_poster(&node.slug, 1024), loading="lazy"]; } .cardhover.item { @if node.media.is_some() { - a.play.icon[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "play_arrow" } + a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } } } } div.details { - a.title[href=uri!(r_library_node(&node.slug))] { @node.title } + a.title[href=u_node_slug(&node.slug)] { @node.title } @Props { node, udata, full: false, lang } span.overview { @node.description } } diff --git a/ui/src/node_page.rs b/ui/src/node_page.rs index b48fca2..7fb299f 100644 --- a/ui/src/node_page.rs +++ b/ui/src/node_page.rs @@ -15,7 +15,12 @@ use crate::{ use jellycommon::{ Chapter, Node, NodeKind, PeopleGroup, api::NodeFilterSort, - user::{NodeUserData, WatchedState}, + routes::{ + u_node_slug, u_node_slug_backdrop, u_node_slug_person_asset, u_node_slug_player, + u_node_slug_player_time, u_node_slug_poster, u_node_slug_thumbnail, + u_node_slug_update_rating, u_node_slug_watched, + }, + user::{ApiWatchedState, NodeUserData, WatchedState}, }; use std::sync::Arc; @@ -23,6 +28,9 @@ impl Page for NodePage<'_> { fn title(&self) -> String { self.node.title.clone().unwrap_or_default() } + fn to_render(&self) -> markup::DynRender { + markup::new!(@self) + } } markup::define! { @@ -37,43 +45,43 @@ markup::define! { player: bool, ) { @if !matches!(node.kind, NodeKind::Collection) && !player { - img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))), loading="lazy"]; + img.backdrop[src=u_node_slug_backdrop(&node.slug, 2048), loading="lazy"]; } .page.node { @if !matches!(node.kind, NodeKind::Collection) && !player { @let cls = format!("bigposter {}", aspect_class(node.kind)); - div[class=cls] { img[src=uri!(r_item_poster(&node.slug, Some(2048))), loading="lazy"]; } + div[class=cls] { img[src=u_node_slug_poster(&node.slug, 2048), loading="lazy"]; } } .title { h1 { @node.title } ul.parents { @for (node, _) in *parents { li { - a.component[href=uri!(r_library_node(&node.slug))] { @node.title } + a.component[href=u_node_slug(&node.slug)] { @node.title } }}} @if node.media.is_some() { - a.play[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { @trs(lang, "node.player_link") } + a.play[href=u_node_slug_player(&node.slug)] { @trs(lang, "node.player_link") } } @if !matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { @if matches!(udata.watched, WatchedState::None | WatchedState::Pending | WatchedState::Progress(_)) { - form.mark_watched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Watched))] { + form.mark_watched[method="POST", action=u_node_slug_watched(&node.slug, ApiWatchedState::Watched)] { input[type="submit", value=trs(lang, "node.watched.set")]; } } @if matches!(udata.watched, WatchedState::Watched) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] { + form.mark_unwatched[method="POST", action=u_node_slug_watched(&node.slug, ApiWatchedState::None)] { input[type="submit", value=trs(lang, "node.watched.unset")]; } } @if matches!(udata.watched, WatchedState::None) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Pending))] { + form.mark_unwatched[method="POST", action=u_node_slug_watched(&node.slug, ApiWatchedState::Pending)] { input[type="submit", value=trs(lang, "node.watchlist.set")]; } } @if matches!(udata.watched, WatchedState::Pending) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] { + form.mark_unwatched[method="POST", action=u_node_slug_watched(&node.slug, ApiWatchedState::None)] { input[type="submit", value=trs(lang, "node.watchlist.unset")]; } } - form.rating[method="POST", action=uri!(r_node_userdata_rating(&node.slug))] { + form.rating[method="POST", action=u_node_slug_update_rating(&node.slug)] { input[type="range", name="rating", min=-10, max=10, step=1, value=udata.rating]; input[type="submit", value=trs(lang, "node.update_rating")]; } @@ -92,8 +100,8 @@ markup::define! { @let (inl, sub) = format_chapter(chap); li { .card."aspect-thumb" { .poster { - a[href=&uri!(r_player(&node.slug, PlayerConfig::seek(chap.time_start.unwrap_or(0.))))] { - img[src=&uri!(r_node_thumbnail(&node.slug, chapter_key_time(chap, media.duration), Some(1024))), loading="lazy"]; + a[href=u_node_slug_player_time(&node.slug, chap.time_start.unwrap_or(0.))] { + img[src=u_node_slug_thumbnail(&node.slug, chapter_key_time(chap, media.duration), 1024), loading="lazy"]; } .cardhover { .props { p { @inl } } } } @@ -110,7 +118,7 @@ markup::define! { li { .card."aspect-port" { .poster { a[href="#"] { - img[src=&uri!(r_person_asset(&node.slug, i, group.to_string(), Some(1024))), loading="lazy"]; + img[src=u_node_slug_person_asset(&node.slug, *group, i, 1024), loading="lazy"]; } } .title { diff --git a/ui/src/scaffold.rs b/ui/src/scaffold.rs index cc5886b..bcff54c 100644 --- a/ui/src/scaffold.rs +++ b/ui/src/scaffold.rs @@ -5,7 +5,11 @@ */ use crate::locale::{Language, escape, tr, trs}; -use markup::{DynRender, Render, raw}; +use jellycommon::routes::{ + u_account_login, u_account_logout, u_account_register, u_account_settings, u_admin_dashboard, + u_home, u_items, u_node_slug, u_search, u_stats, +}; +use markup::{Render, raw}; use std::sync::LazyLock; static LOGO_ENABLED: LazyLock<bool> = LazyLock::new(|| CONF.asset_path.join("logo.svg").exists()); @@ -22,25 +26,25 @@ markup::define! { } body[class=class] { nav { - h1 { a[href=if session.is_some() {"/home"} else {"/"}] { @if *LOGO_ENABLED { img.logo[src="/assets/logo.svg"]; } else { @CONF.brand } } } " " + h1 { a[href=if session.is_some() {u_home()} else {"/".to_string()}] { @if *LOGO_ENABLED { img.logo[src="/assets/logo.svg"]; } else { @CONF.brand } } } " " @if let Some(_) = session { - a.library[href=uri!(r_library_node("library"))] { @trs(lang, "nav.root") } " " - a.library[href=uri!(r_all_items())] { @trs(lang, "nav.all") } " " - a.library[href=uri!(r_search(None::<&'static str>, None::<usize>))] { @trs(lang, "nav.search") } " " - a.library[href=uri!(r_stats())] { @trs(lang, "nav.stats") } " " + a.library[href=u_node_slug("library")] { @trs(lang, "nav.root") } " " + a.library[href=u_items()] { @trs(lang, "nav.all") } " " + a.library[href=u_search()] { @trs(lang, "nav.search") } " " + a.library[href=u_stats()] { @trs(lang, "nav.stats") } " " } @if is_importing() { span.warn { "Library database is updating..." } } div.account { @if let Some(session) = session { span { @raw(tr(*lang, "nav.username").replace("{name}", &format!("<b class=\"username\">{}</b>", escape(&session.user.display_name)))) } " " @if session.user.admin { - a.admin.hybrid_button[href=uri!(r_admin_dashboard())] { p {@trs(lang, "nav.admin")} } " " + a.admin.hybrid_button[href=u_admin_dashboard()] { p {@trs(lang, "nav.admin")} } " " } - a.settings.hybrid_button[href=uri!(r_account_settings())] { p {@trs(lang, "nav.settings")} } " " - a.logout.hybrid_button[href=uri!(r_account_logout())] { p {@trs(lang, "nav.logout")} } + a.settings.hybrid_button[href=u_account_settings()] { p {@trs(lang, "nav.settings")} } " " + a.logout.hybrid_button[href=u_account_logout()] { p {@trs(lang, "nav.logout")} } } else { - a.register.hybrid_button[href=uri!(r_account_register())] { p {@trs(lang, "nav.register")} } " " - a.login.hybrid_button[href=uri!(r_account_login())] { p {@trs(lang, "nav.login")} } + a.register.hybrid_button[href=u_account_register()] { p {@trs(lang, "nav.register")} } " " + a.login.hybrid_button[href=u_account_login()] { p {@trs(lang, "nav.login")} } } } } @@ -61,21 +65,3 @@ markup::define! { } } } - -pub type DynLayoutPage<'a> = LayoutPage<DynRender<'a>>; - -pub struct LayoutPage<T> { - pub title: String, - pub class: Option<&'static str>, - pub content: T, -} - -impl Default for LayoutPage<DynRender<'_>> { - fn default() -> Self { - Self { - class: None, - content: markup::new!(), - title: String::new(), - } - } -} diff --git a/ui/src/settings.rs b/ui/src/settings.rs index 9bc4b1d..fb4ef0f 100644 --- a/ui/src/settings.rs +++ b/ui/src/settings.rs @@ -4,7 +4,10 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::locale::{Language, tr, trs}; -use jellycommon::user::{PlayerKind, Theme}; +use jellycommon::{ + routes::{u_account_login, u_account_settings}, + user::{PlayerKind, Theme}, +}; use markup::RenderAttributeValue; markup::define! { @@ -17,42 +20,42 @@ markup::define! { } } h2 { @trs(&lang, "account") } - a.switch_account[href=uri!(r_account_login())] { "Switch Account" } - form[method="POST", action=uri!(r_account_settings_post())] { + a.switch_account[href=u_account_login()] { "Switch Account" } + form[method="POST", action=u_account_settings()] { label[for="username"] { @trs(&lang, "account.username") } input[type="text", id="username", disabled, value=&session.user.name]; input[type="submit", disabled, value=&*tr(**lang, "settings.immutable")]; } - form[method="POST", action=uri!(r_account_settings_post())] { + form[method="POST", action=u_account_settings()] { label[for="display_name"] { @trs(lang, "account.display_name") } input[type="text", id="display_name", name="display_name", value=&session.user.display_name]; input[type="submit", value=&*tr(**lang, "settings.update")]; } - form[method="POST", action=uri!(r_account_settings_post())] { + form[method="POST", action=u_account_settings()] { label[for="password"] { @trs(lang, "account.password") } input[type="password", id="password", name="password"]; input[type="submit", value=&*tr(**lang, "settings.update")]; } h2 { @trs(&lang, "settings.appearance") } - form[method="POST", action=uri!(r_account_settings_post())] { + form[method="POST", action=u_account_settings()] { fieldset { legend { @trs(&lang, "settings.appearance.theme") } - @for (t, tlabel) in Theme::LIST { - label { input[type="radio", name="theme", value=A(*t), checked=session.user.theme==*t]; @tlabel } br; + @for theme in Theme::ALL { + label { input[type="radio", name="theme", value=A(*theme), checked=session.user.theme==*theme]; @trs(lang, &format!("theme.{theme}")) } br; } } input[type="submit", value=&*tr(**lang, "settings.apply")]; } - form[method="POST", action=uri!(r_account_settings_post())] { + form[method="POST", action=u_account_settings()] { fieldset { legend { @trs(&lang, "settings.player_preference") } - @for (t, tlabel) in PlayerKind::LIST { - label { input[type="radio", name="player_preference", value=A(*t), checked=session.user.player_preference==*t]; @tlabel } br; + @for kind in PlayerKind::ALL { + label { input[type="radio", name="player_preference", value=A(*kind), checked=session.user.player_preference==*kind]; @trs(lang, &format!("player_kind.{kind}")) } br; } } input[type="submit", value=&*tr(**lang, "settings.apply")]; } - form[method="POST", action=uri!(r_account_settings_post())] { + form[method="POST", action=u_account_settings()] { label[for="native_secret"] { "Native Secret" } input[type="password", id="native_secret", name="native_secret"]; input[type="submit", value=&*tr(**lang, "settings.update")]; @@ -64,12 +67,12 @@ markup::define! { struct A<T>(pub T); impl markup::Render for A<Theme> { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_fmt(format_args!("{}", self as &dyn UriDisplay<Query>)) + writer.write_str(self.0.to_str()) } } impl markup::Render for A<PlayerKind> { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_fmt(format_args!("{}", self as &dyn UriDisplay<Query>)) + writer.write_str(self.0.to_str()) } } impl RenderAttributeValue for A<Theme> {} |