Compare commits

...

8 Commits

Author SHA1 Message Date
Gregory Schier
8328d20150 Environments data model 2023-10-22 18:28:56 -07:00
Gregory Schier
afe6a3bf57 Environment data model backend 2023-10-22 16:05:09 -07:00
Gregory Schier
d920632cbd Fix some eslint warnings 2023-10-22 11:02:39 -07:00
Gregory Schier
5c456fd4d5 Add APPLE_TEAM_ID 2023-10-18 14:12:08 -07:00
Gregory Schier
38c247e350 Revert artifacts things 2023-10-18 13:25:35 -07:00
Gregory Schier
0c8f72124a Bump cargo deps 2023-10-18 13:25:20 -07:00
Gregory Schier
80ed6b1525 Bump version 2023-10-18 12:14:38 -07:00
Gregory Schier
4424b3f208 Fix sidebar drag-n-drop 2023-10-18 11:58:58 -07:00
35 changed files with 973 additions and 1580 deletions

View File

@@ -11,7 +11,7 @@ jobs:
include:
- os: macos-12
target: aarch64-apple-darwin
- os: macos-12
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-2022
target: x86_64-pc-windows-msvc
@@ -58,6 +58,7 @@ jobs:
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
with:
tagName: 'v__VERSION__'

5
.sqllsrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "yaak-dev",
"adapter": "sqlite3",
"filename": "src-tauri/db.sqlite"
}

972
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
"@tanstack/react-query": "^4.28.0",
"@tanstack/react-query-devtools": "^4.28.0",
"@tanstack/react-query-persist-client": "^4.28.0",
"@tauri-apps/api": "^1.3.0",
"@tauri-apps/api": "^1.5.1",
"@vitejs/plugin-react": "^3.1.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
@@ -55,7 +55,7 @@
},
"devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tauri-apps/cli": "^1.3.1",
"@tauri-apps/cli": "^1.5.4",
"@types/node": "^18.7.10",
"@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",

1040
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"
cocoa = "0.24.1"
cocoa = "0.25.0"
[dependencies]
serde_json = { version = "1.0", features = ["raw_value"] }
@@ -25,8 +25,6 @@ http = "0.2.8"
reqwest = { version = "0.11.14", features = ["json"] }
tokio = { version = "1.25.0", features = ["sync"] }
futures = "0.3.26"
deno_core = "0.179.0"
deno_ast = { version = "0.25.0", features = ["transpiling"] }
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time", "offline"] }
uuid = "1.3.0"
rand = "0.8.5"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,15 @@
CREATE TABLE environments
(
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'workspace' NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted_at DATETIME,
workspace_id TEXT NOT NULL
REFERENCES workspaces
ON DELETE CASCADE,
name TEXT NOT NULL,
data TEXT NOT NULL
DEFAULT '{}'
);

View File

@@ -1 +0,0 @@
Deno.core.opAsync('op_hello', 'Deno');

View File

@@ -398,6 +398,70 @@
},
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
},
"986763e31599881f287ef378002fc35d8e983af10a30a9aa4ade606dacf83260": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "workspace_id",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "model",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 4,
"type_info": "Datetime"
},
{
"name": "name",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "data!: Json<HashMap<String, JsonValue>>",
"ordinal": 6,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false,
false,
false,
false,
false
],
"parameters": {
"Right": 1
}
},
"query": "\n SELECT id, workspace_id, model, created_at, updated_at, name,\n data AS \"data!: Json<HashMap<String, JsonValue>>\"\n FROM environments\n WHERE workspace_id = ?\n "
},
"ab7294b681f1202ef06aaa26885147ead2db6ac740023793cda1e1c92665d996": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 4
}
},
"query": "\n INSERT INTO environments (\n id,\n workspace_id,\n name,\n data\n )\n VALUES (?, ?, ?, ?)\n "
},
"b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": {
"describe": {
"columns": [],
@@ -731,5 +795,59 @@
}
},
"query": "\n INSERT INTO workspaces (id, name, description)\n VALUES (?, ?, ?)\n "
},
"fb89f653780b3f3ab0dd0bb2af30c8d3945203819cb9df7bdd331df56a6ae690": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "model",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "workspace_id",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 4,
"type_info": "Datetime"
},
{
"name": "name",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "data!: Json<HashMap<String, JsonValue>>",
"ordinal": 6,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false,
false,
false,
false,
false
],
"parameters": {
"Right": 1
}
},
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n data AS \"data!: Json<HashMap<String, JsonValue>>\"\n FROM environments\n WHERE id = ?\n "
}
}

