feat: started work on the main ui

This commit is contained in:
antifallobst 2023-09-23 14:45:03 +02:00
parent 70d39c35ab
commit da4fba9203
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
4 changed files with 190 additions and 50 deletions

View File

@ -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(())
} }

View File

@ -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");
} }
} }

132
src/ui/master.rs Normal file
View File

@ -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(())
}
}

View File

@ -1,12 +1,47 @@
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")?;
@ -14,3 +49,15 @@ pub fn prepare() -> Result<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");
}