mirror of
https://seed.radicle.dev/rad:z39mP9rQAaGmERfUMPULfPUi473tY.git
synced 2026-11-12 14:40:42 +08:00
lib: Add generic exit payload to flux app
This commit is contained in:
+24
-17
@@ -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<A> {
|
||||
type Exit;
|
||||
|
||||
pub trait State<A, P>
|
||||
where
|
||||
P: Clone + Debug + Send + Sync,
|
||||
{
|
||||
fn tick(&self);
|
||||
|
||||
fn handle_action(&mut self, action: A) -> Option<Self::Exit>;
|
||||
fn handle_action(&mut self, action: A) -> Option<Exit<P>>;
|
||||
}
|
||||
|
||||
pub struct Store<A, S>
|
||||
pub struct Store<A, S, P>
|
||||
where
|
||||
S: State<A> + Clone + Send + Sync,
|
||||
S: State<A, P> + Clone + Send + Sync,
|
||||
P: Clone + Debug + Send + Sync,
|
||||
{
|
||||
state_tx: UnboundedSender<S>,
|
||||
_phantom: PhantomData<A>,
|
||||
_phantom: PhantomData<(A, P)>,
|
||||
}
|
||||
|
||||
impl<A, S> Store<A, S>
|
||||
impl<A, S, P> Store<A, S, P>
|
||||
where
|
||||
S: State<A> + Clone + Send + Sync,
|
||||
S: State<A, P> + Clone + Send + Sync,
|
||||
P: Clone + Debug + Send + Sync,
|
||||
{
|
||||
pub fn new() -> (Self, UnboundedReceiver<S>) {
|
||||
let (state_tx, state_rx) = mpsc::unbounded_channel::<S>();
|
||||
@@ -42,17 +47,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> Store<A, S>
|
||||
impl<A, S, P> Store<A, S, P>
|
||||
where
|
||||
S: State<A> + Clone + Send + Sync + 'static + Debug,
|
||||
S: State<A, P> + 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<P>,
|
||||
mut action_rx: UnboundedReceiver<A>,
|
||||
mut interrupt_rx: broadcast::Receiver<Interrupted>,
|
||||
) -> anyhow::Result<Interrupted> {
|
||||
mut interrupt_rx: broadcast::Receiver<Interrupted<P>>,
|
||||
) -> anyhow::Result<Interrupted<P>> {
|
||||
// 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
|
||||
|
||||
+28
-11
@@ -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<P>
|
||||
where
|
||||
P: Clone + Send + Sync + Debug,
|
||||
{
|
||||
OsSignal,
|
||||
User { payload: Option<P> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Terminator {
|
||||
interrupt_tx: broadcast::Sender<Interrupted>,
|
||||
pub struct Terminator<P>
|
||||
where
|
||||
P: Clone + Send + Sync + Debug,
|
||||
{
|
||||
interrupt_tx: broadcast::Sender<Interrupted<P>>,
|
||||
}
|
||||
|
||||
impl Terminator {
|
||||
pub fn new(interrupt_tx: broadcast::Sender<Interrupted>) -> Self {
|
||||
impl<P> Terminator<P>
|
||||
where
|
||||
P: Clone + Send + Sync + Debug + 'static,
|
||||
{
|
||||
pub fn new(interrupt_tx: broadcast::Sender<Interrupted<P>>) -> Self {
|
||||
Self { interrupt_tx }
|
||||
}
|
||||
|
||||
pub fn terminate(&mut self, interrupted: Interrupted) -> anyhow::Result<()> {
|
||||
pub fn terminate(&mut self, interrupted: Interrupted<P>) -> 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<P>(mut terminator: Terminator<P>)
|
||||
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<Interrupted>) {
|
||||
pub fn create_termination<P>() -> (Terminator<P>, broadcast::Receiver<Interrupted<P>>)
|
||||
where
|
||||
P: Clone + Send + Sync + Debug + 'static,
|
||||
{
|
||||
let (tx, rx) = broadcast::channel(1);
|
||||
let terminator = Terminator::new(tx);
|
||||
|
||||
|
||||
+13
-5
@@ -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<A> Frontend<A> {
|
||||
(Self { action_tx }, action_rx)
|
||||
}
|
||||
|
||||
pub async fn main_loop<S: State<A>, W: Widget<S, A> + Render<()>>(
|
||||
pub async fn main_loop<S, W, P>(
|
||||
self,
|
||||
mut state_rx: UnboundedReceiver<S>,
|
||||
mut interrupt_rx: broadcast::Receiver<Interrupted>,
|
||||
) -> anyhow::Result<Interrupted> {
|
||||
mut interrupt_rx: broadcast::Receiver<Interrupted<P>>,
|
||||
) -> anyhow::Result<Interrupted<P>>
|
||||
where
|
||||
S: State<A, P>,
|
||||
W: Widget<S, A> + 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<A> Frontend<A> {
|
||||
};
|
||||
|
||||
// let mut last_frame: Option<CompletedFrame> = None;
|
||||
let result: anyhow::Result<Interrupted> = loop {
|
||||
let result: anyhow::Result<Interrupted<P>> = loop {
|
||||
tokio::select! {
|
||||
// Tick to terminate the select every N milliseconds
|
||||
_ = ticker.tick() => (),
|
||||
@@ -95,7 +101,9 @@ fn setup_terminal() -> anyhow::Result<Terminal<Backend>> {
|
||||
)?)
|
||||
}
|
||||
|
||||
fn restore_terminal(_terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
|
||||
fn restore_terminal(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
|
||||
// let size = terminal.get_frame().size();
|
||||
terminal.clear()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+3
-19
@@ -25,12 +25,9 @@ pub trait Widget<S, A> {
|
||||
}
|
||||
|
||||
pub trait Render<P> {
|
||||
fn render<B: ratatui::backend::Backend>(&mut self, frame: &mut Frame, area: Rect, props: P);
|
||||
fn render<B: ratatui::backend::Backend>(&self, frame: &mut Frame, area: Rect, props: P);
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub struct Shortcut {
|
||||
pub short: String,
|
||||
pub long: String,
|
||||
@@ -80,12 +77,7 @@ impl<S, A> Widget<S, A> for Shortcuts<A> {
|
||||
}
|
||||
|
||||
impl<A> Render<ShortcutsProps> for Shortcuts<A> {
|
||||
fn render<B: Backend>(
|
||||
&mut self,
|
||||
frame: &mut ratatui::Frame,
|
||||
area: Rect,
|
||||
props: ShortcutsProps,
|
||||
) {
|
||||
fn render<B: Backend>(&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<A> Render<ShortcutsProps> for Shortcuts<A> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub trait ToRow<const W: usize> {
|
||||
fn to_row(&self) -> [Cell; W];
|
||||
}
|
||||
@@ -204,12 +193,7 @@ impl<'a, A, R, const W: usize> Render<TableProps<'a, R, W>> for Table<A>
|
||||
where
|
||||
R: ToRow<W> + Debug,
|
||||
{
|
||||
fn render<B: Backend>(
|
||||
&mut self,
|
||||
frame: &mut ratatui::Frame,
|
||||
area: Rect,
|
||||
props: TableProps<R, W>,
|
||||
) {
|
||||
fn render<B: Backend>(&self, frame: &mut ratatui::Frame, area: Rect, props: TableProps<R, W>) {
|
||||
let layout = if props.footer.is_some() {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
||||
+18
-37
@@ -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<T> {
|
||||
pub value: Option<T>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
ids: Vec<Id>,
|
||||
args: Vec<String>,
|
||||
pub struct Selection<I>
|
||||
where
|
||||
I: ToString,
|
||||
{
|
||||
pub operation: Option<String>,
|
||||
pub ids: Vec<I>,
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
impl SelectionExit {
|
||||
impl<I> Selection<I>
|
||||
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<I> Serialize for Selection<I>
|
||||
where
|
||||
I: ToString,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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::<Vec<_>>(),
|
||||
&self.ids.iter().map(|id| id.to_string()).collect::<Vec<_>>(),
|
||||
)?;
|
||||
state.serialize_field("args", &self.args)?;
|
||||
state.end()
|
||||
|
||||
Reference in New Issue
Block a user