diff options
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/routes/ui/node.rs | 52 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 24 |
2 files changed, 65 insertions, 11 deletions
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs index 9deffbb..76ecd82 100644 --- a/server/src/routes/ui/node.rs +++ b/server/src/routes/ui/node.rs @@ -36,7 +36,7 @@ use jellycommon::{ Chapter, MediaInfo, Node, NodeID, NodeKind, PeopleGroup, Rating, SourceTrackKind, Visibility, }; use rocket::{get, serde::json::Json, Either, State}; -use std::{fmt::Write, sync::Arc}; +use std::{cmp::Reverse, collections::BTreeMap, fmt::Write, sync::Arc}; /// This function is a stub and only useful for use in the uri! macro. #[get("/n/<id>")] @@ -74,6 +74,9 @@ pub async fn r_library_node_filter<'a>( Vec::new() }; + let mut similar = get_similar_media(&node, db, &session)?; + + similar.retain(|(n, _)| n.visibility >= Visibility::Reduced); children.retain(|(n, _)| n.visibility >= Visibility::Reduced); parents.retain(|(n, _)| n.visibility >= Visibility::Reduced); @@ -98,13 +101,38 @@ pub async fn r_library_node_filter<'a>( Either::Left(LayoutPage { title: node.title.clone().unwrap_or_default(), content: markup::new! { - @NodePage { node: &node, udata: &udata, children: &children, parents: &parents, filter: &filter } + @NodePage { node: &node, udata: &udata, children: &children, parents: &parents, filter: &filter, player: false, similar: &similar } }, ..Default::default() }) }) } +pub fn get_similar_media( + node: &Node, + db: &Database, + session: &Session, +) -> Result<Vec<(Arc<Node>, NodeUserData)>> { + let this_id = NodeID::from_slug(&node.slug); + let mut ranking = BTreeMap::<NodeID, usize>::new(); + for tag in &node.tags { + let nodes = db.get_tag_nodes(tag)?; + let weight = 1_000_000 / nodes.len(); + for n in nodes { + if n != this_id { + *ranking.entry(n).or_default() += weight; + } + } + } + let mut ranking = ranking.into_iter().collect::<Vec<_>>(); + ranking.sort_by_key(|(_, k)| Reverse(*k)); + ranking + .into_iter() + .take(32) + .map(|(pid, _)| db.get_node_with_userdata(pid, &session)) + .collect::<anyhow::Result<Vec<_>>>() +} + markup::define! { NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData) { @let cls = format!("node card poster {}", aspect_class(node.kind)); @@ -151,12 +179,12 @@ markup::define! { } } } - NodePage<'a>(node: &'a Node, udata: &'a NodeUserData, children: &'a [(Arc<Node>, NodeUserData)], parents: &'a [(Arc<Node>, NodeUserData)], filter: &'a NodeFilterSort) { - @if !matches!(node.kind, NodeKind::Collection) { + 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, player: bool) { + @if !matches!(node.kind, NodeKind::Collection) && !player { img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))), loading="lazy"]; } .page.node { - @if !matches!(node.kind, NodeKind::Collection) { + @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"]; } } @@ -248,10 +276,24 @@ markup::define! { }} } } + @if !node.tags.is_empty() { + details { + summary { "Tags" } + ol { @for tag in &node.tags { + li { @tag } + }} + } + } } @if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { @NodeFilterSortForm { f: filter } } + @if !similar.is_empty() { + h2 { "Similar Media" } + ul.children.hlist {@for (node, udata) in similar.iter() { + li { @NodeCard { node, udata } } + }} + } @match node.kind { NodeKind::Show | NodeKind::Series | NodeKind::Season => { ol { @for (node, udata) in children.iter() { diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs index 2cc2dd4..d2a8236 100644 --- a/server/src/routes/ui/player.rs +++ b/server/src/routes/ui/player.rs @@ -6,6 +6,7 @@ use super::{ account::session::{token, Session}, layout::LayoutPage, + node::{get_similar_media, DatabaseNodeUserDataExt}, }; use crate::{ database::Database, @@ -19,7 +20,7 @@ use jellybase::CONF; use jellycommon::{ stream::{StreamContainer, StreamSpec}, user::{PermissionSet, PlayerKind}, - Node, NodeID, SourceTrackKind, TrackID, + Node, NodeID, SourceTrackKind, TrackID, Visibility, }; use markup::DynRender; use rocket::{get, response::Redirect, Either, FromForm, State, UriDisplayQuery}; @@ -59,28 +60,39 @@ fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: & #[get("/n/<id>/player?<conf..>", rank = 4)] pub fn r_player( - sess: Session, + session: Session, db: &State<Database>, id: NodeID, conf: PlayerConfig, ) -> MyResult<Either<DynLayoutPage<'_>, Redirect>> { - let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; + let (node, _udata) = db.get_node_with_userdata(id, &session)?; + + let mut parents = node + .parents + .iter() + .map(|pid| db.get_node_with_userdata(*pid, &session)) + .collect::<anyhow::Result<Vec<_>>>()?; + + let mut similar = get_similar_media(&node, db, &session)?; + + similar.retain(|(n, _)| n.visibility >= Visibility::Reduced); + parents.retain(|(n, _)| n.visibility >= Visibility::Reduced); let native_session = |action: &str| { Ok(Either::Right(Redirect::temporary(jellynative_url( action, conf.t.unwrap_or(0.), - &sess.user.native_secret, + &session.user.native_secret, &id.to_string(), &token::create( - sess.user.name, + session.user.name, PermissionSet::default(), // TODO chrono::Duration::hours(24), ), )))) }; - match conf.kind.unwrap_or(sess.user.player_preference) { + match conf.kind.unwrap_or(session.user.player_preference) { PlayerKind::Browser => (), PlayerKind::Native => { return native_session("player-v2"); |