aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/node.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/routes/ui/node.rs')
-rw-r--r--server/src/routes/ui/node.rs101
1 files changed, 65 insertions, 36 deletions
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index cc31de2..2932bf3 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -12,6 +12,7 @@ use crate::{
database::Database,
routes::{
api::AcceptJson,
+ progress::rocket_uri_macro_r_player_watched,
ui::{
account::session::Session,
assets::AssetRole,
@@ -21,9 +22,12 @@ use crate::{
},
uri,
};
-use anyhow::{anyhow, Context};
+use anyhow::{anyhow, Context, Result};
use jellybase::permission::NodePermissionExt;
-use jellycommon::{MediaInfo, NodeKind, NodePublic, Rating, SourceTrackKind};
+use jellycommon::{
+ user::{NodeUserData, WatchedState},
+ MediaInfo, NodeKind, NodePublic, Rating, SourceTrackKind,
+};
use rocket::{get, serde::json::Json, Either, State};
/// This function is a stub and only useful for use in the uri! macro.
@@ -48,6 +52,11 @@ pub async fn r_library_node_filter<'a>(
.ok_or(anyhow!("node does not exist"))?
.public;
+ let udata = db
+ .user_node
+ .get(&(session.user.name.clone(), id.to_string()))?
+ .unwrap_or_default();
+
if *aj {
return Ok(Either::Right(Json(node)));
}
@@ -55,34 +64,24 @@ pub async fn r_library_node_filter<'a>(
let mut children = node
.children
.iter()
- .map(|c| {
- Ok((
- c.to_owned(),
- db.node
- .get(c)?
- .ok_or(anyhow!("child does not exist: {c}"))?
- .public,
- ))
- })
+ .map(|c| Ok(db.get_node_with_userdata(c, &session)?))
.collect::<anyhow::Result<Vec<_>>>()?
.into_iter()
.collect();
filter_and_sort_nodes(&filter, &mut children);
- // node.media.unwrap().tracks[0].
-
Ok(Either::Left(LayoutPage {
title: node.title.to_string(),
content: markup::new! {
- @NodePage { node: &node, id: &id, children: &children, filter: &filter }
+ @NodePage { node: &node, id: &id, udata: &udata, children: &children, filter: &filter }
},
..Default::default()
}))
}
markup::define! {
- NodeCard<'a>(id: &'a str, node: &'a NodePublic) {
+ NodeCard<'a>(id: &'a str, node: &'a NodePublic, udata: &'a NodeUserData) {
@let cls = format!("node card poster {}", match node.kind {NodeKind::Channel => "aspect-square", NodeKind::Video => "aspect-thumb", NodeKind::Collection => "aspect-land", _ => "aspect-port"});
div[class=cls] {
.poster {
@@ -93,22 +92,8 @@ markup::define! {
@if !(matches!(node.kind, NodeKind::Collection | NodeKind::Channel)) {
a.play.icon[href=&uri!(r_player(id, PlayerConfig::default()))] { "play_arrow" }
}
- @Props { node }
+ @Props { node, udata }
}
- // .inner {
- // a[href=uri!(r_library_node(id))] {
- // img[src=uri!(r_item_assets(id, AssetRole::Poster, Some(1024)))];
- // }
- // div.details {
- // h3 { @node.title }
- // p.description { @node.description }
- // @if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) {
- // a[href=&uri!(r_library_node(id))] { "Open" }
- // } else {
- // a.play[href=&uri!(r_player(id, PlayerConfig::default()))] { "Watch now" }
- // }
- // }
- // }
}
div.title {
a[href=uri!(r_library_node(id))] {
@@ -117,7 +102,7 @@ markup::define! {
}
}
}
- NodePage<'a>(id: &'a str, node: &'a NodePublic, children: &'a Vec<(String, NodePublic)>, filter: &'a NodeFilterSort) {
+ NodePage<'a>(id: &'a str, node: &'a NodePublic, udata: &'a NodeUserData, children: &'a Vec<(String, NodePublic, NodeUserData)>, filter: &'a NodeFilterSort) {
@if !matches!(node.kind, NodeKind::Collection) {
img.backdrop[src=uri!(r_item_assets(id, AssetRole::Backdrop, Some(2048)))];
}
@@ -128,9 +113,22 @@ markup::define! {
.title {
h1 { @node.title }
@if node.media.is_some() { a.play[href=&uri!(r_player(id, PlayerConfig::default()))] { "Watch now" }}
+ @match udata.watched {
+ WatchedState::None |
+ WatchedState::Progress(_) => {
+ form.mark_watched[method="POST", action=uri!(r_player_watched(id, true))] {
+ input[type="submit", value="Mark Watched"];
+ }
+ }
+ WatchedState::Watched => {
+ form.mark_unwatched[method="POST", action=uri!(r_player_watched(id, false))] {
+ input[type="submit", value="Mark Unwatched"];
+ }
+ }
+ }
}
.details {
- @Props { node }
+ @Props { node, udata }
h3 { @node.tagline }
@if let Some(description) = &node.description {
p { @for line in description.lines() { @line br; } }
@@ -154,12 +152,12 @@ markup::define! {
}
@match node.kind {
NodeKind::Collection | NodeKind::Channel => {
- ul.children {@for (id, node) in children.iter() {
- li { @NodeCard { id, node } }
+ ul.children {@for (id, node, udata) in children.iter() {
+ li { @NodeCard { id, node, udata } }
}}
}
NodeKind::Series => {
- ol { @for (id, c) in children.iter() {
+ ol { @for (id, c, _) in children.iter() {
li { a[href=uri!(r_library_node(id))] { @c.title } }
}}
}
@@ -168,7 +166,7 @@ markup::define! {
}
}
- Props<'a>(node: &'a NodePublic) {
+ Props<'a>(node: &'a NodePublic, udata: &'a NodeUserData) {
.props {
@if let Some(m) = &node.media {
p { @format_duration(m.duration) }
@@ -193,6 +191,11 @@ markup::define! {
@if let Some(f) = &node.federated {
p.federation { @f }
}
+ @match udata.watched {
+ WatchedState::None => {}
+ WatchedState::Progress(x) => { p.progress { "Watched up to " @format_duration(x) } }
+ WatchedState::Watched => { p.watched { "Watched" } }
+ }
}
}
}
@@ -212,6 +215,32 @@ pub fn format_duration(mut d: f64) -> String {
s
}
+pub trait DatabaseNodeUserDataExt {
+ fn get_node_with_userdata(
+ &self,
+ id: &str,
+ session: &Session,
+ ) -> Result<(String, NodePublic, NodeUserData)>;
+}
+impl DatabaseNodeUserDataExt for Database {
+ fn get_node_with_userdata(
+ &self,
+ id: &str,
+ session: &Session,
+ ) -> Result<(String, NodePublic, NodeUserData)> {
+ Ok((
+ id.to_owned(),
+ self.node
+ .get(&id.to_owned())?
+ .ok_or(anyhow!("node does not exist: {id}"))?
+ .public,
+ self.user_node
+ .get(&(session.user.name.to_owned(), id.to_owned()))?
+ .unwrap_or_default(),
+ ))
+ }
+}
+
trait MediaInfoExt {
fn resolution_name(&self) -> &'static str;
}