feat: add exit dialogue and auth fields
This commit is contained in:
parent
7cf30ab18b
commit
52d184626d
9 changed files with 367 additions and 18 deletions
124
Cargo.lock
generated
124
Cargo.lock
generated
|
@ -32,6 +32,17 @@ version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -77,6 +88,12 @@ version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -233,24 +250,28 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dyn-clone"
|
||||||
version = "0.15.7"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "echoed"
|
name = "echoed"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"cookie_store",
|
"cookie_store",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"dotenvy",
|
"dyn-clone",
|
||||||
|
"paste",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest_cookie_store",
|
"reqwest_cookie_store",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tui-textarea",
|
||||||
|
"tui_confirm_dialog",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -739,6 +760,15 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.2"
|
version = "0.32.2"
|
||||||
|
@ -863,6 +893,15 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.89"
|
version = "1.0.89"
|
||||||
|
@ -897,6 +936,36 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
@ -913,6 +982,7 @@ dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"strum",
|
"strum",
|
||||||
|
"time",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-truncate",
|
"unicode-truncate",
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
|
@ -1363,7 +1433,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
@ -1515,6 +1587,29 @@ version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui-textarea"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"ratatui",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui_confirm_dialog"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117929c8e294f8ceea4f56f4d09a6a4e694f057d175f2cf61433309fb01e9a94"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"rand",
|
||||||
|
"ratatui",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
@ -1832,6 +1927,27 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|
|
@ -4,17 +4,18 @@ publish = false
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
|
||||||
dotenvy = ["dep:dotenvy"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.83"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
cookie_store = "0.21.0"
|
cookie_store = "0.21.0"
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
dotenvy = { version = "0.15.7", optional = true }
|
dyn-clone = "1.0.17"
|
||||||
|
paste = "1.0.15"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
reqwest = { version = "0.12.9", features = ["cookies", "multipart"] }
|
reqwest = { version = "0.12.9", features = ["cookies", "multipart"] }
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tui-textarea = "0.7.0"
|
||||||
|
tui_confirm_dialog = "0.2.4"
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
|
|
1
src/helper.rs
Normal file
1
src/helper.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod tui;
|
20
src/helper/tui.rs
Normal file
20
src/helper/tui.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, BorderType, Borders},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a block with
|
||||||
|
pub fn border(frame: &mut Frame, title: Option<&str>) -> Rect {
|
||||||
|
let outer = frame.area();
|
||||||
|
let mut block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded);
|
||||||
|
|
||||||
|
if let Some(title) = title {
|
||||||
|
block = block.title(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.render_widget(block.clone(), outer);
|
||||||
|
|
||||||
|
block.inner(outer)
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
pub type Terminal = ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stdout>>;
|
|
127
src/main.rs
127
src/main.rs
|
@ -1,18 +1,127 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossterm::event::{self, KeyCode, KeyEventKind};
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
use echoed::Terminal;
|
use ratatui::{
|
||||||
use ratatui::widgets::Paragraph;
|
prelude::*,
|
||||||
|
widgets::{BorderType, Borders},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
io::Stdout,
|
||||||
|
sync::{mpsc, Arc},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tui_confirm_dialog::{ConfirmDialog, ConfirmDialogState, Listener, PopupMessage};
|
||||||
|
use views::{ViewCommand, ViewContainer};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
|
||||||
|
mod helper;
|
||||||
|
pub mod prelude;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
const EXIT_TIMEOUT: Duration = Duration::from_millis(150);
|
||||||
|
const POLL_TIMEOUT: Duration = Duration::from_millis(16);
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
exit: ConfirmDialogState,
|
||||||
|
exit_rx: mpsc::Receiver<Listener>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
let (exit_tx, exit_rx) = mpsc::channel::<Listener>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
exit: ConfirmDialogState::default()
|
||||||
|
.with_title("Exit")
|
||||||
|
.with_text(Text::raw("Are you sure you would like to exit?"))
|
||||||
|
.with_listener(Some(exit_tx)),
|
||||||
|
exit_rx,
|
||||||
|
// ..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
||||||
|
let mut view: ViewContainer = Arc::new(Mutex::new(views::Auth::default()));
|
||||||
|
view.lock().await.setup();
|
||||||
|
let mut state = State::default();
|
||||||
|
let (view_tx, view_rx) = mpsc::channel::<ViewCommand>();
|
||||||
|
let mut exit_triggered = Instant::now();
|
||||||
|
exit_triggered -= EXIT_TIMEOUT;
|
||||||
|
|
||||||
async fn run(mut terminal: Terminal) -> Result<()> {
|
|
||||||
loop {
|
loop {
|
||||||
|
// check if the app should exit
|
||||||
|
if let Ok((_, exit)) = state.exit_rx.try_recv() {
|
||||||
|
if exit.unwrap_or_default() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for new view commands
|
||||||
|
match view_rx.try_recv() {
|
||||||
|
Ok(ViewCommand::Change(new_view)) => {
|
||||||
|
view = new_view;
|
||||||
|
view.lock().await.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the view
|
||||||
|
let mut view = view.lock().await;
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
let greeting = Paragraph::new("Hello Ratatui! (press 'q' to quit)");
|
view.draw(frame);
|
||||||
frame.render_widget(greeting, frame.area());
|
|
||||||
|
if state.exit.is_opened() {
|
||||||
|
frame.render_stateful_widget(
|
||||||
|
ConfirmDialog::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.selected_button_style(Style::default().yellow().underlined().bold()),
|
||||||
|
frame.area(),
|
||||||
|
&mut state.exit,
|
||||||
|
);
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let event::Event::Key(key) = event::read()? {
|
// handle events
|
||||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
if event::poll(POLL_TIMEOUT)? {
|
||||||
return Ok(());
|
match event::read()? {
|
||||||
|
Event::Key(key) if key.kind == KeyEventKind::Press => {
|
||||||
|
// handle exit popup keypresses above all else
|
||||||
|
if state.exit.is_opened() {
|
||||||
|
if key.code == KeyCode::Esc && exit_triggered.elapsed() < EXIT_TIMEOUT {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
state.exit.handle(key);
|
||||||
|
exit_triggered = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then handle view keypresses
|
||||||
|
view.keypress(key, &view_tx).await?;
|
||||||
|
|
||||||
|
// then handle global keypresses
|
||||||
|
|
||||||
|
// esc + ctrl+c quit
|
||||||
|
if (key.code == KeyCode::Esc
|
||||||
|
|| (key.code == KeyCode::Char('c')
|
||||||
|
&& key.modifiers.contains(KeyModifiers::CONTROL)))
|
||||||
|
&& !state.exit.is_opened()
|
||||||
|
&& exit_triggered.elapsed() >= EXIT_TIMEOUT
|
||||||
|
{
|
||||||
|
state.exit = state.exit.open();
|
||||||
|
exit_triggered = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(view);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
src/prelude.rs
Normal file
7
src/prelude.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub use crate::{helper::tui as helper, views::ViewCommand};
|
||||||
|
pub use color_eyre::Result;
|
||||||
|
pub use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
||||||
|
pub use ratatui::prelude::*;
|
||||||
|
pub use std::any::Any;
|
||||||
|
|
||||||
|
pub type ViewSender = std::sync::mpsc::Sender<ViewCommand>;
|
30
src/views.rs
Normal file
30
src/views.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
macro_rules! import_view {
|
||||||
|
($($view:ident),+) => {
|
||||||
|
paste::paste! {
|
||||||
|
$(
|
||||||
|
mod [<$view:lower>];
|
||||||
|
pub use [<$view:lower>]::$view;
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
import_view!(Auth);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait View: DynClone {
|
||||||
|
fn setup(&mut self);
|
||||||
|
fn draw(&self, frame: &mut Frame);
|
||||||
|
async fn keypress(&mut self, key: KeyEvent, command_tx: &ViewSender) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ViewContainer = Arc<Mutex<dyn View + Send + Sync>>;
|
||||||
|
|
||||||
|
pub enum ViewCommand {
|
||||||
|
Change(ViewContainer),
|
||||||
|
}
|
66
src/views/auth.rs
Normal file
66
src/views/auth.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use super::View;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
|
const SHOW_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);
|
||||||
|
const HIDE_STYLE: Style = Style::new().bg(Color::Reset);
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Auth<'t> {
|
||||||
|
email: TextArea<'t>,
|
||||||
|
password: TextArea<'t>,
|
||||||
|
pass_select: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'t> View for Auth<'t> {
|
||||||
|
fn setup(&mut self) {
|
||||||
|
self.password.set_mask_char('\u{2022}');
|
||||||
|
self.password.set_cursor_style(HIDE_STYLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, frame: &mut Frame) {
|
||||||
|
let area = helper::border(frame, Some("Auth"));
|
||||||
|
let field_areas =
|
||||||
|
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).split(area);
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
for ((mut field, label), area) in [(&self.email, "Email"), (&self.password, "Password")]
|
||||||
|
.iter()
|
||||||
|
.zip(field_areas.iter())
|
||||||
|
{
|
||||||
|
let [label_area, field_area] =
|
||||||
|
Layout::horizontal([Constraint::Percentage(20), Constraint::Percentage(80)])
|
||||||
|
.areas(*area);
|
||||||
|
|
||||||
|
frame.render_widget(Text::raw(*label), label_area);
|
||||||
|
frame.render_widget(field, field_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn keypress(&mut self, key: KeyEvent, _command_tx: &ViewSender) -> Result<()> {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up => {
|
||||||
|
self.pass_select = false;
|
||||||
|
self.email.set_cursor_style(SHOW_STYLE);
|
||||||
|
self.password.set_cursor_style(HIDE_STYLE);
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
self.pass_select = true;
|
||||||
|
self.email.set_cursor_style(HIDE_STYLE);
|
||||||
|
self.password.set_cursor_style(SHOW_STYLE);
|
||||||
|
}
|
||||||
|
KeyCode::Char(_) | KeyCode::Backspace => {
|
||||||
|
let field = if self.pass_select {
|
||||||
|
&mut self.password
|
||||||
|
} else {
|
||||||
|
&mut self.email
|
||||||
|
};
|
||||||
|
|
||||||
|
field.input(key);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue