Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
bin: Add a generic browser state
Erik Kundt committed 1 year ago
commit 4eff044d331cb07da2fe44f7f67a1227c1ade62d
parent faca8b43cbae1e28c63bdf0da8780f90fe2578e9
1 file changed +121 -0
modified bin/ui/widget.rs
@@ -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>,