diff options
Diffstat (limited to 'ui/src/old')
| -rw-r--r-- | ui/src/old/account/mod.rs | 103 | ||||
| -rw-r--r-- | ui/src/old/account/settings.rs | 84 | ||||
| -rw-r--r-- | ui/src/old/admin/import.rs | 46 | ||||
| -rw-r--r-- | ui/src/old/admin/log.rs | 127 | ||||
| -rw-r--r-- | ui/src/old/admin/mod.rs | 64 | ||||
| -rw-r--r-- | ui/src/old/admin/user.rs | 81 | ||||
| -rw-r--r-- | ui/src/old/filter_sort.rs | 111 | ||||
| -rw-r--r-- | ui/src/old/home.rs | 33 | ||||
| -rw-r--r-- | ui/src/old/items.rs | 37 | ||||
| -rw-r--r-- | ui/src/old/node_card.rs | 59 | ||||
| -rw-r--r-- | ui/src/old/search.rs | 38 |
11 files changed, 783 insertions, 0 deletions
diff --git a/ui/src/old/account/mod.rs b/ui/src/old/account/mod.rs new file mode 100644 index 0000000..e7da26f --- /dev/null +++ b/ui/src/old/account/mod.rs @@ -0,0 +1,103 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ +pub mod settings; + +use crate::{Page, locale::tr, scaffold::RenderInfo}; +use jellycommon::routes::{u_account_login, u_account_register}; + +impl Page for AccountLogin<'_> { + fn title(&self) -> String { + tr( + self.ri.lang, + if self.logged_in { + "account.login.switch" + } else { + "account.login" + }, + ) + .to_string() + } + + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} +impl Page for AccountRegister<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "account.register").to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} +impl Page for AccountRegisterSuccess<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "account.register").to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} +impl Page for AccountLogout<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "account.logout").to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define! { + AccountRegister<'a>(ri: &'a RenderInfo<'a>) { + form.account[method="POST", action=""] { + h1 { @tr(ri.lang, "account.register") } + + label[for="inp-invitation"] { @tr(ri.lang, "account.register.invitation") } + input[type="text", id="inp-invitation", name="invitation"]; br; + + label[for="inp-username"] { @tr(ri.lang, "account.username") } + input[type="text", id="inp-username", name="username"]; br; + label[for="inp-password"] { @tr(ri.lang, "account.password") } + input[type="password", id="inp-password", name="password"]; br; + + input[type="submit", value=tr(ri.lang, "account.register.submit")]; + + p { @tr(ri.lang, "account.register.login") " " a[href=u_account_login()] { @tr(ri.lang, "account.register.login_here") } } + } + } + AccountRegisterSuccess<'a>(ri: &'a RenderInfo<'a>, logged_in: bool) { + h1 { @tr(ri.lang, if *logged_in { + "account.register.success.switch" + } else { + "account.register.success" + })} + } + AccountLogin<'a>(ri: &'a RenderInfo<'a>, logged_in: bool) { + form.account[method="POST", action=""] { + h1 { @self.title() } + + label[for="inp-username"] { @tr(ri.lang, "account.username") } + input[type="text", id="inp-username", name="username"]; br; + label[for="inp-password"] { @tr(ri.lang, "account.password") } + input[type="password", id="inp-password", name="password"]; br; + + input[type="submit", value=tr(ri.lang, if *logged_in { "account.login.submit.switch" } else { "account.login.submit" })]; + + @if *logged_in { + p { @tr(ri.lang, "account.login.register.switch") " " a[href=u_account_register()] { @tr(ri.lang, "account.login.register_here") } } + } else { + p { @tr(ri.lang, "account.login.cookie_note") } + p { @tr(ri.lang, "account.login.register") " " a[href=u_account_register()] { @tr(ri.lang, "account.login.register_here") } } + } + } + } + AccountLogout<'a>(ri: &'a RenderInfo<'a>) { + form.account[method="POST", action=""] { + h1 { @tr(ri.lang, "account.logout") } + input[type="submit", value=tr(ri.lang, "account.logout.submit")]; + } + } +} diff --git a/ui/src/old/account/settings.rs b/ui/src/old/account/settings.rs new file mode 100644 index 0000000..83f72b0 --- /dev/null +++ b/ui/src/old/account/settings.rs @@ -0,0 +1,84 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ +use crate::{ + FlashM, Page, + locale::tr, + scaffold::{FlashDisplay, RenderInfo, SessionInfo}, +}; +use jellycommon::routes::{u_account_login, u_account_settings}; +use markup::RenderAttributeValue; + +impl Page for SettingsPage<'_> { + fn title(&self) -> String { + "Settings".to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define! { + SettingsPage<'a>(ri: &'a RenderInfo<'a>, session: &'a SessionInfo, flash: &'a FlashM) { + h1 { "Settings" } + @FlashDisplay {flash} + h2 { @tr(ri.lang, "account") } + a.switch_account[href=u_account_login()] { "Switch Account" } + form[method="POST", action=u_account_settings()] { + label[for="username"] { @tr(ri.lang, "account.username") } + input[type="text", id="username", disabled, value=&session.user.name]; + input[type="submit", disabled, value=tr(ri.lang, "settings.immutable")]; + } + form[method="POST", action=u_account_settings()] { + label[for="display_name"] { @tr(ri.lang, "account.display_name") } + input[type="text", id="display_name", name="display_name", value=&session.user.display_name]; + input[type="submit", value=tr(ri.lang, "settings.update")]; + } + form[method="POST", action=u_account_settings()] { + label[for="password"] { @tr(ri.lang, "account.password") } + input[type="password", id="password", name="password"]; + input[type="submit", value=tr(ri.lang, "settings.update")]; + } + h2 { @tr(ri.lang, "settings.appearance") } + form[method="POST", action=u_account_settings()] { + fieldset { + legend { @tr(ri.lang, "settings.appearance.theme") } + @for theme in Theme::ALL { + label { input[type="radio", name="theme", value=A(*theme), checked=session.user.theme==*theme]; @tr(ri.lang, &format!("theme.{theme}")) } br; + } + } + input[type="submit", value=tr(ri.lang, "settings.apply")]; + } + form[method="POST", action=u_account_settings()] { + fieldset { + legend { @tr(ri.lang, "settings.player_preference") } + @for kind in PlayerKind::ALL { + label { input[type="radio", name="player_preference", value=A(*kind), checked=session.user.player_preference==*kind]; @tr(ri.lang, &format!("player_kind.{kind}")) } br; + } + } + input[type="submit", value=tr(ri.lang, "settings.apply")]; + } + form[method="POST", action=u_account_settings()] { + label[for="native_secret"] { "Native Secret" } + input[type="password", id="native_secret", name="native_secret"]; + input[type="submit", value=tr(ri.lang, "settings.update")]; + p { "The secret can be found in " code{"$XDG_CONFIG_HOME/jellynative_secret"} " or by clicking " a.button[href="jellynative://show-secret-v1"] { "Show Secret" } "." } + } + } +} + +struct A<T>(pub T); +impl markup::Render for A<Theme> { + fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { + writer.write_str(self.0.to_str()) + } +} +impl markup::Render for A<PlayerKind> { + fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { + writer.write_str(self.0.to_str()) + } +} +impl RenderAttributeValue for A<Theme> {} +impl RenderAttributeValue for A<PlayerKind> {} diff --git a/ui/src/old/admin/import.rs b/ui/src/old/admin/import.rs new file mode 100644 index 0000000..805d787 --- /dev/null +++ b/ui/src/old/admin/import.rs @@ -0,0 +1,46 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{FlashM, Page, locale::tr, scaffold::{FlashDisplay, RenderInfo}}; +use jellycommon::routes::u_admin_import_post; + +impl Page for AdminImportPage<'_> { + fn title(&self) -> String { + "Import".to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define!( + AdminImportPage<'a>(ri: &'a RenderInfo<'a>, busy: bool, last_import_err: &'a [String], flash: &'a FlashM) { + @FlashDisplay { flash } + @if *busy { + h1 { @tr(ri.lang, "admin.import.running") } + noscript { "Live import progress needs javascript." } + div[id="admin_import"] {} + } else { + h1 { @tr(ri.lang, "admin.import.title") } + @if !last_import_err.is_empty() { + section.message.error { + details { + summary { p.error { @tr(ri.lang, "admin.import_errors").replace("{n}", &last_import_err.len().to_string()) } } + ol { @for e in *last_import_err { + li.error { pre.error { @e } } + }} + } + } + } + form[method="POST", action=u_admin_import_post(true)] { + input[type="submit", value=tr(ri.lang, "admin.dashboard.import.inc").to_string()]; + } + form[method="POST", action=u_admin_import_post(false)] { + input[type="submit", value=tr(ri.lang, "admin.dashboard.import.full").to_string()]; + } + } + } +); diff --git a/ui/src/old/admin/log.rs b/ui/src/old/admin/log.rs new file mode 100644 index 0000000..637158f --- /dev/null +++ b/ui/src/old/admin/log.rs @@ -0,0 +1,127 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::Page; +use jellycommon::routes::u_admin_log; +use markup::raw; +use std::fmt::Write; + +impl Page for ServerLogPage<'_> { + fn title(&self) -> String { + "Server Log".to_string() + } + fn class(&self) -> Option<&'static str> { + Some("admin_log") + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define! { + ServerLogPage<'a>(warnonly: bool, messages: &'a [String]) { + h1 { "Server Log" } + a[href=u_admin_log(!warnonly)] { @if *warnonly { "Show everything" } else { "Show only warnings" }} + code.log[id="log"] { + table { @for e in *messages { + @raw(e) + }} + } + } + ServerLogLine<'a>(e: &'a LogLine) { + tr[class=format!("level-{}", e.level).to_ascii_lowercase()] { + td.time { @e.time.to_rfc3339() } + td.loglevel { @format_level(e.level) } + td.module { @e.module } + td { @markup::raw(vt100_to_html(&e.message)) } + } + } +} + +pub fn render_log_line(line: &LogLine) -> String { + ServerLogLine { e: line }.to_string() +} + +fn vt100_to_html(s: &str) -> String { + let mut out = HtmlOut::default(); + let mut st = vte::Parser::new(); + st.advance(&mut out, s.as_bytes()); + out.s +} + +fn format_level(level: LogLevel) -> impl markup::Render { + let (s, c) = match level { + LogLevel::Debug => ("DEBUG", "blue"), + LogLevel::Error => ("ERROR", "red"), + LogLevel::Warn => ("WARN", "yellow"), + LogLevel::Info => ("INFO", "green"), + LogLevel::Trace => ("TRACE", "lightblue"), + }; + markup::new! { span[style=format!("color:{c}")] {@s} } +} + +#[derive(Default)] +pub struct HtmlOut { + s: String, + color: bool, +} +impl HtmlOut { + pub fn set_color(&mut self, [r, g, b]: [u8; 3]) { + self.reset_color(); + self.color = true; + write!(self.s, "<span style=color:#{:02x}{:02x}{:02x}>", r, g, b).unwrap() + } + pub fn reset_color(&mut self) { + if self.color { + write!(self.s, "</span>").unwrap(); + self.color = false; + } + } +} +impl vte::Perform for HtmlOut { + fn print(&mut self, c: char) { + match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' => self.s.push(c), + x => write!(self.s, "&#{};", x as u32).unwrap(), + } + } + fn execute(&mut self, _byte: u8) {} + fn hook(&mut self, _params: &vte::Params, _i: &[u8], _ignore: bool, _a: char) {} + fn put(&mut self, _byte: u8) {} + fn unhook(&mut self) {} + fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {} + fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} + fn csi_dispatch( + &mut self, + params: &vte::Params, + _intermediates: &[u8], + _ignore: bool, + action: char, + ) { + let mut k = params.iter(); + #[allow(clippy::single_match)] + match action { + 'm' => match k.next().unwrap_or(&[0]).first().unwrap_or(&0) { + c @ (30..=37 | 40..=47) => { + let c = if *c >= 40 { *c - 10 } else { *c }; + self.set_color(match c { + 30 => [0, 0, 0], + 31 => [255, 0, 0], + 32 => [0, 255, 0], + 33 => [255, 255, 0], + 34 => [0, 0, 255], + 35 => [255, 0, 255], + 36 => [0, 255, 255], + 37 => [255, 255, 255], + _ => unreachable!(), + }); + } + _ => (), + }, + _ => (), + } + } +} diff --git a/ui/src/old/admin/mod.rs b/ui/src/old/admin/mod.rs new file mode 100644 index 0000000..f42ba76 --- /dev/null +++ b/ui/src/old/admin/mod.rs @@ -0,0 +1,64 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +pub mod import; +pub mod log; +pub mod user; + +use crate::{FlashM, Page, locale::tr, scaffold::{FlashDisplay, RenderInfo}}; +use jellycommon::routes::{ + u_admin_import, u_admin_invite_create, u_admin_invite_remove, u_admin_log, + u_admin_update_search, u_admin_users, +}; + +impl Page for AdminDashboardPage<'_> { + fn title(&self) -> String { + "Admin Dashboard".to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define!( + AdminDashboardPage<'a>(ri: &'a RenderInfo<'a>, busy: Option<&'static str>, flash: &'a FlashM, invites: &'a [String]) { + h1 { @tr(ri.lang, "admin.dashboard.title") } + @FlashDisplay { flash } + ul { + li{a[href=u_admin_log(true)] { @tr(ri.lang, "admin.log.warnonly") }} + li{a[href=u_admin_log(false)] { @tr(ri.lang, "admin.log.full") }} + } + + a[href=u_admin_import()] { h2 { @tr(ri.lang, "admin.import.title") }} + @if let Some(text) = busy { + section.message { p.warn { @text } } + } + form[method="POST", action=u_admin_update_search()] { + input[type="submit", value=tr(ri.lang, "admin.dashboard.update_search").to_string()]; + } + h2 { @tr(ri.lang, "admin.dashboard.users") } + p { a[href=u_admin_users()] { @tr(ri.lang, "admin.dashboard.manage_users") } } + h2 { @tr(ri.lang, "admin.dashboard.invites") } + form[method="POST", action=u_admin_invite_create()] { + input[type="submit", value=tr(ri.lang, "admin.dashboard.create_invite").to_string()]; + } + ul { @for t in *invites { + li { + form[method="POST", action=u_admin_invite_remove()] { + span { @t } + input[type="text", name="invite", value=&t, hidden]; + input[type="submit", value=tr(ri.lang, "admin.dashboard.create_invite").to_string()]; + } + } + }} + + // h2 { "Database" } + // @match db_stats(&database) { + // Ok(s) => { @s } + // Err(e) => { pre.error { @format!("{e:?}") } } + // } + } +); diff --git a/ui/src/old/admin/user.rs b/ui/src/old/admin/user.rs new file mode 100644 index 0000000..e4a8975 --- /dev/null +++ b/ui/src/old/admin/user.rs @@ -0,0 +1,81 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{FlashM, Page, scaffold::FlashDisplay}; +use jellycommon::routes::{ + u_admin_user, u_admin_user_permission, u_admin_user_remove, u_admin_users, +}; + +impl Page for AdminUserPage<'_> { + fn title(&self) -> String { + "User Management".to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} +impl Page for AdminUsersPage<'_> { + fn title(&self) -> String { + "User Management".to_string() + } + fn to_render(&self) -> markup::DynRender<'_> { + markup::new!(@self) + } +} + +markup::define! { + AdminUsersPage<'a>(lang: &'a Language, users: &'a [User], flash: &'a FlashM) { + h1 { @trs(lang, "admin.users.title") } + @FlashDisplay { flash } + h2 { @trs(lang, "admin.users.user_list") } + ul { @for u in *users { + li { + a[href=u_admin_user(&u.name)] { @format!("{:?}", u.display_name) " (" @u.name ")" } + } + }} + } + AdminUserPage<'a>(lang: &'a Language, user: &'a User, flash: &'a FlashM) { + h1 { @format!("{:?}", user.display_name) " (" @user.name ")" } + a[href=u_admin_users()] { @trs(lang, "admin.users.return_to_list") } + @FlashDisplay { flash } + form[method="POST", action=u_admin_user_remove(&user.name)] { + // input[type="text", name="name", value=&user.name, hidden]; + input.danger[type="submit", value="Remove user(!)"]; + } + + h2 { "Permissions" } + @PermissionDisplay { perms: &user.permissions } + + form[method="POST", action=u_admin_user_permission(&user.name)] { + // input[type="text", name="name", value=&user.name, hidden]; + fieldset.perms { + legend { "Permission" } + @for p in UserPermission::ALL_ENUMERABLE { + label { + input[type="radio", name="permission", value=serde_json::to_string(p).unwrap()]; + @format!("{p}") + } br; + } + } + fieldset.perms { + legend { "State" } + label { input[type="radio", name="action", value="unset"]; "Unset" } br; + label { input[type="radio", name="action", value="grant"]; "Grant" } br; + label { input[type="radio", name="action", value="revoke"]; "Revoke" } br; + } + input[type="submit", value="Update"]; + } + } + PermissionDisplay<'a>(perms: &'a PermissionSet) { + ul { @for (perm,grant) in &perms.0 { + @if *grant { + li[class="perm-grant"] { @format!("Allow {}", perm) } + } else { + li[class="perm-revoke"] { @format!("Deny {}", perm) } + } + }} + } +} diff --git a/ui/src/old/filter_sort.rs b/ui/src/old/filter_sort.rs new file mode 100644 index 0000000..55cb113 --- /dev/null +++ b/ui/src/old/filter_sort.rs @@ -0,0 +1,111 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +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>(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; + // } + // } + // } + // } + } + 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; + // } + // } + // } + // } + } + 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; + // } + } + input[type="submit", value="Apply"]; a[href="?"] { "Clear" } + } + } + } +} diff --git a/ui/src/old/home.rs b/ui/src/old/home.rs new file mode 100644 index 0000000..a3088c8 --- /dev/null +++ b/ui/src/old/home.rs @@ -0,0 +1,33 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ +use crate::{CONF, Page, locale::tr, node_card::NodeCard, scaffold::RenderInfo}; +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 nodeu in &r.toplevel { + li { @NodeCard { ri, nodeu } } + }} + @for (name, nodes) in &r.categories { + // @if !nodes.is_empty() { + // h2 { @trs(lang, name) } + // ul.children.hlist {@for (node, udata) in nodes { + // li { @NodeCard { node, udata, lang } } + // }} + // } + } + } +} + +impl Page for HomePage<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "home").to_string() + } + fn to_render(&self) -> DynRender<'_> { + markup::new!(@self) + } +} diff --git a/ui/src/old/items.rs b/ui/src/old/items.rs new file mode 100644 index 0000000..529a5d6 --- /dev/null +++ b/ui/src/old/items.rs @@ -0,0 +1,37 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ +use crate::{Page, locale::tr, scaffold::RenderInfo}; +use markup::DynRender; + +markup::define! { + ItemsPage<'a>(ri: &'a RenderInfo<'a>, r: ApiItemsResponse, filter: &'a NodeFilterSort, page: usize) { + .page.dir { + h1 { "All Items" } + // @NodeFilterSortForm { f: filter, lang } + // ul.children { @for (node, udata) in &r.items { + // li {@NodeCard { node, udata, lang }} + // }} + // p.pagecontrols { + // span.current { @tr(**lang, "page.curr").replace("{cur}", &(page + 1).to_string()).replace("{max}", &r.pages.to_string()) " " } + // @if *page > 0 { + // a.prev[href=u_items_filter(page - 1, filter)] { @trs(lang, "page.prev") } " " + // } + // @if page + 1 < r.pages { + // a.next[href=u_items_filter(page + 1, filter)] { @trs(lang, "page.next") } + // } + // } + } + } +} + +impl Page for ItemsPage<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "home").to_string() + } + fn to_render(&self) -> DynRender<'_> { + markup::new!(@self) + } +} diff --git a/ui/src/old/node_card.rs b/ui/src/old/node_card.rs new file mode 100644 index 0000000..f87f490 --- /dev/null +++ b/ui/src/old/node_card.rs @@ -0,0 +1,59 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{ + node_page::{NodeUdata, aspect_class}, + scaffold::RenderInfo, +}; +use jellycommon::*; + +markup::define! { + NodeCard<'a>(ri: &'a RenderInfo<'a>, nodeu: NodeUdata<'a>) { + @let cls = format!("node card poster {}", aspect_class(nodeu.node.get(NO_KIND).unwrap_or(KIND_COLLECTION))); + div[class=cls] { + // .poster { + // a[href=u_node_slug(&node.slug)] { + // img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; + // } + // .cardhover.item { + // @if node.media.is_some() { + // a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } + // } + // @Props { node, udata, full: false, lang } + // } + // } + // div.title { + // a[href=u_node_slug(&node.slug)] { + // @node.title + // } + // } + // div.subtitle { + // span { + // @node.subtitle + // } + // } + } + } + NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nodeu: NodeUdata<'a>) { + div[class="node card widecard poster"] { + // div[class=&format!("poster {}", aspect_class(node.kind))] { + // a[href=u_node_slug(&node.slug)] { + // img[src=u_node_image(&node.slug, PictureSlot::Cover, 512), loading="lazy"]; + // } + // .cardhover.item { + // @if node.media.is_some() { + // a.play.icon[href=u_node_slug_player(&node.slug)] { "play_arrow" } + // } + // } + // } + // div.details { + // a.title[href=u_node_slug(&node.slug)] { @node.title } + // @Props { node, udata, full: false, lang } + // span.overview { @node.description } + // } + } + } +} diff --git a/ui/src/old/search.rs b/ui/src/old/search.rs new file mode 100644 index 0000000..0eb34b9 --- /dev/null +++ b/ui/src/old/search.rs @@ -0,0 +1,38 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{Page, locale::tr, node_card::NodeCard, scaffold::RenderInfo}; +use markup::DynRender; + +impl Page for SearchPage<'_> { + fn title(&self) -> String { + tr(self.ri.lang, "search.title").to_string() + } + fn class(&self) -> Option<&'static str> { + Some("search") + } + fn to_render(&self) -> DynRender<'_> { + markup::new!(@self) + } +} + +markup::define! { + SearchPage<'a>(ri: &'a RenderInfo<'a>, r: Option<ApiSearchResponse>, query: &'a Option<String>) { + h1 { @tr(ri.lang, "search.title") } + form[action="", method="GET"] { + input[type="text", name="query", placeholder=tr(ri.lang, "search.placeholder"), value=&query]; + input[type="submit", value="Search"]; + } + @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 nodeu in r.results.iter() { + li { @NodeCard { ri, nodeu } } + }} + // TODO pagination + } + } +} |