1use crate::tui::state::{AppBlock, AppState, EndpointField, JsonViewMode, SettingsField};
2use crate::tui::widgets::{
3 create_button, create_input_widget, create_json_viewer, create_list_widget,
4};
5use ratatui::{
6 Frame,
7 backend::Backend,
8 layout::{Constraint, Direction, Layout, Rect},
9 style::{Color, Style},
10 text::{Span, Spans, Text},
11 widgets::{Block, Borders, Paragraph},
12};
13
14pub fn draw_ui<B: Backend>(f: &mut Frame<B>, app_state: &mut AppState) {
15 let chunks = Layout::default()
16 .direction(Direction::Vertical)
17 .constraints(
18 [
19 Constraint::Length(5),
20 Constraint::Min(1),
21 Constraint::Length(2),
22 ]
23 .as_ref(),
24 )
25 .split(f.size());
26
27 draw_settings_screen(f, app_state, chunks[0]);
28
29 let main_chunks = Layout::default()
30 .direction(Direction::Horizontal)
31 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
32 .split(chunks[1]);
33
34 draw_endpoints_screen(f, app_state, main_chunks[0]);
35 draw_response_screen(f, app_state, main_chunks[1]);
36
37 draw_help_text(f, chunks[2]);
38}
39
40fn draw_help_text<B: Backend>(f: &mut Frame<B>, area: Rect) {
41 let help_text = Paragraph::new("Press Esc to quit | Use Tab key to switch block | Use arrow keys to navigate | Enter to press button")
42 .style(Style::default().fg(Color::Gray))
43 .alignment(ratatui::layout::Alignment::Center);
44 f.render_widget(help_text, area);
45}
46
47fn draw_settings_screen<B: Backend>(f: &mut Frame<B>, app_state: &mut AppState, area: Rect) {
48 let is_focused = app_state.current_block == AppBlock::Settings;
49 let title = Spans::from(vec![Span::styled(
50 " Settings ",
51 Style::default().fg(if is_focused {
52 Color::Yellow
53 } else {
54 Color::Gray
55 }),
56 )]);
57
58 let settings_block = Block::default()
59 .borders(Borders::ALL)
60 .border_style(Style::default().fg(if is_focused {
61 Color::Yellow
62 } else {
63 Color::Gray
64 }))
65 .title(title);
66
67 f.render_widget(settings_block, area);
68
69 let settings_chunks = Layout::default()
70 .direction(Direction::Horizontal)
71 .constraints([
72 Constraint::Percentage(20),
73 Constraint::Percentage(20),
74 Constraint::Percentage(20),
75 Constraint::Percentage(20),
76 Constraint::Percentage(20),
77 ])
78 .margin(1)
79 .split(area);
80
81 let url_input = create_input_widget(
82 " URL ",
83 &app_state.url,
84 app_state.focused_settings_field == Some(SettingsField::Url),
85 );
86 let username_input = create_input_widget(
87 " Username ",
88 &app_state.username,
89 app_state.focused_settings_field == Some(SettingsField::Username),
90 );
91 let password_input = create_input_widget(
92 " Password ",
93 &app_state.password,
94 app_state.focused_settings_field == Some(SettingsField::Password),
95 );
96
97 let connect_button = create_button(
98 "Connect",
99 !app_state.connected,
100 app_state.focused_settings_field == Some(SettingsField::ConnectButton),
101 );
102
103 let disconnect_button = create_button(
104 "Disconnect",
105 app_state.connected,
106 app_state.focused_settings_field == Some(SettingsField::DisconnectButton),
107 );
108
109 f.render_widget(url_input, settings_chunks[0]);
110 f.render_widget(username_input, settings_chunks[1]);
111 f.render_widget(password_input, settings_chunks[2]);
112 f.render_widget(connect_button, settings_chunks[3]);
113 f.render_widget(disconnect_button, settings_chunks[4]);
114}
115
116fn draw_endpoints_screen<B: Backend>(f: &mut Frame<B>, app_state: &mut AppState, area: Rect) {
117 let endpoint_chunks = Layout::default()
118 .direction(Direction::Horizontal)
119 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
120 .split(area);
121
122 let is_focused = app_state.current_block == AppBlock::EndpointList;
123 if app_state.connected {
124 let list_widget = create_list_widget(
125 &app_state.endpoints,
126 app_state.selected_endpoint,
127 is_focused,
128 );
129 f.render_widget(list_widget, endpoint_chunks[0]);
130 } else {
131 let title = Spans::from(vec![Span::styled(
132 " Endpoint List ",
133 Style::default().fg(if is_focused {
134 Color::Yellow
135 } else {
136 Color::Gray
137 }),
138 )]);
139
140 let empty_block = Block::default()
141 .borders(Borders::ALL)
142 .border_style(Style::default().fg(if is_focused {
143 Color::Yellow
144 } else {
145 Color::Gray
146 }))
147 .title(title);
148
149 f.render_widget(empty_block, endpoint_chunks[0]);
150 }
151
152 let is_focused = app_state.current_block == AppBlock::EndpointsReq;
153
154 let title = Spans::from(vec![Span::styled(
155 " Request ",
156 Style::default().fg(if is_focused {
157 Color::Yellow
158 } else {
159 Color::Gray
160 }),
161 )]);
162
163 let request_block = Block::default()
164 .borders(Borders::ALL)
165 .border_style(Style::default().fg(if is_focused {
166 Color::Yellow
167 } else {
168 Color::Gray
169 }))
170 .title(title);
171
172 f.render_widget(request_block, endpoint_chunks[1]);
173
174 let num_params = app_state.params.len();
175 let num_items = num_params + 4;
176
177 let mut constraints = Vec::new();
178
179 for _ in 0..num_items {
180 constraints.push(Constraint::Length(3));
181 }
182
183 constraints.push(Constraint::Min(1));
184
185 let request_chunks = Layout::default()
186 .direction(Direction::Vertical)
187 .constraints(constraints.as_slice())
188 .margin(1)
189 .split(endpoint_chunks[1]);
190
191 let service_name_text = match &app_state.service_name {
192 Some(service_name) => service_name.clone(),
193 None => String::from(""),
194 };
195
196 let service_name_paragraph = Paragraph::new(Text::raw(service_name_text))
197 .block(
198 Block::default()
199 .borders(Borders::ALL)
200 .border_style(Style::default().fg(Color::Gray))
201 .title(" Service Name "),
202 )
203 .style(Style::default().fg(Color::Gray))
204 .alignment(ratatui::layout::Alignment::Left);
205 f.render_widget(service_name_paragraph, request_chunks[0]);
206
207 let method_id_text = match app_state.method_id {
208 Some(method_id) => method_id.to_string(),
209 None => String::from(""),
210 };
211
212 let method_id_paragraph = Paragraph::new(Text::raw(method_id_text))
213 .block(
214 Block::default()
215 .borders(Borders::ALL)
216 .border_style(Style::default().fg(Color::Gray))
217 .title(" Method ID "),
218 )
219 .style(Style::default().fg(Color::Gray))
220 .alignment(ratatui::layout::Alignment::Left);
221 f.render_widget(method_id_paragraph, request_chunks[1]);
222
223 for (i, param) in app_state.params.iter().enumerate() {
224 let param_label = format!(" {}:{:?} ", param.name, param.ty);
225 let param_value = app_state.param_values[i].to_string();
226 let param_input = create_input_widget(
227 ¶m_label,
228 ¶m_value,
229 app_state.focused_endpoint_field == Some(EndpointField::Param(i)),
230 );
231 f.render_widget(param_input, request_chunks[i + 2]);
232 }
233
234 let button_chunks = Layout::default()
236 .direction(Direction::Horizontal)
237 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
238 .split(request_chunks[request_chunks.len() - 3]);
239
240 let connect_button = create_button(
241 "Connect",
242 !app_state.endpoint_connected,
243 app_state.focused_endpoint_field == Some(EndpointField::ConnectButton),
244 );
245
246 let disconnect_button = create_button(
247 "Disconnect",
248 app_state.endpoint_connected,
249 app_state.focused_endpoint_field == Some(EndpointField::DisconnectButton),
250 );
251
252 f.render_widget(connect_button, button_chunks[0]);
253 f.render_widget(disconnect_button, button_chunks[1]);
254
255 let json_toggle_button = create_button(
256 if app_state.json_view_mode == JsonViewMode::Pretty {
257 " Pretty JSON "
258 } else {
259 " Raw JSON "
260 },
261 true,
262 app_state.focused_endpoint_field == Some(EndpointField::JsonToggleButton),
263 );
264
265 f.render_widget(json_toggle_button, request_chunks[request_chunks.len() - 2]);
266}
267
268fn draw_response_screen<B: Backend>(f: &mut Frame<B>, app_state: &mut AppState, area: Rect) {
269 let is_focused = app_state.current_block == AppBlock::EndpointsRes;
270 let json_viewer = create_json_viewer(&app_state.json_data, is_focused)
271 .scroll((app_state.response_scroll.0, app_state.response_scroll.1));
272 f.render_widget(json_viewer, area);
273}