feat: started work on the main ui
This commit is contained in:
parent
70d39c35ab
commit
da4fba9203
|
@ -28,8 +28,6 @@ async fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let base_url = args.base_url.unwrap();
|
let base_url = args.base_url.unwrap();
|
||||||
|
|
||||||
let stdout = ui::prepare()?;
|
|
||||||
|
|
||||||
let api = match if let Some(token) = args.token {
|
let api = match if let Some(token) = args.token {
|
||||||
match Api::from_token(&base_url, token).await? {
|
match Api::from_token(&base_url, token).await? {
|
||||||
Ok(a) => Some(a),
|
Ok(a) => Some(a),
|
||||||
|
@ -42,12 +40,14 @@ async fn main() -> Result<()> {
|
||||||
} {
|
} {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => {
|
None => {
|
||||||
let mut login = ui::login::UI::new(stdout, base_url)?;
|
let mut login = ui::login::UI::new(ui::prepare()?, base_url)?;
|
||||||
login.run().await?
|
login.run().await?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{:#?}", api);
|
let mut ui = ui::master::UI::new(api, ui::prepare()?)?;
|
||||||
|
|
||||||
|
ui.run().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
use crate::api::Api;
|
use crate::{api::Api, ui, ui::TextAreaExt};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::event::{Event, KeyCode, KeyEvent};
|
||||||
event::{DisableMouseCapture, Event, KeyCode, KeyEvent},
|
|
||||||
execute,
|
|
||||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
|
||||||
};
|
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
|
@ -16,33 +12,6 @@ use tui::{
|
||||||
};
|
};
|
||||||
use tui_textarea::TextArea;
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
trait TextAreaExt {
|
|
||||||
fn disable(&mut self);
|
|
||||||
fn enable(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextAreaExt for TextArea<'_> {
|
|
||||||
fn disable(&mut self) {
|
|
||||||
self.set_cursor_line_style(Style::default());
|
|
||||||
self.set_cursor_style(Style::default());
|
|
||||||
let b = self
|
|
||||||
.block()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
|
||||||
self.set_block(b.style(Style::default().fg(Color::DarkGray)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(&mut self) {
|
|
||||||
self.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED));
|
|
||||||
self.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
|
||||||
let b = self
|
|
||||||
.block()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
|
||||||
self.set_block(b.style(Style::default()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum InputPosition {
|
enum InputPosition {
|
||||||
Username,
|
Username,
|
||||||
Password,
|
Password,
|
||||||
|
@ -63,15 +32,7 @@ pub struct UI<'a> {
|
||||||
|
|
||||||
impl Drop for UI<'_> {
|
impl Drop for UI<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode");
|
ui::restore(&mut self.terminal);
|
||||||
execute!(
|
|
||||||
self.terminal.backend_mut(),
|
|
||||||
LeaveAlternateScreen,
|
|
||||||
DisableMouseCapture
|
|
||||||
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
|
||||||
self.terminal
|
|
||||||
.show_cursor()
|
|
||||||
.expect("While destructing UI -> Failed to re-enable cursor");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
use crate::api::Api;
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::{Event, KeyCode, KeyEvent};
|
||||||
|
use std::io::Stdout;
|
||||||
|
use tui::widgets::{Block, Borders, List, ListItem, ListState};
|
||||||
|
use tui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
layout::{Constraint, Direction, Layout},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum InputPosition {
|
||||||
|
Sidebar,
|
||||||
|
Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UI<'a> {
|
||||||
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||||
|
sidebar_items: Vec<&'a str>,
|
||||||
|
sidebar_state: ListState,
|
||||||
|
position: InputPosition,
|
||||||
|
|
||||||
|
api: Api,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UI<'_> {
|
||||||
|
pub fn new(api: Api, stdout: Stdout) -> Result<Self> {
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
let sidebar_items = vec!["Overview", "Users", "Projects", "Server Status", "Backup"];
|
||||||
|
let mut sidebar_state = ListState::default();
|
||||||
|
sidebar_state.select(Some(0));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
terminal,
|
||||||
|
sidebar_items,
|
||||||
|
sidebar_state,
|
||||||
|
position: InputPosition::Sidebar,
|
||||||
|
api,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
self.draw().await?;
|
||||||
|
let event = crossterm::event::read()?;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Esc, ..
|
||||||
|
}) => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Tab, ..
|
||||||
|
}) => {
|
||||||
|
self.cycle();
|
||||||
|
}
|
||||||
|
input => match self.position {
|
||||||
|
InputPosition::Sidebar => match input {
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Up, ..
|
||||||
|
}) => {
|
||||||
|
if let Some(i) = self.sidebar_state.selected() {
|
||||||
|
if i > 0 {
|
||||||
|
self.sidebar_state.select(Some(i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Down,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if let Some(i) = self.sidebar_state.selected() {
|
||||||
|
if i < self.sidebar_items.len() - 1 {
|
||||||
|
self.sidebar_state.select(Some(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
InputPosition::Content => (),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cycle(&mut self) {
|
||||||
|
self.position = match self.position {
|
||||||
|
InputPosition::Sidebar => InputPosition::Content,
|
||||||
|
InputPosition::Content => InputPosition::Sidebar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn draw(&mut self) -> Result<()> {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(32), // Sidebar
|
||||||
|
Constraint::Min(32), // Content
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(self.terminal.size()?);
|
||||||
|
|
||||||
|
let sidebar = List::new(
|
||||||
|
self.sidebar_items
|
||||||
|
.iter()
|
||||||
|
.map(|&x| ListItem::new(x))
|
||||||
|
.collect::<Vec<ListItem>>(),
|
||||||
|
)
|
||||||
|
.block(Block::default().borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Green)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol("> ");
|
||||||
|
|
||||||
|
let content_block = Block::default().borders(Borders::ALL);
|
||||||
|
|
||||||
|
self.terminal.draw(|f| {
|
||||||
|
f.render_stateful_widget(sidebar, chunks[0], &mut self.sidebar_state);
|
||||||
|
f.render_widget(content_block, chunks[1]);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,63 @@
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod master;
|
||||||
|
|
||||||
use std::{io, io::Stdout};
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::EnableMouseCapture,
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
execute,
|
execute,
|
||||||
terminal::{enable_raw_mode, EnterAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
use std::{io, io::Stdout};
|
||||||
|
use tui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
widgets::{Block, Borders},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
|
pub trait TextAreaExt {
|
||||||
|
fn disable(&mut self);
|
||||||
|
fn enable(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextAreaExt for TextArea<'_> {
|
||||||
|
fn disable(&mut self) {
|
||||||
|
self.set_cursor_line_style(Style::default());
|
||||||
|
self.set_cursor_style(Style::default());
|
||||||
|
let b = self
|
||||||
|
.block()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
||||||
|
self.set_block(b.style(Style::default().fg(Color::DarkGray)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable(&mut self) {
|
||||||
|
self.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED));
|
||||||
|
self.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
|
let b = self
|
||||||
|
.block()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
||||||
|
self.set_block(b.style(Style::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prepare() -> Result<Stdout> {
|
pub fn prepare() -> Result<Stdout> {
|
||||||
enable_raw_mode().context("Failed to enable raw mode")?;
|
enable_raw_mode().context("Failed to enable raw mode")?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
Ok(stdout)
|
Ok(stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restore(terminal: &mut Terminal<CrosstermBackend<Stdout>>) {
|
||||||
|
disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode");
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture,
|
||||||
|
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
||||||
|
terminal
|
||||||
|
.show_cursor()
|
||||||
|
.expect("While destructing UI -> Failed to re-enable cursor");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue