mirror of
https://seed.radicle.dev/rad:z39mP9rQAaGmERfUMPULfPUi473tY.git
synced 2026-11-09 21:30:33 +08:00
bin: Add a generic browser state
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
|
||||
use radicle::issue::{self, CloseReason};
|
||||
use ratatui::layout::{Constraint, Layout};
|
||||
@@ -9,6 +10,7 @@ use ratatui::Frame;
|
||||
|
||||
use radicle_tui as tui;
|
||||
|
||||
use tui::store;
|
||||
use tui::ui::theme::style;
|
||||
use tui::ui::widget::{RenderProps, View, ViewProps};
|
||||
use tui::ui::{layout, span};
|
||||
@@ -16,6 +18,125 @@ use tui::ui::{layout, span};
|
||||
use super::format;
|
||||
use super::items::IssueItem;
|
||||
|
||||
use crate::ui::items::Filter;
|
||||
|
||||
/// A `BrowserState` represents the internal state of a browser widget.
|
||||
/// A browser widget would consist of 2 child widgets: a list of items and a
|
||||
/// buffered search field. The search fields value is used to build an
|
||||
/// item filter that the item list reacts on dynamically.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BrowserState<I, F> {
|
||||
items: Vec<I>,
|
||||
selected: Option<usize>,
|
||||
filter: F,
|
||||
search: store::StateValue<String>,
|
||||
show_search: bool,
|
||||
}
|
||||
|
||||
impl<I, F> Default for BrowserState<I, F>
|
||||
where
|
||||
I: Clone,
|
||||
F: Filter<I> + Default + FromStr,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
items: vec![],
|
||||
selected: None,
|
||||
filter: F::default(),
|
||||
search: store::StateValue::new(String::default()),
|
||||
show_search: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F> BrowserState<I, F>
|
||||
where
|
||||
I: Clone,
|
||||
F: Filter<I> + Default + FromStr,
|
||||
{
|
||||
pub fn build(items: Vec<I>, filter: F, search: store::StateValue<String>) -> Self {
|
||||
let selected = items.first().map(|_| 0);
|
||||
|
||||
Self {
|
||||
items,
|
||||
selected,
|
||||
filter,
|
||||
search,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn items(&self) -> Vec<I> {
|
||||
self.items_ref().into_iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn items_ref(&self) -> Vec<&I> {
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|patch| self.filter.matches(patch))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Option<usize> {
|
||||
self.selected
|
||||
}
|
||||
|
||||
pub fn selected_item(&self) -> Option<&I> {
|
||||
self.selected
|
||||
.and_then(|selected| self.items_ref().get(selected).copied())
|
||||
}
|
||||
|
||||
pub fn select_item(&mut self, selected: Option<usize>) -> Option<&I> {
|
||||
self.selected = selected;
|
||||
self.selected_item()
|
||||
}
|
||||
|
||||
pub fn select_first_item(&mut self) -> Option<&I> {
|
||||
self.selected.and_then(|selected| {
|
||||
if selected > self.items_ref().len() {
|
||||
self.selected = Some(0);
|
||||
self.items_ref().first().cloned()
|
||||
} else {
|
||||
self.items_ref().get(selected).cloned()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn filter_items(&mut self) {
|
||||
self.filter = F::from_str(&self.search.read()).unwrap_or_default();
|
||||
}
|
||||
|
||||
pub fn update_search(&mut self, value: String) {
|
||||
self.search.write(value);
|
||||
self.filter_items();
|
||||
}
|
||||
|
||||
pub fn show_search(&mut self) {
|
||||
self.show_search = true;
|
||||
}
|
||||
|
||||
pub fn hide_search(&mut self) {
|
||||
self.show_search = false;
|
||||
}
|
||||
|
||||
pub fn apply_search(&mut self) {
|
||||
self.search.apply();
|
||||
}
|
||||
|
||||
pub fn reset_search(&mut self) {
|
||||
self.search.reset();
|
||||
self.filter_items();
|
||||
}
|
||||
|
||||
pub fn is_search_shown(&self) -> bool {
|
||||
self.show_search
|
||||
}
|
||||
|
||||
pub fn read_search(&self) -> String {
|
||||
self.search.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct IssueDetailsProps {
|
||||
issue: Option<IssueItem>,
|
||||
|
||||
Reference in New Issue
Block a user