feat: paginated results

This commit is contained in:
imgurbot12 2023-12-14 13:14:08 -07:00
parent 7c3c3cf486
commit 3f4fd61aa8
3 changed files with 75 additions and 25 deletions

View File

@ -73,6 +73,8 @@ struct IndexTemplate<'a> {
#[derive(Template)] #[derive(Template)]
#[template(path = "results.html")] #[template(path = "results.html")]
struct ResultsTemplate<'a> { struct ResultsTemplate<'a> {
start: usize,
end: usize,
results: &'a Vec<&'a Entry>, results: &'a Vec<&'a Entry>,
config: &'a Config, config: &'a Config,
} }
@ -81,6 +83,7 @@ struct ResultsTemplate<'a> {
struct AppState<'a> { struct AppState<'a> {
pos: usize, pos: usize,
subpos: usize, subpos: usize,
page: usize,
search: String, search: String,
results: Vec<&'a Entry>, results: Vec<&'a Entry>,
data: &'a AppData, data: &'a AppData,
@ -97,27 +100,40 @@ impl<'a> AppState<'a> {
Self { Self {
pos: 0, pos: 0,
subpos: 0, subpos: 0,
page: 0,
search: "".to_owned(), search: "".to_owned(),
results: vec![], results: vec![],
data, data,
} }
} }
/// Update AppState w/ new Search and Render HTML Results /// Render Current Page of Results
fn search(&mut self, search: String) -> String { fn render_results_page(&self) -> String {
// update search and calculate matching results let size = self.data.config.page_size;
let sfn = build_searchfn(&self.data.config, &search); let start = self.page * size;
self.pos = 0; let max = (self.page + 1) * size;
self.search = search; let nresults = std::cmp::max(self.results.len(), 1);
self.results = self.data.entries.iter().filter(|e| sfn(e)).collect(); let end = std::cmp::min(max, nresults - 1);
// generate results html from template // generate results html from template
let template = ResultsTemplate { let template = ResultsTemplate {
start,
end,
config: &self.data.config, config: &self.data.config,
results: &self.results, results: &self.results,
}; };
template.render().unwrap() template.render().unwrap()
} }
/// Update AppState w/ new Search and Render HTML Results
fn search(&mut self, search: String) -> String {
let sfn = build_searchfn(&self.data.config, &search);
self.pos = 0;
self.page = 0;
self.search = search;
self.results = self.data.entries.iter().filter(|e| sfn(e)).collect();
self.render_results_page()
}
/// Execute Action associated w/ Current Position/Subposition /// Execute Action associated w/ Current Position/Subposition
fn execute(&self) { fn execute(&self) {
log::debug!("execute {} {}", self.pos, self.subpos); log::debug!("execute {} {}", self.pos, self.subpos);
@ -132,14 +148,36 @@ impl<'a> AppState<'a> {
execute(action, self.data.config.terminal.clone()); execute(action, self.data.config.terminal.clone());
} }
#[inline] /// Return Additional Page Results to Load (when nessesary)
fn move_up(&mut self, up: usize) { fn append_results(&mut self, smooth: bool) -> Option<String> {
self.pos = std::cmp::max(self.pos, up) - up; let pos = self.pos as f64;
let size = self.data.config.page_size as f64;
let pages = pos / size;
let ratio = (pos % size) / size;
if pages > self.page as f64 && ratio > self.data.config.page_load {
println!("loading next page!");
self.page += 1;
let results = self.render_results_page();
return Some(format!("append({}, {results:?}, {smooth})", self.pos));
}
None
} }
#[inline] #[inline]
fn move_down(&mut self, down: usize) { fn move_up(&mut self, up: usize) -> Option<String> {
self.pos = std::cmp::min(self.pos + down, self.results.len() - 1); self.pos = std::cmp::max(self.pos, up) - up;
Some(format!("setpos({})", self.pos))
}
#[inline]
fn move_down(&mut self, down: usize) -> Option<String> {
let max = (self.page + 1) * self.data.config.page_size;
let end = std::cmp::min(max, self.results.len()) - 1;
self.pos = std::cmp::min(self.pos + down, end);
match self.append_results(false) {
Some(operation) => Some(operation),
None => Some(format!("setpos({})", self.pos)),
}
} }
/// Handle Search Event sent by UI /// Handle Search Event sent by UI
@ -148,8 +186,6 @@ impl<'a> AppState<'a> {
Some(format!("update({results:?})")) Some(format!("update({results:?})"))
} }
//TODO: need to increase page-size as cursor moves down
//TODO: add loading on scroll as well
//TODO: add submenu access and selection //TODO: add submenu access and selection
//TODO: put back main to reference actual config //TODO: put back main to reference actual config
//TODO: update sway config to make borderless //TODO: update sway config to make borderless
@ -165,11 +201,9 @@ impl<'a> AppState<'a> {
} else if matches(&keybinds.exit, &mods, &code) { } else if matches(&keybinds.exit, &mods, &code) {
std::process::exit(0); std::process::exit(0);
} else if matches(&keybinds.move_next, &mods, &code) { } else if matches(&keybinds.move_next, &mods, &code) {
self.move_down(1); self.move_down(1)
Some(format!("setpos({})", self.pos))
} else if matches(&keybinds.move_prev, &mods, &code) { } else if matches(&keybinds.move_prev, &mods, &code) {
self.move_up(1); self.move_up(1)
Some(format!("setpos({})", self.pos))
} else if matches(&keybinds.open_menu, &mods, &code) { } else if matches(&keybinds.open_menu, &mods, &code) {
// k_updater.set_event(KeyEvent::OpenMenu); // k_updater.set_event(KeyEvent::OpenMenu);
None None
@ -177,11 +211,9 @@ impl<'a> AppState<'a> {
// k_updater.set_event(KeyEvent::CloseMenu); // k_updater.set_event(KeyEvent::CloseMenu);
None None
} else if matches(&keybinds.jump_next, &mods, &code) { } else if matches(&keybinds.jump_next, &mods, &code) {
self.move_down(self.data.config.jump_dist); self.move_down(self.data.config.jump_dist)
Some(format!("setpos({})", self.pos))
} else if matches(&keybinds.jump_prev, &mods, &code) { } else if matches(&keybinds.jump_prev, &mods, &code) {
self.move_up(self.data.config.jump_dist); self.move_up(self.data.config.jump_dist)
Some(format!("setpos({})", self.pos))
} else { } else {
None None
} }
@ -206,7 +238,10 @@ impl<'a> AppState<'a> {
if let Some((pos, subpos)) = self.parse_id_pos(&id) { if let Some((pos, subpos)) = self.parse_id_pos(&id) {
self.pos = pos; self.pos = pos;
self.subpos = subpos; self.subpos = subpos;
return Some(format!("setpos({pos}, true)")); return match self.append_results(true) {
Some(op) => Some(op),
None => Some(format!("setpos({pos}, true)")),
};
} }
} }
ClickEvent::Double { id } => { ClickEvent::Double { id } => {
@ -222,7 +257,13 @@ impl<'a> AppState<'a> {
/// Handle Scrolling Events sent by UI /// Handle Scrolling Events sent by UI
fn scroll_event(&mut self, event: ScrollEvent) -> Option<String> { fn scroll_event(&mut self, event: ScrollEvent) -> Option<String> {
println!("scroll: {event:?}"); // load additonal results when scrolled near bottom
let ratio = event.y as f64 / event.maxy as f64;
if ratio >= self.data.config.page_load {
self.page += 1;
let results = self.render_results_page();
return Some(format!("append(null, {results:?})"));
}
None None
} }

View File

@ -1,4 +1,5 @@
{%- for (i, entry) in results.iter().enumerate() %} {%- for i in start..end %}
{% let entry = results[i] %}
<div class="result-entry"> <div class="result-entry">
<div <div
id="result-{{ i }}" id="result-{{ i }}"

View File

@ -70,6 +70,14 @@ function update(html) {
setpos(0); setpos(0);
} }
// Append Results HTML
function append(pos, html, smooth = false) {
results.innerHTML += html;
if (pos != null && pos != undefined) {
setpos(pos, smooth);
}
}
/* Init */ /* Init */
// start position at zero // start position at zero