diff --git a/bin/commands/patch/list.rs b/bin/commands/patch/list.rs index f48cc9ab..1195bfbe 100644 --- a/bin/commands/patch/list.rs +++ b/bin/commands/patch/list.rs @@ -20,7 +20,7 @@ use radicle_tui as tui; use tui::event::Key; use tui::store; -use tui::task::EmptyProcessors; +use tui::task::{Process, Task}; use tui::ui; use tui::ui::layout::Spacing; use tui::ui::widget::{ @@ -101,6 +101,7 @@ pub enum PatchOperation { type Selection = tui::Selection; +#[derive(Clone, Debug)] pub struct Context { pub profile: Profile, pub rid: RepoId, @@ -109,6 +110,59 @@ pub struct Context { pub search: Option, } +#[derive(Clone, Debug)] +pub struct Loader { + context: Context, +} + +impl Loader { + fn new(context: Context) -> Self { + Self { context } + } +} + +#[derive(Debug)] +pub struct PatchLoader { + context: Context, +} + +impl PatchLoader { + fn new(context: Context) -> Self { + PatchLoader { context } + } +} + +impl Task for PatchLoader { + type Return = Message; + + fn run(&self) -> anyhow::Result> { + let context = &self.context; + let profile = context.profile.clone(); + let repo = profile.storage.repository(context.rid)?; + let cache = profile.patches(&repo)?; + let patches = cache + .list()? + .filter_map(|patch| patch.ok()) + .flat_map(|patch| Patch::new(&context.profile, &repo, patch.clone()).ok()) + .collect::>(); + + Ok(vec![Message::Loaded(patches)]) + } +} + +impl Process for Loader { + async fn process(&mut self, message: Message) -> anyhow::Result> { + match message { + Message::Initialize | Message::Reload => { + let loader = PatchLoader::new(self.context.clone()); + let messages = tokio::spawn(async move { loader.run() }).await.unwrap()?; + Ok(messages) + } + _ => Ok(vec![]), + } + } +} + pub struct Tui { context: Context, } @@ -123,7 +177,13 @@ impl Tui { let channel = Channel::default(); let state = App::try_from(&self.context)?; - tui::im(state, viewport, channel, EmptyProcessors::new()).await + tui::im( + state, + viewport, + channel, + vec![Loader::new(self.context.clone())], + ) + .await } } @@ -148,9 +208,12 @@ pub enum Change { #[derive(Clone, Debug)] pub enum Message { + Initialize, Changed(Change), ShowSearch, HideSearch { apply: bool }, + Reload, + Loaded(Vec), Exit { operation: Option }, Quit, } @@ -170,6 +233,8 @@ pub struct AppState { show_search: bool, help: TextViewState, filter: PatchFilter, + loading: bool, + initialized: bool, } #[derive(Clone, Debug)] @@ -226,6 +291,8 @@ impl TryFrom<&Context> for App { show_search: false, help: TextViewState::new(Position::default()), filter, + loading: false, + initialized: false, }, }) } @@ -236,6 +303,21 @@ impl store::Update for App { fn update(&mut self, message: Message) -> Option> { match message { + Message::Initialize => { + self.state.loading = true; + self.state.initialized = true; + None + } + Message::Reload => { + self.state.loading = true; + None + } + Message::Loaded(patches) => { + self.apply_patches(patches); + self.apply_sorting(); + self.state.loading = false; + None + } Message::Quit => Some(Exit { value: None }), Message::Exit { operation } => Some(Exit { value: Some(Selection { @@ -295,6 +377,11 @@ impl store::Update for App { impl Show for App { fn show(&self, ctx: &ui::Context, frame: &mut Frame) -> Result<()> { Window::default().show(ctx, |ui| { + // Initialize + if !self.state.initialized { + ui.send_message(Message::Initialize); + } + match self.state.page { Page::Main => { let show_search = self.state.show_search; @@ -406,6 +493,10 @@ impl App { state: TableState::new(selected), })); } + + if self.state.loading { + self.show_loading_popup(frame, ui); + } }, ); @@ -413,6 +504,9 @@ impl App { if ui.has_input(|key| key == Key::Char('/')) { ui.send_message(Message::ShowSearch); } + if ui.has_input(|key| key == Key::Char('r')) { + ui.send_message(Message::Reload); + } if let Ok(args) = OperationArguments::try_from((&patches, &self.state)) { if ui.has_input(|key| key == Key::Enter) { @@ -623,6 +717,7 @@ impl App { ("c", "checkout"), ("d", "diff"), ("/", "search"), + ("r", "reload"), ("?", "help"), ], '∙', @@ -630,6 +725,35 @@ impl App { ); } + fn show_loading_popup(&self, frame: &mut Frame, ui: &mut Ui) { + ui.popup(Layout::vertical([Constraint::Min(1)]), |ui| { + ui.layout( + Layout::vertical([Constraint::Min(1), Constraint::Length(3)]).margin(1), + None, + |ui| { + ui.label(frame, ""); + ui.layout( + Layout::horizontal([Constraint::Min(1), Constraint::Length(11)]), + None, + |ui| { + ui.label(frame, ""); + ui.column_bar( + frame, + [Column::new( + Span::raw(" Loading ").magenta().rapid_blink(), + Constraint::Fill(1), + )] + .to_vec(), + Spacing::from(0), + Some(Borders::All), + ); + }, + ); + }, + ); + }); + } + fn show_help_text(&self, frame: &mut Frame, ui: &mut Ui) { ui.column_bar( frame, @@ -677,3 +801,15 @@ impl App { ); } } + +impl App { + fn apply_patches(&mut self, patches: Vec) { + let mut items = self.patches.lock().unwrap(); + *items = patches; + } + + fn apply_sorting(&mut self) { + let mut items = self.patches.lock().unwrap(); + items.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + } +}