diff --git a/frontend/src/pages/problems.rs b/frontend/src/pages/problems.rs index acdb50a..baafc51 100644 --- a/frontend/src/pages/problems.rs +++ b/frontend/src/pages/problems.rs @@ -1,5 +1,4 @@ -use std::{borrow::BorrowMut, default, ops::DerefMut}; -use std::rc::Rc; +use std::{borrow::BorrowMut, default, ops::DerefMut, rc::Rc}; use leptos::{html::s, *}; use leptos_router::*; @@ -10,12 +9,9 @@ use crate::{ config::{self, use_token, WithToken}, error::*, grpc::{problem_set_client::*, *}, - pages::problems::toggle::Toggle, + pages::{error_fallback, problems::toggle::Toggle}, }; -use crate::pages::error_fallback; -const PAGESIZEU64: u64 = 10; -const PAGESIZEUSIZE: usize = 10; -const PAGESIZEI64: i64 = 10; +const PAGESIZE: u64 = 10; #[derive(Deserialize, Serialize, Default, Clone, PartialEq)] pub enum SearchDeps { @@ -27,20 +23,19 @@ pub enum SearchDeps { #[derive(Deserialize, Serialize, Default, Clone, PartialEq, Params)] pub struct RawPager { - /// trailing pager session - tp: Option, - /// leading pager session - lp: Option, - /// trailing pager offset - to: Option, - /// leading pager offset - lo: Option, - /// text search + /// session + s: Option, + /// offset + o: Option, + /// direction + d: Option, text: Option, /// column search sort_by: Option, /// start from end - se: Option, + e: Option, + /// page_number + p: Option, } /// Abtraction of paged(rather than cursor api of backend) content @@ -51,22 +46,13 @@ pub struct Pager { // search deps deps: SearchDeps, session: Option, - /// whether to session is at end - at_end: bool, - offset: (u64, u64), + direction: bool, + offset: u64, + // how many page before this page + page_number: u64, start_from_end: bool, } -/// merge two option -macro_rules! merge { - ($a:expr,$b:expr) => { - match $a { - Some(x) => Some(x), - None => $b, - } - }; -} - impl From for Pager { fn from(value: RawPager) -> Self { let deps = match (value.text, value.sort_by) { @@ -77,19 +63,16 @@ impl From for Pager { _ => SearchDeps::None, }; Self { - at_end: value.tp.is_some(), deps, - session: merge!(value.tp, value.lp), - offset: ( - value.to.unwrap_or_default(), - value.lo.unwrap_or_default(), - ), - start_from_end: value.se.unwrap_or_default()==1, + session: value.s, + direction: value.d.unwrap_or_default() == 1, + offset: value.o.unwrap_or_default(), + page_number: value.p.unwrap_or_default(), + start_from_end: value.e.unwrap_or_default() == 1, } } } - impl From for RawPager { fn from(value: Pager) -> Self { let mut text = None; @@ -100,69 +83,40 @@ impl From for RawPager { _ => {} }; - let mut tp = None; - let mut lp = None; - if let Some(session) = value.session { - match value.at_end { - true => lp = Some(session), - false => tp = Some(session), - } - } - + /// FIXME: use None on default value to shorten url Self { - tp, - lp, - to: Some(value.offset.0), - lo: Some(value.offset.1), + s: value.session, + o: Some(value.offset), + d: Some(value.direction as u8), text, sort_by, - se: Some(value.start_from_end as u32), + e: Some(value.start_from_end as u8), + p: Some(value.page_number), } } } impl Pager { - pub fn search(deps: SearchDeps,start_from_end:bool)->Self{ - let self_=Self{ - deps, - session: None, - at_end: false, - offset: (0, 0), - start_from_end, - }; - self_.store(); - self_ + fn into_query(self) -> String { + let raw: RawPager = self.into(); + ["?", &*serde_qs::to_string(&raw).unwrap()].concat() } - pub fn is_default(&self)->bool{ - self.offset.0==0 - } - /// store pager to url - fn store(&self) { - let navigate = leptos_router::use_navigate(); - let raw: RawPager = self.clone().into(); - let param = serde_qs::to_string(&raw).unwrap(); - - navigate( - &["/problems?".to_string(), param].concat(), - Default::default(), - ); - } - /// load pager from url, return default if not found - fn load() -> Pager { - use_query::() - .with(|v| v.clone().map(Into::into).ok()) - .unwrap_or_default() + fn get() -> Memo { + Memo::new(move |_| { + use_query::() + .with(|v| v.clone().map(Into::into).ok()) + .unwrap_or_default() + }) } - /// emit rpc with corresponding endpoint and search parameter(if session is empty) - async fn raw_next( - &mut self, - size: i64, - offset: u64, - session: Option, - ) -> Result { + async fn get_respond(&mut self) -> Result { + let size = match self.direction { + true => -(PAGESIZE as i64), + false => PAGESIZE as i64, + }; let (get_token, _) = use_token(); - let offset = Some(offset); - Ok(match &self.deps { + let offset = Some(self.offset); + let session = self.session.clone().map(|session| Paginator { session }); + let mut res = match &self.deps { SearchDeps::Text(text) => { ProblemSetClient::new(new_client().await?) .search_by_text( @@ -236,68 +190,93 @@ impl Pager { .await? } } - .into_inner()) - } - /// move to next page - /// - /// note that use can pass pages of 0 to fetch the current page from server again - /// - /// * `pages` - how many pages to move. - pub async fn next(&mut self, pages: i64) -> Result { - /// FIXME: add bound check - let mut offset=(pages.abs()as u64)*PAGESIZEU64; - if (pages>0)^self.at_end{ - offset+=self.offset.1-self.offset.0; - } - let mut res = self - .raw_next( - PAGESIZEI64, - offset, - self.session.clone().map(|session| Paginator { session }), - ) - .await?; + .into_inner(); - match pages{ - 0 if self.at_end =>res.list.reverse(), - x if x<0=>res.list.reverse(), - _=>(), + if self.direction { + res.list.reverse(); } - let res_len = res.list.len() as u64; + Ok(res) + } + fn get_queries( + &self, + new_session: String, + remain: u64, + ) -> (Vec, Vec) { + let deps = self.deps.clone(); + let start_from_end = self.start_from_end; - if pages.is_positive() { - self.offset.0 = self.offset.1; - self.offset.1 += res_len; - } else { - self.offset.1 = self.offset.0; - self.offset.0 = self.offset.0.saturating_sub(res_len); + let mut previous_session = self.session.clone(); + let mut next_session = Some(new_session); + if self.direction { + std::mem::swap(&mut previous_session, &mut next_session); } - self.session=Some(res.next_session); - self.store(); - - Ok(RenderInfo { - // FIXME: add bound check for underflow entity - previous_page: (self.offset.0) as u64 / PAGESIZEU64, - list: res.list, - next_page: (res.remain + PAGESIZEU64 - 1) / PAGESIZEU64, - }) + let previous: Vec<_> = (0..self.page_number) + .map(|p| { + Self { + deps: deps.clone(), + session: previous_session.clone(), + direction: true, + offset: p * PAGESIZE, + page_number: self.page_number.saturating_sub(p + 1), + start_from_end, + } + .into_query() + }) + .collect(); + let next: Vec<_> = (0..remain.div_ceil(PAGESIZE)) + .map(|p| { + Self { + deps: deps.clone(), + session: next_session.clone(), + direction: false, + offset: p * PAGESIZE, + page_number: self.page_number.saturating_add(p + 1), + start_from_end, + } + .into_query() + }) + .collect(); + (previous, next) } - // pub async fn load_and_next(pages: i64) -> Result{ - // let mut self_=Self::load(); - // let list=self_.next(pages).await?; - // Ok(list) - // } } #[derive(Deserialize, Serialize, PartialEq, Clone, Default)] struct RenderInfo { - previous_page: u64, + previous_queries: Vec, list: Vec, - next_page: u64, + next_queries: Vec, +} + +impl RenderInfo { + fn get() -> impl Fn() -> Resource> { + let pager = Pager::get(); + move || { + create_resource( + move || pager.get(), + move |_| { + let mut pager = pager.get().clone(); + async move { + pager.get_respond().await.map(|res| { + let list = res.list; + let (previous_queries, next_queries) = + pager.get_queries(res.next_session, res.remain); + + Self { + list, + previous_queries, + next_queries, + } + }) + } + }, + ) + } + } } #[component] -pub fn ProblemList(render: ReadSignal>, next_action:Action) -> impl IntoView { +pub fn ProblemList() -> impl IntoView { fn difficulty_color(difficulty: u32) -> impl IntoView { let color: &'static str = match difficulty { 0..=1000 => "green", @@ -311,175 +290,78 @@ pub fn ProblemList(render: ReadSignal>, next_action:Action - { - move||render.get().map(|v| - v.list - .into_iter() - .map(|info| { - view! { -
-
- {info.title} + let r = RenderInfo::get()(); + + view! { +
+ Loading

} + }> + {move || { + r.get() + .map(|v| v.map(|v|view!{ +
+
+
+
Title
+ + +
Difficulty
- - -
{difficulty_color(info.difficulty)}
+
+ { + v.list.into_iter().map(|info| { + view! { +
+ + + +
{difficulty_color(info.difficulty)}
+
+ } + }) + .collect_view() + } +
+
+
    + { + v.previous_queries.into_iter().enumerate() + .map(|(n,query)|view!{ +
  • + back {n+1} page +
  • + }).collect_view() } - }) - .collect_view() - ) - } -
-
    - { - move||render.get().clone().map(|v| - (1..=(v.previous_page)) - .map(|i|{ - view!{ -
  • - back {i}th page -
  • +
+
    + { + v.next_queries.into_iter().enumerate() + .map(|(n,query)|view!{ +
  • + back {n+1} page +
  • + }).collect_view() } - }).collect_view() - ) - } -
-
    - { - move||render.get().clone().map(|v| - (1..=(v.next_page)) - .map(|i|{ - view!{ -
  • - advance {i}th page -
  • - } - }).collect_view() - ) - } -
- }.into_view() - }; - view! { -
-
-
-
Title
- - -
Difficulty
-
-
- {list} -
- } -} - -#[component] -pub fn ProblemSearch(set_render: WriteSignal>) -> impl IntoView { - let search_text = create_rw_signal("".to_owned()); - let reverse = create_rw_signal(false); - - let submit = - create_action(move |(search_text, reverse): &(String, bool)| { - let serach_text = search_text.clone(); - - let (get_token, _) = use_token(); - - async move { - // let mut problem_set = problem_set_client::ProblemSetClient::new( - // new_client().await?, - // ); - // match search_text.is_empty(){ - // true=>{ - // let resp = problem_set - // .list( - // ListProblemRequest { - // size: 50, - // offset: None, - // request: Some( - // list_problem_request::Request::Create( - // list_problem_request::Create { - // sort_by: ProblemSortBy::UpdateDate - // .into(), - // start_from_end: Some(*reverse), - // }, - // ), - // ), - // } - // .with_token(get_token()), - // ) - // .await?; - // let resp = resp.into_inner(); - // Some(resp) - // } - // } + + })) + } } - }); - - let disabled = Signal::derive(move || submit.pending()()); - - view! { -
- - -
- -
Start from end
-
+
} } #[component] pub fn Problems() -> impl IntoView { - let (render, set_render) = create_signal(Ok(RenderInfo::default())); - - let render_resource =create_resource(||(), move|_| { - let set_render=set_render.clone(); - let mut pager = Pager::load(); - let pages = match pager.is_default() { - true => 1, - false => 0 - }; - async move { - set_render.set(pager.next(pages).await); - } - }); - - let next_action=create_action(move |(pages):&(i64)|{ - let mut pager=Pager::load(); - let pages=*pages; - async move{ - set_render.set(pager.next(pages).await); - } - }); - view! {
- - Loading

} - }> - - { - render_resource.get().map(|_|view!{}) - } - -
+ + +
} }