diff --git a/package.json b/package.json index bd88c60d..748994b1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "tauri-dev": "tauri dev", + "tauri-dev": "YAAK_ENV=development tauri dev", "tauri-build": "npm run build:icon && tauri build", "build": "npm run build:frontend", "dev": "vite dev", diff --git a/src-tauri/migrations/20230314160400_add-body-type.sql b/src-tauri/migrations/20230314160400_add-body-type.sql new file mode 100644 index 00000000..77d27a1b --- /dev/null +++ b/src-tauri/migrations/20230314160400_add-body-type.sql @@ -0,0 +1 @@ +ALTER TABLE http_requests ADD COLUMN body_type TEXT NULL; diff --git a/src-tauri/sqlx-data.json b/src-tauri/sqlx-data.json index c04253b8..20a1790f 100644 --- a/src-tauri/sqlx-data.json +++ b/src-tauri/sqlx-data.json @@ -20,78 +20,6 @@ }, "query": "\n DELETE FROM http_responses\n WHERE request_id = ?\n " }, - "3d2a542964d946ff9854d053b1adf04985d97a6de27b713188505c1f99c77707": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "workspace_id", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Datetime" - }, - { - "name": "deleted_at", - "ordinal": 4, - "type_info": "Datetime" - }, - { - "name": "name", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "url", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "method", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "body", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "headers!: sqlx::types::Json>", - "ordinal": 9, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - false, - true, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " - }, "448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": { "describe": { "columns": [], @@ -102,7 +30,7 @@ }, "query": "\n DELETE FROM http_requests\n WHERE id = ?\n " }, - "7ec60cbc3c9f26e8af86a21ef6b66e564f4fa518925c92308b04f882237a244e": { + "795391626a74376de88fb8ff015f99fb10d6d8a5c9a0623dc279e833e8e86a07": { "describe": { "columns": [ { @@ -151,9 +79,14 @@ "type_info": "Text" }, { - "name": "headers!: sqlx::types::Json>", + "name": "body_type", "ordinal": 9, "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 10, + "type_info": "Text" } ], "nullable": [ @@ -166,13 +99,14 @@ false, false, true, + true, false ], "parameters": { "Right": 1 } }, - "query": "\n SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n ORDER BY created_at DESC\n " + "query": "\n SELECT\n id,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE id = ?\n ORDER BY created_at DESC\n " }, "7f623d0e8f1ddad33d356e2d159b776a2bef1a238cb9200d74eb0c5e3983df85": { "describe": { @@ -312,16 +246,6 @@ }, "query": "\n SELECT id, created_at, updated_at, deleted_at, name, description\n FROM workspaces\n " }, - "a097740ea4ab772ec6f9d8a5144d6871e0b172130d5abe4da61e663155d2bf25": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 7 - } - }, - "query": "\n INSERT INTO http_requests (id, workspace_id, name, url, method, body, headers, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n url = excluded.url\n " - }, "a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": { "describe": { "columns": [], @@ -332,6 +256,84 @@ }, "query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n " }, + "d572673b8eadbd5238c7a972b581fa8788f92a44fcc8decd0f1333e662f3374b": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "workspace_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 2, + "type_info": "Datetime" + }, + { + "name": "updated_at", + "ordinal": 3, + "type_info": "Datetime" + }, + { + "name": "deleted_at", + "ordinal": 4, + "type_info": "Datetime" + }, + { + "name": "name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "url", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "method", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "body", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "body_type", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "headers!: sqlx::types::Json>", + "ordinal": 10, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT\n id,\n workspace_id,\n created_at,\n updated_at,\n deleted_at,\n name,\n url,\n method,\n body,\n body_type,\n headers AS \"headers!: sqlx::types::Json>\"\n FROM http_requests\n WHERE workspace_id = ?\n " + }, "e767522f92c8c49cd2e563e58737a05092daf9b1dc763bacc82a5c14d696d78e": { "describe": { "columns": [], @@ -342,6 +344,16 @@ }, "query": "\n INSERT INTO http_responses (id, request_id, workspace_id, elapsed, url, status, status_reason, body, headers)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n " }, + "f0bc12d939b0f26774a3e599be7518510d298f327cca8743231242dd6105768c": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 8 + } + }, + "query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n headers,\n updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n url = excluded.url\n " + }, "f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": { "describe": { "columns": [], diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6b60d970..78ad3349 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,6 +8,8 @@ windows_subsystem = "windows" extern crate objc; use std::collections::HashMap; +use std::env; +use std::env::current_dir; use std::fs::create_dir_all; use http::header::{ACCEPT, HeaderName, USER_AGENT}; @@ -202,7 +204,7 @@ async fn create_request( let pool = &*db_instance.lock().await; let headers = Vec::new(); let created_request = - models::upsert_request(None, workspace_id, name, "GET", None, "", headers, pool) + models::upsert_request(None, workspace_id, name, "GET", None, None, "", headers, pool) .await .expect("Failed to create request"); @@ -237,6 +239,7 @@ async fn update_request( request.name.as_str(), request.method.as_str(), body, + request.body_type, request.url.as_str(), request.headers.0, pool, @@ -359,7 +362,11 @@ fn main() { Ok(()) }) .setup(|app| { - let dir = app.path_resolver().app_data_dir().unwrap(); + let dir = match is_dev() { + true => current_dir().unwrap(), + false => app.path_resolver().app_data_dir().unwrap(), + }; + create_dir_all(dir.clone()).expect("Problem creating App directory!"); let p = dir.join("db.sqlite"); let p_string = p.to_string_lossy().replace(' ', "%20"); @@ -431,3 +438,8 @@ fn main() { .run(tauri::generate_context!()) .expect("error while running tauri application"); } + +fn is_dev() -> bool { + let env = option_env!("YAAK_ENV"); + env.unwrap_or("production") == "development" +} diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 97e2cbda..0ab91f96 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -34,6 +34,7 @@ pub struct HttpRequest { pub url: String, pub method: String, pub body: Option, + pub body_type: Option, pub headers: Json>, } @@ -116,6 +117,7 @@ pub async fn upsert_request( name: &str, method: &str, body: Option<&str>, + body_type: Option, url: &str, headers: Vec, pool: &Pool, @@ -131,14 +133,25 @@ pub async fn upsert_request( let headers_json = Json(headers); sqlx::query!( r#" - INSERT INTO http_requests (id, workspace_id, name, url, method, body, headers, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + INSERT INTO http_requests ( + id, + workspace_id, + name, + url, + method, + body, + body_type, + headers, + updated_at + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (id) DO UPDATE SET updated_at = CURRENT_TIMESTAMP, name = excluded.name, method = excluded.method, headers = excluded.headers, body = excluded.body, + body_type = excluded.body_type, url = excluded.url "#, id, @@ -147,6 +160,7 @@ pub async fn upsert_request( url, method, body, + body_type, headers_json, ) .execute(pool) @@ -162,7 +176,17 @@ pub async fn find_requests( sqlx::query_as!( HttpRequest, r#" - SELECT id, workspace_id, created_at, updated_at, deleted_at, name, url, method, body, + SELECT + id, + workspace_id, + created_at, + updated_at, + deleted_at, + name, + url, + method, + body, + body_type, headers AS "headers!: sqlx::types::Json>" FROM http_requests WHERE workspace_id = ? @@ -177,7 +201,17 @@ pub async fn get_request(id: &str, pool: &Pool) -> Result>" FROM http_requests WHERE id = ? diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index 7d67a6c2..692efcd3 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -34,7 +34,19 @@ export function RequestPane({ fullHeight, className }: Props) { updateRequest.mutate({ bodyType: bodyType.value }), + value: activeRequest.bodyType ?? 'nobody', + items: [ + { label: 'No Body', value: 'nobody' }, + { label: 'JSON', value: 'json' }, + { label: 'GraphQL', value: 'graphql' }, + ], + }, + }, { value: 'params', label: 'Params' }, { value: 'headers', label: 'Headers' }, { value: 'auth', label: 'Auth' }, @@ -48,15 +60,29 @@ export function RequestPane({ fullHeight, className }: Props) { - updateRequest.mutate({ body })} - /> + {activeRequest.bodyType === 'json' ? ( + updateRequest.mutate({ body })} + /> + ) : activeRequest.bodyType === 'graphql' ? ( + updateRequest.mutate({ body })} + /> + ) : ( +
No Body
+ )}
diff --git a/src-web/components/ResponsePane.tsx b/src-web/components/ResponsePane.tsx index 8f603b30..f2bb314d 100644 --- a/src-web/components/ResponsePane.tsx +++ b/src-web/components/ResponsePane.tsx @@ -5,7 +5,7 @@ import { useDeleteResponse } from '../hooks/useResponseDelete'; import { useResponses } from '../hooks/useResponses'; import { tryFormatJson } from '../lib/formatters'; import type { HttpResponse } from '../lib/models'; -import { Dropdown } from './core/Dropdown'; +import { Dropdown, DropdownMenuTrigger } from './core/Dropdown'; import { Editor } from './core/Editor'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; @@ -97,7 +97,9 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) { })), ]} > - + + + diff --git a/src-web/components/core/Button.tsx b/src-web/components/core/Button.tsx index c313856b..e4d66a2f 100644 --- a/src-web/components/core/Button.tsx +++ b/src-web/components/core/Button.tsx @@ -21,6 +21,7 @@ export type ButtonProps = HTMLAttributes & { justify?: 'start' | 'center'; type?: 'button' | 'submit'; forDropdown?: boolean; + disabled?: boolean; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx index 7c56e27b..5a8c14aa 100644 --- a/src-web/components/core/Dropdown.tsx +++ b/src-web/components/core/Dropdown.tsx @@ -2,18 +2,20 @@ import * as D from '@radix-ui/react-dropdown-menu'; import { CheckIcon } from '@radix-ui/react-icons'; import classnames from 'classnames'; import { motion } from 'framer-motion'; -import type { ReactNode, ForwardedRef } from 'react'; +import type { ForwardedRef, ReactElement, ReactNode } from 'react'; import { forwardRef, useImperativeHandle, useLayoutEffect, useState } from 'react'; -interface DropdownMenuRadioProps { - children: ReactNode; - onValueChange: ((v: { label: string; value: string }) => void) | null; +export interface DropdownMenuRadioItem { + label: string; + value: string; +} + +export interface DropdownMenuRadioProps { + children: ReactElement; + onValueChange: ((v: DropdownMenuRadioItem) => void) | null; value: string; label?: string; - items: { - label: string; - value: string; - }[]; + items: DropdownMenuRadioItem[]; } export function DropdownMenuRadio({ @@ -32,7 +34,7 @@ export function DropdownMenuRadio({ return ( - {children} + {children} {label && {label}} @@ -50,7 +52,7 @@ export function DropdownMenuRadio({ } export interface DropdownProps { - children: ReactNode; + children: ReactElement; items: ( | { label: string; @@ -65,7 +67,7 @@ export interface DropdownProps { export function Dropdown({ children, items }: DropdownProps) { return ( - {children} + {children} {items.map((item, i) => { @@ -268,7 +270,7 @@ type DropdownMenuTriggerProps = D.DropdownMenuTriggerProps & { className?: string; }; -function DropdownMenuTrigger({ children, className, ...props }: DropdownMenuTriggerProps) { +export function DropdownMenuTrigger({ children, className, ...props }: DropdownMenuTriggerProps) { return ( {children} diff --git a/src-web/components/core/Tabs/Tabs.tsx b/src-web/components/core/Tabs/Tabs.tsx index 17d09184..573e81f3 100644 --- a/src-web/components/core/Tabs/Tabs.tsx +++ b/src-web/components/core/Tabs/Tabs.tsx @@ -1,9 +1,11 @@ import * as T from '@radix-ui/react-tabs'; import classnames from 'classnames'; -import type { ReactNode } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import { useState } from 'react'; import { Button } from '../Button'; -import { ScrollArea } from '../ScrollArea'; +import type { DropdownMenuRadioItem, DropdownMenuRadioProps } from '../Dropdown'; +import { DropdownMenuRadio, DropdownMenuTrigger } from '../Dropdown'; +import { Icon } from '../Icon'; import { HStack } from '../Stacks'; import './Tabs.css'; @@ -11,7 +13,15 @@ import './Tabs.css'; interface Props { defaultValue?: string; label: string; - tabs: { value: string; label: ReactNode }[]; + tabs: { + value: string; + label: string; + options?: { + onValueChange: DropdownMenuRadioProps['onValueChange']; + value: string; + items: DropdownMenuRadioItem[]; + }; + }[]; tabListClassName?: string; className?: string; children: ReactNode; @@ -32,43 +42,72 @@ export function Tabs({ defaultValue, label, children, tabs, className, tabListCl 'h-auto flex items-center overflow-x-auto mb-1 pb-1', )} > - {/**/} - {tabs.map((t) => ( - - {t.label} - - ))} + {tabs.map((t) => { + const isActive = t.value === value; + if (t.options && isActive) { + return ( + + + + + + ); + } else if (t.options && !isActive) { + return ( + + + + ); + } else { + return ( + + + + ); + } + })} - {/**/} {children} ); } -interface TabTriggerProps { - value: string; - children: ReactNode; - active?: boolean; -} - -export function TabTrigger({ value, children, active }: TabTriggerProps) { - return ( - - - - ); -} - interface TabContentProps { value: string; children: ReactNode; diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index 7a9aec9c..67731dc5 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -20,6 +20,7 @@ export interface HttpRequest extends BaseModel { name: string; url: string; body: string | null; + bodyType: string | null; method: string; headers: HttpHeader[]; }