aboutsummaryrefslogtreecommitdiff
path: root/ui/src/node_page.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-01-20 01:53:09 +0100
committermetamuffin <metamuffin@disroot.org>2026-01-20 01:53:09 +0100
commitbc155f7abea6ee5155b6460d367a6797205db4fd (patch)
treea2c55d96c493cf2b71bfc79539d4f8d0d8953392 /ui/src/node_page.rs
parent10736db63ad6d99e6cdce41920aa10dbeab02129 (diff)
downloadjellything-bc155f7abea6ee5155b6460d367a6797205db4fd.tar
jellything-bc155f7abea6ee5155b6460d367a6797205db4fd.tar.bz2
jellything-bc155f7abea6ee5155b6460d367a6797205db4fd.tar.zst
started ui refactor
Diffstat (limited to 'ui/src/node_page.rs')
-rw-r--r--ui/src/node_page.rs345
1 files changed, 167 insertions, 178 deletions
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",
}
}