Add inline editor for connecting to new rooms

This commit is contained in:
Joscha 2022-07-23 22:09:05 +02:00
parent 709ab07442
commit f1899ab295
2 changed files with 102 additions and 56 deletions

View file

@ -15,20 +15,29 @@ use crate::vault::Vault;
use super::room::EuphRoom; use super::room::EuphRoom;
use super::widgets::background::Background; use super::widgets::background::Background;
use super::widgets::border::Border;
use super::widgets::editor::EditorState;
use super::widgets::float::Float;
use super::widgets::join::{HJoin, Segment, VJoin};
use super::widgets::layer::Layer;
use super::widgets::list::{List, ListState}; use super::widgets::list::{List, ListState};
use super::widgets::text::Text; use super::widgets::text::Text;
use super::widgets::BoxedWidget; use super::widgets::BoxedWidget;
use super::{util, UiEvent}; use super::UiEvent;
enum State {
ShowList,
ShowRoom(String),
Connect(EditorState),
}
pub struct Rooms { pub struct Rooms {
vault: Vault, vault: Vault,
ui_event_tx: mpsc::UnboundedSender<UiEvent>, ui_event_tx: mpsc::UnboundedSender<UiEvent>,
state: State,
list: ListState<String>, list: ListState<String>,
/// If set, a single room is displayed in full instead of the room list.
focus: Option<String>,
euph_rooms: HashMap<String, EuphRoom>, euph_rooms: HashMap<String, EuphRoom>,
} }
@ -37,8 +46,8 @@ impl Rooms {
Self { Self {
vault, vault,
ui_event_tx, ui_event_tx,
state: State::ShowList,
list: ListState::new(), list: ListState::new(),
focus: None,
euph_rooms: HashMap::new(), euph_rooms: HashMap::new(),
} }
} }
@ -48,16 +57,23 @@ impl Rooms {
/// These kinds of rooms are either /// These kinds of rooms are either
/// - failed connection attempts, or /// - failed connection attempts, or
/// - rooms that were deleted from the db. /// - rooms that were deleted from the db.
async fn stabilize_rooms(&mut self) -> Vec<String> { async fn stabilize_rooms(&mut self) {
let mut rooms = self.vault.euph_rooms().await; let rooms_set = self
let rooms_set = rooms.iter().map(|n| n as &str).collect::<HashSet<_>>(); .vault
.euph_rooms()
.await
.into_iter()
.collect::<HashSet<_>>();
self.euph_rooms self.euph_rooms
.retain(|n, r| !r.stopped() || rooms_set.contains(n as &str)); .retain(|n, r| !r.stopped() || rooms_set.contains(n));
for room in self.euph_rooms.values_mut() { for room in self.euph_rooms.values_mut() {
room.retain(); room.retain();
} }
}
async fn room_names(&self) -> Vec<String> {
let mut rooms = self.vault.euph_rooms().await;
for room in self.euph_rooms.keys() { for room in self.euph_rooms.keys() {
rooms.push(room.clone()); rooms.push(room.clone());
} }
@ -66,14 +82,38 @@ impl Rooms {
rooms rooms
} }
fn get_or_insert_room(&mut self, name: String) -> &mut EuphRoom {
self.euph_rooms
.entry(name.clone())
.or_insert_with(|| EuphRoom::new(self.vault.euph(name), self.ui_event_tx.clone()))
}
pub async fn widget(&mut self) -> BoxedWidget { pub async fn widget(&mut self) -> BoxedWidget {
if let Some(room) = &self.focus { match &self.state {
let actual_room = self.euph_rooms.entry(room.clone()).or_insert_with(|| { State::ShowRoom(_) => {}
EuphRoom::new(self.vault.euph(room.clone()), self.ui_event_tx.clone()) _ => self.stabilize_rooms().await,
}); }
actual_room.widget().await
} else { match &self.state {
self.rooms_widget().await State::ShowList => self.rooms_widget().await,
State::ShowRoom(name) => self.get_or_insert_room(name.clone()).widget().await,
State::Connect(ed) => {
let room_style = ContentStyle::default().bold().blue();
Layer::new(vec![
self.rooms_widget().await,
Float::new(Border::new(VJoin::new(vec![
Segment::new(Text::new("Connect to ")),
Segment::new(HJoin::new(vec![
Segment::new(Text::new(("&", room_style))),
Segment::new(ed.widget().highlight(|s| Styled::new((s, room_style)))),
])),
])))
.horizontal(0.5)
.vertical(0.5)
.into(),
])
.into()
}
} }
} }
@ -147,8 +187,8 @@ impl Rooms {
} }
} }
async fn rooms_widget(&mut self) -> BoxedWidget { async fn rooms_widget(&self) -> BoxedWidget {
let rooms = self.stabilize_rooms().await; let rooms = self.room_names().await;
let mut list = self.list.widget().focus(true); let mut list = self.list.widget().focus(true);
self.render_rows(&mut list, rooms).await; self.render_rows(&mut list, rooms).await;
list.into() list.into()
@ -160,57 +200,60 @@ impl Rooms {
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: KeyEvent,
) { ) {
if let Some(room) = &self.focus { match &self.state {
if event.code == KeyCode::Enter { State::ShowList => match event.code {
self.focus = None; KeyCode::Enter => {
} else { if let Some(name) = self.list.cursor() {
let actual_room = self.euph_rooms.entry(room.clone()).or_insert_with(|| { self.state = State::ShowRoom(name);
EuphRoom::new(self.vault.euph(room.clone()), self.ui_event_tx.clone()) }
});
actual_room
.handle_key_event(terminal, crossterm_lock, event)
.await;
} }
} else {
match event.code {
KeyCode::Enter => self.focus = self.list.cursor(),
KeyCode::Char('j') | KeyCode::Down => self.list.move_cursor_down(), KeyCode::Char('j') | KeyCode::Down => self.list.move_cursor_down(),
KeyCode::Char('k') | KeyCode::Up => self.list.move_cursor_up(), KeyCode::Char('k') | KeyCode::Up => self.list.move_cursor_up(),
KeyCode::Char('J') => self.list.scroll_down(), // TODO Replace by Ctrl+E and mouse scroll KeyCode::Char('J') => self.list.scroll_down(), // TODO Replace by Ctrl+E and mouse scroll
KeyCode::Char('K') => self.list.scroll_up(), // TODO Replace by Ctrl+Y and mouse scroll KeyCode::Char('K') => self.list.scroll_up(), // TODO Replace by Ctrl+Y and mouse scroll
KeyCode::Char('c') => { KeyCode::Char('c') => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.cursor() {
let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| { self.get_or_insert_room(name).connect();
EuphRoom::new(self.vault.euph(name.clone()), self.ui_event_tx.clone())
});
room.connect();
}
}
KeyCode::Char('C') => {
if let Some(name) = util::prompt(terminal, crossterm_lock) {
let name = name.trim().to_string();
let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| {
EuphRoom::new(self.vault.euph(name), self.ui_event_tx.clone())
});
room.connect();
} }
} }
KeyCode::Char('C') => self.state = State::Connect(EditorState::new()),
KeyCode::Char('d') => { KeyCode::Char('d') => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.cursor() {
let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| { self.get_or_insert_room(name).disconnect();
EuphRoom::new(self.vault.euph(name.clone()), self.ui_event_tx.clone())
});
room.disconnect();
} }
} }
KeyCode::Char('D') => { KeyCode::Char('D') => {
// TODO Check whether user wanted this via popup
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.cursor() {
self.euph_rooms.remove(&name); self.euph_rooms.remove(&name);
self.vault.euph(name.clone()).delete(); self.vault.euph(name.clone()).delete();
} }
} }
_ => {} _ => {}
},
State::ShowRoom(_) if event.code == KeyCode::Esc => self.state = State::ShowList,
State::ShowRoom(name) => {
self.get_or_insert_room(name.clone())
.handle_key_event(terminal, crossterm_lock, event)
.await
} }
State::Connect(ed) => match event.code {
KeyCode::Esc => self.state = State::ShowList,
KeyCode::Enter => {
let text = ed.text();
let name = text.trim();
if !name.is_empty() {
self.get_or_insert_room(name.to_string()).connect();
self.state = State::ShowRoom(name.to_string());
}
}
KeyCode::Backspace => ed.backspace(),
KeyCode::Left => ed.move_cursor_left(),
KeyCode::Right => ed.move_cursor_right(),
KeyCode::Delete => ed.delete(),
KeyCode::Char(ch) => ed.insert_char(ch),
_ => {}
},
} }
} }
} }

View file

@ -193,26 +193,29 @@ impl EditorState {
} }
} }
fn insert_char(&self, ch: char) { pub fn text(&self) -> String {
self.0.lock().text.clone()
}
pub fn insert_char(&self, ch: char) {
self.0.lock().insert_char(ch); self.0.lock().insert_char(ch);
} }
/// Delete the grapheme before the cursor position. /// Delete the grapheme before the cursor position.
fn backspace(&self) { pub fn backspace(&self) {
self.0.lock().backspace(); self.0.lock().backspace();
} }
/// Delete the grapheme after the cursor position. /// Delete the grapheme after the cursor position.
fn delete(&self) { pub fn delete(&self) {
self.0.lock().delete(); self.0.lock().delete();
} }
// TODO Un-mut pub fn move_cursor_left(&self) {
fn move_cursor_left(&mut self) {
self.0.lock().move_cursor_left(); self.0.lock().move_cursor_left();
} }
fn move_cursor_right(&self) { pub fn move_cursor_right(&self) {
self.0.lock().move_cursor_right(); self.0.lock().move_cursor_right();
} }
} }