Open link popup via key binding
This commit is contained in:
parent
bb542ae08e
commit
c09608d1f8
6 changed files with 123 additions and 4 deletions
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
69
src/ui/euph/links.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue