diff options
-rw-r--r-- | server/src/routes/ui/sort.rs | 151 | ||||
-rw-r--r-- | web/style/forms.css | 59 | ||||
-rw-r--r-- | web/style/player.css | 26 |
3 files changed, 158 insertions, 78 deletions
diff --git a/server/src/routes/ui/sort.rs b/server/src/routes/ui/sort.rs index d8a44b2..dc017a2 100644 --- a/server/src/routes/ui/sort.rs +++ b/server/src/routes/ui/sort.rs @@ -11,67 +11,97 @@ pub struct NodeFilterSort { sort_order: Option<SortOrder>, } -#[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, +macro_rules! form_enum { + (enum $i:ident { $($vi:ident = $vk:literal),*, }) => { + #[derive(FromFormField, UriDisplayQuery, Clone, PartialEq, Eq)] + enum $i { $(#[field(value = $vk)] $vi),* } + impl $i { #[allow(unused)] const ALL: &'static [$i] = &[$($i::$vi),*]; } + }; } -#[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, -} +form_enum!( + enum FilterProperty { + FederationLocal = "fed_local", + FederationRemote = "fed_remote", + KindMovie = "kind_movie", + KindVideo = "kind_video", + KindCollection = "kind_collection", + KindChannel = "kind_channel", + KindShow = "kind_show", + KindSeries = "kind_series", + KindSeason = "kind_season", + KindEpisode = "kind_episode", + } +); + +form_enum!( + enum SortProperty { + ReleaseDate = "release_date", + Title = "title", + RatingRottenTomatoes = "rating_rt", + RatingMetacritic = "rating_mc", + RatingImdb = "rating_imdb", + RatingTmdb = "rating_tmdb", + RatingYoutubeViews = "rating_yt_views", + RatingYoutubeLikes = "rating_yt_likes", + RatingYoutubeFollowers = "rating_yt_followers", + } +); impl SortProperty { - const ALL: &'static [(SortProperty, &'static str)] = { + const CATS: &'static [(&'static str, &'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"), + ( + "General", + &[(Title, "Title"), (ReleaseDate, "Release Date")], + ), + ( + "By Rating", + &[ + (RatingImdb, "IMDb Rating"), + (RatingTmdb, "TMDB Rating"), + (RatingMetacritic, "Metacritic Rating"), + (RatingRottenTomatoes, "Rotten Tomatoes"), + (RatingYoutubeFollowers, "Youtube Subscribers"), + (RatingYoutubeLikes, "Youtube Likes"), + (RatingYoutubeViews, "Youtube Views"), + ], + ), ] }; } impl FilterProperty { - const ALL: &'static [(FilterProperty, &'static str)] = { + const CATS: &'static [(&'static str, &'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"), + ( + "By Kind", + &[ + (KindMovie, "Movie"), + (KindVideo, "Video"), + (KindCollection, "Collection"), + (KindChannel, "Channel"), + (KindShow, "Show"), + (KindSeries, "Series"), + (KindSeason, "Season"), + (KindEpisode, "Episode"), + ], + ), + ( + "By Federation", + &[(FederationLocal, "Local"), (FederationRemote, "Remote")], + ), ] }; } +impl NodeFilterSort { + pub fn is_open(&self) -> bool { + self.filter_kind.is_some() || self.sort_by.is_some() + } +} + #[rustfmt::skip] #[derive(FromFormField, UriDisplayQuery, Clone, Copy, PartialEq, Eq)] enum SortOrder { @@ -86,7 +116,7 @@ pub fn filter_and_sort_nodes( nodes.retain(|(_id, node, _udata)| { let mut o = true; if let Some(prop) = &f.filter_kind { - for (p, _) in FilterProperty::ALL { + for p in FilterProperty::ALL { if prop.contains(p) { continue; } @@ -124,6 +154,9 @@ pub fn filter_and_sort_nodes( SortProperty::RatingImdb => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::Imdb).unwrap_or(&0.)) }), + SortProperty::RatingTmdb => nodes.sort_by_cached_key(|(_, n, _)| { + SortAnyway(*n.ratings.get(&Rating::Tmdb).unwrap_or(&0.)) + }), SortProperty::RatingYoutubeViews => nodes.sort_by_cached_key(|(_, n, _)| { SortAnyway(*n.ratings.get(&Rating::YoutubeViews).unwrap_or(&0.)) }), @@ -143,19 +176,33 @@ pub fn filter_and_sort_nodes( markup::define! { NodeFilterSortForm<'a>(f: &'a NodeFilterSort) { - details.filtersort { + details.filtersort[open=f.is_open()] { 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; + .categories { + @for (cname, cat) in FilterProperty::CATS { + .category { + h3 { @cname } + @for (value, label) in *cat { + 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; + legend { "Sort" } + .categories { + @for (cname, cat) in SortProperty::CATS { + .category { + h3 { @cname } + @for (value, label) in *cat { + label { input[type="radio", name="sort_by", value=value, checked=Some(value)==f.sort_by.as_ref()]; @label } br; + } + } + } } } fieldset.sortorder { diff --git a/web/style/forms.css b/web/style/forms.css index 5e5319d..48a7dba 100644 --- a/web/style/forms.css +++ b/web/style/forms.css @@ -83,3 +83,62 @@ form.account h1 { form.account p { color: var(--font-dark); } + +legend { + font-size: 1.5em; +} +input[type="radio"] { + appearance: none; + display: inline-block; + width: 16px; + height: 16px; + border-radius: 8px; + padding: 2px; + background-clip: content-box; + border: 2px solid var(--font); + background-color: transparent; + transition: background-color 0.3s; +} +input[type="radio"]:checked { + background-color: var(--accent-light); +} + +input[type="checkbox"] { + appearance: none; + display: inline-block; + width: 16px; + height: 16px; + border-radius: 3px; + padding: 2px; + background-clip: content-box; + border: 2px solid var(--font); + background-color: transparent; + transition: background-color 0.3s; +} +input[type="checkbox"]:checked { + background-color: var(--accent-light); +} + +fieldset label { + transition: color 0.2s; +} +fieldset label:hover { + color: var(--accent-light); +} + +fieldset .categories { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +fieldset .categories .category { + margin-right: 2em; + margin-left: 2em; + min-width: max-content; + flex-basis: auto; + display: inline-flex; + flex-direction: column; +} +fieldset .categories .category h3 { + margin: 0px; +} diff --git a/web/style/player.css b/web/style/player.css index e01cbc3..1683573 100644 --- a/web/style/player.css +++ b/web/style/player.css @@ -16,32 +16,6 @@ form.playerconf { grid-template-rows: 3em auto 5em; } -legend { - font-size: 1.5em; -} -input[type="radio"] { - appearance: none; - display: inline-block; - width: 1.2em; - height: 1.2em; - border-radius: 8px; - padding: 2px; - background-clip: content-box; - border: 2px solid var(--font); - background-color: transparent; - transition: background-color 0.3s; -} -input[type="radio"]:checked { - background-color: var(--accent-light); -} - -fieldset label { - transition: color 0.2s; -} -fieldset label:hover { - color: var(--accent-light); -} - .playerconf { margin: 2em; } |