mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-19 23:41:18 +02:00
Remove response body and basic hotkeys
This commit is contained in:
1550
package-lock.json
generated
1550
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,7 @@
|
|||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"postcss-nesting": "^11.2.1",
|
"postcss-nesting": "^11.2.1",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
|
"react-devtools": "^4.28.5",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.0.0",
|
"vite": "^4.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n ",
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -53,34 +53,29 @@
|
|||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "body_path",
|
"name": "body_path",
|
||||||
"ordinal": 11,
|
"ordinal": 10,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "elapsed",
|
"name": "elapsed",
|
||||||
"ordinal": 12,
|
"ordinal": 11,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "error",
|
"name": "error",
|
||||||
"ordinal": 13,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||||
"ordinal": 14,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Right": 2
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
@@ -94,11 +89,10 @@
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "26072725d536c3cfdffd9a681d17c0ee2f246ca98e0459630a2430236d3bbdd2"
|
"hash": "07b0c398efd1d5f8f479652de658716a9e7faef6aba6583dd209a4f290c5edd1"
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ",
|
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 11
|
"Right": 10
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c"
|
"hash": "198bd086ccc87d2e6c24cb1c717f486d3ab58c0c958ede850c018fc266eade87"
|
||||||
}
|
}
|
||||||
12
src-tauri/.sqlx/query-294cbe19f9ddd9519ace3558df4308948082ec0ce7096855aa7d8fba519b8b4f.json
generated
Normal file
12
src-tauri/.sqlx/query-294cbe19f9ddd9519ace3558df4308948082ec0ce7096855aa7d8fba519b8b4f.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE http_responses SET (\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body_path,\n error,\n headers,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 9
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "294cbe19f9ddd9519ace3558df4308948082ec0ce7096855aa7d8fba519b8b4f"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n ",
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE workspace_id = ?\n ORDER BY created_at DESC\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -53,29 +53,24 @@
|
|||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "body_path",
|
"name": "body_path",
|
||||||
"ordinal": 11,
|
"ordinal": 10,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "elapsed",
|
"name": "elapsed",
|
||||||
"ordinal": 12,
|
"ordinal": 11,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "error",
|
"name": "error",
|
||||||
"ordinal": 13,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||||
"ordinal": 14,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -94,11 +89,10 @@
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "c23c61b05a4c9e04ab0c1fc2c579d6f2a82a37aeed8addf9861b4985f2a5422e"
|
"hash": "3d199d371be948211f4a50c869b307f5df60784293c52397d77a187633a406dd"
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n UPDATE http_responses SET (\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n error,\n headers,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 10
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "62475fd9483fb5eda01c937949da2ef66ac7005b4be06b87aa6210d462348aca"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at DESC\n ",
|
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -53,29 +53,24 @@
|
|||||||
"ordinal": 9,
|
"ordinal": 9,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "body_path",
|
"name": "body_path",
|
||||||
"ordinal": 11,
|
"ordinal": 10,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "elapsed",
|
"name": "elapsed",
|
||||||
"ordinal": 12,
|
"ordinal": 11,
|
||||||
"type_info": "Int64"
|
"type_info": "Int64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "error",
|
"name": "error",
|
||||||
"ordinal": 13,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||||
"ordinal": 14,
|
"ordinal": 13,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -94,11 +89,10 @@
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "5aa070e61995f8b1724efaa94c5f0cef5a4be6efda5d70354ad449d7d4b5aee4"
|
"hash": "679a519475adeb50abf046114d3c0d1e48e103f2bb11ef47637d7f0b00ed241f"
|
||||||
}
|
}
|
||||||
1
src-tauri/migrations/20231122055216_remove_body.sql
Normal file
1
src-tauri/migrations/20231122055216_remove_body.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE http_responses DROP COLUMN body;
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ESNext"
|
"ESNext",
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
@@ -18,6 +18,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"./src"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,20 @@ use fern::colors::ColoredLevelConfig;
|
|||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use sqlx::{Pool, Sqlite, SqlitePool};
|
||||||
use sqlx::migrate::Migrator;
|
use sqlx::migrate::Migrator;
|
||||||
use sqlx::types::Json;
|
use sqlx::types::Json;
|
||||||
use sqlx::{Pool, Sqlite, SqlitePool};
|
use tauri::{AppHandle, RunEvent, State, Window, WindowUrl, Wry};
|
||||||
|
use tauri::{Manager, WindowEvent};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::TitleBarStyle;
|
use tauri::TitleBarStyle;
|
||||||
use tauri::{AppHandle, Menu, RunEvent, State, Submenu, Window, WindowUrl, Wry};
|
|
||||||
use tauri::{CustomMenuItem, Manager, WindowEvent};
|
|
||||||
use tauri_plugin_log::{fern, LogTarget};
|
use tauri_plugin_log::{fern, LogTarget};
|
||||||
use tauri_plugin_window_state::{StateFlags, WindowExt};
|
use tauri_plugin_window_state::{StateFlags, WindowExt};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use window_ext::TrafficLightWindowExt;
|
use window_ext::TrafficLightWindowExt;
|
||||||
|
|
||||||
use crate::analytics::{track_event, AnalyticsAction, AnalyticsResource};
|
use crate::analytics::{AnalyticsAction, AnalyticsResource, track_event};
|
||||||
use crate::plugin::{ImportResources, ImportResult};
|
use crate::plugin::{ImportResources, ImportResult};
|
||||||
use crate::send::actually_send_request;
|
use crate::send::actually_send_request;
|
||||||
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
|
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
|
||||||
@@ -186,7 +186,7 @@ async fn send_request(
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to get request");
|
.expect("Failed to get request");
|
||||||
|
|
||||||
let response = models::create_response(&req.id, 0, "", 0, None, None, None, None, vec![], pool)
|
let response = models::create_response(&req.id, 0, "", 0, None, None, None, vec![], pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create response");
|
.expect("Failed to create response");
|
||||||
|
|
||||||
@@ -551,10 +551,11 @@ async fn get_workspace(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn list_responses(
|
async fn list_responses(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
|
limit: Option<i64>,
|
||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<Vec<models::HttpResponse>, String> {
|
) -> Result<Vec<models::HttpResponse>, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
models::find_responses(request_id, pool)
|
models::find_responses(request_id, limit, pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ pub struct HttpResponse {
|
|||||||
pub elapsed: i64,
|
pub elapsed: i64,
|
||||||
pub status: i64,
|
pub status: i64,
|
||||||
pub status_reason: Option<String>,
|
pub status_reason: Option<String>,
|
||||||
pub body: Option<Vec<u8>>,
|
|
||||||
pub body_path: Option<String>,
|
pub body_path: Option<String>,
|
||||||
pub headers: Json<Vec<HttpResponseHeader>>,
|
pub headers: Json<Vec<HttpResponseHeader>>,
|
||||||
}
|
}
|
||||||
@@ -594,7 +593,6 @@ pub async fn create_response(
|
|||||||
status: i64,
|
status: i64,
|
||||||
status_reason: Option<&str>,
|
status_reason: Option<&str>,
|
||||||
content_length: Option<i64>,
|
content_length: Option<i64>,
|
||||||
body: Option<Vec<u8>>,
|
|
||||||
body_path: Option<&str>,
|
body_path: Option<&str>,
|
||||||
headers: Vec<HttpResponseHeader>,
|
headers: Vec<HttpResponseHeader>,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
@@ -613,11 +611,10 @@ pub async fn create_response(
|
|||||||
status,
|
status,
|
||||||
status_reason,
|
status_reason,
|
||||||
content_length,
|
content_length,
|
||||||
body,
|
|
||||||
body_path,
|
body_path,
|
||||||
headers
|
headers
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
request_id,
|
request_id,
|
||||||
@@ -627,7 +624,6 @@ pub async fn create_response(
|
|||||||
status,
|
status,
|
||||||
status_reason,
|
status_reason,
|
||||||
content_length,
|
content_length,
|
||||||
body,
|
|
||||||
body_path,
|
body_path,
|
||||||
headers_json,
|
headers_json,
|
||||||
)
|
)
|
||||||
@@ -704,19 +700,17 @@ pub async fn update_response(
|
|||||||
status,
|
status,
|
||||||
status_reason,
|
status_reason,
|
||||||
content_length,
|
content_length,
|
||||||
body,
|
|
||||||
body_path,
|
body_path,
|
||||||
error,
|
error,
|
||||||
headers,
|
headers,
|
||||||
updated_at
|
updated_at
|
||||||
) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
) = (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||||
"#,
|
"#,
|
||||||
response.elapsed,
|
response.elapsed,
|
||||||
response.url,
|
response.url,
|
||||||
response.status,
|
response.status,
|
||||||
response.status_reason,
|
response.status_reason,
|
||||||
response.content_length,
|
response.content_length,
|
||||||
response.body,
|
|
||||||
response.body_path,
|
response.body_path,
|
||||||
response.error,
|
response.error,
|
||||||
headers_json,
|
headers_json,
|
||||||
@@ -732,7 +726,7 @@ pub async fn get_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse,
|
|||||||
HttpResponse,
|
HttpResponse,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
||||||
status, status_reason, content_length, body, body_path, elapsed, error,
|
status, status_reason, content_length, body_path, elapsed, error,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||||
FROM http_responses
|
FROM http_responses
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -745,19 +739,26 @@ pub async fn get_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse,
|
|||||||
|
|
||||||
pub async fn find_responses(
|
pub async fn find_responses(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
|
limit: Option<i64>,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<Vec<HttpResponse>, sqlx::Error> {
|
) -> Result<Vec<HttpResponse>, sqlx::Error> {
|
||||||
|
let limit_unwrapped = match limit {
|
||||||
|
Some(l) => l,
|
||||||
|
None => i64::MAX,
|
||||||
|
};
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
||||||
status, status_reason, content_length, body, body_path, elapsed, error,
|
status, status_reason, content_length, body_path, elapsed, error,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||||
FROM http_responses
|
FROM http_responses
|
||||||
WHERE request_id = ?
|
WHERE request_id = ?
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
|
LIMIT ?
|
||||||
"#,
|
"#,
|
||||||
request_id,
|
request_id,
|
||||||
|
limit_unwrapped,
|
||||||
)
|
)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
@@ -771,7 +772,7 @@ pub async fn find_responses_by_workspace_id(
|
|||||||
HttpResponse,
|
HttpResponse,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
||||||
status, status_reason, content_length, body, body_path, elapsed, error,
|
status, status_reason, content_length, body_path, elapsed, error,
|
||||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||||
FROM http_responses
|
FROM http_responses
|
||||||
WHERE workspace_id = ?
|
WHERE workspace_id = ?
|
||||||
@@ -810,7 +811,7 @@ pub async fn delete_all_responses(
|
|||||||
request_id: &str,
|
request_id: &str,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
for r in find_responses(request_id, pool).await? {
|
for r in find_responses(request_id, None, pool).await? {
|
||||||
delete_response(&r.id, pool).await?;
|
delete_response(&r.id, pool).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -228,11 +228,6 @@ pub async fn actually_send_request(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also store body directly on the model, if small enough
|
|
||||||
if body_bytes.len() < 100_000 {
|
|
||||||
response.body = Some(body_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.elapsed = start.elapsed().as_millis() as i64;
|
response.elapsed = start.elapsed().as_millis() as i64;
|
||||||
response = models::update_response_if_id(&response, pool)
|
response = models::update_response_if_id(&response, pool)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -92,9 +92,11 @@ export function GlobalHooks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldIgnoreModel(payload)) {
|
if (!shouldIgnoreModel(payload)) {
|
||||||
|
console.time('set query date');
|
||||||
queryClient.setQueryData<Model[]>(queryKey, (values) =>
|
queryClient.setQueryData<Model[]>(queryKey, (values) =>
|
||||||
values?.map((v) => (modelsEq(v, payload) ? payload : v)),
|
values?.map((v) => (modelsEq(v, payload) ? payload : v)),
|
||||||
);
|
);
|
||||||
|
console.timeEnd('set query date');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
|
||||||
import { appWindow } from '@tauri-apps/api/window';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
@@ -47,7 +43,6 @@ const useActiveTab = createGlobalState<string>('body');
|
|||||||
export const RequestPane = memo(function RequestPane({ style, fullHeight, className }: Props) {
|
export const RequestPane = memo(function RequestPane({ style, fullHeight, className }: Props) {
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const activeRequestId = activeRequest?.id ?? null;
|
const activeRequestId = activeRequest?.id ?? null;
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
|
||||||
const updateRequest = useUpdateRequest(activeRequestId);
|
const updateRequest = useUpdateRequest(activeRequestId);
|
||||||
const [activeTab, setActiveTab] = useActiveTab();
|
const [activeTab, setActiveTab] = useActiveTab();
|
||||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||||
@@ -183,18 +178,6 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
[updateRequest],
|
[updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
useListenToTauriEvent(
|
|
||||||
'send_request',
|
|
||||||
async ({ windowLabel }) => {
|
|
||||||
if (windowLabel !== appWindow.label) return;
|
|
||||||
await invoke('send_request', {
|
|
||||||
requestId: activeRequestId,
|
|
||||||
environmentId: activeEnvironmentId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[activeRequestId, activeEnvironmentId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useCallback, memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||||
@@ -18,12 +18,12 @@ import { StatusTag } from './core/StatusTag';
|
|||||||
import type { TabItem } from './core/Tabs/Tabs';
|
import type { TabItem } from './core/Tabs/Tabs';
|
||||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||||
import { EmptyStateText } from './EmptyStateText';
|
import { EmptyStateText } from './EmptyStateText';
|
||||||
|
import { RecentResponsesDropdown } from './RecentResponsesDropdown';
|
||||||
import { ResponseHeaders } from './ResponseHeaders';
|
import { ResponseHeaders } from './ResponseHeaders';
|
||||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||||
import { TextViewer } from './responseViewers/TextViewer';
|
import { TextViewer } from './responseViewers/TextViewer';
|
||||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||||
import { RecentResponsesDropdown } from './RecentResponsesDropdown';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -48,11 +48,14 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
|
|
||||||
const contentType = useResponseContentType(activeResponse);
|
const contentType = useResponseContentType(activeResponse);
|
||||||
|
|
||||||
const handlePinnedResponse = useCallback((r: HttpResponse) => {
|
const handlePinnedResponse = useCallback(
|
||||||
setPinnedResponseId(r.id);
|
(r: HttpResponse) => {
|
||||||
}, [setPinnedResponseId])
|
setPinnedResponseId(r.id);
|
||||||
|
},
|
||||||
|
[setPinnedResponseId],
|
||||||
|
);
|
||||||
|
|
||||||
const tabs: TabItem[] = useMemo(
|
const tabs = useMemo<TabItem[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
value: 'body',
|
value: 'body',
|
||||||
@@ -62,7 +65,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
onChange: setViewMode,
|
onChange: setViewMode,
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Pretty', value: 'pretty' },
|
{ label: 'Pretty', value: 'pretty' },
|
||||||
{ label: 'Raw', value: 'raw' },
|
...(contentType?.startsWith('image') ? [] : [{ label: 'Raw', value: 'raw' }]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -78,7 +81,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
value: 'headers',
|
value: 'headers',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[activeResponse?.headers, setViewMode, viewMode],
|
[activeResponse?.headers, contentType, setViewMode, viewMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -145,10 +148,14 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
<TabContent value="body">
|
<TabContent value="body">
|
||||||
{!activeResponse.contentLength ? (
|
{!activeResponse.contentLength ? (
|
||||||
<EmptyStateText>Empty Body</EmptyStateText>
|
<EmptyStateText>Empty Body</EmptyStateText>
|
||||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
|
||||||
<WebPageViewer response={activeResponse} />
|
|
||||||
) : contentType?.startsWith('image') ? (
|
) : contentType?.startsWith('image') ? (
|
||||||
<ImageViewer className="pb-2" response={activeResponse} />
|
<ImageViewer className="pb-2" response={activeResponse} />
|
||||||
|
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
||||||
|
<div className="text-sm italic text-gray-500">
|
||||||
|
Cannot preview text responses larger than 2MB
|
||||||
|
</div>
|
||||||
|
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||||
|
<WebPageViewer response={activeResponse} />
|
||||||
) : contentType?.match(/csv|tab-separated/) ? (
|
) : contentType?.match(/csv|tab-separated/) ? (
|
||||||
<CsvViewer className="pb-2" response={activeResponse} />
|
<CsvViewer className="pb-2" response={activeResponse} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import { useCreateRequest } from '../hooks/useCreateRequest';
|
|||||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
||||||
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
||||||
import { useFolders } from '../hooks/useFolders';
|
import { useFolders } from '../hooks/useFolders';
|
||||||
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { useSendManyRequests } from '../hooks/useSendFolder';
|
import { useSendManyRequests } from '../hooks/useSendFolder';
|
||||||
@@ -52,7 +52,6 @@ interface TreeNode {
|
|||||||
|
|
||||||
export function Sidebar({ className }: Props) {
|
export function Sidebar({ className }: Props) {
|
||||||
const { hidden } = useSidebarHidden();
|
const { hidden } = useSidebarHidden();
|
||||||
const createRequest = useCreateRequest();
|
|
||||||
const sidebarRef = useRef<HTMLLIElement>(null);
|
const sidebarRef = useRef<HTMLLIElement>(null);
|
||||||
const activeRequestId = useActiveRequestId();
|
const activeRequestId = useActiveRequestId();
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
@@ -116,9 +115,6 @@ export function Sidebar({ className }: Props) {
|
|||||||
return { tree, treeParentMap, selectableRequests };
|
return { tree, treeParentMap, selectableRequests };
|
||||||
}, [activeWorkspace, requests, folders]);
|
}, [activeWorkspace, requests, folders]);
|
||||||
|
|
||||||
// TODO: Move these listeners to a central place
|
|
||||||
useListenToTauriEvent('new_request', async () => createRequest.mutate({}));
|
|
||||||
|
|
||||||
const focusActiveRequest = useCallback(
|
const focusActiveRequest = useCallback(
|
||||||
(args: { forced?: { id: string; tree: TreeNode }; noFocusSidebar?: boolean } = {}) => {
|
(args: { forced?: { id: string; tree: TreeNode }; noFocusSidebar?: boolean } = {}) => {
|
||||||
const { forced, noFocusSidebar } = args;
|
const { forced, noFocusSidebar } = args;
|
||||||
@@ -193,19 +189,15 @@ export function Sidebar({ className }: Props) {
|
|||||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||||
useKeyPressEvent('Delete', handleDeleteKey);
|
useKeyPressEvent('Delete', handleDeleteKey);
|
||||||
|
|
||||||
useListenToTauriEvent(
|
useHotkey('sidebar.focus', () => {
|
||||||
'focus_sidebar',
|
if (hidden || hasFocus) return;
|
||||||
() => {
|
// Select 0 index on focus if none selected
|
||||||
if (hidden || hasFocus) return;
|
focusActiveRequest(
|
||||||
// Select 0 index on focus if none selected
|
selectedTree != null && selectedId != null
|
||||||
focusActiveRequest(
|
? { forced: { id: selectedId, tree: selectedTree } }
|
||||||
selectedTree != null && selectedId != null
|
: undefined,
|
||||||
? { forced: { id: selectedId, tree: selectedTree } }
|
);
|
||||||
: undefined,
|
});
|
||||||
);
|
|
||||||
},
|
|
||||||
[focusActiveRequest, hidden, activeRequestId],
|
|
||||||
);
|
|
||||||
|
|
||||||
useKeyPressEvent('Enter', (e) => {
|
useKeyPressEvent('Enter', (e) => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useCreateFolder } from '../hooks/useCreateFolder';
|
import { useCreateFolder } from '../hooks/useCreateFolder';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||||
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
@@ -12,6 +13,8 @@ export const SidebarActions = memo(function SidebarActions() {
|
|||||||
const createFolder = useCreateFolder();
|
const createFolder = useCreateFolder();
|
||||||
const { hidden, toggle } = useSidebarHidden();
|
const { hidden, toggle } = useSidebarHidden();
|
||||||
|
|
||||||
|
useHotkey('request.create', () => createRequest.mutate({}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack>
|
<HStack>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import classNames from 'classnames';
|
|||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { FormEvent } from 'react';
|
import type { FormEvent } from 'react';
|
||||||
import { memo, useCallback, useRef, useState } from 'react';
|
import { memo, useCallback, useRef, useState } from 'react';
|
||||||
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
@@ -40,9 +40,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
[sendRequest],
|
[sendRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
useListenToTauriEvent('focus_url', () => {
|
useHotkey('url.focus', () => inputRef.current?.focus());
|
||||||
inputRef.current?.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={classNames('url-bar', className)}>
|
<form onSubmit={handleSubmit} className={classNames('url-bar', className)}>
|
||||||
@@ -79,6 +77,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
className="!h-auto w-8 mr-0.5 my-0.5"
|
className="!h-auto w-8 mr-0.5 my-0.5"
|
||||||
icon={loading ? 'update' : 'paperPlane'}
|
icon={loading ? 'update' : 'paperPlane'}
|
||||||
spin={loading}
|
spin={loading}
|
||||||
|
hotkeyAction="request.send"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useOsInfo } from '../hooks/useOsInfo';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
@@ -39,7 +40,7 @@ export default function Workspace() {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
useListenToTauriEvent('toggle_sidebar', toggle);
|
useHotkey('sidebar.toggle', toggle);
|
||||||
|
|
||||||
// float/un-float sidebar on window resize
|
// float/un-float sidebar on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, memo, useMemo } from 'react';
|
import { forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react';
|
||||||
|
import type { HotkeyAction } from '../../hooks/useHotkey';
|
||||||
|
import { useHotkey } from '../../hooks/useHotkey';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
const colorStyles = {
|
const colorStyles = {
|
||||||
@@ -26,6 +28,7 @@ export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
|||||||
title?: string;
|
title?: string;
|
||||||
leftSlot?: ReactNode;
|
leftSlot?: ReactNode;
|
||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
|
hotkeyAction?: HotkeyAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -43,6 +46,8 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
leftSlot,
|
leftSlot,
|
||||||
rightSlot,
|
rightSlot,
|
||||||
disabled,
|
disabled,
|
||||||
|
hotkeyAction,
|
||||||
|
onClick,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps,
|
}: ButtonProps,
|
||||||
ref,
|
ref,
|
||||||
@@ -66,8 +71,25 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
[className, disabled, color, justify, size],
|
[className, disabled, color, justify, size],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
|
||||||
|
ref,
|
||||||
|
() => buttonRef.current,
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkey(hotkeyAction ?? null, () => {
|
||||||
|
buttonRef.current?.click();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
type={type}
|
||||||
|
className={classes}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Icon icon="update" size={size} className="animate-spin mr-1" />
|
<Icon icon="update" size={size} className="animate-spin mr-1" />
|
||||||
) : leftSlot ? (
|
) : leftSlot ? (
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ export type { EditorProps } from './Editor';
|
|||||||
// showing any content
|
// showing any content
|
||||||
// const editor = await import('./Editor');
|
// const editor = await import('./Editor');
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
console.log('E', e.key);
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Editor = editor.Editor;
|
export const Editor = editor.Editor;
|
||||||
export const graphql = editor.graphql;
|
export const graphql = editor.graphql;
|
||||||
export const getIntrospectionQuery = editor.getIntrospectionQuery;
|
export const getIntrospectionQuery = editor.getIntrospectionQuery;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function WebPageViewer({ response }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full pb-3">
|
<div className="h-full pb-3">
|
||||||
<iframe
|
<iframe
|
||||||
|
key={body ? 'has-body' : 'no-body'}
|
||||||
title="Response preview"
|
title="Response preview"
|
||||||
srcDoc={contentForIframe}
|
srcDoc={contentForIframe}
|
||||||
sandbox="allow-scripts allow-same-origin"
|
sandbox="allow-scripts allow-same-origin"
|
||||||
|
|||||||
60
src-web/hooks/useHotkey.ts
Normal file
60
src-web/hooks/useHotkey.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export type HotkeyAction =
|
||||||
|
| 'request.send'
|
||||||
|
| 'request.create'
|
||||||
|
| 'request.duplicate'
|
||||||
|
| 'sidebar.toggle'
|
||||||
|
| 'sidebar.focus'
|
||||||
|
| 'url.focus';
|
||||||
|
|
||||||
|
const hotkeys: Record<HotkeyAction, string[]> = {
|
||||||
|
'request.send': ['Meta+Enter', 'Meta+r'],
|
||||||
|
'request.create': ['Meta+n'],
|
||||||
|
'request.duplicate': ['Meta+d'],
|
||||||
|
'sidebar.toggle': ['Meta+b'],
|
||||||
|
'sidebar.focus': ['Meta+1'],
|
||||||
|
'url.focus': ['Meta+l'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useHotkey(action: HotkeyAction | null, callback: (e: KeyboardEvent) => void) {
|
||||||
|
const currentKeys = useRef<Set<string>>(new Set());
|
||||||
|
const callbackRef = useRef(callback);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
callbackRef.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const down = (e: KeyboardEvent) => {
|
||||||
|
console.log('KEY DOWN', e.key);
|
||||||
|
currentKeys.current.add(e.key);
|
||||||
|
for (const [hkAction, hkKeys] of Object.entries(hotkeys)) {
|
||||||
|
for (const hkKey of hkKeys) {
|
||||||
|
const keys = hkKey.split('+');
|
||||||
|
if (
|
||||||
|
keys.length === currentKeys.current.size &&
|
||||||
|
keys.every((key) => currentKeys.current.has(key)) &&
|
||||||
|
hkAction === action
|
||||||
|
) {
|
||||||
|
// Triggered hotkey!
|
||||||
|
console.log('TRIGGER!', action);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
callbackRef.current(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const up = (e: KeyboardEvent) => {
|
||||||
|
currentKeys.current.delete(e.key);
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', down);
|
||||||
|
window.addEventListener('keyup', up);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', down);
|
||||||
|
window.removeEventListener('keyup', up);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [action, callback]);
|
||||||
|
}
|
||||||
@@ -13,9 +13,7 @@ export function useResponses(requestId: string | null) {
|
|||||||
initialData: [],
|
initialData: [],
|
||||||
queryKey: responsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
queryKey: responsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return (await invoke('list_responses', {
|
return (await invoke('list_responses', { requestId, limit: 200 })) as HttpResponse[];
|
||||||
requestId,
|
|
||||||
})) as HttpResponse[];
|
|
||||||
},
|
},
|
||||||
}).data ?? []
|
}).data ?? []
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export interface HttpResponse extends BaseModel {
|
|||||||
readonly workspaceId: string;
|
readonly workspaceId: string;
|
||||||
readonly model: 'http_response';
|
readonly model: 'http_response';
|
||||||
readonly requestId: string;
|
readonly requestId: string;
|
||||||
readonly body: number[] | null;
|
|
||||||
readonly bodyPath: string | null;
|
readonly bodyPath: string | null;
|
||||||
readonly contentLength: number | null;
|
readonly contentLength: number | null;
|
||||||
readonly error: string;
|
readonly error: string;
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ import { readBinaryFile, readTextFile } from '@tauri-apps/api/fs';
|
|||||||
import type { HttpResponse } from './models';
|
import type { HttpResponse } from './models';
|
||||||
|
|
||||||
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
||||||
if (response.body) {
|
|
||||||
const uint8Array = Uint8Array.from(response.body);
|
|
||||||
return new TextDecoder().decode(uint8Array);
|
|
||||||
}
|
|
||||||
if (response.bodyPath) {
|
if (response.bodyPath) {
|
||||||
return await readTextFile(response.bodyPath);
|
return await readTextFile(response.bodyPath);
|
||||||
}
|
}
|
||||||
@@ -13,9 +9,6 @@ export async function getResponseBodyText(response: HttpResponse): Promise<strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getResponseBodyBlob(response: HttpResponse): Promise<Uint8Array | null> {
|
export async function getResponseBodyBlob(response: HttpResponse): Promise<Uint8Array | null> {
|
||||||
if (response.body) {
|
|
||||||
return Uint8Array.from(response.body);
|
|
||||||
}
|
|
||||||
if (response.bodyPath) {
|
if (response.bodyPath) {
|
||||||
return readBinaryFile(response.bodyPath);
|
return readBinaryFile(response.bodyPath);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user