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
|
/// A [`Reaction::Composed`] message was sent, either successfully or
|
||||||
/// unsuccessfully.
|
/// 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>) {
|
fn sent(&mut self, id: Option<M::Id>) {
|
||||||
if let Cursor::Pseudo { coming_from, .. } = &self.cursor {
|
if let Cursor::Pseudo { coming_from, .. } = &self.cursor {
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
|
|
@ -385,6 +392,10 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn cursor(&self) -> Option<M::Id> {
|
||||||
|
self.0.lock().await.cursor()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn sent(&mut self, id: Option<M::Id>) {
|
pub async fn sent(&mut self, id: Option<M::Id>) {
|
||||||
self.0.lock().await.sent(id)
|
self.0.lock().await.sent(id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod account;
|
mod account;
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod links;
|
||||||
mod nick;
|
mod nick;
|
||||||
mod nick_list;
|
mod nick_list;
|
||||||
mod popup;
|
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;
|
use crate::ui::widgets::BoxedWidget;
|
||||||
|
|
||||||
pub enum RoomPopup {
|
pub enum RoomPopup {
|
||||||
ServerError { description: String, reason: String },
|
Error { description: String, reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomPopup {
|
impl RoomPopup {
|
||||||
|
|
@ -26,7 +26,7 @@ impl RoomPopup {
|
||||||
|
|
||||||
pub fn widget(&self) -> BoxedWidget {
|
pub fn widget(&self) -> BoxedWidget {
|
||||||
let widget = match self {
|
let widget = match self {
|
||||||
Self::ServerError {
|
Self::Error {
|
||||||
description,
|
description,
|
||||||
reason,
|
reason,
|
||||||
} => Self::server_error_widget(description, reason),
|
} => Self::server_error_widget(description, reason),
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ use crate::ui::UiEvent;
|
||||||
use crate::vault::EuphVault;
|
use crate::vault::EuphVault;
|
||||||
|
|
||||||
use super::account::{self, AccountUiState};
|
use super::account::{self, AccountUiState};
|
||||||
|
use super::links::{self, LinksState};
|
||||||
use super::popup::RoomPopup;
|
use super::popup::RoomPopup;
|
||||||
use super::{auth, nick, nick_list};
|
use super::{auth, nick, nick_list};
|
||||||
|
|
||||||
|
|
@ -37,6 +38,7 @@ enum State {
|
||||||
Auth(EditorState),
|
Auth(EditorState),
|
||||||
Nick(EditorState),
|
Nick(EditorState),
|
||||||
Account(AccountUiState),
|
Account(AccountUiState),
|
||||||
|
Links(LinksState),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
|
@ -222,6 +224,7 @@ impl EuphRoom {
|
||||||
State::Auth(editor) => layers.push(auth::widget(editor)),
|
State::Auth(editor) => layers.push(auth::widget(editor)),
|
||||||
State::Nick(editor) => layers.push(nick::widget(editor)),
|
State::Nick(editor) => layers.push(nick::widget(editor)),
|
||||||
State::Account(account) => layers.push(account.widget()),
|
State::Account(account) => layers.push(account.widget()),
|
||||||
|
State::Links(links) => layers.push(links.widget()),
|
||||||
}
|
}
|
||||||
|
|
||||||
for popup in &self.popups {
|
for popup in &self.popups {
|
||||||
|
|
@ -316,6 +319,8 @@ impl EuphRoom {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bindings.binding("I", "show message links");
|
||||||
|
|
||||||
bindings.empty();
|
bindings.empty();
|
||||||
self.chat.list_key_bindings(bindings, can_compose).await;
|
self.chat.list_key_bindings(bindings, can_compose).await;
|
||||||
}
|
}
|
||||||
|
|
@ -326,6 +331,15 @@ impl EuphRoom {
|
||||||
crossterm_lock: &Arc<FairMutex<()>>,
|
crossterm_lock: &Arc<FairMutex<()>>,
|
||||||
event: &InputEvent,
|
event: &InputEvent,
|
||||||
) -> bool {
|
) -> 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 {
|
if let Some(room) = &self.room {
|
||||||
let status = room.status().await;
|
let status = room.status().await;
|
||||||
let can_compose = matches!(status, Ok(Some(Status::Joined(_))));
|
let can_compose = matches!(status, Ok(Some(Status::Joined(_))));
|
||||||
|
|
@ -395,6 +409,7 @@ impl EuphRoom {
|
||||||
State::Auth(_) => auth::list_key_bindings(bindings),
|
State::Auth(_) => auth::list_key_bindings(bindings),
|
||||||
State::Nick(_) => nick::list_key_bindings(bindings),
|
State::Nick(_) => nick::list_key_bindings(bindings),
|
||||||
State::Account(account) => account.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 {
|
if let Some((action, reason)) = error {
|
||||||
let description = format!("Failed to {action}.");
|
let description = format!("Failed to {action}.");
|
||||||
let reason = reason.unwrap_or_else(|| "no idea, the server wouldn't say".to_string());
|
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,
|
description,
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
@ -522,7 +554,7 @@ impl EuphRoom {
|
||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
let description = format!("Failed to {action}.");
|
let description = format!("Failed to {action}.");
|
||||||
self.popups.push_front(RoomPopup::ServerError {
|
self.popups.push_front(RoomPopup::Error {
|
||||||
description,
|
description,
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue