mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Generalized frontend model store (#193)
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -21,6 +21,9 @@
|
|||||||
"src-tauri/yaak-ws",
|
"src-tauri/yaak-ws",
|
||||||
"src-web"
|
"src-web"
|
||||||
],
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"jotai": "^2.12.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.4.0",
|
"@tauri-apps/cli": "^2.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||||
@@ -8558,9 +8561,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jotai": {
|
"node_modules/jotai": {
|
||||||
"version": "2.10.0",
|
"version": "2.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.2.tgz",
|
||||||
"integrity": "sha512-8W4u0aRlOIwGlLQ0sqfl/c6+eExl5D8lZgAUolirZLktyaj4WnxO/8a0HEPmtriQAB6X5LMhXzZVmw02X0P0qQ==",
|
"integrity": "sha512-oN8715y7MkjXlSrpyjlR887TOuc/NLZMs9gvgtfWH/JP47ChwO0lR2ijSwBvPMYyXRAPT+liIAhuBavluKGgtA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
@@ -15741,7 +15744,6 @@
|
|||||||
"fuzzbunny": "^1.0.1",
|
"fuzzbunny": "^1.0.1",
|
||||||
"hexy": "^0.3.5",
|
"hexy": "^0.3.5",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"jotai": "^2.9.3",
|
|
||||||
"js-md5": "^0.8.3",
|
"js-md5": "^0.8.3",
|
||||||
"lucide-react": "^0.474.0",
|
"lucide-react": "^0.474.0",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
"tauri-before-build": "npm run bootstrap && npm run --workspaces --if-present build",
|
"tauri-before-build": "npm run bootstrap && npm run --workspaces --if-present build",
|
||||||
"tauri-before-dev": "npm run --workspaces --if-present dev"
|
"tauri-before-dev": "npm run --workspaces --if-present dev"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jotai": "^2.12.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.4.0",
|
"@tauri-apps/cli": "^2.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||||
|
|||||||
2
src-tauri/gen/schemas/acl-manifests.json
generated
2
src-tauri/gen/schemas/acl-manifests.json
generated
File diff suppressed because one or more lines are too long
50
src-tauri/gen/schemas/desktop-schema.json
generated
50
src-tauri/gen/schemas/desktop-schema.json
generated
@@ -5642,21 +5642,71 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:allow-delete"
|
"const": "yaak-models:allow-delete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-grpc-events"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the upsert command without any pre-configured scope.",
|
"description": "Enables the upsert command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:allow-upsert"
|
"const": "yaak-models:allow-upsert"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-workspace-models"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the delete command without any pre-configured scope.",
|
"description": "Denies the delete command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:deny-delete"
|
"const": "yaak-models:deny-delete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-grpc-events"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the upsert command without any pre-configured scope.",
|
"description": "Denies the upsert command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:deny-upsert"
|
"const": "yaak-models:deny-upsert"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-workspace-models"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Default permissions for the plugin",
|
"description": "Default permissions for the plugin",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
50
src-tauri/gen/schemas/macOS-schema.json
generated
50
src-tauri/gen/schemas/macOS-schema.json
generated
@@ -5642,21 +5642,71 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:allow-delete"
|
"const": "yaak-models:allow-delete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-grpc-events"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the upsert command without any pre-configured scope.",
|
"description": "Enables the upsert command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:allow-upsert"
|
"const": "yaak-models:allow-upsert"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:allow-workspace-models"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the delete command without any pre-configured scope.",
|
"description": "Denies the delete command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:deny-delete"
|
"const": "yaak-models:deny-delete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-grpc-events"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the upsert command without any pre-configured scope.",
|
"description": "Denies the upsert command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-models:deny-upsert"
|
"const": "yaak-models:deny-upsert"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-models:deny-workspace-models"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Default permissions for the plugin",
|
"description": "Default permissions for the plugin",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
43
src-tauri/migrations/20250326193143_key-value-id.sql
Normal file
43
src-tauri/migrations/20250326193143_key-value-id.sql
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
-- 1. Create the new table with `id` as the primary key
|
||||||
|
CREATE TABLE key_values_new
|
||||||
|
(
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
model TEXT DEFAULT 'key_value' NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
deleted_at DATETIME,
|
||||||
|
namespace TEXT NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2. Copy data from the old table
|
||||||
|
INSERT INTO key_values_new (id, model, created_at, updated_at, deleted_at, namespace, key, value)
|
||||||
|
SELECT (
|
||||||
|
-- This is the best way to generate a random string in SQLite, apparently
|
||||||
|
'kv_' || SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
|
||||||
|
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1)
|
||||||
|
) AS id,
|
||||||
|
model,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
deleted_at,
|
||||||
|
namespace,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
FROM key_values;
|
||||||
|
|
||||||
|
-- 3. Drop the old table
|
||||||
|
DROP TABLE key_values;
|
||||||
|
|
||||||
|
-- 4. Rename the new table
|
||||||
|
ALTER TABLE key_values_new
|
||||||
|
RENAME TO key_values;
|
||||||
@@ -24,11 +24,11 @@ use tokio::fs::{create_dir_all, File};
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::sync::watch::Receiver;
|
use tokio::sync::watch::Receiver;
|
||||||
use tokio::sync::{oneshot, Mutex};
|
use tokio::sync::{oneshot, Mutex};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
|
||||||
use yaak_models::models::{
|
use yaak_models::models::{
|
||||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
||||||
HttpResponseState, ProxySetting, ProxySettingAuth,
|
HttpResponseState, ProxySetting, ProxySettingAuth,
|
||||||
};
|
};
|
||||||
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext,
|
CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext,
|
||||||
@@ -46,10 +46,9 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
let app_handle = window.app_handle().clone();
|
let app_handle = window.app_handle().clone();
|
||||||
let plugin_manager = app_handle.state::<PluginManager>();
|
let plugin_manager = app_handle.state::<PluginManager>();
|
||||||
let update_source = &UpdateSource::from_window(&window);
|
|
||||||
let (settings, workspace) = {
|
let (settings, workspace) = {
|
||||||
let db = window.db();
|
let db = window.db();
|
||||||
let settings = db.get_or_create_settings(update_source);
|
let settings = db.get_settings();
|
||||||
let workspace = db.get_workspace(&unrendered_request.workspace_id)?;
|
let workspace = db.get_workspace(&unrendered_request.workspace_id)?;
|
||||||
(settings, workspace)
|
(settings, workspace)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ use error::Result as YaakResult;
|
|||||||
use eventsource_client::{EventParser, SSE};
|
use eventsource_client::{EventParser, SSE};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{File, create_dir_all};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, panic};
|
use std::{fs, panic};
|
||||||
use tauri::{is_dev, AppHandle, Emitter, RunEvent, State, WebviewWindow};
|
use tauri::{AppHandle, Emitter, RunEvent, State, WebviewWindow, is_dev};
|
||||||
use tauri::{Listener, Runtime};
|
use tauri::{Listener, Runtime};
|
||||||
use tauri::{Manager, WindowEvent};
|
use tauri::{Manager, WindowEvent};
|
||||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||||
@@ -29,15 +29,15 @@ use tokio::sync::Mutex;
|
|||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use yaak_common::window::WorkspaceWindowTrait;
|
use yaak_common::window::WorkspaceWindowTrait;
|
||||||
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
|
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
|
||||||
use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
|
use yaak_grpc::{Code, ServiceDefinition, deserialize_message, serialize_message};
|
||||||
use yaak_models::models::{
|
use yaak_models::models::{
|
||||||
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState,
|
CookieJar, Environment, Folder, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType,
|
||||||
GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue,
|
GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, WebsocketRequest, Workspace,
|
||||||
Plugin, Settings, WebsocketRequest, Workspace, WorkspaceMeta,
|
WorkspaceMeta,
|
||||||
};
|
};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::{
|
use yaak_models::util::{
|
||||||
get_workspace_export_resources, maybe_gen_id, maybe_gen_id_opt, BatchUpsertResult, UpdateSource,
|
BatchUpsertResult, UpdateSource, get_workspace_export_resources, maybe_gen_id, maybe_gen_id_opt,
|
||||||
};
|
};
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
|
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
|
||||||
@@ -1064,47 +1064,6 @@ fn response_err<R: Runtime>(
|
|||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_set_update_mode<R: Runtime>(
|
|
||||||
update_mode: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<KeyValue> {
|
|
||||||
let (key_value, _created) = app_handle.db().set_key_value_raw(
|
|
||||||
"app",
|
|
||||||
"update_mode",
|
|
||||||
update_mode,
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
);
|
|
||||||
Ok(key_value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_get_key_value<R: Runtime>(
|
|
||||||
namespace: &str,
|
|
||||||
key: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Option<KeyValue>> {
|
|
||||||
Ok(app_handle.db().get_key_value_raw(namespace, key))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_set_key_value<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
namespace: &str,
|
|
||||||
key: &str,
|
|
||||||
value: &str,
|
|
||||||
) -> YaakResult<KeyValue> {
|
|
||||||
let (key_value, _created) = app_handle.db().set_key_value_raw(
|
|
||||||
namespace,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
);
|
|
||||||
Ok(key_value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_install_plugin<R: Runtime>(
|
async fn cmd_install_plugin<R: Runtime>(
|
||||||
directory: &str,
|
directory: &str,
|
||||||
@@ -1144,64 +1103,6 @@ async fn cmd_uninstall_plugin<R: Runtime>(
|
|||||||
Ok(plugin)
|
Ok(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_cookie_jar<R: Runtime>(
|
|
||||||
cookie_jar: CookieJar,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<CookieJar> {
|
|
||||||
Ok(app_handle.db().upsert_cookie_jar(&cookie_jar, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_cookie_jar<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
cookie_jar_id: &str,
|
|
||||||
) -> YaakResult<CookieJar> {
|
|
||||||
Ok(app_handle
|
|
||||||
.db()
|
|
||||||
.delete_cookie_jar_by_id(cookie_jar_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_create_cookie_jar<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
name: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<CookieJar> {
|
|
||||||
Ok(app_handle.db().upsert_cookie_jar(
|
|
||||||
&CookieJar {
|
|
||||||
name: name.to_string(),
|
|
||||||
workspace_id: workspace_id.to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_create_environment<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
environment_id: Option<&str>,
|
|
||||||
name: &str,
|
|
||||||
variables: Vec<EnvironmentVariable>,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Environment> {
|
|
||||||
Ok(app_handle.db().upsert_environment(
|
|
||||||
&Environment {
|
|
||||||
workspace_id: workspace_id.to_string(),
|
|
||||||
environment_id: environment_id.map(|s| s.to_string()),
|
|
||||||
name: name.to_string(),
|
|
||||||
variables,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_create_grpc_request<R: Runtime>(
|
async fn cmd_create_grpc_request<R: Runtime>(
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
@@ -1223,193 +1124,6 @@ async fn cmd_create_grpc_request<R: Runtime>(
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_duplicate_grpc_request<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<GrpcRequest> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let request = db.get_grpc_request(id)?;
|
|
||||||
Ok(db.duplicate_grpc_request(&request, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_duplicate_folder<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
id: &str,
|
|
||||||
) -> YaakResult<Folder> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let folder = db.get_folder(id)?;
|
|
||||||
Ok(db.duplicate_folder(&folder, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_duplicate_http_request<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<HttpRequest> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let request = db.get_http_request(id)?;
|
|
||||||
Ok(db.duplicate_http_request(&request, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_workspace<R: Runtime>(
|
|
||||||
workspace: Workspace,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Workspace> {
|
|
||||||
Ok(app_handle.db().upsert_workspace(&workspace, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_workspace_meta<R: Runtime>(
|
|
||||||
workspace_meta: WorkspaceMeta,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<WorkspaceMeta> {
|
|
||||||
Ok(app_handle
|
|
||||||
.db()
|
|
||||||
.upsert_workspace_meta(&workspace_meta, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_environment<R: Runtime>(
|
|
||||||
environment: Environment,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Environment> {
|
|
||||||
Ok(app_handle.db().upsert_environment(&environment, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_grpc_request<R: Runtime>(
|
|
||||||
request: GrpcRequest,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<GrpcRequest> {
|
|
||||||
Ok(app_handle.db().upsert_grpc_request(&request, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_upsert_http_request<R: Runtime>(
|
|
||||||
request: HttpRequest,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<HttpRequest> {
|
|
||||||
Ok(app_handle.db().upsert(&request, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_grpc_request<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
request_id: &str,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<GrpcRequest> {
|
|
||||||
Ok(app_handle
|
|
||||||
.db()
|
|
||||||
.delete_grpc_request_by_id(request_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_http_request<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
request_id: &str,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<HttpRequest> {
|
|
||||||
Ok(app_handle
|
|
||||||
.db()
|
|
||||||
.delete_http_request_by_id(request_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_folders<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<Folder>> {
|
|
||||||
Ok(app_handle.db().list_folders(workspace_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_folder<R: Runtime>(
|
|
||||||
folder: Folder,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Folder> {
|
|
||||||
Ok(app_handle.db().upsert_folder(&folder, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_folder<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
folder_id: &str,
|
|
||||||
) -> YaakResult<Folder> {
|
|
||||||
Ok(app_handle.db().delete_folder_by_id(folder_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_environment<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
environment_id: &str,
|
|
||||||
) -> YaakResult<Environment> {
|
|
||||||
Ok(app_handle
|
|
||||||
.db()
|
|
||||||
.delete_environment_by_id(environment_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_grpc_connections<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<GrpcConnection>> {
|
|
||||||
Ok(app_handle.db().list_grpc_connections_for_workspace(workspace_id, None)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_grpc_events<R: Runtime>(
|
|
||||||
connection_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<GrpcEvent>> {
|
|
||||||
Ok(app_handle.db().list_grpc_events(connection_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_grpc_requests<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<GrpcRequest>> {
|
|
||||||
Ok(app_handle.db().list_grpc_requests(workspace_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_http_requests<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<HttpRequest>> {
|
|
||||||
Ok(app_handle.db().list_http_requests(workspace_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_environments<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<Environment>> {
|
|
||||||
// Not sure of a better place to put this...
|
|
||||||
let db = app_handle.db();
|
|
||||||
db.ensure_base_environment(workspace_id)?;
|
|
||||||
Ok(db.list_environments(workspace_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_plugins<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<Vec<Plugin>> {
|
|
||||||
Ok(app_handle.db().list_plugins()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_reload_plugins<R: Runtime>(
|
async fn cmd_reload_plugins<R: Runtime>(
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
@@ -1438,99 +1152,6 @@ async fn cmd_plugin_info<R: Runtime>(
|
|||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_get_settings<R: Runtime>(window: WebviewWindow<R>) -> YaakResult<Settings> {
|
|
||||||
Ok(window.db().get_or_create_settings(&UpdateSource::from_window(&window)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_update_settings<R: Runtime>(
|
|
||||||
settings: Settings,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Settings> {
|
|
||||||
Ok(app_handle.db().upsert_settings(&settings, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_get_folder<R: Runtime>(id: &str, app_handle: AppHandle<R>) -> YaakResult<Folder> {
|
|
||||||
Ok(app_handle.db().get_folder(id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_cookie_jars<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Vec<CookieJar>> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let cookie_jars = db.list_cookie_jars(workspace_id)?;
|
|
||||||
|
|
||||||
if cookie_jars.is_empty() {
|
|
||||||
let cookie_jar = db.upsert_cookie_jar(
|
|
||||||
&CookieJar {
|
|
||||||
name: "Default".to_string(),
|
|
||||||
workspace_id: workspace_id.to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
)?;
|
|
||||||
Ok(vec![cookie_jar])
|
|
||||||
} else {
|
|
||||||
Ok(cookie_jars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_key_values<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<Vec<KeyValue>> {
|
|
||||||
Ok(app_handle.db().list_key_values_raw()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_get_environment<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Environment> {
|
|
||||||
Ok(app_handle.db().get_environment(id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_get_workspace<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Workspace> {
|
|
||||||
Ok(app_handle.db().get_workspace(id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_http_responses<R: Runtime>(
|
|
||||||
workspace_id: &str,
|
|
||||||
limit: Option<i64>,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
) -> YaakResult<Vec<HttpResponse>> {
|
|
||||||
Ok(app_handle.db().list_http_responses_for_workspace(workspace_id, limit.map(|l| l as u64))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_http_response<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<HttpResponse> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let http_response = db.get_http_response(id)?;
|
|
||||||
Ok(db.delete_http_response(&http_response, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_grpc_connection<R: Runtime>(
|
|
||||||
id: &str,
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<GrpcConnection> {
|
|
||||||
Ok(app_handle.db().delete_grpc_connection_by_id(id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_delete_all_grpc_connections<R: Runtime>(
|
async fn cmd_delete_all_grpc_connections<R: Runtime>(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
@@ -1568,29 +1189,6 @@ async fn cmd_delete_all_http_responses<R: Runtime>(
|
|||||||
.delete_all_http_responses_for_request(request_id, &UpdateSource::from_window(&window))?)
|
.delete_all_http_responses_for_request(request_id, &UpdateSource::from_window(&window))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_list_workspaces<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
) -> YaakResult<Vec<Workspace>> {
|
|
||||||
let db = app_handle.db();
|
|
||||||
let mut workspaces = db.find_all::<Workspace>()?;
|
|
||||||
|
|
||||||
if workspaces.is_empty() {
|
|
||||||
workspaces.push(db.upsert_workspace(
|
|
||||||
&Workspace {
|
|
||||||
name: "Yaak".to_string(),
|
|
||||||
setting_follow_redirects: true,
|
|
||||||
setting_validate_certificates: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&UpdateSource::from_window(&window),
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(workspaces)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_get_workspace_meta<R: Runtime>(
|
async fn cmd_get_workspace_meta<R: Runtime>(
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
@@ -1599,7 +1197,7 @@ async fn cmd_get_workspace_meta<R: Runtime>(
|
|||||||
) -> YaakResult<WorkspaceMeta> {
|
) -> YaakResult<WorkspaceMeta> {
|
||||||
let db = app_handle.db();
|
let db = app_handle.db();
|
||||||
let workspace = db.get_workspace(workspace_id)?;
|
let workspace = db.get_workspace(workspace_id)?;
|
||||||
Ok(db.get_or_create_workspace_meta(&workspace, &UpdateSource::from_window(&window))?)
|
Ok(db.get_or_create_workspace_meta(&workspace.id, &UpdateSource::from_window(&window))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1620,15 +1218,6 @@ async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> Result<(), Str
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn cmd_delete_workspace<R: Runtime>(
|
|
||||||
app_handle: AppHandle<R>,
|
|
||||||
window: WebviewWindow<R>,
|
|
||||||
workspace_id: &str,
|
|
||||||
) -> YaakResult<Workspace> {
|
|
||||||
Ok(app_handle.db().delete_workspace_by_id(workspace_id, &UpdateSource::from_window(&window))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_check_for_updates<R: Runtime>(
|
async fn cmd_check_for_updates<R: Runtime>(
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
@@ -1722,53 +1311,24 @@ pub fn run() {
|
|||||||
cmd_call_http_authentication_action,
|
cmd_call_http_authentication_action,
|
||||||
cmd_call_http_request_action,
|
cmd_call_http_request_action,
|
||||||
cmd_check_for_updates,
|
cmd_check_for_updates,
|
||||||
cmd_create_cookie_jar,
|
|
||||||
cmd_create_environment,
|
|
||||||
cmd_create_grpc_request,
|
cmd_create_grpc_request,
|
||||||
cmd_curl_to_request,
|
cmd_curl_to_request,
|
||||||
cmd_delete_all_grpc_connections,
|
cmd_delete_all_grpc_connections,
|
||||||
cmd_delete_all_http_responses,
|
cmd_delete_all_http_responses,
|
||||||
cmd_delete_cookie_jar,
|
|
||||||
cmd_delete_environment,
|
|
||||||
cmd_delete_folder,
|
|
||||||
cmd_delete_grpc_connection,
|
|
||||||
cmd_delete_grpc_request,
|
|
||||||
cmd_delete_http_request,
|
|
||||||
cmd_delete_http_response,
|
|
||||||
cmd_delete_send_history,
|
cmd_delete_send_history,
|
||||||
cmd_delete_workspace,
|
|
||||||
cmd_dismiss_notification,
|
cmd_dismiss_notification,
|
||||||
cmd_duplicate_folder,
|
|
||||||
cmd_duplicate_grpc_request,
|
|
||||||
cmd_duplicate_http_request,
|
|
||||||
cmd_export_data,
|
cmd_export_data,
|
||||||
cmd_filter_response,
|
cmd_filter_response,
|
||||||
cmd_format_json,
|
cmd_format_json,
|
||||||
cmd_get_environment,
|
|
||||||
cmd_get_folder,
|
|
||||||
cmd_get_http_authentication_summaries,
|
cmd_get_http_authentication_summaries,
|
||||||
cmd_get_http_authentication_config,
|
cmd_get_http_authentication_config,
|
||||||
cmd_get_key_value,
|
|
||||||
cmd_get_settings,
|
|
||||||
cmd_get_sse_events,
|
cmd_get_sse_events,
|
||||||
cmd_get_workspace,
|
|
||||||
cmd_get_workspace_meta,
|
cmd_get_workspace_meta,
|
||||||
cmd_grpc_go,
|
cmd_grpc_go,
|
||||||
cmd_grpc_reflect,
|
cmd_grpc_reflect,
|
||||||
cmd_http_request_actions,
|
cmd_http_request_actions,
|
||||||
cmd_import_data,
|
cmd_import_data,
|
||||||
cmd_install_plugin,
|
cmd_install_plugin,
|
||||||
cmd_list_cookie_jars,
|
|
||||||
cmd_list_environments,
|
|
||||||
cmd_list_folders,
|
|
||||||
cmd_list_grpc_connections,
|
|
||||||
cmd_list_grpc_events,
|
|
||||||
cmd_list_grpc_requests,
|
|
||||||
cmd_list_key_values,
|
|
||||||
cmd_list_http_requests,
|
|
||||||
cmd_list_http_responses,
|
|
||||||
cmd_list_plugins,
|
|
||||||
cmd_list_workspaces,
|
|
||||||
cmd_metadata,
|
cmd_metadata,
|
||||||
cmd_new_child_window,
|
cmd_new_child_window,
|
||||||
cmd_new_main_window,
|
cmd_new_main_window,
|
||||||
@@ -1779,19 +1339,9 @@ pub fn run() {
|
|||||||
cmd_save_response,
|
cmd_save_response,
|
||||||
cmd_send_ephemeral_request,
|
cmd_send_ephemeral_request,
|
||||||
cmd_send_http_request,
|
cmd_send_http_request,
|
||||||
cmd_set_key_value,
|
|
||||||
cmd_set_update_mode,
|
|
||||||
cmd_template_functions,
|
cmd_template_functions,
|
||||||
cmd_template_tokens_to_string,
|
cmd_template_tokens_to_string,
|
||||||
cmd_uninstall_plugin,
|
cmd_uninstall_plugin,
|
||||||
cmd_update_cookie_jar,
|
|
||||||
cmd_update_environment,
|
|
||||||
cmd_update_folder,
|
|
||||||
cmd_update_grpc_request,
|
|
||||||
cmd_upsert_http_request,
|
|
||||||
cmd_update_settings,
|
|
||||||
cmd_update_workspace,
|
|
||||||
cmd_update_workspace_meta,
|
|
||||||
])
|
])
|
||||||
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
|
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
@@ -1859,7 +1409,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_update_mode<R: Runtime>(window: &WebviewWindow<R>) -> YaakResult<UpdateMode> {
|
async fn get_update_mode<R: Runtime>(window: &WebviewWindow<R>) -> YaakResult<UpdateMode> {
|
||||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
let settings = window.db().get_settings();
|
||||||
Ok(UpdateMode::new(settings.update_channel.as_str()))
|
Ok(UpdateMode::new(settings.update_channel.as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1934,13 +1484,10 @@ fn get_window_from_window_context<R: Runtime>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let window = app_handle.webview_windows().iter().find_map(|(_, w)| {
|
let window = app_handle
|
||||||
if w.label() == label {
|
.webview_windows()
|
||||||
Some(w.to_owned())
|
.iter()
|
||||||
} else {
|
.find_map(|(_, w)| if w.label() == label { Some(w.to_owned()) } else { None });
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if window.is_none() {
|
if window.is_none() {
|
||||||
error!("Failed to find window by {window_context:?}");
|
error!("Failed to find window by {window_context:?}");
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use tauri_plugin_dialog::{DialogExt, MessageDialogButtons};
|
|||||||
use tauri_plugin_updater::UpdaterExt;
|
use tauri_plugin_updater::UpdaterExt;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
|
|
||||||
use crate::is_dev;
|
use crate::is_dev;
|
||||||
@@ -67,7 +66,7 @@ impl YaakUpdater {
|
|||||||
mode: UpdateMode,
|
mode: UpdateMode,
|
||||||
update_trigger: UpdateTrigger,
|
update_trigger: UpdateTrigger,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
let settings = window.db().get_settings();
|
||||||
let update_key = format!("{:x}", md5::compute(settings.id));
|
let update_key = format!("{:x}", md5::compute(settings.id));
|
||||||
self.last_update_check = SystemTime::now();
|
self.last_update_check = SystemTime::now();
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ pub async fn check_license<R: Runtime>(
|
|||||||
payload: CheckActivationRequestPayload,
|
payload: CheckActivationRequestPayload,
|
||||||
) -> Result<LicenseCheckStatus> {
|
) -> Result<LicenseCheckStatus> {
|
||||||
let activation_id = get_activation_id(window.app_handle()).await;
|
let activation_id = get_activation_id(window.app_handle()).await;
|
||||||
let settings = window.db().get_or_create_settings(&UpdateSource::from_window(window));
|
let settings = window.db().get_settings();
|
||||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
||||||
|
|
||||||
debug!("Trial ending at {trial_end:?}");
|
debug!("Trial ending at {trial_end:?}");
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export type HttpResponseState = "initialized" | "connected" | "closed";
|
|||||||
|
|
||||||
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
||||||
|
|
||||||
export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" };
|
export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" };
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
const COMMANDS: &[&str] = &["upsert", "delete"];
|
const COMMANDS: &[&str] = &[
|
||||||
|
"delete",
|
||||||
|
"duplicate",
|
||||||
|
"get_settings",
|
||||||
|
"grpc_events",
|
||||||
|
"upsert",
|
||||||
|
"websocket_events",
|
||||||
|
"workspace_models",
|
||||||
|
];
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri_plugin::Builder::new(COMMANDS).build();
|
tauri_plugin::Builder::new(COMMANDS).build();
|
||||||
|
|||||||
80
src-tauri/yaak-models/guest-js/atoms.ts
Normal file
80
src-tauri/yaak-models/guest-js/atoms.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
|
import { selectAtom } from 'jotai/utils';
|
||||||
|
import type { AnyModel } from '../bindings/gen_models';
|
||||||
|
import { ExtractModel } from './types';
|
||||||
|
import { newStoreData } from './util';
|
||||||
|
|
||||||
|
export const modelStoreDataAtom = atom(newStoreData());
|
||||||
|
|
||||||
|
export const cookieJarsAtom = createOrderedModelAtom('cookie_jar', 'name', 'asc');
|
||||||
|
export const environmentsAtom = createOrderedModelAtom('environment', 'name', 'asc');
|
||||||
|
export const foldersAtom = createModelAtom('folder');
|
||||||
|
export const grpcConnectionsAtom = createOrderedModelAtom('grpc_connection', 'createdAt', 'desc');
|
||||||
|
export const grpcEventsAtom = createOrderedModelAtom('grpc_event', 'createdAt', 'asc');
|
||||||
|
export const grpcRequestsAtom = createModelAtom('grpc_request');
|
||||||
|
export const httpRequestsAtom = createModelAtom('http_request');
|
||||||
|
export const httpResponsesAtom = createOrderedModelAtom('http_response', 'createdAt', 'desc');
|
||||||
|
export const keyValuesAtom = createModelAtom('key_value');
|
||||||
|
export const pluginsAtom = createModelAtom('plugin');
|
||||||
|
export const settingsAtom = createSingularModelAtom('settings');
|
||||||
|
export const websocketRequestsAtom = createModelAtom('websocket_request');
|
||||||
|
export const websocketEventsAtom = createOrderedModelAtom('websocket_event', 'createdAt', 'asc');
|
||||||
|
export const websocketConnectionsAtom = createOrderedModelAtom(
|
||||||
|
'websocket_connection',
|
||||||
|
'createdAt',
|
||||||
|
'desc',
|
||||||
|
);
|
||||||
|
export const workspaceMetasAtom = createModelAtom('workspace_meta');
|
||||||
|
export const workspacesAtom = createOrderedModelAtom('workspace', 'name', 'asc');
|
||||||
|
|
||||||
|
export function createModelAtom<M extends AnyModel['model']>(modelType: M) {
|
||||||
|
return selectAtom(
|
||||||
|
modelStoreDataAtom,
|
||||||
|
(data) => Object.values(data[modelType] ?? {}),
|
||||||
|
shallowEqual,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSingularModelAtom<M extends AnyModel['model']>(modelType: M) {
|
||||||
|
return selectAtom(modelStoreDataAtom, (data) => {
|
||||||
|
const modelData = Object.values(data[modelType] ?? {});
|
||||||
|
const item = modelData[0];
|
||||||
|
if (item == null) throw new Error('Failed creating singular model with no data: ' + modelType);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createOrderedModelAtom<M extends AnyModel['model']>(
|
||||||
|
modelType: M,
|
||||||
|
field: keyof ExtractModel<AnyModel, M>,
|
||||||
|
order: 'asc' | 'desc',
|
||||||
|
) {
|
||||||
|
return selectAtom(
|
||||||
|
modelStoreDataAtom,
|
||||||
|
(data) => {
|
||||||
|
const modelData = data[modelType] ?? {};
|
||||||
|
return Object.values(modelData).sort(
|
||||||
|
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
||||||
|
const n = a[field] > b[field] ? 1 : -1;
|
||||||
|
return order === 'desc' ? n * -1 : n;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
shallowEqual,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shallowEqual<T>(a: T[], b: T[]): boolean {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
10
src-tauri/yaak-models/guest-js/index.ts
Normal file
10
src-tauri/yaak-models/guest-js/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { AnyModel } from '../bindings/gen_models';
|
||||||
|
|
||||||
|
export * from '../bindings/gen_models';
|
||||||
|
export * from './store';
|
||||||
|
export * from './atoms';
|
||||||
|
|
||||||
|
export function modelTypeLabel(m: AnyModel): string {
|
||||||
|
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
return m.model.split('_').map(capitalize).join(' ');
|
||||||
|
}
|
||||||
199
src-tauri/yaak-models/guest-js/store.ts
Normal file
199
src-tauri/yaak-models/guest-js/store.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { AnyModel, ModelPayload } from '../bindings/gen_models';
|
||||||
|
import { modelStoreDataAtom } from './atoms';
|
||||||
|
import { ExtractModel, JotaiStore, ModelStoreData } from './types';
|
||||||
|
import { newStoreData } from './util';
|
||||||
|
|
||||||
|
let _store: JotaiStore | null = null;
|
||||||
|
|
||||||
|
export function initModelStore(store: JotaiStore) {
|
||||||
|
_store = store;
|
||||||
|
|
||||||
|
getCurrentWebviewWindow()
|
||||||
|
.listen<ModelPayload>('upserted_model', ({ payload }) => {
|
||||||
|
if (shouldIgnoreModel(payload)) return;
|
||||||
|
|
||||||
|
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[payload.model.model]: {
|
||||||
|
...prev[payload.model.model],
|
||||||
|
[payload.model.id]: payload.model,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
getCurrentWebviewWindow()
|
||||||
|
.listen<ModelPayload>('deleted_model', ({ payload }) => {
|
||||||
|
if (shouldIgnoreModel(payload)) return;
|
||||||
|
|
||||||
|
console.log('Delete model', payload);
|
||||||
|
|
||||||
|
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||||
|
const modelData = { ...prev[payload.model.model] };
|
||||||
|
delete modelData[payload.model.id];
|
||||||
|
return { ...prev, [payload.model.model]: modelData };
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustStore(): JotaiStore {
|
||||||
|
if (_store == null) {
|
||||||
|
throw new Error('Model store was not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
return _store;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _activeWorkspaceId: string | null = null;
|
||||||
|
|
||||||
|
export async function changeModelStoreWorkspace(workspaceId: string | null) {
|
||||||
|
console.log('Syncing models with new workspace', workspaceId);
|
||||||
|
const workspaceModels = await invoke<AnyModel[]>('plugin:yaak-models|workspace_models', {
|
||||||
|
workspaceId, // NOTE: if no workspace id provided, it will just fetch global models
|
||||||
|
});
|
||||||
|
const data = newStoreData();
|
||||||
|
for (const model of workspaceModels) {
|
||||||
|
data[model.model][model.id] = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
mustStore().set(modelStoreDataAtom, data);
|
||||||
|
|
||||||
|
console.log('Synced model store with workspace', workspaceId, data);
|
||||||
|
|
||||||
|
_activeWorkspaceId = workspaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAnyModel(id: string): AnyModel | null {
|
||||||
|
let data = mustStore().get(modelStoreDataAtom);
|
||||||
|
for (const modelData of Object.values(data)) {
|
||||||
|
let model = modelData[id];
|
||||||
|
if (model != null) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||||
|
modelType: M | M[],
|
||||||
|
id: string,
|
||||||
|
): T | null {
|
||||||
|
let data = mustStore().get(modelStoreDataAtom);
|
||||||
|
for (const t of Array.isArray(modelType) ? modelType : [modelType]) {
|
||||||
|
let v = data[t][id];
|
||||||
|
if (v?.model === t) return v as T;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchModelById<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||||
|
model: M,
|
||||||
|
id: string,
|
||||||
|
patch: Partial<T> | ((prev: T) => T),
|
||||||
|
): Promise<string> {
|
||||||
|
let prev = getModel<M, T>(model, id);
|
||||||
|
if (prev == null) {
|
||||||
|
throw new Error(`Failed to get model to patch id=${id} model=${model}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newModel = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch };
|
||||||
|
return invoke<string>('plugin:yaak-models|upsert', { model: newModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||||
|
base: Pick<T, 'id' | 'model'>,
|
||||||
|
patch: Partial<T>,
|
||||||
|
): Promise<string> {
|
||||||
|
return patchModelById<M, T>(base.model, base.id, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteModelById<
|
||||||
|
M extends AnyModel['model'],
|
||||||
|
T extends ExtractModel<AnyModel, M>,
|
||||||
|
>(modelType: M | M[], id: string) {
|
||||||
|
let model = getModel<M, T>(modelType, id);
|
||||||
|
await deleteModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||||
|
model: T | null,
|
||||||
|
) {
|
||||||
|
if (model == null) {
|
||||||
|
throw new Error('Failed to delete null model');
|
||||||
|
}
|
||||||
|
await invoke<string>('plugin:yaak-models|delete', { model });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function duplicateModelById<
|
||||||
|
M extends AnyModel['model'],
|
||||||
|
T extends ExtractModel<AnyModel, M>,
|
||||||
|
>(modelType: M | M[], id: string) {
|
||||||
|
let model = getModel<M, T>(modelType, id);
|
||||||
|
return duplicateModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function duplicateModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>(
|
||||||
|
model: T | null,
|
||||||
|
) {
|
||||||
|
if (model == null) {
|
||||||
|
throw new Error('Failed to delete null model');
|
||||||
|
}
|
||||||
|
return invoke<string>('plugin:yaak-models|duplicate', { model });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>(
|
||||||
|
patch: Partial<T> & Pick<T, 'model'>,
|
||||||
|
): Promise<string> {
|
||||||
|
return invoke<string>('plugin:yaak-models|upsert', { model: patch });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>(
|
||||||
|
patch: Partial<T> & Pick<T, 'model' | 'workspaceId'>,
|
||||||
|
): Promise<string> {
|
||||||
|
return invoke<string>('plugin:yaak-models|upsert', { model: patch });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceModelsInStore<
|
||||||
|
M extends AnyModel['model'],
|
||||||
|
T extends Extract<AnyModel, { model: M }>,
|
||||||
|
>(model: M, models: T[]) {
|
||||||
|
const newModels: Record<string, T> = {};
|
||||||
|
for (const model of models) {
|
||||||
|
newModels[model.id] = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[model]: newModels,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
|
||||||
|
// Never ignore updates from non-user sources
|
||||||
|
if (updateSource.type !== 'window') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never ignore same-window updates
|
||||||
|
if (updateSource.label === getCurrentWebviewWindow().label) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only sync models that belong to this workspace, if a workspace ID is present
|
||||||
|
if ('workspaceId' in model && model.workspaceId !== _activeWorkspaceId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.model === 'key_value' && model.namespace === 'no_sync') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
8
src-tauri/yaak-models/guest-js/types.ts
Normal file
8
src-tauri/yaak-models/guest-js/types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { createStore } from 'jotai/index';
|
||||||
|
import { AnyModel } from '../bindings/gen_models';
|
||||||
|
|
||||||
|
export type ExtractModel<T, M> = T extends { model: M } ? T : never;
|
||||||
|
export type ModelStoreData<T extends AnyModel = AnyModel> = {
|
||||||
|
[M in T['model']]: Record<string, Extract<T, { model: M }>>;
|
||||||
|
};
|
||||||
|
export type JotaiStore = ReturnType<typeof createStore>;
|
||||||
23
src-tauri/yaak-models/guest-js/util.ts
Normal file
23
src-tauri/yaak-models/guest-js/util.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ModelStoreData } from './types';
|
||||||
|
|
||||||
|
export function newStoreData(): ModelStoreData {
|
||||||
|
return {
|
||||||
|
cookie_jar: {},
|
||||||
|
environment: {},
|
||||||
|
folder: {},
|
||||||
|
grpc_connection: {},
|
||||||
|
grpc_event: {},
|
||||||
|
grpc_request: {},
|
||||||
|
http_request: {},
|
||||||
|
http_response: {},
|
||||||
|
key_value: {},
|
||||||
|
plugin: {},
|
||||||
|
settings: {},
|
||||||
|
sync_state: {},
|
||||||
|
websocket_connection: {},
|
||||||
|
websocket_event: {},
|
||||||
|
websocket_request: {},
|
||||||
|
workspace: {},
|
||||||
|
workspace_meta: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import type { AnyModel } from './bindings/gen_models';
|
|
||||||
|
|
||||||
export * from './bindings/gen_models';
|
|
||||||
|
|
||||||
export async function upsertAnyModel(model: AnyModel): Promise<String> {
|
|
||||||
return invoke<String>('plugin:yaak-models|upsert', { model });
|
|
||||||
}
|
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"name": "@yaakapp-internal/models",
|
"name": "@yaakapp-internal/models",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.ts"
|
"main": "guest-js/index.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-duplicate"
|
||||||
|
description = "Enables the duplicate command without any pre-configured scope."
|
||||||
|
commands.allow = ["duplicate"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-duplicate"
|
||||||
|
description = "Denies the duplicate command without any pre-configured scope."
|
||||||
|
commands.deny = ["duplicate"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-get-settings"
|
||||||
|
description = "Enables the get_settings command without any pre-configured scope."
|
||||||
|
commands.allow = ["get_settings"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-get-settings"
|
||||||
|
description = "Denies the get_settings command without any pre-configured scope."
|
||||||
|
commands.deny = ["get_settings"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-grpc-events"
|
||||||
|
description = "Enables the grpc_events command without any pre-configured scope."
|
||||||
|
commands.allow = ["grpc_events"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-grpc-events"
|
||||||
|
description = "Denies the grpc_events command without any pre-configured scope."
|
||||||
|
commands.deny = ["grpc_events"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-websocket-events"
|
||||||
|
description = "Enables the websocket_events command without any pre-configured scope."
|
||||||
|
commands.allow = ["websocket_events"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-websocket-events"
|
||||||
|
description = "Denies the websocket_events command without any pre-configured scope."
|
||||||
|
commands.deny = ["websocket_events"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-workspace-models"
|
||||||
|
description = "Enables the workspace_models command without any pre-configured scope."
|
||||||
|
commands.allow = ["workspace_models"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-workspace-models"
|
||||||
|
description = "Denies the workspace_models command without any pre-configured scope."
|
||||||
|
commands.deny = ["workspace_models"]
|
||||||
@@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
Default permissions for the plugin
|
Default permissions for the plugin
|
||||||
|
|
||||||
- `allow-upsert`
|
|
||||||
- `allow-delete`
|
- `allow-delete`
|
||||||
|
- `allow-duplicate`
|
||||||
|
- `allow-get-settings`
|
||||||
|
- `allow-grpc-events`
|
||||||
|
- `allow-upsert`
|
||||||
|
- `allow-websocket-events`
|
||||||
|
- `allow-workspace-models`
|
||||||
|
|
||||||
## Permission Table
|
## Permission Table
|
||||||
|
|
||||||
@@ -43,6 +48,84 @@ Denies the delete command without any pre-configured scope.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:allow-duplicate`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the duplicate command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:deny-duplicate`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the duplicate command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:allow-get-settings`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the get_settings command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:deny-get-settings`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the get_settings command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:allow-grpc-events`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the grpc_events command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:deny-grpc-events`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the grpc_events command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
`yaak-models:allow-upsert`
|
`yaak-models:allow-upsert`
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
@@ -63,6 +146,58 @@ Enables the upsert command without any pre-configured scope.
|
|||||||
|
|
||||||
Denies the upsert command without any pre-configured scope.
|
Denies the upsert command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:allow-websocket-events`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the websocket_events command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:deny-websocket-events`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the websocket_events command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:allow-workspace-models`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the workspace_models command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-models:deny-workspace-models`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the workspace_models command without any pre-configured scope.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
[default]
|
[default]
|
||||||
description = "Default permissions for the plugin"
|
description = "Default permissions for the plugin"
|
||||||
permissions = [
|
permissions = [
|
||||||
"allow-upsert",
|
|
||||||
"allow-delete",
|
"allow-delete",
|
||||||
|
"allow-duplicate",
|
||||||
|
"allow-get-settings",
|
||||||
|
"allow-grpc-events",
|
||||||
|
"allow-upsert",
|
||||||
|
"allow-websocket-events",
|
||||||
|
"allow-workspace-models",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -304,6 +304,36 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "deny-delete"
|
"const": "deny-delete"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the duplicate command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-duplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the get_settings command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-get-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-grpc-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the grpc_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-grpc-events"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the upsert command without any pre-configured scope.",
|
"description": "Enables the upsert command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -314,6 +344,26 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "deny-upsert"
|
"const": "deny-upsert"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the websocket_events command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-websocket-events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-workspace-models"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the workspace_models command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-workspace-models"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Default permissions for the plugin",
|
"description": "Default permissions for the plugin",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
use crate::error::Error::GenericError;
|
use crate::error::Error::GenericError;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::AnyModel;
|
use crate::models::{AnyModel, GrpcEvent, Settings, WebsocketEvent};
|
||||||
use crate::query_manager::QueryManagerExt;
|
use crate::query_manager::QueryManagerExt;
|
||||||
use crate::util::UpdateSource;
|
use crate::util::UpdateSource;
|
||||||
use tauri::{Runtime, WebviewWindow};
|
use tauri::{AppHandle, Runtime, WebviewWindow};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) async fn upsert<R: Runtime>(
|
pub(crate) fn upsert<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||||
window: WebviewWindow<R>,
|
|
||||||
model: AnyModel,
|
|
||||||
) -> Result<String> {
|
|
||||||
let db = window.db();
|
let db = window.db();
|
||||||
let source = &UpdateSource::from_window(&window);
|
let source = &UpdateSource::from_window(&window);
|
||||||
let id = match model {
|
let id = match model {
|
||||||
AnyModel::HttpRequest(m) => db.upsert_http_request(&m, source)?.id,
|
|
||||||
AnyModel::CookieJar(m) => db.upsert_cookie_jar(&m, source)?.id,
|
AnyModel::CookieJar(m) => db.upsert_cookie_jar(&m, source)?.id,
|
||||||
AnyModel::Environment(m) => db.upsert_environment(&m, source)?.id,
|
AnyModel::Environment(m) => db.upsert_environment(&m, source)?.id,
|
||||||
AnyModel::Folder(m) => db.upsert_folder(&m, source)?.id,
|
AnyModel::Folder(m) => db.upsert_folder(&m, source)?.id,
|
||||||
AnyModel::GrpcRequest(m) => db.upsert_grpc_request(&m, source)?.id,
|
AnyModel::GrpcRequest(m) => db.upsert_grpc_request(&m, source)?.id,
|
||||||
|
AnyModel::HttpRequest(m) => db.upsert_http_request(&m, source)?.id,
|
||||||
AnyModel::HttpResponse(m) => db.upsert_http_response(&m, source)?.id,
|
AnyModel::HttpResponse(m) => db.upsert_http_response(&m, source)?.id,
|
||||||
|
AnyModel::KeyValue(m) => db.upsert_key_value(&m, source)?.id,
|
||||||
AnyModel::Plugin(m) => db.upsert_plugin(&m, source)?.id,
|
AnyModel::Plugin(m) => db.upsert_plugin(&m, source)?.id,
|
||||||
AnyModel::Settings(m) => db.upsert_settings(&m, source)?.id,
|
AnyModel::Settings(m) => db.upsert_settings(&m, source)?.id,
|
||||||
AnyModel::WebsocketRequest(m) => db.upsert_websocket_request(&m, source)?.id,
|
AnyModel::WebsocketRequest(m) => db.upsert_websocket_request(&m, source)?.id,
|
||||||
@@ -32,22 +30,95 @@ pub(crate) async fn upsert<R: Runtime>(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub(crate) fn delete<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
pub(crate) fn delete<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||||
let db = window.db();
|
// Use transaction for deletions because it might recurse
|
||||||
let source = &UpdateSource::from_window(&window);
|
window.with_tx(|tx| {
|
||||||
let id = match model {
|
let source = &UpdateSource::from_window(&window);
|
||||||
AnyModel::HttpRequest(m) => db.delete_http_request(&m, source)?.id,
|
let id = match model {
|
||||||
AnyModel::CookieJar(m) => db.delete_cookie_jar(&m, source)?.id,
|
AnyModel::CookieJar(m) => tx.delete_cookie_jar(&m, source)?.id,
|
||||||
AnyModel::Environment(m) => db.delete_environment(&m, source)?.id,
|
AnyModel::Environment(m) => tx.delete_environment(&m, source)?.id,
|
||||||
AnyModel::Folder(m) => db.delete_folder(&m, source)?.id,
|
AnyModel::Folder(m) => tx.delete_folder(&m, source)?.id,
|
||||||
AnyModel::GrpcConnection(m) => db.delete_grpc_connection(&m, source)?.id,
|
AnyModel::GrpcConnection(m) => tx.delete_grpc_connection(&m, source)?.id,
|
||||||
AnyModel::GrpcRequest(m) => db.delete_grpc_request(&m, source)?.id,
|
AnyModel::GrpcRequest(m) => tx.delete_grpc_request(&m, source)?.id,
|
||||||
AnyModel::HttpResponse(m) => db.delete_http_response(&m, source)?.id,
|
AnyModel::HttpRequest(m) => tx.delete_http_request(&m, source)?.id,
|
||||||
AnyModel::Plugin(m) => db.delete_plugin(&m, source)?.id,
|
AnyModel::HttpResponse(m) => tx.delete_http_response(&m, source)?.id,
|
||||||
AnyModel::WebsocketConnection(m) => db.delete_websocket_connection(&m, source)?.id,
|
AnyModel::Plugin(m) => tx.delete_plugin(&m, source)?.id,
|
||||||
AnyModel::WebsocketRequest(m) => db.delete_websocket_request(&m, source)?.id,
|
AnyModel::WebsocketConnection(m) => tx.delete_websocket_connection(&m, source)?.id,
|
||||||
AnyModel::Workspace(m) => db.delete_workspace(&m, source)?.id,
|
AnyModel::WebsocketRequest(m) => tx.delete_websocket_request(&m, source)?.id,
|
||||||
a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))),
|
AnyModel::Workspace(m) => tx.delete_workspace(&m, source)?.id,
|
||||||
};
|
a => return Err(GenericError(format!("Cannot delete AnyModel {a:?})"))),
|
||||||
|
};
|
||||||
Ok(id)
|
Ok(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn duplicate<R: Runtime>(window: WebviewWindow<R>, model: AnyModel) -> Result<String> {
|
||||||
|
// Use transaction for duplications because it might recurse
|
||||||
|
window.with_tx(|tx| {
|
||||||
|
let source = &UpdateSource::from_window(&window);
|
||||||
|
let id = match model {
|
||||||
|
AnyModel::Environment(m) => tx.duplicate_environment(&m, source)?.id,
|
||||||
|
AnyModel::Folder(m) => tx.duplicate_folder(&m, source)?.id,
|
||||||
|
AnyModel::GrpcRequest(m) => tx.duplicate_grpc_request(&m, source)?.id,
|
||||||
|
AnyModel::HttpRequest(m) => tx.duplicate_http_request(&m, source)?.id,
|
||||||
|
AnyModel::WebsocketRequest(m) => tx.duplicate_websocket_request(&m, source)?.id,
|
||||||
|
a => return Err(GenericError(format!("Cannot duplicate AnyModel {a:?})"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn websocket_events<R: Runtime>(
|
||||||
|
app_handle: AppHandle<R>,
|
||||||
|
connection_id: &str,
|
||||||
|
) -> Result<Vec<WebsocketEvent>> {
|
||||||
|
Ok(app_handle.db().list_websocket_events(connection_id)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn grpc_events<R: Runtime>(
|
||||||
|
app_handle: AppHandle<R>,
|
||||||
|
connection_id: &str,
|
||||||
|
) -> Result<Vec<GrpcEvent>> {
|
||||||
|
Ok(app_handle.db().list_grpc_events(connection_id)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn get_settings<R: Runtime>(app_handle: AppHandle<R>) -> Result<Settings> {
|
||||||
|
Ok(app_handle.db().get_settings())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn workspace_models<R: Runtime>(
|
||||||
|
window: WebviewWindow<R>,
|
||||||
|
workspace_id: Option<&str>,
|
||||||
|
) -> Result<Vec<AnyModel>> {
|
||||||
|
let db = window.db();
|
||||||
|
let mut l: Vec<AnyModel> = Vec::new();
|
||||||
|
|
||||||
|
// Add the settings
|
||||||
|
l.push(db.get_settings().into());
|
||||||
|
|
||||||
|
// Add global models
|
||||||
|
l.append(&mut db.list_workspaces()?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_key_values()?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_plugins()?.into_iter().map(Into::into).collect());
|
||||||
|
|
||||||
|
// Add the workspace children
|
||||||
|
if let Some(wid) = workspace_id {
|
||||||
|
l.append(&mut db.list_cookie_jars(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_environments(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_folders(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_grpc_connections(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_grpc_requests(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_http_requests(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_http_responses(wid, None)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_websocket_connections(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_websocket_requests(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
l.append(&mut db.list_workspace_metas(wid)?.into_iter().map(Into::into).collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(l)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use sea_query_rusqlite::RusqliteBinder;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub struct DbContext<'a> {
|
pub struct DbContext<'a> {
|
||||||
pub(crate) tx: mpsc::Sender<ModelPayload>,
|
pub(crate) events_tx: mpsc::Sender<ModelPayload>,
|
||||||
pub(crate) conn: ConnectionOrTx<'a>,
|
pub(crate) conn: ConnectionOrTx<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ impl<'a> DbContext<'a> {
|
|||||||
update_source: source.clone(),
|
update_source: source.clone(),
|
||||||
change: ModelChangeEvent::Upsert,
|
change: ModelChangeEvent::Upsert,
|
||||||
};
|
};
|
||||||
self.tx.try_send(payload).unwrap();
|
self.events_tx.try_send(payload).unwrap();
|
||||||
|
|
||||||
Ok(m)
|
Ok(m)
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ impl<'a> DbContext<'a> {
|
|||||||
change: ModelChangeEvent::Delete,
|
change: ModelChangeEvent::Delete,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.tx.try_send(payload).unwrap();
|
self.events_tx.try_send(payload).unwrap();
|
||||||
Ok(m.clone())
|
Ok(m.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::commands::{delete, upsert};
|
use crate::commands::*;
|
||||||
use crate::query_manager::QueryManager;
|
use crate::query_manager::QueryManager;
|
||||||
use crate::util::ModelChangeEvent;
|
use crate::util::ModelChangeEvent;
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -22,11 +22,11 @@ mod commands;
|
|||||||
mod connection_or_tx;
|
mod connection_or_tx;
|
||||||
mod db_context;
|
mod db_context;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod query_manager;
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod queries;
|
pub mod queries;
|
||||||
pub mod util;
|
pub mod query_manager;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
pub struct SqliteConnection(pub Mutex<Pool<SqliteConnectionManager>>);
|
pub struct SqliteConnection(pub Mutex<Pool<SqliteConnectionManager>>);
|
||||||
|
|
||||||
@@ -38,7 +38,15 @@ impl SqliteConnection {
|
|||||||
|
|
||||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
tauri::plugin::Builder::new("yaak-models")
|
tauri::plugin::Builder::new("yaak-models")
|
||||||
.invoke_handler(generate_handler![upsert, delete])
|
.invoke_handler(generate_handler![
|
||||||
|
upsert,
|
||||||
|
delete,
|
||||||
|
duplicate,
|
||||||
|
workspace_models,
|
||||||
|
grpc_events,
|
||||||
|
websocket_events,
|
||||||
|
get_settings,
|
||||||
|
])
|
||||||
.setup(|app_handle, _api| {
|
.setup(|app_handle, _api| {
|
||||||
let app_path = app_handle.path().app_data_dir().unwrap();
|
let app_path = app_handle.path().app_data_dir().unwrap();
|
||||||
create_dir_all(app_path.clone()).expect("Problem creating App directory!");
|
create_dir_all(app_path.clone()).expect("Problem creating App directory!");
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ pub struct Settings {
|
|||||||
pub interface_scale: f32,
|
pub interface_scale: f32,
|
||||||
pub open_workspace_new_window: Option<bool>,
|
pub open_workspace_new_window: Option<bool>,
|
||||||
pub proxy: Option<ProxySetting>,
|
pub proxy: Option<ProxySetting>,
|
||||||
pub theme: String,
|
|
||||||
pub theme_dark: String,
|
pub theme_dark: String,
|
||||||
pub theme_light: String,
|
pub theme_light: String,
|
||||||
pub update_channel: String,
|
pub update_channel: String,
|
||||||
@@ -148,7 +147,6 @@ impl UpsertModelInfo for Settings {
|
|||||||
(InterfaceFontSize, self.interface_font_size.into()),
|
(InterfaceFontSize, self.interface_font_size.into()),
|
||||||
(InterfaceScale, self.interface_scale.into()),
|
(InterfaceScale, self.interface_scale.into()),
|
||||||
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
||||||
(Theme, self.theme.as_str().into()),
|
|
||||||
(ThemeDark, self.theme_dark.as_str().into()),
|
(ThemeDark, self.theme_dark.as_str().into()),
|
||||||
(ThemeLight, self.theme_light.as_str().into()),
|
(ThemeLight, self.theme_light.as_str().into()),
|
||||||
(UpdateChannel, self.update_channel.into()),
|
(UpdateChannel, self.update_channel.into()),
|
||||||
@@ -167,7 +165,6 @@ impl UpsertModelInfo for Settings {
|
|||||||
SettingsIden::InterfaceScale,
|
SettingsIden::InterfaceScale,
|
||||||
SettingsIden::OpenWorkspaceNewWindow,
|
SettingsIden::OpenWorkspaceNewWindow,
|
||||||
SettingsIden::Proxy,
|
SettingsIden::Proxy,
|
||||||
SettingsIden::Theme,
|
|
||||||
SettingsIden::ThemeDark,
|
SettingsIden::ThemeDark,
|
||||||
SettingsIden::ThemeLight,
|
SettingsIden::ThemeLight,
|
||||||
SettingsIden::UpdateChannel,
|
SettingsIden::UpdateChannel,
|
||||||
@@ -193,7 +190,6 @@ impl UpsertModelInfo for Settings {
|
|||||||
interface_scale: row.get("interface_scale")?,
|
interface_scale: row.get("interface_scale")?,
|
||||||
open_workspace_new_window: row.get("open_workspace_new_window")?,
|
open_workspace_new_window: row.get("open_workspace_new_window")?,
|
||||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||||
theme: row.get("theme")?,
|
|
||||||
theme_dark: row.get("theme_dark")?,
|
theme_dark: row.get("theme_dark")?,
|
||||||
theme_light: row.get("theme_light")?,
|
theme_light: row.get("theme_light")?,
|
||||||
update_channel: row.get("update_channel")?,
|
update_channel: row.get("update_channel")?,
|
||||||
@@ -1751,6 +1747,7 @@ impl UpsertModelInfo for SyncState {
|
|||||||
pub struct KeyValue {
|
pub struct KeyValue {
|
||||||
#[ts(type = "\"key_value\"")]
|
#[ts(type = "\"key_value\"")]
|
||||||
pub model: String,
|
pub model: String,
|
||||||
|
pub id: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
|
||||||
@@ -1759,17 +1756,53 @@ pub struct KeyValue {
|
|||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> TryFrom<&Row<'s>> for KeyValue {
|
impl UpsertModelInfo for KeyValue {
|
||||||
type Error = rusqlite::Error;
|
fn table_name() -> impl IntoTableRef {
|
||||||
|
KeyValueIden::Table
|
||||||
|
}
|
||||||
|
|
||||||
fn try_from(r: &Row<'s>) -> std::result::Result<Self, Self::Error> {
|
fn id_column() -> impl IntoIden + Eq + Clone {
|
||||||
|
KeyValueIden::Id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_id() -> String {
|
||||||
|
generate_prefixed_id("kv")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_values(
|
||||||
|
self,
|
||||||
|
source: &UpdateSource,
|
||||||
|
) -> Result<Vec<(impl IntoIden + Eq, impl Into<SimpleExpr>)>> {
|
||||||
|
use KeyValueIden::*;
|
||||||
|
Ok(vec![
|
||||||
|
(CreatedAt, upsert_date(source, self.created_at)),
|
||||||
|
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||||
|
(Namespace, self.namespace.clone().into()),
|
||||||
|
(Key, self.key.clone().into()),
|
||||||
|
(Value, self.value.clone().into()),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_columns() -> Vec<impl IntoIden> {
|
||||||
|
vec![KeyValueIden::UpdatedAt, KeyValueIden::Value]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_row(row: &Row) -> rusqlite::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
model: r.get("model")?,
|
id: row.get("id")?,
|
||||||
created_at: r.get("created_at")?,
|
model: row.get("model")?,
|
||||||
updated_at: r.get("updated_at")?,
|
created_at: row.get("created_at")?,
|
||||||
namespace: r.get("namespace")?,
|
updated_at: row.get("updated_at")?,
|
||||||
key: r.get("key")?,
|
namespace: row.get("namespace")?,
|
||||||
value: r.get("value")?,
|
key: row.get("key")?,
|
||||||
|
value: row.get("value")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1876,18 +1909,23 @@ impl<'de> Deserialize<'de> for AnyModel {
|
|||||||
use serde_json::from_value as fv;
|
use serde_json::from_value as fv;
|
||||||
|
|
||||||
let model = match model.get("model") {
|
let model = match model.get("model") {
|
||||||
Some(m) if m == "http_request" => AnyModel::HttpRequest(fv(value).unwrap()),
|
Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()),
|
||||||
Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()),
|
|
||||||
Some(m) if m == "workspace" => AnyModel::Workspace(fv(value).unwrap()),
|
|
||||||
Some(m) if m == "environment" => AnyModel::Environment(fv(value).unwrap()),
|
Some(m) if m == "environment" => AnyModel::Environment(fv(value).unwrap()),
|
||||||
Some(m) if m == "folder" => AnyModel::Folder(fv(value).unwrap()),
|
Some(m) if m == "folder" => AnyModel::Folder(fv(value).unwrap()),
|
||||||
Some(m) if m == "key_value" => AnyModel::KeyValue(fv(value).unwrap()),
|
|
||||||
Some(m) if m == "grpc_connection" => AnyModel::GrpcConnection(fv(value).unwrap()),
|
Some(m) if m == "grpc_connection" => AnyModel::GrpcConnection(fv(value).unwrap()),
|
||||||
Some(m) if m == "grpc_event" => AnyModel::GrpcEvent(fv(value).unwrap()),
|
Some(m) if m == "grpc_event" => AnyModel::GrpcEvent(fv(value).unwrap()),
|
||||||
Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()),
|
Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "http_request" => AnyModel::HttpRequest(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "key_value" => AnyModel::KeyValue(fv(value).unwrap()),
|
||||||
Some(m) if m == "plugin" => AnyModel::Plugin(fv(value).unwrap()),
|
Some(m) if m == "plugin" => AnyModel::Plugin(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "settings" => AnyModel::Settings(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "websocket_connection" => AnyModel::WebsocketConnection(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "websocket_event" => AnyModel::WebsocketEvent(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "websocket_request" => AnyModel::WebsocketRequest(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "workspace" => AnyModel::Workspace(fv(value).unwrap()),
|
||||||
|
Some(m) if m == "workspace_meta" => AnyModel::WorkspaceMeta(fv(value).unwrap()),
|
||||||
Some(m) => {
|
Some(m) => {
|
||||||
return Err(serde::de::Error::custom(format!("Unknown model {}", m)));
|
return Err(serde::de::Error::custom(format!("Failed to deserialize AnyModel {}", m)));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
return Err(serde::de::Error::custom("Missing or invalid model"));
|
return Err(serde::de::Error::custom("Missing or invalid model"));
|
||||||
@@ -1905,11 +1943,7 @@ impl AnyModel {
|
|||||||
return name.to_string();
|
return name.to_string();
|
||||||
}
|
}
|
||||||
let without_variables = url.replace(r"\$\{\[\s*([^\]\s]+)\s*]}", "$1");
|
let without_variables = url.replace(r"\$\{\[\s*([^\]\s]+)\s*]}", "$1");
|
||||||
if without_variables.is_empty() {
|
if without_variables.is_empty() { fallback.to_string() } else { without_variables }
|
||||||
fallback.to_string()
|
|
||||||
} else {
|
|
||||||
without_variables
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.clone() {
|
match self.clone() {
|
||||||
|
|||||||
@@ -9,7 +9,18 @@ impl<'a> DbContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_cookie_jars(&self, workspace_id: &str) -> Result<Vec<CookieJar>> {
|
pub fn list_cookie_jars(&self, workspace_id: &str) -> Result<Vec<CookieJar>> {
|
||||||
self.find_many(CookieJarIden::WorkspaceId, workspace_id, None)
|
let mut cookie_jars = self.find_many(CookieJarIden::WorkspaceId, workspace_id, None)?;
|
||||||
|
|
||||||
|
if cookie_jars.is_empty() {
|
||||||
|
let jar = CookieJar {
|
||||||
|
name: "Default".to_string(),
|
||||||
|
workspace_id: workspace_id.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
cookie_jars.push(self.upsert_cookie_jar(&jar, &UpdateSource::Background)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cookie_jars)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_cookie_jar(
|
pub fn delete_cookie_jar(
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use crate::error::Result;
|
|
||||||
use crate::models::{Environment, EnvironmentIden, UpsertModelInfo};
|
|
||||||
use crate::util::UpdateSource;
|
|
||||||
use log::info;
|
|
||||||
use sea_query::ColumnRef::Asterisk;
|
|
||||||
use sea_query::{Cond, Expr, Query, SqliteQueryBuilder};
|
|
||||||
use sea_query_rusqlite::RusqliteBinder;
|
|
||||||
use crate::db_context::DbContext;
|
use crate::db_context::DbContext;
|
||||||
|
use crate::error::Error::GenericError;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::models::{Environment, EnvironmentIden};
|
||||||
|
use crate::util::UpdateSource;
|
||||||
|
|
||||||
impl<'a> DbContext<'a> {
|
impl<'a> DbContext<'a> {
|
||||||
pub fn get_environment(&self, id: &str) -> Result<Environment> {
|
pub fn get_environment(&self, id: &str) -> Result<Environment> {
|
||||||
@@ -13,42 +10,38 @@ impl<'a> DbContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_base_environment(&self, workspace_id: &str) -> Result<Environment> {
|
pub fn get_base_environment(&self, workspace_id: &str) -> Result<Environment> {
|
||||||
let (sql, params) = Query::select()
|
// Will create base environment if it doesn't exist
|
||||||
.from(EnvironmentIden::Table)
|
let environments = self.list_environments(workspace_id)?;
|
||||||
.column(Asterisk)
|
|
||||||
.cond_where(
|
let base_environment = environments
|
||||||
Cond::all()
|
.into_iter()
|
||||||
.add(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id))
|
.find(|e| e.environment_id == None && e.workspace_id == workspace_id)
|
||||||
.add(Expr::col(EnvironmentIden::EnvironmentId).is_null()),
|
.ok_or(GenericError(format!("No base environment found for {workspace_id}")))?;
|
||||||
)
|
|
||||||
.build_rusqlite(SqliteQueryBuilder);
|
Ok(base_environment)
|
||||||
let mut stmt = self.conn.prepare(sql.as_str())?;
|
|
||||||
Ok(stmt.query_row(&*params.as_params(), Environment::from_row)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_base_environment(&self, workspace_id: &str) -> Result<()> {
|
pub fn list_environments(&self, workspace_id: &str) -> Result<Vec<Environment>> {
|
||||||
let environments = self.list_environments(workspace_id)?;
|
let mut environments =
|
||||||
|
self.find_many::<Environment>(EnvironmentIden::WorkspaceId, workspace_id, None)?;
|
||||||
|
|
||||||
let base_environment = environments
|
let base_environment = environments
|
||||||
.iter()
|
.iter()
|
||||||
.find(|e| e.environment_id == None && e.workspace_id == workspace_id);
|
.find(|e| e.environment_id == None && e.workspace_id == workspace_id);
|
||||||
|
|
||||||
if let None = base_environment {
|
if let None = base_environment {
|
||||||
info!("Creating base environment for {workspace_id}");
|
environments.push(self.upsert_environment(
|
||||||
self.upsert_environment(
|
|
||||||
&Environment {
|
&Environment {
|
||||||
workspace_id: workspace_id.to_string(),
|
workspace_id: workspace_id.to_string(),
|
||||||
|
environment_id: None,
|
||||||
name: "Global Variables".to_string(),
|
name: "Global Variables".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&UpdateSource::Background,
|
&UpdateSource::Background,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(environments)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_environments(&self, workspace_id: &str) -> Result<Vec<Environment>> {
|
|
||||||
self.find_many(EnvironmentIden::WorkspaceId, workspace_id, None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_environment(
|
pub fn delete_environment(
|
||||||
@@ -69,6 +62,16 @@ impl<'a> DbContext<'a> {
|
|||||||
self.delete_environment(&environment, source)
|
self.delete_environment(&environment, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn duplicate_environment(
|
||||||
|
&self,
|
||||||
|
environment: &Environment,
|
||||||
|
source: &UpdateSource,
|
||||||
|
) -> Result<Environment> {
|
||||||
|
let mut environment = environment.clone();
|
||||||
|
environment.id = "".to_string();
|
||||||
|
self.upsert(&environment, source)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn upsert_environment(
|
pub fn upsert_environment(
|
||||||
&self,
|
&self,
|
||||||
environment: &Environment,
|
environment: &Environment,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::connection_or_tx::ConnectionOrTx;
|
||||||
use crate::db_context::DbContext;
|
use crate::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
@@ -16,20 +17,29 @@ impl<'a> DbContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_folder(&self, folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
pub fn delete_folder(&self, folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
||||||
for folder in self.find_many::<Folder>(FolderIden::FolderId, &folder.id, None)? {
|
match self.conn {
|
||||||
|
ConnectionOrTx::Connection(_) => {}
|
||||||
|
ConnectionOrTx::Transaction(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fid = &folder.id;
|
||||||
|
for m in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, fid, None)? {
|
||||||
|
self.delete_http_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, fid, None)? {
|
||||||
|
self.delete_grpc_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, fid, None)? {
|
||||||
|
self.delete_websocket_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse down into child folders
|
||||||
|
for folder in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
|
||||||
self.delete_folder(&folder, source)?;
|
self.delete_folder(&folder, source)?;
|
||||||
}
|
}
|
||||||
for request in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, &folder.id, None)? {
|
|
||||||
self.delete_http_request(&request, source)?;
|
|
||||||
}
|
|
||||||
for request in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, &folder.id, None)? {
|
|
||||||
self.delete_grpc_request(&request, source)?;
|
|
||||||
}
|
|
||||||
for request in
|
|
||||||
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &folder.id, None)?
|
|
||||||
{
|
|
||||||
self.delete_websocket_request(&request, source)?;
|
|
||||||
}
|
|
||||||
self.delete(folder, source)
|
self.delete(folder, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,22 +53,7 @@ impl<'a> DbContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn duplicate_folder(&self, src_folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
pub fn duplicate_folder(&self, src_folder: &Folder, source: &UpdateSource) -> Result<Folder> {
|
||||||
let workspace_id = src_folder.workspace_id.as_str();
|
let fid = &src_folder.id;
|
||||||
|
|
||||||
let http_requests = self
|
|
||||||
.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, workspace_id, None)?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
|
||||||
|
|
||||||
let grpc_requests = self
|
|
||||||
.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, workspace_id, None)?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
|
||||||
|
|
||||||
let folders = self
|
|
||||||
.find_many::<Folder>(FolderIden::WorkspaceId, workspace_id, None)?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|m| m.folder_id.as_ref() == Some(&src_folder.id));
|
|
||||||
|
|
||||||
let new_folder = self.upsert_folder(
|
let new_folder = self.upsert_folder(
|
||||||
&Folder {
|
&Folder {
|
||||||
@@ -69,29 +64,40 @@ impl<'a> DbContext<'a> {
|
|||||||
source,
|
source,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for m in http_requests {
|
for m in self.find_many::<HttpRequest>(HttpRequestIden::FolderId, fid, None)? {
|
||||||
self.upsert_http_request(
|
self.upsert_http_request(
|
||||||
&HttpRequest {
|
&HttpRequest {
|
||||||
id: "".into(),
|
id: "".into(),
|
||||||
folder_id: Some(new_folder.id.clone()),
|
folder_id: Some(new_folder.id.clone()),
|
||||||
sort_priority: m.sort_priority + 0.001,
|
|
||||||
..m
|
..m
|
||||||
},
|
},
|
||||||
source,
|
source,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for m in grpc_requests {
|
|
||||||
|
for m in self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, fid, None)? {
|
||||||
|
self.upsert_websocket_request(
|
||||||
|
&WebsocketRequest {
|
||||||
|
id: "".into(),
|
||||||
|
folder_id: Some(new_folder.id.clone()),
|
||||||
|
..m
|
||||||
|
},
|
||||||
|
source,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::FolderId, fid, None)? {
|
||||||
self.upsert_grpc_request(
|
self.upsert_grpc_request(
|
||||||
&GrpcRequest {
|
&GrpcRequest {
|
||||||
id: "".into(),
|
id: "".into(),
|
||||||
folder_id: Some(new_folder.id.clone()),
|
folder_id: Some(new_folder.id.clone()),
|
||||||
sort_priority: m.sort_priority + 0.001,
|
|
||||||
..m
|
..m
|
||||||
},
|
},
|
||||||
source,
|
source,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for m in folders {
|
|
||||||
|
for m in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
|
||||||
// Recurse down
|
// Recurse down
|
||||||
self.duplicate_folder(
|
self.duplicate_folder(
|
||||||
&Folder {
|
&Folder {
|
||||||
@@ -101,6 +107,7 @@ impl<'a> DbContext<'a> {
|
|||||||
source,
|
source,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(new_folder)
|
Ok(new_folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
use crate::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{GrpcConnection, GrpcConnectionIden, GrpcConnectionState};
|
use crate::models::{GrpcConnection, GrpcConnectionIden, GrpcConnectionState};
|
||||||
|
use crate::queries::MAX_HISTORY_ITEMS;
|
||||||
use crate::util::UpdateSource;
|
use crate::util::UpdateSource;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||||
use sea_query_rusqlite::RusqliteBinder;
|
use sea_query_rusqlite::RusqliteBinder;
|
||||||
use crate::db_context::DbContext;
|
|
||||||
use crate::queries::MAX_HISTORY_ITEMS;
|
|
||||||
|
|
||||||
impl<'a> DbContext<'a> {
|
impl<'a> DbContext<'a> {
|
||||||
pub fn get_grpc_connection(&self, id: &str) -> Result<GrpcConnection> {
|
pub fn get_grpc_connection(&self, id: &str) -> Result<GrpcConnection> {
|
||||||
@@ -29,7 +29,7 @@ impl<'a> DbContext<'a> {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
source: &UpdateSource,
|
source: &UpdateSource,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for m in self.list_grpc_connections_for_workspace(workspace_id, None)? {
|
for m in self.list_grpc_connections(workspace_id)? {
|
||||||
self.delete(&m, source)?;
|
self.delete(&m, source)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -60,12 +60,8 @@ impl<'a> DbContext<'a> {
|
|||||||
self.find_many(GrpcConnectionIden::RequestId, request_id, limit)
|
self.find_many(GrpcConnectionIden::RequestId, request_id, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_grpc_connections_for_workspace(
|
pub fn list_grpc_connections(&self, workspace_id: &str) -> Result<Vec<GrpcConnection>> {
|
||||||
&self,
|
self.find_many(GrpcConnectionIden::WorkspaceId, workspace_id, None)
|
||||||
workspace_id: &str,
|
|
||||||
limit: Option<u64>,
|
|
||||||
) -> Result<Vec<GrpcConnection>> {
|
|
||||||
self.find_many(GrpcConnectionIden::WorkspaceId, workspace_id, limit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel_pending_grpc_connections(&self) -> Result<()> {
|
pub fn cancel_pending_grpc_connections(&self) -> Result<()> {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ impl<'a> DbContext<'a> {
|
|||||||
self.find_many(HttpResponseIden::RequestId, request_id, limit)
|
self.find_many(HttpResponseIden::RequestId, request_id, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_http_responses_for_workspace(
|
pub fn list_http_responses(
|
||||||
&self,
|
&self,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
limit: Option<u64>,
|
limit: Option<u64>,
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
use crate::db_context::DbContext;
|
use crate::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{KeyValue, KeyValueIden};
|
use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
||||||
use crate::util::{ModelChangeEvent, ModelPayload, UpdateSource};
|
use crate::util::UpdateSource;
|
||||||
use log::error;
|
use log::error;
|
||||||
use sea_query::Keyword::CurrentTimestamp;
|
use sea_query::{Asterisk, Cond, Expr, Query, SqliteQueryBuilder};
|
||||||
use sea_query::{Asterisk, Cond, Expr, OnConflict, Query, SqliteQueryBuilder};
|
|
||||||
use sea_query_rusqlite::RusqliteBinder;
|
use sea_query_rusqlite::RusqliteBinder;
|
||||||
|
|
||||||
impl<'a> DbContext<'a> {
|
impl<'a> DbContext<'a> {
|
||||||
pub fn list_key_values_raw(&self) -> Result<Vec<KeyValue>> {
|
pub fn list_key_values(&self) -> Result<Vec<KeyValue>> {
|
||||||
let (sql, params) = Query::select()
|
let (sql, params) = Query::select()
|
||||||
.from(KeyValueIden::Table)
|
.from(KeyValueIden::Table)
|
||||||
.column(Asterisk)
|
.column(Asterisk)
|
||||||
.build_rusqlite(SqliteQueryBuilder);
|
.build_rusqlite(SqliteQueryBuilder);
|
||||||
let mut stmt = self.conn.prepare(sql.as_str())?;
|
let mut stmt = self.conn.prepare(sql.as_str())?;
|
||||||
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
let items = stmt.query_map(&*params.as_params(), KeyValue::from_row)?;
|
||||||
Ok(items.map(|v| v.unwrap()).collect())
|
Ok(items.map(|v| v.unwrap()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ impl<'a> DbContext<'a> {
|
|||||||
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
||||||
)
|
)
|
||||||
.build_rusqlite(SqliteQueryBuilder);
|
.build_rusqlite(SqliteQueryBuilder);
|
||||||
self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok()
|
self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), KeyValue::from_row).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_key_value_string(
|
pub fn set_key_value_string(
|
||||||
@@ -125,43 +124,7 @@ impl<'a> DbContext<'a> {
|
|||||||
key_value: &KeyValue,
|
key_value: &KeyValue,
|
||||||
source: &UpdateSource,
|
source: &UpdateSource,
|
||||||
) -> Result<KeyValue> {
|
) -> Result<KeyValue> {
|
||||||
let (sql, params) = Query::insert()
|
self.upsert(key_value, source)
|
||||||
.into_table(KeyValueIden::Table)
|
|
||||||
.columns([
|
|
||||||
KeyValueIden::CreatedAt,
|
|
||||||
KeyValueIden::UpdatedAt,
|
|
||||||
KeyValueIden::Namespace,
|
|
||||||
KeyValueIden::Key,
|
|
||||||
KeyValueIden::Value,
|
|
||||||
])
|
|
||||||
.values_panic([
|
|
||||||
CurrentTimestamp.into(),
|
|
||||||
CurrentTimestamp.into(),
|
|
||||||
key_value.namespace.clone().into(),
|
|
||||||
key_value.key.clone().into(),
|
|
||||||
key_value.value.clone().into(),
|
|
||||||
])
|
|
||||||
.on_conflict(
|
|
||||||
OnConflict::new()
|
|
||||||
.update_columns([KeyValueIden::UpdatedAt, KeyValueIden::Value])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.returning_all()
|
|
||||||
.build_rusqlite(SqliteQueryBuilder);
|
|
||||||
|
|
||||||
let mut stmt = self.conn.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert");
|
|
||||||
let m: KeyValue = stmt
|
|
||||||
.query_row(&*params.as_params(), |row| row.try_into())
|
|
||||||
.expect("Failed to upsert KeyValue");
|
|
||||||
|
|
||||||
let payload = ModelPayload {
|
|
||||||
model: m.clone().into(),
|
|
||||||
update_source: source.clone(),
|
|
||||||
change: ModelChangeEvent::Upsert,
|
|
||||||
};
|
|
||||||
self.tx.try_send(payload).unwrap();
|
|
||||||
|
|
||||||
Ok(m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_key_value(
|
pub fn delete_key_value(
|
||||||
@@ -175,21 +138,7 @@ impl<'a> DbContext<'a> {
|
|||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (sql, params) = Query::delete()
|
self.delete(&kv, source)?;
|
||||||
.from_table(KeyValueIden::Table)
|
|
||||||
.cond_where(
|
|
||||||
Cond::all()
|
|
||||||
.add(Expr::col(KeyValueIden::Namespace).eq(namespace))
|
|
||||||
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
|
||||||
)
|
|
||||||
.build_rusqlite(SqliteQueryBuilder);
|
|
||||||
self.conn.execute(sql.as_str(), &*params.as_params())?;
|
|
||||||
let payload = ModelPayload {
|
|
||||||
model: kv.clone().into(),
|
|
||||||
update_source: source.clone(),
|
|
||||||
change: ModelChangeEvent::Delete,
|
|
||||||
};
|
|
||||||
self.tx.try_send(payload).unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ mod websocket_requests;
|
|||||||
mod workspace_metas;
|
mod workspace_metas;
|
||||||
mod workspaces;
|
mod workspaces;
|
||||||
|
|
||||||
const MAX_HISTORY_ITEMS: usize = 20;
|
const MAX_HISTORY_ITEMS: usize = 20;
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
use crate::db_context::DbContext;
|
use crate::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{Settings, SettingsIden};
|
use crate::models::{EditorKeymap, Settings, SettingsIden};
|
||||||
use crate::util::UpdateSource;
|
use crate::util::UpdateSource;
|
||||||
|
|
||||||
impl<'a> DbContext<'a> {
|
impl<'a> DbContext<'a> {
|
||||||
pub fn get_or_create_settings(&self, source: &UpdateSource) -> Settings {
|
pub fn get_settings(&self) -> Settings {
|
||||||
let id = "default".to_string();
|
let id = "default".to_string();
|
||||||
|
|
||||||
if let Some(s) = self.find_optional::<Settings>(SettingsIden::Id, &id) {
|
if let Some(s) = self.find_optional::<Settings>(SettingsIden::Id, &id) {
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.upsert(
|
let settings = Settings {
|
||||||
&Settings {
|
model: "settings".to_string(),
|
||||||
id,
|
id,
|
||||||
..Default::default()
|
created_at: Default::default(),
|
||||||
},
|
updated_at: Default::default(),
|
||||||
source,
|
|
||||||
)
|
appearance: "system".to_string(),
|
||||||
.expect("Failed to upsert settings")
|
editor_font_size: 13,
|
||||||
|
editor_keymap: EditorKeymap::Default,
|
||||||
|
editor_soft_wrap: true,
|
||||||
|
interface_font_size: 15,
|
||||||
|
interface_scale: 1.0,
|
||||||
|
open_workspace_new_window: None,
|
||||||
|
proxy: None,
|
||||||
|
theme_dark: "yaak-dark".to_string(),
|
||||||
|
theme_light: "yaak-light".to_string(),
|
||||||
|
update_channel: "stable".to_string(),
|
||||||
|
};
|
||||||
|
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upsert_settings(&self, settings: &Settings, source: &UpdateSource) -> Result<Settings> {
|
pub fn upsert_settings(&self, settings: &Settings, source: &UpdateSource) -> Result<Settings> {
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ impl<'a> DbContext<'a> {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
source: &UpdateSource,
|
source: &UpdateSource,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let responses = self.list_websocket_connections_for_workspace(workspace_id)?;
|
let responses = self.list_websocket_connections(workspace_id)?;
|
||||||
for m in responses {
|
for m in responses {
|
||||||
self.delete(&m, source)?;
|
self.delete(&m, source)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_websocket_connections_for_workspace(
|
pub fn list_websocket_connections(
|
||||||
&self,
|
&self,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
) -> Result<Vec<WebsocketConnection>> {
|
) -> Result<Vec<WebsocketConnection>> {
|
||||||
|
|||||||
@@ -1,25 +1,40 @@
|
|||||||
use crate::db_context::DbContext;
|
use crate::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{Workspace, WorkspaceMeta, WorkspaceMetaIden};
|
use crate::models::{WorkspaceMeta, WorkspaceMetaIden};
|
||||||
use crate::util::UpdateSource;
|
use crate::util::UpdateSource;
|
||||||
|
|
||||||
impl<'a> DbContext<'a> {
|
impl<'a> DbContext<'a> {
|
||||||
pub fn get_workspace_meta(&self, workspace: &Workspace) -> Option<WorkspaceMeta> {
|
pub fn get_workspace_meta(&self, workspace_id: &str) -> Option<WorkspaceMeta> {
|
||||||
self.find_optional(WorkspaceMetaIden::WorkspaceId, &workspace.id)
|
self.find_optional(WorkspaceMetaIden::WorkspaceId, workspace_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_workspace_metas(&self, workspace_id: &str) -> Result<Vec<WorkspaceMeta>> {
|
||||||
|
let mut workspace_metas =
|
||||||
|
self.find_many(WorkspaceMetaIden::WorkspaceId, workspace_id, None)?;
|
||||||
|
|
||||||
|
if workspace_metas.is_empty() {
|
||||||
|
let wm = WorkspaceMeta {
|
||||||
|
workspace_id: workspace_id.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
workspace_metas.push(self.upsert_workspace_meta(&wm, &UpdateSource::Background)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(workspace_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_create_workspace_meta(
|
pub fn get_or_create_workspace_meta(
|
||||||
&self,
|
&self,
|
||||||
workspace: &Workspace,
|
workspace_id: &str,
|
||||||
source: &UpdateSource,
|
source: &UpdateSource,
|
||||||
) -> Result<WorkspaceMeta> {
|
) -> Result<WorkspaceMeta> {
|
||||||
let workspace_meta = self.get_workspace_meta(workspace);
|
let workspace_meta = self.get_workspace_meta(workspace_id);
|
||||||
if let Some(workspace_meta) = workspace_meta {
|
if let Some(workspace_meta) = workspace_meta {
|
||||||
return Ok(workspace_meta);
|
return Ok(workspace_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace_meta = WorkspaceMeta {
|
let workspace_meta = WorkspaceMeta {
|
||||||
workspace_id: workspace.to_owned().id,
|
workspace_id: workspace_id.to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,21 @@ impl<'a> DbContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_workspaces(&self) -> Result<Vec<Workspace>> {
|
pub fn list_workspaces(&self) -> Result<Vec<Workspace>> {
|
||||||
self.find_all()
|
let mut workspaces = self.find_all()?;
|
||||||
|
|
||||||
|
if workspaces.is_empty() {
|
||||||
|
workspaces.push(self.upsert_workspace(
|
||||||
|
&Workspace {
|
||||||
|
name: "Yaak".to_string(),
|
||||||
|
setting_follow_redirects: true,
|
||||||
|
setting_validate_certificates: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&UpdateSource::Background,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(workspaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_workspace(
|
pub fn delete_workspace(
|
||||||
@@ -20,24 +34,24 @@ impl<'a> DbContext<'a> {
|
|||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
source: &UpdateSource,
|
source: &UpdateSource,
|
||||||
) -> Result<Workspace> {
|
) -> Result<Workspace> {
|
||||||
|
for m in self.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, &workspace.id, None)? {
|
||||||
|
self.delete_http_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in self.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, &workspace.id, None)? {
|
||||||
|
self.delete_grpc_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in
|
||||||
|
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &workspace.id, None)?
|
||||||
|
{
|
||||||
|
self.delete_websocket_request(&m, source)?;
|
||||||
|
}
|
||||||
|
|
||||||
for folder in self.find_many::<Folder>(FolderIden::WorkspaceId, &workspace.id, None)? {
|
for folder in self.find_many::<Folder>(FolderIden::WorkspaceId, &workspace.id, None)? {
|
||||||
self.delete_folder(&folder, source)?;
|
self.delete_folder(&folder, source)?;
|
||||||
}
|
}
|
||||||
for request in
|
|
||||||
self.find_many::<HttpRequest>(HttpRequestIden::WorkspaceId, &workspace.id, None)?
|
|
||||||
{
|
|
||||||
self.delete_http_request(&request, source)?;
|
|
||||||
}
|
|
||||||
for request in
|
|
||||||
self.find_many::<GrpcRequest>(GrpcRequestIden::WorkspaceId, &workspace.id, None)?
|
|
||||||
{
|
|
||||||
self.delete_grpc_request(&request, source)?;
|
|
||||||
}
|
|
||||||
for request in
|
|
||||||
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, &workspace.id, None)?
|
|
||||||
{
|
|
||||||
self.delete_websocket_request(&request, source)?;
|
|
||||||
}
|
|
||||||
self.delete(workspace, source)
|
self.delete(workspace, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub trait QueryManagerExt<'a, R> {
|
|||||||
impl<'a, R: Runtime, M: Manager<R>> QueryManagerExt<'a, R> for M {
|
impl<'a, R: Runtime, M: Manager<R>> QueryManagerExt<'a, R> for M {
|
||||||
fn db(&'a self) -> DbContext<'a> {
|
fn db(&'a self) -> DbContext<'a> {
|
||||||
let qm = self.state::<QueryManager>();
|
let qm = self.state::<QueryManager>();
|
||||||
qm.inner().connect_2()
|
qm.inner().connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_db<F, T>(&'a self, func: F) -> T
|
fn with_db<F, T>(&'a self, func: F) -> T
|
||||||
@@ -59,7 +59,7 @@ impl QueryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect_2(&self) -> DbContext {
|
pub fn connect(&self) -> DbContext {
|
||||||
let conn = self
|
let conn = self
|
||||||
.pool
|
.pool
|
||||||
.lock()
|
.lock()
|
||||||
@@ -67,7 +67,7 @@ impl QueryManager {
|
|||||||
.get()
|
.get()
|
||||||
.expect("Failed to get a new DB connection from the pool");
|
.expect("Failed to get a new DB connection from the pool");
|
||||||
DbContext {
|
DbContext {
|
||||||
tx: self.events_tx.clone(),
|
events_tx: self.events_tx.clone(),
|
||||||
conn: ConnectionOrTx::Connection(conn),
|
conn: ConnectionOrTx::Connection(conn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ impl QueryManager {
|
|||||||
.expect("Failed to get new DB connection from the pool");
|
.expect("Failed to get new DB connection from the pool");
|
||||||
|
|
||||||
let db_context = DbContext {
|
let db_context = DbContext {
|
||||||
tx: self.events_tx.clone(),
|
events_tx: self.events_tx.clone(),
|
||||||
conn: ConnectionOrTx::Connection(conn),
|
conn: ConnectionOrTx::Connection(conn),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ impl QueryManager {
|
|||||||
.expect("Failed to start DB transaction");
|
.expect("Failed to start DB transaction");
|
||||||
|
|
||||||
let db_context = DbContext {
|
let db_context = DbContext {
|
||||||
tx: self.events_tx.clone(),
|
events_tx: self.events_tx.clone(),
|
||||||
conn: ConnectionOrTx::Transaction(&tx),
|
conn: ConnectionOrTx::Transaction(&tx),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ pub enum ModelChangeEvent {
|
|||||||
#[serde(rename_all = "snake_case", tag = "type")]
|
#[serde(rename_all = "snake_case", tag = "type")]
|
||||||
#[ts(export, export_to = "gen_models.ts")]
|
#[ts(export, export_to = "gen_models.ts")]
|
||||||
pub enum UpdateSource {
|
pub enum UpdateSource {
|
||||||
Sync,
|
|
||||||
Window { label: String },
|
|
||||||
Plugin,
|
|
||||||
Background,
|
Background,
|
||||||
Import,
|
Import,
|
||||||
|
Plugin,
|
||||||
|
Sync,
|
||||||
|
Window { label: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateSource {
|
impl UpdateSource {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use tokio::io::AsyncWriteExt;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use yaak_models::models::{SyncState, WorkspaceMeta};
|
use yaak_models::models::{SyncState, WorkspaceMeta};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::{get_workspace_export_resources, UpdateSource};
|
use yaak_models::util::{UpdateSource, get_workspace_export_resources};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
#[serde(rename_all = "camelCase", tag = "type")]
|
#[serde(rename_all = "camelCase", tag = "type")]
|
||||||
@@ -454,7 +454,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
|
|||||||
let sync_dir_string = sync_dir.to_string_lossy().to_string();
|
let sync_dir_string = sync_dir.to_string_lossy().to_string();
|
||||||
let db = app_handle.db();
|
let db = app_handle.db();
|
||||||
for workspace in upserted_models.workspaces {
|
for workspace in upserted_models.workspaces {
|
||||||
let r = match db.get_workspace_meta(&workspace) {
|
let r = match db.get_workspace_meta(&workspace.id) {
|
||||||
Some(m) => {
|
Some(m) => {
|
||||||
if m.setting_sync_dir == Some(sync_dir_string.clone()) {
|
if m.setting_sync_dir == Some(sync_dir_string.clone()) {
|
||||||
// We don't need to update if unchanged
|
// We don't need to update if unchanged
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ pub(crate) async fn list_connections<R: Runtime>(
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
) -> Result<Vec<WebsocketConnection>> {
|
) -> Result<Vec<WebsocketConnection>> {
|
||||||
Ok(app_handle.db().list_websocket_connections_for_workspace(workspace_id)?)
|
Ok(app_handle.db().list_websocket_connections(workspace_id)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import type { Folder } from '@yaakapp-internal/models';
|
import type { Folder } from '@yaakapp-internal/models';
|
||||||
|
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||||
import { Banner } from '../components/core/Banner';
|
import { Banner } from '../components/core/Banner';
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
import { InlineCode } from '../components/core/InlineCode';
|
||||||
import { VStack } from '../components/core/Stacks';
|
import { VStack } from '../components/core/Stacks';
|
||||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
import { createFastMutation } from '../hooks/useFastMutation';
|
||||||
import { showConfirm } from '../lib/confirm';
|
import { showConfirm } from '../lib/confirm';
|
||||||
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
import { jotaiStore } from '../lib/jotai';
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { showPrompt } from '../lib/prompt';
|
import { showPrompt } from '../lib/prompt';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
||||||
|
|
||||||
export const createFolder = createFastMutation<
|
export const createFolder = createFastMutation<
|
||||||
Folder | null,
|
void,
|
||||||
void,
|
void,
|
||||||
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
|
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
|
||||||
>({
|
>({
|
||||||
mutationKey: ['create_folder'],
|
mutationKey: ['create_folder'],
|
||||||
mutationFn: async (patch) => {
|
mutationFn: async (patch) => {
|
||||||
const workspaceId = getActiveWorkspaceId();
|
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
if (workspaceId == null) {
|
if (workspaceId == null) {
|
||||||
throw new Error("Cannot create folder when there's no active workspace");
|
throw new Error("Cannot create folder when there's no active workspace");
|
||||||
}
|
}
|
||||||
@@ -33,13 +34,13 @@ export const createFolder = createFastMutation<
|
|||||||
confirmText: 'Create',
|
confirmText: 'Create',
|
||||||
placeholder: 'Name',
|
placeholder: 'Name',
|
||||||
});
|
});
|
||||||
if (name == null) return null;
|
if (name == null) throw new Error('No name provided to create folder');
|
||||||
|
|
||||||
patch.name = name;
|
patch.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch.sortPriority = patch.sortPriority || -Date.now();
|
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||||
return invokeCmd<Folder>('cmd_update_folder', { folder: { workspaceId, ...patch } });
|
await createWorkspaceModel({ model: 'folder', workspaceId, ...patch });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { WebsocketConnection } from '@yaakapp-internal/models';
|
|
||||||
import { deleteWebsocketConnection as cmdDeleteWebsocketConnection } from '@yaakapp-internal/ws';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
|
|
||||||
export const deleteWebsocketConnection = createFastMutation({
|
|
||||||
mutationKey: ['delete_websocket_connection'],
|
|
||||||
mutationFn: async function (connection: WebsocketConnection) {
|
|
||||||
return cmdDeleteWebsocketConnection(connection.id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
|
||||||
import { deleteWebsocketRequest as cmdDeleteWebsocketRequest } from '@yaakapp-internal/ws';
|
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
import { showConfirmDelete } from '../lib/confirm';
|
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
|
||||||
|
|
||||||
export const deleteWebsocketRequest = createFastMutation({
|
|
||||||
mutationKey: ['delete_websocket_request'],
|
|
||||||
mutationFn: async (request: WebsocketRequest) => {
|
|
||||||
const confirmed = await showConfirmDelete({
|
|
||||||
id: 'delete-websocket-request',
|
|
||||||
title: 'Delete WebSocket Request',
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Permanently delete <InlineCode>{resolvedModelName(request)}</InlineCode>?
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmdDeleteWebsocketRequest(request.id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { duplicateWebsocketRequest as cmdDuplicateWebsocketRequest } from '@yaakapp-internal/ws';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
import { router } from '../lib/router';
|
|
||||||
|
|
||||||
export const duplicateWebsocketRequest = createFastMutation({
|
|
||||||
mutationKey: ['delete_websocket_connection'],
|
|
||||||
mutationFn: async function (requestId: string) {
|
|
||||||
return cmdDuplicateWebsocketRequest(requestId);
|
|
||||||
},
|
|
||||||
onSuccess: async (request) => {
|
|
||||||
await router.navigate({
|
|
||||||
to: '/workspaces/$workspaceId',
|
|
||||||
params: { workspaceId: request.workspaceId },
|
|
||||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { SettingsTab } from '../components/Settings/SettingsTab';
|
import { SettingsTab } from '../components/Settings/SettingsTab';
|
||||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
import { createFastMutation } from '../hooks/useFastMutation';
|
||||||
|
import { jotaiStore } from '../lib/jotai';
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
|
|
||||||
export const openSettings = createFastMutation<void, string, SettingsTab | null>({
|
export const openSettings = createFastMutation<void, string, SettingsTab | null>({
|
||||||
mutationKey: ['open_settings'],
|
mutationKey: ['open_settings'],
|
||||||
mutationFn: async function (tab) {
|
mutationFn: async function (tab) {
|
||||||
const workspaceId = getActiveWorkspaceId();
|
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
if (workspaceId == null) return;
|
if (workspaceId == null) return;
|
||||||
|
|
||||||
const location = router.buildLocation({
|
const location = router.buildLocation({
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { WorkspaceSettingsDialog } from '../components/WorkspaceSettingsDialog';
|
import { WorkspaceSettingsDialog } from '../components/WorkspaceSettingsDialog';
|
||||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
import { createFastMutation } from '../hooks/useFastMutation';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
|
import { jotaiStore } from '../lib/jotai';
|
||||||
|
|
||||||
export const openWorkspaceSettings = createFastMutation<void, string, { openSyncMenu?: boolean }>({
|
export const openWorkspaceSettings = createFastMutation<void, string, { openSyncMenu?: boolean }>({
|
||||||
mutationKey: ['open_workspace_settings'],
|
mutationKey: ['open_workspace_settings'],
|
||||||
async mutationFn({ openSyncMenu }) {
|
async mutationFn({ openSyncMenu }) {
|
||||||
const workspaceId = getActiveWorkspaceId();
|
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
showDialog({
|
showDialog({
|
||||||
id: 'workspace-settings',
|
id: 'workspace-settings',
|
||||||
title: 'Workspace Settings',
|
title: 'Workspace Settings',
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
|
||||||
import { upsertWebsocketRequest as cmdUpsertWebsocketRequest } from '@yaakapp-internal/ws';
|
|
||||||
import { differenceInMilliseconds } from 'date-fns';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
import { router } from '../lib/router';
|
|
||||||
|
|
||||||
export const upsertWebsocketRequest = createFastMutation<
|
|
||||||
WebsocketRequest,
|
|
||||||
void,
|
|
||||||
Parameters<typeof cmdUpsertWebsocketRequest>[0]
|
|
||||||
>({
|
|
||||||
mutationKey: ['upsert_websocket_request'],
|
|
||||||
mutationFn: (request) => cmdUpsertWebsocketRequest(request),
|
|
||||||
onSuccess: async (request) => {
|
|
||||||
const isNew = differenceInMilliseconds(new Date(), request.createdAt + 'Z') < 100;
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
await router.navigate({
|
|
||||||
to: '/workspaces/$workspaceId',
|
|
||||||
params: { workspaceId: request.workspaceId },
|
|
||||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import type { Workspace } from '@yaakapp-internal/models';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
|
|
||||||
export const upsertWorkspace = createFastMutation<
|
|
||||||
Workspace,
|
|
||||||
void,
|
|
||||||
Workspace | Partial<Omit<Workspace, 'id'>>
|
|
||||||
>({
|
|
||||||
mutationKey: ['upsert_workspace'],
|
|
||||||
mutationFn: (workspace) => invokeCmd<Workspace>('cmd_update_workspace', { workspace }),
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
|
||||||
import { workspaceMetaAtom } from '../hooks/useWorkspaceMeta';
|
|
||||||
import { jotaiStore } from '../lib/jotai';
|
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
|
|
||||||
export const upsertWorkspaceMeta = createFastMutation<
|
|
||||||
WorkspaceMeta,
|
|
||||||
unknown,
|
|
||||||
WorkspaceMeta | (Partial<Omit<WorkspaceMeta, 'id'>> & { workspaceId: string })
|
|
||||||
>({
|
|
||||||
mutationKey: ['update_workspace_meta'],
|
|
||||||
mutationFn: async (patch) => {
|
|
||||||
const workspaceMeta = jotaiStore.get(workspaceMetaAtom);
|
|
||||||
return invokeCmd<WorkspaceMeta>('cmd_update_workspace_meta', {
|
|
||||||
workspaceMeta: { ...workspaceMeta, ...patch },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { fuzzyFilter } from 'fuzzbunny';
|
import { fuzzyFilter } from 'fuzzbunny';
|
||||||
|
import { useAtomValue } from 'jotai/index';
|
||||||
import type { KeyboardEvent, ReactNode } from 'react';
|
import type { KeyboardEvent, ReactNode } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { createFolder } from '../commands/commands';
|
import { createFolder } from '../commands/commands';
|
||||||
@@ -8,26 +10,25 @@ import { switchWorkspace } from '../commands/switchWorkspace';
|
|||||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
|
import { useAllRequests } from '../hooks/useAllRequests';
|
||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
||||||
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||||
import { useDebouncedState } from '../hooks/useDebouncedState';
|
import { useDebouncedState } from '../hooks/useDebouncedState';
|
||||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
|
||||||
import type { HotkeyAction } from '../hooks/useHotKey';
|
import type { HotkeyAction } from '../hooks/useHotKey';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||||
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 { useRenameRequest } from '../hooks/useRenameRequest';
|
|
||||||
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 { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { showDialog, toggleDialog } from '../lib/dialog';
|
import { showDialog, toggleDialog } from '../lib/dialog';
|
||||||
|
import { renameModelWithPrompt } from '../lib/renameModelWithPrompt';
|
||||||
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||||
@@ -60,23 +61,20 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
|||||||
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
const httpRequestActions = useHttpRequestActions();
|
const httpRequestActions = useHttpRequestActions();
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const { subEnvironments } = useEnvironments();
|
const { baseEnvironment, subEnvironments } = useEnvironmentsBreakdown();
|
||||||
const createWorkspace = useCreateWorkspace();
|
const createWorkspace = useCreateWorkspace();
|
||||||
const recentEnvironments = useRecentEnvironments();
|
const recentEnvironments = useRecentEnvironments();
|
||||||
const recentWorkspaces = useRecentWorkspaces();
|
const recentWorkspaces = useRecentWorkspaces();
|
||||||
const requests = useRequests();
|
const requests = useAllRequests();
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const activeCookieJar = useActiveCookieJar();
|
const activeCookieJar = useActiveCookieJar();
|
||||||
const [recentRequests] = useRecentRequests();
|
const [recentRequests] = useRecentRequests();
|
||||||
const [, setSidebarHidden] = useSidebarHidden();
|
const [, setSidebarHidden] = useSidebarHidden();
|
||||||
const { baseEnvironment } = useEnvironments();
|
|
||||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||||
const { mutate: createEnvironment } = useCreateEnvironment();
|
const { mutate: createEnvironment } = useCreateEnvironment();
|
||||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||||
const { mutate: renameRequest } = useRenameRequest(activeRequest?.id ?? null);
|
|
||||||
const { mutate: deleteRequest } = useDeleteAnyRequest();
|
|
||||||
|
|
||||||
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
|
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
|
||||||
const commands: CommandPaletteItem[] = [
|
const commands: CommandPaletteItem[] = [
|
||||||
@@ -166,13 +164,13 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
|||||||
commands.push({
|
commands.push({
|
||||||
key: 'http_request.rename',
|
key: 'http_request.rename',
|
||||||
label: 'Rename Request',
|
label: 'Rename Request',
|
||||||
onSelect: renameRequest,
|
onSelect: () => renameModelWithPrompt(activeRequest),
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.push({
|
commands.push({
|
||||||
key: 'http_request.delete',
|
key: 'sidebar.delete_selected_item',
|
||||||
label: 'Delete Request',
|
label: 'Delete Request',
|
||||||
onSelect: () => deleteRequest(activeRequest.id),
|
onSelect: () => deleteModelWithConfirm(activeRequest),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +188,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
|||||||
createGrpcRequest,
|
createGrpcRequest,
|
||||||
createHttpRequest,
|
createHttpRequest,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
deleteRequest,
|
|
||||||
httpRequestActions,
|
httpRequestActions,
|
||||||
renameRequest,
|
|
||||||
sendRequest,
|
sendRequest,
|
||||||
setSidebarHidden,
|
setSidebarHidden,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Cookie } from '@yaakapp-internal/models';
|
import type { Cookie} from '@yaakapp-internal/models';
|
||||||
import { useCookieJars } from '../hooks/useCookieJars';
|
import { cookieJarsAtom, patchModel } from '@yaakapp-internal/models';
|
||||||
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
|
import { useAtomValue } from 'jotai';
|
||||||
import { cookieDomain } from '../lib/model_util';
|
import { cookieDomain } from '../lib/model_util';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -11,8 +11,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CookieDialog = function ({ cookieJarId }: Props) {
|
export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||||
const updateCookieJar = useUpdateCookieJar(cookieJarId ?? null);
|
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||||
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) {
|
||||||
@@ -39,7 +38,7 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-surface-highlight">
|
<tbody className="divide-y divide-surface-highlight">
|
||||||
{cookieJar?.cookies.map((c: Cookie) => (
|
{cookieJar?.cookies.map((c: Cookie) => (
|
||||||
<tr key={c.domain + c.raw_cookie}>
|
<tr key={c.domain + c.raw_cookie + c.path + c.expires}>
|
||||||
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
||||||
{cookieDomain(c)}
|
{cookieDomain(c)}
|
||||||
</td>
|
</td>
|
||||||
@@ -53,12 +52,11 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
|||||||
iconSize="sm"
|
iconSize="sm"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onClick={async () => {
|
onClick={() =>
|
||||||
await updateCookieJar.mutateAsync({
|
patchModel(cookieJar, {
|
||||||
...cookieJar,
|
|
||||||
cookies: cookieJar.cookies.filter((c2: Cookie) => c2 !== c),
|
cookies: cookieJar.cookies.filter((c2: Cookie) => c2 !== c),
|
||||||
});
|
})
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { cookieJarsAtom, patchModel } from '@yaakapp-internal/models';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||||
import { cookieJarsAtom } from '../hooks/useCookieJars';
|
|
||||||
import { useCreateCookieJar } from '../hooks/useCreateCookieJar';
|
import { useCreateCookieJar } from '../hooks/useCreateCookieJar';
|
||||||
import { useDeleteCookieJar } from '../hooks/useDeleteCookieJar';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
|
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { showPrompt } from '../lib/prompt';
|
import { showPrompt } from '../lib/prompt';
|
||||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||||
@@ -16,8 +15,6 @@ import { InlineCode } from './core/InlineCode';
|
|||||||
|
|
||||||
export const CookieDropdown = memo(function CookieDropdown() {
|
export const CookieDropdown = memo(function CookieDropdown() {
|
||||||
const activeCookieJar = useActiveCookieJar();
|
const activeCookieJar = useActiveCookieJar();
|
||||||
const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null);
|
|
||||||
const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null);
|
|
||||||
const createCookieJar = useCreateCookieJar();
|
const createCookieJar = useCreateCookieJar();
|
||||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
|||||||
defaultValue: activeCookieJar?.name,
|
defaultValue: activeCookieJar?.name,
|
||||||
});
|
});
|
||||||
if (name == null) return;
|
if (name == null) return;
|
||||||
updateCookieJar.mutate({ name });
|
await patchModel(activeCookieJar, { name });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...(((cookieJars ?? []).length > 1 // Never delete the last one
|
...(((cookieJars ?? []).length > 1 // Never delete the last one
|
||||||
@@ -76,7 +73,9 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
|||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
onSelect: deleteCookieJar.mutate,
|
onSelect: async () => {
|
||||||
|
await deleteModelWithConfirm(activeCookieJar);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []) as DropdownItem[]),
|
: []) as DropdownItem[]),
|
||||||
@@ -90,7 +89,7 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
|||||||
onSelect: () => createCookieJar.mutate(),
|
onSelect: () => createCookieJar.mutate(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [activeCookieJar, cookieJars, createCookieJar, deleteCookieJar, updateCookieJar]);
|
}, [activeCookieJar, cookieJars, createCookieJar]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown items={items}>
|
<Dropdown items={items}>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useGitInit } from '@yaakapp-internal/git';
|
import { useGitInit } from '@yaakapp-internal/git';
|
||||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||||
|
import { createGlobalModel, patchModel } from '@yaakapp-internal/models';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
|
||||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { showErrorToast } from '../lib/toast';
|
import { showErrorToast } from '../lib/toast';
|
||||||
@@ -31,16 +30,15 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
|||||||
className="pb-3 max-h-[50vh]"
|
className="pb-3 max-h-[50vh]"
|
||||||
onSubmit={async (e) => {
|
onSubmit={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const workspace = await upsertWorkspace.mutateAsync({ name });
|
const workspaceId = await createGlobalModel({ model: 'workspace', name });
|
||||||
if (workspace == null) return;
|
if (workspaceId == null) return;
|
||||||
|
|
||||||
// Do getWorkspaceMeta instead of naively creating one because it might have
|
// Do getWorkspaceMeta instead of naively creating one because it might have
|
||||||
// been created already when the store refreshes the workspace meta after
|
// been created already when the store refreshes the workspace meta after
|
||||||
const workspaceMeta = await invokeCmd<WorkspaceMeta>('cmd_get_workspace_meta', {
|
const workspaceMeta = await invokeCmd<WorkspaceMeta>('cmd_get_workspace_meta', {
|
||||||
workspaceId: workspace.id,
|
workspaceId,
|
||||||
});
|
});
|
||||||
await upsertWorkspaceMeta.mutateAsync({
|
await patchModel(workspaceMeta, {
|
||||||
...workspaceMeta,
|
|
||||||
settingSyncDir: syncConfig.filePath,
|
settingSyncDir: syncConfig.filePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
|||||||
// Navigate to workspace
|
// Navigate to workspace
|
||||||
await router.navigate({
|
await router.navigate({
|
||||||
to: '/workspaces/$workspaceId',
|
to: '/workspaces/$workspaceId',
|
||||||
params: { workspaceId: workspace.id },
|
params: { workspaceId },
|
||||||
});
|
});
|
||||||
|
|
||||||
hide();
|
hide();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
|
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
|
||||||
|
import { foldersAtom, httpRequestsAtom } from '@yaakapp-internal/models';
|
||||||
import type {
|
import type {
|
||||||
FormInput,
|
FormInput,
|
||||||
FormInputCheckbox,
|
FormInputCheckbox,
|
||||||
@@ -10,10 +11,9 @@ import type {
|
|||||||
JsonPrimitive,
|
JsonPrimitive,
|
||||||
} from '@yaakapp-internal/plugins';
|
} from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai/index';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useFolders } from '../hooks/useFolders';
|
|
||||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
|
||||||
import { capitalize } from '../lib/capitalize';
|
import { capitalize } from '../lib/capitalize';
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
@@ -354,8 +354,8 @@ function HttpRequestArg({
|
|||||||
value: string;
|
value: string;
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const folders = useFolders();
|
const folders = useAtomValue(foldersAtom);
|
||||||
const httpRequests = useHttpRequests();
|
const httpRequests = useAtomValue(httpRequestsAtom);
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||||
import { toggleDialog } from '../lib/dialog';
|
import { toggleDialog } from '../lib/dialog';
|
||||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
@@ -19,7 +19,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
className,
|
className,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { subEnvironments, baseEnvironment } = useEnvironments();
|
const { subEnvironments, baseEnvironment } = useEnvironmentsBreakdown();
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
|
|
||||||
const showEnvironmentDialog = useCallback(() => {
|
const showEnvironmentDialog = useCallback(() => {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { Environment } from '@yaakapp-internal/models';
|
import type { Environment } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { showPrompt } from '../lib/prompt';
|
import { showPrompt } from '../lib/prompt';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -29,8 +29,7 @@ interface Props {
|
|||||||
|
|
||||||
export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironments();
|
const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironmentsBreakdown();
|
||||||
|
|
||||||
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
|
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
|
||||||
initialEnvironment?.id ?? null,
|
initialEnvironment?.id ?? null,
|
||||||
);
|
);
|
||||||
@@ -42,9 +41,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
|
|
||||||
const handleCreateEnvironment = async () => {
|
const handleCreateEnvironment = async () => {
|
||||||
if (baseEnvironment == null) return;
|
if (baseEnvironment == null) return;
|
||||||
const e = await createEnvironment.mutateAsync(baseEnvironment);
|
const id = await createEnvironment.mutateAsync(baseEnvironment);
|
||||||
if (e == null) return;
|
setSelectedEnvironmentId(id);
|
||||||
setSelectedEnvironmentId(e.id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -127,11 +125,10 @@ const EnvironmentEditor = function ({
|
|||||||
key: 'environmentValueVisibility',
|
key: 'environmentValueVisibility',
|
||||||
fallback: true,
|
fallback: true,
|
||||||
});
|
});
|
||||||
const { allEnvironments } = useEnvironments();
|
const { allEnvironments } = useEnvironmentsBreakdown();
|
||||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
|
||||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||||
(variables) => updateEnvironment.mutate({ variables }),
|
(variables) => patchModel(activeEnvironment, { variables }),
|
||||||
[updateEnvironment],
|
[activeEnvironment],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Gather a list of env names from other environments, to help the user get them aligned
|
// Gather a list of env names from other environments, to help the user get them aligned
|
||||||
@@ -217,8 +214,6 @@ function SidebarButton({
|
|||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
environment: Environment | null;
|
environment: Environment | null;
|
||||||
}) {
|
}) {
|
||||||
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
|
||||||
const deleteEnvironment = useDeleteEnvironment(environment);
|
|
||||||
const [showContextMenu, setShowContextMenu] = useState<{
|
const [showContextMenu, setShowContextMenu] = useState<{
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -277,17 +272,16 @@ function SidebarButton({
|
|||||||
defaultValue: environment.name,
|
defaultValue: environment.name,
|
||||||
});
|
});
|
||||||
if (name == null) return;
|
if (name == null) return;
|
||||||
updateEnvironment.mutate({ name });
|
await patchModel(environment, { name });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
leftSlot: <Icon icon="trash" size="sm" />,
|
leftSlot: <Icon icon="trash" size="sm" />,
|
||||||
onSelect: () => {
|
onSelect: async () => {
|
||||||
deleteEnvironment.mutate(undefined, {
|
await deleteModelWithConfirm(environment);
|
||||||
onSuccess: onDelete,
|
onDelete?.();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { save } from '@tauri-apps/plugin-dialog';
|
import { save } from '@tauri-apps/plugin-dialog';
|
||||||
import type { Workspace } from '@yaakapp-internal/models';
|
import type { Workspace} from '@yaakapp-internal/models';
|
||||||
|
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import slugify from 'slugify';
|
import slugify from 'slugify';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
@@ -17,8 +18,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ExportDataDialog({ onHide, onSuccess }: Props) {
|
export function ExportDataDialog({ onHide, onSuccess }: Props) {
|
||||||
const allWorkspaces = useWorkspaces();
|
const allWorkspaces = useAtomValue(workspacesAtom);
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||||
if (activeWorkspace == null || allWorkspaces.length === 0) return null;
|
if (activeWorkspace == null || allWorkspaces.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,7 +41,7 @@ function ExportDataDialogContent({
|
|||||||
allWorkspaces: Workspace[];
|
allWorkspaces: Workspace[];
|
||||||
activeWorkspace: Workspace;
|
activeWorkspace: Workspace;
|
||||||
}) {
|
}) {
|
||||||
const [includeEnvironments, setIncludeEnvironments] = useState<boolean>(true);
|
const [includeEnvironments, setIncludeEnvironments] = useState<boolean>(false);
|
||||||
const [selectedWorkspaces, setSelectedWorkspaces] = useState<Record<string, boolean>>({
|
const [selectedWorkspaces, setSelectedWorkspaces] = useState<Record<string, boolean>>({
|
||||||
[activeWorkspace.id]: true,
|
[activeWorkspace.id]: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useFolders } from '../hooks/useFolders';
|
import { foldersAtom, patchModel } from '@yaakapp-internal/models';
|
||||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
import { useAtomValue } from 'jotai/index';
|
||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
import { MarkdownEditor } from './MarkdownEditor';
|
import { MarkdownEditor } from './MarkdownEditor';
|
||||||
@@ -9,8 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function FolderSettingsDialog({ folderId }: Props) {
|
export function FolderSettingsDialog({ folderId }: Props) {
|
||||||
const { mutate: updateFolder } = useUpdateAnyFolder();
|
const folders = useAtomValue(foldersAtom);
|
||||||
const folders = useFolders();
|
|
||||||
const folder = folders.find((f) => f.id === folderId);
|
const folder = folders.find((f) => f.id === folderId);
|
||||||
|
|
||||||
if (folder == null) return null;
|
if (folder == null) return null;
|
||||||
@@ -20,10 +19,7 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
|||||||
<Input
|
<Input
|
||||||
label="Folder Name"
|
label="Folder Name"
|
||||||
defaultValue={folder.name}
|
defaultValue={folder.name}
|
||||||
onChange={(name) => {
|
onChange={(name) => patchModel(folder, { name })}
|
||||||
if (folderId == null) return;
|
|
||||||
updateFolder({ id: folderId, update: (folder) => ({ ...folder, name }) });
|
|
||||||
}}
|
|
||||||
stateKey={`name.${folder.id}`}
|
stateKey={`name.${folder.id}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -33,13 +29,7 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
|||||||
className="min-h-[10rem] border border-border px-2"
|
className="min-h-[10rem] border border-border px-2"
|
||||||
defaultValue={folder.description}
|
defaultValue={folder.description}
|
||||||
stateKey={`description.${folder.id}`}
|
stateKey={`description.${folder.id}`}
|
||||||
onChange={(description) => {
|
onChange={(description) => patchModel(folder, { description })}
|
||||||
if (folderId == null) return;
|
|
||||||
updateFolder({
|
|
||||||
id: folderId,
|
|
||||||
update: (folder) => ({ ...folder, description }),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useGit } from '@yaakapp-internal/git';
|
import { useGit } from '@yaakapp-internal/git';
|
||||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
|
||||||
import { sync } from '../init/sync';
|
import { sync } from '../init/sync';
|
||||||
import { showConfirm, showConfirmDelete } from '../lib/confirm';
|
import { showConfirm, showConfirmDelete } from '../lib/confirm';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
@@ -22,7 +22,7 @@ import { HistoryDialog } from './git/HistoryDialog';
|
|||||||
import { GitCommitDialog } from './GitCommitDialog';
|
import { GitCommitDialog } from './GitCommitDialog';
|
||||||
|
|
||||||
export function GitDropdown() {
|
export function GitDropdown() {
|
||||||
const workspaceMeta = useWorkspaceMeta();
|
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
|
||||||
if (workspaceMeta == null) return null;
|
if (workspaceMeta == null) return null;
|
||||||
|
|
||||||
if (workspaceMeta.settingSyncDir == null) {
|
if (workspaceMeta.settingSyncDir == null) {
|
||||||
@@ -33,7 +33,7 @@ export function GitDropdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const [
|
const [
|
||||||
{ status, log },
|
{ status, log },
|
||||||
{ branch, deleteBranch, fetchAll, mergeBranch, push, pull, checkout, init },
|
{ branch, deleteBranch, fetchAll, mergeBranch, push, pull, checkout, init },
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useSubscribeHttpAuthentication } from '../hooks/useHttpAuthentication';
|
|||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
|
||||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||||
@@ -17,7 +16,6 @@ import { showPrompt } from '../lib/prompt';
|
|||||||
import { showToast } from '../lib/toast';
|
import { showToast } from '../lib/toast';
|
||||||
|
|
||||||
export function GlobalHooks() {
|
export function GlobalHooks() {
|
||||||
useSyncModelStores();
|
|
||||||
useSyncZoomSetting();
|
useSyncZoomSetting();
|
||||||
useSyncFontSizeSetting();
|
useSyncFontSizeSetting();
|
||||||
useGenerateThemeCss();
|
useGenerateThemeCss();
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { updateSchema } from 'cm6-graphql';
|
|||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
|
|
||||||
import { formatSdl } from 'format-graphql';
|
import { formatSdl } from 'format-graphql';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
||||||
|
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -30,19 +31,20 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
|||||||
const { schema, isLoading, error, refetch, clear } = useIntrospectGraphQL(baseRequest, {
|
const { schema, isLoading, error, refetch, clear } = useIntrospectGraphQL(baseRequest, {
|
||||||
disabled: autoIntrospectDisabled?.[baseRequest.id],
|
disabled: autoIntrospectDisabled?.[baseRequest.id],
|
||||||
});
|
});
|
||||||
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string | undefined }>(
|
const [currentBody, setCurrentBody] = useStateWithDeps<{
|
||||||
() => {
|
query: string;
|
||||||
// Migrate text bodies to GraphQL format
|
variables: string | undefined;
|
||||||
// NOTE: This is how GraphQL used to be stored
|
}>(() => {
|
||||||
if ('text' in request.body) {
|
// Migrate text bodies to GraphQL format
|
||||||
const b = tryParseJson(request.body.text, {});
|
// NOTE: This is how GraphQL used to be stored
|
||||||
const variables = JSON.stringify(b.variables || undefined, null, 2);
|
if ('text' in request.body) {
|
||||||
return { query: b.query ?? '', variables };
|
const b = tryParseJson(request.body.text, {});
|
||||||
}
|
const variables = JSON.stringify(b.variables || undefined, null, 2);
|
||||||
|
return { query: b.query ?? '', variables };
|
||||||
|
}
|
||||||
|
|
||||||
return { query: request.body.query ?? '', variables: request.body.variables ?? '' };
|
return { query: request.body.query ?? '', variables: request.body.variables ?? '' };
|
||||||
},
|
}, [extraEditorProps.forceUpdateKey]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeQuery = (query: string) => {
|
const handleChangeQuery = (query: string) => {
|
||||||
const newBody = { query, variables: currentBody.variables || undefined };
|
const newBody = { query, variables: currentBody.variables || undefined };
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useGrpc } from '../hooks/useGrpc';
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
|
||||||
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
|
||||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
import { activeGrpcConnectionAtom, useGrpcEvents } from '../hooks/usePinnedGrpcConnection';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { HotKeyList } from './core/HotKeyList';
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
import { SplitLayout } from './core/SplitLayout';
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
import { GrpcConnectionMessagesPane } from './GrpcConnectionMessagesPane';
|
import { GrpcRequestPane } from './GrpcRequestPane';
|
||||||
import { GrpcConnectionSetupPane } from './GrpcConnectionSetupPane';
|
import { GrpcResponsePane } from './GrpcResponsePane';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style: CSSProperties;
|
style: CSSProperties;
|
||||||
@@ -21,10 +21,8 @@ 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 activeConnection = useAtomValue(activeGrpcConnectionAtom);
|
||||||
const connections = useGrpcConnections().filter((c) => c.requestId === activeRequest?.id);
|
const grpcEvents = useGrpcEvents(activeConnection?.id ?? null);
|
||||||
const activeConnection = connections[0] ?? null;
|
|
||||||
const messages = useGrpcEvents(activeConnection?.id ?? null);
|
|
||||||
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
|
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
|
||||||
const protoFiles = protoFilesKv.value ?? emptyArray;
|
const protoFiles = protoFilesKv.value ?? emptyArray;
|
||||||
const grpc = useGrpc(activeRequest, activeConnection, protoFiles);
|
const grpc = useGrpc(activeRequest, activeConnection, protoFiles);
|
||||||
@@ -34,25 +32,21 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
if (services == null || activeRequest == null) return;
|
if (services == null || activeRequest == null) return;
|
||||||
const s = services.find((s) => s.name === activeRequest.service);
|
const s = services.find((s) => s.name === activeRequest.service);
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
updateRequest.mutate({
|
patchModel(activeRequest, {
|
||||||
id: activeRequest.id,
|
service: services[0]?.name ?? null,
|
||||||
update: {
|
method: services[0]?.methods[0]?.name ?? null,
|
||||||
service: services[0]?.name ?? null,
|
}).catch(console.error);
|
||||||
method: services[0]?.methods[0]?.name ?? null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = s.methods.find((m) => m.name === activeRequest.method);
|
const m = s.methods.find((m) => m.name === activeRequest.method);
|
||||||
if (m == null) {
|
if (m == null) {
|
||||||
updateRequest.mutate({
|
patchModel(activeRequest, {
|
||||||
id: activeRequest.id,
|
method: s.methods[0]?.name ?? null,
|
||||||
update: { method: s.methods[0]?.name ?? null },
|
}).catch(console.error);
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [activeRequest, services, updateRequest]);
|
}, [activeRequest, services]);
|
||||||
|
|
||||||
const activeMethod = useMemo(() => {
|
const activeMethod = useMemo(() => {
|
||||||
if (services == null || activeRequest == null) return null;
|
if (services == null || activeRequest == null) return null;
|
||||||
@@ -87,7 +81,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
className="p-3 gap-1.5"
|
className="p-3 gap-1.5"
|
||||||
style={style}
|
style={style}
|
||||||
firstSlot={({ style }) => (
|
firstSlot={({ style }) => (
|
||||||
<GrpcConnectionSetupPane
|
<GrpcRequestPane
|
||||||
style={style}
|
style={style}
|
||||||
activeRequest={activeRequest}
|
activeRequest={activeRequest}
|
||||||
protoFiles={protoFiles}
|
protoFiles={protoFiles}
|
||||||
@@ -117,8 +111,8 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
<Banner color="danger" className="m-2">
|
<Banner color="danger" className="m-2">
|
||||||
{grpc.go.error}
|
{grpc.go.error}
|
||||||
</Banner>
|
</Banner>
|
||||||
) : messages.length >= 0 ? (
|
) : grpcEvents.length >= 0 ? (
|
||||||
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
<GrpcResponsePane activeRequest={activeRequest} methodType={methodType} />
|
||||||
) : (
|
) : (
|
||||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'url_bar.focus']} />
|
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'url_bar.focus']} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { Editor } from './core/Editor/Editor';
|
|||||||
import { FormattedError } from './core/FormattedError';
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
import { GrpcProtoSelection } from './GrpcProtoSelection';
|
import { GrpcProtoSelectionDialog } from './GrpcProtoSelectionDialog';
|
||||||
|
|
||||||
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className' | 'forceUpdateKey'> & {
|
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className' | 'forceUpdateKey'> & {
|
||||||
services: ReflectResponseService[] | null;
|
services: ReflectResponseService[] | null;
|
||||||
@@ -143,13 +143,7 @@ export function GrpcEditor({
|
|||||||
title: 'Configure Schema',
|
title: 'Configure Schema',
|
||||||
size: 'md',
|
size: 'md',
|
||||||
id: 'reflection-failed',
|
id: 'reflection-failed',
|
||||||
render: ({ hide }) => {
|
render: ({ hide }) => <GrpcProtoSelectionDialog onDone={hide} />,
|
||||||
return (
|
|
||||||
<VStack space={6} className="pb-5">
|
|
||||||
<GrpcProtoSelection onDone={hide} requestId={request.id} />
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -167,14 +161,7 @@ export function GrpcEditor({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>,
|
</div>,
|
||||||
],
|
],
|
||||||
[
|
[protoFiles.length, reflectionError, reflectionLoading, reflectionUnavailable, services],
|
||||||
protoFiles.length,
|
|
||||||
reflectionError,
|
|
||||||
reflectionLoading,
|
|
||||||
reflectionUnavailable,
|
|
||||||
request.id,
|
|
||||||
services,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
|
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||||
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useGrpc } from '../hooks/useGrpc';
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||||
import { useGrpcRequest } from '../hooks/useGrpcRequest';
|
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -11,13 +12,18 @@ import { Link } from './core/Link';
|
|||||||
import { HStack, VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
requestId: string;
|
|
||||||
onDone: () => void;
|
onDone: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GrpcProtoSelection({ requestId }: Props) {
|
export function GrpcProtoSelectionDialog(props: Props) {
|
||||||
const request = useGrpcRequest(requestId);
|
const request = useActiveRequest();
|
||||||
const protoFilesKv = useGrpcProtoFiles(requestId);
|
if (request?.model !== 'grpc_request') return null;
|
||||||
|
|
||||||
|
return GrpcProtoSelectionDialogWithRequest({ ...props, request });
|
||||||
|
}
|
||||||
|
|
||||||
|
function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: GrpcRequest }) {
|
||||||
|
const protoFilesKv = useGrpcProtoFiles(request.id);
|
||||||
const protoFiles = protoFilesKv.value ?? [];
|
const protoFiles = protoFilesKv.value ?? [];
|
||||||
const grpc = useGrpc(request, null, protoFiles);
|
const grpc = useGrpc(request, null, protoFiles);
|
||||||
const services = grpc.reflect.data;
|
const services = grpc.reflect.data;
|
||||||
@@ -34,7 +40,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack className="flex-col-reverse" space={3}>
|
<VStack className="flex-col-reverse mb-3" space={3}>
|
||||||
{/* Buttons on top so they get focus first */}
|
{/* Buttons on top so they get focus first */}
|
||||||
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
||||||
<Button
|
<Button
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp-internal/models';
|
import { type GrpcMetadataEntry, type GrpcRequest, patchModel } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
@@ -7,7 +7,6 @@ import type { ReflectResponseService } from '../hooks/useGrpc';
|
|||||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { CountBadge } from './core/CountBadge';
|
import { CountBadge } from './core/CountBadge';
|
||||||
@@ -51,7 +50,7 @@ const TAB_METADATA = 'metadata';
|
|||||||
const TAB_AUTH = 'auth';
|
const TAB_AUTH = 'auth';
|
||||||
const TAB_DESCRIPTION = 'description';
|
const TAB_DESCRIPTION = 'description';
|
||||||
|
|
||||||
export function GrpcConnectionSetupPane({
|
export function GrpcRequestPane({
|
||||||
style,
|
style,
|
||||||
services,
|
services,
|
||||||
methodType,
|
methodType,
|
||||||
@@ -65,28 +64,25 @@ export function GrpcConnectionSetupPane({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSend,
|
onSend,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const updateRequest = useUpdateAnyGrpcRequest();
|
|
||||||
const authentication = useHttpAuthenticationSummaries();
|
const authentication = useHttpAuthenticationSummaries();
|
||||||
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
||||||
namespace: 'no_sync',
|
namespace: 'no_sync',
|
||||||
key: 'grpcRequestActiveTabs',
|
key: 'grpcRequestActiveTabs',
|
||||||
fallback: {},
|
fallback: {},
|
||||||
});
|
});
|
||||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null);
|
||||||
|
|
||||||
const urlContainerEl = useRef<HTMLDivElement>(null);
|
const urlContainerEl = useRef<HTMLDivElement>(null);
|
||||||
const { width: paneWidth } = useContainerSize(urlContainerEl);
|
const { width: paneWidth } = useContainerSize(urlContainerEl);
|
||||||
|
|
||||||
const handleChangeUrl = useCallback(
|
const handleChangeUrl = useCallback(
|
||||||
(url: string) => updateRequest.mutateAsync({ id: activeRequest.id, update: { url } }),
|
(url: string) => patchModel(activeRequest, { url }),
|
||||||
[activeRequest.id, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeMessage = useCallback(
|
const handleChangeMessage = useCallback(
|
||||||
(message: string) => {
|
(message: string) => patchModel(activeRequest, { message }),
|
||||||
return updateRequest.mutateAsync({ id: activeRequest.id, update: { message } });
|
[activeRequest],
|
||||||
},
|
|
||||||
[activeRequest.id, updateRequest],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const select = useMemo(() => {
|
const select = useMemo(() => {
|
||||||
@@ -105,15 +101,12 @@ export function GrpcConnectionSetupPane({
|
|||||||
async (v: string) => {
|
async (v: string) => {
|
||||||
const [serviceName, methodName] = v.split('/', 2);
|
const [serviceName, methodName] = v.split('/', 2);
|
||||||
if (serviceName == null || methodName == null) throw new Error('Should never happen');
|
if (serviceName == null || methodName == null) throw new Error('Should never happen');
|
||||||
await updateRequest.mutateAsync({
|
await patchModel(activeRequest, {
|
||||||
id: activeRequest.id,
|
service: serviceName,
|
||||||
update: {
|
method: methodName,
|
||||||
service: serviceName,
|
|
||||||
method: methodName,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[activeRequest.id, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleConnect = useCallback(async () => {
|
const handleConnect = useCallback(async () => {
|
||||||
@@ -151,16 +144,16 @@ export function GrpcConnectionSetupPane({
|
|||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||||
],
|
],
|
||||||
onChange: (authenticationType) => {
|
onChange: async (authenticationType) => {
|
||||||
let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
|
let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
|
||||||
if (activeRequest.authenticationType !== authenticationType) {
|
if (activeRequest.authenticationType !== authenticationType) {
|
||||||
authentication = {
|
authentication = {
|
||||||
// Reset auth if changing types
|
// Reset auth if changing types
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
updateRequest.mutate({
|
await patchModel(activeRequest, {
|
||||||
id: activeRequest.id,
|
authenticationType,
|
||||||
update: { authenticationType, authentication },
|
authentication,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -172,14 +165,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
rightSlot: activeRequest.description && <CountBadge count={true} />,
|
rightSlot: activeRequest.description && <CountBadge count={true} />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[activeRequest, authentication],
|
||||||
activeRequest.authentication,
|
|
||||||
activeRequest.authenticationType,
|
|
||||||
activeRequest.description,
|
|
||||||
activeRequest.id,
|
|
||||||
authentication,
|
|
||||||
updateRequest,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeTab = activeTabs?.[activeRequest.id];
|
const activeTab = activeTabs?.[activeRequest.id];
|
||||||
@@ -191,15 +177,13 @@ export function GrpcConnectionSetupPane({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleMetadataChange = useCallback(
|
const handleMetadataChange = useCallback(
|
||||||
(metadata: GrpcMetadataEntry[]) =>
|
(metadata: GrpcMetadataEntry[]) => patchModel(activeRequest, { metadata }),
|
||||||
updateRequest.mutate({ id: activeRequest.id, update: { metadata } }),
|
[activeRequest],
|
||||||
[activeRequest.id, updateRequest],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDescriptionChange = useCallback(
|
const handleDescriptionChange = useCallback(
|
||||||
(description: string) =>
|
(description: string) => patchModel(activeRequest, { description }),
|
||||||
updateRequest.mutate({ id: activeRequest.id, update: { description } }),
|
[activeRequest],
|
||||||
[activeRequest.id, updateRequest],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -209,7 +193,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'grid grid-cols-[minmax(0,1fr)_auto] gap-1.5',
|
'grid grid-cols-[minmax(0,1fr)_auto] gap-1.5',
|
||||||
paneWidth === 0 && 'opacity-0',
|
paneWidth === 0 && 'opacity-0',
|
||||||
paneWidth < 400 && '!grid-cols-1',
|
paneWidth > 0 && paneWidth < 400 && '!grid-cols-1',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<UrlBar
|
<UrlBar
|
||||||
@@ -346,7 +330,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
className="font-sans !text-xl !px-0"
|
className="font-sans !text-xl !px-0"
|
||||||
containerClassName="border-0"
|
containerClassName="border-0"
|
||||||
placeholder={resolvedModelName(activeRequest)}
|
placeholder={resolvedModelName(activeRequest)}
|
||||||
onChange={(name) => updateRequest.mutate({ id: activeRequest.id, update: { name } })}
|
onChange={(name) => patchModel(activeRequest, { name })}
|
||||||
/>
|
/>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
name="request-description"
|
name="request-description"
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
import type { GrpcEvent, GrpcRequest } from '@yaakapp-internal/models';
|
import type { GrpcEvent, GrpcRequest } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { useSetAtom } from 'jotai/index';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useCopy } from '../hooks/useCopy';
|
import { useCopy } from '../hooks/useCopy';
|
||||||
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
import {
|
||||||
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
|
activeGrpcConnectionAtom,
|
||||||
|
activeGrpcConnections,
|
||||||
|
pinnedGrpcConnectionIdAtom,
|
||||||
|
useGrpcEvents,
|
||||||
|
} from '../hooks/usePinnedGrpcConnection';
|
||||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||||
import { AutoScroller } from './core/AutoScroller';
|
import { AutoScroller } from './core/AutoScroller';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
@@ -34,13 +40,14 @@ interface Props {
|
|||||||
| 'no-method';
|
| 'no-method';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
|
export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
|
||||||
const [activeEventId, setActiveEventId] = useState<string | null>(null);
|
const [activeEventId, setActiveEventId] = useState<string | null>(null);
|
||||||
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
|
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
|
||||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||||
const { activeConnection, connections, setPinnedConnectionId } =
|
const connections = useAtomValue(activeGrpcConnections);
|
||||||
usePinnedGrpcConnection(activeRequest);
|
const activeConnection = useAtomValue(activeGrpcConnectionAtom);
|
||||||
const events = useGrpcEvents(activeConnection?.id ?? null);
|
const events = useGrpcEvents(activeConnection?.id ?? null);
|
||||||
|
const setPinnedGrpcConnectionId = useSetAtom(pinnedGrpcConnectionIdAtom);
|
||||||
const copy = useCopy();
|
const copy = useCopy();
|
||||||
|
|
||||||
const activeEvent = useMemo(
|
const activeEvent = useMemo(
|
||||||
@@ -78,7 +85,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
<RecentGrpcConnectionsDropdown
|
<RecentGrpcConnectionsDropdown
|
||||||
connections={connections}
|
connections={connections}
|
||||||
activeConnection={activeConnection}
|
activeConnection={activeConnection}
|
||||||
onPinnedConnectionId={setPinnedConnectionId}
|
onPinnedConnectionId={setPinnedGrpcConnectionId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -113,7 +120,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
<div className="pb-3 px-2">
|
<div className="pb-3 px-2">
|
||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-2 overflow-y-auto">
|
<div className="h-full pl-2 overflow-y-auto grid grid-rows-[auto_minmax(0,1fr)] ">
|
||||||
{activeEvent.eventType === 'client_message' ||
|
{activeEvent.eventType === 'client_message' ||
|
||||||
activeEvent.eventType === 'server_message' ? (
|
activeEvent.eventType === 'server_message' ? (
|
||||||
<>
|
<>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { settingsAtom } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useOsInfo } from '../hooks/useOsInfo';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
|
||||||
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||||
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||||
import { WindowControls } from './WindowControls';
|
import { WindowControls } from './WindowControls';
|
||||||
@@ -23,7 +24,7 @@ export function HeaderSize({
|
|||||||
children,
|
children,
|
||||||
}: HeaderSizeProps) {
|
}: HeaderSizeProps) {
|
||||||
const osInfo = useOsInfo();
|
const osInfo = useOsInfo();
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
const stoplightsVisible = useStoplightsVisible();
|
const stoplightsVisible = useStoplightsVisible();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
|
||||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
|
||||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { Checkbox } from './core/Checkbox';
|
import { Checkbox } from './core/Checkbox';
|
||||||
import type { DropdownItem } from './core/Dropdown';
|
import type { DropdownItem } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
@@ -18,8 +16,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HttpAuthenticationEditor({ request }: Props) {
|
export function HttpAuthenticationEditor({ request }: Props) {
|
||||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
|
||||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
|
||||||
const authConfig = useHttpAuthenticationConfig(
|
const authConfig = useHttpAuthenticationConfig(
|
||||||
request.authenticationType,
|
request.authenticationType,
|
||||||
request.authentication,
|
request.authentication,
|
||||||
@@ -27,22 +23,8 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(authentication: Record<string, boolean>) => {
|
(authentication: Record<string, boolean>) => patchModel(request, { authentication }),
|
||||||
if (request.model === 'http_request') {
|
[request],
|
||||||
updateHttpRequest.mutate({
|
|
||||||
id: request.id,
|
|
||||||
update: (r) => ({ ...r, authentication }),
|
|
||||||
});
|
|
||||||
} else if (request.model === 'websocket_request') {
|
|
||||||
upsertWebsocketRequest.mutate({ ...request, authentication });
|
|
||||||
} else {
|
|
||||||
updateGrpcRequest.mutate({
|
|
||||||
id: request.id,
|
|
||||||
update: (r) => ({ ...r, authentication }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[request, updateGrpcRequest, updateHttpRequest],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (authConfig.data == null) {
|
if (authConfig.data == null) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
@@ -6,16 +7,14 @@ import type { CSSProperties } from 'react';
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||||
import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
|
|
||||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||||
import { httpRequestsAtom } from '../hooks/useHttpRequests';
|
|
||||||
import { useImportCurl } from '../hooks/useImportCurl';
|
import { useImportCurl } from '../hooks/useImportCurl';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||||
|
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { deepEqualAtom } from '../lib/atoms';
|
import { deepEqualAtom } from '../lib/atoms';
|
||||||
import { languageFromContentType } from '../lib/contentType';
|
import { languageFromContentType } from '../lib/contentType';
|
||||||
import { generateId } from '../lib/generateId';
|
import { generateId } from '../lib/generateId';
|
||||||
@@ -67,7 +66,7 @@ const TAB_DESCRIPTION = 'description';
|
|||||||
|
|
||||||
const nonActiveRequestUrlsAtom = atom((get) => {
|
const nonActiveRequestUrlsAtom = atom((get) => {
|
||||||
const activeRequestId = get(activeRequestIdAtom);
|
const activeRequestId = get(activeRequestIdAtom);
|
||||||
const requests = [...get(httpRequestsAtom), ...get(grpcRequestsAtom)];
|
const requests = get(allRequestsAtom);
|
||||||
return requests
|
return requests
|
||||||
.filter((r) => r.id !== activeRequestId)
|
.filter((r) => r.id !== activeRequestId)
|
||||||
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
||||||
@@ -77,20 +76,19 @@ const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
|||||||
|
|
||||||
export function HttpRequestPane({ style, fullHeight, className, activeRequest }: Props) {
|
export function HttpRequestPane({ style, fullHeight, className, activeRequest }: Props) {
|
||||||
const activeRequestId = activeRequest.id;
|
const activeRequestId = activeRequest.id;
|
||||||
const { mutateAsync: updateRequestAsync, mutate: updateRequest } = useUpdateAnyHttpRequest();
|
|
||||||
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
||||||
namespace: 'no_sync',
|
namespace: 'no_sync',
|
||||||
key: 'httpRequestActiveTabs',
|
key: 'httpRequestActiveTabs',
|
||||||
fallback: {},
|
fallback: {},
|
||||||
});
|
});
|
||||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null);
|
||||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||||
const contentType = getContentTypeFromHeaders(activeRequest.headers);
|
const contentType = getContentTypeFromHeaders(activeRequest.headers);
|
||||||
const authentication = useHttpAuthenticationSummaries();
|
const authentication = useHttpAuthenticationSummaries();
|
||||||
|
|
||||||
const handleContentTypeChange = useCallback(
|
const handleContentTypeChange = useCallback(
|
||||||
async (contentType: string | null) => {
|
async (contentType: string | null, patch: Partial<Omit<HttpRequest, 'headers'>> = {}) => {
|
||||||
if (activeRequest == null) {
|
if (activeRequest == null) {
|
||||||
console.error('Failed to get active request to update', activeRequest);
|
console.error('Failed to get active request to update', activeRequest);
|
||||||
return;
|
return;
|
||||||
@@ -106,12 +104,12 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
id: generateId(),
|
id: generateId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await updateRequestAsync({ id: activeRequest.id, update: { headers } });
|
await patchModel(activeRequest, { ...patch, headers });
|
||||||
|
|
||||||
// Force update header editor so any changed headers are reflected
|
// Force update header editor so any changed headers are reflected
|
||||||
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
||||||
},
|
},
|
||||||
[activeRequest, updateRequestAsync],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
||||||
@@ -203,10 +201,10 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
showMethodToast(patch.method);
|
showMethodToast(patch.method);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateRequestAsync({ id: activeRequestId, update: patch });
|
|
||||||
|
|
||||||
if (newContentType !== undefined) {
|
if (newContentType !== undefined) {
|
||||||
await handleContentTypeChange(newContentType);
|
await handleContentTypeChange(newContentType, patch);
|
||||||
|
} else {
|
||||||
|
await patchModel(activeRequest, patch);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -242,10 +240,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
// Reset auth if changing types
|
// Reset auth if changing types
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
updateRequest({
|
await patchModel(activeRequest, { authenticationType, authentication });
|
||||||
id: activeRequestId,
|
|
||||||
update: { authenticationType, authentication },
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -254,36 +249,23 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
label: 'Info',
|
label: 'Info',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[activeRequest, authentication, handleContentTypeChange, numParams, urlParameterPairs.length],
|
||||||
activeRequest.authentication,
|
|
||||||
activeRequest.authenticationType,
|
|
||||||
activeRequest.bodyType,
|
|
||||||
activeRequest.headers,
|
|
||||||
activeRequest.method,
|
|
||||||
activeRequestId,
|
|
||||||
authentication,
|
|
||||||
handleContentTypeChange,
|
|
||||||
numParams,
|
|
||||||
updateRequest,
|
|
||||||
updateRequestAsync,
|
|
||||||
urlParameterPairs.length,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||||
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
||||||
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
||||||
const { updateKey } = useRequestUpdateKey(activeRequestId);
|
const updateKey = useRequestUpdateKey(activeRequestId);
|
||||||
const { mutate: importCurl } = useImportCurl();
|
const { mutate: importCurl } = useImportCurl();
|
||||||
|
|
||||||
const handleBodyChange = useCallback(
|
const handleBodyChange = useCallback(
|
||||||
(body: HttpRequest['body']) => updateRequest({ id: activeRequestId, update: { body } }),
|
(body: HttpRequest['body']) => patchModel(activeRequest, { body }),
|
||||||
[activeRequestId, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBodyTextChange = useCallback(
|
const handleBodyTextChange = useCallback(
|
||||||
(text: string) => updateRequest({ id: activeRequestId, update: { body: { text } } }),
|
(text: string) => patchModel(activeRequest, { body: { text } }),
|
||||||
[activeRequestId, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeTab = activeTabs?.[activeRequestId];
|
const activeTab = activeTabs?.[activeRequestId];
|
||||||
@@ -315,15 +297,15 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handlePaste = useCallback(
|
const handlePaste = useCallback(
|
||||||
(e: ClipboardEvent, text: string) => {
|
async (e: ClipboardEvent, text: string) => {
|
||||||
if (text.startsWith('curl ')) {
|
if (text.startsWith('curl ')) {
|
||||||
importCurl({ overwriteRequestId: activeRequestId, command: text });
|
importCurl({ overwriteRequestId: activeRequestId, command: text });
|
||||||
} else {
|
} else {
|
||||||
const data = prepareImportQuerystring(text);
|
const patch = prepareImportQuerystring(text);
|
||||||
if (data != null) {
|
if (patch != null) {
|
||||||
e.preventDefault(); // Prevent input onChange
|
e.preventDefault(); // Prevent input onChange
|
||||||
|
|
||||||
updateRequest({ id: activeRequestId, update: data });
|
await patchModel(activeRequest, patch);
|
||||||
focusParamsTab();
|
focusParamsTab();
|
||||||
|
|
||||||
// Wait for request to update, then refresh the UI
|
// Wait for request to update, then refresh the UI
|
||||||
@@ -336,12 +318,12 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
activeRequest,
|
||||||
activeRequestId,
|
activeRequestId,
|
||||||
focusParamsTab,
|
focusParamsTab,
|
||||||
forceParamsRefresh,
|
forceParamsRefresh,
|
||||||
forceUrlRefresh,
|
forceUrlRefresh,
|
||||||
importCurl,
|
importCurl,
|
||||||
updateRequest,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
const handleSend = useCallback(
|
const handleSend = useCallback(
|
||||||
@@ -350,13 +332,13 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleMethodChange = useCallback(
|
const handleMethodChange = useCallback(
|
||||||
(method: string) => updateRequest({ id: activeRequestId, update: { method } }),
|
(method: string) => patchModel(activeRequest, { method }),
|
||||||
[activeRequestId, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUrlChange = useCallback(
|
const handleUrlChange = useCallback(
|
||||||
(url: string) => updateRequest({ id: activeRequestId, update: { url } }),
|
(url: string) => patchModel(activeRequest, { url }),
|
||||||
[activeRequestId, updateRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -397,7 +379,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
forceUpdateKey={`${forceUpdateHeaderEditorKey}::${forceUpdateKey}`}
|
forceUpdateKey={`${forceUpdateHeaderEditorKey}::${forceUpdateKey}`}
|
||||||
headers={activeRequest.headers}
|
headers={activeRequest.headers}
|
||||||
stateKey={`headers.${activeRequest.id}`}
|
stateKey={`headers.${activeRequest.id}`}
|
||||||
onChange={(headers) => updateRequest({ id: activeRequestId, update: { headers } })}
|
onChange={(headers) => patchModel(activeRequest, { headers })}
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_PARAMS}>
|
<TabContent value={TAB_PARAMS}>
|
||||||
@@ -405,9 +387,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
stateKey={`params.${activeRequest.id}`}
|
stateKey={`params.${activeRequest.id}`}
|
||||||
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||||
pairs={urlParameterPairs}
|
pairs={urlParameterPairs}
|
||||||
onChange={(urlParameters) =>
|
onChange={(urlParameters) => patchModel(activeRequest, { urlParameters })}
|
||||||
updateRequest({ id: activeRequestId, update: { urlParameters } })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_BODY}>
|
<TabContent value={TAB_BODY}>
|
||||||
@@ -459,7 +439,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
requestId={activeRequest.id}
|
requestId={activeRequest.id}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
body={activeRequest.body}
|
body={activeRequest.body}
|
||||||
onChange={(body) => updateRequest({ id: activeRequestId, update: { body } })}
|
onChange={(body) => patchModel(activeRequest, { body })}
|
||||||
onChangeContentType={handleContentTypeChange}
|
onChangeContentType={handleContentTypeChange}
|
||||||
/>
|
/>
|
||||||
) : typeof activeRequest.bodyType === 'string' ? (
|
) : typeof activeRequest.bodyType === 'string' ? (
|
||||||
@@ -488,7 +468,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
className="font-sans !text-xl !px-0"
|
className="font-sans !text-xl !px-0"
|
||||||
containerClassName="border-0"
|
containerClassName="border-0"
|
||||||
placeholder={resolvedModelName(activeRequest)}
|
placeholder={resolvedModelName(activeRequest)}
|
||||||
onChange={(name) => updateRequest({ id: activeRequestId, update: { name } })}
|
onChange={(name) => patchModel(activeRequest, { name })}
|
||||||
/>
|
/>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
name="request-description"
|
name="request-description"
|
||||||
@@ -496,9 +476,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
|||||||
defaultValue={activeRequest.description}
|
defaultValue={activeRequest.description}
|
||||||
stateKey={`description.${activeRequest.id}`}
|
stateKey={`description.${activeRequest.id}`}
|
||||||
forceUpdateKey={updateKey}
|
forceUpdateKey={updateKey}
|
||||||
onChange={(description) =>
|
onChange={(description) => patchModel(activeRequest, { description })}
|
||||||
updateRequest({ id: activeRequestId, update: { description } })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const details: Record<
|
|||||||
commercial_use: null,
|
commercial_use: null,
|
||||||
invalid_license: { label: 'License Error', color: 'danger' },
|
invalid_license: { label: 'License Error', color: 'danger' },
|
||||||
personal_use: { label: 'Personal Use', color: 'notice' },
|
personal_use: { label: 'Personal Use', color: 'notice' },
|
||||||
trialing: { label: 'Personal Use', color: 'notice' },
|
trialing: { label: 'Personal Use', color: 'info' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LicenseBadge() {
|
export function LicenseBadge() {
|
||||||
@@ -23,12 +23,7 @@ export function LicenseBadge() {
|
|||||||
|
|
||||||
if (check.error) {
|
if (check.error) {
|
||||||
return (
|
return (
|
||||||
<LicenseBadgeButton
|
<LicenseBadgeButton color="danger" onClick={() => openSettings.mutate(SettingsTab.License)}>
|
||||||
color="danger"
|
|
||||||
onClick={() => {
|
|
||||||
openSettings.mutate(SettingsTab.License);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
License Error
|
License Error
|
||||||
</LicenseBadgeButton>
|
</LicenseBadgeButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
import type {
|
||||||
|
GrpcRequest,
|
||||||
|
HttpRequest,
|
||||||
|
WebsocketRequest} from '@yaakapp-internal/models';
|
||||||
|
import {
|
||||||
|
patchModel,
|
||||||
|
workspacesAtom,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
|
||||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
|
||||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { showToast } from '../lib/toast';
|
import { showToast } from '../lib/toast';
|
||||||
@@ -19,9 +23,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Props) {
|
export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Props) {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
|
||||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
|
||||||
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,18 +42,12 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
|
|||||||
color="primary"
|
color="primary"
|
||||||
disabled={selectedWorkspaceId === activeWorkspaceId}
|
disabled={selectedWorkspaceId === activeWorkspaceId}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const update = {
|
const patch = {
|
||||||
workspaceId: selectedWorkspaceId,
|
workspaceId: selectedWorkspaceId,
|
||||||
folderId: null,
|
folderId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.model === 'http_request') {
|
await patchModel(request, patch);
|
||||||
await updateHttpRequest.mutateAsync({ id: request.id, update });
|
|
||||||
} else if (request.model === 'grpc_request') {
|
|
||||||
await updateGrpcRequest.mutateAsync({ id: request.id, update });
|
|
||||||
} else if (request.model === 'websocket_request') {
|
|
||||||
await upsertWebsocketRequest.mutateAsync({ ...request, ...update });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide after a moment, to give time for request to disappear
|
// Hide after a moment, to give time for request to disappear
|
||||||
setTimeout(onDone, 100);
|
setTimeout(onDone, 100);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { formatDistanceToNowStrict } from 'date-fns';
|
|
||||||
import { useDeleteGrpcConnection } from '../hooks/useDeleteGrpcConnection';
|
|
||||||
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
|
|
||||||
import type { GrpcConnection } from '@yaakapp-internal/models';
|
import type { GrpcConnection } from '@yaakapp-internal/models';
|
||||||
|
import { deleteModel } from '@yaakapp-internal/models';
|
||||||
|
import { formatDistanceToNowStrict } from 'date-fns';
|
||||||
|
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
@@ -19,7 +19,6 @@ export function RecentGrpcConnectionsDropdown({
|
|||||||
connections,
|
connections,
|
||||||
onPinnedConnectionId,
|
onPinnedConnectionId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
|
|
||||||
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
||||||
const latestConnectionId = connections[0]?.id ?? 'n/a';
|
const latestConnectionId = connections[0]?.id ?? 'n/a';
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ export function RecentGrpcConnectionsDropdown({
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Clear Connection',
|
label: 'Clear Connection',
|
||||||
onSelect: deleteConnection.mutate,
|
onSelect: () => deleteModel(activeConnection),
|
||||||
disabled: connections.length === 0,
|
disabled: connections.length === 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||||
|
import { deleteModel } from '@yaakapp-internal/models';
|
||||||
import { useCopyHttpResponse } from '../hooks/useCopyHttpResponse';
|
import { useCopyHttpResponse } from '../hooks/useCopyHttpResponse';
|
||||||
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
|
||||||
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||||
import { useSaveResponse } from '../hooks/useSaveResponse';
|
import { useSaveResponse } from '../hooks/useSaveResponse';
|
||||||
import { pluralize } from '../lib/pluralize';
|
import { pluralize } from '../lib/pluralize';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
|
import { HttpStatusTag } from './core/HttpStatusTag';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { HttpStatusTag } from './core/HttpStatusTag';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
responses: HttpResponse[];
|
responses: HttpResponse[];
|
||||||
@@ -22,7 +22,6 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
|||||||
responses,
|
responses,
|
||||||
onPinnedResponseId,
|
onPinnedResponseId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
|
||||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||||
const latestResponseId = responses[0]?.id ?? 'n/a';
|
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||||
const saveResponse = useSaveResponse(activeResponse);
|
const saveResponse = useSaveResponse(activeResponse);
|
||||||
@@ -48,7 +47,7 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
|||||||
{
|
{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
onSelect: deleteResponse.mutate,
|
onSelect: () => deleteModel(activeResponse),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Unpin Response',
|
label: 'Unpin Response',
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
|
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { requestsAtom } from '../hooks/useRequests';
|
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
|
||||||
import { jotaiStore } from '../lib/jotai';
|
import { jotaiStore } from '../lib/jotai';
|
||||||
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { DropdownItem, DropdownRef } from './core/Dropdown';
|
import type { DropdownItem, DropdownRef } from './core/Dropdown';
|
||||||
@@ -47,10 +47,10 @@ export function RecentRequestsDropdown({ className }: Props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const activeWorkspaceId = getActiveWorkspaceId();
|
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
if (activeWorkspaceId === null) return [];
|
if (activeWorkspaceId === null) return [];
|
||||||
|
|
||||||
const requests = jotaiStore.get(requestsAtom);
|
const requests = jotaiStore.get(allRequestsAtom);
|
||||||
const recentRequestItems: DropdownItem[] = [];
|
const recentRequestItems: DropdownItem[] = [];
|
||||||
for (const id of recentRequestIds) {
|
for (const id of recentRequestIds) {
|
||||||
const request = requests.find((r) => r.id === id);
|
const request = requests.find((r) => r.id === id);
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { WebsocketConnection } from '@yaakapp-internal/models';
|
import type { WebsocketConnection } from '@yaakapp-internal/models';
|
||||||
|
import { deleteModel, getModel } from '@yaakapp-internal/models';
|
||||||
import { formatDistanceToNowStrict } from 'date-fns';
|
import { formatDistanceToNowStrict } from 'date-fns';
|
||||||
import { deleteWebsocketConnection } from '../commands/deleteWebsocketConnection';
|
|
||||||
import { deleteWebsocketConnections } from '../commands/deleteWebsocketConnections';
|
import { deleteWebsocketConnections } from '../commands/deleteWebsocketConnections';
|
||||||
import { websocketRequestsAtom } from '../hooks/useWebsocketRequests';
|
|
||||||
import { jotaiStore } from '../lib/jotai';
|
|
||||||
import { pluralizeCount } from '../lib/pluralize';
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
@@ -28,15 +26,13 @@ export function RecentWebsocketConnectionsDropdown({
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Clear Connection',
|
label: 'Clear Connection',
|
||||||
onSelect: () => deleteWebsocketConnection.mutate(activeConnection),
|
onSelect: () => deleteModel(activeConnection),
|
||||||
disabled: connections.length === 0,
|
disabled: connections.length === 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Clear ${pluralizeCount('Connection', connections.length)}`,
|
label: `Clear ${pluralizeCount('Connection', connections.length)}`,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const request = jotaiStore
|
const request = getModel('websocket_request', activeConnection.requestId);
|
||||||
.get(websocketRequestsAtom)
|
|
||||||
.find((r) => r.id === activeConnection.requestId);
|
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
deleteWebsocketConnections.mutate(request);
|
deleteWebsocketConnections.mutate(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
|
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
|
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
import { getRecentRequests } from '../hooks/useRecentRequests';
|
import { getRecentRequests } from '../hooks/useRecentRequests';
|
||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
|
|
||||||
export function RedirectToLatestWorkspace() {
|
export function RedirectToLatestWorkspace() {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const recentWorkspaces = useRecentWorkspaces();
|
const recentWorkspaces = useRecentWorkspaces();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaces.length === 0 || recentWorkspaces == null) {
|
if (workspaces.length === 0 || recentWorkspaces == null) {
|
||||||
console.log('No workspaces found to redirect to. Skipping.');
|
console.log('No workspaces found to redirect to. Skipping.', {
|
||||||
|
workspaces,
|
||||||
|
recentWorkspaces,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { EditorKeymap } from '@yaakapp-internal/models';
|
import type { EditorKeymap } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||||
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
|
||||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
|
||||||
import { clamp } from '../../lib/clamp';
|
import { clamp } from '../../lib/clamp';
|
||||||
import { getThemes } from '../../lib/theme/themes';
|
import { getThemes } from '../../lib/theme/themes';
|
||||||
import { isThemeDark } from '../../lib/theme/window';
|
import { isThemeDark } from '../../lib/theme/window';
|
||||||
@@ -62,9 +62,8 @@ const icons: IconProps['icon'][] = [
|
|||||||
const { themes } = getThemes();
|
const { themes } = getThemes();
|
||||||
|
|
||||||
export function SettingsAppearance() {
|
export function SettingsAppearance() {
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
const updateSettings = useUpdateSettings();
|
|
||||||
const appearance = useResolvedAppearance();
|
const appearance = useResolvedAppearance();
|
||||||
const activeTheme = useResolvedTheme();
|
const activeTheme = useResolvedTheme();
|
||||||
|
|
||||||
@@ -93,18 +92,20 @@ export function SettingsAppearance() {
|
|||||||
name="interfaceFontSize"
|
name="interfaceFontSize"
|
||||||
label="Font Size"
|
label="Font Size"
|
||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
|
defaultValue="15"
|
||||||
value={`${settings.interfaceFontSize}`}
|
value={`${settings.interfaceFontSize}`}
|
||||||
options={fontSizeOptions}
|
options={fontSizeOptions}
|
||||||
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
|
onChange={(v) => patchModel(settings, { interfaceFontSize: parseInt(v) })}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
size="sm"
|
size="sm"
|
||||||
name="editorFontSize"
|
name="editorFontSize"
|
||||||
label="Editor Font Size"
|
label="Editor Font Size"
|
||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
|
defaultValue="13"
|
||||||
value={`${settings.editorFontSize}`}
|
value={`${settings.editorFontSize}`}
|
||||||
options={fontSizeOptions}
|
options={fontSizeOptions}
|
||||||
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
onChange={(v) => patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -113,12 +114,12 @@ export function SettingsAppearance() {
|
|||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
value={`${settings.editorKeymap}`}
|
value={`${settings.editorKeymap}`}
|
||||||
options={keymaps}
|
options={keymaps}
|
||||||
onChange={(v) => updateSettings.mutate({ editorKeymap: v })}
|
onChange={(v) => patchModel(settings, { editorKeymap: v })}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={settings.editorSoftWrap}
|
checked={settings.editorSoftWrap}
|
||||||
title="Wrap Editor Lines"
|
title="Wrap Editor Lines"
|
||||||
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
|
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
@@ -129,7 +130,7 @@ export function SettingsAppearance() {
|
|||||||
labelPosition="top"
|
labelPosition="top"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={settings.appearance}
|
value={settings.appearance}
|
||||||
onChange={(appearance) => updateSettings.mutate({ appearance })}
|
onChange={(appearance) => patchModel(settings, { appearance })}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Automatic', value: 'system' },
|
{ label: 'Automatic', value: 'system' },
|
||||||
{ label: 'Light', value: 'light' },
|
{ label: 'Light', value: 'light' },
|
||||||
@@ -147,7 +148,7 @@ export function SettingsAppearance() {
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
value={activeTheme.light.id}
|
value={activeTheme.light.id}
|
||||||
options={lightThemes}
|
options={lightThemes}
|
||||||
onChange={(themeLight) => updateSettings.mutate({ ...settings, themeLight })}
|
onChange={(themeLight) => patchModel(settings, { themeLight })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
||||||
@@ -160,7 +161,7 @@ export function SettingsAppearance() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
value={activeTheme.dark.id}
|
value={activeTheme.dark.id}
|
||||||
options={darkThemes}
|
options={darkThemes}
|
||||||
onChange={(themeDark) => updateSettings.mutate({ ...settings, themeDark })}
|
onChange={(themeDark) => patchModel(settings, { themeDark })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||||
|
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai/index';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { upsertWorkspace } from '../../commands/upsertWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
|
||||||
import { useAppInfo } from '../../hooks/useAppInfo';
|
import { useAppInfo } from '../../hooks/useAppInfo';
|
||||||
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
|
||||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
|
||||||
import { revealInFinderText } from '../../lib/reveal';
|
import { revealInFinderText } from '../../lib/reveal';
|
||||||
import { Checkbox } from '../core/Checkbox';
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { Heading } from '../core/Heading';
|
import { Heading } from '../core/Heading';
|
||||||
@@ -17,9 +16,8 @@ import { Separator } from '../core/Separator';
|
|||||||
import { VStack } from '../core/Stacks';
|
import { VStack } from '../core/Stacks';
|
||||||
|
|
||||||
export function SettingsGeneral() {
|
export function SettingsGeneral() {
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
const updateSettings = useUpdateSettings();
|
|
||||||
const appInfo = useAppInfo();
|
const appInfo = useAppInfo();
|
||||||
const checkForUpdates = useCheckForUpdates();
|
const checkForUpdates = useCheckForUpdates();
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ export function SettingsGeneral() {
|
|||||||
labelClassName="w-[14rem]"
|
labelClassName="w-[14rem]"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={settings.updateChannel}
|
value={settings.updateChannel}
|
||||||
onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
|
onChange={(updateChannel) => patchModel(settings, { updateChannel })}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Stable (less frequent)', value: 'stable' },
|
{ label: 'Stable (less frequent)', value: 'stable' },
|
||||||
{ label: 'Beta (more frequent)', value: 'beta' },
|
{ label: 'Beta (more frequent)', value: 'beta' },
|
||||||
@@ -65,10 +63,10 @@ export function SettingsGeneral() {
|
|||||||
? 'current'
|
? 'current'
|
||||||
: 'ask'
|
: 'ask'
|
||||||
}
|
}
|
||||||
onChange={(v) => {
|
onChange={async (v) => {
|
||||||
if (v === 'current') updateSettings.mutate({ openWorkspaceNewWindow: false });
|
if (v === 'current') await patchModel(settings, { openWorkspaceNewWindow: false });
|
||||||
else if (v === 'new') updateSettings.mutate({ openWorkspaceNewWindow: true });
|
else if (v === 'new') await patchModel(settings, { openWorkspaceNewWindow: true });
|
||||||
else updateSettings.mutate({ openWorkspaceNewWindow: null });
|
else await patchModel(settings, { openWorkspaceNewWindow: null });
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Always Ask', value: 'ask' },
|
{ label: 'Always Ask', value: 'ask' },
|
||||||
@@ -96,9 +94,7 @@ export function SettingsGeneral() {
|
|||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
defaultValue={`${workspace.settingRequestTimeout}`}
|
defaultValue={`${workspace.settingRequestTimeout}`}
|
||||||
validate={(value) => parseInt(value) >= 0}
|
validate={(value) => parseInt(value) >= 0}
|
||||||
onChange={(v) =>
|
onChange={(v) => patchModel(workspace, { settingRequestTimeout: parseInt(v) || 0 })}
|
||||||
upsertWorkspace.mutate({ ...workspace, settingRequestTimeout: parseInt(v) || 0 })
|
|
||||||
}
|
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -106,7 +102,7 @@ export function SettingsGeneral() {
|
|||||||
checked={workspace.settingValidateCertificates}
|
checked={workspace.settingValidateCertificates}
|
||||||
title="Validate TLS Certificates"
|
title="Validate TLS Certificates"
|
||||||
onChange={(settingValidateCertificates) =>
|
onChange={(settingValidateCertificates) =>
|
||||||
upsertWorkspace.mutate({ ...workspace, settingValidateCertificates })
|
patchModel(workspace, { settingValidateCertificates })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -114,8 +110,7 @@ export function SettingsGeneral() {
|
|||||||
checked={workspace.settingFollowRedirects}
|
checked={workspace.settingFollowRedirects}
|
||||||
title="Follow Redirects"
|
title="Follow Redirects"
|
||||||
onChange={(settingFollowRedirects) =>
|
onChange={(settingFollowRedirects) =>
|
||||||
upsertWorkspace.mutate({
|
patchModel(workspace, {
|
||||||
...workspace,
|
|
||||||
settingFollowRedirects,
|
settingFollowRedirects,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ export function SettingsLicense() {
|
|||||||
className="max-w-sm"
|
className="max-w-sm"
|
||||||
onSubmit={async (e) => {
|
onSubmit={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
await activate.mutateAsync({ licenseKey: key });
|
||||||
toggleActivateFormVisible();
|
toggleActivateFormVisible();
|
||||||
activate.mutate({ licenseKey: key });
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlainInput
|
<PlainInput
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||||
import type { Plugin } from '@yaakapp-internal/models';
|
import type { Plugin} from '@yaakapp-internal/models';
|
||||||
|
import { pluginsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
||||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||||
import { usePlugins, useRefreshPlugins } from '../../hooks/usePlugins';
|
import { useRefreshPlugins } from '../../hooks/usePlugins';
|
||||||
import { useUninstallPlugin } from '../../hooks/useUninstallPlugin';
|
import { useUninstallPlugin } from '../../hooks/useUninstallPlugin';
|
||||||
import { Button } from '../core/Button';
|
import { Button } from '../core/Button';
|
||||||
import { IconButton } from '../core/IconButton';
|
import { IconButton } from '../core/IconButton';
|
||||||
@@ -14,7 +16,7 @@ import { SelectFile } from '../SelectFile';
|
|||||||
|
|
||||||
export function SettingsPlugins() {
|
export function SettingsPlugins() {
|
||||||
const [directory, setDirectory] = React.useState<string | null>(null);
|
const [directory, setDirectory] = React.useState<string | null>(null);
|
||||||
const plugins = usePlugins();
|
const plugins = useAtomValue(pluginsAtom);
|
||||||
const createPlugin = useInstallPlugin();
|
const createPlugin = useInstallPlugin();
|
||||||
const refreshPlugins = useRefreshPlugins();
|
const refreshPlugins = useRefreshPlugins();
|
||||||
return (
|
return (
|
||||||
@@ -61,12 +63,7 @@ export function SettingsPlugins() {
|
|||||||
/>
|
/>
|
||||||
<HStack>
|
<HStack>
|
||||||
{directory && (
|
{directory && (
|
||||||
<Button
|
<Button size="xs" type="submit" color="primary" className="ml-auto">
|
||||||
size="xs"
|
|
||||||
type="submit"
|
|
||||||
color="primary"
|
|
||||||
className="ml-auto"
|
|
||||||
>
|
|
||||||
Add Plugin
|
Add Plugin
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
|
||||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
|
||||||
import { Checkbox } from '../core/Checkbox';
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { PlainInput } from '../core/PlainInput';
|
import { PlainInput } from '../core/PlainInput';
|
||||||
import { Select } from '../core/Select';
|
import { Select } from '../core/Select';
|
||||||
@@ -8,8 +8,7 @@ import { Separator } from '../core/Separator';
|
|||||||
import { HStack, VStack } from '../core/Stacks';
|
import { HStack, VStack } from '../core/Stacks';
|
||||||
|
|
||||||
export function SettingsProxy() {
|
export function SettingsProxy() {
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
const updateSettings = useUpdateSettings();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={1.5} className="mb-4">
|
<VStack space={1.5} className="mb-4">
|
||||||
@@ -19,11 +18,11 @@ export function SettingsProxy() {
|
|||||||
hideLabel
|
hideLabel
|
||||||
size="sm"
|
size="sm"
|
||||||
value={settings.proxy?.type ?? 'automatic'}
|
value={settings.proxy?.type ?? 'automatic'}
|
||||||
onChange={(v) => {
|
onChange={async (v) => {
|
||||||
if (v === 'automatic') {
|
if (v === 'automatic') {
|
||||||
updateSettings.mutate({ proxy: undefined });
|
await patchModel(settings, { proxy: undefined });
|
||||||
} else if (v === 'enabled') {
|
} else if (v === 'enabled') {
|
||||||
updateSettings.mutate({
|
await patchModel(settings, {
|
||||||
proxy: {
|
proxy: {
|
||||||
type: 'enabled',
|
type: 'enabled',
|
||||||
http: '',
|
http: '',
|
||||||
@@ -32,7 +31,7 @@ export function SettingsProxy() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
updateSettings.mutate({ proxy: { type: 'disabled' } });
|
await patchModel(settings, { proxy: { type: 'disabled' } });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
@@ -49,10 +48,10 @@ export function SettingsProxy() {
|
|||||||
label="HTTP"
|
label="HTTP"
|
||||||
placeholder="localhost:9090"
|
placeholder="localhost:9090"
|
||||||
defaultValue={settings.proxy?.http}
|
defaultValue={settings.proxy?.http}
|
||||||
onChange={(http) => {
|
onChange={async (http) => {
|
||||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<PlainInput
|
<PlainInput
|
||||||
@@ -60,10 +59,10 @@ export function SettingsProxy() {
|
|||||||
label="HTTPS"
|
label="HTTPS"
|
||||||
placeholder="localhost:9090"
|
placeholder="localhost:9090"
|
||||||
defaultValue={settings.proxy?.https}
|
defaultValue={settings.proxy?.https}
|
||||||
onChange={(https) => {
|
onChange={async (https) => {
|
||||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -71,11 +70,11 @@ export function SettingsProxy() {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={settings.proxy.auth != null}
|
checked={settings.proxy.auth != null}
|
||||||
title="Enable authentication"
|
title="Enable authentication"
|
||||||
onChange={(enabled) => {
|
onChange={async (enabled) => {
|
||||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||||
const auth = enabled ? { user: '', password: '' } : null;
|
const auth = enabled ? { user: '', password: '' } : null;
|
||||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -87,13 +86,13 @@ export function SettingsProxy() {
|
|||||||
label="User"
|
label="User"
|
||||||
placeholder="myUser"
|
placeholder="myUser"
|
||||||
defaultValue={settings.proxy.auth.user}
|
defaultValue={settings.proxy.auth.user}
|
||||||
onChange={(user) => {
|
onChange={async (user) => {
|
||||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||||
const password =
|
const password =
|
||||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.password ?? '') : '';
|
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.password ?? '') : '';
|
||||||
const auth = { user, password };
|
const auth = { user, password };
|
||||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<PlainInput
|
<PlainInput
|
||||||
@@ -102,13 +101,13 @@ export function SettingsProxy() {
|
|||||||
type="password"
|
type="password"
|
||||||
placeholder="s3cretPassw0rd"
|
placeholder="s3cretPassw0rd"
|
||||||
defaultValue={settings.proxy.auth.password}
|
defaultValue={settings.proxy.auth.password}
|
||||||
onChange={(password) => {
|
onChange={async (password) => {
|
||||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||||
const user =
|
const user =
|
||||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.user ?? '') : '';
|
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.user ?? '') : '';
|
||||||
const auth = { user, password };
|
const auth = { user, password };
|
||||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Workspace } from '@yaakapp-internal/models';
|
import type { Workspace } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
|
||||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { Checkbox } from './core/Checkbox';
|
import { Checkbox } from './core/Checkbox';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
@@ -15,8 +15,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
const updateSettings = useUpdateSettings();
|
|
||||||
const [remember, setRemember] = useState<boolean>(false);
|
const [remember, setRemember] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -28,11 +27,11 @@ export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
className="focus"
|
className="focus"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
hide();
|
hide();
|
||||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
||||||
if (remember) {
|
if (remember) {
|
||||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
await patchModel(settings, { openWorkspaceNewWindow: false });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -42,11 +41,11 @@ export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
|||||||
className="focus"
|
className="focus"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
rightSlot={<Icon icon="external_link" />}
|
rightSlot={<Icon icon="external_link" />}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
hide();
|
hide();
|
||||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
||||||
if (remember) {
|
if (remember) {
|
||||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
await patchModel(settings, { openWorkspaceNewWindow: true });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||||
|
import { patchModel } from '@yaakapp-internal/models';
|
||||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||||
import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws';
|
import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
|
||||||
import { getActiveCookieJar } from '../hooks/useActiveCookieJar';
|
import { getActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||||
import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||||
@@ -13,11 +13,10 @@ import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
|||||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||||
|
import { activeWebsocketConnectionAtom } from '../hooks/usePinnedWebsocketConnection';
|
||||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||||
import { requestsAtom } from '../hooks/useRequests';
|
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { useLatestWebsocketConnection } from '../hooks/useWebsocketConnections';
|
|
||||||
import { deepEqualAtom } from '../lib/atoms';
|
import { deepEqualAtom } from '../lib/atoms';
|
||||||
import { languageFromContentType } from '../lib/contentType';
|
import { languageFromContentType } from '../lib/contentType';
|
||||||
import { generateId } from '../lib/generateId';
|
import { generateId } from '../lib/generateId';
|
||||||
@@ -52,10 +51,11 @@ const TAB_DESCRIPTION = 'description';
|
|||||||
|
|
||||||
const nonActiveRequestUrlsAtom = atom((get) => {
|
const nonActiveRequestUrlsAtom = atom((get) => {
|
||||||
const activeRequestId = get(activeRequestIdAtom);
|
const activeRequestId = get(activeRequestIdAtom);
|
||||||
const requests = get(requestsAtom);
|
const requests = get(allRequestsAtom);
|
||||||
return requests
|
const urls = requests
|
||||||
.filter((r) => r.id !== activeRequestId)
|
.filter((r) => r.id !== activeRequestId)
|
||||||
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
||||||
|
return urls;
|
||||||
});
|
});
|
||||||
|
|
||||||
const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
||||||
@@ -67,7 +67,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
key: 'websocketRequestActiveTabs',
|
key: 'websocketRequestActiveTabs',
|
||||||
fallback: {},
|
fallback: {},
|
||||||
});
|
});
|
||||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
const forceUpdateKey = useRequestUpdateKey(activeRequest.id);
|
||||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||||
const authentication = useHttpAuthenticationSummaries();
|
const authentication = useHttpAuthenticationSummaries();
|
||||||
|
|
||||||
@@ -89,17 +89,6 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
}, [activeRequest.url, activeRequest.urlParameters]);
|
}, [activeRequest.url, activeRequest.urlParameters]);
|
||||||
|
|
||||||
const tabs = useMemo<TabItem[]>(() => {
|
const tabs = useMemo<TabItem[]>(() => {
|
||||||
// const options: Omit<RadioDropdownProps<WebsocketMessageType>, 'children'> = {
|
|
||||||
// value: activeRequest.messageType ?? 'text',
|
|
||||||
// items: [
|
|
||||||
// { label: 'Text', value: 'text' },
|
|
||||||
// { label: 'Binary', value: 'binary' },
|
|
||||||
// ],
|
|
||||||
// onChange: async (messageType) => {
|
|
||||||
// if (messageType === activeRequest.messageType) return;
|
|
||||||
// upsertWebsocketRequest.mutate({ ...activeRequest, messageType });
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
value: TAB_MESSAGE,
|
value: TAB_MESSAGE,
|
||||||
@@ -136,8 +125,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
// Reset auth if changing types
|
// Reset auth if changing types
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
upsertWebsocketRequest.mutate({
|
await patchModel(activeRequest, {
|
||||||
...activeRequest,
|
|
||||||
authenticationType,
|
authenticationType,
|
||||||
authentication,
|
authentication,
|
||||||
});
|
});
|
||||||
@@ -153,9 +141,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
|
|
||||||
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
||||||
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
||||||
const { mutate: updateRequest } = useUpdateAnyHttpRequest();
|
const connection = useAtomValue(activeWebsocketConnectionAtom);
|
||||||
const { updateKey } = useRequestUpdateKey(activeRequestId);
|
|
||||||
const connection = useLatestWebsocketConnection(activeRequestId);
|
|
||||||
|
|
||||||
const activeTab = activeTabs?.[activeRequestId];
|
const activeTab = activeTabs?.[activeRequestId];
|
||||||
const setActiveTab = useCallback(
|
const setActiveTab = useCallback(
|
||||||
@@ -207,17 +193,17 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
const handleUrlChange = useCallback(
|
const handleUrlChange = useCallback(
|
||||||
(url: string) => upsertWebsocketRequest.mutate({ ...activeRequest, url }),
|
(url: string) => patchModel(activeRequest, { url }),
|
||||||
[activeRequest],
|
[activeRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePaste = useCallback(
|
const handlePaste = useCallback(
|
||||||
(e: ClipboardEvent, text: string) => {
|
async (e: ClipboardEvent, text: string) => {
|
||||||
const data = prepareImportQuerystring(text);
|
const patch = prepareImportQuerystring(text);
|
||||||
if (data != null) {
|
if (patch != null) {
|
||||||
e.preventDefault(); // Prevent input onChange
|
e.preventDefault(); // Prevent input onChange
|
||||||
|
|
||||||
updateRequest({ id: activeRequestId, update: data });
|
await patchModel(activeRequest, patch);
|
||||||
focusParamsTab();
|
focusParamsTab();
|
||||||
|
|
||||||
// Wait for request to update, then refresh the UI
|
// Wait for request to update, then refresh the UI
|
||||||
@@ -228,7 +214,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeRequestId, focusParamsTab, forceParamsRefresh, forceUrlRefresh, updateRequest],
|
[activeRequest, focusParamsTab, forceParamsRefresh, forceUrlRefresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageLanguage = languageFromContentType(null, activeRequest.message);
|
const messageLanguage = languageFromContentType(null, activeRequest.message);
|
||||||
@@ -266,7 +252,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
onSend={isLoading ? handleSend : handleConnect}
|
onSend={isLoading ? handleSend : handleConnect}
|
||||||
onCancel={cancelResponse}
|
onCancel={cancelResponse}
|
||||||
onUrlChange={handleUrlChange}
|
onUrlChange={handleUrlChange}
|
||||||
forceUpdateKey={updateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
isLoading={activeResponse != null && activeResponse.state !== 'closed'}
|
isLoading={activeResponse != null && activeResponse.state !== 'closed'}
|
||||||
method={null}
|
method={null}
|
||||||
/>
|
/>
|
||||||
@@ -287,7 +273,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
headers={activeRequest.headers}
|
headers={activeRequest.headers}
|
||||||
stateKey={`headers.${activeRequest.id}`}
|
stateKey={`headers.${activeRequest.id}`}
|
||||||
onChange={(headers) => upsertWebsocketRequest.mutate({ ...activeRequest, headers })}
|
onChange={(headers) => patchModel(activeRequest, { headers })}
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_PARAMS}>
|
<TabContent value={TAB_PARAMS}>
|
||||||
@@ -295,9 +281,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
stateKey={`params.${activeRequest.id}`}
|
stateKey={`params.${activeRequest.id}`}
|
||||||
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||||
pairs={urlParameterPairs}
|
pairs={urlParameterPairs}
|
||||||
onChange={(urlParameters) =>
|
onChange={(urlParameters) => patchModel(activeRequest, { urlParameters })}
|
||||||
upsertWebsocketRequest.mutate({ ...activeRequest, urlParameters })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_MESSAGE}>
|
<TabContent value={TAB_MESSAGE}>
|
||||||
@@ -309,7 +293,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
defaultValue={activeRequest.message}
|
defaultValue={activeRequest.message}
|
||||||
language={messageLanguage}
|
language={messageLanguage}
|
||||||
onChange={(message) => upsertWebsocketRequest.mutate({ ...activeRequest, message })}
|
onChange={(message) => patchModel(activeRequest, { message })}
|
||||||
stateKey={`json.${activeRequest.id}`}
|
stateKey={`json.${activeRequest.id}`}
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
@@ -318,22 +302,20 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
|||||||
<PlainInput
|
<PlainInput
|
||||||
label="Request Name"
|
label="Request Name"
|
||||||
hideLabel
|
hideLabel
|
||||||
forceUpdateKey={updateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
defaultValue={activeRequest.name}
|
defaultValue={activeRequest.name}
|
||||||
className="font-sans !text-xl !px-0"
|
className="font-sans !text-xl !px-0"
|
||||||
containerClassName="border-0"
|
containerClassName="border-0"
|
||||||
placeholder={resolvedModelName(activeRequest)}
|
placeholder={resolvedModelName(activeRequest)}
|
||||||
onChange={(name) => upsertWebsocketRequest.mutate({ ...activeRequest, name })}
|
onChange={(name) => patchModel(activeRequest, { name })}
|
||||||
/>
|
/>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
name="request-description"
|
name="request-description"
|
||||||
placeholder="Request description"
|
placeholder="Request description"
|
||||||
defaultValue={activeRequest.description}
|
defaultValue={activeRequest.description}
|
||||||
stateKey={`description.${activeRequest.id}`}
|
stateKey={`description.${activeRequest.id}`}
|
||||||
forceUpdateKey={updateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
onChange={(description) =>
|
onChange={(description) => patchModel(activeRequest, { description })}
|
||||||
upsertWebsocketRequest.mutate({ ...activeRequest, description })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import type { WebsocketEvent, WebsocketRequest } from '@yaakapp-internal/models'
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { hexy } from 'hexy';
|
import { hexy } from 'hexy';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import { useCopy } from '../hooks/useCopy';
|
import { useCopy } from '../hooks/useCopy';
|
||||||
import { useFormatText } from '../hooks/useFormatText';
|
import { useFormatText } from '../hooks/useFormatText';
|
||||||
import { usePinnedWebsocketConnection } from '../hooks/usePinnedWebsocketConnection';
|
import {
|
||||||
|
activeWebsocketConnectionAtom,
|
||||||
|
activeWebsocketConnectionsAtom,
|
||||||
|
setPinnedWebsocketConnectionId,
|
||||||
|
useWebsocketEvents,
|
||||||
|
} from '../hooks/usePinnedWebsocketConnection';
|
||||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||||
import { useWebsocketEvents } from '../hooks/useWebsocketEvents';
|
|
||||||
import { languageFromContentType } from '../lib/contentType';
|
import { languageFromContentType } from '../lib/contentType';
|
||||||
import { AutoScroller } from './core/AutoScroller';
|
import { AutoScroller } from './core/AutoScroller';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
@@ -33,10 +38,9 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
|||||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||||
const [hexDumps, setHexDumps] = useState<Record<string, boolean>>({});
|
const [hexDumps, setHexDumps] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
const { activeConnection, connections, setPinnedConnectionId } =
|
const activeConnection = useAtomValue(activeWebsocketConnectionAtom);
|
||||||
usePinnedWebsocketConnection(activeRequest);
|
const connections = useAtomValue(activeWebsocketConnectionsAtom);
|
||||||
|
|
||||||
// const isLoading = activeConnection !== null && activeConnection.state !== 'closed';
|
|
||||||
const events = useWebsocketEvents(activeConnection?.id ?? null);
|
const events = useWebsocketEvents(activeConnection?.id ?? null);
|
||||||
|
|
||||||
const activeEvent = useMemo(
|
const activeEvent = useMemo(
|
||||||
@@ -82,7 +86,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
|||||||
<RecentWebsocketConnectionsDropdown
|
<RecentWebsocketConnectionsDropdown
|
||||||
connections={connections}
|
connections={connections}
|
||||||
activeConnection={activeConnection}
|
activeConnection={activeConnection}
|
||||||
onPinnedConnectionId={setPinnedConnectionId}
|
onPinnedConnectionId={setPinnedWebsocketConnectionId}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import * as m from 'motion/react-m';
|
import * as m from 'motion/react-m';
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import {duplicateWebsocketRequest} from "../commands/duplicateWebsocketRequest";
|
|
||||||
import {
|
import {
|
||||||
useEnsureActiveCookieJar,
|
useEnsureActiveCookieJar,
|
||||||
useSubscribeActiveCookieJarId,
|
useSubscribeActiveCookieJarId,
|
||||||
} from '../hooks/useActiveCookieJar';
|
} from '../hooks/useActiveCookieJar';
|
||||||
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
||||||
import { getActiveRequest, useActiveRequest } from '../hooks/useActiveRequest';
|
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
||||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
|
||||||
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
|
||||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useImportData } from '../hooks/useImportData';
|
import { useImportData } from '../hooks/useImportData';
|
||||||
@@ -25,7 +24,8 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
|||||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||||
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
||||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { duplicateRequestAndNavigate } from '../lib/deleteRequestAndNavigate';
|
||||||
|
import { jotaiStore } from '../lib/jotai';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { HotKeyList } from './core/HotKeyList';
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
@@ -51,7 +51,7 @@ export function Workspace() {
|
|||||||
// First, subscribe to some things applicable to workspaces
|
// First, subscribe to some things applicable to workspaces
|
||||||
useGlobalWorkspaceHooks();
|
useGlobalWorkspaceHooks();
|
||||||
|
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||||
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
||||||
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
||||||
@@ -181,8 +181,8 @@ export function Workspace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WorkspaceBody() {
|
function WorkspaceBody() {
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useAtomValue(activeRequestAtom);
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const importData = useImportData();
|
const importData = useImportData();
|
||||||
|
|
||||||
if (activeWorkspace == null) {
|
if (activeWorkspace == null) {
|
||||||
@@ -242,29 +242,7 @@ function useGlobalWorkspaceHooks() {
|
|||||||
const toggleCommandPalette = useToggleCommandPalette();
|
const toggleCommandPalette = useToggleCommandPalette();
|
||||||
useHotKey('command_palette.toggle', toggleCommandPalette);
|
useHotKey('command_palette.toggle', toggleCommandPalette);
|
||||||
|
|
||||||
const activeRequest = useActiveRequest();
|
useHotKey('http_request.duplicate', () =>
|
||||||
const duplicateHttpRequest = useDuplicateHttpRequest({
|
duplicateRequestAndNavigate(jotaiStore.get(activeRequestAtom)),
|
||||||
id: activeRequest?.id ?? null,
|
);
|
||||||
navigateAfter: true,
|
|
||||||
});
|
|
||||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({
|
|
||||||
id: activeRequest?.id ?? null,
|
|
||||||
navigateAfter: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
useHotKey('http_request.duplicate', async () => {
|
|
||||||
const activeRequest = getActiveRequest();
|
|
||||||
if (activeRequest == null) {
|
|
||||||
// Nothing
|
|
||||||
} else if (activeRequest.model === 'http_request') {
|
|
||||||
await duplicateHttpRequest.mutateAsync();
|
|
||||||
} else if (activeRequest.model === 'grpc_request') {
|
|
||||||
await duplicateGrpcRequest.mutateAsync();
|
|
||||||
} else if (activeRequest.model === 'websocket_request') {
|
|
||||||
await duplicateWebsocketRequest.mutateAsync(activeRequest.id);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
throw new Error('Failed to duplicate invalid request model: ' + (activeRequest as any).model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import {open} from "@tauri-apps/plugin-dialog";
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||||
|
import { getModel, settingsAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { openWorkspaceFromSyncDir } from '../commands/openWorkspaceFromSyncDir';
|
import { openWorkspaceFromSyncDir } from '../commands/openWorkspaceFromSyncDir';
|
||||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||||
import { settingsAtom } from '../hooks/useSettings';
|
|
||||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
|
||||||
import { getWorkspace, useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { jotaiStore } from '../lib/jotai';
|
import { jotaiStore } from '../lib/jotai';
|
||||||
import { revealInFinderText } from '../lib/reveal';
|
import { revealInFinderText } from '../lib/reveal';
|
||||||
@@ -28,10 +27,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
className,
|
className,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const createWorkspace = useCreateWorkspace();
|
const createWorkspace = useCreateWorkspace();
|
||||||
const workspaceMeta = useWorkspaceMeta();
|
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
|
||||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||||
|
|
||||||
const { workspaceItems, extraItems } = useMemo<{
|
const { workspaceItems, extraItems } = useMemo<{
|
||||||
@@ -92,7 +91,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
return { workspaceItems, extraItems };
|
return { workspaceItems, extraItems };
|
||||||
}, [workspaces, workspaceMeta, deleteSendHistory, createWorkspace, workspace?.id]);
|
}, [workspaces, workspaceMeta, deleteSendHistory, createWorkspace, workspace?.id]);
|
||||||
|
|
||||||
const handleChangeWorkspace = useCallback(async (workspaceId: string | null) => {
|
const handleSwitchWorkspace = useCallback(async (workspaceId: string | null) => {
|
||||||
if (workspaceId == null) return;
|
if (workspaceId == null) return;
|
||||||
|
|
||||||
const settings = jotaiStore.get(settingsAtom);
|
const settings = jotaiStore.get(settingsAtom);
|
||||||
@@ -101,7 +100,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspace = getWorkspace(workspaceId);
|
const workspace = getModel('workspace', workspaceId);
|
||||||
if (workspace == null) return;
|
if (workspace == null) return;
|
||||||
|
|
||||||
showDialog({
|
showDialog({
|
||||||
@@ -116,7 +115,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
<RadioDropdown
|
<RadioDropdown
|
||||||
items={workspaceItems}
|
items={workspaceItems}
|
||||||
extraItems={extraItems}
|
extraItems={extraItems}
|
||||||
onChange={handleChangeWorkspace}
|
onChange={handleSwitchWorkspace}
|
||||||
value={workspace?.id ?? null}
|
value={workspace?.id ?? null}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
import { patchModel, workspaceMetasAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
import { useAtomValue } from 'jotai/index';
|
||||||
import { useDeleteActiveWorkspace } from '../hooks/useDeleteActiveWorkspace';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
import { router } from '../lib/router';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
@@ -19,10 +18,10 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Props) {
|
export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Props) {
|
||||||
const workspaces = useWorkspaces();
|
const workspace = useAtomValue(workspacesAtom).find((w) => w.id === workspaceId);
|
||||||
const workspace = workspaces.find((w) => w.id === workspaceId);
|
const workspaceMeta = useAtomValue(workspaceMetasAtom).find(
|
||||||
const workspaceMeta = useWorkspaceMeta();
|
(wm) => wm.workspaceId === workspaceId,
|
||||||
const { mutateAsync: deleteActiveWorkspace } = useDeleteActiveWorkspace();
|
);
|
||||||
|
|
||||||
if (workspace == null) {
|
if (workspace == null) {
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +44,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
|||||||
required
|
required
|
||||||
label="Name"
|
label="Name"
|
||||||
defaultValue={workspace.name}
|
defaultValue={workspace.name}
|
||||||
onChange={(name) => upsertWorkspace.mutate({ ...workspace, name })}
|
onChange={(name) => patchModel(workspace, { name })}
|
||||||
stateKey={`name.${workspace.id}`}
|
stateKey={`name.${workspace.id}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
|||||||
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
||||||
defaultValue={workspace.description}
|
defaultValue={workspace.description}
|
||||||
stateKey={`description.${workspace.id}`}
|
stateKey={`description.${workspace.id}`}
|
||||||
onChange={(description) => upsertWorkspace.mutate({ ...workspace, description })}
|
onChange={(description) => patchModel(workspace, { description })}
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -64,16 +63,15 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
|||||||
value={{ filePath: workspaceMeta.settingSyncDir }}
|
value={{ filePath: workspaceMeta.settingSyncDir }}
|
||||||
forceOpen={openSyncMenu}
|
forceOpen={openSyncMenu}
|
||||||
onCreateNewWorkspace={hide}
|
onCreateNewWorkspace={hide}
|
||||||
onChange={({ filePath }) => {
|
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||||
upsertWorkspaceMeta.mutate({ ...workspaceMeta, settingSyncDir: filePath });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Separator />
|
<Separator />
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const workspace = await deleteActiveWorkspace();
|
const didDelete = await deleteModelWithConfirm(workspace);
|
||||||
if (workspace) {
|
if (didDelete) {
|
||||||
hide(); // Only hide if actually deleted workspace
|
hide(); // Only hide if actually deleted workspace
|
||||||
|
await router.navigate({ to: '/workspaces' });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
color="danger"
|
color="danger"
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/vie
|
|||||||
import { emacs } from '@replit/codemirror-emacs';
|
import { emacs } from '@replit/codemirror-emacs';
|
||||||
import { vim } from '@replit/codemirror-vim';
|
import { vim } from '@replit/codemirror-vim';
|
||||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||||
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
|
import type {EditorKeymap, EnvironmentVariable} from '@yaakapp-internal/models';
|
||||||
|
import { settingsAtom} from '@yaakapp-internal/models';
|
||||||
import type { EditorLanguage, TemplateFunction } from '@yaakapp-internal/plugins';
|
import type { EditorLanguage, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
|
import {useAtomValue} from "jotai";
|
||||||
import { md5 } from 'js-md5';
|
import { md5 } from 'js-md5';
|
||||||
import type { MutableRefObject, ReactNode } from 'react';
|
import type { MutableRefObject, ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +28,6 @@ import {
|
|||||||
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
|
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
|
||||||
import { parseTemplate } from '../../../hooks/useParseTemplate';
|
import { parseTemplate } from '../../../hooks/useParseTemplate';
|
||||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||||
import { useSettings } from '../../../hooks/useSettings';
|
|
||||||
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
||||||
import { showDialog } from '../../../lib/dialog';
|
import { showDialog } from '../../../lib/dialog';
|
||||||
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
||||||
@@ -125,7 +126,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
}: EditorProps,
|
}: EditorProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const settings = useSettings();
|
const settings = useAtomValue(settingsAtom);
|
||||||
|
|
||||||
const allEnvironmentVariables = useActiveEnvironmentVariables();
|
const allEnvironmentVariables = useActiveEnvironmentVariables();
|
||||||
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface SelectProps<T extends string> {
|
|||||||
leftSlot?: ReactNode;
|
leftSlot?: ReactNode;
|
||||||
options: RadioDropdownItem<T>[];
|
options: RadioDropdownItem<T>[];
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
|
defaultValue?: T;
|
||||||
size?: ButtonProps['size'];
|
size?: ButtonProps['size'];
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -36,6 +37,7 @@ export function Select<T extends string>({
|
|||||||
leftSlot,
|
leftSlot,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
|
defaultValue,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
}: SelectProps<T>) {
|
}: SelectProps<T>) {
|
||||||
const osInfo = useOsInfo();
|
const osInfo = useOsInfo();
|
||||||
@@ -83,7 +85,9 @@ export function Select<T extends string>({
|
|||||||
onChange={(e) => handleChange(e.target.value as T)}
|
onChange={(e) => handleChange(e.target.value as T)}
|
||||||
onFocus={() => setFocused(true)}
|
onFocus={() => setFocused(true)}
|
||||||
onBlur={() => setFocused(false)}
|
onBlur={() => setFocused(false)}
|
||||||
className={classNames('pr-7 w-full outline-none bg-transparent disabled:opacity-disabled')}
|
className={classNames(
|
||||||
|
'pr-7 w-full outline-none bg-transparent disabled:opacity-disabled',
|
||||||
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{isInvalidSelection && <option value={'__NONE__'}>-- Select an Option --</option>}
|
{isInvalidSelection && <option value={'__NONE__'}>-- Select an Option --</option>}
|
||||||
@@ -92,6 +96,7 @@ export function Select<T extends string>({
|
|||||||
return (
|
return (
|
||||||
<option key={o.value} value={o.value}>
|
<option key={o.value} value={o.value}>
|
||||||
{o.label}
|
{o.label}
|
||||||
|
{o.value === defaultValue && ' (default)'}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||||
import { clamp } from '../../lib/clamp';
|
import { clamp } from '../../lib/clamp';
|
||||||
import { ResizeHandle } from '../ResizeHandle';
|
import { ResizeHandle } from '../ResizeHandle';
|
||||||
@@ -42,7 +43,7 @@ export function SplitLayout({
|
|||||||
minWidthPx = 10,
|
minWidthPx = 10,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const [widthRaw, setWidth] = useLocalStorage<number>(
|
const [widthRaw, setWidth] = useLocalStorage<number>(
|
||||||
`${name}_width::${activeWorkspace?.id ?? 'n/a'}`,
|
`${name}_width::${activeWorkspace?.id ?? 'n/a'}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,29 +5,26 @@ import type {
|
|||||||
WebsocketRequest,
|
WebsocketRequest,
|
||||||
Workspace,
|
Workspace,
|
||||||
} from '@yaakapp-internal/models';
|
} from '@yaakapp-internal/models';
|
||||||
|
import { getAnyModel, patchModelById } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import React, { useCallback, useRef, useState } from 'react';
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKey, useKeyPressEvent } from 'react-use';
|
||||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
import { activeRequestIdAtom } from '../../hooks/useActiveRequestId';
|
||||||
import { getActiveRequest } from '../../hooks/useActiveRequest';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
|
||||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
|
||||||
import { useHotKey } from '../../hooks/useHotKey';
|
import { useHotKey } from '../../hooks/useHotKey';
|
||||||
import { useSidebarHidden } from '../../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../../hooks/useSidebarHidden';
|
||||||
import { getSidebarCollapsedMap } from '../../hooks/useSidebarItemCollapsed';
|
import { getSidebarCollapsedMap } from '../../hooks/useSidebarItemCollapsed';
|
||||||
import { useUpdateAnyFolder } from '../../hooks/useUpdateAnyFolder';
|
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
import { jotaiStore } from '../../lib/jotai';
|
||||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
|
||||||
import { router } from '../../lib/router';
|
import { router } from '../../lib/router';
|
||||||
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
||||||
import { ContextMenu } from '../core/Dropdown';
|
import { ContextMenu } from '../core/Dropdown';
|
||||||
|
import { GitDropdown } from '../GitDropdown';
|
||||||
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
||||||
import type { SidebarItemProps } from './SidebarItem';
|
import type { SidebarItemProps } from './SidebarItem';
|
||||||
import { SidebarItems } from './SidebarItems';
|
import { SidebarItems } from './SidebarItems';
|
||||||
import { GitDropdown } from '../GitDropdown';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -49,13 +46,10 @@ export interface SidebarTreeNode {
|
|||||||
export function Sidebar({ className }: Props) {
|
export function Sidebar({ className }: Props) {
|
||||||
const [hidden, setHidden] = useSidebarHidden();
|
const [hidden, setHidden] = useSidebarHidden();
|
||||||
const sidebarRef = useRef<HTMLElement>(null);
|
const sidebarRef = useRef<HTMLElement>(null);
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||||
const [selectedId, setSelectedId] = useAtom(sidebarSelectedIdAtom);
|
const [selectedId, setSelectedId] = useAtom(sidebarSelectedIdAtom);
|
||||||
const [selectedTree, setSelectedTree] = useState<SidebarTreeNode | null>(null);
|
const [selectedTree, setSelectedTree] = useState<SidebarTreeNode | null>(null);
|
||||||
const { mutateAsync: updateAnyHttpRequest } = useUpdateAnyHttpRequest();
|
|
||||||
const { mutateAsync: updateAnyGrpcRequest } = useUpdateAnyGrpcRequest();
|
|
||||||
const { mutateAsync: updateAnyFolder } = useUpdateAnyFolder();
|
|
||||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||||
const [hoveredTree, setHoveredTree] = useState<SidebarTreeNode | null>(null);
|
const [hoveredTree, setHoveredTree] = useState<SidebarTreeNode | null>(null);
|
||||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
@@ -72,11 +66,11 @@ export function Sidebar({ className }: Props) {
|
|||||||
noFocusSidebar?: boolean;
|
noFocusSidebar?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
) => {
|
) => {
|
||||||
const activeRequest = getActiveRequest();
|
const activeRequestId = jotaiStore.get(activeRequestIdAtom);
|
||||||
const { forced, noFocusSidebar } = args;
|
const { forced, noFocusSidebar } = args;
|
||||||
const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null;
|
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
||||||
const children = tree?.children ?? [];
|
const children = tree?.children ?? [];
|
||||||
const id = forced?.id ?? children.find((m) => m.id === activeRequest?.id)?.id ?? null;
|
const id = forced?.id ?? children.find((m) => m.id === activeRequestId)?.id ?? null;
|
||||||
|
|
||||||
setHasFocus(true);
|
setHasFocus(true);
|
||||||
setSelectedId(id);
|
setSelectedId(id);
|
||||||
@@ -129,14 +123,14 @@ export function Sidebar({ className }: Props) {
|
|||||||
}, [focusActiveRequest, hasFocus]);
|
}, [focusActiveRequest, hasFocus]);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]);
|
const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]);
|
||||||
const deleteRequest = useDeleteAnyRequest();
|
|
||||||
|
|
||||||
useHotKey(
|
useHotKey(
|
||||||
'http_request.delete',
|
'sidebar.delete_selected_item',
|
||||||
async () => {
|
async () => {
|
||||||
// Delete only works if a request is focused
|
const request = getAnyModel(selectedId ?? 'n/a');
|
||||||
if (selectedId == null) return;
|
if (request != null) {
|
||||||
deleteRequest.mutate(selectedId);
|
await deleteModelWithConfirm(request);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ enable: hasFocus },
|
{ enable: hasFocus },
|
||||||
);
|
);
|
||||||
@@ -178,7 +172,8 @@ export function Sidebar({ className }: Props) {
|
|||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||||
const newSelectable = selectableRequests[i - 1];
|
const newI = i <= 0 ? selectableRequests.length - 1 : i - 1;
|
||||||
|
const newSelectable = selectableRequests[newI];
|
||||||
if (newSelectable == null) {
|
if (newSelectable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -196,7 +191,8 @@ export function Sidebar({ className }: Props) {
|
|||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||||
const newSelectable = selectableRequests[i + 1];
|
const newI = i >= selectableRequests.length - 1 ? 0 : i + 1;
|
||||||
|
const newSelectable = selectableRequests[newI];
|
||||||
if (newSelectable == null) {
|
if (newSelectable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -277,52 +273,16 @@ export function Sidebar({ className }: Props) {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
newChildren.map((child, i) => {
|
newChildren.map((child, i) => {
|
||||||
const sortPriority = i * 1000;
|
const sortPriority = i * 1000;
|
||||||
if (child.model === 'folder') {
|
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
|
||||||
return updateAnyFolder({ id: child.id, update: updateFolder });
|
|
||||||
} else if (child.model === 'grpc_request') {
|
|
||||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
|
||||||
return updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
|
||||||
} else if (child.model === 'http_request') {
|
|
||||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
|
||||||
return updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
|
||||||
} else if (child.model === 'websocket_request') {
|
|
||||||
const request = getWebsocketRequest(child.id);
|
|
||||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid model to update: ' + child.model);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const sortPriority = afterPriority - (afterPriority - beforePriority) / 2;
|
const sortPriority = afterPriority - (afterPriority - beforePriority) / 2;
|
||||||
if (child.model === 'folder') {
|
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
|
||||||
await updateAnyFolder({ id: child.id, update: updateFolder });
|
|
||||||
} else if (child.model === 'grpc_request') {
|
|
||||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
|
||||||
await updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
|
||||||
} else if (child.model === 'http_request') {
|
|
||||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
|
||||||
await updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
|
||||||
} else if (child.model === 'websocket_request') {
|
|
||||||
const request = getWebsocketRequest(child.id);
|
|
||||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid model to update: ' + child.model);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setDraggingId(null);
|
setDraggingId(null);
|
||||||
},
|
},
|
||||||
[
|
[handleClearSelected, hoveredTree, hoveredIndex, treeParentMap],
|
||||||
handleClearSelected,
|
|
||||||
hoveredTree,
|
|
||||||
hoveredIndex,
|
|
||||||
treeParentMap,
|
|
||||||
updateAnyFolder,
|
|
||||||
updateAnyGrpcRequest,
|
|
||||||
updateAnyHttpRequest,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showMainContextMenu, setShowMainContextMenu] = useState<{
|
const [showMainContextMenu, setShowMainContextMenu] = useState<{
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
import {
|
||||||
|
type Folder,
|
||||||
|
foldersAtom,
|
||||||
|
type GrpcRequest,
|
||||||
|
type HttpRequest,
|
||||||
|
type WebsocketRequest,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
|
|
||||||
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
|
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { foldersAtom } from '../../hooks/useFolders';
|
import { allRequestsAtom } from '../../hooks/useAllRequests';
|
||||||
import { requestsAtom } from '../../hooks/useRequests';
|
|
||||||
import { deepEqualAtom } from '../../lib/atoms';
|
import { deepEqualAtom } from '../../lib/atoms';
|
||||||
import { resolvedModelName } from '../../lib/resolvedModelName';
|
import { resolvedModelName } from '../../lib/resolvedModelName';
|
||||||
import type { SidebarTreeNode } from './Sidebar';
|
import type { SidebarTreeNode } from './Sidebar';
|
||||||
@@ -12,7 +17,7 @@ import type { SidebarTreeNode } from './Sidebar';
|
|||||||
export const sidebarSelectedIdAtom = atom<string | null>(null);
|
export const sidebarSelectedIdAtom = atom<string | null>(null);
|
||||||
|
|
||||||
const allPotentialChildrenAtom = atom((get) => {
|
const allPotentialChildrenAtom = atom((get) => {
|
||||||
const requests = get(requestsAtom);
|
const requests = get(allRequestsAtom);
|
||||||
const folders = get(foldersAtom);
|
const folders = get(foldersAtom);
|
||||||
return [...requests, ...folders].map((v) => ({
|
return [...requests, ...folders].map((v) => ({
|
||||||
id: v.id,
|
id: v.id,
|
||||||
|
|||||||
@@ -4,26 +4,22 @@ import type {
|
|||||||
HttpResponse,
|
HttpResponse,
|
||||||
WebsocketConnection,
|
WebsocketConnection,
|
||||||
} from '@yaakapp-internal/models';
|
} from '@yaakapp-internal/models';
|
||||||
|
import { foldersAtom, patchModelById } from '@yaakapp-internal/models';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
|
||||||
import { activeRequestAtom } from '../../hooks/useActiveRequest';
|
import { activeRequestAtom } from '../../hooks/useActiveRequest';
|
||||||
import { foldersAtom } from '../../hooks/useFolders';
|
import {allRequestsAtom} from "../../hooks/useAllRequests";
|
||||||
import { requestsAtom } from '../../hooks/useRequests';
|
|
||||||
import { useScrollIntoView } from '../../hooks/useScrollIntoView';
|
import { useScrollIntoView } from '../../hooks/useScrollIntoView';
|
||||||
import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed';
|
import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed';
|
||||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
|
||||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
|
||||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
|
||||||
import { jotaiStore } from '../../lib/jotai';
|
import { jotaiStore } from '../../lib/jotai';
|
||||||
import { HttpMethodTag } from '../core/HttpMethodTag';
|
import { HttpMethodTag } from '../core/HttpMethodTag';
|
||||||
|
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
import { LoadingIcon } from '../core/LoadingIcon';
|
import { LoadingIcon } from '../core/LoadingIcon';
|
||||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
|
||||||
import type { SidebarTreeNode } from './Sidebar';
|
import type { SidebarTreeNode } from './Sidebar';
|
||||||
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
||||||
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
||||||
@@ -110,8 +106,6 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
|
|
||||||
connectDrag(connectDrop(ref));
|
connectDrag(connectDrop(ref));
|
||||||
|
|
||||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
|
||||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
|
|
||||||
const [selected, setSelected] = useState<boolean>(
|
const [selected, setSelected] = useState<boolean>(
|
||||||
@@ -137,26 +131,12 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
|
|
||||||
const handleSubmitNameEdit = useCallback(
|
const handleSubmitNameEdit = useCallback(
|
||||||
async (el: HTMLInputElement) => {
|
async (el: HTMLInputElement) => {
|
||||||
if (itemModel === 'http_request') {
|
await patchModelById(itemModel, itemId, { name: el.value });
|
||||||
await updateHttpRequest.mutateAsync({
|
|
||||||
id: itemId,
|
|
||||||
update: (r) => ({ ...r, name: el.value }),
|
|
||||||
});
|
|
||||||
} else if (itemModel === 'grpc_request') {
|
|
||||||
await updateGrpcRequest.mutateAsync({
|
|
||||||
id: itemId,
|
|
||||||
update: (r) => ({ ...r, name: el.value }),
|
|
||||||
});
|
|
||||||
} else if (itemModel === 'websocket_request') {
|
|
||||||
const request = getWebsocketRequest(itemId);
|
|
||||||
if (request == null) return;
|
|
||||||
await upsertWebsocketRequest.mutateAsync({ ...request, name: el.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slight delay for the model to propagate to the local store
|
// Slight delay for the model to propagate to the local store
|
||||||
setTimeout(() => setEditing(false));
|
setTimeout(() => setEditing(false));
|
||||||
},
|
},
|
||||||
[itemId, itemModel, updateGrpcRequest, updateHttpRequest],
|
[itemId, itemModel],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
||||||
@@ -199,8 +179,11 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(async () => {
|
const handleSelect = useCallback(async () => {
|
||||||
if (itemModel === 'folder') toggleCollapsed();
|
if (itemModel === 'folder') {
|
||||||
else onSelect(itemId);
|
toggleCollapsed();
|
||||||
|
} else {
|
||||||
|
onSelect(itemId);
|
||||||
|
}
|
||||||
}, [itemModel, toggleCollapsed, onSelect, itemId]);
|
}, [itemModel, toggleCollapsed, onSelect, itemId]);
|
||||||
const [showContextMenu, setShowContextMenu] = useState<{
|
const [showContextMenu, setShowContextMenu] = useState<{
|
||||||
x: number;
|
x: number;
|
||||||
@@ -220,7 +203,7 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
if (itemModel === 'folder') {
|
if (itemModel === 'folder') {
|
||||||
return get(foldersAtom).find((v) => v.id === itemId);
|
return get(foldersAtom).find((v) => v.id === itemId);
|
||||||
} else {
|
} else {
|
||||||
return get(requestsAtom).find((v) => v.id === itemId);
|
return get(allRequestsAtom).find((v) => v.id === itemId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [itemId, itemModel]);
|
}, [itemId, itemModel]);
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
|
import {
|
||||||
|
deleteModelById,
|
||||||
|
duplicateModelById,
|
||||||
|
getModel,
|
||||||
|
workspacesAtom,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { duplicateWebsocketRequest } from '../../commands/duplicateWebsocketRequest';
|
|
||||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
|
||||||
import { useDeleteFolder } from '../../hooks/useDeleteFolder';
|
|
||||||
import { useDuplicateFolder } from '../../hooks/useDuplicateFolder';
|
|
||||||
import { useDuplicateGrpcRequest } from '../../hooks/useDuplicateGrpcRequest';
|
|
||||||
import { useDuplicateHttpRequest } from '../../hooks/useDuplicateHttpRequest';
|
|
||||||
import { useHttpRequestActions } from '../../hooks/useHttpRequestActions';
|
import { useHttpRequestActions } from '../../hooks/useHttpRequestActions';
|
||||||
import { getHttpRequest } from '../../hooks/useHttpRequests';
|
|
||||||
import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace';
|
import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace';
|
||||||
import { useRenameRequest } from '../../hooks/useRenameRequest';
|
|
||||||
import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest';
|
import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest';
|
||||||
import { useSendManyRequests } from '../../hooks/useSendManyRequests';
|
import { useSendManyRequests } from '../../hooks/useSendManyRequests';
|
||||||
import { useWorkspaces } from '../../hooks/useWorkspaces';
|
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||||
|
import { duplicateRequestAndNavigate } from '../../lib/deleteRequestAndNavigate';
|
||||||
|
|
||||||
import { showDialog } from '../../lib/dialog';
|
import { showDialog } from '../../lib/dialog';
|
||||||
|
import { renameModelWithPrompt } from '../../lib/renameModelWithPrompt';
|
||||||
import type { DropdownItem } from '../core/Dropdown';
|
import type { DropdownItem } from '../core/Dropdown';
|
||||||
import { ContextMenu } from '../core/Dropdown';
|
import { ContextMenu } from '../core/Dropdown';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
@@ -29,15 +30,9 @@ interface Props {
|
|||||||
|
|
||||||
export function SidebarItemContextMenu({ child, show, close }: Props) {
|
export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||||
const sendManyRequests = useSendManyRequests();
|
const sendManyRequests = useSendManyRequests();
|
||||||
const duplicateFolder = useDuplicateFolder(child.id);
|
|
||||||
const deleteFolder = useDeleteFolder(child.id);
|
|
||||||
const httpRequestActions = useHttpRequestActions();
|
const httpRequestActions = useHttpRequestActions();
|
||||||
const sendRequest = useSendAnyHttpRequest();
|
const sendRequest = useSendAnyHttpRequest();
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const deleteRequest = useDeleteAnyRequest();
|
|
||||||
const renameRequest = useRenameRequest(child.id);
|
|
||||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: child.id, navigateAfter: true });
|
|
||||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: child.id, navigateAfter: true });
|
|
||||||
const moveToWorkspace = useMoveToWorkspace(child.id);
|
const moveToWorkspace = useMoveToWorkspace(child.id);
|
||||||
const createDropdownItems = useCreateDropdownItems({
|
const createDropdownItems = useCreateDropdownItems({
|
||||||
folderId: child.model === 'folder' ? child.id : null,
|
folderId: child.model === 'folder' ? child.id : null,
|
||||||
@@ -65,13 +60,15 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
|||||||
{
|
{
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
leftSlot: <Icon icon="copy" />,
|
leftSlot: <Icon icon="copy" />,
|
||||||
onSelect: () => duplicateFolder.mutate(),
|
onSelect: () => duplicateModelById(child.model, child.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
onSelect: () => deleteFolder.mutate(),
|
onSelect: async () => {
|
||||||
|
await deleteModelWithConfirm(getModel(child.model, child.id));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
...createDropdownItems,
|
...createDropdownItems,
|
||||||
@@ -92,7 +89,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
const request = getHttpRequest(child.id);
|
const request = getModel('http_request', child.id);
|
||||||
if (request != null) await a.call(request);
|
if (request != null) await a.call(request);
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
@@ -104,23 +101,25 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
|||||||
{
|
{
|
||||||
label: 'Rename',
|
label: 'Rename',
|
||||||
leftSlot: <Icon icon="pencil" />,
|
leftSlot: <Icon icon="pencil" />,
|
||||||
onSelect: renameRequest.mutate,
|
onSelect: async () => {
|
||||||
|
const request = getModel(
|
||||||
|
['http_request', 'grpc_request', 'websocket_request'],
|
||||||
|
child.id,
|
||||||
|
);
|
||||||
|
await renameModelWithPrompt(request);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
hotKeyAction: 'http_request.duplicate',
|
hotKeyAction: 'http_request.duplicate',
|
||||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||||
leftSlot: <Icon icon="copy" />,
|
leftSlot: <Icon icon="copy" />,
|
||||||
onSelect: () => {
|
onSelect: async () => {
|
||||||
if (child.model === 'http_request') {
|
const request = getModel(
|
||||||
duplicateHttpRequest.mutate();
|
['http_request', 'grpc_request', 'websocket_request'],
|
||||||
} else if (child.model === 'grpc_request') {
|
child.id,
|
||||||
duplicateGrpcRequest.mutate();
|
);
|
||||||
} else if (child.model === 'websocket_request') {
|
await duplicateRequestAndNavigate(request);
|
||||||
duplicateWebsocketRequest.mutate(child.id);
|
|
||||||
} else {
|
|
||||||
throw new Error('Cannot duplicate invalid model: ' + child.model);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -132,10 +131,10 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
|||||||
{
|
{
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
hotKeyAction: 'http_request.delete',
|
hotKeyAction: 'sidebar.delete_selected_item',
|
||||||
hotKeyLabelOnly: true,
|
hotKeyLabelOnly: true,
|
||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
onSelect: () => deleteRequest.mutate(child.id),
|
onSelect: async () => deleteModelById(child.model, child.id),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -144,14 +143,8 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
|||||||
child.id,
|
child.id,
|
||||||
child.model,
|
child.model,
|
||||||
createDropdownItems,
|
createDropdownItems,
|
||||||
deleteFolder,
|
|
||||||
deleteRequest,
|
|
||||||
duplicateFolder,
|
|
||||||
duplicateGrpcRequest,
|
|
||||||
duplicateHttpRequest,
|
|
||||||
httpRequestActions,
|
httpRequestActions,
|
||||||
moveToWorkspace.mutate,
|
moveToWorkspace.mutate,
|
||||||
renameRequest.mutate,
|
|
||||||
sendManyRequests,
|
sendManyRequests,
|
||||||
sendRequest,
|
sendRequest,
|
||||||
workspaces.length,
|
workspaces.length,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user