diff --git a/Cargo.lock b/Cargo.lock index e00a07b..c766fe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,17 @@ version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.2.0" @@ -1271,6 +1282,7 @@ name = "reqtui" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "cc", "config", "lazy_static", diff --git a/TODO.md b/TODO.md index 823bae5..fc868ee 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,16 @@ # ✅ TODO -## Core +- [x] most used http methods (GET, PUT, POST, PATCH, DELETE) - [ ] add environments for collections, to define variables to replace keys - [ ] allow for authentication, primarily Bearer tokens - [ ] import collections from postman - [ ] support HTML, XML, plain text and other response types - [ ] support other types of bodies, Multipart, URL encoded forms - - -## Other +- [ ] prevent from synchronizing to disk when no changes were made +- [ ] force synchronization when switching from dirty requests +- [ ] allow for saving a sample response from a request +- [ ] moving requests from folders to others +- [ ] creating requests inside of folders - [ ] better CLI interfacing - [ ] edit bodies in $EDITOR - [ ] add scripting to requests diff --git a/reqtui/Cargo.toml b/reqtui/Cargo.toml index 02c5ab8..36e2f5c 100644 --- a/reqtui/Cargo.toml +++ b/reqtui/Cargo.toml @@ -17,6 +17,7 @@ tree-sitter-json.workspace = true lazy_static.workspace = true ropey = "1.6.1" +async-trait = "0.1.80" [build-dependencies] cc="*" diff --git a/reqtui/src/collection/types.rs b/reqtui/src/collection/types.rs index b5f9ba7..b20e7dc 100644 --- a/reqtui/src/collection/types.rs +++ b/reqtui/src/collection/types.rs @@ -80,7 +80,13 @@ pub struct Request { pub uri: String, pub body: Option, #[serde(rename = "bodyType")] - pub body_type: Option, + pub body_type: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum BodyType { + #[serde(rename = "json")] + Json, } impl Hash for Request { diff --git a/reqtui/src/net.rs b/reqtui/src/net.rs index 7b8be0e..39a91a3 100644 --- a/reqtui/src/net.rs +++ b/reqtui/src/net.rs @@ -1,2 +1,5 @@ pub mod request_manager; +pub mod request_strategies; +pub mod response_decoders; + pub use request_manager::handle_request; diff --git a/reqtui/src/net/request_manager.rs b/reqtui/src/net/request_manager.rs index b5a2cb0..1280f5a 100644 --- a/reqtui/src/net/request_manager.rs +++ b/reqtui/src/net/request_manager.rs @@ -1,65 +1,74 @@ use crate::{ - collection::types::Request, + collection::types::{BodyType, Request}, + net::request_strategies::{http_strategy::HttpResponse, RequestStrategy}, text_object::{Readonly, TextObject}, }; use reqwest::header::{HeaderMap, HeaderValue}; +use std::time::Duration; use tokio::sync::mpsc::UnboundedSender; #[derive(Debug, PartialEq)] -pub struct ReqtuiResponse { +pub struct Response { pub body: String, pub pretty_body: TextObject, pub headers: HeaderMap, + pub duration: Duration, } -#[derive(Debug, PartialEq)] -pub enum ReqtuiNetRequest { - Request(Request), - Response(ReqtuiResponse), - Error(String), +pub struct RequestManager; + +impl RequestManager { + pub async fn handle(strategy: S, request: Request) -> anyhow::Result + where + S: RequestStrategy, + { + let response = strategy.handle(request).await?; + Ok(response) + } +} + +pub enum ContentType { + TextPlain, + TextHtml, + TextCss, + TextJavascript, + ApplicationJson, + ApplicationXml, +} + +impl From<&str> for ContentType { + fn from(value: &str) -> Self { + match value { + _ if value.to_ascii_lowercase().contains("application/json") => Self::ApplicationJson, + _ if value.to_ascii_lowercase().contains("application/xml") => Self::ApplicationXml, + _ if value.to_ascii_lowercase().contains("text/plain") => Self::TextPlain, + _ if value.to_ascii_lowercase().contains("text/plain") => Self::TextPlain, + _ if value.to_ascii_lowercase().contains("text/html") => Self::TextHtml, + _ if value.to_ascii_lowercase().contains("text/css") => Self::TextCss, + _ if value.to_ascii_lowercase().contains("text/javascript") => Self::TextJavascript, + _ => Self::TextPlain, + } + } } #[tracing::instrument(skip_all)] -pub fn handle_request(request: Request, response_tx: UnboundedSender) { +pub fn handle_request(request: Request, response_tx: UnboundedSender) { tracing::debug!("starting to handle user request"); tokio::spawn(async move { - let client = reqwest::Client::new(); - match client.get(request.uri).send().await { - Ok(res) => { - tracing::debug!("request handled successfully, sending response"); - - let headers = res.headers().to_owned(); - - let body: serde_json::Value = res - .json() - .await - .map_err(|_| { - response_tx.send(ReqtuiNetRequest::Error( - "failed to decode json response".into(), - )) - }) - .expect("failed to send response through channel"); - - let pretty_body = serde_json::to_string_pretty(&body) - .map_err(|_| { - response_tx.send(ReqtuiNetRequest::Error( - "failed to decode json response".into(), - )) - }) - .expect("failed to send response through channel"); - - let body = body.to_string(); - let pretty_body = TextObject::from(&pretty_body); + let result = match request.body_type.as_ref() { + // if we dont have a body type, this is a GET request, so we use HTTP strategy + None => RequestManager::handle(HttpResponse, request).await, + Some(body_type) => match body_type { + BodyType::Json => RequestManager::handle(HttpResponse, request).await, + }, + }; - response_tx - .send(ReqtuiNetRequest::Response(ReqtuiResponse { - body, - pretty_body, - headers, - })) - .expect("failed to send response through channel"); + match result { + Ok(response) => response_tx.send(response).ok(), + Err(e) => { + tracing::error!("{e:?}"); + todo!(); } - Err(_) => todo!(), } }); } diff --git a/reqtui/src/net/request_strategies.rs b/reqtui/src/net/request_strategies.rs new file mode 100644 index 0000000..d422b81 --- /dev/null +++ b/reqtui/src/net/request_strategies.rs @@ -0,0 +1,8 @@ +pub mod http_strategy; + +use crate::{collection::types::Request, net::request_manager::Response}; + +#[async_trait::async_trait] +pub trait RequestStrategy { + async fn handle(&self, request: Request) -> anyhow::Result; +} diff --git a/reqtui/src/net/request_strategies/http_strategy.rs b/reqtui/src/net/request_strategies/http_strategy.rs new file mode 100644 index 0000000..295c0e0 --- /dev/null +++ b/reqtui/src/net/request_strategies/http_strategy.rs @@ -0,0 +1,122 @@ +use crate::{ + collection::types::{Request, RequestMethod}, + net::{ + request_manager::Response, + request_strategies::RequestStrategy, + response_decoders::{decoder_from_headers, ResponseDecoder}, + }, +}; + +pub struct HttpResponse; + +#[async_trait::async_trait] +impl RequestStrategy for HttpResponse { + async fn handle(&self, request: Request) -> anyhow::Result { + let client = reqwest::Client::new(); + + match request.method { + RequestMethod::Get => self.handle_get_request(client, request).await, + RequestMethod::Post => self.handle_post_request(client, request).await, + RequestMethod::Put => self.handle_put_request(client, request).await, + RequestMethod::Patch => self.handle_patch_request(client, request).await, + RequestMethod::Delete => self.handle_delete_request(client, request).await, + } + } +} + +impl HttpResponse { + async fn handle_get_request( + &self, + client: reqwest::Client, + request: Request, + ) -> anyhow::Result { + let now = std::time::Instant::now(); + match client.get(request.uri).send().await { + Ok(response) => { + let decoder = decoder_from_headers(response.headers()); + decoder.decode(response, now).await + } + Err(_) => todo!(), + } + } + + async fn handle_post_request( + &self, + client: reqwest::Client, + request: Request, + ) -> anyhow::Result { + let now = std::time::Instant::now(); + match client + .post(request.uri) + .json(&request.body.unwrap_or_default()) + .send() + .await + { + Ok(response) => { + let decoder = decoder_from_headers(response.headers()); + decoder.decode(response, now).await + } + Err(_) => todo!(), + } + } + + async fn handle_put_request( + &self, + client: reqwest::Client, + request: Request, + ) -> anyhow::Result { + let now = std::time::Instant::now(); + match client + .put(request.uri) + .json(&request.body.unwrap_or_default()) + .send() + .await + { + Ok(response) => { + let decoder = decoder_from_headers(response.headers()); + decoder.decode(response, now).await + } + Err(_) => todo!(), + } + } + + async fn handle_patch_request( + &self, + client: reqwest::Client, + request: Request, + ) -> anyhow::Result { + let now = std::time::Instant::now(); + match client + .patch(request.uri) + .json(&request.body.unwrap_or_default()) + .send() + .await + { + Ok(response) => { + let decoder = decoder_from_headers(response.headers()); + decoder.decode(response, now).await + } + Err(_) => todo!(), + } + } + + async fn handle_delete_request( + &self, + client: reqwest::Client, + request: Request, + ) -> anyhow::Result { + let now = std::time::Instant::now(); + match client + .delete(request.uri) + .json(&request.body.unwrap_or_default()) + .send() + .await + { + Ok(response) => { + let decoder = decoder_from_headers(response.headers()); + decoder.decode(response, now).await + } + Err(_) => todo!(), + } + } +} diff --git a/reqtui/src/net/response_decoders.rs b/reqtui/src/net/response_decoders.rs new file mode 100644 index 0000000..a384aa3 --- /dev/null +++ b/reqtui/src/net/response_decoders.rs @@ -0,0 +1,24 @@ +mod json_decoder; + +use crate::net::{ + request_manager::{ContentType, Response}, + response_decoders::json_decoder::JsonDecoder, +}; +use reqwest::header::HeaderMap; +use std::time::Instant; + +#[async_trait::async_trait] +pub trait ResponseDecoder { + async fn decode(&self, response: reqwest::Response, start: Instant) + -> anyhow::Result; +} + +pub fn decoder_from_headers(headers: &HeaderMap) -> impl ResponseDecoder { + match headers.get("Content-Type") { + Some(header) => match ContentType::from(header.to_str().unwrap_or_default()) { + ContentType::ApplicationJson => JsonDecoder, + _ => JsonDecoder, + }, + None => JsonDecoder, + } +} diff --git a/reqtui/src/net/response_decoders/json_decoder.rs b/reqtui/src/net/response_decoders/json_decoder.rs new file mode 100644 index 0000000..8ed3120 --- /dev/null +++ b/reqtui/src/net/response_decoders/json_decoder.rs @@ -0,0 +1,30 @@ +use crate::{ + net::{request_manager::Response, response_decoders::ResponseDecoder}, + text_object::TextObject, +}; +use std::time::Instant; + +pub struct JsonDecoder; + +#[async_trait::async_trait] +impl ResponseDecoder for JsonDecoder { + async fn decode( + &self, + response: reqwest::Response, + start: Instant, + ) -> anyhow::Result { + let duration = start.elapsed(); + let headers = response.headers().to_owned(); + let body: serde_json::Value = response.json().await?; + let pretty_body = serde_json::to_string_pretty(&body)?; + let pretty_body = TextObject::from(&pretty_body); + let body = body.to_string(); + + Ok(Response { + body, + pretty_body, + headers, + duration, + }) + } +} diff --git a/tui/benches/collection_viewer_bench.rs b/tui/benches/collection_viewer_bench.rs index 9fc87d8..af4ba3f 100644 --- a/tui/benches/collection_viewer_bench.rs +++ b/tui/benches/collection_viewer_bench.rs @@ -3,14 +3,14 @@ use lazy_static::lazy_static; use ratatui::{backend::TestBackend, layout::Rect, Terminal}; use reqtui::{ collection::{ - types::{Info, Request, RequestKind, RequestMethod}, + types::{BodyType, Info, Request, RequestKind, RequestMethod}, Collection, }, syntax::highlighter::Highlighter, }; use tree_sitter::Tree; use tui::{ - components::{api_explorer::CollectionViewer, Eventful, Page}, + pages::{collection_viewer::CollectionViewer, Eventful, Page}, utils::build_syntax_highlighted_lines, }; @@ -18,10 +18,10 @@ fn main() { divan::main(); } -fn create_sample_schema() -> Collection { +fn create_sample_collection() -> Collection { Collection { info: Info { - name: "sample schema".to_string(), + name: "sample collection".to_string(), description: None, }, path: "any_path".into(), @@ -32,7 +32,7 @@ fn create_sample_schema() -> Collection { uri: "https://jsonplaceholder.typicode.com/users".to_string(), method: RequestMethod::Get, body: Some("[\r\n {\r\n \"id\": 1,\r\n \"name\": \"Leanne Graham\",\r\n \"username\": \"Bret\",\r\n \"email\": \"Sincere@april.biz\",\r\n \"address\": {\r\n \"street\": \"Kulas Light\",\r\n \"suite\": \"Apt. 556\",\r\n \"city\": \"Gwenborough\",\r\n \"zipcode\": \"92998-3874\",\r\n \"geo\": {\r\n \"lat\": \"-37.3159\",\r\n \"lng\": \"81.1496\"\r\n }\r\n },\r\n \"phone\": \"1-770-736-8031 x56442\",\r\n \"website\": \"hildegard.org\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Crona\",\r\n \"catchPhrase\": \"Multi-layered client-server neural-net\",\r\n \"bs\": \"harness real-time e-markets\"\r\n }\r\n },\r\n {\r\n \"id\": 2,\r\n \"name\": \"Ervin Howell\",\r\n \"username\": \"Antonette\",\r\n \"email\": \"Shanna@melissa.tv\",\r\n \"address\": {\r\n \"street\": \"Victor Plains\",\r\n \"suite\": \"Suite 879\",\r\n \"city\": \"Wisokyburgh\",\r\n \"zipcode\": \"90566-7771\",\r\n \"geo\": {\r\n \"lat\": \"-43.9509\",\r\n \"lng\": \"-34.4618\"\r\n }\r\n },\r\n \"phone\": \"010-692-6593 x09125\",\r\n \"website\": \"anastasia.net\",\r\n \"company\": {\r\n \"name\": \"Deckow-Crist\",\r\n \"catchPhrase\": \"Proactive didactic contingency\",\r\n \"bs\": \"synergize scalable supply-chains\"\r\n }\r\n },\r\n {\r\n \"id\": 3,\r\n \"name\": \"Clementine Bauch\",\r\n \"username\": \"Samantha\",\r\n \"email\": \"Nathan@yesenia.net\",\r\n \"address\": {\r\n \"street\": \"Douglas Extension\",\r\n \"suite\": \"Suite 847\",\r\n \"city\": \"McKenziehaven\",\r\n \"zipcode\": \"59590-4157\",\r\n \"geo\": {\r\n \"lat\": \"-68.6102\",\r\n \"lng\": \"-47.0653\"\r\n }\r\n },\r\n \"phone\": \"1-463-123-4447\",\r\n \"website\": \"ramiro.info\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Jacobson\",\r\n \"catchPhrase\": \"Face to face bifurcated interface\",\r\n \"bs\": \"e-enable strategic applications\"\r\n }\r\n },\r\n {\r\n \"id\": 4,\r\n \"name\": \"Patricia Lebsack\",\r\n \"username\": \"Karianne\",\r\n \"email\": \"Julianne.OConner@kory.org\",\r\n \"address\": {\r\n \"street\": \"Hoeger Mall\",\r\n \"suite\": \"Apt. 692\",\r\n \"city\": \"South Elvis\",\r\n \"zipcode\": \"53919-4257\",\r\n \"geo\": {\r\n \"lat\": \"29.4572\",\r\n \"lng\": \"-164.2990\"\r\n }\r\n },\r\n \"phone\": \"493-170-9623 x156\",\r\n \"website\": \"kale.biz\",\r\n \"company\": {\r\n \"name\": \"Robel-Corkery\",\r\n \"catchPhrase\": \"Multi-tiered zero tolerance productivity\",\r\n \"bs\": \"transition cutting-edge web services\"\r\n }\r\n },\r\n {\r\n \"id\": 5,\r\n \"name\": \"Chelsey Dietrich\",\r\n \"username\": \"Kamren\",\r\n \"email\": \"Lucio_Hettinger@annie.ca\",\r\n \"address\": {\r\n \"street\": \"Skiles Walks\",\r\n \"suite\": \"Suite 351\",\r\n \"city\": \"Roscoeview\",\r\n \"zipcode\": \"33263\",\r\n \"geo\": {\r\n \"lat\": \"-31.8129\",\r\n \"lng\": \"62.5342\"\r\n }\r\n },\r\n \"phone\": \"(254)954-1289\",\r\n \"website\": \"demarco.info\",\r\n \"company\": {\r\n \"name\": \"Keebler LLC\",\r\n \"catchPhrase\": \"User-centric fault-tolerant solution\",\r\n \"bs\": \"revolutionize end-to-end systems\"\r\n }\r\n },\r\n {\r\n \"id\": 6,\r\n \"name\": \"Mrs. Dennis Schulist\",\r\n \"username\": \"Leopoldo_Corkery\",\r\n \"email\": \"Karley_Dach@jasper.info\",\r\n \"address\": {\r\n \"street\": \"Norberto Crossing\",\r\n \"suite\": \"Apt. 950\",\r\n \"city\": \"South Christy\",\r\n \"zipcode\": \"23505-1337\",\r\n \"geo\": {\r\n \"lat\": \"-71.4197\",\r\n \"lng\": \"71.7478\"\r\n }\r\n },\r\n \"phone\": \"1-477-935-8478 x6430\",\r\n \"website\": \"ola.org\",\r\n \"company\": {\r\n \"name\": \"Considine-Lockman\",\r\n \"catchPhrase\": \"Synchronised bottom-line interface\",\r\n \"bs\": \"e-enable innovative applications\"\r\n }\r\n },\r\n {\r\n \"id\": 7,\r\n \"name\": \"Kurtis Weissnat\",\r\n \"username\": \"Elwyn.Skiles\",\r\n \"email\": \"Telly.Hoeger@billy.biz\",\r\n \"address\": {\r\n \"street\": \"Rex Trail\",\r\n \"suite\": \"Suite 280\",\r\n \"city\": \"Howemouth\",\r\n \"zipcode\": \"58804-1099\",\r\n \"geo\": {\r\n \"lat\": \"24.8918\",\r\n \"lng\": \"21.8984\"\r\n }\r\n },\r\n \"phone\": \"210.067.6132\",\r\n \"website\": \"elvis.io\",\r\n \"company\": {\r\n \"name\": \"Johns Group\",\r\n \"catchPhrase\": \"Configurable multimedia task-force\",\r\n \"bs\": \"generate enterprise e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 8,\r\n \"name\": \"Nicholas Runolfsdottir V\",\r\n \"username\": \"Maxime_Nienow\",\r\n \"email\": \"Sherwood@rosamond.me\",\r\n \"address\": {\r\n \"street\": \"Ellsworth Summit\",\r\n \"suite\": \"Suite 729\",\r\n \"city\": \"Aliyaview\",\r\n \"zipcode\": \"45169\",\r\n \"geo\": {\r\n \"lat\": \"-14.3990\",\r\n \"lng\": \"-120.7677\"\r\n }\r\n },\r\n \"phone\": \"586.493.6943 x140\",\r\n \"website\": \"jacynthe.com\",\r\n \"company\": {\r\n \"name\": \"Abernathy Group\",\r\n \"catchPhrase\": \"Implemented secondary concept\",\r\n \"bs\": \"e-enable extensible e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 9,\r\n \"name\": \"Glenna Reichert\",\r\n \"username\": \"Delphine\",\r\n \"email\": \"Chaim_McDermott@dana.io\",\r\n \"address\": {\r\n \"street\": \"Dayna Park\",\r\n \"suite\": \"Suite 449\",\r\n \"city\": \"Bartholomebury\",\r\n \"zipcode\": \"76495-3109\",\r\n \"geo\": {\r\n \"lat\": \"24.6463\",\r\n \"lng\": \"-168.8889\"\r\n }\r\n },\r\n \"phone\": \"(775)976-6794 x41206\",\r\n \"website\": \"conrad.com\",\r\n \"company\": {\r\n \"name\": \"Yost and Sons\",\r\n \"catchPhrase\": \"Switchable contextually-based project\",\r\n \"bs\": \"aggregate real-time technologies\"\r\n }\r\n },\r\n {\r\n \"id\": 10,\r\n \"name\": \"Clementina DuBuque\",\r\n \"username\": \"Moriah.Stanton\",\r\n \"email\": \"Rey.Padberg@karina.biz\",\r\n \"address\": {\r\n \"street\": \"Kattie Turnpike\",\r\n \"suite\": \"Suite 198\",\r\n \"city\": \"Lebsackbury\",\r\n \"zipcode\": \"31428-2261\",\r\n \"geo\": {\r\n \"lat\": \"-38.2386\",\r\n \"lng\": \"57.2232\"\r\n }\r\n },\r\n \"phone\": \"024-648-3804\",\r\n \"website\": \"ambrose.net\",\r\n \"company\": {\r\n \"name\": \"Hoeger LLC\",\r\n \"catchPhrase\": \"Centralized empowering task-force\",\r\n \"bs\": \"target end-to-end models\"\r\n }\r\n }\r\n]".to_string()), - body_type: Some("application/json".to_string()), + body_type: Some(BodyType::Json), }), RequestKind::Single(Request { id: "any_other_id".to_string(), @@ -40,7 +40,7 @@ fn create_sample_schema() -> Collection { uri: "https://jsonplaceholder.typicode.com/users".to_string(), method: RequestMethod::Get, body: Some("[\r\n {\r\n \"id\": 1,\r\n \"name\": \"Leanne Graham\",\r\n \"username\": \"Bret\",\r\n \"email\": \"Sincere@april.biz\",\r\n \"address\": {\r\n \"street\": \"Kulas Light\",\r\n \"suite\": \"Apt. 556\",\r\n \"city\": \"Gwenborough\",\r\n \"zipcode\": \"92998-3874\",\r\n \"geo\": {\r\n \"lat\": \"-37.3159\",\r\n \"lng\": \"81.1496\"\r\n }\r\n },\r\n \"phone\": \"1-770-736-8031 x56442\",\r\n \"website\": \"hildegard.org\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Crona\",\r\n \"catchPhrase\": \"Multi-layered client-server neural-net\",\r\n \"bs\": \"harness real-time e-markets\"\r\n }\r\n },\r\n {\r\n \"id\": 2,\r\n \"name\": \"Ervin Howell\",\r\n \"username\": \"Antonette\",\r\n \"email\": \"Shanna@melissa.tv\",\r\n \"address\": {\r\n \"street\": \"Victor Plains\",\r\n \"suite\": \"Suite 879\",\r\n \"city\": \"Wisokyburgh\",\r\n \"zipcode\": \"90566-7771\",\r\n \"geo\": {\r\n \"lat\": \"-43.9509\",\r\n \"lng\": \"-34.4618\"\r\n }\r\n },\r\n \"phone\": \"010-692-6593 x09125\",\r\n \"website\": \"anastasia.net\",\r\n \"company\": {\r\n \"name\": \"Deckow-Crist\",\r\n \"catchPhrase\": \"Proactive didactic contingency\",\r\n \"bs\": \"synergize scalable supply-chains\"\r\n }\r\n },\r\n {\r\n \"id\": 3,\r\n \"name\": \"Clementine Bauch\",\r\n \"username\": \"Samantha\",\r\n \"email\": \"Nathan@yesenia.net\",\r\n \"address\": {\r\n \"street\": \"Douglas Extension\",\r\n \"suite\": \"Suite 847\",\r\n \"city\": \"McKenziehaven\",\r\n \"zipcode\": \"59590-4157\",\r\n \"geo\": {\r\n \"lat\": \"-68.6102\",\r\n \"lng\": \"-47.0653\"\r\n }\r\n },\r\n \"phone\": \"1-463-123-4447\",\r\n \"website\": \"ramiro.info\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Jacobson\",\r\n \"catchPhrase\": \"Face to face bifurcated interface\",\r\n \"bs\": \"e-enable strategic applications\"\r\n }\r\n },\r\n {\r\n \"id\": 4,\r\n \"name\": \"Patricia Lebsack\",\r\n \"username\": \"Karianne\",\r\n \"email\": \"Julianne.OConner@kory.org\",\r\n \"address\": {\r\n \"street\": \"Hoeger Mall\",\r\n \"suite\": \"Apt. 692\",\r\n \"city\": \"South Elvis\",\r\n \"zipcode\": \"53919-4257\",\r\n \"geo\": {\r\n \"lat\": \"29.4572\",\r\n \"lng\": \"-164.2990\"\r\n }\r\n },\r\n \"phone\": \"493-170-9623 x156\",\r\n \"website\": \"kale.biz\",\r\n \"company\": {\r\n \"name\": \"Robel-Corkery\",\r\n \"catchPhrase\": \"Multi-tiered zero tolerance productivity\",\r\n \"bs\": \"transition cutting-edge web services\"\r\n }\r\n },\r\n {\r\n \"id\": 5,\r\n \"name\": \"Chelsey Dietrich\",\r\n \"username\": \"Kamren\",\r\n \"email\": \"Lucio_Hettinger@annie.ca\",\r\n \"address\": {\r\n \"street\": \"Skiles Walks\",\r\n \"suite\": \"Suite 351\",\r\n \"city\": \"Roscoeview\",\r\n \"zipcode\": \"33263\",\r\n \"geo\": {\r\n \"lat\": \"-31.8129\",\r\n \"lng\": \"62.5342\"\r\n }\r\n },\r\n \"phone\": \"(254)954-1289\",\r\n \"website\": \"demarco.info\",\r\n \"company\": {\r\n \"name\": \"Keebler LLC\",\r\n \"catchPhrase\": \"User-centric fault-tolerant solution\",\r\n \"bs\": \"revolutionize end-to-end systems\"\r\n }\r\n },\r\n {\r\n \"id\": 6,\r\n \"name\": \"Mrs. Dennis Schulist\",\r\n \"username\": \"Leopoldo_Corkery\",\r\n \"email\": \"Karley_Dach@jasper.info\",\r\n \"address\": {\r\n \"street\": \"Norberto Crossing\",\r\n \"suite\": \"Apt. 950\",\r\n \"city\": \"South Christy\",\r\n \"zipcode\": \"23505-1337\",\r\n \"geo\": {\r\n \"lat\": \"-71.4197\",\r\n \"lng\": \"71.7478\"\r\n }\r\n },\r\n \"phone\": \"1-477-935-8478 x6430\",\r\n \"website\": \"ola.org\",\r\n \"company\": {\r\n \"name\": \"Considine-Lockman\",\r\n \"catchPhrase\": \"Synchronised bottom-line interface\",\r\n \"bs\": \"e-enable innovative applications\"\r\n }\r\n },\r\n {\r\n \"id\": 7,\r\n \"name\": \"Kurtis Weissnat\",\r\n \"username\": \"Elwyn.Skiles\",\r\n \"email\": \"Telly.Hoeger@billy.biz\",\r\n \"address\": {\r\n \"street\": \"Rex Trail\",\r\n \"suite\": \"Suite 280\",\r\n \"city\": \"Howemouth\",\r\n \"zipcode\": \"58804-1099\",\r\n \"geo\": {\r\n \"lat\": \"24.8918\",\r\n \"lng\": \"21.8984\"\r\n }\r\n },\r\n \"phone\": \"210.067.6132\",\r\n \"website\": \"elvis.io\",\r\n \"company\": {\r\n \"name\": \"Johns Group\",\r\n \"catchPhrase\": \"Configurable multimedia task-force\",\r\n \"bs\": \"generate enterprise e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 8,\r\n \"name\": \"Nicholas Runolfsdottir V\",\r\n \"username\": \"Maxime_Nienow\",\r\n \"email\": \"Sherwood@rosamond.me\",\r\n \"address\": {\r\n \"street\": \"Ellsworth Summit\",\r\n \"suite\": \"Suite 729\",\r\n \"city\": \"Aliyaview\",\r\n \"zipcode\": \"45169\",\r\n \"geo\": {\r\n \"lat\": \"-14.3990\",\r\n \"lng\": \"-120.7677\"\r\n }\r\n },\r\n \"phone\": \"586.493.6943 x140\",\r\n \"website\": \"jacynthe.com\",\r\n \"company\": {\r\n \"name\": \"Abernathy Group\",\r\n \"catchPhrase\": \"Implemented secondary concept\",\r\n \"bs\": \"e-enable extensible e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 9,\r\n \"name\": \"Glenna Reichert\",\r\n \"username\": \"Delphine\",\r\n \"email\": \"Chaim_McDermott@dana.io\",\r\n \"address\": {\r\n \"street\": \"Dayna Park\",\r\n \"suite\": \"Suite 449\",\r\n \"city\": \"Bartholomebury\",\r\n \"zipcode\": \"76495-3109\",\r\n \"geo\": {\r\n \"lat\": \"24.6463\",\r\n \"lng\": \"-168.8889\"\r\n }\r\n },\r\n \"phone\": \"(775)976-6794 x41206\",\r\n \"website\": \"conrad.com\",\r\n \"company\": {\r\n \"name\": \"Yost and Sons\",\r\n \"catchPhrase\": \"Switchable contextually-based project\",\r\n \"bs\": \"aggregate real-time technologies\"\r\n }\r\n },\r\n {\r\n \"id\": 10,\r\n \"name\": \"Clementina DuBuque\",\r\n \"username\": \"Moriah.Stanton\",\r\n \"email\": \"Rey.Padberg@karina.biz\",\r\n \"address\": {\r\n \"street\": \"Kattie Turnpike\",\r\n \"suite\": \"Suite 198\",\r\n \"city\": \"Lebsackbury\",\r\n \"zipcode\": \"31428-2261\",\r\n \"geo\": {\r\n \"lat\": \"-38.2386\",\r\n \"lng\": \"57.2232\"\r\n }\r\n },\r\n \"phone\": \"024-648-3804\",\r\n \"website\": \"ambrose.net\",\r\n \"company\": {\r\n \"name\": \"Hoeger LLC\",\r\n \"catchPhrase\": \"Centralized empowering task-force\",\r\n \"bs\": \"target end-to-end models\"\r\n }\r\n }\r\n]".to_string()), - body_type: Some("application/json".to_string()), + body_type: Some(BodyType::Json), }), ]) } @@ -62,10 +62,10 @@ fn feed_keys(widget: &mut CollectionViewer, key_codes: Vec) { #[divan::bench] fn handling_key_events() { let colors = colors::Colors::default(); - let schema = create_sample_schema(); + let collection = create_sample_collection(); let size = Rect::new(0, 0, 80, 24); let config = config::load_config(); - let mut api_explorer = CollectionViewer::new(size, schema, &colors, &config); + let mut api_explorer = CollectionViewer::new(size, collection, &colors, &config); let mut terminal = Terminal::new(TestBackend::new(size.width, size.height)).unwrap(); let mut frame = terminal.get_frame(); @@ -90,10 +90,10 @@ fn handling_key_events() { #[divan::bench] fn creating_with_highlight() { let colors = colors::Colors::default(); - let schema = create_sample_schema(); + let collection = create_sample_collection(); let size = Rect::new(0, 0, 80, 24); let config = config::load_config(); - let mut api_explorer = CollectionViewer::new(size, schema, &colors, &config); + let mut api_explorer = CollectionViewer::new(size, collection, &colors, &config); let mut terminal = Terminal::new(TestBackend::new(size.width, size.height)).unwrap(); let _frame = terminal.get_frame(); diff --git a/tui/src/app.rs b/tui/src/app.rs index 873df25..adb3146 100644 --- a/tui/src/app.rs +++ b/tui/src/app.rs @@ -1,6 +1,6 @@ use crate::{ - components::{Eventful, Page}, event_pool::{Event, EventPool}, + pages::{Eventful, Page}, screen_manager::ScreenManager, }; use ratatui::{backend::CrosstermBackend, Terminal}; @@ -18,12 +18,12 @@ pub struct App<'app> { impl<'app> App<'app> { pub fn new( colors: &'app colors::Colors, - schemas: Vec, + collections: Vec, config: &'app config::Config, ) -> anyhow::Result { let terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?; Ok(Self { - screen_manager: ScreenManager::new(terminal.size()?, colors, schemas, config)?, + screen_manager: ScreenManager::new(terminal.size()?, colors, collections, config)?, event_pool: EventPool::new(60f64, 30f64), should_quit: false, terminal, diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 09d7a0e..1371c14 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -1,5 +1,5 @@ pub mod app; -pub mod components; pub mod event_pool; +pub mod pages; pub mod screen_manager; pub mod utils; diff --git a/tui/src/main.rs b/tui/src/main.rs index 0d4fa2b..40b42a8 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -33,9 +33,9 @@ async fn main() -> anyhow::Result<()> { let _guard = setup_tracing()?; let colors = colors::Colors::default(); - let mut schemas = collection::get_collections_from_config()?; - schemas.sort_by_key(|key| key.info.name.clone()); - let mut app = app::App::new(&colors, schemas, &config)?; + let mut collections = collection::get_collections_from_config()?; + collections.sort_by_key(|key| key.info.name.clone()); + let mut app = app::App::new(&colors, collections, &config)?; app.run().await?; } } diff --git a/tui/src/components.rs b/tui/src/pages.rs similarity index 97% rename from tui/src/components.rs rename to tui/src/pages.rs index 5d7d291..2e9d2b2 100644 --- a/tui/src/components.rs +++ b/tui/src/pages.rs @@ -1,6 +1,6 @@ -pub mod api_explorer; +pub mod collection_dashboard; +pub mod collection_viewer; pub mod confirm_popup; -pub mod dashboard; pub mod error_popup; pub mod input; mod overlay; diff --git a/tui/src/components/dashboard/dashboard.rs b/tui/src/pages/collection_dashboard/collection_dashboard.rs similarity index 99% rename from tui/src/components/dashboard/dashboard.rs rename to tui/src/pages/collection_dashboard/collection_dashboard.rs index cff4b48..f6f2151 100644 --- a/tui/src/components/dashboard/dashboard.rs +++ b/tui/src/pages/collection_dashboard/collection_dashboard.rs @@ -1,9 +1,9 @@ -use crate::components::{ - confirm_popup::ConfirmPopup, - dashboard::{ +use crate::pages::{ + collection_dashboard::{ collection_list::{CollectionList, CollectionListState}, new_collection_form::{FormFocus, FormState, NewCollectionForm}, }, + confirm_popup::ConfirmPopup, error_popup::ErrorPopup, overlay::draw_overlay, Eventful, Page, diff --git a/tui/src/components/dashboard/collection_list.rs b/tui/src/pages/collection_dashboard/collection_list.rs similarity index 100% rename from tui/src/components/dashboard/collection_list.rs rename to tui/src/pages/collection_dashboard/collection_list.rs diff --git a/tui/src/components/dashboard/mod.rs b/tui/src/pages/collection_dashboard/mod.rs similarity index 51% rename from tui/src/components/dashboard/mod.rs rename to tui/src/pages/collection_dashboard/mod.rs index 7510643..b9e9817 100644 --- a/tui/src/components/dashboard/mod.rs +++ b/tui/src/pages/collection_dashboard/mod.rs @@ -1,6 +1,6 @@ -mod collection_list; #[allow(clippy::module_inception)] -mod dashboard; +mod collection_dashboard; +mod collection_list; mod new_collection_form; -pub use dashboard::CollectionDashboard; +pub use collection_dashboard::CollectionDashboard; diff --git a/tui/src/components/dashboard/new_collection_form.rs b/tui/src/pages/collection_dashboard/new_collection_form.rs similarity index 99% rename from tui/src/components/dashboard/new_collection_form.rs rename to tui/src/pages/collection_dashboard/new_collection_form.rs index d12dfcf..3136a2c 100644 --- a/tui/src/components/dashboard/new_collection_form.rs +++ b/tui/src/pages/collection_dashboard/new_collection_form.rs @@ -6,7 +6,7 @@ use ratatui::{ widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph, StatefulWidget, Widget}, }; -use crate::components::input::Input; +use crate::pages::input::Input; #[derive(Debug, Default, PartialEq, Eq)] pub enum FormFocus { diff --git a/tui/src/components/api_explorer/api_explorer.rs b/tui/src/pages/collection_viewer/collection_viewer.rs similarity index 96% rename from tui/src/components/api_explorer/api_explorer.rs rename to tui/src/pages/collection_viewer/collection_viewer.rs index 1905c9e..180acc8 100644 --- a/tui/src/components/api_explorer/api_explorer.rs +++ b/tui/src/pages/collection_viewer/collection_viewer.rs @@ -1,5 +1,5 @@ -use crate::components::{ - api_explorer::{ +use crate::pages::{ + collection_viewer::{ req_editor::{ReqEditor, ReqEditorState, ReqEditorTabs}, req_uri::{ReqUri, ReqUriState}, res_viewer::{ResViewer, ResViewerState, ResViewerTabs}, @@ -19,9 +19,9 @@ use ratatui::{ Frame, }; use reqtui::{ - collection::types::{Collection, Directory, Request, RequestKind, RequestMethod}, + collection::types::{BodyType, Collection, Directory, Request, RequestKind, RequestMethod}, command::Command, - net::request_manager::{ReqtuiNetRequest, ReqtuiResponse}, + net::request_manager::Response, }; use std::{ cell::RefCell, @@ -122,12 +122,12 @@ pub enum CreateReqKind { #[derive(Debug)] pub struct CollectionViewer<'ae> { - schema: Collection, + collection: Collection, colors: &'ae colors::Colors, config: &'ae config::Config, layout: ExplorerLayout, - response_rx: UnboundedReceiver, - request_tx: UnboundedSender, + response_rx: UnboundedReceiver, + request_tx: UnboundedSender, selected_request: Option>>, hovered_request: Option, dirs_expanded: HashMap, @@ -150,19 +150,19 @@ pub struct CollectionViewer<'ae> { sender: Option>, - responses_map: HashMap>>, + responses_map: HashMap>>, } impl<'ae> CollectionViewer<'ae> { pub fn new( size: Rect, - schema: Collection, + collection: Collection, colors: &'ae colors::Colors, config: &'ae config::Config, ) -> Self { let layout = build_layout(size); - let mut selected_request = schema.requests.as_ref().and_then(|requests| { + let mut selected_request = collection.requests.as_ref().and_then(|requests| { requests.first().and_then(|req| { if let RequestKind::Single(req) = req { Some(Rc::new(RefCell::new(req.clone()))) @@ -172,15 +172,15 @@ impl<'ae> CollectionViewer<'ae> { }) }); - let hovered_request = schema + let hovered_request = collection .requests .as_ref() .and_then(|requests| requests.first().cloned()); - let (request_tx, response_rx) = unbounded_channel::(); + let (request_tx, response_rx) = unbounded_channel::(); CollectionViewer { - schema, + collection, focused_pane: PaneFocus::ReqUri, selected_pane: None, colors, @@ -243,8 +243,8 @@ impl<'ae> CollectionViewer<'ae> { KeyCode::Char('j') => { if let Some(ref req) = self.hovered_request { self.hovered_request = find_next_entry( - self.schema.requests.as_ref().context( - "should never have a selected request without any requests on schema", + self.collection.requests.as_ref().context( + "should never have a selected request without any requests on collection", )?, VisitNode::Next, &self.dirs_expanded, @@ -256,8 +256,8 @@ impl<'ae> CollectionViewer<'ae> { KeyCode::Char('k') => { if let Some(ref id) = self.hovered_request { self.hovered_request = find_next_entry( - self.schema.requests.as_ref().context( - "should never have a selected request without any requests on schema", + self.collection.requests.as_ref().context( + "should never have a selected request without any requests on collection", )?, VisitNode::Prev, &self.dirs_expanded, @@ -317,7 +317,7 @@ impl<'ae> CollectionViewer<'ae> { fn draw_sidebar(&mut self, frame: &mut Frame) { let mut state = SidebarState::new( - self.schema.requests.as_deref(), + self.collection.requests.as_deref(), &self.selected_request, self.hovered_request.as_ref(), &mut self.dirs_expanded, @@ -370,7 +370,7 @@ impl<'ae> CollectionViewer<'ae> { } fn drain_response_rx(&mut self) { - while let Ok(ReqtuiNetRequest::Response(res)) = self.response_rx.try_recv() { + while let Ok(res) = self.response_rx.try_recv() { let res = Rc::new(RefCell::new(res)); self.selected_request.as_ref().and_then(|req| { self.responses_map @@ -761,7 +761,7 @@ impl<'ae> CollectionViewer<'ae> { }), }; - self.schema + self.collection .requests .get_or_insert_with(Vec::new) .push(new_request); @@ -769,17 +769,17 @@ impl<'ae> CollectionViewer<'ae> { self.create_req_form_state = CreateReqFormState::default(); self.curr_overlay = Overlays::None; - self.sync_schema_changes(); + self.sync_collection_changes(); } - fn sync_schema_changes(&mut self) { + fn sync_collection_changes(&mut self) { let sender = self .sender .as_ref() .expect("should have a sender at this point") .clone(); - let mut schema = self.schema.clone(); + let mut collection = self.collection.clone(); if let Some(request) = self.selected_request.as_ref() { let mut request = request.borrow().clone(); let body = self.editor.body().to_string(); @@ -787,16 +787,16 @@ impl<'ae> CollectionViewer<'ae> { // body types like GraphQL if !body.is_empty() { request.body = Some(body); - request.body_type = Some("application/json".into()); + request.body_type = Some(BodyType::Json) } // we might later on decide to keep track of the actual dir/request index // so we dont have to go over all the possible requests, this might be a // problem for huge collections, but I haven't tested - schema + collection .requests .as_mut() - .expect("no requests on schema, but we have a selected request") + .expect("no requests on collection, but we have a selected request") .iter_mut() .for_each(|other| match other { RequestKind::Single(inner) => { @@ -812,7 +812,7 @@ impl<'ae> CollectionViewer<'ae> { self.sync_interval = std::time::Instant::now(); tokio::spawn(async move { - match reqtui::fs::sync_collection(schema).await { + match reqtui::fs::sync_collection(collection).await { Ok(_) => {} Err(e) => { if sender.send(Command::Error(e.to_string())).is_err() { @@ -880,7 +880,7 @@ impl Page for CollectionViewer<'_> { fn handle_tick(&mut self) -> anyhow::Result<()> { if self.sync_interval.elapsed().as_secs().ge(&5) { - self.sync_schema_changes(); + self.sync_collection_changes(); } Ok(()) } diff --git a/tui/src/components/api_explorer/mod.rs b/tui/src/pages/collection_viewer/mod.rs similarity index 58% rename from tui/src/components/api_explorer/mod.rs rename to tui/src/pages/collection_viewer/mod.rs index 6dc0c10..cce6b4d 100644 --- a/tui/src/components/api_explorer/mod.rs +++ b/tui/src/pages/collection_viewer/mod.rs @@ -1,8 +1,8 @@ #[allow(clippy::module_inception)] -mod api_explorer; +mod collection_viewer; mod req_editor; mod req_uri; mod res_viewer; mod sidebar; -pub use api_explorer::CollectionViewer; +pub use collection_viewer::CollectionViewer; diff --git a/tui/src/components/api_explorer/req_editor.rs b/tui/src/pages/collection_viewer/req_editor.rs similarity index 99% rename from tui/src/components/api_explorer/req_editor.rs rename to tui/src/pages/collection_viewer/req_editor.rs index b2ed52e..7ea97bb 100644 --- a/tui/src/components/api_explorer/req_editor.rs +++ b/tui/src/pages/collection_viewer/req_editor.rs @@ -1,4 +1,4 @@ -use crate::{components::Eventful, utils::build_syntax_highlighted_lines}; +use crate::{pages::Eventful, utils::build_syntax_highlighted_lines}; use config::{Action, EditorMode, KeyAction}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ratatui::{ diff --git a/tui/src/components/api_explorer/req_uri.rs b/tui/src/pages/collection_viewer/req_uri.rs similarity index 100% rename from tui/src/components/api_explorer/req_uri.rs rename to tui/src/pages/collection_viewer/req_uri.rs diff --git a/tui/src/components/api_explorer/res_viewer.rs b/tui/src/pages/collection_viewer/res_viewer.rs similarity index 98% rename from tui/src/components/api_explorer/res_viewer.rs rename to tui/src/pages/collection_viewer/res_viewer.rs index a631292..f2e870a 100644 --- a/tui/src/components/api_explorer/res_viewer.rs +++ b/tui/src/pages/collection_viewer/res_viewer.rs @@ -1,4 +1,4 @@ -use reqtui::{net::request_manager::ReqtuiResponse, syntax::highlighter::HIGHLIGHTER}; +use reqtui::{net::request_manager::Response, syntax::highlighter::HIGHLIGHTER}; use crate::utils::build_syntax_highlighted_lines; use ratatui::{ @@ -89,13 +89,13 @@ impl<'a> ResViewerState<'a> { #[derive(Debug, Clone)] pub struct ResViewer<'a> { colors: &'a colors::Colors, - response: Option>>, + response: Option>>, tree: Option, lines: Vec>, } impl<'a> ResViewer<'a> { - pub fn new(colors: &'a colors::Colors, response: Option>>) -> Self { + pub fn new(colors: &'a colors::Colors, response: Option>>) -> Self { let tree = response.as_ref().and_then(|response| { let pretty_body = response.borrow().pretty_body.to_string(); let mut highlighter = HIGHLIGHTER.write().unwrap(); @@ -110,7 +110,7 @@ impl<'a> ResViewer<'a> { } } - pub fn update(&mut self, response: Option>>) { + pub fn update(&mut self, response: Option>>) { self.tree = response.as_ref().and_then(|response| { let pretty_body = response.borrow().pretty_body.to_string(); let mut highlighter = HIGHLIGHTER.write().unwrap(); diff --git a/tui/src/components/api_explorer/sidebar.rs b/tui/src/pages/collection_viewer/sidebar.rs similarity index 100% rename from tui/src/components/api_explorer/sidebar.rs rename to tui/src/pages/collection_viewer/sidebar.rs diff --git a/tui/src/components/confirm_popup.rs b/tui/src/pages/confirm_popup.rs similarity index 100% rename from tui/src/components/confirm_popup.rs rename to tui/src/pages/confirm_popup.rs diff --git a/tui/src/components/error_popup.rs b/tui/src/pages/error_popup.rs similarity index 100% rename from tui/src/components/error_popup.rs rename to tui/src/pages/error_popup.rs diff --git a/tui/src/components/input.rs b/tui/src/pages/input.rs similarity index 100% rename from tui/src/components/input.rs rename to tui/src/pages/input.rs diff --git a/tui/src/components/overlay.rs b/tui/src/pages/overlay.rs similarity index 100% rename from tui/src/components/overlay.rs rename to tui/src/pages/overlay.rs diff --git a/tui/src/components/terminal_too_small.rs b/tui/src/pages/terminal_too_small.rs similarity index 98% rename from tui/src/components/terminal_too_small.rs rename to tui/src/pages/terminal_too_small.rs index 28bc294..bea4e2c 100644 --- a/tui/src/components/terminal_too_small.rs +++ b/tui/src/pages/terminal_too_small.rs @@ -1,4 +1,4 @@ -use crate::components::Page; +use crate::pages::Page; use ratatui::{ layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, style::Stylize, diff --git a/tui/src/screen_manager.rs b/tui/src/screen_manager.rs index 8272f86..9ae7049 100644 --- a/tui/src/screen_manager.rs +++ b/tui/src/screen_manager.rs @@ -1,9 +1,9 @@ use crate::{ - components::{ - api_explorer::CollectionViewer, dashboard::CollectionDashboard, + event_pool::Event, + pages::{ + collection_dashboard::CollectionDashboard, collection_viewer::CollectionViewer, terminal_too_small::TerminalTooSmall, Eventful, Page, }, - event_pool::Event, }; use reqtui::{collection::Collection, command::Command}; diff --git a/tui/tests/dashboard.rs b/tui/tests/collection_dashboard.rs similarity index 99% rename from tui/tests/dashboard.rs rename to tui/tests/collection_dashboard.rs index 6545dde..f0c43a5 100644 --- a/tui/tests/dashboard.rs +++ b/tui/tests/collection_dashboard.rs @@ -6,7 +6,7 @@ use std::{ io::Write, }; use tempfile::{tempdir, TempDir}; -use tui::components::{dashboard::CollectionDashboard, Eventful, Page}; +use tui::pages::{collection_dashboard::CollectionDashboard, Eventful, Page}; fn setup_temp_collections(amount: usize) -> (TempDir, String) { let tmp_data_dir = tempdir().expect("Failed to create temp data dir");