aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/api.rs1
-rw-r--r--locale/en.ini2
-rw-r--r--server/src/compat/youtube.rs6
-rw-r--r--server/src/ui/home.rs27
-rw-r--r--ui/client-style/src/node_card.css56
-rw-r--r--ui/client-style/src/node_page.css2
-rw-r--r--ui/src/components/node_card.rs35
-rw-r--r--ui/src/components/node_list.rs38
-rw-r--r--ui/src/components/node_page.rs11
-rw-r--r--ui/src/components/props.rs6
-rw-r--r--ui/src/format.rs90
11 files changed, 168 insertions, 106 deletions
diff --git a/common/src/api.rs b/common/src/api.rs
index 12d9243..beccaa5 100644
--- a/common/src/api.rs
+++ b/common/src/api.rs
@@ -60,6 +60,7 @@ enums! {
NLSTYLE_GRID = b"grid";
NLSTYLE_INLINE = b"inli";
NLSTYLE_LIST = b"list";
+ NLSTYLE_HIGHLIGHT = b"hglt";
}
// use crate::user::{NodeUserData, User};
diff --git a/locale/en.ini b/locale/en.ini
index 1e901ef..b49273c 100644
--- a/locale/en.ini
+++ b/locale/en.ini
@@ -33,7 +33,7 @@ home.bin.latest_video=Latest in Videos
home.bin.latest_music=Latest in Music
home.bin.latest_short_form=Latest in Short form
home.bin.max_rating=Top Rated
-home.bin.daily_random=Today´s Picks
+home.bin.daily_random=Today´s Pick
home.bin.watch_again=Watch again
home.bin.daily_random_music=Discover Music
diff --git a/server/src/compat/youtube.rs b/server/src/compat/youtube.rs
index 9d27235..a5f540b 100644
--- a/server/src/compat/youtube.rs
+++ b/server/src/compat/youtube.rs
@@ -38,7 +38,8 @@ pub fn r_youtube_watch(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> {
}
#[get("/channel/<id>")]
-pub fn r_youtube_channel(ri: RequestInfo<'_>, id: &str) -> MyResult<Redirect> {
+pub fn r_youtube_channel(_ri: RequestInfo<'_>, id: &str) -> MyResult<Redirect> {
+ let _ = id;
// let Some(id) = (if id.starts_with("UC") {
// get_node_by_eid(&session.0, IdentifierType::YoutubeChannel, id)?
// } else if id.starts_with("@") {
@@ -54,7 +55,8 @@ pub fn r_youtube_channel(ri: RequestInfo<'_>, id: &str) -> MyResult<Redirect> {
}
#[get("/embed/<v>")]
-pub fn r_youtube_embed(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> {
+pub fn r_youtube_embed(_ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> {
+ let _ = v;
// if v.len() != 11 {
// Err(anyhow!("video id length incorrect"))?
// }
diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs
index f6340be..0fd4432 100644
--- a/server/src/ui/home.rs
+++ b/server/src/ui/home.rs
@@ -45,6 +45,15 @@ pub fn r_home(ri: RequestInfo<'_>) -> MyResult<UiResponse> {
);
page.push(
VIEW_NODE_LIST,
+ home_row_highlight(
+ &ri,
+ "home.bin.daily_random",
+ "FILTER (visi = visi AND kind = movi) SORT RANDOM",
+ )?
+ .as_object(),
+ );
+ page.push(
+ VIEW_NODE_LIST,
home_row(
&ri,
"home.bin.max_rating",
@@ -77,3 +86,21 @@ fn home_row(ri: &RequestInfo<'_>, title: &str, query: &str) -> Result<ObjectBuff
})?;
Ok(res)
}
+
+fn home_row_highlight(ri: &RequestInfo<'_>, title: &str, query: &str) -> Result<ObjectBuffer> {
+ let q = Query::from_str(query).context("parse query")?;
+ let mut res = ObjectBuffer::empty();
+ ri.state.database.transaction(&mut |txn| {
+ let Some(row) = txn.query(q.clone())?.next() else {
+ return Ok(());
+ };
+ let row = row?.0;
+ let node = txn.get(row)?.unwrap();
+ let nku = ObjectBuffer::new(&mut [(NKU_NODE.0, &node.as_object())]);
+ res = Object::EMPTY.insert(NODELIST_DISPLAYSTYLE, NLSTYLE_HIGHLIGHT);
+ res = res.as_object().insert(NODELIST_TITLE, title);
+ res = res.as_object().insert(NODELIST_ITEM, nku.as_object());
+ Ok(())
+ })?;
+ Ok(res)
+}
diff --git a/ui/client-style/src/node_card.css b/ui/client-style/src/node_card.css
index 2c0b97f..43c3898 100644
--- a/ui/client-style/src/node_card.css
+++ b/ui/client-style/src/node_card.css
@@ -68,7 +68,7 @@
grid-area: 1 / 1;
}
-.card .poster .cardhover {
+.card .poster .overlay {
position: relative;
pointer-events: none;
grid-area: 1 / 1;
@@ -79,7 +79,7 @@
justify-content: center;
align-items: center;
}
-.card .poster:hover .cardhover {
+.card .poster:hover .overlay {
opacity: 1;
}
@@ -90,12 +90,11 @@
.card .poster a img {
transition: transform 0.3s;
}
-
.card .poster:hover a img {
transform: scale(1.1);
}
-.card .poster .cardhover a.play {
+.card .poster .overlay a.play {
text-decoration: none;
width: 1em;
height: 1em;
@@ -108,40 +107,61 @@
background-color: var(--overlay);
transition: background-color 0.3s, font-size 0.3s;
}
-.card .poster .cardhover a.play:hover {
+.card .poster .overlay a.play:hover {
background-color: var(--overlay-hover);
font-size: 3em;
}
-.card .poster .cardhover .props {
+.card .poster .overlay .props {
position: absolute;
bottom: 0px;
left: 0px;
}
-.widecard {
+.card.wide {
display: grid;
grid-template-columns: 1fr 10000fr;
width: 100%;
}
-.widecard .poster {
+.card.wide .poster {
grid-column: 1;
}
-.widecard .details {
+.card.wide .details {
grid-column: 2;
margin: 1em;
}
-.widecard .details .title {
+.card.wide .details .title {
font-size: large;
}
-.widecard .details .props {
+.card.wide .details .props {
margin-bottom: 0.5em;
}
-@media (max-width: 750px) {
- nav .library {
- display: none;
- }
- .children {
- justify-content: center;
- }
+.card.highlight {
+ padding: 0em;
+ background-size: cover;
+ background-position: center;
+ background-image: linear-gradient(#0009);
+ border-radius: 1em;
+ width: 100%;
+ box-sizing: border-box;
+}
+.card.highlight .inner {
+ height: var(--card-size);
+ padding: 2em;
+ display: flex;
+ flex-direction: row;
+ backdrop-filter: blur(5px);
+ background-image: linear-gradient(
+ 90deg,
+ #000a 0%,
+ #0005 60%,
+ transparent 100%
+ );
+}
+.card.highlight .overview h2 {
+ margin-bottom: 0em;
+}
+.card.highlight .poster {
+ flex-shrink: 0;
+ margin-left: 5em;
}
diff --git a/ui/client-style/src/node_page.css b/ui/client-style/src/node_page.css
index 62c8c7e..b8306e8 100644
--- a/ui/client-style/src/node_page.css
+++ b/ui/client-style/src/node_page.css
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
Copyright (C) 2023 tpart
*/
-.backdrop {
+#main > .backdrop {
width: calc(100% + 2 * var(--main-side-margin));
height: min(50vh, calc(var(--backdrop-height) + 5em));
margin-left: calc(-1 * var(--main-side-margin));
diff --git a/ui/src/components/node_card.rs b/ui/src/components/node_card.rs
index eb00ccc..d93825b 100644
--- a/ui/src/components/node_card.rs
+++ b/ui/src/components/node_card.rs
@@ -18,13 +18,12 @@ markup::define! {
NodeCard<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) {
@let node = nku.get(NKU_NODE).unwrap_or_default();
@let slug = node.get(NO_SLUG).unwrap_or_default();
- @let cls = format!("node card poster {}", aspect_class(node));
- div[class=cls] {
+ div[class=&format!("card {}", aspect_class(node))] {
.poster {
a[href=u_node_slug(&slug)] {
img[src=cover_image(&node, 512), loading="lazy"];
}
- .cardhover.item {
+ .overlay {
@if node.has(NO_TRACK.0) {
a.play.icon[href=u_node_slug_player(&slug)] { "play_arrow" }
}
@@ -43,15 +42,16 @@ markup::define! {
}
}
}
+
NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) {
@let node = nku.get(NKU_NODE).unwrap_or_default();
@let slug = node.get(NO_SLUG).unwrap_or_default();
- div[class="node card widecard poster"] {
+ div[class="card wide"] {
div[class=&format!("poster {}", aspect_class(node))] {
a[href=u_node_slug(&slug)] {
img[src=cover_image(&node, 512), loading="lazy"];
}
- .cardhover.item {
+ .overlay {
@if node.has(NO_TRACK.0) {
a.play.icon[href=u_node_slug_player(&slug)] { "play_arrow" }
}
@@ -64,6 +64,31 @@ markup::define! {
}
}
}
+
+ NodeCardHightlight<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) {
+ @let node = nku.get(NKU_NODE).unwrap_or_default();
+ @let slug = node.get(NO_SLUG).unwrap_or_default();
+ @let backdrop = u_image(node.get(NO_PICTURES).unwrap_or_default().get(PICT_BACKDROP).unwrap_or_default(), 2048);
+ div[class="card highlight", style=format!("background-image: url(\"{backdrop}\")")] {
+ .inner {
+ div.overview {
+ h2 { a[href=u_node_slug(slug)] { @node.get(NO_TITLE) } }
+ @Props { ri, nku: *nku, full: false }
+ p { b { @node.get(NO_TAGLINE) } " " @node.get(NO_DESCRIPTION) }
+ }
+ div[class=&format!("poster {}", aspect_class(node))] {
+ a[href=u_node_slug(&slug)] {
+ img[src=cover_image(&node, 512), loading="lazy"];
+ }
+ .overlay {
+ @if node.has(NO_TRACK.0) {
+ a.play.icon[href=u_node_slug_player(&slug)] { "play_arrow" }
+ }
+ }
+ }
+ }
+ }
+ }
}
fn cover_image(node: &Object, size: usize) -> String {
diff --git a/ui/src/components/node_list.rs b/ui/src/components/node_list.rs
index d1d16fb..679a11d 100644
--- a/ui/src/components/node_list.rs
+++ b/ui/src/components/node_list.rs
@@ -6,7 +6,7 @@
use crate::{
RenderInfo,
- components::node_card::{NodeCard, NodeCardWide},
+ components::node_card::{NodeCard, NodeCardHightlight, NodeCardWide},
};
use jellycommon::{jellyobject::Object, *};
use jellyui_locale::tr;
@@ -17,20 +17,28 @@ markup::define! {
@if let Some(title) = nl.get(NODELIST_TITLE) {
h2 { @tr(ri.lang, title) }
}
- @if ds == NLSTYLE_GRID {
- ul.nl.grid { @for nku in nl.iter(NODELIST_ITEM) {
- li { @NodeCard { ri, nku } }
- }}
- }
- @if ds == NLSTYLE_INLINE {
- ul.nl.inline { @for nku in nl.iter(NODELIST_ITEM) {
- li { @NodeCard { ri, nku } }
- }}
- }
- @if ds == NLSTYLE_LIST {
- ol.nl.list { @for nku in nl.iter(NODELIST_ITEM) {
- li { @NodeCardWide { ri, nku } }
- }}
+ @match ds {
+ NLSTYLE_GRID => {
+ ul.nl.grid { @for nku in nl.iter(NODELIST_ITEM) {
+ li { @NodeCard { ri, nku } }
+ }}
+ }
+ NLSTYLE_INLINE => {
+ ul.nl.inline { @for nku in nl.iter(NODELIST_ITEM) {
+ li { @NodeCard { ri, nku } }
+ }}
+ }
+ NLSTYLE_LIST => {
+ ol.nl.list { @for nku in nl.iter(NODELIST_ITEM) {
+ li { @NodeCardWide { ri, nku } }
+ }}
+ }
+ NLSTYLE_HIGHLIGHT => {
+ @if let Some(nku) = nl.get(NODELIST_ITEM) {
+ @NodeCardHightlight { ri, nku }
+ }
+ }
+ _ => {}
}
}
}
diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs
index 7deaf61..5823933 100644
--- a/ui/src/components/node_page.rs
+++ b/ui/src/components/node_page.rs
@@ -181,6 +181,7 @@ markup::define! {
}
Player<'a>(ri: &'a RenderInfo<'a>, nku: Object<'a>) {
+ @let _ = ri;
@let node = nku.get(NKU_NODE).unwrap_or_default();
@let pics = node.get(NO_PICTURES).unwrap_or_default();
video[id="player", poster=pics.get(PICT_COVER).map(|p| u_image(p, 2048))] {}
@@ -188,11 +189,11 @@ markup::define! {
}
-fn chapter_key_time(c: Object, dur: f64) -> f64 {
- let start = c.get(CH_START).unwrap_or(0.);
- let end = c.get(CH_END).unwrap_or(dur);
- start * 0.8 + end * 0.2
-}
+// fn chapter_key_time(c: Object, dur: f64) -> f64 {
+// let start = c.get(CH_START).unwrap_or(0.);
+// let end = c.get(CH_END).unwrap_or(dur);
+// start * 0.8 + end * 0.2
+// }
pub fn aspect_class(node: Object<'_>) -> &'static str {
let kind = node.get(NO_KIND).unwrap_or(KIND_COLLECTION);
diff --git a/ui/src/components/props.rs b/ui/src/components/props.rs
index c11dca6..5fa9d3e 100644
--- a/ui/src/components/props.rs
+++ b/ui/src/components/props.rs
@@ -47,11 +47,11 @@ markup::define! {
RTYP_YOUTUBE_LIKES => {p.likes{ @format_count(value as usize) " Likes" }}
RTYP_YOUTUBE_VIEWS => {p{ @format_count(value as usize) " Views" }}
RTYP_YOUTUBE_SUBSCRIBERS => {p{ @format_count(value as usize) " Subscribers" }}
- RTYP_ROTTEN_TOMATOES => {p.rating{ "Rotten Tomatoes: " @value "%" }}
+ RTYP_ROTTEN_TOMATOES if *full => {p.rating{ "Rotten Tomatoes: " @value "%" }}
RTYP_METACRITIC if *full => {p.rating{ "Metacritic: " @value "/100" }}
RTYP_IMDB => {p.rating{ "IMDb " @value }}
- RTYP_TMDB => {p.rating{ "TMDB " @format!("{:.01}", value) }}
- RTYP_TRAKT => {p.rating{ "Trakt " @format!("{:.01}", value) }}
+ RTYP_TMDB if *full => {p.rating{ "TMDB " @format!("{:.01}", value) }}
+ RTYP_TRAKT if *full => {p.rating{ "Trakt " @format!("{:.01}", value) }}
_ => {}
}
}
diff --git a/ui/src/format.rs b/ui/src/format.rs
index 4eb8f84..46d7154 100644
--- a/ui/src/format.rs
+++ b/ui/src/format.rs
@@ -4,12 +4,8 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use jellycommon::{
- jellyobject::{Object, Tag},
- *,
-};
use jellyui_locale::tr;
-use std::{borrow::Cow, fmt::Write};
+use std::fmt::Write;
pub fn format_duration(d: f64) -> String {
format_duration_mode("en", d, false)
@@ -73,48 +69,30 @@ fn test_duration_long() {
pub fn format_size(size: u64) -> String {
humansize::format_size(size, humansize::DECIMAL)
}
-pub fn format_kind(lang: &str, kind: Tag) -> Cow<'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 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)
- }
- 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 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)
+// }
+// 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 {
let n: usize = n.into();
@@ -128,13 +106,13 @@ pub fn format_count(n: impl Into<usize>) -> String {
}
}
-pub fn format_chapter(c: &Object) -> (String, String) {
- (
- format!(
- "{}-{}",
- c.get(CH_START).map(format_duration).unwrap_or_default(),
- c.get(CH_END).map(format_duration).unwrap_or_default(),
- ),
- c.get(CH_NAME).unwrap_or_default().to_string(),
- )
-}
+// pub fn format_chapter(c: &Object) -> (String, String) {
+// (
+// format!(
+// "{}-{}",
+// c.get(CH_START).map(format_duration).unwrap_or_default(),
+// c.get(CH_END).map(format_duration).unwrap_or_default(),
+// ),
+// c.get(CH_NAME).unwrap_or_default().to_string(),
+// )
+// }