diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-27 19:25:11 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-27 19:25:11 +0200 |
commit | 11a585b3dbe620dcc8772e713b22f1d9ba80d598 (patch) | |
tree | 44f8d97137412aefc79a2425a489c34fa3e5f6c5 /server/src/ui/home.rs | |
parent | d871aa7c5bba49ff55170b5d2dac9cd440ae7170 (diff) | |
download | jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.bz2 jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.zst |
move files around
Diffstat (limited to 'server/src/ui/home.rs')
-rw-r--r-- | server/src/ui/home.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs new file mode 100644 index 0000000..fbce99b --- /dev/null +++ b/server/src/ui/home.rs @@ -0,0 +1,173 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ +use super::{ + error::MyResult, + layout::{trs, DynLayoutPage, LayoutPage}, + node::{DatabaseNodeUserDataExt, NodeCard}, +}; +use crate::{api::AcceptJson, database::Database, locale::AcceptLanguage, logic::session::Session}; +use anyhow::Context; +use chrono::{Datelike, Utc}; +use jellybase::{locale::tr, CONF}; +use jellycommon::{api::ApiHomeResponse, user::WatchedState, NodeID, NodeKind, Rating, Visibility}; +use rocket::{get, serde::json::Json, Either, State}; + +#[get("/home")] +pub fn r_home( + sess: Session, + db: &State<Database>, + aj: AcceptJson, + lang: AcceptLanguage, +) -> MyResult<Either<DynLayoutPage, Json<ApiHomeResponse>>> { + let AcceptLanguage(lang) = lang; + let mut items = db.list_nodes_with_udata(&sess.user.name)?; + + let mut toplevel = db + .get_node_children(NodeID::from_slug("library")) + .context("root node missing")? + .into_iter() + .map(|n| db.get_node_with_userdata(n, &sess)) + .collect::<anyhow::Result<Vec<_>>>()?; + toplevel.sort_by_key(|(n, _)| n.index.unwrap_or(usize::MAX)); + + let mut categories = Vec::<(String, Vec<_>)>::new(); + + categories.push(( + "home.bin.continue_watching".to_string(), + items + .iter() + .filter(|(_, u)| matches!(u.watched, WatchedState::Progress(_))) + .cloned() + .collect(), + )); + categories.push(( + "home.bin.watchlist".to_string(), + items + .iter() + .filter(|(_, u)| matches!(u.watched, WatchedState::Pending)) + .cloned() + .collect(), + )); + + items.retain(|(n, _)| matches!(n.visibility, Visibility::Visible)); + + items.sort_by_key(|(n, _)| n.release_date.map(|d| -d).unwrap_or(i64::MAX)); + + categories.push(( + "home.bin.latest_video".to_string(), + items + .iter() + .filter(|(n, _)| matches!(n.kind, NodeKind::Video)) + .take(16) + .cloned() + .collect(), + )); + categories.push(( + "home.bin.latest_music".to_string(), + items + .iter() + .filter(|(n, _)| matches!(n.kind, NodeKind::Music)) + .take(16) + .cloned() + .collect(), + )); + categories.push(( + "home.bin.latest_short_form".to_string(), + items + .iter() + .filter(|(n, _)| matches!(n.kind, NodeKind::ShortFormVideo)) + .take(16) + .cloned() + .collect(), + )); + + items.sort_by_key(|(n, _)| { + n.ratings + .get(&Rating::Tmdb) + .map(|x| (*x * -1000.) as i32) + .unwrap_or(0) + }); + + categories.push(( + "home.bin.max_rating".to_string(), + items + .iter() + .take(16) + .filter(|(n, _)| n.ratings.contains_key(&Rating::Tmdb)) + .cloned() + .collect(), + )); + + items.retain(|(n, _)| { + matches!( + n.kind, + NodeKind::Video | NodeKind::Movie | NodeKind::Episode | NodeKind::Music + ) + }); + + categories.push(( + "home.bin.daily_random".to_string(), + (0..16) + .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) + .collect(), + )); + + { + let mut items = items.clone(); + items.retain(|(_, u)| matches!(u.watched, WatchedState::Watched)); + categories.push(( + "home.bin.watch_again".to_string(), + (0..16) + .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) + .collect(), + )); + } + + items.retain(|(n, _)| matches!(n.kind, NodeKind::Music)); + categories.push(( + "home.bin.daily_random_music".to_string(), + (0..16) + .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) + .collect(), + )); + + Ok(if *aj { + Either::Right(Json(ApiHomeResponse { + toplevel, + categories, + })) + } else { + Either::Left(LayoutPage { + title: tr(lang, "home").to_string(), + content: markup::new! { + h2 { @tr(lang, "home.bin.root").replace("{title}", &CONF.brand) } + ul.children.hlist {@for (node, udata) in &toplevel { + li { @NodeCard { node, udata, lang: &lang } } + }} + @for (name, nodes) in &categories { + @if !nodes.is_empty() { + h2 { @trs(&lang, &name) } + ul.children.hlist {@for (node, udata) in nodes { + li { @NodeCard { node, udata, lang: &lang } } + }} + } + } + }, + ..Default::default() + }) + }) +} + +fn cheap_daily_random(i: usize) -> usize { + xorshift(xorshift(Utc::now().num_days_from_ce() as u64) + i as u64) as usize +} + +fn xorshift(mut x: u64) -> u64 { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + x +} |