From 438a07776a7f71ba70b1addedb7517f636aff67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Thu, 6 Mar 2025 18:03:58 -0300 Subject: [PATCH 1/6] body impl --- crates/crabapi/src/gui/iced/mod.rs | 77 +++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index a5fada6..c0c34ec 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -4,7 +4,7 @@ mod default_styles; // dependencies use iced; use iced::widget::{Button, Row, Text, TextInput}; -use iced::widget::{button, column, container, pick_list, row}; +use iced::widget::{button, column, container, pick_list, radio, row}; use iced::{Alignment, Element, Length}; // internal dependencies @@ -24,6 +24,13 @@ enum Message { #[allow(dead_code)] // TODO: Remove this out-out warning RemoveHeader(usize), AddHeader, + BodyTypeChanged(BodyType), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum BodyType { + File, + Text, } #[derive(Debug)] @@ -34,6 +41,8 @@ struct GUI { url_input: String, url_input_valid: bool, header_input: Vec<(String, String)>, + // body_input: String, + body_type_select: Option, } impl GUI { @@ -44,6 +53,7 @@ impl GUI { url_input: String::new(), url_input_valid: false, header_input: vec![(String::new(), String::new())], + body_type_select: Some(BodyType::Text), } } @@ -76,6 +86,10 @@ impl GUI { Message::AddHeader => { self.header_input.push((String::new(), String::new())); } + Message::BodyTypeChanged(body_type) => { + println!("Body Type Changed: {:?}", body_type); + self.body_type_select = Some(body_type); + } _ => {} // TODO: REmove this. Unnecessary if all implemented and enum is non-exhaustive } } @@ -85,15 +99,12 @@ impl GUI { let request_row = self.view_request(); // ROW: Headers - let headers_column = self.view_request_headers(); + let headers_row = self.view_request_headers(); - column![ - request_row, - container(headers_column) - .width(Length::Fill) - .padding(default_styles::padding()) - ] - .into() + // ROW: Body + let body_row = self.view_request_body(); + + column![request_row, headers_row, body_row].into() } fn view_request(&self) -> Element { @@ -105,11 +116,19 @@ impl GUI { let request_row = Self::view_request_row_setup(row![method_input, url_input, send_button]); - request_row.into() + let title_row = Self::view_request_row_setup(row![Self::view_request_title()]); + + column![title_row, request_row].into() } // VIEW REQUEST - GENERAL + fn view_request_title() -> Element<'static, Message> { + Text::new("Request") + .size(default_styles::input_size()) + .into() + } + fn view_request_url_input(&self) -> Element { let url_input_icon = Self::view_request_url_input_icon(self.url_input_valid); let url_input = TextInput::new("Enter URI", &self.url_input) @@ -159,6 +178,13 @@ impl GUI { // VIEW REQUEST - HEADERS fn view_request_headers(&self) -> Element { + container(self.view_request_headers_inner()) + .width(Length::Fill) + .padding(default_styles::padding()) + .into() + } + + fn view_request_headers_inner(&self) -> Element { let headers_title = Self::view_request_headers_title(); let headers_column = self.view_request_headers_column(); @@ -171,7 +197,9 @@ impl GUI { } fn view_request_headers_title() -> Element<'static, Message> { - Text::new("Headers").size(16).into() + Text::new("Headers") + .size(default_styles::input_size()) + .into() } fn view_request_headers_column(&self) -> Element { @@ -209,6 +237,33 @@ impl GUI { .on_press(Message::AddHeader) .into() } + + // VIEW REQUEST - BODY + + fn view_request_body(&self) -> Element { + let body_title = Self::view_request_body_title(); + + let text = radio( + "Text", + BodyType::Text, + self.body_type_select, + Message::BodyTypeChanged, + ); + let file = radio( + "File", + BodyType::File, + self.body_type_select, + Message::BodyTypeChanged, + ); + + column!(body_title, row![text, file].spacing(default_styles::spacing())).into() + } + + fn view_request_body_title() -> Element<'static, Message> { + Text::new("Body") + .size(default_styles::input_size()) + .into() + } } impl Default for GUI { From c2f3935b84981bb7a3961b2018e4d4e4e63b47ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Fri, 7 Mar 2025 11:09:47 -0300 Subject: [PATCH 2/6] added body container --- crates/crabapi/src/gui/iced/mod.rs | 47 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index f6c6e54..89b6aae 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -308,26 +308,33 @@ impl GUI { // VIEW REQUEST - BODY fn view_request_body(&self) -> Element { - let body_title = Self::view_request_body_title(); - - let text = radio( - "Text", - BodyType::Text, - self.body_type_select, - Message::BodyTypeChanged, - ); - let file = radio( - "File", - BodyType::File, - self.body_type_select, - Message::BodyTypeChanged, - ); - - column!( - body_title, - row![text, file].spacing(default_styles::spacing()) - ) - .into() + container(self.view_request_body_inner()) + .width(Length::Fill) + .padding(default_styles::padding()) + .into() + } + + fn view_request_body_inner(&self) -> Element { + let body_title = Self::view_request_body_title(); + + let text = radio( + "Text", + BodyType::Text, + self.body_type_select, + Message::BodyTypeChanged, + ); + let file = radio( + "File", + BodyType::File, + self.body_type_select, + Message::BodyTypeChanged, + ); + + column!( + body_title, + row![text, file].spacing(default_styles::spacing()) + ) + .into() } fn view_request_body_title() -> Element<'static, Message> { From ca511ef86c34052a610ecaf5a879d7193d86436a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Fri, 7 Mar 2025 11:41:31 -0300 Subject: [PATCH 3/6] basic body --- crates/crabapi/src/gui/iced/mod.rs | 74 ++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index 89b6aae..7a27105 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -7,8 +7,8 @@ use std::collections::HashMap; use crate::core::requests; use iced; use iced::widget::text_editor::{Action, Content}; -use iced::widget::{Button, Row, Text, TextInput, scrollable, text_editor}; -use iced::widget::{button, column, container, pick_list, radio, row}; +use iced::widget::{Button, Row, Text, TextInput}; +use iced::widget::{button, column, container, pick_list, radio, row, scrollable, text_editor}; use iced::{Alignment, Center, Element, Length, Task}; use iced_highlighter::Highlighter; use reqwest::{Body, Client}; @@ -31,10 +31,12 @@ enum Message { ResponseBodyChanged(String), ResponseBodyText(Action), BodyTypeChanged(BodyType), + BodyContentChanged(text_editor::Action), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum BodyType { + Empty, File, Text, } @@ -49,7 +51,7 @@ struct GUI { url_input_valid: bool, header_input: Vec<(String, String)>, response_body: Content, - // body_input: String, + body_content: text_editor::Content, body_type_select: Option, } @@ -63,6 +65,7 @@ impl GUI { url_input_valid: false, header_input: vec![(String::new(), String::new())], response_body: Content::with_text("Response body will go here..."), + body_content: text_editor::Content::default(), body_type_select: Some(BodyType::Text), } } @@ -155,6 +158,10 @@ impl GUI { self.body_type_select = Some(body_type); Task::none() } + Message::BodyContentChanged(action) => { + self.body_content.perform(action); + Task::none() + } } } @@ -315,32 +322,53 @@ impl GUI { } fn view_request_body_inner(&self) -> Element { - let body_title = Self::view_request_body_title(); - - let text = radio( - "Text", - BodyType::Text, - self.body_type_select, - Message::BodyTypeChanged, - ); - let file = radio( - "File", - BodyType::File, - self.body_type_select, - Message::BodyTypeChanged, - ); - - column!( - body_title, - row![text, file].spacing(default_styles::spacing()) - ) - .into() + let body_title = Self::view_request_body_title(); + + let empty = radio( + "Empty", + BodyType::Empty, + self.body_type_select, + Message::BodyTypeChanged, + ); + + let text = radio( + "Text", + BodyType::Text, + self.body_type_select, + Message::BodyTypeChanged, + ); + let file = radio( + "File", + BodyType::File, + self.body_type_select, + Message::BodyTypeChanged, + ); + + let content = match self.body_type_select { + Some(BodyType::Empty) => row![], + Some(BodyType::File) => row![], + Some(BodyType::Text) => self.view_request_body_text(), + None => row![], + }; + + column!( + body_title, + row![empty, text, file].spacing(default_styles::spacing()), + content, + ) + .into() } fn view_request_body_title() -> Element<'static, Message> { Text::new("Body").size(default_styles::input_size()).into() } + fn view_request_body_text(&self) -> Row { + row![text_editor(&self.body_content) + .on_action(Message::BodyContentChanged) + .placeholder("Introduce body here...") ] + } + // VIEW RESPONSE fn view_response(&self) -> Element<'_, Message> { From 81f703816019e994e28f1419dfd181af29f4b04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Fri, 7 Mar 2025 12:46:21 -0300 Subject: [PATCH 4/6] added body from file --- crates/crabapi/Cargo.toml | 6 +- crates/crabapi/src/gui/iced/mod.rs | 96 ++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/crates/crabapi/Cargo.toml b/crates/crabapi/Cargo.toml index af9f8e1..88432e6 100644 --- a/crates/crabapi/Cargo.toml +++ b/crates/crabapi/Cargo.toml @@ -11,10 +11,8 @@ iced = ["dep:iced", "dep:iced_highlighter"] clap = "4.5.31" const_format = "0.2.34" http = "1.2.0" +rfd = "0.15.2" reqwest = "0.12.12" tokio = { version = "1", features = ["full"] } - -# gui options are optional -iced = { version = "0.13.1", optional = true, features = ["advanced"] } +iced = { version = "0.13.1", optional = true, features = ["advanced", "tokio"] } iced_highlighter = { version = "0.13.0", optional = true } - diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index 7a27105..10bf725 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -1,10 +1,9 @@ // internal mods mod default_styles; -use http::{HeaderMap, HeaderName}; -use std::collections::HashMap; -// dependencies use crate::core::requests; +use crate::core::requests::{Method, constants, send_requests, validators}; +use http::{HeaderMap, HeaderName}; use iced; use iced::widget::text_editor::{Action, Content}; use iced::widget::{Button, Row, Text, TextInput}; @@ -12,8 +11,10 @@ use iced::widget::{button, column, container, pick_list, radio, row, scrollable, use iced::{Alignment, Center, Element, Length, Task}; use iced_highlighter::Highlighter; use reqwest::{Body, Client}; -// internal dependencies -use crate::core::requests::{Method, constants, send_requests, validators}; +use std::collections::HashMap; +use std::io; +use std::path::PathBuf; +use std::sync::Arc; pub fn init() { iced::run(GUI::title, GUI::update, GUI::view).unwrap() @@ -32,6 +33,8 @@ enum Message { ResponseBodyText(Action), BodyTypeChanged(BodyType), BodyContentChanged(text_editor::Action), + BodyContentOpenFile, + BodyContentFileOpened(Result<(PathBuf, Arc), FileOpenDialogError>), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -41,6 +44,12 @@ enum BodyType { Text, } +#[derive(Debug, Clone)] +pub enum FileOpenDialogError { + DialogClosed, + IoError(io::ErrorKind), +} + #[derive(Debug)] #[allow(clippy::upper_case_acronyms)] struct GUI { @@ -53,6 +62,8 @@ struct GUI { response_body: Content, body_content: text_editor::Content, body_type_select: Option, + body_file_path: Option, + body_file_content: Option>, } impl GUI { @@ -67,6 +78,8 @@ impl GUI { response_body: Content::with_text("Response body will go here..."), body_content: text_editor::Content::default(), body_type_select: Some(BodyType::Text), + body_file_path: None, + body_file_content: None, } } @@ -154,7 +167,6 @@ impl GUI { Task::none() } Message::BodyTypeChanged(body_type) => { - println!("Body Type Changed: {:?}", body_type); self.body_type_select = Some(body_type); Task::none() } @@ -162,6 +174,25 @@ impl GUI { self.body_content.perform(action); Task::none() } + Message::BodyContentOpenFile => { + Task::perform(open_file(), Message::BodyContentFileOpened) + } + Message::BodyContentFileOpened(result) => { + match result { + Ok((path, content)) => { + self.body_file_content = Some(content); + self.body_file_path = Some(path); + } + Err(error) => { + // TODO: use tracing + println!("Error opening file: {:?}", error); + if let FileOpenDialogError::IoError(kind) = error { + println!("Error kind: {:?}", kind); + } + } + } + Task::none() + } } } @@ -346,7 +377,7 @@ impl GUI { let content = match self.body_type_select { Some(BodyType::Empty) => row![], - Some(BodyType::File) => row![], + Some(BodyType::File) => self.view_request_body_file(), Some(BodyType::Text) => self.view_request_body_text(), None => row![], }; @@ -364,9 +395,31 @@ impl GUI { } fn view_request_body_text(&self) -> Row { - row![text_editor(&self.body_content) - .on_action(Message::BodyContentChanged) - .placeholder("Introduce body here...") ] + row![ + text_editor(&self.body_content) + .on_action(Message::BodyContentChanged) + .placeholder("Introduce body here...") + ] + } + + fn view_request_body_file(&self) -> Row { + let file_name_string = format!( + "File: {}", + self.body_file_path + .as_ref() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| "No file selected".to_string()) + ); + row![ + Self::view_request_body_text_button(), + Text::new(file_name_string).size(default_styles::input_size()) + ] + } + + fn view_request_body_text_button() -> Element<'static, Message> { + Button::new(Text::new("Select File").size(default_styles::input_size())) + .on_press(Message::BodyContentOpenFile) + .into() } // VIEW RESPONSE @@ -399,3 +452,26 @@ impl Default for GUI { GUI::new() } } + +async fn open_file() -> Result<(PathBuf, Arc), FileOpenDialogError> { + let picked_file = rfd::AsyncFileDialog::new() + .set_title("Open a file...") + .pick_file() + .await + .ok_or(FileOpenDialogError::DialogClosed)?; + + load_file(picked_file).await +} + +async fn load_file( + path: impl Into, +) -> Result<(PathBuf, Arc), FileOpenDialogError> { + let path = path.into(); + + let contents = tokio::fs::read_to_string(&path) + .await + .map(Arc::new) + .map_err(|error| FileOpenDialogError::IoError(error.kind()))?; + + Ok((path, contents)) +} From 596472d9f09d3ed50dc64bd507e70adf8ce0a495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Fri, 7 Mar 2025 12:54:01 -0300 Subject: [PATCH 5/6] improve layout --- crates/crabapi/src/gui/iced/mod.rs | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index 10bf725..6efee90 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -213,6 +213,8 @@ impl GUI { } fn view_request(&self) -> Element { + let title_row = Self::view_request_row_setup(row![Self::view_request_title()]); + let url_input = self.view_request_url_input(); let method_input = self.view_request_method_input(); @@ -221,8 +223,6 @@ impl GUI { let request_row = Self::view_request_row_setup(row![method_input, url_input, send_button]); - let title_row = Self::view_request_row_setup(row![Self::view_request_title()]); - column![title_row, request_row].into() } @@ -355,6 +355,24 @@ impl GUI { fn view_request_body_inner(&self) -> Element { let body_title = Self::view_request_body_title(); + let radio_buttons = self.view_request_body_radio_buttons(); + + let content = self.view_request_body_content(); + + column!( + body_title, + radio_buttons, + content, + ) + .spacing(default_styles::spacing()) + .into() + } + + fn view_request_body_title() -> Element<'static, Message> { + Text::new("Body").size(default_styles::input_size()).into() + } + + fn view_request_body_radio_buttons(&self) -> Row { let empty = radio( "Empty", BodyType::Empty, @@ -375,6 +393,10 @@ impl GUI { Message::BodyTypeChanged, ); + row![empty, text, file].spacing(default_styles::spacing()) + } + + fn view_request_body_content(&self) -> Row { let content = match self.body_type_select { Some(BodyType::Empty) => row![], Some(BodyType::File) => self.view_request_body_file(), @@ -382,16 +404,7 @@ impl GUI { None => row![], }; - column!( - body_title, - row![empty, text, file].spacing(default_styles::spacing()), - content, - ) - .into() - } - - fn view_request_body_title() -> Element<'static, Message> { - Text::new("Body").size(default_styles::input_size()).into() + content } fn view_request_body_text(&self) -> Row { From b632702a9c7547629a3c4dff9d58f3d10e4caca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= Date: Fri, 7 Mar 2025 13:04:27 -0300 Subject: [PATCH 6/6] last layout improvements --- crates/crabapi/src/gui/iced/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index 6efee90..5903abb 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -6,7 +6,7 @@ use crate::core::requests::{Method, constants, send_requests, validators}; use http::{HeaderMap, HeaderName}; use iced; use iced::widget::text_editor::{Action, Content}; -use iced::widget::{Button, Row, Text, TextInput}; +use iced::widget::{Button, Row, Space, Text, TextInput}; use iced::widget::{button, column, container, pick_list, radio, row, scrollable, text_editor}; use iced::{Alignment, Center, Element, Length, Task}; use iced_highlighter::Highlighter; @@ -359,13 +359,9 @@ impl GUI { let content = self.view_request_body_content(); - column!( - body_title, - radio_buttons, - content, - ) - .spacing(default_styles::spacing()) - .into() + column!(body_title, radio_buttons, content,) + .spacing(default_styles::spacing()) + .into() } fn view_request_body_title() -> Element<'static, Message> { @@ -412,6 +408,7 @@ impl GUI { text_editor(&self.body_content) .on_action(Message::BodyContentChanged) .placeholder("Introduce body here...") + .size(default_styles::input_size()) ] } @@ -423,10 +420,13 @@ impl GUI { .map(|path| path.to_string_lossy().to_string()) .unwrap_or_else(|| "No file selected".to_string()) ); + row![ Self::view_request_body_text_button(), + Space::new(default_styles::input_size(), default_styles::input_size()), Text::new(file_name_string).size(default_styles::input_size()) ] + .align_y(Center) } fn view_request_body_text_button() -> Element<'static, Message> {