From ff1c09202c82e2abd52140b8ee77d00f0fdabd19 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 21 Feb 2024 02:02:56 +0100 Subject: [PATCH] lib: Add generic exit payload to flux app --- src/flux/store.rs | 41 +++++++++++++++++------------- src/flux/termination.rs | 39 ++++++++++++++++++++--------- src/flux/ui.rs | 18 ++++++++++---- src/flux/ui/widget.rs | 22 +++-------------- src/lib.rs | 55 ++++++++++++++--------------------------- 5 files changed, 86 insertions(+), 89 deletions(-) diff --git a/src/flux/store.rs b/src/flux/store.rs index 86dfe2ae..08d2737f 100644 --- a/src/flux/store.rs +++ b/src/flux/store.rs @@ -5,29 +5,34 @@ use std::time::Duration; use tokio::sync::broadcast; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use crate::Exit; + use super::termination::{Interrupted, Terminator}; const STORE_TICK_RATE: Duration = Duration::from_millis(1000); -pub trait State { - type Exit; - +pub trait State +where + P: Clone + Debug + Send + Sync, +{ fn tick(&self); - fn handle_action(&mut self, action: A) -> Option; + fn handle_action(&mut self, action: A) -> Option>; } -pub struct Store +pub struct Store where - S: State + Clone + Send + Sync, + S: State + Clone + Send + Sync, + P: Clone + Debug + Send + Sync, { state_tx: UnboundedSender, - _phantom: PhantomData, + _phantom: PhantomData<(A, P)>, } -impl Store +impl Store where - S: State + Clone + Send + Sync, + S: State + Clone + Send + Sync, + P: Clone + Debug + Send + Sync, { pub fn new() -> (Self, UnboundedReceiver) { let (state_tx, state_rx) = mpsc::unbounded_channel::(); @@ -42,17 +47,18 @@ where } } -impl Store +impl Store where - S: State + Clone + Send + Sync + 'static + Debug, + S: State + Clone + Debug + Send + Sync + 'static, + P: Clone + Debug + Send + Sync + 'static, { pub async fn main_loop( self, mut state: S, - mut terminator: Terminator, + mut terminator: Terminator

, mut action_rx: UnboundedReceiver, - mut interrupt_rx: broadcast::Receiver, - ) -> anyhow::Result { + mut interrupt_rx: broadcast::Receiver>, + ) -> anyhow::Result> { // the initial state once self.state_tx.send(state.clone())?; @@ -63,10 +69,11 @@ where // Handle the actions coming from the UI // and process them to do async operations Some(action) = action_rx.recv() => { - if let Some(_exit) = state.handle_action(action) { - let _ = terminator.terminate(Interrupted::UserInt); + if let Some(exit) = state.handle_action(action) { + let interrupted = Interrupted::User { payload: exit.value }; + let _ = terminator.terminate(interrupted.clone()); - break Interrupted::UserInt; + break interrupted; } }, // Tick to terminate the select every N milliseconds diff --git a/src/flux/termination.rs b/src/flux/termination.rs index f93ea132..1f225d14 100644 --- a/src/flux/termination.rs +++ b/src/flux/termination.rs @@ -1,24 +1,35 @@ +use std::fmt::Debug; + #[cfg(unix)] use tokio::signal::unix::signal; use tokio::sync::broadcast; #[derive(Debug, Clone)] -pub enum Interrupted { - OsSigInt, - UserInt, +pub enum Interrupted

+where + P: Clone + Send + Sync + Debug, +{ + OsSignal, + User { payload: Option

}, } #[derive(Debug, Clone)] -pub struct Terminator { - interrupt_tx: broadcast::Sender, +pub struct Terminator

+where + P: Clone + Send + Sync + Debug, +{ + interrupt_tx: broadcast::Sender>, } -impl Terminator { - pub fn new(interrupt_tx: broadcast::Sender) -> Self { +impl

Terminator

+where + P: Clone + Send + Sync + Debug + 'static, +{ + pub fn new(interrupt_tx: broadcast::Sender>) -> Self { Self { interrupt_tx } } - pub fn terminate(&mut self, interrupted: Interrupted) -> anyhow::Result<()> { + pub fn terminate(&mut self, interrupted: Interrupted

) -> anyhow::Result<()> { self.interrupt_tx.send(interrupted)?; Ok(()) @@ -26,19 +37,25 @@ impl Terminator { } #[cfg(unix)] -async fn terminate_by_unix_signal(mut terminator: Terminator) { +async fn terminate_by_unix_signal

(mut terminator: Terminator

) +where + P: Clone + Send + Sync + Debug + 'static, +{ let mut interrupt_signal = signal(tokio::signal::unix::SignalKind::interrupt()) .expect("failed to create interrupt signal stream"); interrupt_signal.recv().await; terminator - .terminate(Interrupted::OsSigInt) + .terminate(Interrupted::OsSignal) .expect("failed to send interrupt signal"); } // create a broadcast channel for retrieving the application kill signal -pub fn create_termination() -> (Terminator, broadcast::Receiver) { +pub fn create_termination

() -> (Terminator

, broadcast::Receiver>) +where + P: Clone + Send + Sync + Debug + 'static, +{ let (tx, rx) = broadcast::channel(1); let terminator = Terminator::new(tx); diff --git a/src/flux/ui.rs b/src/flux/ui.rs index 8972b136..06ec301a 100644 --- a/src/flux/ui.rs +++ b/src/flux/ui.rs @@ -6,6 +6,7 @@ pub mod span; pub mod theme; pub mod widget; +use std::fmt::Debug; use std::io::{self}; use std::thread; use std::time::Duration; @@ -38,11 +39,16 @@ impl Frontend { (Self { action_tx }, action_rx) } - pub async fn main_loop, W: Widget + Render<()>>( + pub async fn main_loop( self, mut state_rx: UnboundedReceiver, - mut interrupt_rx: broadcast::Receiver, - ) -> anyhow::Result { + mut interrupt_rx: broadcast::Receiver>, + ) -> anyhow::Result> + where + S: State, + W: Widget + Render<()>, + P: Clone + Send + Sync + Debug, + { let mut terminal = setup_terminal()?; let mut ticker = tokio::time::interval(RENDERING_TICK_RATE); let mut events_rx = events(); @@ -54,7 +60,7 @@ impl Frontend { }; // let mut last_frame: Option = None; - let result: anyhow::Result = loop { + let result: anyhow::Result> = loop { tokio::select! { // Tick to terminate the select every N milliseconds _ = ticker.tick() => (), @@ -95,7 +101,9 @@ fn setup_terminal() -> anyhow::Result> { )?) } -fn restore_terminal(_terminal: &mut Terminal) -> anyhow::Result<()> { +fn restore_terminal(terminal: &mut Terminal) -> anyhow::Result<()> { + // let size = terminal.get_frame().size(); + terminal.clear()?; Ok(()) } diff --git a/src/flux/ui/widget.rs b/src/flux/ui/widget.rs index b75f7a77..5d08c879 100644 --- a/src/flux/ui/widget.rs +++ b/src/flux/ui/widget.rs @@ -25,12 +25,9 @@ pub trait Widget { } pub trait Render

{ - fn render(&mut self, frame: &mut Frame, area: Rect, props: P); + fn render(&self, frame: &mut Frame, area: Rect, props: P); } -/// -/// -/// pub struct Shortcut { pub short: String, pub long: String, @@ -80,12 +77,7 @@ impl Widget for Shortcuts { } impl Render for Shortcuts { - fn render( - &mut self, - frame: &mut ratatui::Frame, - area: Rect, - props: ShortcutsProps, - ) { + fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: ShortcutsProps) { use ratatui::widgets::Table; let mut shortcuts = props.shortcuts.iter().peekable(); @@ -123,9 +115,6 @@ impl Render for Shortcuts { } } -/// -/// -/// pub trait ToRow { fn to_row(&self) -> [Cell; W]; } @@ -204,12 +193,7 @@ impl<'a, A, R, const W: usize> Render> for Table where R: ToRow + Debug, { - fn render( - &mut self, - frame: &mut ratatui::Frame, - area: Rect, - props: TableProps, - ) { + fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: TableProps) { let layout = if props.footer.is_some() { Layout::default() .direction(Direction::Vertical) diff --git a/src/lib.rs b/src/lib.rs index 071fb34b..e931ea7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,7 @@ -use std::fmt::Display; - use anyhow::Result; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use radicle::cob::ObjectId; -use radicle::node::notifications::NotificationId; - pub mod common; #[cfg(feature = "realm")] @@ -16,45 +11,32 @@ pub mod realm; pub mod flux; /// An optional return value. +#[derive(Clone, Debug)] pub struct Exit { pub value: Option, } -/// Returned ids can be of type `ObjectId` or `NotificationId`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Id { - Object(ObjectId), - Notification(NotificationId), -} - -impl Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Id::Object(id) => { - write!(f, "{id}") - } - Id::Notification(id) => { - write!(f, "{id}") - } - } - } -} - /// The output that is returned by all selection interfaces. #[derive(Clone, Default, Debug, Eq, PartialEq)] -pub struct SelectionExit { - operation: Option, - ids: Vec, - args: Vec, +pub struct Selection +where + I: ToString, +{ + pub operation: Option, + pub ids: Vec, + pub args: Vec, } -impl SelectionExit { +impl Selection +where + I: ToString, +{ pub fn with_operation(mut self, operation: String) -> Self { self.operation = Some(operation); self } - pub fn with_id(mut self, id: Id) -> Self { + pub fn with_id(mut self, id: I) -> Self { self.ids.push(id); self } @@ -65,7 +47,10 @@ impl SelectionExit { } } -impl Serialize for SelectionExit { +impl Serialize for Selection +where + I: ToString, +{ fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -74,11 +59,7 @@ impl Serialize for SelectionExit { state.serialize_field("operation", &self.operation)?; state.serialize_field( "ids", - &self - .ids - .iter() - .map(|id| format!("{}", id)) - .collect::>(), + &self.ids.iter().map(|id| id.to_string()).collect::>(), )?; state.serialize_field("args", &self.args)?; state.end()