Skip to content

Commit

Permalink
chore: refactor ui mod
Browse files Browse the repository at this point in the history
  • Loading branch information
fedeya committed Nov 3, 2024
1 parent 2c5065d commit a35d505
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 292 deletions.
19 changes: 17 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod form;
use crate::event::input::Input;
use clap::ValueEnum;
use form::Form;
use std::collections::HashMap;
use std::{collections::HashMap, str::FromStr};

use tokio::sync::mpsc::{channel, Receiver, Sender};

Expand All @@ -15,7 +15,7 @@ pub enum InputMode {
Insert,
}

#[derive(Clone, PartialEq, ValueEnum)]
#[derive(Clone, PartialEq)]
pub enum RequestMethod {
Get,
Post,
Expand All @@ -36,6 +36,21 @@ impl ToString for RequestMethod {
}
}

impl FromStr for RequestMethod {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"get" => Ok(Self::Get),
"post" => Ok(Self::Post),
"put" => Ok(Self::Put),
"delete" => Ok(Self::Delete),
"patch" => Ok(Self::Patch),
_ => Err("Invalid method".to_string()),
}
}
}

pub trait OrderNavigation: Clone + PartialEq {
fn get_order(&self) -> Vec<Self>
where
Expand Down
4 changes: 2 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use clap::Parser;
use crate::app::RequestMethod;

#[derive(Parser)]
#[command(version, about, long_about=None)]
#[command(version, about, long_about=None, author)]
pub struct Cli {
pub endpoint: Option<String>,

#[arg(value_enum, default_value = "get", short, long)]
#[arg(default_value = "get", short = 'X', long, value_parser = clap::value_parser!(RequestMethod))]
pub method: Option<RequestMethod>,
}
308 changes: 20 additions & 288 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
mod input;
mod popup;
mod request_tab;
mod syntax;

use std::{io::Stdout, iter::once};
use std::io::Stdout;

use popup::render_popup;
use ratatui::{
prelude::{Alignment, Constraint, CrosstermBackend, Direction, Layout, Rect},
prelude::{Alignment, Constraint, CrosstermBackend, Direction, Layout},
style::{Color, Style},
text::Span,
widgets::{Block, Borders, Clear, Paragraph, Row, Table, TableState, Tabs},
widgets::{Block, Borders, Paragraph, Tabs},
Frame,
};
use request_tab::render_request_tab;

use crate::app::{
App, AppBlock, AppPopup, BodyContentType, BodyType, InputMode, OrderNavigation, RequestMethod,
RequestTab,
};
use crate::app::{App, AppBlock, InputMode, OrderNavigation, RequestMethod};

use self::input::{create_input, create_textarea};
use self::input::create_input;

fn selectable_block(block: AppBlock, app: &App) -> Block {
let is_selected = block == app.selected_block && app.popup.is_none();
Expand Down Expand Up @@ -113,146 +114,22 @@ pub fn draw(frame: &mut Frame<CrosstermBackend<Stdout>>, app: &mut App) {

frame.render_widget(tab, request_chunks[0]);

match app.request_tab {
RequestTab::Body => {
let body_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(3)])
.split(request_chunks[1]);

let content_type_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(body_chunks[1]);

let content_type_mode_p = Paragraph::new(match app.body_content_type {
BodyContentType::Text(_) => "Text",
BodyContentType::Form => "Form",
})
.block(selectable_block(AppBlock::RequestContent, app).title("Content"))
.alignment(Alignment::Center);

frame.render_widget(
content_type_mode_p,
match app.body_content_type {
BodyContentType::Text(_) => content_type_chunks[0],
BodyContentType::Form => body_chunks[1],
},
);

if let BodyContentType::Text(body_type) = app.body_content_type.clone() {
let content_type_format_p = Paragraph::new(match body_type {
BodyType::Json => "JSON",
BodyType::Raw => "Raw",
BodyType::Xml => "XML",
})
.block(selectable_block(AppBlock::RequestContent, app).title("Type"))
.style(Style::default().fg(Color::Yellow))
.alignment(Alignment::Center);

frame.render_widget(content_type_format_p, content_type_chunks[1]);

let raw_body_input = create_textarea(&app.raw_body, app)
.block(selectable_block(AppBlock::RequestContent, app).title("Body"));

frame.render_widget(raw_body_input, body_chunks[0]);
} else {
let rows: Vec<Row> = app
.body_form
.iter()
.map(|(key, value)| {
Row::new(vec![key.clone(), value.clone()])
.style(Style::default().fg(Color::White))
})
.collect();

let table = Table::new(rows)
.header(
Row::new(vec!["Key", "Value"])
.style(Style::default().fg(Color::Yellow))
.bottom_margin(1),
)
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)])
.highlight_style(Style::default().fg(Color::Green))
.highlight_symbol(">> ")
.block(
selectable_block(AppBlock::RequestContent, app)
.title("Body")
.padding(ratatui::widgets::Padding::new(1, 1, 1, 1)),
);

let mut state = TableState::default();

state.select(Some(app.selected_form_field.into()));

frame.render_stateful_widget(table, body_chunks[0], &mut state);
}
}
RequestTab::Headers => {
let rows: Vec<Row> = app
.headers
.iter()
.map(|(key, value)| {
Row::new(vec![key.clone(), value.clone()])
.style(Style::default().fg(Color::White))
})
.collect();

let table = Table::new(rows)
.header(
Row::new(vec!["Key", "Value"])
.style(Style::default().fg(Color::Yellow))
.bottom_margin(1),
)
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)])
.highlight_style(Style::default().fg(Color::Green))
.highlight_symbol(">> ")
.block(
selectable_block(AppBlock::RequestContent, app)
.title("Headers")
.padding(ratatui::widgets::Padding::new(1, 1, 1, 1)),
);
render_request_tab(app, frame, request_chunks.to_vec());

