mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-16 12:47:46 +01:00
Compare commits
4 Commits
v2024.11.2
...
v2024.11.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cd08499aa | ||
|
|
c652df82a3 | ||
|
|
c8342fb0a9 | ||
|
|
d0b59a0fb4 |
File diff suppressed because it is too large
Load Diff
@@ -509,7 +509,7 @@ pub async fn get_grpc_connection<R: Runtime>(
|
||||
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
||||
}
|
||||
|
||||
pub async fn list_grpc_connections<R: Runtime>(
|
||||
pub async fn list_grpc_connections_for_workspace<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<GrpcConnection>> {
|
||||
@@ -573,6 +573,16 @@ pub async fn delete_all_grpc_connections<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_grpc_connections_for_workspace<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
) -> Result<()> {
|
||||
for r in list_grpc_connections_for_workspace(window, workspace_id).await? {
|
||||
delete_grpc_connection(window, &r.id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_grpc_event<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
event: &GrpcEvent,
|
||||
@@ -1433,7 +1443,17 @@ pub async fn delete_all_http_responses_for_request<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_http_responses<R: Runtime>(
|
||||
pub async fn delete_all_http_responses_for_workspace<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
) -> Result<()> {
|
||||
for r in list_http_responses_for_workspace(window, workspace_id, None).await? {
|
||||
delete_http_response(window, &r.id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_http_responses_for_workspace<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
workspace_id: &str,
|
||||
limit: Option<i64>,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
|
||||
import type {
|
||||
TemplateFunction,
|
||||
TemplateFunctionArg,
|
||||
@@ -12,6 +13,7 @@ import classNames from 'classnames';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
||||
import { useFolders } from '../hooks/useFolders';
|
||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
||||
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
|
||||
@@ -253,6 +255,7 @@ function HttpRequestArg({
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const folders = useFolders();
|
||||
const httpRequests = useHttpRequests();
|
||||
const activeRequest = useActiveRequest();
|
||||
return (
|
||||
@@ -262,15 +265,35 @@ function HttpRequestArg({
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
options={[
|
||||
...httpRequests.map((r) => ({
|
||||
label: fallbackRequestName(r) + (activeRequest?.id === r.id ? ' (current)' : ''),
|
||||
value: r.id,
|
||||
})),
|
||||
...httpRequests.map((r) => {
|
||||
return {
|
||||
label: buildRequestBreadcrumbs(r, folders).join(' / ') + (r.id == activeRequest?.id ? ' (current)' : ''),
|
||||
value: r.id,
|
||||
};
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function buildRequestBreadcrumbs(request: HttpRequest, folders: Folder[]): string[] {
|
||||
const ancestors: (HttpRequest | Folder)[] = [request];
|
||||
|
||||
const next = () => {
|
||||
const latest = ancestors[0];
|
||||
if (latest == null) return [];
|
||||
|
||||
const parent = folders.find((f) => f.id === latest.folderId);
|
||||
if (parent == null) return;
|
||||
|
||||
ancestors.unshift(parent);
|
||||
next();
|
||||
};
|
||||
next();
|
||||
|
||||
return ancestors.map((a) => (a.model === 'folder' ? a.name : fallbackRequestName(a)));
|
||||
}
|
||||
|
||||
function CheckboxArg({
|
||||
arg,
|
||||
onChange,
|
||||
|
||||
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
@@ -36,6 +37,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const settings = useSettings();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
const deleteSendHistory = useDeleteSendHistory();
|
||||
|
||||
const { workspaceItems, extraItems } = useMemo<{
|
||||
workspaceItems: RadioDropdownItem[];
|
||||
@@ -70,9 +72,15 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
updateWorkspace.mutate({ name });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete-responses',
|
||||
label: 'Clear Send History',
|
||||
leftSlot: <Icon icon="history" />,
|
||||
onSelect: deleteSendHistory.mutate,
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
label: 'Delete Workspace',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteWorkspace.mutate,
|
||||
variant: 'danger',
|
||||
@@ -90,7 +98,8 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
}, [
|
||||
activeWorkspace?.name,
|
||||
activeWorkspaceId,
|
||||
createWorkspace,
|
||||
createWorkspace.mutate,
|
||||
deleteSendHistory.mutate,
|
||||
deleteWorkspace.mutate,
|
||||
prompt,
|
||||
updateWorkspace,
|
||||
|
||||
@@ -364,7 +364,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
changes: cm.current.view.state.changes({
|
||||
from: 0,
|
||||
to: currentDoc.length,
|
||||
insert: currentDoc,
|
||||
insert: defaultValue,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const icons = {
|
||||
chevron_down: lucide.ChevronDownIcon,
|
||||
chevron_right: lucide.ChevronRightIcon,
|
||||
circle_alert: lucide.CircleAlertIcon,
|
||||
cloud: lucide.CloudIcon,
|
||||
clock: lucide.ClockIcon,
|
||||
code: lucide.CodeIcon,
|
||||
cookie: lucide.CookieIcon,
|
||||
copy: lucide.CopyIcon,
|
||||
@@ -48,6 +48,7 @@ const icons = {
|
||||
grip_vertical: lucide.GripVerticalIcon,
|
||||
hand: lucide.HandIcon,
|
||||
help: lucide.CircleHelpIcon,
|
||||
history: lucide.HistoryIcon,
|
||||
house: lucide.HomeIcon,
|
||||
info: lucide.InfoIcon,
|
||||
keyboard: lucide.KeyboardIcon,
|
||||
|
||||
@@ -65,7 +65,14 @@ function ActualEventStreamViewer({ response }: Props) {
|
||||
<Separator />
|
||||
</div>
|
||||
<div className="pl-2 overflow-y-auto">
|
||||
<div className="mb-2 select-text cursor-text font-semibold">Message Received</div>
|
||||
<HStack space={1.5} className="mb-2 select-text cursor-text font-semibold">
|
||||
<EventLabels
|
||||
className="text-sm"
|
||||
event={activeEvent}
|
||||
index={activeEventIndex ?? 0}
|
||||
/>
|
||||
Message Received
|
||||
</HStack>
|
||||
{!showLarge && activeEvent.data.length > 1000 * 1000 ? (
|
||||
<VStack space={2} className="italic text-text-subtlest">
|
||||
Message previews larger than 1MB are hidden
|
||||
@@ -148,6 +155,7 @@ function EventStreamEventsVirtual({
|
||||
<EventStreamEvent
|
||||
event={event}
|
||||
isActive={virtualItem.index === activeEventIndex}
|
||||
index={virtualItem.index}
|
||||
onClick={() => {
|
||||
if (virtualItem.index === activeEventIndex) setActiveEventIndex(null);
|
||||
else setActiveEventIndex(virtualItem.index);
|
||||
@@ -166,11 +174,13 @@ function EventStreamEvent({
|
||||
isActive,
|
||||
event,
|
||||
className,
|
||||
index,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
isActive: boolean;
|
||||
event: ServerSentEvent;
|
||||
className?: string;
|
||||
index: number;
|
||||
}) {
|
||||
return (
|
||||
<motion.button
|
||||
@@ -186,19 +196,33 @@ function EventStreamEvent({
|
||||
)}
|
||||
>
|
||||
<Icon className={classNames('text-info')} title="Server Message" icon="arrow_big_down_dash" />
|
||||
<HStack space={1.5} className="text-sm">
|
||||
{event.eventType && (
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.eventType}
|
||||
</InlineCode>
|
||||
)}
|
||||
{event.id && (
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.id}
|
||||
</InlineCode>
|
||||
)}
|
||||
</HStack>
|
||||
<EventLabels className="text-sm" event={event} isActive={isActive} index={index} />
|
||||
<div className={classNames('w-full truncate text-xs')}>{event.data.slice(0, 1000)}</div>
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
|
||||
function EventLabels({
|
||||
className,
|
||||
event,
|
||||
index,
|
||||
isActive,
|
||||
}: {
|
||||
event: ServerSentEvent;
|
||||
index: number;
|
||||
className: string;
|
||||
isActive?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<HStack space={1.5} alignItems="center" className={className}>
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.id ?? index}
|
||||
</InlineCode>
|
||||
{event.eventType && (
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.eventType}
|
||||
</InlineCode>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,20 +4,17 @@ import { useDialog } from '../components/DialogContext';
|
||||
import type { AlertProps } from './Alert';
|
||||
import { Alert } from './Alert';
|
||||
|
||||
interface AlertArg {
|
||||
id: string;
|
||||
title: DialogProps['title'];
|
||||
body: AlertProps['body'];
|
||||
size?: DialogProps['size'];
|
||||
}
|
||||
|
||||
export function useAlert() {
|
||||
const dialog = useDialog();
|
||||
return useCallback(
|
||||
({
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
size = 'sm',
|
||||
}: {
|
||||
id: string;
|
||||
title: DialogProps['title'];
|
||||
body: AlertProps['body'];
|
||||
size?: DialogProps['size'];
|
||||
}) =>
|
||||
return useCallback<(a: AlertArg) => void>(
|
||||
({ id, title, body, size = 'sm' }: AlertArg) =>
|
||||
dialog.show({
|
||||
id,
|
||||
title,
|
||||
|
||||
43
src-web/hooks/useDeleteSendHistory.tsx
Normal file
43
src-web/hooks/useDeleteSendHistory.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
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';
|
||||
|
||||
export function useDeleteSendHistory() {
|
||||
const confirm = useConfirm();
|
||||
const alert = useAlert();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const httpResponses = useHttpResponses();
|
||||
const grpcConnections = useGrpcConnections();
|
||||
const labels = [
|
||||
httpResponses.length > 0 ? count('Http Response', httpResponses.length) : null,
|
||||
grpcConnections.length > 0 ? count('Grpc Connection', grpcConnections.length) : null,
|
||||
].filter((l) => l != null);
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['delete_send_history'],
|
||||
mutationFn: async () => {
|
||||
if (labels.length === 0) {
|
||||
alert({
|
||||
id: 'no-responses',
|
||||
title: 'Nothing to Delete',
|
||||
body: 'There are no Http Response or Grpc Connections to delete',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await confirm({
|
||||
id: 'delete-send-history',
|
||||
title: 'Clear Send History',
|
||||
variant: 'delete',
|
||||
description: <>Delete {labels.join(' and ')}?</>,
|
||||
});
|
||||
if (!confirmed) return;
|
||||
await invokeCmd('cmd_delete_send_history', { workspaceId: activeWorkspace?.id ?? 'n/a' });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { emit } from '@tauri-apps/api/event';
|
||||
import type { GrpcConnection, GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import { isResponseLoading } from '../lib/model_util';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
@@ -22,27 +23,27 @@ export function useGrpc(
|
||||
|
||||
const go = useMutation<void, string>({
|
||||
mutationKey: ['grpc_go', conn?.id],
|
||||
mutationFn: async () =>
|
||||
await invokeCmd('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
|
||||
mutationFn: () =>
|
||||
invokeCmd<void>('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
|
||||
onSettled: () => trackEvent('grpc_request', 'send'),
|
||||
});
|
||||
|
||||
const send = useMutation({
|
||||
mutationKey: ['grpc_send', conn?.id],
|
||||
mutationFn: async ({ message }: { message: string }) =>
|
||||
await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }),
|
||||
mutationFn: ({ message }: { message: string }) =>
|
||||
emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }),
|
||||
onSettled: () => trackEvent('grpc_connection', 'send'),
|
||||
});
|
||||
|
||||
const cancel = useMutation({
|
||||
mutationKey: ['grpc_cancel', conn?.id ?? 'n/a'],
|
||||
mutationFn: async () => await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'),
|
||||
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'),
|
||||
onSettled: () => trackEvent('grpc_connection', 'cancel'),
|
||||
});
|
||||
|
||||
const commit = useMutation({
|
||||
mutationKey: ['grpc_commit', conn?.id ?? 'n/a'],
|
||||
mutationFn: async () => await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'),
|
||||
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'),
|
||||
onSettled: () => trackEvent('grpc_connection', 'commit'),
|
||||
});
|
||||
|
||||
@@ -51,11 +52,11 @@ export function useGrpc(
|
||||
const reflect = useQuery<ReflectResponseService[], string>({
|
||||
enabled: req != null,
|
||||
queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, protoFiles],
|
||||
queryFn: async () =>
|
||||
(await minPromiseMillis(
|
||||
queryFn: () =>
|
||||
minPromiseMillis<ReflectResponseService[]>(
|
||||
invokeCmd('cmd_grpc_reflect', { requestId, protoFiles }),
|
||||
300,
|
||||
)) as ReflectResponseService[],
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -63,7 +64,7 @@ export function useGrpc(
|
||||
reflect,
|
||||
cancel,
|
||||
commit,
|
||||
isStreaming: conn != null && conn.elapsed === 0,
|
||||
isStreaming: isResponseLoading(conn),
|
||||
send,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type TauriCmd =
|
||||
| 'cmd_curl_to_request'
|
||||
| 'cmd_delete_all_grpc_connections'
|
||||
| 'cmd_delete_all_http_responses'
|
||||
| 'cmd_delete_send_history'
|
||||
| 'cmd_delete_cookie_jar'
|
||||
| 'cmd_delete_environment'
|
||||
| 'cmd_delete_folder'
|
||||
|
||||
Reference in New Issue
Block a user