From 3a6205f6077c1555f2f3bcf308f5e9605b8dbf53 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sat, 7 Mar 2026 13:34:04 +0100 Subject: reenable node page extras --- common/src/api.rs | 2 +- server/src/routes/node.rs | 241 +++++++++++++++++------------------------ ui/locale/src/lib.rs | 4 +- ui/src/components/node_card.rs | 2 +- ui/src/components/node_page.rs | 41 +++++-- 5 files changed, 134 insertions(+), 156 deletions(-) diff --git a/common/src/api.rs b/common/src/api.rs index bf79cc0..9cf5e04 100644 --- a/common/src/api.rs +++ b/common/src/api.rs @@ -12,7 +12,7 @@ use std::collections::BTreeMap; pub struct Nku<'a> { pub node: Cow<'a, Object>, pub userdata: Cow<'a, Object>, - pub role: Option<&'a str>, + pub role: Option>, } pub struct Stats { diff --git a/server/src/routes/node.rs b/server/src/routes/node.rs index ca07bac..d7ef0b5 100644 --- a/server/src/routes/node.rs +++ b/server/src/routes/node.rs @@ -6,27 +6,33 @@ use super::error::MyResult; use crate::request_info::RequestInfo; -use anyhow::anyhow; +use anyhow::{Result, anyhow}; use jellycommon::{ - jellyobject::{EMPTY, Path}, + jellyobject::{EMPTY, Object, Path, Tag}, *, }; -use jellydb::{Filter, Query}; +use jellydb::{Filter, MultiBehaviour, Query, Sort, SortOrder, Transaction, ValueSort}; use jellyui::components::node_page::NodePage; use rocket::{get, response::content::RawHtml}; -use std::borrow::Cow; +use std::{borrow::Cow, collections::BTreeMap}; #[get("/n/")] pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult> { ri.require_user()?; let mut nku = None; + let mut children = Vec::new(); + let mut credits = Vec::new(); + let mut credited = Vec::new(); ri.state.database.transaction(&mut |txn| { if let Some(row) = txn.query_single(Query { filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()), ..Default::default() })? { let n = txn.get(row)?.unwrap(); + children = c_children(txn, row, &n)?; + credits = c_credits(txn, &n)?; + credited = c_credited(txn, row)?; nku = Some(Nku { node: Cow::Owned(n), userdata: Cow::Borrowed(EMPTY), @@ -42,146 +48,99 @@ pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult> { Ok(ri.respond_ui(&NodePage { ri: &ri.render_info(), nku, + children: &children, + credited: &credited, + credits: &credits, })) } -// fn c_children( -// page: &mut ObjectBufferBuilder, -// txn: &mut dyn Transaction, -// row: u64, -// nku: &Object, -// ) -> Result<()> { -// let kind = nku -// .get(NKU_NODE) -// .unwrap_or_default() -// .get(NO_KIND) -// .unwrap_or(KIND_COLLECTION); - -// let (order, path) = match kind { -// KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])), -// KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])), -// _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])), -// }; - -// let children_rows = txn -// .query(Query { -// sort: Sort::Value(ValueSort { -// multi: MultiBehaviour::First, -// offset: None, -// order, -// path, -// }), -// filter: Filter::All(vec![ -// Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), -// Filter::Match(Path(vec![NO_PARENT.0]), row.into()), -// ]), -// ..Default::default() -// })? -// .collect::>>()?; - -// if children_rows.is_empty() { -// return Ok(()); -// } - -// let mut list = ObjectBufferBuilder::default(); - -// list.push( -// NODELIST_DISPLAYSTYLE, -// match kind { -// KIND_SEASON | KIND_SHOW => NLSTYLE_LIST, -// _ => NLSTYLE_GRID, -// }, -// ); - -// for (row, _) in children_rows { -// list.push( -// NODELIST_ITEM, -// Object::EMPTY -// .insert(NKU_NODE, txn.get(row)?.unwrap().as_object()) -// .as_object(), -// ); -// } - -// page.push(VIEW_NODE_LIST, list.finish().as_object()); -// Ok(()) -// } - -// fn c_credits( -// page: &mut ObjectBufferBuilder, -// txn: &mut dyn Transaction, -// nku: &Object, -// ) -> Result<()> { -// if !nku.get(NKU_NODE).unwrap_or_default().has(NO_CREDIT.0) { -// return Ok(()); -// } - -// let mut cats = BTreeMap::<_, Vec<_>>::new(); -// for cred in nku.get(NKU_NODE).unwrap_or_default().iter(NO_CREDIT) { -// let mut o = ObjectBuffer::empty(); -// if let Some(row) = cred.get(CR_NODE) { -// let node = txn.get(row)?.unwrap(); -// o = o.as_object().insert(NKU_NODE, node.as_object()); -// } -// if let Some(role) = cred.get(CR_ROLE) { -// o = o.as_object().insert(NKU_ROLE, role) -// } -// cats.entry(cred.get(CR_KIND).unwrap_or(CRCAT_CREW)) -// .or_default() -// .push(o); -// } -// let mut cats = cats.into_iter().collect::>(); -// cats.sort_by_key(|(c, _)| match *c { -// CRCAT_CAST => 0, -// CRCAT_CREW => 1, -// _ => 100, -// }); -// for (cat, elems) in cats { -// let mut list = ObjectBufferBuilder::default(); -// list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE); -// list.push(NODELIST_TITLE, &format!("tag.cred.kind.{cat}")); -// for item in elems { -// list.push(NODELIST_ITEM, item.as_object()); -// } -// page.push(VIEW_NODE_LIST, list.finish().as_object()); -// } - -// Ok(()) -// } - -// fn c_credited(page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64) -> Result<()> { -// let children_rows = txn -// .query(Query { -// sort: Sort::Value(ValueSort { -// multi: MultiBehaviour::First, -// offset: None, -// order: SortOrder::Ascending, -// path: Path(vec![NO_TITLE.0]), -// }), -// filter: Filter::All(vec![ -// Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), -// Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()), -// ]), -// ..Default::default() -// })? -// .collect::>>()?; - -// if children_rows.is_empty() { -// return Ok(()); -// } +fn c_children(txn: &mut dyn Transaction, row: u64, node: &Object) -> Result>> { + let kind = node.get(NO_KIND).unwrap_or(KIND_COLLECTION); + let (order, path) = match kind { + KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])), + KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])), + _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])), + }; -// let mut list = ObjectBufferBuilder::default(); -// list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_GRID); -// list.push(NODELIST_TITLE, "node.credited"); + let children_rows = txn + .query(Query { + sort: Sort::Value(ValueSort { + multi: MultiBehaviour::First, + offset: None, + order, + path, + }), + filter: Filter::All(vec![ + Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), + Filter::Match(Path(vec![NO_PARENT.0]), row.into()), + ]), + ..Default::default() + })? + .collect::>>()?; + + let mut list = Vec::new(); + for (row, _) in children_rows { + list.push(Nku { + node: Cow::Owned(txn.get(row)?.unwrap()), + role: None, + userdata: Cow::Borrowed(EMPTY), + }); + } + Ok(list) +} -// for (row, _) in children_rows { -// list.push( -// NODELIST_ITEM, -// Object::EMPTY -// .insert(NKU_NODE, txn.get(row)?.unwrap().as_object()) -// .as_object(), -// ); -// } +fn c_credits(txn: &mut dyn Transaction, node: &Object) -> Result>)>> { + if !node.has(NO_CREDIT.0) { + return Ok(Vec::new()); + } + + let mut cats = BTreeMap::<_, Vec<_>>::new(); + for cred in node.iter(NO_CREDIT) { + let Some(row) = cred.get(CR_NODE) else { + continue; + }; + cred.get(CR_ROLE); + cats.entry(cred.get(CR_KIND).unwrap_or(CRCAT_CREW)) + .or_default() + .push(Nku { + node: Cow::Owned(txn.get(row)?.unwrap()), + role: cred.get(CR_ROLE).map(String::from).map(Cow::Owned), + userdata: Cow::Borrowed(EMPTY), + }); + } + let mut cats = cats.into_iter().collect::>(); + cats.sort_by_key(|(c, _)| match *c { + CRCAT_CAST => 0, + CRCAT_CREW => 1, + _ => 100, + }); + Ok(cats) +} -// page.push(VIEW_NODE_LIST, list.finish().as_object()); -// Ok(()) -// } +fn c_credited(txn: &mut dyn Transaction, row: u64) -> Result>> { + let children_rows = txn + .query(Query { + sort: Sort::Value(ValueSort { + multi: MultiBehaviour::First, + offset: None, + order: SortOrder::Ascending, + path: Path(vec![NO_TITLE.0]), + }), + filter: Filter::All(vec![ + Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), + Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()), + ]), + ..Default::default() + })? + .collect::>>()?; + + let mut list = Vec::new(); + for (row, _) in children_rows { + list.push(Nku { + node: Cow::Owned(txn.get(row)?.unwrap()), + role: None, + userdata: Cow::Borrowed(EMPTY), + }); + } + Ok(list) +} diff --git a/ui/locale/src/lib.rs b/ui/locale/src/lib.rs index 2028418..d50d766 100644 --- a/ui/locale/src/lib.rs +++ b/ui/locale/src/lib.rs @@ -48,8 +48,8 @@ pub fn tr(lang: &str, key: &str) -> Cow<'static, str> { tr_map .get(key) .copied() - .unwrap_or("MISSING TRANSLATION") - .into() + .map(Cow::Borrowed) + .unwrap_or_else(|| format!("MISSING TRANSLATION {:?}", key).into()) } pub fn escape(str: &str) -> String { diff --git a/ui/src/components/node_card.rs b/ui/src/components/node_card.rs index 2f5bae7..e9853c7 100644 --- a/ui/src/components/node_card.rs +++ b/ui/src/components/node_card.rs @@ -37,7 +37,7 @@ markup::define! { } div.subtitle { span { - @nku.role.or(node.get(NO_SUBTITLE)) + @nku.role.as_deref().or(node.get(NO_SUBTITLE)) } } } diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs index 2a5848b..265e2d4 100644 --- a/ui/src/components/node_page.rs +++ b/ui/src/components/node_page.rs @@ -4,7 +4,11 @@ Copyright (C) 2026 metamuffin */ -use crate::{RenderInfo, components::props::Props, page}; +use crate::{ + RenderInfo, + components::{node_card::NodeCard, props::Props}, + page, +}; use jellycommon::{ jellyobject::{EMPTY, Object, Tag, TypedTag}, routes::{u_image, u_node_slug_player}, @@ -29,7 +33,13 @@ page!(Player<'_>, |x| x .into()); markup::define! { - NodePage<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { + NodePage<'a>( + ri: &'a RenderInfo<'a>, + nku: Nku<'a>, + children: &'a [Nku<'a>], + credits: &'a [(Tag, Vec>)], + credited: &'a [Nku<'a>] + ) { @let node = &nku.node; @let slug = node.get(NO_SLUG).unwrap_or_default(); @let pics = node.get(NO_PICTURES).unwrap_or(EMPTY); @@ -145,15 +155,24 @@ markup::define! { } } } - // @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 } } - // }} - // } + + @for (cat, items) in *credits { + h2 { @tr(ri.lang, &format!("tag.cred.kind.{cat}")) } + ul.nl.inline { @for nku in items { + li { @NodeCard { ri, nku } } + }} + } + @if !credited.is_empty() { + h2 { @tr(ri.lang, &format!("node.credited")) } + ul.nl.grid { @for nku in *credited { + li { @NodeCard { ri, nku } } + }} + } + @if !children.is_empty() { + ul.nl.grid { @for nku in *children { + li { @NodeCard { ri, nku } } + }} + } } Player<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { -- cgit v1.3