forked from trinitrix/core
Refactor(events): Split up event handling into multiple files
This commit is contained in:
parent
05d4b4d097
commit
ef5afcda02
384
src/app/event.rs
384
src/app/event.rs
|
@ -1,384 +0,0 @@
|
||||||
use anyhow::{Error, Result};
|
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
use matrix_sdk::{config::SyncSettings, Client, LoopCtrl};
|
|
||||||
use tokio::{sync::mpsc, time::Duration};
|
|
||||||
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app::{status::State, App},
|
|
||||||
ui,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventStatus {
|
|
||||||
Ok,
|
|
||||||
Finished,
|
|
||||||
Terminate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Event {
|
|
||||||
input_event: Option<crossterm::event::Event>,
|
|
||||||
matrix_event: Option<matrix_sdk::deserialized_responses::SyncResponse>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventBuilder {
|
|
||||||
event: Event,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Event {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
input_event: None,
|
|
||||||
matrix_event: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EventBuilder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
event: Event::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventBuilder {
|
|
||||||
fn input_event(&mut self, input_event: crossterm::event::Event) -> &Self {
|
|
||||||
self.event.input_event = Some(input_event);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matrix_event(
|
|
||||||
&mut self,
|
|
||||||
matrix_event: matrix_sdk::deserialized_responses::SyncResponse,
|
|
||||||
) -> &Self {
|
|
||||||
self.event.matrix_event = Some(matrix_event);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&self) -> Event {
|
|
||||||
Event {
|
|
||||||
input_event: self.event.input_event.clone(),
|
|
||||||
matrix_event: self.event.matrix_event.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event {
|
|
||||||
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
|
||||||
if self.matrix_event.is_some() {
|
|
||||||
return self.handle_matrix(app).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = match app.status.state() {
|
|
||||||
State::None => EventStatus::Ok,
|
|
||||||
State::Main => self.handle_main(app).await?,
|
|
||||||
State::Setup => self.handle_setup(app).await?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_matrix(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
|
||||||
let sync = self.matrix_event.clone().unwrap();
|
|
||||||
for (m_room_id, m_room) in sync.rooms.join.iter() {
|
|
||||||
let room = match app.status.get_room_mut(m_room_id) {
|
|
||||||
Some(r) => r,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
for m_event in m_room.timeline.events.clone() {
|
|
||||||
let event = m_event
|
|
||||||
.event
|
|
||||||
.deserialize()
|
|
||||||
.unwrap()
|
|
||||||
.into_full_event(m_room_id.clone());
|
|
||||||
room.timeline_add(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_main(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
|
||||||
if self.input_event.is_some() {
|
|
||||||
match self.input_event.clone().unwrap() {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Esc, ..
|
|
||||||
}) => return Ok(EventStatus::Terminate),
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Tab, ..
|
|
||||||
}) => {
|
|
||||||
app.ui.cycle_main_input_position();
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::BackTab,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
app.ui.cycle_main_input_position_rev();
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('c'),
|
|
||||||
modifiers: KeyModifiers::CONTROL,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
app.ui.cli_enable();
|
|
||||||
}
|
|
||||||
input => match app.ui.input_position() {
|
|
||||||
ui::MainInputPosition::MessageCompose => {
|
|
||||||
match input {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Enter,
|
|
||||||
modifiers: KeyModifiers::ALT,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
match app.status.room_mut() {
|
|
||||||
Some(room) => {
|
|
||||||
room.send(app.ui.message_compose.lines().join("\n"))
|
|
||||||
.await?;
|
|
||||||
app.ui.message_compose_clear();
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
app.ui
|
|
||||||
.message_compose
|
|
||||||
.input(tui_textarea::Input::from(input));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ui::MainInputPosition::Rooms => {
|
|
||||||
match input {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Up, ..
|
|
||||||
}) => {
|
|
||||||
let i = match app.ui.rooms_state.selected() {
|
|
||||||
Some(cur) => {
|
|
||||||
if cur > 0 {
|
|
||||||
cur - 1
|
|
||||||
} else {
|
|
||||||
cur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
app.ui.rooms_state.select(Some(i));
|
|
||||||
app.status.set_room_by_index(i)?;
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Down,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let i = match app.ui.rooms_state.selected() {
|
|
||||||
Some(cur) => {
|
|
||||||
if cur < app.status.rooms().len() - 1 {
|
|
||||||
cur + 1
|
|
||||||
} else {
|
|
||||||
cur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
app.ui.rooms_state.select(Some(i));
|
|
||||||
app.status.set_room_by_index(i)?;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ui::MainInputPosition::Messages => {
|
|
||||||
match input {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Up, ..
|
|
||||||
}) => {
|
|
||||||
match app.status.room_mut() {
|
|
||||||
Some(room) => {
|
|
||||||
let len = room.timeline().len();
|
|
||||||
let i = match room.view_scroll() {
|
|
||||||
Some(i) => i + 1,
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
if i < len {
|
|
||||||
room.set_view_scroll(Some(i))
|
|
||||||
}
|
|
||||||
if i <= len - 5 {
|
|
||||||
room.poll_old_timeline().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Down,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
match app.status.room_mut() {
|
|
||||||
Some(room) => {
|
|
||||||
match room.view_scroll() {
|
|
||||||
Some(i) => {
|
|
||||||
if i == 0 {
|
|
||||||
room.set_view_scroll(None);
|
|
||||||
} else {
|
|
||||||
room.set_view_scroll(Some(i - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ui::MainInputPosition::CLI => {
|
|
||||||
if let Some(cli) = &mut app.ui.cli {
|
|
||||||
match input {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Enter,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let cli_event = cli.lines()[0].clone();
|
|
||||||
app.status.cli_event(cli_event);
|
|
||||||
app.ui.cli_disable();
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
cli.input(tui_textarea::Input::from(input));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_setup(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
|
||||||
let ui = match &mut app.ui.setup_ui {
|
|
||||||
Some(ui) => ui,
|
|
||||||
None => return Err(Error::msg("SetupUI instance not found")),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.input_event.is_some() {
|
|
||||||
match self.input_event.clone().unwrap() {
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Esc, ..
|
|
||||||
}) => return Ok(EventStatus::Terminate),
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Tab, ..
|
|
||||||
}) => {
|
|
||||||
ui.cycle_input_position();
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::BackTab,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
ui.cycle_input_position_rev();
|
|
||||||
}
|
|
||||||
crossterm::event::Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Enter,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
match ui.input_position() {
|
|
||||||
ui::SetupInputPosition::Ok => {
|
|
||||||
let homeserver = ui.homeserver.lines()[0].clone();
|
|
||||||
let username = ui.username.lines()[0].clone();
|
|
||||||
let password = ui.password_data.lines()[0].clone();
|
|
||||||
let login = app.login(&homeserver, &username, &password).await;
|
|
||||||
if login.is_ok() {
|
|
||||||
return Ok(EventStatus::Finished);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => ui.cycle_input_position(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
input => match ui.input_position() {
|
|
||||||
ui::SetupInputPosition::Homeserver => {
|
|
||||||
ui.homeserver.input(input);
|
|
||||||
}
|
|
||||||
ui::SetupInputPosition::Username => {
|
|
||||||
ui.username.input(input);
|
|
||||||
}
|
|
||||||
ui::SetupInputPosition::Password => {
|
|
||||||
let textarea_input = tui_textarea::Input::from(input);
|
|
||||||
ui.password_data.input(textarea_input.clone());
|
|
||||||
match textarea_input.key {
|
|
||||||
tui_textarea::Key::Char(_) => {
|
|
||||||
ui.password.input(tui_textarea::Input {
|
|
||||||
key: tui_textarea::Key::Char('*'),
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ui.password.input(textarea_input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn poll_input_events_stage_2(channel: mpsc::Sender<Event>) -> Result<()> {
|
|
||||||
loop {
|
|
||||||
if crossterm::event::poll(Duration::from_millis(100))? {
|
|
||||||
let event = EventBuilder::default()
|
|
||||||
.input_event(crossterm::event::read()?)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
channel.send(event).await?;
|
|
||||||
} else {
|
|
||||||
tokio::task::yield_now().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn poll_input_events(
|
|
||||||
channel: mpsc::Sender<Event>,
|
|
||||||
kill: CancellationToken,
|
|
||||||
) -> Result<()> {
|
|
||||||
tokio::select! {
|
|
||||||
output = poll_input_events_stage_2(channel) => output,
|
|
||||||
_ = kill.cancelled() => Err(Error::msg("received kill signal"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn poll_matrix_events_stage_2(channel: mpsc::Sender<Event>, client: Client) -> Result<()> {
|
|
||||||
let sync_settings = SyncSettings::default();
|
|
||||||
// .token(sync_token)
|
|
||||||
// .timeout(Duration::from_secs(30));
|
|
||||||
|
|
||||||
let tx = &channel;
|
|
||||||
|
|
||||||
client
|
|
||||||
.sync_with_callback(sync_settings, |response| async move {
|
|
||||||
let event = EventBuilder::default().matrix_event(response).build();
|
|
||||||
|
|
||||||
match tx.send(event).await {
|
|
||||||
Ok(_) => LoopCtrl::Continue,
|
|
||||||
Err(_) => LoopCtrl::Break,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn poll_matrix_events(
|
|
||||||
channel: mpsc::Sender<Event>,
|
|
||||||
kill: CancellationToken,
|
|
||||||
client: Client,
|
|
||||||
) -> Result<()> {
|
|
||||||
tokio::select! {
|
|
||||||
output = poll_matrix_events_stage_2(channel, client) => output,
|
|
||||||
_ = kill.cancelled() => Err(Error::msg("received kill signal")),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{events::event_types::EventStatus, App},
|
||||||
|
ui,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
||||||
|
match input_event {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Esc, ..
|
||||||
|
}) => return Ok(EventStatus::Terminate),
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Tab, ..
|
||||||
|
}) => {
|
||||||
|
app.ui.cycle_main_input_position();
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::BackTab,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
app.ui.cycle_main_input_position_rev();
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('c'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
app.ui.cli_enable();
|
||||||
|
}
|
||||||
|
input => match app.ui.input_position() {
|
||||||
|
ui::MainInputPosition::MessageCompose => {
|
||||||
|
match input {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Enter,
|
||||||
|
modifiers: KeyModifiers::ALT,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
match app.status.room_mut() {
|
||||||
|
Some(room) => {
|
||||||
|
room.send(app.ui.message_compose.lines().join("\n")).await?;
|
||||||
|
app.ui.message_compose_clear();
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
app.ui
|
||||||
|
.message_compose
|
||||||
|
.input(tui_textarea::Input::from(input.to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ui::MainInputPosition::Rooms => {
|
||||||
|
match input {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Up, ..
|
||||||
|
}) => {
|
||||||
|
let i = match app.ui.rooms_state.selected() {
|
||||||
|
Some(cur) => {
|
||||||
|
if cur > 0 {
|
||||||
|
cur - 1
|
||||||
|
} else {
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
app.ui.rooms_state.select(Some(i));
|
||||||
|
app.status.set_room_by_index(i)?;
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Down,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let i = match app.ui.rooms_state.selected() {
|
||||||
|
Some(cur) => {
|
||||||
|
if cur < app.status.rooms().len() - 1 {
|
||||||
|
cur + 1
|
||||||
|
} else {
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
app.ui.rooms_state.select(Some(i));
|
||||||
|
app.status.set_room_by_index(i)?;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ui::MainInputPosition::Messages => {
|
||||||
|
match input {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Up, ..
|
||||||
|
}) => {
|
||||||
|
match app.status.room_mut() {
|
||||||
|
Some(room) => {
|
||||||
|
let len = room.timeline().len();
|
||||||
|
let i = match room.view_scroll() {
|
||||||
|
Some(i) => i + 1,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
if i < len {
|
||||||
|
room.set_view_scroll(Some(i))
|
||||||
|
}
|
||||||
|
if i <= len - 5 {
|
||||||
|
room.poll_old_timeline().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Down,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
match app.status.room_mut() {
|
||||||
|
Some(room) => {
|
||||||
|
match room.view_scroll() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
room.set_view_scroll(None);
|
||||||
|
} else {
|
||||||
|
room.set_view_scroll(Some(i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ui::MainInputPosition::CLI => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use matrix_sdk::deserialized_responses::SyncResponse;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::app::{events::event_types::EventStatus, App};
|
||||||
|
|
||||||
|
pub async fn handle<'a>(app: &mut App<'a>, sync: &SyncResponse) -> Result<EventStatus> {
|
||||||
|
for (m_room_id, m_room) in sync.rooms.join.iter() {
|
||||||
|
let room = match app.status.get_room_mut(m_room_id) {
|
||||||
|
Some(r) => r,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
for m_event in m_room.timeline.events.clone() {
|
||||||
|
let event = m_event
|
||||||
|
.event
|
||||||
|
.deserialize()
|
||||||
|
.unwrap()
|
||||||
|
.into_full_event(m_room_id.clone());
|
||||||
|
room.timeline_add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod matrix;
|
||||||
|
pub mod setup;
|
||||||
|
pub mod main;
|
|
@ -0,0 +1,75 @@
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{events::event_types::EventStatus, App},
|
||||||
|
ui,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
||||||
|
let ui = match &mut app.ui.setup_ui {
|
||||||
|
Some(ui) => ui,
|
||||||
|
None => bail!("SetupUI instance not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
match input_event {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Esc, ..
|
||||||
|
}) => return Ok(EventStatus::Terminate),
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Tab, ..
|
||||||
|
}) => {
|
||||||
|
ui.cycle_input_position();
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::BackTab,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
ui.cycle_input_position_rev();
|
||||||
|
}
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Enter,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
match ui.input_position() {
|
||||||
|
ui::SetupInputPosition::Ok => {
|
||||||
|
let homeserver = ui.homeserver.lines()[0].clone();
|
||||||
|
let username = ui.username.lines()[0].clone();
|
||||||
|
let password = ui.password_data.lines()[0].clone();
|
||||||
|
app.login(&homeserver, &username, &password)
|
||||||
|
.await
|
||||||
|
.context("Failed to login")?;
|
||||||
|
// We bailed in the line above, thus login must have succeeded
|
||||||
|
return Ok(EventStatus::Finished);
|
||||||
|
}
|
||||||
|
_ => ui.cycle_input_position(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
input => match ui.input_position() {
|
||||||
|
ui::SetupInputPosition::Homeserver => {
|
||||||
|
ui.homeserver.input(input.to_owned());
|
||||||
|
}
|
||||||
|
ui::SetupInputPosition::Username => {
|
||||||
|
ui.username.input(input.to_owned());
|
||||||
|
}
|
||||||
|
ui::SetupInputPosition::Password => {
|
||||||
|
let textarea_input = tui_textarea::Input::from(input.to_owned());
|
||||||
|
ui.password_data.input(textarea_input.clone());
|
||||||
|
match textarea_input.key {
|
||||||
|
tui_textarea::Key::Char(_) => {
|
||||||
|
ui.password.input(tui_textarea::Input {
|
||||||
|
key: tui_textarea::Key::Char('*'),
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ui.password.input(textarea_input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
mod handlers;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
|
|
||||||
|
use crate::app::{status::State, App};
|
||||||
|
|
||||||
|
use self::handlers::{main, matrix, setup};
|
||||||
|
|
||||||
|
use super::EventStatus;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Event {
|
||||||
|
pub(super) input_event: Option<CrosstermEvent>,
|
||||||
|
pub(super) matrix_event: Option<matrix_sdk::deserialized_responses::SyncResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
|
if let Some(matrix_event) = &self.matrix_event {
|
||||||
|
return matrix::handle(app, matrix_event)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", matrix_event));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(input_event) = &self.input_event {
|
||||||
|
let status = match app.status.state() {
|
||||||
|
State::None => EventStatus::Ok,
|
||||||
|
State::Main => main::handle(app, input_event).await.with_context(|| {
|
||||||
|
format!("Failed to handle input event: `{:#?}`", input_event)
|
||||||
|
})?,
|
||||||
|
State::Setup => setup::handle(app, input_event).await.with_context(|| {
|
||||||
|
format!("Failed to handle input event: `{:#?}`", input_event)
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
return Ok(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
use super::Event;
|
||||||
|
|
||||||
|
pub struct EventBuilder {
|
||||||
|
event: Event,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Event {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
input_event: None,
|
||||||
|
matrix_event: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EventBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
event: Event::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventBuilder {
|
||||||
|
pub fn input_event(&mut self, input_event: crossterm::event::Event) -> &Self {
|
||||||
|
self.event.input_event = Some(input_event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matrix_event(
|
||||||
|
&mut self,
|
||||||
|
matrix_event: matrix_sdk::deserialized_responses::SyncResponse,
|
||||||
|
) -> &Self {
|
||||||
|
self.event.matrix_event = Some(matrix_event);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Event {
|
||||||
|
Event {
|
||||||
|
input_event: self.event.input_event.to_owned(),
|
||||||
|
matrix_event: self.event.matrix_event.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EventStatus {
|
||||||
|
Ok,
|
||||||
|
Finished,
|
||||||
|
Terminate,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod event_builder;
|
||||||
|
pub mod event;
|
||||||
|
pub mod event_status;
|
||||||
|
|
||||||
|
pub use self::event_builder::*;
|
||||||
|
pub use self::event::*;
|
||||||
|
pub use self::event_status::*;
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
pub mod event_types;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use matrix_sdk::{config::SyncSettings, Client, LoopCtrl};
|
||||||
|
use tokio::{sync::mpsc, time::Duration};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::app::events::event_types::EventBuilder;
|
||||||
|
|
||||||
|
use self::event_types::Event;
|
||||||
|
|
||||||
|
pub async fn poll_input_events(
|
||||||
|
channel: mpsc::Sender<Event>,
|
||||||
|
kill: CancellationToken,
|
||||||
|
) -> Result<()> {
|
||||||
|
async fn poll_input_events_stage_2(channel: mpsc::Sender<Event>) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
if crossterm::event::poll(Duration::from_millis(100))? {
|
||||||
|
let event = EventBuilder::default()
|
||||||
|
.input_event(crossterm::event::read()?)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
channel.send(event).await?;
|
||||||
|
} else {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
output = poll_input_events_stage_2(channel) => output,
|
||||||
|
_ = kill.cancelled() => bail!("received kill signal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn poll_matrix_events(
|
||||||
|
channel: mpsc::Sender<Event>,
|
||||||
|
kill: CancellationToken,
|
||||||
|
client: Client,
|
||||||
|
) -> Result<()> {
|
||||||
|
async fn poll_matrix_events_stage_2(
|
||||||
|
channel: mpsc::Sender<Event>,
|
||||||
|
client: Client,
|
||||||
|
) -> Result<()> {
|
||||||
|
let sync_settings = SyncSettings::default();
|
||||||
|
// .token(sync_token)
|
||||||
|
// .timeout(Duration::from_secs(30));
|
||||||
|
|
||||||
|
let tx = &channel;
|
||||||
|
|
||||||
|
client
|
||||||
|
.sync_with_callback(sync_settings, |response| async move {
|
||||||
|
let event = EventBuilder::default().matrix_event(response).build();
|
||||||
|
|
||||||
|
match tx.send(event).await {
|
||||||
|
Ok(_) => LoopCtrl::Continue,
|
||||||
|
Err(_) => LoopCtrl::Break,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
output = poll_matrix_events_stage_2(channel, client) => output,
|
||||||
|
_ = kill.cancelled() => bail!("received kill signal"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +1,33 @@
|
||||||
pub mod event;
|
pub mod command_interface;
|
||||||
|
pub mod events;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use accounts::{Account, AccountsManager};
|
use accounts::{Account, AccountsManager};
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
use matrix_sdk::Client;
|
use matrix_sdk::Client;
|
||||||
|
use rlua::Lua;
|
||||||
use status::{State, Status};
|
use status::{State, Status};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{accounts, ui};
|
use crate::{accounts, app::command_interface::generate_ci_functions, ui};
|
||||||
|
|
||||||
pub struct App<'a> {
|
use self::events::event_types;
|
||||||
ui: ui::UI<'a>,
|
|
||||||
|
pub struct App<'ui> {
|
||||||
|
ui: ui::UI<'ui>,
|
||||||
accounts_manager: accounts::AccountsManager,
|
accounts_manager: accounts::AccountsManager,
|
||||||
status: Status,
|
status: Status,
|
||||||
|
|
||||||
channel_tx: mpsc::Sender<event::Event>,
|
channel_tx: mpsc::Sender<event_types::Event>,
|
||||||
channel_rx: mpsc::Receiver<event::Event>,
|
channel_rx: mpsc::Receiver<event_types::Event>,
|
||||||
input_listener_killer: CancellationToken,
|
input_listener_killer: CancellationToken,
|
||||||
matrix_listener_killer: CancellationToken,
|
matrix_listener_killer: CancellationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for App<'_> {
|
|
||||||
fn drop(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
||||||
|
@ -54,7 +54,7 @@ impl App<'_> {
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
// Spawn input event listener
|
// Spawn input event listener
|
||||||
tokio::task::spawn(event::poll_input_events(
|
tokio::task::spawn(events::poll_input_events(
|
||||||
self.channel_tx.clone(),
|
self.channel_tx.clone(),
|
||||||
self.input_listener_killer.clone(),
|
self.input_listener_killer.clone(),
|
||||||
));
|
));
|
||||||
|
@ -71,14 +71,14 @@ impl App<'_> {
|
||||||
self.status.set_state(State::Main);
|
self.status.set_state(State::Main);
|
||||||
self.ui.update(&self.status).await?;
|
self.ui.update(&self.status).await?;
|
||||||
|
|
||||||
let event: event::Event = match self.channel_rx.recv().await {
|
let event: event_types::Event = match self.channel_rx.recv().await {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => return Err(Error::msg("Event channel has no senders")),
|
None => return Err(Error::msg("Event channel has no senders")),
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
event::EventStatus::Ok => (),
|
event_types::EventStatus::Ok => (),
|
||||||
event::EventStatus::Terminate => break,
|
event_types::EventStatus::Terminate => break,
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -94,15 +94,15 @@ impl App<'_> {
|
||||||
self.status.set_state(State::Setup);
|
self.status.set_state(State::Setup);
|
||||||
self.ui.update_setup().await?;
|
self.ui.update_setup().await?;
|
||||||
|
|
||||||
let event: event::Event = match self.channel_rx.recv().await {
|
let event: event_types::Event = match self.channel_rx.recv().await {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => return Err(Error::msg("Event channel has no senders")),
|
None => return Err(Error::msg("Event channel has no senders")),
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
event::EventStatus::Ok => (),
|
event_types::EventStatus::Ok => (),
|
||||||
event::EventStatus::Finished => return Ok(()),
|
event_types::EventStatus::Finished => return Ok(()),
|
||||||
event::EventStatus::Terminate => return Err(Error::msg("Terminated by user")),
|
event_types::EventStatus::Terminate => return Err(Error::msg("Terminated by user")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ impl App<'_> {
|
||||||
self.matrix_listener_killer = CancellationToken::new();
|
self.matrix_listener_killer = CancellationToken::new();
|
||||||
|
|
||||||
// Spawn Matrix Event Listener
|
// Spawn Matrix Event Listener
|
||||||
tokio::task::spawn(event::poll_matrix_events(
|
tokio::task::spawn(events::poll_matrix_events(
|
||||||
self.channel_tx.clone(),
|
self.channel_tx.clone(),
|
||||||
self.matrix_listener_killer.clone(),
|
self.matrix_listener_killer.clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
|
|
|
@ -204,8 +204,4 @@ impl Status {
|
||||||
pub fn set_state(&mut self, state: State) {
|
pub fn set_state(&mut self, state: State) {
|
||||||
self.state = state;
|
self.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cli_event(&mut self, event: String) {
|
|
||||||
info!("CLI Event: {}", event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue