From a9166f398426fdc35cf803469b6d8e6f7bbae86c Mon Sep 17 00:00:00 2001 From: asonix Date: Mon, 25 Jan 2021 20:38:05 -0600 Subject: [PATCH] Server: Expose profile search on discover page --- server/src/pagination/mod.rs | 62 ++++++++ server/src/profile_list.rs | 164 +++++++++++++++++++-- server/templates/profiles/discover.rs.html | 18 ++- 3 files changed, 227 insertions(+), 17 deletions(-) diff --git a/server/src/pagination/mod.rs b/server/src/pagination/mod.rs index 45fd0b2..9f06b86 100644 --- a/server/src/pagination/mod.rs +++ b/server/src/pagination/mod.rs @@ -8,6 +8,11 @@ pub(crate) enum PageSource { OlderThan(Uuid), } +#[derive(Debug)] +pub(crate) struct PageNum { + pub(crate) page: usize, +} + #[derive(Debug)] pub(crate) struct Page { pub(crate) items: Vec, @@ -16,12 +21,25 @@ pub(crate) struct Page { pub(crate) reset: bool, } +#[derive(Debug)] +pub(crate) struct SearchPage { + pub(crate) items: Vec, + pub(crate) next: Option, + pub(crate) prev: Option, + pub(crate) term: String, +} + pub trait Pagination { fn from_max<'a>(&'a mut self, max: Uuid) -> Box + 'a>; fn from_min<'a>(&'a mut self, min: Uuid) -> Box + 'a>; fn from_start<'a>(&'a mut self) -> Box + 'a>; } +pub trait SearchPagination { + fn from_term<'a>(&'a mut self, term: &'a str) + -> Box + 'a>; +} + impl Page { pub(crate) fn from_pagination( mut pagination: impl Pagination, @@ -83,3 +101,47 @@ impl Page { } } } + +impl SearchPage { + pub(crate) fn from_pagination( + mut pagination: impl SearchPagination, + per_page: usize, + term: String, + page: Option, + ) -> Self { + let mut items: Vec; + let mut next = None; + let mut prev = None; + + match page { + Some(PageNum { page }) => { + items = pagination + .from_term(&term) + .skip(page * per_page) + .take(per_page + 1) + .collect(); + if page > 0 { + prev = Some(page - 1); + } + if items.len() == per_page + 1 { + items.pop(); + next = Some(page + 1); + } + } + None => { + items = pagination.from_term(&term).take(per_page + 1).collect(); + if items.len() == per_page + 1 { + items.pop(); + next = Some(1); + } + } + } + + SearchPage { + items, + next, + prev, + term, + } + } +} diff --git a/server/src/profile_list.rs b/server/src/profile_list.rs index 9a4ab6b..0d7763c 100644 --- a/server/src/profile_list.rs +++ b/server/src/profile_list.rs @@ -3,13 +3,13 @@ use crate::{ extensions::ProfileExt, middleware::UserProfile, nav::NavState, - pagination::{Page, PageSource, Pagination}, + pagination::{Page, PageNum, PageSource, Pagination, SearchPage, SearchPagination}, views::ProfileView, State, }; use actix_web::{web, HttpRequest, HttpResponse, Scope}; use hyaenidae_profiles::store::{File, Profile}; -use hyaenidae_toolkit::Button; +use hyaenidae_toolkit::{Button, TextInput}; use std::collections::HashMap; use uuid::Uuid; @@ -23,23 +23,43 @@ async fn profile_list( req: HttpRequest, profile: UserProfile, page: Option>, + search: Option>, + page_num: Option>, nav_state: NavState, state: web::Data, ) -> Result { let page = page.map(|query| query.into_inner().into()); + let page_num = page_num.map(|query| query.into_inner().into()); - let state = ProfileListState::build(req.path(), profile.0.id(), page, &state).await?; + let state = if let Some(search) = search { + ProfileListState::search( + req.path().to_owned(), + profile.0.id(), + search.into_inner().term, + page_num, + &state, + ) + .await? + } else { + ProfileListState::build(req.path().to_owned(), profile.0.id(), page, &state).await? + }; crate::rendered(HttpResponse::Ok(), |cursor| { crate::templates::profiles::discover(cursor, &state, &nav_state) }) } +#[derive(Debug)] +enum PageKind { + Linear(Page), + Search(SearchPage), +} + #[derive(Debug)] pub struct ProfileListState { path: String, cache: ProfileCache, - profiles: Page, + profiles: PageKind, } #[derive(Clone, Copy, Debug, serde::Deserialize)] @@ -49,6 +69,16 @@ enum ProfilePage { Min { min: Uuid }, } +#[derive(Clone, Copy, Debug, serde::Deserialize)] +struct PageNumQuery { + page: usize, +} + +#[derive(Clone, Debug, serde::Deserialize)] +struct Search { + term: String, +} + #[derive(Debug, Default)] struct ProfileCache { profiles: HashMap, @@ -64,9 +94,31 @@ struct ProfilePager<'b> { viewer: Uuid, } +impl PageKind { + fn items(&self) -> &[Uuid] { + match self { + PageKind::Linear(page) => &page.items, + PageKind::Search(page) => &page.items, + } + } +} + impl ProfileListState { + pub(crate) fn search_path(&self) -> &str { + &self.path + } + + pub(crate) fn search_input(&self) -> TextInput { + let input = TextInput::new("term").title("Handle").placeholder("Handle"); + + match &self.profiles { + PageKind::Search(search) => input.value(&search.term), + _ => input, + } + } + pub(crate) fn profiles<'a>(&'a self) -> impl Iterator> + 'a { - self.profiles.items.iter().filter_map(move |profile_id| { + self.profiles.items().iter().filter_map(move |profile_id| { let profile = self.cache.profiles.get(profile_id)?; let icon = profile .icon() @@ -115,31 +167,92 @@ impl ProfileListState { } pub(crate) fn has_nav(&self) -> bool { - self.profiles.next.is_some() || self.profiles.prev.is_some() + match &self.profiles { + PageKind::Linear(page) => page.next.is_some() || page.prev.is_some(), + PageKind::Search(page) => page.next.is_some() || page.prev.is_some(), + } } pub(crate) fn nav(&self) -> Vec