use crate::{ error::Error, extensions::ProfileExt, middleware::UserProfile, nav::NavState, pagination::{Page, PageNum, PageSource, Pagination, SearchPage, SearchPagination}, views::ProfileView, ActixLoader, State, }; use actix_web::{web, HttpRequest, HttpResponse, Scope}; use hyaenidae_profiles::store::{File, Profile}; use hyaenidae_toolkit::{Button, TextInput}; use i18n_embed_fl::fl; use std::collections::HashMap; use uuid::Uuid; const PER_PAGE: usize = 8; pub(super) fn scope() -> Scope { web::scope("/discover").route("", web::get().to(profile_list)) } async fn profile_list( loader: ActixLoader, 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 search = search.and_then(|search_query| { if search_query.0.term.trim().is_empty() { None } else { Some(search_query.0.term.trim().to_owned()) } }); let state = if let Some(term) = search { ProfileListState::search( req.path().to_owned(), profile.0.id(), 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, &loader, &state, &nav_state) }) } #[derive(Debug)] enum PageKind { Linear(Page), Search(SearchPage), } #[derive(Debug)] pub struct ProfileListState { path: String, cache: ProfileCache, profiles: PageKind, } #[derive(Clone, Copy, Debug, serde::Deserialize)] #[serde(untagged)] enum ProfilePage { Max { max: Uuid }, 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, files: HashMap, blocks: HashMap, following: HashMap, follow_requested: HashMap, } struct ProfilePager<'b> { cache: &'b mut ProfileCache, store: &'b hyaenidae_profiles::State, 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, loader: &ActixLoader) -> TextInput { let input = TextInput::new("term").placeholder(&fl!(loader, "discover-users-search-placeholder")); 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| { let profile = self.cache.profiles.get(profile_id)?; let icon = profile .icon() .and_then(|file_id| self.cache.files.get(&file_id)); let banner = profile .banner() .and_then(|file_id| self.cache.files.get(&file_id)); Some(ProfileView { profile, icon, banner, }) }) } pub(crate) fn profile_buttons<'a>( &'a self, loader: &ActixLoader, view: ProfileView<'a>, ) -> Vec