diff options
| -rw-r--r-- | common/object/src/registry.rs | 2 | ||||
| -rw-r--r-- | common/src/api.rs | 185 | ||||
| -rw-r--r-- | common/src/lib.rs | 151 | ||||
| -rw-r--r-- | common/src/node.rs | 140 | ||||
| -rw-r--r-- | common/src/user.rs | 11 | ||||
| -rw-r--r-- | ui/src/filter_sort.rs | 191 | ||||
| -rw-r--r-- | ui/src/format.rs | 84 | ||||
| -rw-r--r-- | ui/src/home.rs | 4 | ||||
| -rw-r--r-- | ui/src/items.rs | 2 | ||||
| -rw-r--r-- | ui/src/search.rs | 6 |
10 files changed, 375 insertions, 401 deletions
diff --git a/common/object/src/registry.rs b/common/object/src/registry.rs index c831b28..2cd2a1f 100644 --- a/common/object/src/registry.rs +++ b/common/object/src/registry.rs @@ -60,7 +60,7 @@ macro_rules! fields { macro_rules! enums { ($($id:ident = $tag:literal $name:literal;)*) => { $(pub const $id: $crate::Tag = $crate::Tag($tag);)* - fn register_enums(reg: &mut $crate::Registry) { + pub(crate) fn register_enums(reg: &mut $crate::Registry) { $(reg.add($crate::Tag($tag), $crate::TagInfo { name: $name, r#type: None });)* } }; diff --git a/common/src/api.rs b/common/src/api.rs index 0dc8f43..2123477 100644 --- a/common/src/api.rs +++ b/common/src/api.rs @@ -3,120 +3,97 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::user::{NodeUserData, User}; -use std::{collections::BTreeMap, sync::Arc, time::Duration}; -pub struct ApiNodeResponse { - pub parents: NodesWithUdata, - pub children: NodesWithUdata, - pub node: Arc<Node>, - pub userdata: NodeUserData, -} +use jellyobject::{Object, Tag, enums, fields}; -pub struct ApiSearchResponse { - pub count: usize, - pub results: NodesWithUdata, - pub duration: Duration, -} +fields! { + QUERY_PARENT: u64 = 2000 "parent"; + QUERY_SEARCH: &str = 2001 "search"; + QUERY_KIND: Tag = 2004 "kind"; // multi + QUERY_SORT: Tag = 2002 "sort"; // one of RTYP_*, NU_RATING, NO_DURATION, NO_NAME + QUERY_SORT_ASCENDING: () = 2003 "sort_ascending"; -pub struct ApiItemsResponse { - pub count: usize, - pub pages: usize, - pub items: NodesWithUdata, -} + VIEW_TITLE: &str = 2005 "title"; + VIEW_MESSAGE: &str = 2010 "message"; + VIEW_NODE_PAGE: Object = 2011 "node_page"; + VIEW_NODE_LIST: Object = 2012 "node_list"; // multi + VIEW_PLAYER: u64 = 2028 "player"; -pub struct ApiHomeResponse { - pub toplevel: NodesWithUdata, - pub categories: Vec<(String, NodesWithUdata)>, -} + NKU_NODE: Object = 2025 "node"; + NKU_UDATA: Object = 2026 "udata"; + NKU_ROLE: &str = 2027 "role"; -pub struct ApiStatsResponse { - pub kinds: BTreeMap<NodeKind, StatsBin>, - pub total: StatsBin, + NODELIST_TITLE: &str = 2007 "title"; + NODELIST_DISPLAYSTYLE: &str = 2008 "displaystyle"; + NODELIST_ITEM: &str = 2009 "item"; } -pub struct ApiAdminUsersResponse { - pub users: Vec<User>, +enums! { + NLSTYLE_GRID = 1023 "grid"; + NLSTYLE_INLINE = 1024 "inline"; + NLSTYLE_LIST = 1023 "list"; } -pub struct LogLine { - pub time: DateTime<Utc>, - pub module: Option<&'static str>, - pub level: LogLevel, - pub message: String, -} +// use crate::user::{NodeUserData, User}; +// use std::{collections::BTreeMap, sync::Arc, time::Duration}; +// pub struct ApiNodeResponse { +// pub parents: NodesWithUdata, +// pub children: NodesWithUdata, +// pub node: Arc<Node>, +// pub userdata: NodeUserData, +// } -url_enum!( - #[derive(Serialize, Deserialize, Clone, Copy, PartialEq)] - enum LogLevel { - Trace = "trace", - Debug = "debug", - Info = "info", - Warn = "warn", - Error = "error", - } -); +// pub struct ApiSearchResponse { +// pub count: usize, +// pub results: NodesWithUdata, +// pub duration: Duration, +// } -#[derive(Default, Serialize, Deserialize)] -pub struct StatsBin { - pub runtime: f64, - pub size: u64, - pub count: usize, - pub max_runtime: f64, - pub max_runtime_node: String, - pub max_size: u64, - pub max_size_node: String, -} +// pub struct ApiItemsResponse { +// pub count: usize, +// pub pages: usize, +// pub items: NodesWithUdata, +// } -#[derive(Debug, Default, Clone)] -pub struct NodeFilterSort { - pub sort_by: Option<SortProperty>, - pub filter_kind: Option<Vec<FilterProperty>>, - pub sort_order: Option<SortOrder>, -} +// pub struct ApiHomeResponse { +// pub toplevel: NodesWithUdata, +// pub categories: Vec<(String, NodesWithUdata)>, +// } + +// pub struct ApiStatsResponse { +// pub kinds: BTreeMap<NodeKind, StatsBin>, +// pub total: StatsBin, +// } + +// pub struct ApiAdminUsersResponse { +// pub users: Vec<User>, +// } + +// pub struct LogLine { +// pub time: DateTime<Utc>, +// pub module: Option<&'static str>, +// pub level: LogLevel, +// pub message: String, +// } + +// url_enum!( +// #[derive(Serialize, Deserialize, Clone, Copy, PartialEq)] +// enum LogLevel { +// Trace = "trace", +// Debug = "debug", +// Info = "info", +// Warn = "warn", +// Error = "error", +// } +// ); -url_enum!( - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum FilterProperty { - FederationLocal = "fed_local", - FederationRemote = "fed_remote", - Watched = "watched", - Unwatched = "unwatched", - WatchProgress = "watch_progress", - KindMovie = "kind_movie", - KindVideo = "kind_video", - KindShortFormVideo = "kind_short_form_video", - KindMusic = "kind_music", - KindCollection = "kind_collection", - KindChannel = "kind_channel", - KindShow = "kind_show", - KindSeries = "kind_series", - KindSeason = "kind_season", - KindEpisode = "kind_episode", - } -); -url_enum!( - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum SortProperty { - ReleaseDate = "release_date", - Title = "title", - Index = "index", - Duration = "duration", - RatingRottenTomatoes = "rating_rt", - RatingMetacritic = "rating_mc", - RatingImdb = "rating_imdb", - RatingTmdb = "rating_tmdb", - RatingYoutubeViews = "rating_yt_views", - RatingYoutubeLikes = "rating_yt_likes", - RatingYoutubeFollowers = "rating_yt_followers", - RatingUser = "rating_user", - RatingLikesDivViews = "rating_loved", - } -); -url_enum!( - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum SortOrder { - Ascending = "ascending", - Descending = "descending", - } -); +// #[derive(Default, Serialize, Deserialize)] +// pub struct StatsBin { +// pub runtime: f64, +// pub size: u64, +// pub count: usize, +// pub max_runtime: f64, +// pub max_runtime_node: String, +// pub max_size: u64, +// pub max_size_node: String, +// } diff --git a/common/src/lib.rs b/common/src/lib.rs index 38b0d05..af07e1c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -3,19 +3,25 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -#![feature(array_try_map)] +pub mod api; +pub mod node; pub mod routes; pub mod user; -use jellyobject::{Object, Registry, Tag, TypedTag, enums, fields}; -pub use jellystream_types as stream; -use std::sync::LazyLock; pub use jellyobject; +pub use jellystream_types as stream; + +pub use api::*; +pub use node::*; +pub use user::*; + +use jellyobject::{Registry, TypedTag}; +use std::sync::LazyLock; pub static TAGREG: LazyLock<Registry> = LazyLock::new(|| { let mut reg = Registry::default(); - register_fields(&mut reg); - register_enums(&mut reg); + node::register_fields(&mut reg); + node::register_enums(&mut reg); user::register_fields(&mut reg); reg }); @@ -25,137 +31,4 @@ fn check_tag_conflicts() { let _ = &*TAGREG; } -fields! { - // Tag counter: 40 - - NO_KIND: Tag = 1 "kind"; - NO_TITLE: &str = 2 "title"; - NO_PARENT: u64 = 3 "parent"; // multi - NO_SUBTITLE: &str = 38 "subtitle"; - NO_TAGLINE: &str = 4 "tagline"; - NO_DESCRIPTION: &str = 5 "description"; - NO_RELEASEDATE: i64 = 6 "releasedate"; - NO_DURATION: f64 = 39 "duration"; - NO_INDEX: u64 = 7 "index"; - NO_SEASON_INDEX: u64 = 8 "season_index"; - NO_TRACK: Object = 9 "track"; // multi - NO_CHAPTER: Object = 32 "chapter"; // multi - NO_TAG: &str = 10 "tag"; // multi - NO_RATINGS: Object = 11 "ratings"; - NO_PICTURES: Object = 12 "pictures"; - NO_IDENTIFIERS: Object = 13 "identifiers"; - NO_VISIBILITY: Tag = 14 "visibility"; - NO_STORAGE_SIZE: u64 = 15 "storage_size"; - NO_CREDIT: Object = 33 "credit"; // multi - NO_SLUG: &str = 37 "slug"; - - CR_NODE: u64 = 34 "node"; - CR_KIND: Tag = 35 "kind"; - CR_ROLE: &str = 36 "role"; // multi - - TR_KIND: Tag = 16 "kind"; - TR_SOURCE: Object = 17 "source"; - TR_NAME: &str = 18 "name"; - TR_CODEC: &str = 19 "codec"; - TR_LANGUAGE: Tag = 20 "language"; - TR_RATE: f64 = 23 "rate"; - TR_BIT_DEPTH: u32 = 25 "bit_depth"; - TR_CHANNELS: u32 = 28 "channels"; - TR_PIXEL_WIDTH: u32 = 26 "pixel_width"; - TR_PIXEL_HEIGHT: u32 = 27 "pixel_height"; - - TRSOURCE_LOCAL_PATH: &str = 21 "local_path"; - TRSOURCE_LOCAL_TRACKNUM: u64 = 22 "local_tracknum"; - - CH_START: f64 = 29 "start"; - CH_END: f64 = 30 "end"; - CH_NAME: &str = 31 "name"; - - LANG_UND: &str = 0xa000 "und"; - LANG_NATIVE: &str = 0xa001 "native"; - LANG_ENG: &str = 0xa002 "eng"; - LANG_DEU: &str = 0xa003 "deu"; - LANG_JPN: &str = 0xa004 "jpn"; - - PICT_COVER: &str = 0xd001 "cover"; - PICT_BACKDROP: &str = 0xd002 "backdrop"; - - RTYP_IMDB: f64 = 0xf001 "imdb"; - RTYP_TMDB: f64 = 0xf002 "tmdb"; - RTYP_ROTTEN_TOMATOES: f64 = 0xf003 "rotten_tomatoes"; - RTYP_METACRITIC: f64 = 0xf004 "metacritic"; - RTYP_YOUTUBE_VIEWS: f64 = 0xf005 "youtube_views"; - RTYP_YOUTUBE_LIKES: f64 = 0xf006 "youtube_likes"; - RTYP_YOUTUBE_FOLLOWERS: f64 = 0xf007 "youtube_followers"; - RTYP_TRAKT: f64 = 0xf008 "trakt"; - - IDENT_MUSICBRAINZ_RECORDING: &str = 0xc001 "musicbrainz_recording"; - IDENT_MUSICBRAINZ_ARTIST: &str = 0xc002 "musicbrainz_artist"; - IDENT_MUSICBRAINZ_RELEASE: &str = 0xc003 "musicbrainz_release"; - IDENT_MUSICBRAINZ_RELEASE_GROUP: &str = 0xc004 "musicbrainz_release_group"; - IDENT_ACOUST_ID_TRACK: &str = 0xc005 "acoust_id_track"; - IDENT_YOUTUBE_VIDEO: &str = 0xc006 "youtube_video"; - IDENT_YOUTUBE_CHANNEL: &str = 0xc007 "youtube_channel"; - IDENT_YOUTUBE_CHANNEL_HANDLE: &str = 0xc008 "youtube_channel_handle"; - IDENT_BANDCAMP: &str = 0xc009 "bandcamp"; - IDENT_ISRC: &str = 0xc00a "isrc"; - IDENT_BARCODE: &str = 0xc00b "barcode"; - IDENT_TRAKT_MOVIE: &str = 0xc00c "trakt_movie"; - IDENT_TRAKT_SHOW: &str = 0xc00d "trakt_show"; - IDENT_TRAKT_SEASON: &str = 0xc00e "trakt_season"; - IDENT_TRAKT_EPISODE: &str = 0xc00f "trakt_episode"; - IDENT_IMDB: &str = 0xc010 "imdb"; - IDENT_TMDB_SERIES: &str = 0xc011 "tmdb_series"; - IDENT_TMDB_MOVIE: &str = 0xc012 "tmdb_movie"; - IDENT_TVDB: &str = 0xc013 "tvdb"; - IDENT_OMDB: &str = 0xc014 "omdb"; - IDENT_VGMDB_ARTIST: &str = 0xc015 "vgmdb_artist"; - -} - -pub type Kind = Tag; -pub type Language = Tag; - -enums! { - VISI_HIDDEN = 0xe001 "hidden"; - VISI_REDUCED = 0xe002 "reduced"; - VISI_VISIBLE = 0xe003 "visible"; - - TRKIND_VIDEO = 0x3001 "video"; - TRKIND_AUDIO = 0x3002 "audio"; - TRKIND_TEXT = 0x3003 "text"; - TRKIND_UNKNOWN = 0x3004 "unknown"; - - KIND_MOVIE = 0xb001 "movie"; - KIND_VIDEO = 0xb002 "video"; - KIND_MUSIC = 0xb003 "music"; - KIND_SHORTFORMVIDEO = 0xb004 "shortformvideo"; - KIND_COLLECTION = 0xb005 "collection"; - KIND_CHANNEL = 0xb006 "channel"; - KIND_SHOW = 0xb007 "show"; - KIND_SERIES = 0xb008 "series"; - KIND_SEASON = 0xb009 "season"; - KIND_EPISODE = 0xb00a "episode"; - - CRCAT_CAST = 0x2001 "cast"; - CRCAT_WRITING = 0x2002 "writing"; - CRCAT_DIRECTING = 0x2003 "directing"; - CRCAT_ART = 0x2004 "art"; - CRCAT_SOUND = 0x2005 "sound"; - CRCAT_CAMERA = 0x2006 "camera"; - CRCAT_LIGHTING = 0x2007 "lighting"; - CRCAT_CREW = 0x2008 "crew"; - CRCAT_EDITING = 0x2009 "editing"; - CRCAT_PRODUCTION = 0x200a "production"; - CRCAT_VFX = 0x200b "vfx"; - CRCAT_COSTUME_MAKEUP = 0x200c "costume_makeup"; - CRCAT_CREATED_BY = 0x200d "created_by"; - CRCAT_PERFORMANCE = 0x200e "performance"; - CRCAT_INSTRUMENT = 0x200f "instrument"; - CRCAT_VOCAL = 0x2010 "vocal"; - CRCAT_ARRANGER = 0x2011 "arranger"; - CRCAT_PRODUCER = 0x2012 "producer"; - CRCAT_ENGINEER = 0x2013 "engineer"; -} - pub struct Identifier(pub TypedTag<&'static str>, pub String); diff --git a/common/src/node.rs b/common/src/node.rs new file mode 100644 index 0000000..3163dc5 --- /dev/null +++ b/common/src/node.rs @@ -0,0 +1,140 @@ +/* + 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 jellyobject::{Object, Tag, enums, fields}; + +fields! { + // Tag counter: 111 + + NO_KIND: Tag = 1 "kind"; + NO_TITLE: &str = 2 "title"; + NO_PARENT: u64 = 3 "parent"; // multi + NO_SUBTITLE: &str = 38 "subtitle"; + NO_TAGLINE: &str = 4 "tagline"; + NO_DESCRIPTION: &str = 5 "description"; + NO_RELEASEDATE: i64 = 6 "releasedate"; + NO_DURATION: f64 = 39 "duration"; + NO_INDEX: u64 = 7 "index"; + NO_SEASON_INDEX: u64 = 8 "season_index"; + NO_TRACK: Object = 9 "track"; // multi + NO_CHAPTER: Object = 32 "chapter"; // multi + NO_TAG: &str = 10 "tag"; // multi + NO_RATINGS: Object = 11 "ratings"; + NO_PICTURES: Object = 12 "pictures"; + NO_IDENTIFIERS: Object = 13 "identifiers"; + NO_VISIBILITY: Tag = 14 "visibility"; + NO_STORAGE_SIZE: u64 = 15 "storage_size"; + NO_CREDIT: Object = 33 "credit"; // multi + NO_SLUG: &str = 37 "slug"; + + CR_NODE: u64 = 34 "node"; + CR_KIND: Tag = 35 "kind"; + CR_ROLE: &str = 36 "role"; // multi + + TR_KIND: Tag = 16 "kind"; + TR_SOURCE: Object = 17 "source"; + TR_NAME: &str = 18 "name"; + TR_CODEC: &str = 19 "codec"; + TR_LANGUAGE: Tag = 20 "language"; + TR_RATE: f64 = 23 "rate"; + TR_BIT_DEPTH: u32 = 25 "bit_depth"; + TR_CHANNELS: u32 = 28 "channels"; + TR_PIXEL_WIDTH: u32 = 26 "pixel_width"; + TR_PIXEL_HEIGHT: u32 = 27 "pixel_height"; + + TRSOURCE_LOCAL_PATH: &str = 21 "local_path"; + TRSOURCE_LOCAL_TRACKNUM: u64 = 22 "local_tracknum"; + + CH_START: f64 = 29 "start"; + CH_END: f64 = 30 "end"; + CH_NAME: &str = 31 "name"; + + LANG_UND: &str = 40 "und"; + LANG_NATIVE: &str = 41 "native"; + LANG_ENG: &str = 42 "eng"; + LANG_DEU: &str = 43 "deu"; + LANG_JPN: &str = 44 "jpn"; + + PICT_COVER: &str = 45 "cover"; + PICT_BACKDROP: &str = 46 "backdrop"; + + RTYP_IMDB: f64 = 47 "imdb"; + RTYP_TMDB: f64 = 48 "tmdb"; + RTYP_ROTTEN_TOMATOES: f64 = 49 "rotten_tomatoes"; + RTYP_METACRITIC: f64 = 50 "metacritic"; + RTYP_YOUTUBE_VIEWS: f64 = 51 "youtube_views"; + RTYP_YOUTUBE_LIKES: f64 = 52 "youtube_likes"; + RTYP_YOUTUBE_FOLLOWERS: f64 = 53 "youtube_followers"; + RTYP_TRAKT: f64 = 54 "trakt"; + + IDENT_MUSICBRAINZ_RECORDING: &str = 55 "musicbrainz_recording"; + IDENT_MUSICBRAINZ_ARTIST: &str = 56 "musicbrainz_artist"; + IDENT_MUSICBRAINZ_RELEASE: &str = 57 "musicbrainz_release"; + IDENT_MUSICBRAINZ_RELEASE_GROUP: &str = 58 "musicbrainz_release_group"; + IDENT_ACOUST_ID_TRACK: &str = 59 "acoust_id_track"; + IDENT_YOUTUBE_VIDEO: &str = 60 "youtube_video"; + IDENT_YOUTUBE_CHANNEL: &str = 61 "youtube_channel"; + IDENT_YOUTUBE_CHANNEL_HANDLE: &str = 62 "youtube_channel_handle"; + IDENT_BANDCAMP: &str = 63 "bandcamp"; + IDENT_ISRC: &str = 64 "isrc"; + IDENT_BARCODE: &str = 65 "barcode"; + IDENT_TRAKT_MOVIE: &str = 66 "trakt_movie"; + IDENT_TRAKT_SHOW: &str = 67 "trakt_show"; + IDENT_TRAKT_SEASON: &str = 68 "trakt_season"; + IDENT_TRAKT_EPISODE: &str = 69 "trakt_episode"; + IDENT_IMDB: &str = 70 "imdb"; + IDENT_TMDB_SERIES: &str = 71 "tmdb_series"; + IDENT_TMDB_MOVIE: &str = 72 "tmdb_movie"; + IDENT_TVDB: &str = 73 "tvdb"; + IDENT_OMDB: &str = 74 "omdb"; + IDENT_VGMDB_ARTIST: &str = 75 "vgmdb_artist"; + +} + +pub type Kind = Tag; +pub type Language = Tag; + +enums! { + VISI_HIDDEN = 76 "hidden"; + VISI_REDUCED = 77 "reduced"; + VISI_VISIBLE = 78 "visible"; + + TRKIND_VIDEO = 79 "video"; + TRKIND_AUDIO = 80 "audio"; + TRKIND_TEXT = 81 "text"; + TRKIND_UNKNOWN = 82 "unknown"; + + KIND_MOVIE = 83 "movie"; + KIND_VIDEO = 84 "video"; + KIND_MUSIC = 85 "music"; + KIND_SHORTFORMVIDEO = 86 "shortformvideo"; + KIND_COLLECTION = 87 "collection"; + KIND_CHANNEL = 88 "channel"; + KIND_SHOW = 89 "show"; + KIND_SERIES = 90 "series"; + KIND_SEASON = 91 "season"; + KIND_EPISODE = 92 "episode"; + + CRCAT_CAST = 93 "cast"; + CRCAT_WRITING = 94 "writing"; + CRCAT_DIRECTING = 95 "directing"; + CRCAT_ART = 96 "art"; + CRCAT_SOUND = 97 "sound"; + CRCAT_CAMERA = 98 "camera"; + CRCAT_LIGHTING = 99 "lighting"; + CRCAT_CREW = 100 "crew"; + CRCAT_EDITING = 101 "editing"; + CRCAT_PRODUCTION = 102 "production"; + CRCAT_VFX = 0x200b "vfx"; + CRCAT_COSTUME_MAKEUP = 103 "costume_makeup"; + CRCAT_CREATED_BY = 104 "created_by"; + CRCAT_PERFORMANCE = 105 "performance"; + CRCAT_INSTRUMENT = 106 "instrument"; + CRCAT_VOCAL = 107 "vocal"; + CRCAT_ARRANGER = 108 "arranger"; + CRCAT_PRODUCER = 109 "producer"; + CRCAT_ENGINEER = 110 "engineer"; +} diff --git a/common/src/user.rs b/common/src/user.rs index f4aaa0b..939438d 100644 --- a/common/src/user.rs +++ b/common/src/user.rs @@ -7,8 +7,11 @@ use jellyobject::fields; fields! { - USER_LOGIN: &str = 0x1001 "login"; - USER_PASSWORD: &str = 0x1002 "password"; - USER_NAME: &str = 0x1003 "name"; - USER_ADMIN: () = 0x1004 "admin"; + USER_LOGIN: &str = 1001 "login"; + USER_PASSWORD: &str = 1002 "password"; + USER_NAME: &str = 1003 "name"; + USER_ADMIN: () = 1004 "admin"; + + UDATA_WATCHED: () = 1005 "watched"; + UDATA_RATING: f64 = 1006 "rating"; } diff --git a/ui/src/filter_sort.rs b/ui/src/filter_sort.rs index 80395e5..55cb113 100644 --- a/ui/src/filter_sort.rs +++ b/ui/src/filter_sort.rs @@ -3,128 +3,109 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use markup::RenderAttributeValue; -const SORT_CATS: &[(&str, &[(SortProperty, &str)])] = { - &[ - ( - "filter_sort.sort.general", - &[(Title, "node.title"), (ReleaseDate, "node.release_date")], - ), - ("filter_sort.sort.media", &[(Duration, "media.duration")]), - ( - "filter_sort.sort.rating", - &[ - (RatingImdb, "rating.imdb"), - (RatingTmdb, "rating.tmdb"), - (RatingMetacritic, "rating.metacritic"), - (RatingRottenTomatoes, "rating.rotten_tomatoes"), - (RatingYoutubeFollowers, "rating.youtube_followers"), - (RatingYoutubeLikes, "rating.youtube_likes"), - (RatingYoutubeViews, "rating.youtube_views"), - (RatingUser, "filter_sort.sort.rating.user"), - ( - RatingLikesDivViews, - "filter_sort.sort.rating.likes_div_views", - ), - ], - ), - ] -}; -const FILTER_CATS: &[(&str, &[(FilterProperty, &str)])] = { - &[ - ( - "filter_sort.filter.kind", - &[ - (KindMovie, "kind.movie"), - (KindVideo, "kind.video"), - (KindShortFormVideo, "kind.short_form_video"), - (KindMusic, "kind.music"), - (KindCollection, "kind.collection"), - (KindChannel, "kind.channel"), - (KindShow, "kind.show"), - (KindSeries, "kind.series"), - (KindSeason, "kind.season"), - (KindEpisode, "kind.episode"), - ], - ), - ( - "filter_sort.filter.federation", - &[ - (FederationLocal, "federation.local"), - (FederationRemote, "federation.remote"), - ], - ), - ( - "filter_sort.filter.watched", - &[ - (Watched, "watched.watched"), - (Unwatched, "watched.none"), - (WatchProgress, "watched.progress"), - ], - ), - ] -}; +use crate::scaffold::RenderInfo; + +// const SORT_CATS: &[(&str, &[(SortProperty, &str)])] = { +// &[ +// ( +// "filter_sort.sort.general", +// &[(Title, "node.title"), (ReleaseDate, "node.release_date")], +// ), +// ("filter_sort.sort.media", &[(Duration, "media.duration")]), +// ( +// "filter_sort.sort.rating", +// &[ +// (RatingImdb, "rating.imdb"), +// (RatingTmdb, "rating.tmdb"), +// (RatingMetacritic, "rating.metacritic"), +// (RatingRottenTomatoes, "rating.rotten_tomatoes"), +// (RatingYoutubeFollowers, "rating.youtube_followers"), +// (RatingYoutubeLikes, "rating.youtube_likes"), +// (RatingYoutubeViews, "rating.youtube_views"), +// (RatingUser, "filter_sort.sort.rating.user"), +// ( +// RatingLikesDivViews, +// "filter_sort.sort.rating.likes_div_views", +// ), +// ], +// ), +// ] +// }; +// const FILTER_CATS: &[(&str, &[(FilterProperty, &str)])] = { +// &[ +// ( +// "filter_sort.filter.kind", +// &[ +// (KindMovie, "kind.movie"), +// (KindVideo, "kind.video"), +// (KindShortFormVideo, "kind.short_form_video"), +// (KindMusic, "kind.music"), +// (KindCollection, "kind.collection"), +// (KindChannel, "kind.channel"), +// (KindShow, "kind.show"), +// (KindSeries, "kind.series"), +// (KindSeason, "kind.season"), +// (KindEpisode, "kind.episode"), +// ], +// ), +// ( +// "filter_sort.filter.federation", +// &[ +// (FederationLocal, "federation.local"), +// (FederationRemote, "federation.remote"), +// ], +// ), +// ( +// "filter_sort.filter.watched", +// &[ +// (Watched, "watched.watched"), +// (Unwatched, "watched.none"), +// (WatchProgress, "watched.progress"), +// ], +// ), +// ] +// }; markup::define! { - NodeFilterSortForm<'a>(f: &'a NodeFilterSort, lang: &'a Language) { + NodeFilterSortForm<'a>(ri: &'a RenderInfo<'a>, f: &'a NodeFilterSort) { details.filtersort[open=f.filter_kind.is_some() || f.sort_by.is_some()] { summary { "Filter and Sort" } form[method="GET", action=""] { fieldset.filter { legend { "Filter" } - .categories { - @for (cname, cat) in FILTER_CATS { - .category { - h3 { @trs(lang, cname) } - @for (value, label) in *cat { - label { input[type="checkbox", name="filter_kind", value=A(*value), checked=f.filter_kind.as_ref().map(|k|k.contains(value)).unwrap_or(true)]; @trs(lang, label) } br; - } - } - } - } + // .categories { + // @for (cname, cat) in FILTER_CATS { + // .category { + // h3 { @trs(lang, cname) } + // @for (value, label) in *cat { + // label { input[type="checkbox", name="filter_kind", value=A(*value), checked=f.filter_kind.as_ref().map(|k|k.contains(value)).unwrap_or(true)]; @trs(lang, label) } br; + // } + // } + // } + // } } fieldset.sortby { legend { "Sort" } - .categories { - @for (cname, cat) in SORT_CATS { - .category { - h3 { @trs(lang, cname) } - @for (value, label) in *cat { - label { input[type="radio", name="sort_by", value=A(*value), checked=Some(value)==f.sort_by.as_ref()]; @trs(lang, label) } br; - } - } - } - } + // .categories { + // @for (cname, cat) in SORT_CATS { + // .category { + // h3 { @trs(lang, cname) } + // @for (value, label) in *cat { + // label { input[type="radio", name="sort_by", value=A(*value), checked=Some(value)==f.sort_by.as_ref()]; @trs(lang, label) } br; + // } + // } + // } + // } } fieldset.sortorder { legend { "Sort Order" } - @for (value, label) in [(Ascending, "filter_sort.order.asc"), (Descending, "filter_sort.order.desc")] { - label { input[type="radio", name="sort_order", value=A(value), checked=Some(value)==f.sort_order]; @trs(lang, label) } br; - } + // @for (value, label) in [(Ascending, "filter_sort.order.asc"), (Descending, "filter_sort.order.desc")] { + // label { input[type="radio", name="sort_order", value=A(value), checked=Some(value)==f.sort_order]; @trs(lang, label) } br; + // } } input[type="submit", value="Apply"]; a[href="?"] { "Clear" } } } } } - -struct A<T>(pub T); -impl markup::Render for A<SortProperty> { - fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_str(self.0.to_str()) - } -} -impl markup::Render for A<SortOrder> { - fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_str(self.0.to_str()) - } -} -impl markup::Render for A<FilterProperty> { - fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - writer.write_str(self.0.to_str()) - } -} -impl RenderAttributeValue for A<SortOrder> {} -impl RenderAttributeValue for A<FilterProperty> {} -impl RenderAttributeValue for A<SortProperty> {} diff --git a/ui/src/format.rs b/ui/src/format.rs index 11040de..811211a 100644 --- a/ui/src/format.rs +++ b/ui/src/format.rs @@ -4,7 +4,10 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use jellycommon::{LANG_ENG, Language}; +use jellycommon::{ + jellyobject::{Object, Tag}, + *, +}; use crate::locale::tr; use std::fmt::Write; @@ -71,50 +74,47 @@ fn test_duration_long() { pub fn format_size(size: u64) -> String { humansize::format_size(size, humansize::DECIMAL) } -pub fn format_kind(k: NodeKind, lang: Language) -> TrString<'static> { - trs( - &lang, - match k { - NodeKind::Unknown => "kind.unknown", - NodeKind::Movie => "kind.movie", - NodeKind::Video => "kind.video", - NodeKind::Music => "kind.music", - NodeKind::ShortFormVideo => "kind.short_form_video", - NodeKind::Collection => "kind.collection", - NodeKind::Channel => "kind.channel", - NodeKind::Show => "kind.show", - NodeKind::Series => "kind.series", - NodeKind::Season => "kind.season", - NodeKind::Episode => "kind.episode", +pub fn format_kind(lang: Language, kind: Tag) -> &'static str { + tr( + lang, + match kind { + KIND_MOVIE => "kind.movie", + KIND_VIDEO => "kind.video", + KIND_MUSIC => "kind.music", + KIND_SHORTFORMVIDEO => "kind.short_form_video", + KIND_COLLECTION => "kind.collection", + KIND_CHANNEL => "kind.channel", + KIND_SHOW => "kind.show", + KIND_SERIES => "kind.series", + KIND_SEASON => "kind.season", + KIND_EPISODE => "kind.episode", + _ => "kind.unknown", }, ) } -pub trait MediaInfoExt { - fn resolution_name(&self) -> &'static str; -} -impl MediaInfoExt for &MediaInfo { - fn resolution_name(&self) -> &'static str { - let mut maxdim = 0; - for t in &self.tracks { - if let SourceTrackKind::Video { width, height, .. } = &t.kind { - maxdim = maxdim.max(*width.max(height)) - } +pub fn node_resolution_name(node: &Object) -> &'static str { + let mut maxdim = 0; + for t in node.iter(NO_TRACK) { + if let Some(width) = t.get(TR_PIXEL_WIDTH) { + maxdim = maxdim.max(width) } - - match maxdim { - 30720.. => "32K", - 15360.. => "16K", - 7680.. => "8K UHD", - 5120.. => "5K UHD", - 3840.. => "4K UHD", - 2560.. => "QHD 1440p", - 1920.. => "FHD 1080p", - 1280.. => "HD 720p", - 854.. => "SD 480p", - _ => "Unkown", + if let Some(height) = t.get(TR_PIXEL_HEIGHT) { + maxdim = maxdim.max(height) } } + match maxdim { + 30720.. => "32K", + 15360.. => "16K", + 7680.. => "8K UHD", + 5120.. => "5K UHD", + 3840.. => "4K UHD", + 2560.. => "QHD 1440p", + 1920.. => "FHD 1080p", + 1280.. => "HD 720p", + 854.. => "SD 480p", + _ => "Unkown", + } } pub fn format_count(n: impl Into<usize>) -> String { @@ -129,13 +129,13 @@ pub fn format_count(n: impl Into<usize>) -> String { } } -pub fn format_chapter(c: &Chapter) -> (String, String) { +pub fn format_chapter(c: &Object) -> (String, String) { ( format!( "{}-{}", - c.time_start.map(format_duration).unwrap_or_default(), - c.time_end.map(format_duration).unwrap_or_default(), + c.get(CH_START).map(format_duration).unwrap_or_default(), + c.get(CH_END).map(format_duration).unwrap_or_default(), ), - c.labels.first().map(|l| l.1.clone()).unwrap_or_default(), + c.get(CH_NAME).unwrap_or_default().to_string(), ) } diff --git a/ui/src/home.rs b/ui/src/home.rs index 5de9e17..a3088c8 100644 --- a/ui/src/home.rs +++ b/ui/src/home.rs @@ -9,8 +9,8 @@ use markup::DynRender; markup::define! { HomePage<'a>(ri: RenderInfo<'a>, r: ApiHomeResponse) { h2 { @tr(ri.lang, "home.bin.root").replace("{title}", &CONF.brand) } - ul.children.hlist {@for (node, udata) in &r.toplevel { - li { @NodeCard { node, udata, lang } } + ul.children.hlist {@for nodeu in &r.toplevel { + li { @NodeCard { ri, nodeu } } }} @for (name, nodes) in &r.categories { // @if !nodes.is_empty() { diff --git a/ui/src/items.rs b/ui/src/items.rs index 447ec01..529a5d6 100644 --- a/ui/src/items.rs +++ b/ui/src/items.rs @@ -29,7 +29,7 @@ markup::define! { impl Page for ItemsPage<'_> { fn title(&self) -> String { - tr(*self.lang, "home").to_string() + tr(self.ri.lang, "home").to_string() } fn to_render(&self) -> DynRender<'_> { markup::new!(@self) diff --git a/ui/src/search.rs b/ui/src/search.rs index 35ae82d..0eb34b9 100644 --- a/ui/src/search.rs +++ b/ui/src/search.rs @@ -9,7 +9,7 @@ use markup::DynRender; impl Page for SearchPage<'_> { fn title(&self) -> String { - tr(*self.lang, "search.title").to_string() + tr(self.ri.lang, "search.title").to_string() } fn class(&self) -> Option<&'static str> { Some("search") @@ -29,8 +29,8 @@ markup::define! { @if let Some(r) = &r { h2 { @tr(ri.lang, "search.results.title") } p.stats { @tr(ri.lang, "search.results.stats").replace("{count}", &r.count.to_string()).replace("{dur}", &format!("{:?}", r.duration)) } - ul.children {@for (node, udata) in r.results.iter() { - li { @NodeCard { node, udata, lang } } + ul.children {@for nodeu in r.results.iter() { + li { @NodeCard { ri, nodeu } } }} // TODO pagination } |