Open link popup via key binding

This commit is contained in:
Joscha 2022-08-30 00:30:08 +02:00
parent bb542ae08e
commit c09608d1f8
6 changed files with 123 additions and 4 deletions

View file

@ -106,6 +106,12 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
}
}
pub async fn cursor(&self) -> Option<M::Id> {
match self.mode {
Mode::Tree => self.tree.cursor().await,
}
}
/// A [`Reaction::Composed`] message was sent, either successfully or
/// unsuccessfully.
///

View file

@ -337,6 +337,13 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
}
}
fn cursor(&self) -> Option<M::Id> {
match &self.cursor {
Cursor::Msg(id) => Some(id.clone()),
Cursor::Bottom | Cursor::Editor { .. } | Cursor::Pseudo { .. } => None,
}
}
fn sent(&mut self, id: Option<M::Id>) {
if let Cursor::Pseudo { coming_from, .. } = &self.cursor {
if let Some(id) = id {
@ -385,6 +392,10 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
.await
}
pub async fn cursor(&self) -> Option<M::Id> {
self.0.lock().await.cursor()
}
pub async fn sent(&mut self, id: Option<M::Id>) {
self.0.lock().await.sent(id)
}

View file

@ -1,5 +1,6 @@
mod account;
mod auth;
mod links;
mod nick;
mod nick_list;
mod popup;

69
src/ui/euph/links.rs Normal file
View file

@ -0,0 +1,69 @@
use std::io;
use std::sync::Arc;
use crossterm::event::KeyCode;
use crossterm::style::{ContentStyle, Stylize};
use parking_lot::FairMutex;
use toss::terminal::Terminal;
use crate::euph::Room;
use crate::ui::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use crate::ui::widgets::list::ListState;
use crate::ui::widgets::popup::Popup;
use crate::ui::widgets::text::Text;
use crate::ui::widgets::BoxedWidget;
pub struct LinksState {
links: Vec<String>,
list: ListState<usize>,
}
pub enum EventResult {
NotHandled,
Handled,
Close,
ErrorOpeningLink { link: String, error: io::Error },
}
impl LinksState {
pub fn new(content: &str) -> Self {
// TODO Extract links
Self {
links: vec![
"https://example.com/".to_string(),
"https://plugh.de/".to_string(),
],
list: ListState::new(),
}
}
pub fn widget(&self) -> BoxedWidget {
let mut list = self.list.widget();
for (id, link) in self.links.iter().enumerate() {
list.add_sel(
id,
Text::new((link,)),
Text::new((link, ContentStyle::default().black().on_white())),
);
}
Popup::new(list).title("Links").build()
}
pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList) {
bindings.binding("esc", "close links popup")
}
pub fn handle_input_event(
&mut self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
room: &Option<Room>,
) -> EventResult {
match event {
key!(Esc) => EventResult::Close,
_ => EventResult::NotHandled,
}
}
}

View file

@ -7,7 +7,7 @@ use crate::ui::widgets::text::Text;
use crate::ui::widgets::BoxedWidget;
pub enum RoomPopup {
ServerError { description: String, reason: String },
Error { description: String, reason: String },
}
impl RoomPopup {
@ -26,7 +26,7 @@ impl RoomPopup {
pub fn widget(&self) -> BoxedWidget {
let widget = match self {
Self::ServerError {
Self::Error {
description,
reason,
} => Self::server_error_widget(description, reason),

View file

@ -29,6 +29,7 @@ use crate::ui::UiEvent;
use crate::vault::EuphVault;
use super::account::{self, AccountUiState};
use super::links::{self, LinksState};
use super::popup::RoomPopup;
use super::{auth, nick, nick_list};
@ -37,6 +38,7 @@ enum State {
Auth(EditorState),
Nick(EditorState),
Account(AccountUiState),
Links(LinksState),
}
#[allow(clippy::large_enum_variant)]
@ -222,6 +224,7 @@ impl EuphRoom {
State::Auth(editor) => layers.push(auth::widget(editor)),
State::Nick(editor) => layers.push(nick::widget(editor)),
State::Account(account) => layers.push(account.widget()),
State::Links(links) => layers.push(links.widget()),
}
for popup in &self.popups {
@ -316,6 +319,8 @@ impl EuphRoom {
false
};
bindings.binding("I", "show message links");
bindings.empty();
self.chat.list_key_bindings(bindings, can_compose).await;
}
@ -326,6 +331,15 @@ impl EuphRoom {
crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
) -> bool {
if let key!('I') = event {
if let Some(id) = self.chat.cursor().await {
if let Some(msg) = self.vault.msg(&id).await {
self.state = State::Links(LinksState::new(&msg.content));
}
}
return true;
}
if let Some(room) = &self.room {
let status = room.status().await;
let can_compose = matches!(status, Ok(Some(Status::Joined(_))));
@ -395,6 +409,7 @@ impl EuphRoom {
State::Auth(_) => auth::list_key_bindings(bindings),
State::Nick(_) => nick::list_key_bindings(bindings),
State::Account(account) => account.list_key_bindings(bindings),
State::Links(links) => links.list_key_bindings(bindings),
}
}
@ -449,6 +464,23 @@ impl EuphRoom {
}
}
}
State::Links(links) => {
match links.handle_input_event(terminal, crossterm_lock, event, &self.room) {
links::EventResult::NotHandled => false,
links::EventResult::Handled => true,
links::EventResult::Close => {
self.state = State::Normal;
true
}
links::EventResult::ErrorOpeningLink { link, error } => {
self.popups.push_front(RoomPopup::Error {
description: format!("Failed to open link: {link}"),
reason: format!("{error}"),
});
true
}
}
}
}
}
@ -489,7 +521,7 @@ impl EuphRoom {
if let Some((action, reason)) = error {
let description = format!("Failed to {action}.");
let reason = reason.unwrap_or_else(|| "no idea, the server wouldn't say".to_string());
self.popups.push_front(RoomPopup::ServerError {
self.popups.push_front(RoomPopup::Error {
description,
reason,
});
@ -522,7 +554,7 @@ impl EuphRoom {
_ => return false,
};
let description = format!("Failed to {action}.");
self.popups.push_front(RoomPopup::ServerError {
self.popups.push_front(RoomPopup::Error {
description,
reason,
});