use jellycommon::{helpers::SortAnyway, user::NodeUserData, NodeKind, NodePublic, Rating}; use rocket::{ http::uri::fmt::{Query, UriDisplay}, FromForm, FromFormField, UriDisplayQuery, }; #[derive(FromForm, UriDisplayQuery, Default, Clone)] pub struct NodeFilterSort { sort_by: Option, filter_kind: Option>, sort_order: Option, } #[rustfmt::skip] #[derive(FromFormField, UriDisplayQuery, Clone, PartialEq, Eq)] enum FilterProperty { #[field(value = "fed_local")] FederationLocal, #[field(value = "fed_remote")] FederationRemote, #[field(value = "kind_movie")] KindMovie, #[field(value = "kind_video")] KindVideo, #[field(value = "kind_collection")] KindCollection, #[field(value = "kind_channel")] KindChannel, #[field(value = "kind_show")] KindShow, #[field(value = "kind_series")] KindSeries, #[field(value = "kind_season")] KindSeason, #[field(value = "kind_episode")] KindEpisode, } #[rustfmt::skip] #[derive(FromFormField, UriDisplayQuery, Clone, PartialEq, Eq)] enum SortProperty { #[field(value = "release_date")] ReleaseDate, #[field(value = "title")] Title, #[field(value = "rating_rt")] RatingRottenTomatoes, #[field(value = "rating_mc")] RatingMetacritic, #[field(value = "rating_imdb")] RatingImdb, #[field(value = "rating_yt_views")] RatingYoutubeViews, #[field(value = "rating_yt_likes")] RatingYoutubeLikes, #[field(value = "rating_yt_followers")] RatingYoutubeFollowers, } impl SortProperty { const ALL: &'static [(SortProperty, &'static str)] = { use SortProperty::*; &[ (Title, "Title"), (ReleaseDate, "Release Date"), (RatingImdb, "IMDb Rating"), (RatingMetacritic, "Metacritic Rating"), (RatingRottenTomatoes, "Rotten Tomatoes"), (RatingYoutubeFollowers, "Youtube Subscribers"), (RatingYoutubeLikes, "Youtube Likes"), (RatingYoutubeViews, "Youtube Views"), ] }; } impl FilterProperty { const ALL: &'static [(FilterProperty, &'static str)] = { use FilterProperty::*; &[ (FederationLocal, "Local"), (FederationRemote, "Remote"), (KindMovie, "Movie"), (KindVideo, "Video"), (KindCollection, "Collection"), (KindChannel, "Channel"), (KindShow, "Show"), (KindSeries, "Series"), (KindSeason, "Season"), (KindEpisode, "Episode"), ] }; } #[rustfmt::skip] #[derive(FromFormField, UriDisplayQuery, Clone, Copy, PartialEq, Eq)] enum SortOrder { #[field(value = "ascending")] Ascending, #[field(value = "descending")] Descending, } pub fn filter_and_sort_nodes( f: &NodeFilterSort, nodes: &mut Vec<(String, NodePublic, NodeUserData)>, ) { nodes.retain(|(_id, node, _udata)| { let mut o = true; if let Some(prop) = &f.filter_kind { for (p, _) in FilterProperty::ALL { if prop.contains(p) { continue; } o &= !match p { FilterProperty::FederationLocal => node.federated.is_none(), FilterProperty::FederationRemote => node.federated.is_some(), FilterProperty::KindMovie => node.kind == NodeKind::Movie, FilterProperty::KindVideo => node.kind == NodeKind::Video, FilterProperty::KindCollection => node.kind == NodeKind::Collection, FilterProperty::KindChannel => node.kind == NodeKind::Channel, FilterProperty::KindShow => node.kind == NodeKind::Show, FilterProperty::KindSeries => node.kind == NodeKind::Series, FilterProperty::KindSeason => node.kind == NodeKind::Season, FilterProperty::KindEpisode => node.kind == NodeKind::Episode, } } } if let Some(SortProperty::ReleaseDate) = &f.sort_by { o &= node.release_date.is_some() } o }); if let Some(sort_prop) = &f.sort_by { match sort_prop { SortProperty::ReleaseDate => { nodes.sort_by_key(|(_, n, _)| n.release_date.expect("asserted above")) } SortProperty::Title => nodes.sort_by(|(_, a, _), (_, b, _)| a.title.cmp(&b.title)), SortProperty::RatingRottenTomatoes => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::RottenTomatoes).unwrap_or(&0.)) }), SortProperty::RatingMetacritic => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::Metacritic).unwrap_or(&0.)) }), SortProperty::RatingImdb => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::Imdb).unwrap_or(&0.)) }), SortProperty::RatingYoutubeViews => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::YoutubeViews).unwrap_or(&0.)) }), SortProperty::RatingYoutubeLikes => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::YoutubeLikes).unwrap_or(&0.)) }), SortProperty::RatingYoutubeFollowers => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::YoutubeFollowers).unwrap_or(&0.)) }), } } match f.sort_order.unwrap_or(SortOrder::Ascending) { SortOrder::Ascending => (), SortOrder::Descending => nodes.reverse(), } } markup::define! { NodeFilterSortForm<'a>(f: &'a NodeFilterSort) { details.filtersort { summary { "Filter and Sort" } form[method="GET", action=""] { fieldset.filter { legend { "Filter" } @for (value, label) in FilterProperty::ALL { label { input[type="checkbox", name="filter_kind", value=value, checked=f.filter_kind.as_ref().map(|k|k.contains(&value)).unwrap_or(true)]; @label } br; } } fieldset.sortby { legend { "Sort By" } @for (value, label) in SortProperty::ALL { label { input[type="radio", name="sort_by", value=value, checked=Some(value)==f.sort_by.as_ref()]; @label } br; } } fieldset.sortorder { legend { "Sort Order" } @use SortOrder::*; @for (value, label) in [(Ascending, "Ascending"), (Descending, "Descending")] { label { input[type="radio", name="sort_order", value=value, checked=Some(value)==f.sort_order]; @label } br; } } input[type="submit", value="Apply"]; } } } } impl markup::Render for SortProperty { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { writer.write_fmt(format_args!("{}", self as &dyn UriDisplay)) } } impl markup::Render for SortOrder { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { writer.write_fmt(format_args!("{}", self as &dyn UriDisplay)) } } impl markup::Render for FilterProperty { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { writer.write_fmt(format_args!("{}", self as &dyn UriDisplay)) } }