aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/object/src/registry.rs2
-rw-r--r--common/src/api.rs185
-rw-r--r--common/src/lib.rs151
-rw-r--r--common/src/node.rs140
-rw-r--r--common/src/user.rs11
-rw-r--r--ui/src/filter_sort.rs191
-rw-r--r--ui/src/format.rs84
-rw-r--r--ui/src/home.rs4
-rw-r--r--ui/src/items.rs2
-rw-r--r--ui/src/search.rs6
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
}