View File

@@ -20,7 +20,7 @@ use reqwest::redirect::Policy;
use serde::Serialize;
use sqlx::migrate::Migrator;
use sqlx::sqlite::SqlitePoolOptions;
use sqlx::types::Json;
use sqlx::types::{Json, JsonValue};
use sqlx::{Pool, Sqlite};
use tauri::regex::Regex;
#[cfg(target_os = "macos")]
@@ -34,7 +34,6 @@ use window_ext::WindowExt;
use crate::models::generate_id;
mod models;
mod runtime;
mod window_ext;
#[derive(serde::Serialize)]
@@ -192,24 +191,6 @@ async fn actually_send_ephemeral_request(
let raw_response = client.execute(sendable_req).await;
let plugin_rel_path = "plugins/plugin.ts";
let plugin_path = match app_handle.path_resolver().resolve_resource(plugin_rel_path) {
Some(p) => p,
None => {
return response_err(
response,
format!("Plugin not found at {}", plugin_rel_path),
&app_handle,
pool,
)
.await;
}
};
if let Err(e) = runtime::run_plugin_sync(plugin_path.to_str().unwrap()) {
return response_err(response, e.to_string(), &app_handle, pool).await;
}
match raw_response {
Ok(v) => {
let mut response = response.clone();
@@ -478,6 +459,29 @@ async fn requests(
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn environments(
workspace_id: &str,
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<models::Environment>, String> {
let pool = &*db_instance.lock().await;
let environments = models::find_environments(workspace_id, pool)
.await
.expect("Failed to find environments");
println!("");
if environments.is_empty() {
println!("CREATING DEFAULT ENVIRONMENT");
let data: HashMap<String, JsonValue> = HashMap::new();
let environment = models::create_environment(workspace_id, "Default", data, pool)
.await
.expect("Failed to create default environment");
Ok(vec![environment])
} else {
Ok(environments)
}
}
#[tauri::command]
async fn get_request(
id: &str,
@@ -603,10 +607,12 @@ fn main() {
})
})
.invoke_handler(tauri::generate_handler![
new_window,
workspaces,
get_request,
environments,
requests,
responses,
new_window,
get_request,
send_request,
send_ephemeral_request,
duplicate_request,
@@ -617,7 +623,6 @@ fn main() {
update_workspace,
update_request,
delete_request,
responses,
get_key_value,
set_key_value,
delete_response,

View File

@@ -18,6 +18,18 @@ pub struct Workspace {
pub description: String,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Environment {
pub id: String,
pub workspace_id: String,
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub name: String,
pub data: Json<HashMap<String, JsonValue>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HttpRequestHeader {
@@ -31,11 +43,11 @@ pub struct HttpRequestHeader {
#[serde(rename_all = "camelCase")]
pub struct HttpRequest {
pub id: String,
pub workspace_id: String,
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub sort_priority: f64,
pub workspace_id: String,
pub name: String,
pub url: String,
pub method: String,
@@ -193,6 +205,74 @@ pub async fn create_workspace(
get_workspace(&id, pool).await
}
pub async fn find_environments(
workspace_id: &str,
pool: &Pool<Sqlite>,
) -> Result<Vec<Environment>, sqlx::Error> {
sqlx::query_as!(
Environment,
r#"
SELECT id, workspace_id, model, created_at, updated_at, name,
data AS "data!: Json<HashMap<String, JsonValue>>"
FROM environments
WHERE workspace_id = ?
"#,
workspace_id,
)
.fetch_all(pool)
.await
}
pub async fn create_environment(
workspace_id: &str,
name: &str,
data: HashMap<String, JsonValue>,
pool: &Pool<Sqlite>,
) -> Result<Environment, sqlx::Error> {
let id = generate_id(Some("en"));
let data_json = Json(data);
let trimmed_name = name.trim();
sqlx::query!(
r#"
INSERT INTO environments (
id,
workspace_id,
name,
data
)
VALUES (?, ?, ?, ?)
"#,
id,
workspace_id,
trimmed_name,
data_json,
)
.execute(pool)
.await?;
get_environment(&id, pool).await
}
pub async fn get_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environment, sqlx::Error> {
sqlx::query_as!(
Environment,
r#"
SELECT
id,
model,
workspace_id,
created_at,
updated_at,
name,
data AS "data!: Json<HashMap<String, JsonValue>>"
FROM environments
WHERE id = ?
"#,
id,
)
.fetch_one(pool)
.await
}
pub async fn duplicate_request(id: &str, pool: &Pool<Sqlite>) -> Result<HttpRequest, sqlx::Error> {
let existing = get_request(id, pool).await?;

View File

@@ -1,16 +0,0 @@
(function (globalThis) {
// Deno.core.print(Object.keys(Deno.core).join('\n'));
function argsToMessage(...args) {
return args.map((arg) => JSON.stringify(arg)).join(' ');
}
globalThis.console = {
log: (...args) => {
Deno.core.print(`[log]: ${argsToMessage(...args)}\n`, false);
},
error: (...args) => {
Deno.core.print(`[err]: ${argsToMessage(...args)}\n`, true);
},
};
})(globalThis);

View File

@@ -1,121 +0,0 @@
use std::rc::Rc;
use deno_ast::{MediaType, ParseParams, SourceTextInfo};
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::{op, Extension, JsRuntime, ModuleCode, ModuleSource, ModuleType, RuntimeOptions};
use futures::executor;
pub fn run_plugin_sync(file_path: &str) -> Result<(), AnyError> {
executor::block_on(run_plugin(file_path))
}
pub async fn run_plugin(file_path: &str) -> Result<(), AnyError> {
let extension = Extension::builder("runtime")
.ops(vec![op_hello::decl()])
.build();
// Initialize a runtime instance
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(TsModuleLoader)),
extensions: vec![extension],
..Default::default()
});
runtime
.execute_script("<runtime>", include_str!("runtime.js"))
.expect("Failed to execute runtime.js");
let current_dir = &std::env::current_dir().expect("Unable to get CWD");
let main_module =
deno_core::resolve_path(file_path, current_dir).expect("Failed to resolve path");
let mod_id = runtime
.load_main_module(&main_module, None)
.await
.expect("Failed to load main module");
let result = runtime.mod_evaluate(mod_id);
runtime
.run_event_loop(false)
.await
.expect("Failed to run event loop");
result.await?
}
#[op]
async fn op_hello(name: String) -> Result<String, AnyError> {
let contents = format!("Hello {} from Rust!", name);
println!("{}", contents);
Ok(contents)
}
struct TsModuleLoader;
impl deno_core::ModuleLoader for TsModuleLoader {
fn resolve(
&self,
specifier: &str,
referrer: &str,
_kind: deno_core::ResolutionKind,
) -> Result<deno_core::ModuleSpecifier, AnyError> {
deno_core::resolve_import(specifier, referrer).map_err(|e| e.into())
}
fn load(
&self,
module_specifier: &deno_core::ModuleSpecifier,
_maybe_referrer: Option<deno_core::ModuleSpecifier>,
_is_dyn_import: bool,
) -> std::pin::Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.clone();
async move {
let path = module_specifier
.to_file_path()
.expect("Failed to convert to file path");
// Determine what the MediaType is (this is done based on the file
// extension) and whether transpiling is required.
let media_type = MediaType::from_path(&path);
let (module_type, should_transpile) = match media_type {
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
(ModuleType::JavaScript, false)
}
MediaType::Jsx => (ModuleType::JavaScript, true),
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx => (ModuleType::JavaScript, true),
MediaType::Json => (ModuleType::Json, false),
_ => panic!("Unknown extension {:?}", path.extension()),
};
// Read the file, transpile if necessary.
let code = std::fs::read_to_string(&path)?;
let code = if should_transpile {
let parsed = deno_ast::parse_module(ParseParams {
specifier: module_specifier.to_string(),
text_info: SourceTextInfo::from_string(code),
media_type,
capture_tokens: false,
scope_analysis: false,
maybe_syntax: None,
})?;
parsed.transpile(&Default::default())?.text
} else {
code
};
// Load and return module.
let module = ModuleSource {
code: ModuleCode::from(code),
module_type,
module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(),
};
Ok(module)
}
.boxed_local()
}
}

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "Yaak",
"version": "2023.0.18"
"version": "2023.0.20"
},
"tauri": {
"windows": [],
@@ -18,8 +18,10 @@
"all": true
},
"protocol": {
"assetScope": ["$APPDATA/responses/*"],
"asset": true
"assetScope": [
"$APPDATA/responses/*"
],
"asset": true
},
"fs": {
"readFile": true,
@@ -51,7 +53,6 @@
"identifier": "co.schier.yaak",
"longDescription": "The best cross-platform visual API client",
"resources": [
"plugins/*",
"migrations/*"
],
"shortDescription": "The best API client",
@@ -76,8 +77,7 @@
"timestampUrl": ""
}
},
"security": {
},
"security": {},
"systemTray": {
"iconAsTemplate": true,
"iconPath": "icons/icon.png"

View File

@@ -242,6 +242,7 @@ type SidebarItemProps = {
useProminentStyles?: boolean;
selected?: boolean;
onSelect: (requestId: string) => void;
draggable?: boolean;
};
const _SidebarItem = forwardRef(function SidebarItem(
@@ -255,8 +256,8 @@ const _SidebarItem = forwardRef(function SidebarItem(
const isActive = activeRequestId === requestId;
const handleSubmitNameEdit = useCallback(
async (el: HTMLInputElement) => {
await updateRequest.mutate((r) => ({ ...r, name: el.value }));
(el: HTMLInputElement) => {
updateRequest.mutate((r) => ({ ...r, name: el.value }));
setEditing(false);
},
[updateRequest],
@@ -273,7 +274,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
switch (e.key) {
case 'Enter':
e.preventDefault();
await handleSubmitNameEdit(e.currentTarget);
handleSubmitNameEdit(e.currentTarget);
break;
case 'Escape':
e.preventDefault();
@@ -288,7 +289,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
handleSubmitNameEdit(e.currentTarget).catch(console.error);
handleSubmitNameEdit(e.currentTarget);
},
[handleSubmitNameEdit],
);
@@ -300,16 +301,13 @@ const _SidebarItem = forwardRef(function SidebarItem(
return (
<li ref={ref} className={classnames(className, 'block group/item px-2 pb-0.5')}>
<button
tabIndex={-1}
color="custom"
// tabIndex={-1} // Will prevent drag-n-drop
onClick={handleSelect}
disabled={editing}
draggable={false} // Item should drag, not the link
onDoubleClick={handleStartEditing}
data-active={isActive}
data-selected={selected}
className={classnames(
// 'outline-none',
'w-full flex items-center text-sm h-xs px-2 rounded-md transition-colors',
editing && 'ring-1 focus-within:ring-focus',
isActive && 'bg-highlight text-gray-800',
@@ -343,6 +341,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
</li>
);
});
const SidebarItem = memo(_SidebarItem);
type DraggableSidebarItemProps = SidebarItemProps & {
@@ -367,7 +366,7 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
const [, connectDrop] = useDrop<DragItem, void>(
{
accept: ItemTypes.REQUEST,
hover: (item, monitor) => {
hover: (_, monitor) => {
if (!ref.current) return;
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
@@ -396,6 +395,7 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
return (
<SidebarItem
ref={ref}
draggable
className={classnames(isDragging && 'opacity-20')}
requestName={requestName}
requestId={requestId}

View File

@@ -83,12 +83,12 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
workspaces.length <= 1
? []
: [
...workspaceItems,
{
type: 'separator',
label: activeWorkspace?.name,
},
];
...workspaceItems,
{
type: 'separator',
label: activeWorkspace?.name,
},
];
return [
...activeWorkspaceItems,

View File

@@ -7,6 +7,8 @@ import { RecentRequestsDropdown } from './RecentRequestsDropdown';
import { RequestActionsDropdown } from './RequestActionsDropdown';
import { SidebarActions } from './SidebarActions';
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
import { Button } from './core/Button';
import { useDialog } from './DialogContext';
interface Props {
className?: string;
@@ -14,6 +16,7 @@ interface Props {
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
const activeRequest = useActiveRequest();
const dialog = useDialog();
return (
<HStack
@@ -24,6 +27,14 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
<SidebarActions />
<WorkspaceActionsDropdown className="pointer-events-auto" />
<Button onClick={() => {
dialog.show({
title: 'Testing',
render: () => <div>These are THE environments</div>
})
}}>
Environments
</Button>
</HStack>
<div className="pointer-events-none">
<RecentRequestsDropdown />

View File

@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { Environment } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function environmentsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['environments', { workspaceId }];
}
export function useEnvironments() {
const workspaceId = useActiveWorkspaceId();
return (
useQuery({
enabled: workspaceId != null,
queryKey: environmentsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('environments', { workspaceId })) as Environment[];
},
}).data ?? []
);
}

View File

@@ -1,3 +1,15 @@
export const BODY_TYPE_NONE = null;
export const BODY_TYPE_GRAPHQL = 'graphql';
export const BODY_TYPE_JSON = 'application/json';
export const BODY_TYPE_XML = 'text/xml';
export const AUTH_TYPE_NONE = null;
export const AUTH_TYPE_BASIC = 'basic';
export const AUTH_TYPE_BEARER = 'bearer';
export type Model = Workspace | HttpRequest | HttpResponse | KeyValue | Environment;
export interface BaseModel {
readonly id: string;
readonly createdAt: string;
@@ -16,25 +28,11 @@ export interface HttpHeader {
enabled?: boolean;
}
export const BODY_TYPE_NONE = null;
export const BODY_TYPE_GRAPHQL = 'graphql';
export const BODY_TYPE_JSON = 'application/json';
export const BODY_TYPE_XML = 'text/xml';
export const AUTH_TYPE_NONE = null;
export const AUTH_TYPE_BASIC = 'basic';
export const AUTH_TYPE_BEARER = 'bearer';
export type Model = Workspace | HttpRequest | HttpResponse | KeyValue;
export function modelsEq(a: Model, b: Model) {
if (a.model === 'key_value' && b.model === 'key_value') {
return a.key === b.key && a.namespace === b.namespace;
}
if ('id' in a && 'id' in b) {
return a.id === b.id;
}
return false;
export interface Environment extends BaseModel {
readonly workspaceId: string;
readonly model: 'environment';
name: string;
data: Record<string, string | number | boolean | null | undefined>;
}
export interface HttpRequest extends BaseModel {
@@ -78,3 +76,13 @@ export interface HttpResponse extends BaseModel {
export function isResponseLoading(response: HttpResponse): boolean {
return !(response.body || response.status || response.error);
}
export function modelsEq(a: Model, b: Model) {
if (a.model === 'key_value' && b.model === 'key_value') {
return a.key === b.key && a.namespace === b.namespace;
}
if ('id' in a && 'id' in b) {
return a.id === b.id;
}
return false;
}