diff --git a/package-lock.json b/package-lock.json
index 71c4c501..8ff7d9f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2597,9 +2597,9 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.59.6",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.6.tgz",
- "integrity": "sha512-g58YTHe4ClRrjJ50GY9fas/0zARJVozY0Hs+hcSBOmwZaeKY+to0/LX8wKnnH/EJiLYcC1sHmE11CAS3ncfZBg==",
+ "version": "5.59.16",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.16.tgz",
+ "integrity": "sha512-crHn+G3ltqb5JG0oUv6q+PMz1m1YkjpASrXTU+sYWW9pLk0t2GybUHNRqYPZWhxgjPaVGC4yp92gSFEJgYEsPw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2618,12 +2618,12 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.59.6",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.6.tgz",
- "integrity": "sha512-sGg2sNKg8cYf6aS1dzDf4weN+Vt9PfUu+0btwerrbtYysdNBbcGD4rPe9jhPgMtpDDlvi4cbLv+j1Qo814Kf+Q==",
+ "version": "5.59.16",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.16.tgz",
+ "integrity": "sha512-MuyWheG47h6ERd4PKQ6V8gDyBu3ThNG22e1fRVwvq6ap3EqsFhyuxCAwhNP/03m/mLg+DAb0upgbPaX6VB+CkQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.59.6"
+ "@tanstack/query-core": "5.59.16"
},
"funding": {
"type": "github",
@@ -13767,7 +13767,7 @@
"@lezer/lr": "^1.3.3",
"@react-hook/resize-observer": "^2.0.2",
"@tailwindcss/container-queries": "^0.1.1",
- "@tanstack/react-query": "^5.55.4",
+ "@tanstack/react-query": "^5.59.16",
"@tanstack/react-virtual": "^3.10.8",
"@tauri-apps/api": "^2.0.1",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx
index 4fa75012..0e7b6d50 100644
--- a/src-web/components/EnvironmentEditDialog.tsx
+++ b/src-web/components/EnvironmentEditDialog.tsx
@@ -45,6 +45,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
const handleCreateEnvironment = async () => {
const e = await createEnvironment.mutateAsync();
+ if (e == null) return;
setSelectedEnvironmentId(e.id);
};
diff --git a/src-web/components/RecentResponsesDropdown.tsx b/src-web/components/RecentResponsesDropdown.tsx
index 4f40e7ec..9879122b 100644
--- a/src-web/components/RecentResponsesDropdown.tsx
+++ b/src-web/components/RecentResponsesDropdown.tsx
@@ -41,7 +41,7 @@ export const RecentResponsesDropdown = function ResponsePane({
},
{
key: 'copy',
- label: 'Copy to Clipboard',
+ label: 'Copy Body',
onSelect: copyResponse.mutate,
leftSlot: ,
hidden: responses.length === 0,
diff --git a/src-web/hooks/useCreateCookieJar.ts b/src-web/hooks/useCreateCookieJar.ts
index 2a1f6879..e1793ced 100644
--- a/src-web/hooks/useCreateCookieJar.ts
+++ b/src-web/hooks/useCreateCookieJar.ts
@@ -1,15 +1,19 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
+import {cookieJarsAtom} from "./useCookieJars";
import { usePrompt } from './usePrompt';
+import {updateModelList} from "./useSyncModelStores";
export function useCreateCookieJar() {
const workspace = useActiveWorkspace();
const prompt = usePrompt();
+ const setCookieJars = useSetAtom(cookieJarsAtom);
- return useMutation({
+ return useMutation({
mutationKey: ['create_cookie_jar'],
mutationFn: async () => {
if (workspace === null) {
@@ -23,8 +27,16 @@ export function useCreateCookieJar() {
label: 'Name',
defaultValue: 'My Jar',
});
+ if (name == null) return null;
+
return invokeCmd('cmd_create_cookie_jar', { workspaceId: workspace.id, name });
},
+ onSuccess: (cookieJar) => {
+ if (cookieJar == null) return;
+
+ // Optimistic update
+ setCookieJars(updateModelList(cookieJar));
+ },
onSettled: () => trackEvent('cookie_jar', 'create'),
});
}
diff --git a/src-web/hooks/useCreateEnvironment.ts b/src-web/hooks/useCreateEnvironment.ts
index f5738334..31b74723 100644
--- a/src-web/hooks/useCreateEnvironment.ts
+++ b/src-web/hooks/useCreateEnvironment.ts
@@ -1,17 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
+import {environmentsAtom} from "./useEnvironments";
import { usePrompt } from './usePrompt';
+import {updateModelList} from "./useSyncModelStores";
export function useCreateEnvironment() {
const [, setActiveEnvironmentId] = useActiveEnvironment();
const prompt = usePrompt();
const workspace = useActiveWorkspace();
+ const setEnvironments = useSetAtom(environmentsAtom);
- return useMutation({
+ return useMutation({
mutationKey: ['create_environment'],
mutationFn: async () => {
const name = await prompt({
@@ -23,6 +27,8 @@ export function useCreateEnvironment() {
defaultValue: 'My Environment',
confirmText: 'Create',
});
+ if (name == null) return null;
+
return invokeCmd('cmd_create_environment', {
name,
variables: [],
@@ -31,7 +37,11 @@ export function useCreateEnvironment() {
},
onSettled: () => trackEvent('environment', 'create'),
onSuccess: async (environment) => {
- if (workspace == null) return;
+ if (environment == null) return;
+
+ // Optimistic update
+ setEnvironments(updateModelList(environment));
+
setActiveEnvironmentId(environment.id);
},
});
diff --git a/src-web/hooks/useCreateFolder.ts b/src-web/hooks/useCreateFolder.ts
index 7b6749c8..3804add4 100644
--- a/src-web/hooks/useCreateFolder.ts
+++ b/src-web/hooks/useCreateFolder.ts
@@ -1,15 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
+import { foldersAtom } from './useFolders';
import { usePrompt } from './usePrompt';
+import { updateModelList } from './useSyncModelStores';
export function useCreateFolder() {
const workspace = useActiveWorkspace();
const prompt = usePrompt();
+ const setFolders = useSetAtom(foldersAtom);
- return useMutation>>({
+ return useMutation<
+ Folder | null,
+ unknown,
+ Partial>
+ >({
mutationKey: ['create_folder'],
mutationFn: async (patch) => {
if (workspace === null) {
@@ -25,14 +33,19 @@ export function useCreateFolder() {
confirmText: 'Create',
placeholder: 'Name',
});
- if (name == null) {
- return;
- }
+ if (name == null) return null;
+
patch.name = name;
}
patch.sortPriority = patch.sortPriority || -Date.now();
- await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
+ return await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
+ },
+ onSuccess: (folder) => {
+ if (folder == null) return;
+
+ // Optimistic update
+ setFolders(updateModelList(folder));
},
onSettled: () => trackEvent('folder', 'create'),
});
diff --git a/src-web/hooks/useCreateGrpcRequest.ts b/src-web/hooks/useCreateGrpcRequest.ts
index fdc177bb..5571601e 100644
--- a/src-web/hooks/useCreateGrpcRequest.ts
+++ b/src-web/hooks/useCreateGrpcRequest.ts
@@ -1,17 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
+import {grpcRequestsAtom} from "./useGrpcRequests";
+import {updateModelList} from "./useSyncModelStores";
export function useCreateGrpcRequest() {
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
+ const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation<
GrpcRequest,
@@ -33,19 +37,17 @@ export function useCreateGrpcRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
- const request = await invokeCmd('cmd_create_grpc_request', {
+ return invokeCmd('cmd_create_grpc_request', {
workspaceId: workspace.id,
name: '',
...patch,
});
-
- // Give some time for the workspace to sync to the local store
- await new Promise((resolve) => setTimeout(resolve, 100));
-
- return request;
},
onSettled: () => trackEvent('grpc_request', 'create'),
onSuccess: async (request) => {
+ // Optimistic update
+ setGrpcRequests(updateModelList(request));
+
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
diff --git a/src-web/hooks/useCreateHttpRequest.ts b/src-web/hooks/useCreateHttpRequest.ts
index 13693895..809fe1c7 100644
--- a/src-web/hooks/useCreateHttpRequest.ts
+++ b/src-web/hooks/useCreateHttpRequest.ts
@@ -1,17 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai/index';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
+import { httpRequestsAtom } from './useHttpRequests';
+import { updateModelList } from './useSyncModelStores';
export function useCreateHttpRequest() {
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
+ const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation>({
mutationKey: ['create_http_request'],
@@ -29,17 +33,15 @@ export function useCreateHttpRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
- const request = await invokeCmd('cmd_create_http_request', {
+ return invokeCmd('cmd_create_http_request', {
request: { workspaceId: workspace.id, ...patch },
});
-
- // Give some time for the workspace to sync to the local store
- await new Promise((resolve) => setTimeout(resolve, 100));
-
- return request;
},
onSettled: () => trackEvent('http_request', 'create'),
onSuccess: async (request) => {
+ // Optimistic update
+ setHttpRequests(updateModelList(request));
+
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
diff --git a/src-web/hooks/useCreateWorkspace.ts b/src-web/hooks/useCreateWorkspace.ts
index 7b8c8179..456f6f8a 100644
--- a/src-web/hooks/useCreateWorkspace.ts
+++ b/src-web/hooks/useCreateWorkspace.ts
@@ -1,13 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai/index';
import { invokeCmd } from '../lib/tauri';
import { useAppRoutes } from './useAppRoutes';
import { usePrompt } from './usePrompt';
+import {updateModelList} from "./useSyncModelStores";
+import { workspacesAtom } from './useWorkspaces';
export function useCreateWorkspace() {
const routes = useAppRoutes();
const prompt = usePrompt();
- return useMutation({
+ const setWorkspaces = useSetAtom(workspacesAtom);
+
+ return useMutation({
mutationKey: ['create_workspace'],
mutationFn: async () => {
const name = await prompt({
@@ -18,14 +23,17 @@ export function useCreateWorkspace() {
placeholder: 'My Workspace',
confirmText: 'Create',
});
- const workspace = await invokeCmd('cmd_create_workspace', { name });
-
- // Give some time for the workspace to sync to the local store
- await new Promise((resolve) => setTimeout(resolve, 100));
-
- return workspace;
+ if (name == null) {
+ return null;
+ }
+ return invokeCmd('cmd_create_workspace', { name });
},
onSuccess: async (workspace) => {
+ if (workspace == null) return;
+
+ // Optimistic update
+ setWorkspaces(updateModelList(workspace));
+
routes.navigate('workspace', { workspaceId: workspace.id });
},
});
diff --git a/src-web/hooks/useDeleteAnyGrpcRequest.tsx b/src-web/hooks/useDeleteAnyGrpcRequest.tsx
index 68d5872e..46dbef1c 100644
--- a/src-web/hooks/useDeleteAnyGrpcRequest.tsx
+++ b/src-web/hooks/useDeleteAnyGrpcRequest.tsx
@@ -1,14 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
+import {grpcRequestsAtom} from "./useGrpcRequests";
+import {removeModelById} from "./useSyncModelStores";
export function useDeleteAnyGrpcRequest() {
const confirm = useConfirm();
+ const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation({
mutationKey: ['delete_any_grpc_request'],
@@ -29,6 +33,12 @@ export function useDeleteAnyGrpcRequest() {
if (!confirmed) return null;
return invokeCmd('cmd_delete_grpc_request', { requestId: id });
},
+ onSuccess: (request) => {
+ if (request == null) return;
+
+ // Optimistic update
+ setGrpcRequests(removeModelById(request));
+ },
onSettled: () => trackEvent('grpc_request', 'delete'),
});
}
diff --git a/src-web/hooks/useDeleteAnyHttpRequest.tsx b/src-web/hooks/useDeleteAnyHttpRequest.tsx
index bdeb358a..1d37de54 100644
--- a/src-web/hooks/useDeleteAnyHttpRequest.tsx
+++ b/src-web/hooks/useDeleteAnyHttpRequest.tsx
@@ -1,14 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
+import { httpRequestsAtom } from './useHttpRequests';
+import { removeModelById } from './useSyncModelStores';
export function useDeleteAnyHttpRequest() {
const confirm = useConfirm();
+ const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation({
mutationKey: ['delete_any_http_request'],
@@ -27,7 +31,13 @@ export function useDeleteAnyHttpRequest() {
),
});
if (!confirmed) return null;
- return invokeCmd('cmd_delete_http_request', { requestId: id });
+ return invokeCmd('cmd_delete_http_request', { requestId: id });
+ },
+ onSuccess: (request) => {
+ if (request == null) return;
+
+ // Optimistic update
+ setHttpRequests(removeModelById(request));
},
onSettled: () => trackEvent('http_request', 'delete'),
});
diff --git a/src-web/hooks/useDeleteCookieJar.tsx b/src-web/hooks/useDeleteCookieJar.tsx
index 31ad3a42..45ad397f 100644
--- a/src-web/hooks/useDeleteCookieJar.tsx
+++ b/src-web/hooks/useDeleteCookieJar.tsx
@@ -1,12 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
+import {cookieJarsAtom} from "./useCookieJars";
+import {removeModelById} from "./useSyncModelStores";
export function useDeleteCookieJar(cookieJar: CookieJar | null) {
const confirm = useConfirm();
+ const setCookieJars = useSetAtom(cookieJarsAtom);
return useMutation({
mutationKey: ['delete_cookie_jar', cookieJar?.id],
@@ -25,5 +29,10 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) {
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
},
onSettled: () => trackEvent('cookie_jar', 'delete'),
+ onSuccess: (cookieJar) => {
+ if (cookieJar == null) return;
+
+ setCookieJars(removeModelById(cookieJar));
+ }
});
}
diff --git a/src-web/hooks/useDeleteEnvironment.tsx b/src-web/hooks/useDeleteEnvironment.tsx
index 0f5213eb..ea32e637 100644
--- a/src-web/hooks/useDeleteEnvironment.tsx
+++ b/src-web/hooks/useDeleteEnvironment.tsx
@@ -1,12 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
+import {environmentsAtom} from "./useEnvironments";
+import {removeModelById} from "./useSyncModelStores";
export function useDeleteEnvironment(environment: Environment | null) {
const confirm = useConfirm();
+ const setEnvironments = useSetAtom(environmentsAtom);
return useMutation({
mutationKey: ['delete_environment', environment?.id],
@@ -25,5 +29,10 @@ export function useDeleteEnvironment(environment: Environment | null) {
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
},
onSettled: () => trackEvent('environment', 'delete'),
+ onSuccess: (environment) => {
+ if (environment == null) return;
+
+ setEnvironments(removeModelById(environment));
+ }
});
}
diff --git a/src-web/hooks/useDeleteFolder.tsx b/src-web/hooks/useDeleteFolder.tsx
index a3b6be4f..550a4dbb 100644
--- a/src-web/hooks/useDeleteFolder.tsx
+++ b/src-web/hooks/useDeleteFolder.tsx
@@ -1,13 +1,17 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
+import { foldersAtom } from './useFolders';
+import { removeModelById } from './useSyncModelStores';
export function useDeleteFolder(id: string | null) {
const confirm = useConfirm();
+ const setFolders = useSetAtom(foldersAtom);
return useMutation({
mutationKey: ['delete_folder', id],
@@ -27,5 +31,10 @@ export function useDeleteFolder(id: string | null) {
return invokeCmd('cmd_delete_folder', { folderId: id });
},
onSettled: () => trackEvent('folder', 'delete'),
+ onSuccess: (folder) => {
+ if (folder == null) return;
+
+ setFolders(removeModelById(folder));
+ },
});
}
diff --git a/src-web/hooks/useDeleteGrpcConnection.ts b/src-web/hooks/useDeleteGrpcConnection.ts
index b191357c..7145cc7f 100644
--- a/src-web/hooks/useDeleteGrpcConnection.ts
+++ b/src-web/hooks/useDeleteGrpcConnection.ts
@@ -1,14 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcConnection } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
+import {grpcConnectionsAtom} from "./useGrpcConnections";
+import {removeModelById} from "./useSyncModelStores";
export function useDeleteGrpcConnection(id: string | null) {
+ const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
return useMutation({
mutationKey: ['delete_grpc_connection', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_grpc_connection', { id: id });
},
onSettled: () => trackEvent('grpc_connection', 'delete'),
+ onSuccess: (connection) => {
+ if (connection == null) return;
+
+ setGrpcConnections(removeModelById(connection));
+ }
});
}
diff --git a/src-web/hooks/useDeleteGrpcConnections.ts b/src-web/hooks/useDeleteGrpcConnections.ts
index 173e734e..b12be4f2 100644
--- a/src-web/hooks/useDeleteGrpcConnections.ts
+++ b/src-web/hooks/useDeleteGrpcConnections.ts
@@ -1,8 +1,11 @@
import { useMutation } from '@tanstack/react-query';
+import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
+import { grpcConnectionsAtom } from './useGrpcConnections';
export function useDeleteGrpcConnections(requestId?: string) {
+ const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
return useMutation({
mutationKey: ['delete_grpc_connections', requestId],
mutationFn: async () => {
@@ -10,5 +13,8 @@ export function useDeleteGrpcConnections(requestId?: string) {
await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
},
onSettled: () => trackEvent('grpc_connection', 'delete_many'),
+ onSuccess: () => {
+ setGrpcConnections((all) => all.filter((r) => r.requestId !== requestId));
+ },
});
}
diff --git a/src-web/hooks/useDeleteHttpResponse.ts b/src-web/hooks/useDeleteHttpResponse.ts
index 4fd1617a..5d7fe641 100644
--- a/src-web/hooks/useDeleteHttpResponse.ts
+++ b/src-web/hooks/useDeleteHttpResponse.ts
@@ -1,14 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpResponse } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
+import {httpResponsesAtom} from "./useHttpResponses";
+import {removeModelById} from "./useSyncModelStores";
export function useDeleteHttpResponse(id: string | null) {
+ const setHttpResponses = useSetAtom(httpResponsesAtom);
return useMutation({
mutationKey: ['delete_http_response', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_http_response', { id: id });
},
onSettled: () => trackEvent('http_response', 'delete'),
+ onSuccess: (response) => {
+ setHttpResponses(removeModelById(response));
+ }
});
}
diff --git a/src-web/hooks/useDeleteHttpResponses.ts b/src-web/hooks/useDeleteHttpResponses.ts
index 728c64d8..362a49db 100644
--- a/src-web/hooks/useDeleteHttpResponses.ts
+++ b/src-web/hooks/useDeleteHttpResponses.ts
@@ -1,14 +1,20 @@
import { useMutation } from '@tanstack/react-query';
+import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
+import { httpResponsesAtom } from './useHttpResponses';
export function useDeleteHttpResponses(requestId?: string) {
+ const setHttpResponses = useSetAtom(httpResponsesAtom);
return useMutation({
mutationKey: ['delete_http_responses', requestId],
mutationFn: async () => {
if (requestId === undefined) return;
await invokeCmd('cmd_delete_all_http_responses', { requestId });
},
+ onSuccess: () => {
+ setHttpResponses((all) => all.filter((r) => r.requestId !== requestId));
+ },
onSettled: () => trackEvent('http_response', 'delete_many'),
});
}
diff --git a/src-web/hooks/useDeleteSendHistory.tsx b/src-web/hooks/useDeleteSendHistory.tsx
index fb46f36c..ec9973c3 100644
--- a/src-web/hooks/useDeleteSendHistory.tsx
+++ b/src-web/hooks/useDeleteSendHistory.tsx
@@ -1,15 +1,17 @@
import { useMutation } from '@tanstack/react-query';
+import { useSetAtom } from 'jotai/index';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { useConfirm } from './useConfirm';
import { useGrpcConnections } from './useGrpcConnections';
-import { useHttpResponses } from './useHttpResponses';
+import { httpResponsesAtom, useHttpResponses } from './useHttpResponses';
export function useDeleteSendHistory() {
const confirm = useConfirm();
const alert = useAlert();
+ const setHttpResponses = useSetAtom(httpResponsesAtom);
const activeWorkspace = useActiveWorkspace();
const httpResponses = useHttpResponses();
const grpcConnections = useGrpcConnections();
@@ -36,8 +38,15 @@ export function useDeleteSendHistory() {
variant: 'delete',
description: <>Delete {labels.join(' and ')}?>,
});
- if (!confirmed) return;
+ if (!confirmed) return false;
+
await invokeCmd('cmd_delete_send_history', { workspaceId: activeWorkspace?.id ?? 'n/a' });
+ return true;
+ },
+ onSuccess: async (confirmed) => {
+ if (!confirmed) return;
+
+ setHttpResponses((all) => all.filter((r) => r.workspaceId !== activeWorkspace?.id));
},
});
}
diff --git a/src-web/hooks/useDeleteWorkspace.tsx b/src-web/hooks/useDeleteWorkspace.tsx
index 468d81f0..701b759a 100644
--- a/src-web/hooks/useDeleteWorkspace.tsx
+++ b/src-web/hooks/useDeleteWorkspace.tsx
@@ -1,16 +1,20 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import { useConfirm } from './useConfirm';
+import {removeModelById} from "./useSyncModelStores";
+import {workspacesAtom} from "./useWorkspaces";
export function useDeleteWorkspace(workspace: Workspace | null) {
const activeWorkspace = useActiveWorkspace();
const routes = useAppRoutes();
const confirm = useConfirm();
+ const setWorkspaces = useSetAtom(workspacesAtom);
return useMutation({
mutationKey: ['delete_workspace', workspace?.id],
@@ -32,6 +36,9 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
onSuccess: async (workspace) => {
if (workspace === null) return;
+ // Optimistic update
+ setWorkspaces(removeModelById(workspace));
+
const { id: workspaceId } = workspace;
if (workspaceId === activeWorkspace?.id) {
routes.navigate('workspaces');
diff --git a/src-web/hooks/useSyncModelStores.ts b/src-web/hooks/useSyncModelStores.ts
index 5cbb0888..1a4e9474 100644
--- a/src-web/hooks/useSyncModelStores.ts
+++ b/src-web/hooks/useSyncModelStores.ts
@@ -60,29 +60,26 @@ export function useSyncModelStores() {
return;
}
- // Mark these models as DESC instead of ASC
- const pushToFront = model.model === 'http_response' || model.model === 'grpc_connection';
-
if (shouldIgnoreModel(model, windowLabel)) return;
if (model.model === 'workspace') {
- setWorkspaces(updateModelList(model, pushToFront));
+ setWorkspaces(updateModelList(model));
} else if (model.model === 'plugin') {
- setPlugins(updateModelList(model, pushToFront));
+ setPlugins(updateModelList(model));
} else if (model.model === 'http_request') {
- setHttpRequests(updateModelList(model, pushToFront));
+ setHttpRequests(updateModelList(model));
} else if (model.model === 'folder') {
- setFolders(updateModelList(model, pushToFront));
+ setFolders(updateModelList(model));
} else if (model.model === 'http_response') {
- setHttpResponses(updateModelList(model, pushToFront));
+ setHttpResponses(updateModelList(model));
} else if (model.model === 'grpc_request') {
- setGrpcRequests(updateModelList(model, pushToFront));
+ setGrpcRequests(updateModelList(model));
} else if (model.model === 'grpc_connection') {
- setGrpcConnections(updateModelList(model, pushToFront));
+ setGrpcConnections(updateModelList(model));
} else if (model.model === 'environment') {
- setEnvironments(updateModelList(model, pushToFront));
+ setEnvironments(updateModelList(model));
} else if (model.model === 'cookie_jar') {
- setCookieJars(updateModelList(model, pushToFront));
+ setCookieJars(updateModelList(model));
} else if (model.model === 'settings') {
setSettings(model);
} else if (queryKey != null) {
@@ -94,7 +91,7 @@ export function useSyncModelStores() {
}
if (Array.isArray(current)) {
- return updateModelList(model, pushToFront)(current);
+ return updateModelList(model)(current);
}
});
}
@@ -107,32 +104,35 @@ export function useSyncModelStores() {
console.log('Delete model', payload);
if (model.model === 'workspace') {
- setWorkspaces(removeById(model));
+ setWorkspaces(removeModelById(model));
} else if (model.model === 'plugin') {
- setPlugins(removeById(model));
+ setPlugins(removeModelById(model));
} else if (model.model === 'http_request') {
- setHttpRequests(removeById(model));
+ setHttpRequests(removeModelById(model));
} else if (model.model === 'http_response') {
- setHttpResponses(removeById(model));
+ setHttpResponses(removeModelById(model));
} else if (model.model === 'folder') {
- setFolders(removeById(model));
+ setFolders(removeModelById(model));
} else if (model.model === 'environment') {
- setEnvironments(removeById(model));
+ setEnvironments(removeModelById(model));
} else if (model.model === 'grpc_request') {
- setGrpcRequests(removeById(model));
+ setGrpcRequests(removeModelById(model));
} else if (model.model === 'grpc_connection') {
- setGrpcConnections(removeById(model));
+ setGrpcConnections(removeModelById(model));
} else if (model.model === 'grpc_event') {
- queryClient.setQueryData(grpcEventsQueryKey(model), removeById(model));
+ queryClient.setQueryData(grpcEventsQueryKey(model), removeModelById(model));
} else if (model.model === 'key_value') {
queryClient.setQueryData(keyValueQueryKey(model), undefined);
} else if (model.model === 'cookie_jar') {
- setCookieJars(removeById(model));
+ setCookieJars(removeModelById(model));
}
});
}
-function updateModelList(model: T, pushToFront: boolean) {
+export function updateModelList(model: T) {
+ // Mark these models as DESC instead of ASC
+ const pushToFront = model.model === 'http_response' || model.model === 'grpc_connection';
+
return (current: T[] | undefined): T[] => {
const index = current?.findIndex((v) => modelsEq(v, model)) ?? -1;
if (index >= 0) {
@@ -143,7 +143,7 @@ function updateModelList(model: T, pushToFront: boolean) {
};
}
-function removeById(model: T) {
+export function removeModelById(model: T) {
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id) ?? [];
}
diff --git a/src-web/hooks/useUpdateAnyFolder.ts b/src-web/hooks/useUpdateAnyFolder.ts
index 50e23403..1abe625c 100644
--- a/src-web/hooks/useUpdateAnyFolder.ts
+++ b/src-web/hooks/useUpdateAnyFolder.ts
@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai/index";
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import {foldersAtom} from "./useFolders";
+import {updateModelList} from "./useSyncModelStores";
export function useUpdateAnyFolder() {
- return useMutation Folder }>({
+ const setFolders = useSetAtom(foldersAtom);
+ return useMutation Folder }>({
mutationKey: ['update_any_folder'],
mutationFn: async ({ id, update }) => {
const folder = await getFolder(id);
@@ -12,7 +16,10 @@ export function useUpdateAnyFolder() {
throw new Error("Can't update a null folder");
}
- await invokeCmd('cmd_update_folder', { folder: update(folder) });
+ return invokeCmd('cmd_update_folder', { folder: update(folder) });
},
+ onSuccess: async (folder) => {
+ setFolders(updateModelList(folder));
+ }
});
}
diff --git a/src-web/hooks/useUpdateAnyGrpcRequest.ts b/src-web/hooks/useUpdateAnyGrpcRequest.ts
index 88fce22e..a6479b56 100644
--- a/src-web/hooks/useUpdateAnyGrpcRequest.ts
+++ b/src-web/hooks/useUpdateAnyGrpcRequest.ts
@@ -1,11 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai/index';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import { grpcRequestsAtom } from './useGrpcRequests';
+import { updateModelList } from './useSyncModelStores';
export function useUpdateAnyGrpcRequest() {
+ const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation<
- void,
+ GrpcRequest,
unknown,
{ id: string; update: Partial | ((r: GrpcRequest) => GrpcRequest) }
>({
@@ -18,7 +22,10 @@ export function useUpdateAnyGrpcRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
- await invokeCmd('cmd_update_grpc_request', { request: patchedRequest });
+ return invokeCmd('cmd_update_grpc_request', { request: patchedRequest });
+ },
+ onSuccess: (request) => {
+ setGrpcRequests(updateModelList(request));
},
});
}
diff --git a/src-web/hooks/useUpdateAnyHttpRequest.ts b/src-web/hooks/useUpdateAnyHttpRequest.ts
index 40987b65..3f4b6f40 100644
--- a/src-web/hooks/useUpdateAnyHttpRequest.ts
+++ b/src-web/hooks/useUpdateAnyHttpRequest.ts
@@ -1,11 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai/index";
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import {httpRequestsAtom} from "./useHttpRequests";
+import {updateModelList} from "./useSyncModelStores";
export function useUpdateAnyHttpRequest() {
+ const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation<
- void,
+ HttpRequest,
unknown,
{ id: string; update: Partial | ((r: HttpRequest) => HttpRequest) }
>({
@@ -18,7 +22,10 @@ export function useUpdateAnyHttpRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
- await invokeCmd('cmd_update_http_request', { request: patchedRequest });
+ return invokeCmd('cmd_update_http_request', { request: patchedRequest });
},
+ onSuccess: async (request) => {
+ setHttpRequests(updateModelList(request));
+ }
});
}
diff --git a/src-web/hooks/useUpdateCookieJar.ts b/src-web/hooks/useUpdateCookieJar.ts
index 05806ab3..c37ebfd8 100644
--- a/src-web/hooks/useUpdateCookieJar.ts
+++ b/src-web/hooks/useUpdateCookieJar.ts
@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai/index';
import { getCookieJar } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import { cookieJarsAtom } from './useCookieJars';
+import { updateModelList } from './useSyncModelStores';
export function useUpdateCookieJar(id: string | null) {
- return useMutation | ((j: CookieJar) => CookieJar)>({
+ const setCookieJars = useSetAtom(cookieJarsAtom);
+ return useMutation | ((j: CookieJar) => CookieJar)>({
mutationKey: ['update_cookie_jar', id],
mutationFn: async (v) => {
const cookieJar = await getCookieJar(id);
@@ -13,7 +17,10 @@ export function useUpdateCookieJar(id: string | null) {
}
const newCookieJar = typeof v === 'function' ? v(cookieJar) : { ...cookieJar, ...v };
- await invokeCmd('cmd_update_cookie_jar', { cookieJar: newCookieJar });
+ return invokeCmd('cmd_update_cookie_jar', { cookieJar: newCookieJar });
+ },
+ onSuccess: (cookieJar) => {
+ setCookieJars(updateModelList(cookieJar));
},
});
}
diff --git a/src-web/hooks/useUpdateEnvironment.ts b/src-web/hooks/useUpdateEnvironment.ts
index 3982fcc3..3ce2981c 100644
--- a/src-web/hooks/useUpdateEnvironment.ts
+++ b/src-web/hooks/useUpdateEnvironment.ts
@@ -1,10 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai/index';
import { getEnvironment } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import { environmentsAtom } from './useEnvironments';
+import {updateModelList} from "./useSyncModelStores";
export function useUpdateEnvironment(id: string | null) {
- return useMutation | ((r: Environment) => Environment)>({
+ const setEnvironments = useSetAtom(environmentsAtom);
+ return useMutation<
+ Environment,
+ unknown,
+ Partial | ((r: Environment) => Environment)
+ >({
mutationKey: ['update_environment', id],
mutationFn: async (v) => {
const environment = await getEnvironment(id);
@@ -13,7 +21,10 @@ export function useUpdateEnvironment(id: string | null) {
}
const newEnvironment = typeof v === 'function' ? v(environment) : { ...environment, ...v };
- await invokeCmd('cmd_update_environment', { environment: newEnvironment });
+ return invokeCmd('cmd_update_environment', { environment: newEnvironment });
+ },
+ onSuccess: async (environment) => {
+ setEnvironments(updateModelList(environment));
},
});
}
diff --git a/src-web/hooks/useUpdateSettings.ts b/src-web/hooks/useUpdateSettings.ts
index 5681267b..f43b6260 100644
--- a/src-web/hooks/useUpdateSettings.ts
+++ b/src-web/hooks/useUpdateSettings.ts
@@ -1,15 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { Settings } from '@yaakapp-internal/models';
+import { useSetAtom } from 'jotai';
import { getSettings } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import { settingsAtom } from './useSettings';
export function useUpdateSettings() {
- return useMutation>({
+ const setSettings = useSetAtom(settingsAtom);
+ return useMutation>({
mutationKey: ['update_settings'],
mutationFn: async (patch) => {
const settings = await getSettings();
const newSettings: Settings = { ...settings, ...patch };
- await invokeCmd('cmd_update_settings', { settings: newSettings });
+ return invokeCmd('cmd_update_settings', { settings: newSettings });
+ },
+ onSuccess: (settings) => {
+ setSettings(settings);
},
});
}
diff --git a/src-web/hooks/useUpdateWorkspace.ts b/src-web/hooks/useUpdateWorkspace.ts
index bd9fb210..de9b3777 100644
--- a/src-web/hooks/useUpdateWorkspace.ts
+++ b/src-web/hooks/useUpdateWorkspace.ts
@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
+import {useSetAtom} from "jotai/index";
import { getWorkspace } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
+import {updateModelList} from "./useSyncModelStores";
+import {workspacesAtom} from "./useWorkspaces";
export function useUpdateWorkspace(id: string | null) {
- return useMutation | ((w: Workspace) => Workspace)>({
+ const setWorkspaces = useSetAtom(workspacesAtom);
+ return useMutation | ((w: Workspace) => Workspace)>({
mutationKey: ['update_workspace', id],
mutationFn: async (v) => {
const workspace = await getWorkspace(id);
@@ -13,7 +17,10 @@ export function useUpdateWorkspace(id: string | null) {
}
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
- await invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
+ return invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
+ },
+ onSuccess: async (workspace) => {
+ setWorkspaces(updateModelList(workspace));
},
});
}
diff --git a/src-web/package.json b/src-web/package.json
index a6efce06..16b60eb7 100644
--- a/src-web/package.json
+++ b/src-web/package.json
@@ -19,7 +19,7 @@
"@lezer/lr": "^1.3.3",
"@react-hook/resize-observer": "^2.0.2",
"@tailwindcss/container-queries": "^0.1.1",
- "@tanstack/react-query": "^5.55.4",
+ "@tanstack/react-query": "^5.59.16",
"@tanstack/react-virtual": "^3.10.8",
"@tauri-apps/api": "^2.0.1",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",