diff options
Diffstat (limited to 'ui/src')
| -rw-r--r-- | ui/src/filter_sort.rs | 5 | ||||
| -rw-r--r-- | ui/src/format.rs | 10 | ||||
| -rw-r--r-- | ui/src/home.rs | 24 | ||||
| -rw-r--r-- | ui/src/items.rs | 34 | ||||
| -rw-r--r-- | ui/src/lib.rs | 12 | ||||
| -rw-r--r-- | ui/src/locale.rs | 43 | ||||
| -rw-r--r-- | ui/src/node_card.rs | 85 | ||||
| -rw-r--r-- | ui/src/node_page.rs | 345 | ||||
| -rw-r--r-- | ui/src/props.rs | 2 | ||||
| -rw-r--r-- | ui/src/scaffold.rs | 73 | ||||
| -rw-r--r-- | ui/src/search.rs | 2 | ||||
| -rw-r--r-- | ui/src/stats.rs | 2 |
12 files changed, 294 insertions, 343 deletions
diff --git a/ui/src/filter_sort.rs b/ui/src/filter_sort.rs index cf06609..70e6b8a 100644 --- a/ui/src/filter_sort.rs +++ b/ui/src/filter_sort.rs @@ -4,11 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::locale::{Language, trs}; +use crate::locale::trs; use markup::RenderAttributeValue; const SORT_CATS: &[(&str, &[(SortProperty, &str)])] = { - use SortProperty::*; &[ ( "filter_sort.sort.general", @@ -35,7 +34,6 @@ const SORT_CATS: &[(&str, &[(SortProperty, &str)])] = { ] }; const FILTER_CATS: &[(&str, &[(FilterProperty, &str)])] = { - use FilterProperty::*; &[ ( "filter_sort.filter.kind", @@ -103,7 +101,6 @@ markup::define! { } fieldset.sortorder { legend { "Sort Order" } - @use SortOrder::*; @for (value, label) in [(Ascending, "filter_sort.order.asc"), (Descending, "filter_sort.order.desc")] { label { input[type="radio", name="sort_order", value=A(value), checked=Some(value)==f.sort_order]; @trs(lang, label) } br; } diff --git a/ui/src/format.rs b/ui/src/format.rs index 138589f..3b49695 100644 --- a/ui/src/format.rs +++ b/ui/src/format.rs @@ -4,7 +4,9 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::locale::{Language, TrString, tr, trs}; +use jellycommon::LANG_ENG; + +use crate::locale::{TrString, tr, trs}; use std::fmt::Write; pub fn format_duration(d: f64) -> String { @@ -53,15 +55,15 @@ fn test_duration_short() { #[test] fn test_duration_long() { assert_eq!( - format_duration_long(61., Language::English).as_str(), + format_duration_long(61., LANG_ENG).as_str(), "1 minute and 1 second" ); assert_eq!( - format_duration_long(121., Language::English).as_str(), + format_duration_long(121., LANG_ENG).as_str(), "2 minutes and 1 second" ); assert_eq!( - format_duration_long(3661., Language::English).as_str(), + format_duration_long(3661., LANG_ENG).as_str(), "1 hour, 1 minute and 1 second" ); } diff --git a/ui/src/home.rs b/ui/src/home.rs index 64e1ee5..5de9e17 100644 --- a/ui/src/home.rs +++ b/ui/src/home.rs @@ -3,33 +3,29 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{ - CONF, Page, - locale::{Language, tr, trs}, - node_card::NodeCard, -}; +use crate::{CONF, Page, locale::tr, node_card::NodeCard, scaffold::RenderInfo}; use markup::DynRender; markup::define! { - HomePage<'a>(lang: &'a Language, r: ApiHomeResponse) { - h2 { @tr(**lang, "home.bin.root").replace("{title}", &CONF.brand) } + HomePage<'a>(ri: RenderInfo<'a>, r: ApiHomeResponse) { + h2 { @tr(ri.lang, "home.bin.root").replace("{title}", &CONF.brand) } ul.children.hlist {@for (node, udata) in &r.toplevel { li { @NodeCard { node, udata, lang } } }} @for (name, nodes) in &r.categories { - @if !nodes.is_empty() { - h2 { @trs(lang, name) } - ul.children.hlist {@for (node, udata) in nodes { - li { @NodeCard { node, udata, lang } } - }} - } + // @if !nodes.is_empty() { + // h2 { @trs(lang, name) } + // ul.children.hlist {@for (node, udata) in nodes { + // li { @NodeCard { node, udata, lang } } + // }} + // } } } } impl Page for HomePage<'_> { fn title(&self) -> String { - tr(*self.lang, "home").to_string() + tr(self.ri.lang, "home").to_string() } fn to_render(&self) -> DynRender<'_> { markup::new!(@self) diff --git a/ui/src/items.rs b/ui/src/items.rs index 3879b29..4bce43c 100644 --- a/ui/src/items.rs +++ b/ui/src/items.rs @@ -3,32 +3,26 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{ - Page, - filter_sort::NodeFilterSortForm, - locale::{Language, tr, trs}, - node_card::NodeCard, -}; -use jellycommon::routes::u_items_filter; +use crate::{Page, locale::tr}; use markup::DynRender; markup::define! { ItemsPage<'a>(lang: &'a Language, r: ApiItemsResponse, filter: &'a NodeFilterSort, page: usize) { .page.dir { h1 { "All Items" } - @NodeFilterSortForm { f: filter, lang } - ul.children { @for (node, udata) in &r.items { - li {@NodeCard { node, udata, lang }} - }} - p.pagecontrols { - span.current { @tr(**lang, "page.curr").replace("{cur}", &(page + 1).to_string()).replace("{max}", &r.pages.to_string()) " " } - @if *page > 0 { - a.prev[href=u_items_filter(page - 1, filter)] { @trs(lang, "page.prev") } " " - } - @if page + 1 < r.pages { - a.next[href=u_items_filter(page + 1, filter)] { @trs(lang, "page.next") } - } - } + // @NodeFilterSortForm { f: filter, lang } + // ul.children { @for (node, udata) in &r.items { + // li {@NodeCard { node, udata, lang }} + // }} + // p.pagecontrols { + // span.current { @tr(**lang, "page.curr").replace("{cur}", &(page + 1).to_string()).replace("{max}", &r.pages.to_string()) " " } + // @if *page > 0 { + // a.prev[href=u_items_filter(page - 1, filter)] { @trs(lang, "page.prev") } " " + // } + // @if page + 1 < r.pages { + // a.next[href=u_items_filter(page + 1, filter)] { @trs(lang, "page.next") } + // } + // } } } } diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 7e17a20..6dbc837 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -63,20 +63,14 @@ pub trait Page { } } -pub fn render_page(page: &dyn Page, renderinfo: RenderInfo) -> String { +pub fn render_page(renderinfo: &RenderInfo<'_>, page: &dyn Page) -> String { Scaffold { - lang: renderinfo.lang, class: &format!( "{} theme-{}", page.class().unwrap_or("custom-page"), - renderinfo - .session - .as_ref() - .map(|s| s.user.theme) - .unwrap_or(Theme::Dark) - .to_str() + "dark", // todo ), - renderinfo, + ri: renderinfo, title: page.title(), main: page.to_render(), } diff --git a/ui/src/locale.rs b/ui/src/locale.rs index b3bf3b6..a2fdce0 100644 --- a/ui/src/locale.rs +++ b/ui/src/locale.rs @@ -3,21 +3,15 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use markup::{Render, RenderAttributeValue}; -use std::{borrow::Cow, collections::HashMap, sync::LazyLock}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Language { - English, - German, -} +use jellycommon::*; +use std::{collections::HashMap, sync::LazyLock}; static LANG_TABLES: LazyLock<HashMap<Language, HashMap<&'static str, &'static str>>> = LazyLock::new(|| { let mut k = HashMap::new(); for (lang, source) in [ - (Language::English, include_str!("../../locale/en.ini")), - (Language::German, include_str!("../../locale/de.ini")), + (LANG_ENG.0, include_str!("../../locale/en.ini")), + (LANG_DEU.0, include_str!("../../locale/de.ini")), ] { // TODO fallback to english let tr_map = source @@ -32,36 +26,15 @@ static LANG_TABLES: LazyLock<HashMap<Language, HashMap<&'static str, &'static st k }); -pub fn tr(lang: Language, key: &str) -> Cow<'static, str> { +pub fn tr(lang: Language, key: &str) -> &'static str { let tr_map = LANG_TABLES.get(&lang).unwrap(); - match tr_map.get(key) { - Some(value) => Cow::Borrowed(value), - None => Cow::Owned(format!("TR[{key}]")), - } + tr_map.get(key).copied().unwrap_or("MISSING TRANSLATION") } pub fn get_translation_table(lang: &Language) -> &'static HashMap<&'static str, &'static str> { LANG_TABLES.get(lang).unwrap() } -pub struct TrString<'a>(Cow<'a, str>); -impl Render for TrString<'_> { - fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - self.0.render(writer) - } -} -impl RenderAttributeValue for TrString<'_> { - fn is_none(&self) -> bool { - false - } - fn is_true(&self) -> bool { - false - } - fn is_false(&self) -> bool { - false - } -} - pub fn escape(str: &str) -> String { let mut o = String::with_capacity(str.len()); let mut last = 0; @@ -81,7 +54,3 @@ pub fn escape(str: &str) -> String { o += &str[last..]; o } - -pub fn trs<'a>(lang: &Language, key: &str) -> TrString<'a> { - TrString(tr(*lang, key)) -} diff --git a/ui/src/node_card.rs b/ui/src/node_card.rs index d0f9904..f87f490 100644 --- a/ui/src/node_card.rs +++ b/ui/src/node_card.rs @@ -4,53 +4,56 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{locale::Language, node_page::aspect_class, props::Props}; -use jellycommon::routes::{u_node_image, u_node_slug, u_node_slug_player}; +use crate::{ + node_page::{NodeUdata, aspect_class}, + scaffold::RenderInfo, +}; +use jellycommon::*; markup::define! { - NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) { - @let cls = format!("node card poster {}", aspect_class(node.kind)); + NodeCard<'a>(ri: &'a RenderInfo<'a>, nodeu: NodeUdata<'a>) { + @let cls = format!("node card poster {}", aspect_class(nodeu.node.get(NO_KIND).unwrap_or(KIND_COLLECTION))); div[class=cls] { - .poster { - a[href=u_node_slug(&node.slug)] { - img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; - } - .cardhover.item { - @if node.media.is_some() { - a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } - } - @Props { node, udata, full: false, lang } - } - } - div.title { - a[href=u_node_slug(&node.slug)] { - @node.title - } - } - div.subtitle { - span { - @node.subtitle - } - } + // .poster { + // a[href=u_node_slug(&node.slug)] { + // img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; + // } + // .cardhover.item { + // @if node.media.is_some() { + // a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } + // } + // @Props { node, udata, full: false, lang } + // } + // } + // div.title { + // a[href=u_node_slug(&node.slug)] { + // @node.title + // } + // } + // div.subtitle { + // span { + // @node.subtitle + // } + // } } } - NodeCardWide<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) { + NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nodeu: NodeUdata<'a>) { div[class="node card widecard poster"] { - div[class=&format!("poster {}", aspect_class(node.kind))] { - a[href=u_node_slug(&node.slug)] { - img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; - } - .cardhover.item { - @if node.media.is_some() { - a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } - } - } - } - div.details { - a.title[href=u_node_slug(&node.slug)] { @node.title } - @Props { node, udata, full: false, lang } - span.overview { @node.description } - } + // div[class=&format!("poster {}", aspect_class(node.kind))] { + // a[href=u_node_slug(&node.slug)] { + // img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; + // } + // .cardhover.item { + // @if node.media.is_some() { + // a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } + // } + // } + // } + // div.details { + // 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 3e0c64f..fa5c93b 100644 --- a/ui/src/node_page.rs +++ b/ui/src/node_page.rs @@ -4,193 +4,183 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{ - Page, - filter_sort::NodeFilterSortForm, - format::format_chapter, - locale::{Language, trs}, - node_card::{NodeCard, NodeCardWide}, - props::Props, +use crate::Page; +use jellycommon::{ + jellyobject::{Object, Tag}, + *, }; -use jellycommon::routes::{ - u_node_image, u_node_slug, u_node_slug_person_asset, u_node_slug_player, - u_node_slug_player_time, u_node_slug_thumbnail, u_node_slug_update_rating, u_node_slug_watched, -}; -use std::sync::Arc; impl Page for NodePage<'_> { fn title(&self) -> String { - self.node.title.clone().unwrap_or_default() + self.node.node.get(NO_TITLE).unwrap_or_default().to_string() } fn class(&self) -> Option<&'static str> { - if self.player { - Some("player") - } else { - Some("node-page") - } + Some("node-page") } fn to_render(&self) -> markup::DynRender<'_> { markup::new!(@self) } } +pub struct NodeUdata<'a> { + pub node: Object<'a>, + pub udata: Object<'a>, +} + markup::define! { NodePage<'a>( - node: &'a Node, - udata: &'a NodeUserData, - children: &'a [(Arc<Node>, NodeUserData)], - parents: &'a [(Arc<Node>, NodeUserData)], - similar: &'a [(Arc<Node>, NodeUserData)], - filter: &'a NodeFilterSort, - lang: &'a Language, - player: bool, + ri: RenderInfo<'a>, + node: NodeUdata<'a>, + children: &'a [NodeUdata<'a>], + parents: &'a [NodeUdata<'a>], + similar: &'a [NodeUdata<'a>], ) { - @if !matches!(node.kind, NodeKind::Collection) && !player { - img.backdrop[src=u_node_image(&node.slug, PictureSlot::Backdrop, 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=u_node_image(&node.slug, PictureSlot::Cover, 2048), loading="lazy"]; } - } - .title { - h1 { @node.title } - ul.parents { @for (node, _) in *parents { li { - a.component[href=u_node_slug(&node.slug)] { @node.title } - }}} - @if node.media.is_some() { - 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=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=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=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=u_node_slug_watched(&node.slug, ApiWatchedState::None)] { - input[type="submit", value=trs(lang, "node.watchlist.unset")]; - } - } - 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")]; - } - } - } - .details { - @Props { node, udata, full: true, lang } - h3 { @node.tagline } - @if let Some(description) = &node.description { - p { @for line in description.lines() { @line br; } } - } - @if let Some(media) = &node.media { - @if !media.chapters.is_empty() { - h2 { @trs(lang, "node.chapters") } - ul.children.hlist { @for chap in &media.chapters { - @let (inl, sub) = format_chapter(chap); - li { .card."aspect-thumb" { - .poster { - 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 } } } - } - .title { span { @sub } } - }} - }} - } - @if !node.credits.is_empty() { - h2 { @trs(lang, "node.people") } - @for (group, people) in &node.credits { - details[open=group==&CreditCategory::Cast] { - summary { h3 { @format!("{}", group) } } - ul.children.hlist { @for (i, pe) in people.iter().enumerate() { - li { .card."aspect-port" { - .poster { - a[href="#"] { - img[src=u_node_slug_person_asset(&node.slug, *group, i, 1024), loading="lazy"]; - } - } - .title { - // TODO span { @pe.person.name } br; - @if let Some(c) = pe.characters.first() { - span.subtitle { @c } - } - @if let Some(c) = pe.jobs.first() { - span.subtitle { @c } - } - } - }} - }} - } - } - } - details { - summary { @trs(lang, "media.tracks") } - ol { @for track in &media.tracks { - li { @format!("{track}") } - }} - } - } - @if !node.identifiers.is_empty() { - details { - summary { @trs(lang, "node.external_ids") } - table { - @for (key, value) in &node.identifiers { tr { - tr { - td { @trs(lang, &format!("id.{}", key)) } - @if let Some(url) = external_id_url(*key, value) { - td { a[href=url] { pre { @value } } } - } else { - td { pre { @value } } - } - } - }} - } - } - } - @if !node.tags.is_empty() { - details { - summary { @trs(lang, "node.tags") } - ol { @for tag in &node.tags { - li { @tag } - }} - } - } - } - @if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { - @NodeFilterSortForm { f: filter, lang } - } - @if !similar.is_empty() { - h2 { @trs(lang, "node.similar") } - ul.children.hlist {@for (node, udata) in similar.iter() { - li { @NodeCard { node, udata, lang } } - }} - } - @match node.kind { - NodeKind::Show | NodeKind::Series | NodeKind::Season => { - ol { @for (node, udata) in children.iter() { - li { @NodeCardWide { node, udata, lang } } - }} - } - NodeKind::Collection | NodeKind::Channel | _ => { - ul.children {@for (node, udata) in children.iter() { - li { @NodeCard { node, udata, lang } } - }} - } - } - } + // @if !matches!(node.kind, NodeKind::Collection) && !player { + // img.backdrop[src=u_node_image(&node.slug, PictureSlot::Backdrop, 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=u_node_image(&node.slug, PictureSlot::Cover, 2048), loading="lazy"]; } + // } + // .title { + // h1 { @node.title } + // ul.parents { @for (node, _) in *parents { li { + // a.component[href=u_node_slug(&node.slug)] { @node.title } + // }}} + // @if node.media.is_some() { + // 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=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=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=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=u_node_slug_watched(&node.slug, ApiWatchedState::None)] { + // input[type="submit", value=trs(lang, "node.watchlist.unset")]; + // } + // } + // 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")]; + // } + // } + // } + // .details { + // @Props { node, udata, full: true, lang } + // h3 { @node.tagline } + // @if let Some(description) = &node.description { + // p { @for line in description.lines() { @line br; } } + // } + // @if let Some(media) = &node.media { + // @if !media.chapters.is_empty() { + // h2 { @trs(lang, "node.chapters") } + // ul.children.hlist { @for chap in &media.chapters { + // @let (inl, sub) = format_chapter(chap); + // li { .card."aspect-thumb" { + // .poster { + // 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 } } } + // } + // .title { span { @sub } } + // }} + // }} + // } + // @if !node.credits.is_empty() { + // h2 { @trs(lang, "node.people") } + // @for (group, people) in &node.credits { + // details[open=group==&CreditCategory::Cast] { + // summary { h3 { @format!("{}", group) } } + // ul.children.hlist { @for (i, pe) in people.iter().enumerate() { + // li { .card."aspect-port" { + // .poster { + // a[href="#"] { + // img[src=u_node_slug_person_asset(&node.slug, *group, i, 1024), loading="lazy"]; + // } + // } + // .title { + // // TODO span { @pe.person.name } br; + // @if let Some(c) = pe.characters.first() { + // span.subtitle { @c } + // } + // @if let Some(c) = pe.jobs.first() { + // span.subtitle { @c } + // } + // } + // }} + // }} + // } + // } + // } + // details { + // summary { @trs(lang, "media.tracks") } + // ol { @for track in &media.tracks { + // li { @format!("{track}") } + // }} + // } + // } + // @if !node.identifiers.is_empty() { + // details { + // summary { @trs(lang, "node.external_ids") } + // table { + // @for (key, value) in &node.identifiers { tr { + // tr { + // td { @trs(lang, &format!("id.{}", key)) } + // @if let Some(url) = external_id_url(*key, value) { + // td { a[href=url] { pre { @value } } } + // } else { + // td { pre { @value } } + // } + // } + // }} + // } + // } + // } + // @if !node.tags.is_empty() { + // details { + // summary { @trs(lang, "node.tags") } + // ol { @for tag in &node.tags { + // li { @tag } + // }} + // } + // } + // } + // @if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { + // @NodeFilterSortForm { f: filter, lang } + // } + // @if !similar.is_empty() { + // h2 { @trs(lang, "node.similar") } + // ul.children.hlist {@for (node, udata) in similar.iter() { + // li { @NodeCard { node, udata, lang } } + // }} + // } + // @match node.kind { + // NodeKind::Show | NodeKind::Series | NodeKind::Season => { + // ol { @for (node, udata) in children.iter() { + // li { @NodeCardWide { node, udata, lang } } + // }} + // } + // NodeKind::Collection | NodeKind::Channel | _ => { + // ul.children {@for (node, udata) in children.iter() { + // li { @NodeCard { node, udata, lang } } + // }} + // } + // } + // } } } @@ -200,13 +190,12 @@ fn chapter_key_time(c: &Chapter, dur: f64) -> f64 { start * 0.8 + end * 0.2 } -pub fn aspect_class(kind: NodeKind) -> &'static str { - use NodeKind::*; +pub fn aspect_class(kind: Tag) -> &'static str { match kind { - Video | Episode => "aspect-thumb", - Collection => "aspect-land", - Season | Show | Series | Movie | ShortFormVideo => "aspect-port", - Channel | Music | Unknown => "aspect-square", + KIND_VIDEO | KIND_EPISODE => "aspect-thumb", + KIND_COLLECTION => "aspect-land", + KIND_SEASON | KIND_SHOW | KIND_SERIES | KIND_MOVIE | KIND_SHORTFORMVIDEO => "aspect-port", + KIND_CHANNEL | KIND_MUSIC | _ => "aspect-square", } } diff --git a/ui/src/props.rs b/ui/src/props.rs index a2e79e5..2860933 100644 --- a/ui/src/props.rs +++ b/ui/src/props.rs @@ -5,7 +5,7 @@ */ use crate::{ format::{MediaInfoExt, format_count, format_duration}, - locale::{Language, tr, trs}, + locale::{tr, trs}, }; markup::define! { diff --git a/ui/src/scaffold.rs b/ui/src/scaffold.rs index 4f1bc8b..0962f6e 100644 --- a/ui/src/scaffold.rs +++ b/ui/src/scaffold.rs @@ -6,21 +6,24 @@ use crate::{ CONF, FlashM, - locale::{Language, escape, tr, trs}, + locale::{escape, tr, trs}, }; -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 jellycommon::{ + jellyobject::{Object, Tag}, + 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()); -pub struct RenderInfo { - pub session: Option<SessionInfo>, - pub lang: Language, - pub importing: bool, +pub struct RenderInfo<'a> { + pub user: Option<Object<'a>>, + pub lang: Tag, + pub status_message: Option<&'a str>, } pub struct SessionInfo { @@ -28,7 +31,7 @@ pub struct SessionInfo { } markup::define! { - Scaffold<'a, Main: Render>(title: String, main: Main, class: &'a str, renderinfo: RenderInfo, lang: Language) { + Scaffold<'a, Main: Render>(ri: &'a RenderInfo<'a>, title: String, main: Main, class: &'a str) { @markup::doctype() html { head { @@ -38,29 +41,7 @@ markup::define! { script[src="/assets/bundle.js"] {} } body[class=class] { - nav { - h1 { a[href=if renderinfo.session.is_some() {u_home()} else {"/".to_string()}] { @if *LOGO_ENABLED { img.logo[src="/assets/logo.svg"]; } else { @CONF.brand } } } " " - @if let Some(_) = &renderinfo.session { - 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 renderinfo.importing { span.warn { @trs(lang, "nav.importing") } } - } - div.account { - @if let Some(session) = &renderinfo.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=u_admin_dashboard()] { p {@trs(lang, "nav.admin")} } " " - } - 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=u_account_register()] { p {@trs(lang, "nav.register")} } " " - a.login.hybrid_button[href=u_account_login()] { p {@trs(lang, "nav.login")} } - } - } - } + @Navbar { ri } #main { @main } footer { p { @CONF.brand " - " @CONF.slogan " | powered by " a[href="https://codeberg.org/metamuffin/jellything"]{"Jellything"} } @@ -69,8 +50,34 @@ markup::define! { } } + Navbar<'a>(ri: &'a RenderInfo<'a>) { + nav { + h1 { a[href=if ri.user.is_some() {u_home()} else {"/".to_string()}] { @if *LOGO_ENABLED { img.logo[src="/assets/logo.svg"]; } else { @CONF.brand } } } " " + @if ri.user.is_some() { + 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 renderinfo.importing { span.warn { @trs(lang, "nav.importing") } } + } + div.account { + @if let Some(user) = &ri.user { + 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=u_admin_dashboard()] { p {@trs(lang, "nav.admin")} } " " + } + 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=u_account_register()] { p {@trs(lang, "nav.register")} } " " + a.login.hybrid_button[href=u_account_login()] { p {@trs(lang, "nav.login")} } + } + } + } + } + FlashDisplay<'a>(flash: &'a FlashM) { - @if let Some((kind,message)) = &flash { + @if let Some((kind, message)) = &flash { @match kind.as_str() { "success" => { section.message { p.success { @message } } } "error" => { section.message { p.error { @message } } } diff --git a/ui/src/search.rs b/ui/src/search.rs index 99380e9..5b9909c 100644 --- a/ui/src/search.rs +++ b/ui/src/search.rs @@ -6,7 +6,7 @@ use crate::{ Page, - locale::{Language, tr, trs}, + locale::{tr, trs}, node_card::NodeCard, }; use markup::DynRender; diff --git a/ui/src/stats.rs b/ui/src/stats.rs index 2617b2c..0a88fac 100644 --- a/ui/src/stats.rs +++ b/ui/src/stats.rs @@ -7,7 +7,7 @@ use crate::{ Page, format::{format_duration, format_duration_long, format_kind, format_size}, - locale::{Language, tr, trs}, + locale::{tr, trs}, }; use jellycommon::routes::u_node_slug; use markup::raw; |