diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-01-23 04:19:24 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-01-23 04:19:24 +0100 |
| commit | 3671a4e07565c86f8071fb2309f463aeaf684ba3 (patch) | |
| tree | 9a9057c7dcc174ada17a45a195502ff94b2f2946 /ui/src/components/node_page.rs | |
| parent | 10cdaaa30a6b4a187797434dc8d959780f0e8fbf (diff) | |
| download | jellything-3671a4e07565c86f8071fb2309f463aeaf684ba3.tar jellything-3671a4e07565c86f8071fb2309f463aeaf684ba3.tar.bz2 jellything-3671a4e07565c86f8071fb2309f463aeaf684ba3.tar.zst | |
move ui code around
Diffstat (limited to 'ui/src/components/node_page.rs')
| -rw-r--r-- | ui/src/components/node_page.rs | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs new file mode 100644 index 0000000..4a594d6 --- /dev/null +++ b/ui/src/components/node_page.rs @@ -0,0 +1,194 @@ +/* + 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::props::Props, locale::tr}; +use jellycommon::{ + jellyobject::{Object, Tag, TypedTag}, + routes::{u_image, u_node_slug_player}, + *, +}; +use std::marker::PhantomData; + +markup::define! { + NodePage<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) { + @let node = nku.get(NKU_NODE).unwrap_or_default(); + @let slug = node.get(NO_SLUG).unwrap_or_default(); + @if let Some(path) = node.get(NO_PICTURES).unwrap_or_default().get(PICT_BACKDROP) { + img.backdrop[src=u_image(path, 2048)]; + } + @if let Some(path) = node.get(NO_PICTURES).unwrap_or_default().get(PICT_COVER) { + @let cls = format!("bigposter {}", aspect_class(node)); + div[class=cls] { img[src=u_image(path, 2048), loading="lazy"]; } + } + .title { + h1 { @node.get(NO_TITLE).unwrap_or_default() } + // ul.parents { @for (node, _) in *parents { li { + // a.component[href=u_node_slug(&node.slug)] { @node.title } + // }}} + @if node.has(NO_TRACK.0) { + a.play[href=u_node_slug_player(slug)] { @tr(ri.lang, "node.player_link") } + // @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 { ri, nku: *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; } } + } + // @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 { @tr(ri.lang, "media.tracks") } + ol { @for track in node.iter(NO_TRACK) { + li { "track" @track.get(TR_NAME) } + }} + } + @if let Some(idents) = node.get(NO_IDENTIFIERS) { + details { + summary { @tr(ri.lang, "node.external_ids") } + table { + @for (key, value) in idents.entries::<&str>() { tr { + tr { + td { @tr(ri.lang, &format!("id.{}", TAGREG.name(key))) } + @if let Some(url) = external_id_url(key, value) { + td { a[href=url] { pre { @value } } } + } else { + td { pre { @value } } + } + } + }} + } + } + } + @if node.has(NO_TAG.0) { + details { + summary { @tr(ri.lang, "node.tags") } + ol { @for tag in node.iter(NO_TAG) { + 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 } } + // }} + // } + // } + // } + } +} + +fn chapter_key_time(c: Object, dur: f64) -> f64 { + let start = c.get(CH_START).unwrap_or(0.); + let end = c.get(CH_END).unwrap_or(dur); + start * 0.8 + end * 0.2 +} + +pub fn aspect_class(node: Object<'_>) -> &'static str { + let kind = node.get(NO_KIND).unwrap_or(KIND_COLLECTION); + match kind { + 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", + } +} + +fn external_id_url(key: Tag, value: &str) -> Option<String> { + Some(match TypedTag(key, PhantomData) { + IDENT_YOUTUBE_VIDEO => format!("https://youtube.com/watch?v={value}"), + IDENT_YOUTUBE_CHANNEL => format!("https://youtube.com/channel/{value}"), + IDENT_YOUTUBE_CHANNEL_HANDLE => format!("https://youtube.com/channel/@{value}"), + IDENT_MUSICBRAINZ_RELEASE => format!("https://musicbrainz.org/release/{value}"), + IDENT_MUSICBRAINZ_ARTIST => format!("https://musicbrainz.org/artist/{value}"), + IDENT_MUSICBRAINZ_RELEASE_GROUP => { + format!("https://musicbrainz.org/release-group/{value}") + } + IDENT_MUSICBRAINZ_RECORDING => { + format!("https://musicbrainz.org/recording/{value}") + } + _ => return None, + }) +} |