diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-03 18:15:47 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-03 18:15:47 +0100 |
commit | bf84be508aa415b45a51fc0fe007a0879f7bfab7 (patch) | |
tree | 6a98c9a534dbb5eed899d0f9ff6f6499dddfd0db /server/src/routes | |
parent | 26d3a70b0be2809177076e155f987e18e2b2ceb2 (diff) | |
download | jellything-bf84be508aa415b45a51fc0fe007a0879f7bfab7.tar jellything-bf84be508aa415b45a51fc0fe007a0879f7bfab7.tar.bz2 jellything-bf84be508aa415b45a51fc0fe007a0879f7bfab7.tar.zst |
nodepage in player and tags
Diffstat (limited to 'server/src/routes')
-rw-r--r-- | server/src/routes/ui/node.rs | 52 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 36 |
2 files changed, 70 insertions, 18 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 2f28f74..c2188a8 100644 --- a/server/src/routes/ui/player.rs +++ b/server/src/routes/ui/player.rs @@ -6,12 +6,14 @@ use super::{ account::session::{token, Session}, layout::LayoutPage, + node::{get_similar_media, DatabaseNodeUserDataExt, NodePage}, + sort::NodeFilterSort, }; use crate::{ database::Database, routes::{ stream::rocket_uri_macro_r_stream, - ui::{assets::rocket_uri_macro_r_item_backdrop, error::MyResult, layout::DynLayoutPage}, + ui::{error::MyResult, layout::DynLayoutPage}, }, uri, }; @@ -20,7 +22,7 @@ use jellybase::{permission::PermissionSetExt, CONF}; use jellycommon::{ stream::{StreamFormat, StreamSpec}, user::{PermissionSet, PlayerKind, UserPermission}, - Node, NodeID, SourceTrackKind, TrackID, + Node, NodeID, SourceTrackKind, TrackID, Visibility, }; use markup::DynRender; use rocket::{get, response::Redirect, Either, FromForm, State, UriDisplayQuery}; @@ -59,12 +61,23 @@ 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| { let perm = [ @@ -73,22 +86,22 @@ pub fn r_player( UserPermission::StreamFormat(StreamFormat::Fragment), ]; for perm in &perm { - sess.user.permissions.assert(perm)?; + session.user.permissions.assert(perm)?; } 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(perm.map(|e| (e, true)).into()), 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"); @@ -117,11 +130,8 @@ pub fn r_player( title: node.title.to_owned().unwrap_or_default(), class: Some("player"), content: markup::new! { - @if playing { - video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{} - } else { - img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))).to_string()]; - } + video[id="player", src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{} + @NodePage { children: &[], parents: &parents, filter: &NodeFilterSort::default(), node: &node, udata: &udata, player: true, similar: &similar } @conf }, })) |