let mut state = TableState::default();
render_response(app, frame, response_chunks.to_vec());

state.select(Some(app.selected_header.into()));

frame.render_stateful_widget(table, request_chunks[1], &mut state);
}
RequestTab::Query => {
let rows: Vec<Row> = app
.query_params
.iter()
.map(|(key, value)| {
Row::new(vec![key.clone(), value.clone()])
.style(Style::default().fg(Color::White))
})
.collect();

let table = Table::new(rows)
.header(
Row::new(vec!["Key", "Value"])
.style(Style::default().fg(Color::Yellow))
.bottom_margin(1),
)
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)])
.highlight_style(Style::default().fg(Color::Green))
.highlight_symbol(">> ")
.block(
selectable_block(AppBlock::RequestContent, app)
.title("Query Parameters")
.padding(ratatui::widgets::Padding::new(1, 1, 1, 1)),
);

let mut state = TableState::default();

state.select(Some(app.selected_query_param.into()));
frame.render_widget(help_p, main_chunks[2]);

frame.render_stateful_widget(table, request_chunks[1], &mut state);
}
_ => {}
if let Some(_) = app.popup {
render_popup(app, frame);
}
}

fn render_response(
app: &mut App,
frame: &mut Frame<CrosstermBackend<Stdout>>,
response_chunks: Vec<ratatui::prelude::Rect>,
) {
match app.response.as_ref() {
Some(r) => {
let lines_count = u16::try_from(r.text.lines().count()).unwrap_or(1);
Expand Down Expand Up @@ -318,149 +195,4 @@ pub fn draw(frame: &mut Frame<CrosstermBackend<Stdout>>, app: &mut App) {
frame.render_widget(status_blank, response_chunks[1]);
}
}

frame.render_widget(help_p, main_chunks[2]);

match app.popup.as_ref() {
Some(AppPopup::ChangeMethod) => {
let block = Block::default()
.title("Select method")
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::White));

let methods = app.method.get_order();

let methods_blocks = methods.iter().enumerate().map(|(i, method)| {
let cloned_method = method.clone();

let border_style = match cloned_method == app.method.clone() {
true => Style::default().fg(Color::Green),
false => Style::default().fg(Color::White),
};

let style = Style::default().fg(match cloned_method {
RequestMethod::Get => Color::Green,
RequestMethod::Post => Color::Blue,
RequestMethod::Put => Color::Yellow,
RequestMethod::Delete => Color::Red,
RequestMethod::Patch => Color::Magenta,
});

let block = Paragraph::new(cloned_method.to_string())
.style(style)
.alignment(Alignment::Center)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(border_style),
);

(i, block)
});

let height = app.method.get_order().len() as u16 * 3;

let width = 40;

let area = centered_rect(width, height + 4, frame.size());

frame.render_widget(Clear, area);
frame.render_widget(block, area);

methods_blocks.for_each(|(index, p)| {
frame.render_widget(
p,
Rect::new(area.x + 2, area.y + index as u16 * 3 + 1, width - 4, 3),
);
});

let help_p = Paragraph::new("Use j/k to navigate, Enter to select")
.style(Style::default().fg(Color::White))
.alignment(Alignment::Center);

frame.render_widget(
help_p,
Rect::new(area.x + 2, area.y + height + 2, width - 4, 1),
);
}
Some(AppPopup::FormPopup(form)) => {
let block = Block::default()
.title(form.title.clone())
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Blue));

let visible_fields = form.visible_fields();

let height = visible_fields.len() * 3 + 4;

let area = centered_rect(70, height as u16, frame.size());

let inputs = visible_fields.iter().enumerate().map(|(index, field)| {
let input = create_input(&field.input, &app, index == form.selected_field as usize)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(
if index == form.selected_field
&& app.input_mode == InputMode::Insert
{
Color::Green
} else if index == form.selected_field {
Color::Blue
} else {
Color::White
},
))
.title(field.label.clone()),
);

(index, input)
});

frame.render_widget(Clear, area);
frame.render_widget(block, area);

inputs.for_each(|(index, p)| {
frame.render_widget(
p,
Rect::new(area.x + 2, area.y + index as u16 * 3 + 1, area.width - 4, 3),
);
});

frame.render_widget(
Paragraph::new("Press Enter to Accept Changes")
.style(Style::default().fg(Color::White))
.alignment(Alignment::Center),
Rect::new(area.x + 2, area.y + height as u16 - 2, area.width - 4, 1),
);
}
None => {}
}
}

fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length((r.height - height) / 2),
Constraint::Length(height),
Constraint::Length((r.height - height) / 2),
]
.as_ref(),
)
.split(r);

Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Length((r.width - width) / 2),
Constraint::Length(width),
Constraint::Length((r.width - width) / 2),
]
.as_ref(),
)
.split(popup_layout[1])[1]
}
Loading

0 comments on commit a35d505

Please sign in to comment.