From 53b0268eaa850d0a7250c94373d76906a7b28250 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 18 Mar 2026 17:44:58 +0100 Subject: filter by kind without filter parser --- common/src/api.rs | 5 +++ database/src/kv/mod.rs | 6 ++-- database/src/lib.rs | 2 +- server/src/routes/node.rs | 77 +++++++++++++++++++++++++++++++++--------- ui/src/components/filter.rs | 23 ------------- ui/src/components/mod.rs | 4 +-- ui/src/components/node_list.rs | 47 ++++++++++++++++++++++++++ ui/src/components/node_page.rs | 18 +++------- 8 files changed, 123 insertions(+), 59 deletions(-) delete mode 100644 ui/src/components/filter.rs create mode 100644 ui/src/components/node_list.rs diff --git a/common/src/api.rs b/common/src/api.rs index 061ff09..709e407 100644 --- a/common/src/api.rs +++ b/common/src/api.rs @@ -30,3 +30,8 @@ pub struct StatsBin { pub max_size: u64, pub max_size_node: String, } + +#[derive(Debug, Clone)] +pub struct NodeFilter { + pub kind: Option, +} diff --git a/database/src/kv/mod.rs b/database/src/kv/mod.rs index 0ce4683..816dc9f 100644 --- a/database/src/kv/mod.rs +++ b/database/src/kv/mod.rs @@ -14,7 +14,7 @@ pub mod prefix_iterator; pub mod tests; use crate::{ - Database, Query, RowNum, Transaction, + Database, Filter, Query, RowNum, Transaction, kv::{ helpers::{read_counter, write_counter}, index::{iter_index, read_count_index, test_index_identical, update_index}, @@ -135,9 +135,9 @@ impl Transaction for &mut dyn jellykv::Transaction { fn query_single(&mut self, query: Query) -> Result> { Ok(self.query(query)?.next().transpose()?.map(|(e, _)| e)) } - fn count(&mut self, query: Query) -> Result { + fn count(&mut self, filter: Filter) -> Result { let mut total = 0; - for (binning, mut prefix) in query.filter.get_bins() { + for (binning, mut prefix) in filter.get_bins() { let ik = IndexKey(binning, SortKey::Count); let is = get_or_create_index(*self, &ik)?; prefix.splice(0..0, is.to_be_bytes()); diff --git a/database/src/lib.rs b/database/src/lib.rs index 9bd4c06..da58003 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -33,7 +33,7 @@ pub trait Transaction { query: Query, ) -> Result)>> + 'a>>; fn query_single(&mut self, query: Query) -> Result>; - fn count(&mut self, query: Query) -> Result; + fn count(&mut self, filter: Filter) -> Result; } #[derive(Debug, Default, Clone, PartialEq)] diff --git a/server/src/routes/node.rs b/server/src/routes/node.rs index 506d771..240486e 100644 --- a/server/src/routes/node.rs +++ b/server/src/routes/node.rs @@ -12,7 +12,10 @@ use jellycommon::{ *, }; use jellydb::{Filter, MultiBehaviour, Query, Sort, SortOrder, Transaction, ValueSort}; -use jellyui::components::node_page::NodePage; +use jellyui::components::{ + node_list::{NodeFilterInfo, NodeListData}, + node_page::NodePage, +}; use rocket::{get, response::content::RawHtml}; use std::{borrow::Cow, collections::BTreeMap}; @@ -20,8 +23,10 @@ use std::{borrow::Cow, collections::BTreeMap}; pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult> { ri.require_user()?; + let filter = NodeFilter { kind: None }; + let mut nku = None; - let mut children = Vec::new(); + let mut children = None; let mut credits = Vec::new(); let mut credited = Vec::new(); ri.state.database.transaction(&mut |txn| { @@ -31,13 +36,13 @@ pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult> { })? { let n = txn.get(row)?.unwrap(); nku = Some(create_nku(txn, row)?); - children = c_children(txn, row, &n)?; + children = Some(c_children(txn, filter.clone(), row, &n)?); credits = c_credits(txn, &n)?; credited = c_credited(txn, row)?; } Ok(()) })?; - let Some(nku) = nku else { + let (Some(nku), Some(children)) = (nku, children) else { Err(anyhow!("no such node"))? }; @@ -50,7 +55,25 @@ pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult> { })) } -fn c_children(txn: &mut dyn Transaction, row: u64, node: &Object) -> Result>> { +const KINDS_TAB_ORDER: &[Tag] = &[ + KIND_COLLECTION, + KIND_MOVIE, + KIND_SHOW, + KIND_SERIES, + KIND_SEASON, + KIND_EPISODE, + KIND_CHANNEL, + KIND_VIDEO, + KIND_SHORTFORMVIDEO, + KIND_MUSIC, +]; + +fn c_children( + txn: &mut dyn Transaction, + mut filter: NodeFilter, + row: u64, + node: &Object, +) -> Result> { let kind = node.get(NO_KIND).unwrap_or(KIND_COLLECTION); let (order, path) = match kind { KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])), @@ -58,6 +81,28 @@ fn c_children(txn: &mut dyn Transaction, row: u64, node: &Object) -> Result (SortOrder::Ascending, Path(vec![NO_TITLE.0])), }; + let mut filter_kinds = Vec::new(); + for &kind in KINDS_TAB_ORDER { + let count = txn.count(Filter::All(vec![ + Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), + Filter::Match(Path(vec![NO_PARENT.0]), row.into()), + Filter::Match(Path(vec![NO_KIND.0]), kind.into()), + ]))?; + if count > 0 { + filter_kinds.push((kind, count)); + } + } + if filter.kind.is_none() { + filter.kind = filter_kinds.first().map(|(t, _)| *t); + } + + let mut filters = vec![ + Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), + Filter::Match(Path(vec![NO_PARENT.0]), row.into()), + ]; + if let Some(kind) = filter.kind { + filters.push(Filter::Match(Path(vec![NO_KIND.0]), kind.into())) + } let children_rows = txn .query(Query { sort: Sort::Value(ValueSort { @@ -66,19 +111,22 @@ fn c_children(txn: &mut dyn Transaction, row: u64, node: &Object) -> Result>>()?; - let mut list = Vec::new(); + let mut items = Vec::new(); for (row, _) in children_rows { - list.push(create_nku(txn, row)?); + items.push(create_nku(txn, row)?); } - Ok(list) + Ok(NodeListData { + filter_info: NodeFilterInfo { + cur: filter, + kinds: filter_kinds, + }, + items, + }) } fn c_credits(txn: &mut dyn Transaction, node: &Object) -> Result>)>> { @@ -136,10 +184,7 @@ fn c_credited(txn: &mut dyn Transaction, row: u64) -> Result>> } pub fn create_nku(txn: &mut dyn Transaction, row: u64) -> Result> { - let child_count = txn.count(Query { - filter: Filter::Match(Path(vec![NO_PARENT.0]), row.into()), - ..Default::default() - })?; + let child_count = txn.count(Filter::Match(Path(vec![NO_PARENT.0]), row.into()))?; Ok(Nku { node: Cow::Owned(txn.get(row)?.unwrap()), role: None, diff --git a/ui/src/components/filter.rs b/ui/src/components/filter.rs deleted file mode 100644 index cd2b63c..0000000 --- a/ui/src/components/filter.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* - 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 -*/ - -use crate::RenderInfo; -use jellycommon::jellyobject::Tag; -use jellyui_locale::tr; - -pub struct FilterInfo { - kinds: Vec<(Tag, u64)>, -} - -markup::define! { - Filter<'a>(ri: &'a RenderInfo<'a>, info: &'a FilterInfo) { .filter { - ul.kinds { - @for (k, count) in &info.kinds { - li { a[href=format!("?fk={k}")] { @tr(ri.lang, &format!("tag.kind.{k}")) ": " @count } } - } - } - }} -} diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index 5c90a26..28d09f2 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -11,9 +11,9 @@ pub mod items; pub mod login; pub mod message; pub mod node_card; +pub mod node_list; pub mod node_page; pub mod props; +pub mod search; pub mod stats; pub mod user; -pub mod search; -pub mod filter; diff --git a/ui/src/components/node_list.rs b/ui/src/components/node_list.rs new file mode 100644 index 0000000..906deae --- /dev/null +++ b/ui/src/components/node_list.rs @@ -0,0 +1,47 @@ +/* + 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 +*/ + +use crate::{ + RenderInfo, + components::node_card::{NodeCard, NodeCardWide}, +}; +use jellycommon::{Nku, NodeFilter, jellyobject::Tag, *}; +use jellyui_locale::tr; + +pub struct NodeFilterInfo { + pub cur: NodeFilter, + pub kinds: Vec<(Tag, u64)>, +} + +pub struct NodeListData<'a> { + pub filter_info: NodeFilterInfo, + pub items: Vec>, +} + +markup::define! { + NodeList<'a>(ri: &'a RenderInfo<'a>, kind: Tag, data: &'a NodeListData<'a>) { + @if !data.items.is_empty() { + @Filter { ri, info: &data.filter_info } + @if matches!(*kind, KIND_SHOW | KIND_SEASON) { + ul.nl.list { @for nku in &data.items { + li { @NodeCardWide { ri, nku } } + }} + } else { + ul.nl.grid { @for nku in &data.items { + li { @NodeCard { ri, nku } } + }} + } + } + } + + Filter<'a>(ri: &'a RenderInfo<'a>, info: &'a NodeFilterInfo) { .filter { + ul.kinds { + @for (k, count) in &info.kinds { + li { a[href=format!("?fk={k}")] { @tr(ri.lang, &format!("tag.kind.{k}")) ": " @count } } + } + } + }} +} diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs index 29fb6b9..bcee525 100644 --- a/ui/src/components/node_page.rs +++ b/ui/src/components/node_page.rs @@ -7,7 +7,8 @@ use crate::{ RenderInfo, components::{ - node_card::{NodeCard, NodeCardWide}, + node_card::NodeCard, + node_list::{NodeList, NodeListData}, props::Props, }, format::format_duration, @@ -40,7 +41,7 @@ markup::define! { NodePage<'a>( ri: &'a RenderInfo<'a>, nku: Nku<'a>, - children: &'a [Nku<'a>], + children: &'a NodeListData<'a>, credits: &'a [(Tag, Vec>)], credited: &'a [Nku<'a>] ) { @@ -183,18 +184,7 @@ markup::define! { li { @NodeCard { ri, nku } } }} } - @if !children.is_empty() { - // @Filter { ri, info } - @if matches!(node.get(NO_KIND).unwrap_or(KIND_COLLECTION), KIND_SHOW | KIND_SEASON) { - ul.nl.list { @for nku in *children { - li { @NodeCardWide { ri, nku } } - }} - } else { - ul.nl.grid { @for nku in *children { - li { @NodeCard { ri, nku } } - }} - } - } + @NodeList { ri, data: children, kind: node.get(NO_KIND).unwrap_or(KIND_COLLECTION) } } Player<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) { -- cgit v1.3.1