kkx/kkx/src/login.rs

224 lines
6.8 KiB
Rust

use futures::stream::TryStreamExt;
use kkdisp::{
component::{Plan, Widget},
theme::Color,
token::Token,
view::{Event, Key},
Action,
};
use misskey::{
websocket::WebSocketClientBuilder, ClientExt, StreamingClientExt,
WebSocketClient,
};
use crate::{AuthDetail, Message};
pub enum LoginFocus {
Hostname,
Token,
OK,
}
impl LoginFocus {
fn ok(&self) -> bool {
match self {
LoginFocus::OK => true,
_ => false,
}
}
}
pub struct LoginPrompt {
hostname: String,
token: String,
error: Option<String>,
focus: LoginFocus,
client: Option<AuthDetail>,
}
impl LoginPrompt {
pub fn new() -> Self {
Self {
hostname: String::new(),
token: String::new(),
error: None,
focus: LoginFocus::Hostname,
client: None,
}
}
pub fn plan(&self) -> Plan {
Plan::start()
.fill(vec![])
.fixed(8, vec![Widget::new(100, self.tokens())])
.fill(vec![])
}
fn backspace(&mut self) {
match self.focus {
LoginFocus::Hostname => {
self.hostname.pop();
}
LoginFocus::Token => {
self.token.pop();
}
LoginFocus::OK => {}
}
}
pub fn query(&mut self) -> Option<AuthDetail> {
self.client.take()
}
pub async fn attempt(&mut self) {
let hostname =
self.hostname.trim_end_matches('/').to_string();
if hostname.len() == 0 {
self.error = Some("empty hostname".into());
self.focus = LoginFocus::Hostname;
return;
}
let token = self.token.clone();
if token.len() == 0 {
self.error = Some("empty token".into());
self.focus = LoginFocus::Token;
return;
}
let auth = AuthDetail { hostname, token };
match auth.normal().await {
Ok(client) => {
match client.recommended_users().try_next().await {
Ok(next) => {
eprintln!("next: {:#?}", next);
self.client = Some(auth);
}
Err(err) => {
self.error = Some(err.to_string());
}
};
}
Err(err) => {
self.error = Some(err.to_string());
}
};
}
fn down(&mut self) {
self.focus = match self.focus {
LoginFocus::Hostname => LoginFocus::Token,
LoginFocus::Token => LoginFocus::OK,
LoginFocus::OK => LoginFocus::Hostname,
};
}
pub async fn update(
&mut self,
event: Event<Message>,
) -> Result<Action, anyhow::Error> {
match event {
Event::Link(lnk) => {
if lnk == "ok" {
self.attempt().await;
Ok(Action::ReplaceAll(vec![self.plan()]))
} else if lnk == "token" {
self.focus = LoginFocus::Token;
Ok(Action::ReplaceAll(vec![self.plan()]))
} else if lnk == "hostname" {
self.focus = LoginFocus::Hostname;
Ok(Action::ReplaceAll(vec![self.plan()]))
} else {
Ok(Action::Nothing)
}
}
Event::Input(input) => {
match input {
Key::Return => match self.focus {
LoginFocus::Hostname => {
self.focus = LoginFocus::Token;
}
LoginFocus::Token => {
self.focus = LoginFocus::OK;
}
LoginFocus::OK => self.attempt().await,
},
Key::Backspace => self.backspace(),
Key::Tab => self.down(),
Key::Up => {
self.focus = match self.focus {
LoginFocus::Hostname => LoginFocus::OK,
LoginFocus::Token => LoginFocus::Hostname,
LoginFocus::OK => LoginFocus::Token,
};
}
Key::Down => self.down(),
Key::Char(c) => match self.focus {
LoginFocus::Hostname => self.hostname.push(c),
LoginFocus::Token => self.token.push(c),
LoginFocus::OK => {}
},
Key::Ctrl(c) => {
if c == 'u' || c == 'U' {
match self.focus {
LoginFocus::Hostname => {
self.hostname = String::new();
}
LoginFocus::Token => {
self.token = String::new()
}
LoginFocus::OK => {}
}
}
}
Key::Esc => {
self.focus = LoginFocus::OK;
}
_ => {
return Ok(Action::Nothing);
}
};
Ok(Action::ReplaceAll(vec![self.plan()]))
}
Event::Message(_) => todo!(),
}
}
fn tokens(&self) -> Vec<Token> {
const HOSTNAME_PROMPT: &str = "hostname: ";
const TOKEN_PROMPT: &str = "token: ";
let mut hostname = Token::text(self.hostname.clone());
let mut token = Token::text(self.token.clone());
let mut ok = Token::text("[ok]");
match self.focus {
LoginFocus::Hostname => {
hostname = hostname.bg(Color::BLUE)
}
LoginFocus::Token => token = token.bg(Color::BLUE),
LoginFocus::OK => ok = ok.bg(Color::BLUE),
};
vec![
Token::text("kkx cfg")
.bg(Color::BLUE)
.fg(Color::WHITE)
.centered(),
Token::End,
match &self.error {
Some(e) => Token::text(e)
.fg(Color::RED)
.pad_percent(80)
.centered(),
None => Token::End,
},
hostname
.string(HOSTNAME_PROMPT)
.link("hostname")
.pad_percent(80)
.centered(),
token
.string(TOKEN_PROMPT)
.link("token")
.pad_percent(80)
.centered(),
ok.link("ok").centered(),
]
}
}