aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-03 18:15:47 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-03 18:15:47 +0100
commitbf84be508aa415b45a51fc0fe007a0879f7bfab7 (patch)
tree6a98c9a534dbb5eed899d0f9ff6f6499dddfd0db /server/src/routes
parent26d3a70b0be2809177076e155f987e18e2b2ceb2 (diff)
downloadjellything-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.rs52
-rw-r--r--server/src/routes/ui/player.rs36
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
},
}))