aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/routes/ui/node.rs52
-rw-r--r--server/src/routes/ui/player.rs24
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");