mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-05 04:21:50 +02:00
Fix performance related to having 100s of requests (#123)
This commit is contained in:
+23
-10
@@ -46,12 +46,25 @@ use yaak_models::models::{
|
|||||||
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcEvent, GrpcEventType,
|
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcEvent, GrpcEventType,
|
||||||
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Plugin, Settings, Workspace,
|
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Plugin, Settings, Workspace,
|
||||||
};
|
};
|
||||||
use yaak_models::queries::{cancel_pending_grpc_connections, cancel_pending_responses, create_default_http_response, delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar, delete_environment, delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request, delete_http_response, delete_plugin, delete_workspace, duplicate_grpc_request, duplicate_http_request, generate_id, generate_model_id, get_cookie_jar, get_environment, get_folder, get_grpc_connection, get_grpc_request, get_http_request, get_http_response, get_key_value_raw, get_or_create_settings, get_plugin, get_workspace, list_cookie_jars, list_environments, list_folders, list_grpc_connections, list_grpc_events, list_grpc_requests, list_http_requests, list_http_responses, list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace};
|
use yaak_models::queries::{
|
||||||
|
cancel_pending_grpc_connections, cancel_pending_responses, create_default_http_response,
|
||||||
|
delete_all_grpc_connections, delete_all_http_responses_for_request, delete_cookie_jar,
|
||||||
|
delete_environment, delete_folder, delete_grpc_connection, delete_grpc_request,
|
||||||
|
delete_http_request, delete_http_response, delete_plugin, delete_workspace,
|
||||||
|
duplicate_grpc_request, duplicate_http_request, generate_id, generate_model_id, get_cookie_jar,
|
||||||
|
get_environment, get_folder, get_grpc_connection, get_grpc_request, get_http_request,
|
||||||
|
get_http_response, get_key_value_raw, get_or_create_settings, get_plugin, get_workspace,
|
||||||
|
list_cookie_jars, list_environments, list_folders, list_grpc_connections, list_grpc_events,
|
||||||
|
list_grpc_requests, list_http_requests, list_http_responses, list_http_responses_for_request,
|
||||||
|
list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings,
|
||||||
|
upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
|
||||||
|
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace,
|
||||||
|
};
|
||||||
use yaak_plugin_runtime::events::{
|
use yaak_plugin_runtime::events::{
|
||||||
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
|
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
|
||||||
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
|
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
|
||||||
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, RenderPurpose,
|
InternalEvent, InternalEventPayload, PromptTextResponse, RenderHttpRequestResponse,
|
||||||
SendHttpRequestResponse, PromptTextResponse, ShowToastRequest, TemplateRenderResponse,
|
RenderPurpose, SendHttpRequestResponse, ShowToastRequest, TemplateRenderResponse,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use yaak_plugin_runtime::plugin_handle::PluginHandle;
|
use yaak_plugin_runtime::plugin_handle::PluginHandle;
|
||||||
@@ -1080,7 +1093,7 @@ async fn cmd_send_http_request(
|
|||||||
let _ = cancel_tx.send(true);
|
let _ = cancel_tx.send(true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let environment = match environment_id {
|
let environment = match environment_id {
|
||||||
Some(id) => match get_environment(&window, id).await {
|
Some(id) => match get_environment(&window, id).await {
|
||||||
Ok(env) => Some(env),
|
Ok(env) => Some(env),
|
||||||
@@ -1454,10 +1467,10 @@ async fn cmd_delete_environment(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_list_grpc_connections(
|
async fn cmd_list_grpc_connections(
|
||||||
request_id: &str,
|
workspace_id: &str,
|
||||||
w: WebviewWindow,
|
w: WebviewWindow,
|
||||||
) -> Result<Vec<GrpcConnection>, String> {
|
) -> Result<Vec<GrpcConnection>, String> {
|
||||||
list_grpc_connections(&w, request_id)
|
list_grpc_connections(&w, workspace_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@@ -1604,11 +1617,11 @@ async fn cmd_get_workspace(id: &str, w: WebviewWindow) -> Result<Workspace, Stri
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_list_http_responses(
|
async fn cmd_list_http_responses(
|
||||||
request_id: &str,
|
workspace_id: &str,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
w: WebviewWindow,
|
w: WebviewWindow,
|
||||||
) -> Result<Vec<HttpResponse>, String> {
|
) -> Result<Vec<HttpResponse>, String> {
|
||||||
list_http_responses(&w, request_id, limit)
|
list_http_responses(&w, workspace_id, limit)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@@ -1636,7 +1649,7 @@ async fn cmd_delete_all_grpc_connections(request_id: &str, w: WebviewWindow) ->
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_delete_all_http_responses(request_id: &str, w: WebviewWindow) -> Result<(), String> {
|
async fn cmd_delete_all_http_responses(request_id: &str, w: WebviewWindow) -> Result<(), String> {
|
||||||
delete_all_http_responses(&w, request_id)
|
delete_all_http_responses_for_request(&w, request_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@@ -2215,7 +2228,7 @@ async fn handle_plugin_event<R: Runtime>(
|
|||||||
Some(InternalEventPayload::PromptTextResponse(resp))
|
Some(InternalEventPayload::PromptTextResponse(resp))
|
||||||
}
|
}
|
||||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||||
let http_responses = list_http_responses(
|
let http_responses = list_http_responses_for_request(
|
||||||
app_handle,
|
app_handle,
|
||||||
req.request_id.as_str(),
|
req.request_id.as_str(),
|
||||||
req.limit.map(|l| l as i64),
|
req.limit.map(|l| l as i64),
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection, GrpcConnectionIden, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden, Settings, SettingsIden, Workspace, WorkspaceIden};
|
use crate::models::{
|
||||||
|
CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection,
|
||||||
|
GrpcConnectionIden, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest,
|
||||||
|
HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, KeyValue, KeyValueIden,
|
||||||
|
ModelType, Plugin, PluginIden, Settings, SettingsIden, Workspace, WorkspaceIden,
|
||||||
|
};
|
||||||
use crate::plugin::SqliteConnection;
|
use crate::plugin::SqliteConnection;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
@@ -12,6 +17,9 @@ use sea_query_rusqlite::RusqliteBinder;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||||
|
|
||||||
|
const MAX_GRPC_CONNECTIONS_PER_REQUEST: usize = 20;
|
||||||
|
const MAX_HTTP_RESPONSES_PER_REQUEST: usize = MAX_GRPC_CONNECTIONS_PER_REQUEST;
|
||||||
|
|
||||||
pub async fn set_key_value_string<R: Runtime>(
|
pub async fn set_key_value_string<R: Runtime>(
|
||||||
mgr: &WebviewWindow<R>,
|
mgr: &WebviewWindow<R>,
|
||||||
namespace: &str,
|
namespace: &str,
|
||||||
@@ -423,6 +431,13 @@ pub async fn upsert_grpc_connection<R: Runtime>(
|
|||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
connection: &GrpcConnection,
|
connection: &GrpcConnection,
|
||||||
) -> Result<GrpcConnection> {
|
) -> Result<GrpcConnection> {
|
||||||
|
let connections =
|
||||||
|
list_http_responses_for_request(window, connection.request_id.as_str(), None).await?;
|
||||||
|
for c in connections.iter().skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1) {
|
||||||
|
debug!("Deleting old grpc connection {}", c.id);
|
||||||
|
delete_grpc_connection(window, c.id.as_str()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let id = match connection.id.as_str() {
|
let id = match connection.id.as_str() {
|
||||||
"" => generate_model_id(ModelType::TypeGrpcConnection),
|
"" => generate_model_id(ModelType::TypeGrpcConnection),
|
||||||
_ => connection.id.to_string(),
|
_ => connection.id.to_string(),
|
||||||
@@ -497,6 +512,24 @@ pub async fn get_grpc_connection<R: Runtime>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_grpc_connections<R: Runtime>(
|
pub async fn list_grpc_connections<R: Runtime>(
|
||||||
|
mgr: &impl Manager<R>,
|
||||||
|
workspace_id: &str,
|
||||||
|
) -> Result<Vec<GrpcConnection>> {
|
||||||
|
let dbm = &*mgr.state::<SqliteConnection>();
|
||||||
|
let db = dbm.0.lock().await.get().unwrap();
|
||||||
|
|
||||||
|
let (sql, params) = Query::select()
|
||||||
|
.from(GrpcConnectionIden::Table)
|
||||||
|
.cond_where(Expr::col(GrpcConnectionIden::WorkspaceId).eq(workspace_id))
|
||||||
|
.column(Asterisk)
|
||||||
|
.order_by(GrpcConnectionIden::CreatedAt, Order::Desc)
|
||||||
|
.build_rusqlite(SqliteQueryBuilder);
|
||||||
|
let mut stmt = db.prepare(sql.as_str())?;
|
||||||
|
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
||||||
|
Ok(items.map(|v| v.unwrap()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_grpc_connections_for_request<R: Runtime>(
|
||||||
mgr: &impl Manager<R>,
|
mgr: &impl Manager<R>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<Vec<GrpcConnection>> {
|
) -> Result<Vec<GrpcConnection>> {
|
||||||
@@ -536,7 +569,7 @@ pub async fn delete_all_grpc_connections<R: Runtime>(
|
|||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for r in list_grpc_connections(window, request_id).await? {
|
for r in list_grpc_connections_for_request(window, request_id).await? {
|
||||||
delete_grpc_connection(window, &r.id).await?;
|
delete_grpc_connection(window, &r.id).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -794,10 +827,7 @@ pub async fn update_settings<R: Runtime>(
|
|||||||
SettingsIden::EditorSoftWrap,
|
SettingsIden::EditorSoftWrap,
|
||||||
settings.editor_soft_wrap.into(),
|
settings.editor_soft_wrap.into(),
|
||||||
),
|
),
|
||||||
(
|
(SettingsIden::Telemetry, settings.telemetry.into()),
|
||||||
SettingsIden::Telemetry,
|
|
||||||
settings.telemetry.into(),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
SettingsIden::OpenWorkspaceNewWindow,
|
SettingsIden::OpenWorkspaceNewWindow,
|
||||||
settings.open_workspace_new_window.into(),
|
settings.open_workspace_new_window.into(),
|
||||||
@@ -872,10 +902,7 @@ pub async fn get_environment<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Res
|
|||||||
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_plugin<R: Runtime>(
|
pub async fn get_plugin<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Plugin> {
|
||||||
mgr: &impl Manager<R>,
|
|
||||||
id: &str
|
|
||||||
) -> Result<Plugin> {
|
|
||||||
let dbm = &*mgr.state::<SqliteConnection>();
|
let dbm = &*mgr.state::<SqliteConnection>();
|
||||||
let db = dbm.0.lock().await.get().unwrap();
|
let db = dbm.0.lock().await.get().unwrap();
|
||||||
|
|
||||||
@@ -888,9 +915,7 @@ pub async fn get_plugin<R: Runtime>(
|
|||||||
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_plugins<R: Runtime>(
|
pub async fn list_plugins<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<Plugin>> {
|
||||||
mgr: &impl Manager<R>,
|
|
||||||
) -> Result<Vec<Plugin>> {
|
|
||||||
let dbm = &*mgr.state::<SqliteConnection>();
|
let dbm = &*mgr.state::<SqliteConnection>();
|
||||||
let db = dbm.0.lock().await.get().unwrap();
|
let db = dbm.0.lock().await.get().unwrap();
|
||||||
|
|
||||||
@@ -1185,7 +1210,7 @@ pub async fn delete_http_request<R: Runtime>(
|
|||||||
let req = get_http_request(window, id).await?;
|
let req = get_http_request(window, id).await?;
|
||||||
|
|
||||||
// DB deletes will cascade but this will delete the files
|
// DB deletes will cascade but this will delete the files
|
||||||
delete_all_http_responses(window, id).await?;
|
delete_all_http_responses_for_request(window, id).await?;
|
||||||
|
|
||||||
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
||||||
let db = dbm.0.lock().await.get().unwrap();
|
let db = dbm.0.lock().await.get().unwrap();
|
||||||
@@ -1234,6 +1259,12 @@ pub async fn create_http_response<R: Runtime>(
|
|||||||
version: Option<&str>,
|
version: Option<&str>,
|
||||||
remote_addr: Option<&str>,
|
remote_addr: Option<&str>,
|
||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
|
let responses = list_http_responses_for_request(window, request_id, None).await?;
|
||||||
|
for response in responses.iter().skip(MAX_HTTP_RESPONSES_PER_REQUEST - 1) {
|
||||||
|
debug!("Deleting old response {}", response.id);
|
||||||
|
delete_http_response(window, response.id.as_str()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let req = get_http_request(window, request_id).await?;
|
let req = get_http_request(window, request_id).await?;
|
||||||
let id = generate_model_id(ModelType::TypeHttpResponse);
|
let id = generate_model_id(ModelType::TypeHttpResponse);
|
||||||
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
||||||
@@ -1418,17 +1449,37 @@ pub async fn delete_http_response<R: Runtime>(
|
|||||||
emit_deleted_model(window, resp)
|
emit_deleted_model(window, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_all_http_responses<R: Runtime>(
|
pub async fn delete_all_http_responses_for_request<R: Runtime>(
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for r in list_http_responses(window, request_id, None).await? {
|
for r in list_http_responses_for_request(window, request_id, None).await? {
|
||||||
delete_http_response(window, &r.id).await?;
|
delete_http_response(window, &r.id).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_http_responses<R: Runtime>(
|
pub async fn list_http_responses<R: Runtime>(
|
||||||
|
mgr: &impl Manager<R>,
|
||||||
|
workspace_id: &str,
|
||||||
|
limit: Option<i64>,
|
||||||
|
) -> Result<Vec<HttpResponse>> {
|
||||||
|
let limit_unwrapped = limit.unwrap_or_else(|| i64::MAX);
|
||||||
|
let dbm = mgr.state::<SqliteConnection>();
|
||||||
|
let db = dbm.0.lock().await.get().unwrap();
|
||||||
|
let (sql, params) = Query::select()
|
||||||
|
.from(HttpResponseIden::Table)
|
||||||
|
.cond_where(Expr::col(HttpResponseIden::WorkspaceId).eq(workspace_id))
|
||||||
|
.column(Asterisk)
|
||||||
|
.order_by(HttpResponseIden::CreatedAt, Order::Desc)
|
||||||
|
.limit(limit_unwrapped as u64)
|
||||||
|
.build_rusqlite(SqliteQueryBuilder);
|
||||||
|
let mut stmt = db.prepare(sql.as_str())?;
|
||||||
|
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
||||||
|
Ok(items.map(|v| v.unwrap()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_http_responses_for_request<R: Runtime>(
|
||||||
mgr: &impl Manager<R>,
|
mgr: &impl Manager<R>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ const queryClient = new QueryClient({
|
|||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
retry: false,
|
retry: false,
|
||||||
refetchOnWindowFocus: true,
|
|
||||||
networkMode: 'offlineFirst',
|
networkMode: 'offlineFirst',
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnMount: false, // Don't refetch when a hook mounts
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom';
|
import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom';
|
||||||
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss';
|
||||||
|
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||||
|
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||||
|
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||||
import { DefaultLayout } from './DefaultLayout';
|
import { DefaultLayout } from './DefaultLayout';
|
||||||
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
||||||
import RouteError from './RouteError';
|
import RouteError from './RouteError';
|
||||||
@@ -50,6 +54,12 @@ const router = createBrowserRouter([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export function AppRouter() {
|
export function AppRouter() {
|
||||||
|
// Add some global hooks that should remain persistent
|
||||||
|
useSyncModelStores();
|
||||||
|
useSyncZoomSetting();
|
||||||
|
useSyncFontSizeSetting();
|
||||||
|
useGenerateThemeCss();
|
||||||
|
|
||||||
return <RouterProvider router={router} />;
|
return <RouterProvider router={router} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface Props {
|
|||||||
|
|
||||||
export const CookieDialog = function ({ cookieJarId }: Props) {
|
export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||||
const updateCookieJar = useUpdateCookieJar(cookieJarId ?? null);
|
const updateCookieJar = useUpdateCookieJar(cookieJarId ?? null);
|
||||||
const cookieJars = useCookieJars().data ?? [];
|
const cookieJars = useCookieJars();
|
||||||
const cookieJar = cookieJars.find((c) => c.id === cookieJarId);
|
const cookieJar = cookieJars.find((c) => c.id === cookieJarId);
|
||||||
|
|
||||||
if (cookieJar == null) {
|
if (cookieJar == null) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { InlineCode } from './core/InlineCode';
|
|||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
|
|
||||||
export function CookieDropdown() {
|
export function CookieDropdown() {
|
||||||
const cookieJars = useCookieJars().data ?? [];
|
const cookieJars = useCookieJars();
|
||||||
const [activeCookieJar, setActiveCookieJarId] = useActiveCookieJar();
|
const [activeCookieJar, setActiveCookieJarId] = useActiveCookieJar();
|
||||||
const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null);
|
const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null);
|
||||||
const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null);
|
const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import classNames from 'classnames';
|
|||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useOsInfo } from '../hooks/useOsInfo';
|
||||||
import { DialogProvider, Dialogs } from './DialogContext';
|
import { DialogProvider, Dialogs } from './DialogContext';
|
||||||
import { GlobalHooks } from './GlobalHooks';
|
|
||||||
import { ToastProvider, Toasts } from './ToastContext';
|
import { ToastProvider, Toasts } from './ToastContext';
|
||||||
|
import { GlobalHooks } from './GlobalHooks';
|
||||||
|
|
||||||
export function DefaultLayout() {
|
export function DefaultLayout() {
|
||||||
const osInfo = useOsInfo();
|
const osInfo = useOsInfo();
|
||||||
|
|||||||
@@ -1,50 +1,17 @@
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { emit } from '@tauri-apps/api/event';
|
import { emit } from '@tauri-apps/api/event';
|
||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
|
||||||
import type { AnyModel } from '@yaakapp-internal/models';
|
|
||||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin';
|
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useEnsureActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useEnsureActiveCookieJar, useMigrateActiveCookieJarId } from '../hooks/useActiveCookieJar';
|
|
||||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
|
||||||
import { useCopy } from '../hooks/useCopy';
|
|
||||||
import { environmentsAtom } from '../hooks/useEnvironments';
|
|
||||||
import { foldersQueryKey } from '../hooks/useFolders';
|
|
||||||
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
|
|
||||||
import { grpcEventsQueryKey } from '../hooks/useGrpcEvents';
|
|
||||||
import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
|
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { httpRequestsAtom } from '../hooks/useHttpRequests';
|
|
||||||
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
|
||||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||||
import { pluginsAtom } from '../hooks/usePlugins';
|
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useRecentCookieJars } from '../hooks/useRecentCookieJars';
|
import { useRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||||
import { settingsAtom, useSettings } from '../hooks/useSettings';
|
|
||||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||||
import { workspacesAtom } from '../hooks/useWorkspaces';
|
|
||||||
import { useZoom } from '../hooks/useZoom';
|
|
||||||
import { extractKeyValue } from '../lib/keyValueStore';
|
|
||||||
import { modelsEq } from '../lib/model_util';
|
|
||||||
import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin';
|
|
||||||
import { githubLight } from '../lib/theme/themes/github';
|
|
||||||
import { hotdogStandDefault } from '../lib/theme/themes/hotdog-stand';
|
|
||||||
import { monokaiProDefault } from '../lib/theme/themes/monokai-pro';
|
|
||||||
import { rosePineDefault } from '../lib/theme/themes/rose-pine';
|
|
||||||
import { yaakDark } from '../lib/theme/themes/yaak';
|
|
||||||
import { getThemeCSS } from '../lib/theme/window';
|
|
||||||
|
|
||||||
export interface ModelPayload {
|
|
||||||
model: AnyModel;
|
|
||||||
windowLabel: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GlobalHooks() {
|
export function GlobalHooks() {
|
||||||
// Include here so they always update, even if no component references them
|
// Include here so they always update, even if no component references them
|
||||||
@@ -52,136 +19,16 @@ export function GlobalHooks() {
|
|||||||
useRecentEnvironments();
|
useRecentEnvironments();
|
||||||
useRecentCookieJars();
|
useRecentCookieJars();
|
||||||
useRecentRequests();
|
useRecentRequests();
|
||||||
|
useSyncWorkspaceChildModels();
|
||||||
|
|
||||||
// Other useful things
|
// Other useful things
|
||||||
useNotificationToast();
|
useNotificationToast();
|
||||||
useActiveWorkspaceChangedToast();
|
useActiveWorkspaceChangedToast();
|
||||||
useEnsureActiveCookieJar();
|
useEnsureActiveCookieJar();
|
||||||
|
|
||||||
// TODO: Remove in future version
|
|
||||||
useMigrateActiveCookieJarId();
|
|
||||||
|
|
||||||
const toggleCommandPalette = useToggleCommandPalette();
|
const toggleCommandPalette = useToggleCommandPalette();
|
||||||
useHotKey('command_palette.toggle', toggleCommandPalette);
|
useHotKey('command_palette.toggle', toggleCommandPalette);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
|
||||||
|
|
||||||
const setSettings = useSetAtom(settingsAtom);
|
|
||||||
const setWorkspaces = useSetAtom(workspacesAtom);
|
|
||||||
const setPlugins = useSetAtom(pluginsAtom);
|
|
||||||
const setHttpRequests = useSetAtom(httpRequestsAtom);
|
|
||||||
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
|
|
||||||
const setEnvironments = useSetAtom(environmentsAtom);
|
|
||||||
|
|
||||||
useListenToTauriEvent<ModelPayload>('upserted_model', ({ payload }) => {
|
|
||||||
console.log('Upserted model', payload.model);
|
|
||||||
const { model, windowLabel } = payload;
|
|
||||||
const queryKey =
|
|
||||||
model.model === 'http_response'
|
|
||||||
? httpResponsesQueryKey(model)
|
|
||||||
: model.model === 'folder'
|
|
||||||
? foldersQueryKey(model)
|
|
||||||
: model.model === 'grpc_connection'
|
|
||||||
? grpcConnectionsQueryKey(model)
|
|
||||||
: model.model === 'grpc_event'
|
|
||||||
? grpcEventsQueryKey(model)
|
|
||||||
: model.model === 'key_value'
|
|
||||||
? keyValueQueryKey(model)
|
|
||||||
: model.model === 'cookie_jar'
|
|
||||||
? cookieJarsQueryKey(model)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) {
|
|
||||||
wasUpdatedExternally(model.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushToFront = (['http_response', 'grpc_connection'] as AnyModel['model'][]).includes(
|
|
||||||
model.model,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldIgnoreModel(model, windowLabel)) return;
|
|
||||||
|
|
||||||
if (model.model === 'workspace') {
|
|
||||||
setWorkspaces(updateModelList(model, pushToFront));
|
|
||||||
} else if (model.model === 'plugin') {
|
|
||||||
setPlugins(updateModelList(model, pushToFront));
|
|
||||||
} else if (model.model === 'http_request') {
|
|
||||||
setHttpRequests(updateModelList(model, pushToFront));
|
|
||||||
} else if (model.model === 'grpc_request') {
|
|
||||||
setGrpcRequests(updateModelList(model, pushToFront));
|
|
||||||
} else if (model.model === 'environment') {
|
|
||||||
setEnvironments(updateModelList(model, pushToFront));
|
|
||||||
} else if (model.model === 'settings') {
|
|
||||||
setSettings(model);
|
|
||||||
} else if (queryKey != null) {
|
|
||||||
// TODO: Convert all models to use Jotai
|
|
||||||
queryClient.setQueryData(queryKey, (current: unknown) => {
|
|
||||||
if (model.model === 'key_value') {
|
|
||||||
// Special-case for KeyValue
|
|
||||||
return extractKeyValue(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(current)) {
|
|
||||||
return updateModelList(model, pushToFront)(current);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useListenToTauriEvent<ModelPayload>('deleted_model', ({ payload }) => {
|
|
||||||
const { model, windowLabel } = payload;
|
|
||||||
if (shouldIgnoreModel(model, windowLabel)) return;
|
|
||||||
|
|
||||||
console.log('Delete model', payload.model);
|
|
||||||
|
|
||||||
if (model.model === 'workspace') {
|
|
||||||
setWorkspaces(removeById(model));
|
|
||||||
} else if (model.model === 'plugin') {
|
|
||||||
setPlugins(removeById(model));
|
|
||||||
} else if (model.model === 'http_request') {
|
|
||||||
setHttpRequests(removeById(model));
|
|
||||||
} else if (model.model === 'http_response') {
|
|
||||||
queryClient.setQueryData(httpResponsesQueryKey(model), removeById(model));
|
|
||||||
} else if (model.model === 'folder') {
|
|
||||||
queryClient.setQueryData(foldersQueryKey(model), removeById(model));
|
|
||||||
} else if (model.model === 'environment') {
|
|
||||||
setEnvironments(removeById(model));
|
|
||||||
} else if (model.model === 'grpc_request') {
|
|
||||||
setGrpcRequests(removeById(model));
|
|
||||||
} else if (model.model === 'grpc_connection') {
|
|
||||||
queryClient.setQueryData(grpcConnectionsQueryKey(model), removeById(model));
|
|
||||||
} else if (model.model === 'grpc_event') {
|
|
||||||
queryClient.setQueryData(grpcEventsQueryKey(model), removeById(model));
|
|
||||||
} else if (model.model === 'key_value') {
|
|
||||||
queryClient.setQueryData(keyValueQueryKey(model), undefined);
|
|
||||||
} else if (model.model === 'cookie_jar') {
|
|
||||||
queryClient.setQueryData(cookieJarsQueryKey(model), undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const settings = useSettings();
|
|
||||||
useEffect(() => {
|
|
||||||
if (settings == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { interfaceScale, editorFontSize } = settings;
|
|
||||||
getCurrentWebviewWindow().setZoom(interfaceScale).catch(console.error);
|
|
||||||
document.documentElement.style.setProperty('--editor-font-size', `${editorFontSize}px`);
|
|
||||||
}, [settings]);
|
|
||||||
|
|
||||||
// Handle Zoom.
|
|
||||||
// Note, Mac handles it in the app menu, so need to also handle keyboard
|
|
||||||
// shortcuts for Windows/Linux
|
|
||||||
const zoom = useZoom();
|
|
||||||
useHotKey('app.zoom_in', zoom.zoomIn);
|
|
||||||
useListenToTauriEvent('zoom_in', zoom.zoomIn);
|
|
||||||
useHotKey('app.zoom_out', zoom.zoomOut);
|
|
||||||
useListenToTauriEvent('zoom_out', zoom.zoomOut);
|
|
||||||
useHotKey('app.zoom_reset', zoom.zoomReset);
|
|
||||||
useListenToTauriEvent('zoom_reset', zoom.zoomReset);
|
|
||||||
|
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
useListenToTauriEvent<{ replyId: string; args: PromptTextRequest }>(
|
useListenToTauriEvent<{ replyId: string; args: PromptTextRequest }>(
|
||||||
'show_prompt',
|
'show_prompt',
|
||||||
@@ -192,46 +39,5 @@ export function GlobalHooks() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const copy = useCopy();
|
|
||||||
useListenToTauriEvent('generate_theme_css', () => {
|
|
||||||
const themesCss = [
|
|
||||||
yaakDark,
|
|
||||||
monokaiProDefault,
|
|
||||||
rosePineDefault,
|
|
||||||
catppuccinMacchiato,
|
|
||||||
githubLight,
|
|
||||||
hotdogStandDefault,
|
|
||||||
]
|
|
||||||
.map(getThemeCSS)
|
|
||||||
.join('\n\n');
|
|
||||||
copy(themesCss);
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateModelList<T extends AnyModel>(model: T, pushToFront: boolean) {
|
|
||||||
return (current: T[]): T[] => {
|
|
||||||
const index = current.findIndex((v) => modelsEq(v, model)) ?? -1;
|
|
||||||
if (index >= 0) {
|
|
||||||
return [...current.slice(0, index), model, ...current.slice(index + 1)];
|
|
||||||
} else {
|
|
||||||
return pushToFront ? [model, ...(current ?? [])] : [...(current ?? []), model];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeById<T extends { id: string }>(model: T) {
|
|
||||||
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id) ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldIgnoreModel = (payload: AnyModel, windowLabel: string) => {
|
|
||||||
if (windowLabel === getCurrentWebviewWindow().label) {
|
|
||||||
// Never ignore same-window updates
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (payload.model === 'key_value') {
|
|
||||||
return payload.namespace === 'no_sync';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const emptyArray: string[] = [];
|
|||||||
export function GrpcConnectionLayout({ style }: Props) {
|
export function GrpcConnectionLayout({ style }: Props) {
|
||||||
const activeRequest = useActiveRequest('grpc_request');
|
const activeRequest = useActiveRequest('grpc_request');
|
||||||
const updateRequest = useUpdateAnyGrpcRequest();
|
const updateRequest = useUpdateAnyGrpcRequest();
|
||||||
const connections = useGrpcConnections(activeRequest?.id ?? null);
|
const connections = useGrpcConnections().filter((c) => c.requestId === activeRequest?.id);
|
||||||
const activeConnection = connections[0] ?? null;
|
const activeConnection = connections[0] ?? null;
|
||||||
const messages = useGrpcEvents(activeConnection?.id ?? null);
|
const messages = useGrpcEvents(activeConnection?.id ?? null);
|
||||||
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
|
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
AnyModel,
|
AnyModel,
|
||||||
Folder,
|
Folder,
|
||||||
|
GrpcConnection,
|
||||||
GrpcRequest,
|
GrpcRequest,
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
|
HttpResponse,
|
||||||
Workspace,
|
Workspace,
|
||||||
} from '@yaakapp-internal/models';
|
} from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -22,18 +24,19 @@ import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
|||||||
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
||||||
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
||||||
import { useFolders } from '../hooks/useFolders';
|
import { useFolders } from '../hooks/useFolders';
|
||||||
|
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
|
import type { CallableHttpRequestAction } from '../hooks/useHttpRequestActions';
|
||||||
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||||
|
import { useHttpResponses } from '../hooks/useHttpResponses';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useLatestGrpcConnection } from '../hooks/useLatestGrpcConnection';
|
|
||||||
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
|
||||||
import { useMoveToWorkspace } from '../hooks/useMoveToWorkspace';
|
import { useMoveToWorkspace } from '../hooks/useMoveToWorkspace';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useRenameRequest } from '../hooks/useRenameRequest';
|
import { useRenameRequest } from '../hooks/useRenameRequest';
|
||||||
import { useRequests } from '../hooks/useRequests';
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||||
import { useSendManyRequests } from '../hooks/useSendFolder';
|
import { useSendManyRequests } from '../hooks/useSendManyRequests';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||||
@@ -73,6 +76,9 @@ export function Sidebar({ className }: Props) {
|
|||||||
const folders = useFolders();
|
const folders = useFolders();
|
||||||
const requests = useRequests();
|
const requests = useRequests();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
|
const httpRequestActions = useHttpRequestActions();
|
||||||
|
const httpResponses = useHttpResponses();
|
||||||
|
const grpcConnections = useGrpcConnections();
|
||||||
const duplicateHttpRequest = useDuplicateHttpRequest({
|
const duplicateHttpRequest = useDuplicateHttpRequest({
|
||||||
id: activeRequest?.id ?? null,
|
id: activeRequest?.id ?? null,
|
||||||
navigateAfter: true,
|
navigateAfter: true,
|
||||||
@@ -453,6 +459,9 @@ export function Sidebar({ className }: Props) {
|
|||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
selectedTree={selectedTree}
|
selectedTree={selectedTree}
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
|
httpRequestActions={httpRequestActions}
|
||||||
|
httpResponses={httpResponses}
|
||||||
|
grpcConnections={grpcConnections}
|
||||||
tree={tree}
|
tree={tree}
|
||||||
focused={hasFocus}
|
focused={hasFocus}
|
||||||
draggingId={draggingId}
|
draggingId={draggingId}
|
||||||
@@ -483,6 +492,9 @@ interface SidebarItemsProps {
|
|||||||
handleDragStart: (id: string) => void;
|
handleDragStart: (id: string) => void;
|
||||||
onSelect: (requestId: string) => void;
|
onSelect: (requestId: string) => void;
|
||||||
isCollapsed: (id: string) => boolean;
|
isCollapsed: (id: string) => boolean;
|
||||||
|
httpRequestActions: CallableHttpRequestAction[];
|
||||||
|
httpResponses: HttpResponse[];
|
||||||
|
grpcConnections: GrpcConnection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarItems({
|
function SidebarItems({
|
||||||
@@ -500,6 +512,9 @@ function SidebarItems({
|
|||||||
handleEnd,
|
handleEnd,
|
||||||
handleMove,
|
handleMove,
|
||||||
handleDragStart,
|
handleDragStart,
|
||||||
|
httpRequestActions,
|
||||||
|
httpResponses,
|
||||||
|
grpcConnections,
|
||||||
}: SidebarItemsProps) {
|
}: SidebarItemsProps) {
|
||||||
return (
|
return (
|
||||||
<VStack
|
<VStack
|
||||||
@@ -537,6 +552,11 @@ function SidebarItems({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
httpRequestActions={httpRequestActions}
|
||||||
|
latestHttpResponse={httpResponses.find((r) => r.requestId === child.item.id) ?? null}
|
||||||
|
latestGrpcConnection={
|
||||||
|
grpcConnections.find((c) => c.requestId === child.item.id) ?? null
|
||||||
|
}
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
onEnd={handleEnd}
|
onEnd={handleEnd}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
@@ -549,20 +569,23 @@ function SidebarItems({
|
|||||||
!isCollapsed(child.item.id) &&
|
!isCollapsed(child.item.id) &&
|
||||||
draggingId !== child.item.id && (
|
draggingId !== child.item.id && (
|
||||||
<SidebarItems
|
<SidebarItems
|
||||||
treeParentMap={treeParentMap}
|
|
||||||
tree={child}
|
|
||||||
isCollapsed={isCollapsed}
|
|
||||||
draggingId={draggingId}
|
|
||||||
hoveredTree={hoveredTree}
|
|
||||||
hoveredIndex={hoveredIndex}
|
|
||||||
focused={focused}
|
|
||||||
activeId={activeId}
|
activeId={activeId}
|
||||||
|
draggingId={draggingId}
|
||||||
|
focused={focused}
|
||||||
|
handleDragStart={handleDragStart}
|
||||||
|
handleEnd={handleEnd}
|
||||||
|
handleMove={handleMove}
|
||||||
|
hoveredIndex={hoveredIndex}
|
||||||
|
hoveredTree={hoveredTree}
|
||||||
|
httpRequestActions={httpRequestActions}
|
||||||
|
httpResponses={httpResponses}
|
||||||
|
grpcConnections={grpcConnections}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onSelect={onSelect}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
selectedTree={selectedTree}
|
selectedTree={selectedTree}
|
||||||
onSelect={onSelect}
|
tree={child}
|
||||||
handleMove={handleMove}
|
treeParentMap={treeParentMap}
|
||||||
handleEnd={handleEnd}
|
|
||||||
handleDragStart={handleDragStart}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
@@ -590,7 +613,9 @@ type SidebarItemProps = {
|
|||||||
onDragStart: (id: string) => void;
|
onDragStart: (id: string) => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
child: TreeNode;
|
child: TreeNode;
|
||||||
} & Pick<SidebarItemsProps, 'isCollapsed' | 'onSelect'>;
|
latestHttpResponse: HttpResponse | null;
|
||||||
|
latestGrpcConnection: GrpcConnection | null;
|
||||||
|
} & Pick<SidebarItemsProps, 'isCollapsed' | 'onSelect' | 'httpRequestActions'>;
|
||||||
|
|
||||||
type DragItem = {
|
type DragItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -612,6 +637,9 @@ function SidebarItem({
|
|||||||
selected,
|
selected,
|
||||||
itemFallbackName,
|
itemFallbackName,
|
||||||
useProminentStyles,
|
useProminentStyles,
|
||||||
|
latestHttpResponse,
|
||||||
|
latestGrpcConnection,
|
||||||
|
httpRequestActions,
|
||||||
children,
|
children,
|
||||||
}: SidebarItemProps) {
|
}: SidebarItemProps) {
|
||||||
const ref = useRef<HTMLLIElement>(null);
|
const ref = useRef<HTMLLIElement>(null);
|
||||||
@@ -659,14 +687,9 @@ function SidebarItem({
|
|||||||
const renameRequest = useRenameRequest(itemId);
|
const renameRequest = useRenameRequest(itemId);
|
||||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
||||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||||
const httpRequestActions = useHttpRequestActions();
|
|
||||||
const sendRequest = useSendAnyHttpRequest();
|
const sendRequest = useSendAnyHttpRequest();
|
||||||
const moveToWorkspace = useMoveToWorkspace(itemId);
|
const moveToWorkspace = useMoveToWorkspace(itemId);
|
||||||
const sendManyRequests = useSendManyRequests();
|
const sendManyRequests = useSendManyRequests();
|
||||||
const latestHttpResponse = useLatestHttpResponse(itemModel === 'http_request' ? itemId : null);
|
|
||||||
const latestGrpcConnection = useLatestGrpcConnection(
|
|
||||||
itemModel === 'grpc_request' ? itemId : null,
|
|
||||||
);
|
|
||||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Listen for settings changes, the re-compute theme
|
// Listen for settings changes, the re-compute theme
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import type { ModelPayload } from './components/GlobalHooks';
|
import type { ModelPayload } from './hooks/useSyncModelStores';
|
||||||
import { getSettings } from './lib/store';
|
import { getSettings } from './lib/store';
|
||||||
|
|
||||||
function setFontSizeOnDocument(fontSize: number) {
|
function setFontSizeOnDocument(fontSize: number) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
|
||||||
import { useCookieJars } from './useCookieJars';
|
import { useCookieJars } from './useCookieJars';
|
||||||
import { useKeyValue } from './useKeyValue';
|
|
||||||
|
|
||||||
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
|
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
|
||||||
|
|
||||||
@@ -11,9 +9,8 @@ export function useActiveCookieJar() {
|
|||||||
const cookieJars = useCookieJars();
|
const cookieJars = useCookieJars();
|
||||||
|
|
||||||
const activeCookieJar = useMemo(() => {
|
const activeCookieJar = useMemo(() => {
|
||||||
if (cookieJars.data == null) return undefined;
|
return cookieJars.find((cookieJar) => cookieJar.id === activeCookieJarId) ?? null;
|
||||||
return cookieJars.data.find((cookieJar) => cookieJar.id === activeCookieJarId) ?? null;
|
}, [activeCookieJarId, cookieJars]);
|
||||||
}, [activeCookieJarId, cookieJars.data]);
|
|
||||||
|
|
||||||
return [activeCookieJar ?? null, setActiveCookieJarId] as const;
|
return [activeCookieJar ?? null, setActiveCookieJarId] as const;
|
||||||
}
|
}
|
||||||
@@ -22,13 +19,11 @@ export function useEnsureActiveCookieJar() {
|
|||||||
const cookieJars = useCookieJars();
|
const cookieJars = useCookieJars();
|
||||||
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
|
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cookieJars.data == null) return;
|
if (cookieJars.find((j) => j.id === activeCookieJarId)) {
|
||||||
|
|
||||||
if (cookieJars.data.find((j) => j.id === activeCookieJarId)) {
|
|
||||||
return; // There's an active jar
|
return; // There's an active jar
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstJar = cookieJars.data[0];
|
const firstJar = cookieJars[0];
|
||||||
if (firstJar == null) {
|
if (firstJar == null) {
|
||||||
console.log("Workspace doesn't have any cookie jars to activate");
|
console.log("Workspace doesn't have any cookie jars to activate");
|
||||||
return;
|
return;
|
||||||
@@ -37,35 +32,7 @@ export function useEnsureActiveCookieJar() {
|
|||||||
// There's no active jar, so set it to the first one
|
// There's no active jar, so set it to the first one
|
||||||
console.log('Setting active cookie jar to', firstJar.id);
|
console.log('Setting active cookie jar to', firstJar.id);
|
||||||
setActiveCookieJarId(firstJar.id);
|
setActiveCookieJarId(firstJar.id);
|
||||||
}, [activeCookieJarId, cookieJars, cookieJars.data, setActiveCookieJarId]);
|
}, [activeCookieJarId, cookieJars, setActiveCookieJarId]);
|
||||||
}
|
|
||||||
|
|
||||||
export function useMigrateActiveCookieJarId() {
|
|
||||||
const workspace = useActiveWorkspace();
|
|
||||||
const [, setActiveCookieJarId] = useActiveCookieJarId();
|
|
||||||
const {
|
|
||||||
set: setLegacyActiveCookieJarId,
|
|
||||||
value: legacyActiveCookieJarId,
|
|
||||||
isLoading: isLoadingLegacyActiveCookieJarId,
|
|
||||||
} = useKeyValue<string | null>({
|
|
||||||
namespace: 'global',
|
|
||||||
key: ['activeCookieJar', workspace?.id ?? 'n/a'],
|
|
||||||
fallback: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (legacyActiveCookieJarId == null || isLoadingLegacyActiveCookieJarId) return;
|
|
||||||
|
|
||||||
console.log('Migrating active cookie jar ID to query param', legacyActiveCookieJarId);
|
|
||||||
setActiveCookieJarId(legacyActiveCookieJarId);
|
|
||||||
setLegacyActiveCookieJarId(null).catch(console.error);
|
|
||||||
}, [
|
|
||||||
workspace,
|
|
||||||
isLoadingLegacyActiveCookieJarId,
|
|
||||||
legacyActiveCookieJarId,
|
|
||||||
setActiveCookieJarId,
|
|
||||||
setLegacyActiveCookieJarId,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useActiveCookieJarId() {
|
function useActiveCookieJarId() {
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import type { CookieJar } from '@yaakapp-internal/models';
|
import type { CookieJar } from '@yaakapp-internal/models';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
|
||||||
|
|
||||||
export function cookieJarsQueryKey({ workspaceId }: { workspaceId: string }) {
|
export const cookieJarsAtom = atom<CookieJar[]>([]);
|
||||||
return ['cookie_jars', { workspaceId }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCookieJars() {
|
export function useCookieJars() {
|
||||||
const workspace = useActiveWorkspace();
|
return useAtomValue(cookieJarsAtom);
|
||||||
return useQuery({
|
|
||||||
enabled: workspace != null,
|
|
||||||
queryKey: cookieJarsQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
|
|
||||||
queryFn: async () => {
|
|
||||||
if (workspace == null) return [];
|
|
||||||
return (await invokeCmd('cmd_list_cookie_jars', {
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
})) as CookieJar[];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
import type { Environment } from '@yaakapp-internal/models';
|
import type { Environment } from '@yaakapp-internal/models';
|
||||||
import { atom, useAtom } from 'jotai/index';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useEffect } from 'react';
|
import { atom } from 'jotai/index';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
|
||||||
|
|
||||||
export const environmentsAtom = atom<Environment[]>([]);
|
export const environmentsAtom = atom<Environment[]>([]);
|
||||||
|
|
||||||
export function useEnvironments() {
|
export function useEnvironments() {
|
||||||
const [items, setItems] = useAtom(environmentsAtom);
|
return useAtomValue(environmentsAtom);
|
||||||
const workspace = useActiveWorkspace();
|
|
||||||
|
|
||||||
// Fetch new requests when workspace changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (workspace == null) return;
|
|
||||||
invokeCmd<Environment[]>('cmd_list_environments', { workspaceId: workspace.id }).then(setItems);
|
|
||||||
}, [setItems, workspace]);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import type { Folder } from '@yaakapp-internal/models';
|
import type { Folder } from '@yaakapp-internal/models';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
import { atom } from 'jotai/index';
|
||||||
|
|
||||||
export function foldersQueryKey({ workspaceId }: { workspaceId: string }) {
|
export const foldersAtom = atom<Folder[]>([]);
|
||||||
return ['folders', { workspaceId }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFolders() {
|
export function useFolders() {
|
||||||
const workspace = useActiveWorkspace();
|
return useAtomValue(foldersAtom);
|
||||||
return (
|
|
||||||
useQuery({
|
|
||||||
enabled: workspace != null,
|
|
||||||
queryKey: foldersQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
|
|
||||||
queryFn: async () => {
|
|
||||||
if (workspace == null) return [];
|
|
||||||
return (await invokeCmd('cmd_list_folders', { workspaceId: workspace.id })) as Folder[];
|
|
||||||
},
|
|
||||||
}).data ?? []
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin';
|
||||||
|
import { githubLight } from '../lib/theme/themes/github';
|
||||||
|
import { hotdogStandDefault } from '../lib/theme/themes/hotdog-stand';
|
||||||
|
import { monokaiProDefault } from '../lib/theme/themes/monokai-pro';
|
||||||
|
import { rosePineDefault } from '../lib/theme/themes/rose-pine';
|
||||||
|
import { yaakDark } from '../lib/theme/themes/yaak';
|
||||||
|
import { getThemeCSS } from '../lib/theme/window';
|
||||||
|
import { useCopy } from './useCopy';
|
||||||
|
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||||
|
|
||||||
|
export function useGenerateThemeCss() {
|
||||||
|
const copy = useCopy();
|
||||||
|
useListenToTauriEvent('generate_theme_css', () => {
|
||||||
|
const themesCss = [
|
||||||
|
yaakDark,
|
||||||
|
monokaiProDefault,
|
||||||
|
rosePineDefault,
|
||||||
|
catppuccinMacchiato,
|
||||||
|
githubLight,
|
||||||
|
hotdogStandDefault,
|
||||||
|
]
|
||||||
|
.map(getThemeCSS)
|
||||||
|
.join('\n\n');
|
||||||
|
copy(themesCss);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,24 +1,8 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import type { GrpcConnection } from '@yaakapp-internal/models';
|
import type { GrpcConnection } from '@yaakapp-internal/models';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { atom, useAtomValue } from 'jotai/index';
|
||||||
|
|
||||||
export function grpcConnectionsQueryKey({ requestId }: { requestId: string }) {
|
export const grpcConnectionsAtom = atom<GrpcConnection[]>([]);
|
||||||
return ['grpc_connections', { requestId }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGrpcConnections(requestId: string | null) {
|
export function useGrpcConnections() {
|
||||||
return (
|
return useAtomValue(grpcConnectionsAtom);
|
||||||
useQuery<GrpcConnection[]>({
|
|
||||||
enabled: requestId !== null,
|
|
||||||
initialData: [],
|
|
||||||
queryKey: grpcConnectionsQueryKey({ requestId: requestId ?? 'n/a' }),
|
|
||||||
queryFn: async () => {
|
|
||||||
if (requestId == null) return [];
|
|
||||||
return (await invokeCmd('cmd_list_grpc_connections', {
|
|
||||||
requestId,
|
|
||||||
limit: 200,
|
|
||||||
})) as GrpcConnection[];
|
|
||||||
},
|
|
||||||
}).data ?? []
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
|
||||||
|
|
||||||
export const grpcRequestsAtom = atom<GrpcRequest[]>([]);
|
export const grpcRequestsAtom = atom<GrpcRequest[]>([]);
|
||||||
|
|
||||||
export function useGrpcRequests() {
|
export function useGrpcRequests() {
|
||||||
const [items, setItems] = useAtom(grpcRequestsAtom);
|
return useAtomValue(grpcRequestsAtom);
|
||||||
const workspace = useActiveWorkspace();
|
|
||||||
|
|
||||||
// Fetch new requests when workspace changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (workspace == null) return;
|
|
||||||
invokeCmd<GrpcRequest[]>('cmd_list_grpc_requests', { workspaceId: workspace.id }).then(
|
|
||||||
setItems,
|
|
||||||
);
|
|
||||||
}, [setItems, workspace]);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import type { HttpRequest } from '@yaakapp-internal/models';
|
|||||||
import type {
|
import type {
|
||||||
CallHttpRequestActionRequest,
|
CallHttpRequestActionRequest,
|
||||||
GetHttpRequestActionsResponse,
|
GetHttpRequestActionsResponse,
|
||||||
|
HttpRequestAction,
|
||||||
} from '@yaakapp-internal/plugin';
|
} from '@yaakapp-internal/plugin';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { usePluginsKey } from './usePlugins';
|
import { usePluginsKey } from './usePlugins';
|
||||||
|
|
||||||
|
export type CallableHttpRequestAction = Pick<HttpRequestAction, 'key' | 'label' | 'icon'> & {
|
||||||
|
call: (httpRequest: HttpRequest) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
export function useHttpRequestActions() {
|
export function useHttpRequestActions() {
|
||||||
const pluginsKey = usePluginsKey();
|
const pluginsKey = usePluginsKey();
|
||||||
|
|
||||||
@@ -20,7 +25,7 @@ export function useHttpRequestActions() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const actions: CallableHttpRequestAction[] =
|
||||||
httpRequestActions.data?.flatMap((r) =>
|
httpRequestActions.data?.flatMap((r) =>
|
||||||
r.actions.map((a) => ({
|
r.actions.map((a) => ({
|
||||||
key: a.key,
|
key: a.key,
|
||||||
@@ -35,6 +40,7 @@ export function useHttpRequestActions() {
|
|||||||
await invokeCmd('cmd_call_http_request_action', { req: payload });
|
await invokeCmd('cmd_call_http_request_action', { req: payload });
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
) ?? []
|
) ?? [];
|
||||||
);
|
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
|
||||||
|
|
||||||
export const httpRequestsAtom = atom<HttpRequest[]>([]);
|
export const httpRequestsAtom = atom<HttpRequest[]>([]);
|
||||||
|
|
||||||
export function useHttpRequests() {
|
export function useHttpRequests() {
|
||||||
const [items, setItems] = useAtom(httpRequestsAtom);
|
return useAtomValue(httpRequestsAtom);
|
||||||
const workspace = useActiveWorkspace();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (workspace == null) return;
|
|
||||||
invokeCmd<HttpRequest[]>('cmd_list_http_requests', { workspaceId: workspace.id }).then(
|
|
||||||
setItems,
|
|
||||||
);
|
|
||||||
}, [setItems, workspace]);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { atom } from 'jotai/index';
|
||||||
|
|
||||||
export function httpResponsesQueryKey({ requestId }: { requestId: string }) {
|
export const httpResponsesAtom = atom<HttpResponse[]>([]);
|
||||||
return ['http_responses', { requestId }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useHttpResponses(requestId: string | null) {
|
export function useHttpResponses() {
|
||||||
return (
|
return useAtomValue(httpResponsesAtom);
|
||||||
useQuery<HttpResponse[]>({
|
|
||||||
enabled: requestId !== null,
|
|
||||||
initialData: [],
|
|
||||||
queryKey: httpResponsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
|
||||||
queryFn: async () => {
|
|
||||||
if (requestId == null) return [];
|
|
||||||
return (await invokeCmd('cmd_list_http_responses', {
|
|
||||||
requestId,
|
|
||||||
limit: 200,
|
|
||||||
})) as HttpResponse[];
|
|
||||||
},
|
|
||||||
}).data ?? []
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ import type { GrpcConnection } from '@yaakapp-internal/models';
|
|||||||
import { useGrpcConnections } from './useGrpcConnections';
|
import { useGrpcConnections } from './useGrpcConnections';
|
||||||
|
|
||||||
export function useLatestGrpcConnection(requestId: string | null): GrpcConnection | null {
|
export function useLatestGrpcConnection(requestId: string | null): GrpcConnection | null {
|
||||||
const connections = useGrpcConnections(requestId);
|
return useGrpcConnections().find((c) => c.requestId === requestId) ?? null;
|
||||||
return connections[0] ?? null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ import type { HttpResponse } from '@yaakapp-internal/models';
|
|||||||
import { useHttpResponses } from './useHttpResponses';
|
import { useHttpResponses } from './useHttpResponses';
|
||||||
|
|
||||||
export function useLatestHttpResponse(requestId: string | null): HttpResponse | null {
|
export function useLatestHttpResponse(requestId: string | null): HttpResponse | null {
|
||||||
const responses = useHttpResponses(requestId);
|
return useHttpResponses().find((r) => r.requestId === requestId) ?? null;
|
||||||
return responses[0] ?? null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function usePinnedGrpcConnection(activeRequest: GrpcRequest) {
|
|||||||
fallback: null,
|
fallback: null,
|
||||||
namespace: 'global',
|
namespace: 'global',
|
||||||
});
|
});
|
||||||
const connections = useGrpcConnections(activeRequest.id);
|
const connections = useGrpcConnections().filter((c) => c.requestId === activeRequest.id);
|
||||||
const activeConnection: GrpcConnection | null =
|
const activeConnection: GrpcConnection | null =
|
||||||
connections.find((r) => r.id === pinnedConnectionId) ?? latestConnection;
|
connections.find((r) => r.id === pinnedConnectionId) ?? latestConnection;
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { useLatestHttpResponse } from './useLatestHttpResponse';
|
|||||||
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
||||||
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
||||||
const { set, value: pinnedResponseId } = useKeyValue<string | null>({
|
const { set, value: pinnedResponseId } = useKeyValue<string | null>({
|
||||||
// Key on latest response instead of activeRequest because responses change out of band of active request
|
// Key on the latest response instead of activeRequest because responses change out of band of active request
|
||||||
key: ['pinned_http_response_id', latestResponse?.id ?? 'n/a'],
|
key: ['pinned_http_response_id', latestResponse?.id ?? 'n/a'],
|
||||||
fallback: null,
|
fallback: null,
|
||||||
namespace: 'global',
|
namespace: 'global',
|
||||||
});
|
});
|
||||||
const responses = useHttpResponses(activeRequest.id);
|
const allResponses = useHttpResponses();
|
||||||
|
const responses = allResponses.filter((r) => r.requestId === activeRequest.id);
|
||||||
const activeResponse: HttpResponse | null =
|
const activeResponse: HttpResponse | null =
|
||||||
responses.find((r) => r.id === pinnedResponseId) ?? latestResponse;
|
responses.find((r) => r.id === pinnedResponseId) ?? latestResponse;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function useRecentCookieJars() {
|
|||||||
}, [activeCookieJarId]);
|
}, [activeCookieJarId]);
|
||||||
|
|
||||||
const onlyValidIds = useMemo(
|
const onlyValidIds = useMemo(
|
||||||
() => kv.value?.filter((id) => cookieJars.data?.some((e) => e.id === id)) ?? [],
|
() => kv.value?.filter((id) => cookieJars.some((e) => e.id === id)) ?? [],
|
||||||
[kv.value, cookieJars],
|
[kv.value, cookieJars],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||||
import { trackEvent } from '../lib/analytics';
|
import { trackEvent } from '../lib/analytics';
|
||||||
|
import { getHttpRequest } from '../lib/store';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { useActiveCookieJar } from './useActiveCookieJar';
|
import { useActiveCookieJar } from './useActiveCookieJar';
|
||||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||||
import { useAlert } from './useAlert';
|
import { useAlert } from './useAlert';
|
||||||
import { useHttpRequests } from './useHttpRequests';
|
|
||||||
|
|
||||||
export function useSendAnyHttpRequest() {
|
export function useSendAnyHttpRequest() {
|
||||||
const [environment] = useActiveEnvironment();
|
|
||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
|
const [environment] = useActiveEnvironment();
|
||||||
const [activeCookieJar] = useActiveCookieJar();
|
const [activeCookieJar] = useActiveCookieJar();
|
||||||
const requests = useHttpRequests();
|
|
||||||
return useMutation<HttpResponse | null, string, string | null>({
|
return useMutation<HttpResponse | null, string, string | null>({
|
||||||
mutationKey: ['send_any_request'],
|
mutationKey: ['send_any_request'],
|
||||||
mutationFn: async (id) => {
|
mutationFn: async (id) => {
|
||||||
const request = requests.find((r) => r.id === id) ?? null;
|
const request = await getHttpRequest(id);
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useSettings } from './useSettings';
|
||||||
|
|
||||||
|
export function useSyncFontSizeSetting() {
|
||||||
|
const settings = useSettings();
|
||||||
|
useEffect(() => {
|
||||||
|
if (settings == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { interfaceScale, editorFontSize } = settings;
|
||||||
|
getCurrentWebviewWindow().setZoom(interfaceScale).catch(console.error);
|
||||||
|
document.documentElement.style.setProperty('--editor-font-size', `${editorFontSize}px`);
|
||||||
|
}, [settings]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import type { AnyModel } from '@yaakapp-internal/models';
|
||||||
|
import { useSetAtom } from 'jotai/index';
|
||||||
|
import { extractKeyValue } from '../lib/keyValueStore';
|
||||||
|
import { modelsEq } from '../lib/model_util';
|
||||||
|
import { cookieJarsAtom } from './useCookieJars';
|
||||||
|
import { environmentsAtom } from './useEnvironments';
|
||||||
|
import { foldersAtom } from './useFolders';
|
||||||
|
import { grpcConnectionsAtom } from './useGrpcConnections';
|
||||||
|
import { grpcEventsQueryKey } from './useGrpcEvents';
|
||||||
|
import { grpcRequestsAtom } from './useGrpcRequests';
|
||||||
|
import { httpRequestsAtom } from './useHttpRequests';
|
||||||
|
import { httpResponsesAtom } from './useHttpResponses';
|
||||||
|
import { keyValueQueryKey } from './useKeyValue';
|
||||||
|
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||||
|
import { pluginsAtom } from './usePlugins';
|
||||||
|
import { useRequestUpdateKey } from './useRequestUpdateKey';
|
||||||
|
import { settingsAtom } from './useSettings';
|
||||||
|
import { workspacesAtom } from './useWorkspaces';
|
||||||
|
|
||||||
|
export interface ModelPayload {
|
||||||
|
model: AnyModel;
|
||||||
|
windowLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSyncModelStores() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||||
|
|
||||||
|
const setSettings = useSetAtom(settingsAtom);
|
||||||
|
const setWorkspaces = useSetAtom(workspacesAtom);
|
||||||
|
const setCookieJars = useSetAtom(cookieJarsAtom);
|
||||||
|
const setFolders = useSetAtom(foldersAtom);
|
||||||
|
const setPlugins = useSetAtom(pluginsAtom);
|
||||||
|
const setHttpRequests = useSetAtom(httpRequestsAtom);
|
||||||
|
const setHttpResponses = useSetAtom(httpResponsesAtom);
|
||||||
|
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
|
||||||
|
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
|
||||||
|
const setEnvironments = useSetAtom(environmentsAtom);
|
||||||
|
|
||||||
|
useListenToTauriEvent<ModelPayload>('upserted_model', ({ payload }) => {
|
||||||
|
if (payload.model.model !== 'key_value') {
|
||||||
|
console.log('Upserted model', payload.model);
|
||||||
|
}
|
||||||
|
const { model, windowLabel } = payload;
|
||||||
|
const queryKey =
|
||||||
|
model.model === 'grpc_event'
|
||||||
|
? grpcEventsQueryKey(model)
|
||||||
|
: model.model === 'key_value'
|
||||||
|
? keyValueQueryKey(model)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) {
|
||||||
|
wasUpdatedExternally(model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushToFront = (['http_response', 'grpc_connection'] as AnyModel['model'][]).includes(
|
||||||
|
model.model,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldIgnoreModel(model, windowLabel)) return;
|
||||||
|
|
||||||
|
if (model.model === 'workspace') {
|
||||||
|
setWorkspaces(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'plugin') {
|
||||||
|
setPlugins(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'http_request') {
|
||||||
|
setHttpRequests(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'folder') {
|
||||||
|
setFolders(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'http_response') {
|
||||||
|
setHttpResponses(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'grpc_request') {
|
||||||
|
setGrpcRequests(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'grpc_connection') {
|
||||||
|
setGrpcConnections(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'environment') {
|
||||||
|
setEnvironments(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'cookie_jar') {
|
||||||
|
setCookieJars(updateModelList(model, pushToFront));
|
||||||
|
} else if (model.model === 'settings') {
|
||||||
|
setSettings(model);
|
||||||
|
} else if (queryKey != null) {
|
||||||
|
// TODO: Convert all models to use Jotai
|
||||||
|
queryClient.setQueryData(queryKey, (current: unknown) => {
|
||||||
|
if (model.model === 'key_value') {
|
||||||
|
// Special-case for KeyValue
|
||||||
|
return extractKeyValue(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(current)) {
|
||||||
|
return updateModelList(model, pushToFront)(current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useListenToTauriEvent<ModelPayload>('deleted_model', ({ payload }) => {
|
||||||
|
const { model, windowLabel } = payload;
|
||||||
|
if (shouldIgnoreModel(model, windowLabel)) return;
|
||||||
|
|
||||||
|
console.log('Delete model', payload.model);
|
||||||
|
|
||||||
|
if (model.model === 'workspace') {
|
||||||
|
setWorkspaces(removeById(model));
|
||||||
|
} else if (model.model === 'plugin') {
|
||||||
|
setPlugins(removeById(model));
|
||||||
|
} else if (model.model === 'http_request') {
|
||||||
|
setHttpRequests(removeById(model));
|
||||||
|
} else if (model.model === 'http_response') {
|
||||||
|
setHttpResponses(removeById(model));
|
||||||
|
} else if (model.model === 'folder') {
|
||||||
|
setFolders(removeById(model));
|
||||||
|
} else if (model.model === 'environment') {
|
||||||
|
setEnvironments(removeById(model));
|
||||||
|
} else if (model.model === 'grpc_request') {
|
||||||
|
setGrpcRequests(removeById(model));
|
||||||
|
} else if (model.model === 'grpc_connection') {
|
||||||
|
setGrpcConnections(removeById(model));
|
||||||
|
} else if (model.model === 'grpc_event') {
|
||||||
|
queryClient.setQueryData(grpcEventsQueryKey(model), removeById(model));
|
||||||
|
} else if (model.model === 'key_value') {
|
||||||
|
queryClient.setQueryData(keyValueQueryKey(model), undefined);
|
||||||
|
} else if (model.model === 'cookie_jar') {
|
||||||
|
setCookieJars(removeById(model));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateModelList<T extends AnyModel>(model: T, pushToFront: boolean) {
|
||||||
|
return (current: T[]): T[] => {
|
||||||
|
const index = current.findIndex((v) => modelsEq(v, model)) ?? -1;
|
||||||
|
if (index >= 0) {
|
||||||
|
return [...current.slice(0, index), model, ...current.slice(index + 1)];
|
||||||
|
} else {
|
||||||
|
return pushToFront ? [model, ...(current ?? [])] : [...(current ?? []), model];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeById<T extends { id: string }>(model: T) {
|
||||||
|
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldIgnoreModel = (payload: AnyModel, windowLabel: string) => {
|
||||||
|
if (windowLabel === getCurrentWebviewWindow().label) {
|
||||||
|
// Never ignore same-window updates
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (payload.model === 'key_value') {
|
||||||
|
return payload.namespace === 'no_sync';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { useSetAtom } from 'jotai/index';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { invokeCmd } from '../lib/tauri';
|
||||||
|
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||||
|
import { cookieJarsAtom } from './useCookieJars';
|
||||||
|
import { environmentsAtom } from './useEnvironments';
|
||||||
|
import { foldersAtom } from './useFolders';
|
||||||
|
import { grpcConnectionsAtom } from './useGrpcConnections';
|
||||||
|
import { grpcRequestsAtom } from './useGrpcRequests';
|
||||||
|
import { httpRequestsAtom } from './useHttpRequests';
|
||||||
|
import { httpResponsesAtom } from './useHttpResponses';
|
||||||
|
|
||||||
|
export function useSyncWorkspaceChildModels() {
|
||||||
|
const setCookieJars = useSetAtom(cookieJarsAtom);
|
||||||
|
const setFolders = useSetAtom(foldersAtom);
|
||||||
|
const setHttpRequests = useSetAtom(httpRequestsAtom);
|
||||||
|
const setHttpResponses = useSetAtom(httpResponsesAtom);
|
||||||
|
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
|
||||||
|
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
|
||||||
|
const setEnvironments = useSetAtom(environmentsAtom);
|
||||||
|
|
||||||
|
const workspace = useActiveWorkspace();
|
||||||
|
const workspaceId = workspace?.id ?? 'n/a';
|
||||||
|
useEffect(() => {
|
||||||
|
(async function () {
|
||||||
|
// Set the things we need first, first
|
||||||
|
setHttpRequests(await invokeCmd('cmd_list_http_requests', { workspaceId }));
|
||||||
|
setGrpcRequests(await invokeCmd('cmd_list_grpc_requests', { workspaceId }));
|
||||||
|
setFolders(await invokeCmd('cmd_list_folders', { workspaceId }));
|
||||||
|
|
||||||
|
// Then, set the rest
|
||||||
|
setCookieJars(await invokeCmd('cmd_list_cookie_jars', { workspaceId }));
|
||||||
|
setHttpResponses(await invokeCmd('cmd_list_http_responses', { workspaceId }));
|
||||||
|
setGrpcConnections(await invokeCmd('cmd_list_grpc_connections', { workspaceId }));
|
||||||
|
setEnvironments(await invokeCmd('cmd_list_environments', { workspaceId }));
|
||||||
|
})().catch(console.error);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [workspaceId]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { useHotKey } from './useHotKey';
|
||||||
|
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||||
|
import { useZoom } from './useZoom';
|
||||||
|
|
||||||
|
export function useSyncZoomSetting() {
|
||||||
|
// Handle Zoom.
|
||||||
|
// Note, Mac handles it in the app menu, so need to also handle keyboard
|
||||||
|
// shortcuts for Windows/Linux
|
||||||
|
const zoom = useZoom();
|
||||||
|
useHotKey('app.zoom_in', zoom.zoomIn);
|
||||||
|
useListenToTauriEvent('zoom_in', zoom.zoomIn);
|
||||||
|
useHotKey('app.zoom_out', zoom.zoomOut);
|
||||||
|
useListenToTauriEvent('zoom_out', zoom.zoomOut);
|
||||||
|
useHotKey('app.zoom_reset', zoom.zoomReset);
|
||||||
|
useListenToTauriEvent('zoom_reset', zoom.zoomReset);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev --force",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "tsc && eslint . --ext .ts,.tsx"
|
"lint": "tsc && eslint . --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { emit, listen } from '@tauri-apps/api/event';
|
import { emit, listen } from '@tauri-apps/api/event';
|
||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
import type { ModelPayload } from './components/GlobalHooks';
|
import type { ModelPayload } from './hooks/useSyncModelStores';
|
||||||
import { getSettings } from './lib/store';
|
import { getSettings } from './lib/store';
|
||||||
import type { Appearance } from './lib/theme/appearance';
|
import type { Appearance } from './lib/theme/appearance';
|
||||||
import { getCSSAppearance, subscribeToPreferredAppearance } from './lib/theme/appearance';
|
import { getCSSAppearance, subscribeToPreferredAppearance } from './lib/theme/appearance';
|
||||||
|
|||||||
Reference in New Issue
Block a user