mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 02:11:10 +01:00
Use req/conn/msg models in unary/server
This commit is contained in:
@@ -3,20 +3,23 @@ import { appWindow } from '@tauri-apps/api/window';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
|
||||
import { grpcMessagesQueryKey } from '../hooks/useGrpcMessages';
|
||||
import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests';
|
||||
import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
||||
import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { responsesQueryKey } from '../hooks/useResponses';
|
||||
import { settingsQueryKey } from '../hooks/useSettings';
|
||||
import { useSyncAppearance } from '../hooks/useSyncAppearance';
|
||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
|
||||
import type { Model } from '../lib/models';
|
||||
import { modelsEq } from '../lib/models';
|
||||
import { setPathname } from '../lib/persistPathname';
|
||||
|
||||
@@ -49,7 +52,13 @@ export function GlobalHooks() {
|
||||
payload.model === 'http_request'
|
||||
? httpRequestsQueryKey(payload)
|
||||
: payload.model === 'http_response'
|
||||
? responsesQueryKey(payload)
|
||||
? httpResponsesQueryKey(payload)
|
||||
: payload.model === 'grpc_connection'
|
||||
? grpcConnectionsQueryKey(payload)
|
||||
: payload.model === 'grpc_message'
|
||||
? grpcMessagesQueryKey(payload)
|
||||
: payload.model === 'grpc_request'
|
||||
? grpcRequestsQueryKey(payload)
|
||||
: payload.model === 'workspace'
|
||||
? workspacesQueryKey(payload)
|
||||
: payload.model === 'key_value'
|
||||
@@ -78,7 +87,13 @@ export function GlobalHooks() {
|
||||
payload.model === 'http_request'
|
||||
? httpRequestsQueryKey(payload)
|
||||
: payload.model === 'http_response'
|
||||
? responsesQueryKey(payload)
|
||||
? httpResponsesQueryKey(payload)
|
||||
: payload.model === 'grpc_connection'
|
||||
? grpcConnectionsQueryKey(payload)
|
||||
: payload.model === 'grpc_message'
|
||||
? grpcMessagesQueryKey(payload)
|
||||
: payload.model === 'grpc_request'
|
||||
? grpcRequestsQueryKey(payload)
|
||||
: payload.model === 'workspace'
|
||||
? workspacesQueryKey(payload)
|
||||
: payload.model === 'key_value'
|
||||
@@ -113,11 +128,17 @@ export function GlobalHooks() {
|
||||
if (shouldIgnoreModel(payload)) return;
|
||||
|
||||
if (payload.model === 'workspace') {
|
||||
queryClient.setQueryData<Workspace[]>(workspacesQueryKey(), removeById(payload));
|
||||
queryClient.setQueryData(workspacesQueryKey(), removeById(payload));
|
||||
} else if (payload.model === 'http_request') {
|
||||
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey(payload), removeById(payload));
|
||||
queryClient.setQueryData(httpRequestsQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'http_response') {
|
||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
|
||||
queryClient.setQueryData(httpResponsesQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'grpc_request') {
|
||||
queryClient.setQueryData(grpcRequestsQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'grpc_connection') {
|
||||
queryClient.setQueryData(grpcConnectionsQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'grpc_message') {
|
||||
queryClient.setQueryData(grpcMessagesQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'key_value') {
|
||||
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
||||
} else if (payload.model === 'cookie_jar') {
|
||||
|
||||
@@ -5,12 +5,12 @@ import type { CSSProperties, FormEvent } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useAlert } from '../hooks/useAlert';
|
||||
import type { GrpcMessage } from '../hooks/useGrpc';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||
import { useGrpcMessages } from '../hooks/useGrpcMessages';
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { Editor } from './core/Editor';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -30,9 +30,11 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
const activeRequest = useActiveRequest('grpc_request');
|
||||
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
|
||||
const alert = useAlert();
|
||||
const [activeMessage, setActiveMessage] = useState<GrpcMessage | null>(null);
|
||||
const [resp, setResp] = useState<string>('');
|
||||
const [activeMessageId, setActiveMessageId] = useState<string | null>(null);
|
||||
const grpc = useGrpc(activeRequest?.url ?? null, activeRequest?.id ?? null);
|
||||
const connections = useGrpcConnections(activeRequest?.id ?? null);
|
||||
const activeConnection = connections[0] ?? null;
|
||||
const messages = useGrpcMessages(activeConnection?.id ?? null);
|
||||
|
||||
const activeMethod = useMemo(() => {
|
||||
if (grpc.services == null || activeRequest == null) return null;
|
||||
@@ -61,9 +63,9 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
if (activeMethod.clientStreaming && activeMethod.serverStreaming) {
|
||||
await grpc.bidiStreaming.mutateAsync(activeRequest);
|
||||
} else if (activeMethod.serverStreaming && !activeMethod.clientStreaming) {
|
||||
await grpc.serverStreaming.mutateAsync(activeRequest);
|
||||
await grpc.serverStreaming.mutateAsync(activeRequest.id);
|
||||
} else {
|
||||
setResp(await grpc.unary.mutateAsync(activeRequest));
|
||||
await grpc.unary.mutateAsync(activeRequest.id);
|
||||
}
|
||||
},
|
||||
[activeMethod, activeRequest, alert, grpc.bidiStreaming, grpc.serverStreaming, grpc.unary],
|
||||
@@ -127,8 +129,13 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
setPaneSize(entry.contentRect.width);
|
||||
});
|
||||
|
||||
const activeMessage = useMemo(
|
||||
() => messages.find((m) => m.id === activeMessageId) ?? null,
|
||||
[activeMessageId, messages],
|
||||
);
|
||||
|
||||
if (activeRequest == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -136,7 +143,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
name="grpc_layout"
|
||||
className="p-3 gap-1.5"
|
||||
style={style}
|
||||
leftSlot={() => (
|
||||
firstSlot={() => (
|
||||
<VStack space={2}>
|
||||
<div
|
||||
ref={urlContainerEl}
|
||||
@@ -218,7 +225,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
/>
|
||||
</VStack>
|
||||
)}
|
||||
rightSlot={() =>
|
||||
secondSlot={() =>
|
||||
!grpc.unary.isLoading && (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -231,20 +238,20 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
<Banner color="danger" className="m-2">
|
||||
{grpc.unary.error}
|
||||
</Banner>
|
||||
) : (grpc.messages.value ?? []).length > 0 ? (
|
||||
) : messages.length >= 0 ? (
|
||||
<SplitLayout
|
||||
name="grpc_messages2"
|
||||
name="grpc_messages"
|
||||
minHeightPx={20}
|
||||
defaultRatio={0.25}
|
||||
leftSlot={() => (
|
||||
firstSlot={() => (
|
||||
<div className="overflow-y-auto">
|
||||
{...(grpc.messages.value ?? []).map((m, i) => (
|
||||
{...messages.map((m) => (
|
||||
<HStack
|
||||
key={`${m.timestamp}::${m.message}::${i}`}
|
||||
key={m.id}
|
||||
space={2}
|
||||
onClick={() => {
|
||||
if (m === activeMessage) setActiveMessage(null);
|
||||
else setActiveMessage(m);
|
||||
if (m.id === activeMessageId) setActiveMessageId(null);
|
||||
else setActiveMessageId(m.id);
|
||||
}}
|
||||
alignItems="center"
|
||||
className={classNames(
|
||||
@@ -254,29 +261,25 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
>
|
||||
<Icon
|
||||
className={
|
||||
m.type === 'server'
|
||||
m.isInfo
|
||||
? 'text-gray-600'
|
||||
: m.isServer
|
||||
? 'text-blue-600'
|
||||
: m.type === 'client'
|
||||
? 'text-green-600'
|
||||
: 'text-gray-600'
|
||||
: 'text-green-600'
|
||||
}
|
||||
icon={
|
||||
m.type === 'server'
|
||||
? 'arrowBigDownDash'
|
||||
: m.type === 'client'
|
||||
? 'arrowBigUpDash'
|
||||
: 'info'
|
||||
m.isInfo ? 'info' : m.isServer ? 'arrowBigDownDash' : 'arrowBigUpDash'
|
||||
}
|
||||
/>
|
||||
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div>
|
||||
<div className="text-gray-600 text-2xs">
|
||||
{format(m.timestamp, 'HH:mm:ss')}
|
||||
{format(m.createdAt, 'HH:mm:ss')}
|
||||
</div>
|
||||
</HStack>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
rightSlot={
|
||||
secondSlot={
|
||||
!activeMessage
|
||||
? null
|
||||
: () => (
|
||||
@@ -293,15 +296,15 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : resp ? (
|
||||
<Editor
|
||||
className="bg-gray-50 dark:bg-gray-100"
|
||||
contentType="application/json"
|
||||
defaultValue={resp}
|
||||
readOnly
|
||||
forceUpdateKey={resp}
|
||||
/>
|
||||
) : (
|
||||
// ) : ? (
|
||||
// <Editor
|
||||
// readOnly
|
||||
// className="bg-gray-50 dark:bg-gray-100"
|
||||
// contentType="application/json"
|
||||
// defaultValue={resp.message}
|
||||
// forceUpdateKey={resp.id}
|
||||
// />
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,10 @@ export function HttpRequestLayout({ style }: Props) {
|
||||
name="http_layout"
|
||||
className="p-3 gap-1.5"
|
||||
style={style}
|
||||
leftSlot={({ orientation, style }) => (
|
||||
firstSlot={({ orientation, style }) => (
|
||||
<RequestPane style={style} fullHeight={orientation === 'horizontal'} />
|
||||
)}
|
||||
rightSlot={({ style }) => <ResponsePane style={style} />}
|
||||
secondSlot={({ style }) => <ResponsePane style={style} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createGlobalState } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||
import { useResponseContentType } from '../hooks/useResponseContentType';
|
||||
import { useResponses } from '../hooks/useResponses';
|
||||
import { useHttpResponses } from '../hooks/useHttpResponses';
|
||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
@@ -39,7 +39,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
||||
const activeRequest = useActiveRequest();
|
||||
const latestResponse = useLatestResponse(activeRequest?.id ?? null);
|
||||
const responses = useResponses(activeRequest?.id ?? null);
|
||||
const responses = useHttpResponses(activeRequest?.id ?? null);
|
||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||
: latestResponse ?? null;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useKey, useKeyPressEvent } from 'react-use';
|
||||
|
||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useCreateFolder } from '../hooks/useCreateFolder';
|
||||
@@ -15,7 +14,8 @@ import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
||||
import { useDeleteFolder } from '../hooks/useDeleteFolder';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
||||
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
||||
import { useFolders } from '../hooks/useFolders';
|
||||
import { useGrpcRequests } from '../hooks/useGrpcRequests';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
@@ -29,6 +29,7 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||
@@ -58,14 +59,21 @@ interface TreeNode {
|
||||
export function Sidebar({ className }: Props) {
|
||||
const { hidden } = useSidebarHidden();
|
||||
const sidebarRef = useRef<HTMLLIElement>(null);
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const httpRequests = useHttpRequests();
|
||||
const grpcRequests = useGrpcRequests();
|
||||
const folders = useFolders();
|
||||
const deleteAnyRequest = useDeleteAnyRequest();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const duplicateRequest = useDuplicateRequest({ id: activeRequestId, navigateAfter: true });
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
navigateAfter: true,
|
||||
});
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
navigateAfter: true,
|
||||
});
|
||||
const routes = useAppRoutes();
|
||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
@@ -82,8 +90,12 @@ export function Sidebar({ className }: Props) {
|
||||
namespace: NAMESPACE_NO_SYNC,
|
||||
});
|
||||
|
||||
useHotKey('http_request.duplicate', () => {
|
||||
duplicateRequest.mutate();
|
||||
useHotKey('http_request.duplicate', async () => {
|
||||
if (activeRequest?.model === 'http_request') {
|
||||
await duplicateHttpRequest.mutateAsync();
|
||||
} else {
|
||||
await duplicateGrpcRequest.mutateAsync();
|
||||
}
|
||||
});
|
||||
|
||||
const isCollapsed = useCallback(
|
||||
@@ -146,9 +158,10 @@ export function Sidebar({ className }: Props) {
|
||||
} = {},
|
||||
) => {
|
||||
const { forced, noFocusSidebar } = args;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const id = forced?.id ?? children.find((m) => m.item.id === activeRequestId)?.item.id ?? null;
|
||||
const id =
|
||||
forced?.id ?? children.find((m) => m.item.id === activeRequest?.id)?.item.id ?? null;
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +173,7 @@ export function Sidebar({ className }: Props) {
|
||||
sidebarRef.current?.focus();
|
||||
}
|
||||
},
|
||||
[activeRequestId, treeParentMap],
|
||||
[activeRequest, treeParentMap],
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
@@ -230,7 +243,7 @@ export function Sidebar({ className }: Props) {
|
||||
useKeyPressEvent('Enter', (e) => {
|
||||
if (!hasFocus) return;
|
||||
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||
if (!selected || selected.id === activeRequestId || activeWorkspace == null) {
|
||||
if (!selected || selected.id === activeRequest?.id || activeWorkspace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -541,11 +554,13 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
const createFolder = useCreateFolder();
|
||||
const deleteFolder = useDeleteFolder(itemId);
|
||||
const deleteRequest = useDeleteRequest(itemId);
|
||||
const duplicateRequest = useDuplicateRequest({ id: itemId, navigateAfter: true });
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||
const sendRequest = useSendRequest(itemId);
|
||||
const sendManyRequests = useSendManyRequests();
|
||||
const latestResponse = useLatestResponse(itemId);
|
||||
const updateRequest = useUpdateHttpRequest(itemId);
|
||||
const updateHttpRequest = useUpdateHttpRequest(itemId);
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(itemId);
|
||||
const updateAnyFolder = useUpdateAnyFolder();
|
||||
const prompt = usePrompt();
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
@@ -553,10 +568,15 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
(el: HTMLInputElement) => {
|
||||
updateRequest.mutate((r) => ({ ...r, name: el.value }));
|
||||
if (activeRequest == null) return;
|
||||
if (activeRequest.model === 'http_request') {
|
||||
updateHttpRequest.mutate((r) => ({ ...r, name: el.value }));
|
||||
} else if (activeRequest.model === 'grpc_request') {
|
||||
updateGrpcRequest.mutate((r) => ({ ...r, name: el.value }));
|
||||
}
|
||||
setEditing(false);
|
||||
},
|
||||
[updateRequest],
|
||||
[activeRequest, updateGrpcRequest, updateHttpRequest],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
||||
@@ -677,7 +697,9 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => {
|
||||
duplicateRequest.mutate();
|
||||
itemModel === 'http_request'
|
||||
? duplicateHttpRequest.mutate()
|
||||
: duplicateGrpcRequest.mutate();
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,7 +34,6 @@ export default function Workspace() {
|
||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||
const { hide, show, hidden } = useSidebarHidden();
|
||||
const activeRequest = useActiveRequest();
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
const [floating, setFloating] = useState<boolean>(false);
|
||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||
@@ -47,7 +46,7 @@ export default function Workspace() {
|
||||
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||
if (shouldHide && !floating) {
|
||||
setFloating(true);
|
||||
hide();
|
||||
hide().catch(console.error);
|
||||
} else if (!shouldHide && floating) {
|
||||
setFloating(false);
|
||||
}
|
||||
@@ -72,10 +71,10 @@ export default function Workspace() {
|
||||
e.preventDefault(); // Prevent text selection and things
|
||||
const newWidth = startWidth + (e.clientX - mouseStartX);
|
||||
if (newWidth < 100) {
|
||||
hide();
|
||||
await hide();
|
||||
resetWidth();
|
||||
} else {
|
||||
show();
|
||||
await show();
|
||||
setWidth(newWidth);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,8 +16,8 @@ interface SlotProps {
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
leftSlot: (props: SlotProps) => ReactNode;
|
||||
rightSlot: null | ((props: SlotProps) => ReactNode);
|
||||
firstSlot: (props: SlotProps) => ReactNode;
|
||||
secondSlot: null | ((props: SlotProps) => ReactNode);
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
defaultRatio?: number;
|
||||
@@ -33,8 +33,8 @@ const STACK_VERTICAL_WIDTH = 700;
|
||||
|
||||
export function SplitLayout({
|
||||
style,
|
||||
leftSlot,
|
||||
rightSlot,
|
||||
firstSlot,
|
||||
secondSlot,
|
||||
className,
|
||||
name,
|
||||
defaultRatio = 0.5,
|
||||
@@ -54,7 +54,7 @@ export function SplitLayout({
|
||||
null,
|
||||
);
|
||||
|
||||
if (!rightSlot) {
|
||||
if (!secondSlot) {
|
||||
height = 0;
|
||||
minHeightPx = 0;
|
||||
}
|
||||
@@ -145,8 +145,8 @@ export function SplitLayout({
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={classNames(className, 'grid w-full h-full')} style={styles}>
|
||||
{leftSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
{rightSlot && (
|
||||
{firstSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
{secondSlot && (
|
||||
<>
|
||||
<ResizeHandle
|
||||
style={areaD}
|
||||
@@ -158,7 +158,7 @@ export function SplitLayout({
|
||||
side={vertical ? 'top' : 'left'}
|
||||
justify="center"
|
||||
/>
|
||||
{rightSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
{secondSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { HttpRequest } from '../lib/models';
|
||||
import { getHttpRequest } from '../lib/store';
|
||||
import { useConfirm } from './useConfirm';
|
||||
import { httpRequestsQueryKey } from './useHttpRequests';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||
|
||||
export function useDeleteAnyRequest() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -35,7 +35,7 @@ export function useDeleteAnyRequest() {
|
||||
if (request === null) return;
|
||||
|
||||
const { workspaceId, id: requestId } = request;
|
||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []); // Responses were deleted
|
||||
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []); // Responses were deleted
|
||||
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey({ workspaceId }), (requests) =>
|
||||
(requests ?? []).filter((r) => r.id !== requestId),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||
|
||||
export function useDeleteResponse(id: string | null) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -12,7 +12,7 @@ export function useDeleteResponse(id: string | null) {
|
||||
},
|
||||
onSettled: () => trackEvent('HttpResponse', 'Delete'),
|
||||
onSuccess: ({ requestId, id: responseId }) => {
|
||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey({ requestId }), (responses) =>
|
||||
queryClient.setQueryData<HttpResponse[]>(httpResponsesQueryKey({ requestId }), (responses) =>
|
||||
(responses ?? []).filter((response) => response.id !== responseId),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
import { httpResponsesQueryKey } from './useHttpResponses';
|
||||
|
||||
export function useDeleteResponses(requestId?: string) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -13,7 +13,7 @@ export function useDeleteResponses(requestId?: string) {
|
||||
onSettled: () => trackEvent('HttpResponse', 'DeleteMany'),
|
||||
onSuccess: async () => {
|
||||
if (requestId === undefined) return;
|
||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []);
|
||||
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
41
src-web/hooks/useDuplicateGrpcRequest.ts
Normal file
41
src-web/hooks/useDuplicateGrpcRequest.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import type { GrpcRequest } from '../lib/models';
|
||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { grpcRequestsQueryKey } from './useGrpcRequests';
|
||||
|
||||
export function useDuplicateGrpcRequest({
|
||||
id,
|
||||
navigateAfter,
|
||||
}: {
|
||||
id: string | null;
|
||||
navigateAfter: boolean;
|
||||
}) {
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const routes = useAppRoutes();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<GrpcRequest, string>({
|
||||
mutationFn: async () => {
|
||||
if (id === null) throw new Error("Can't duplicate a null grpc request");
|
||||
return invoke('cmd_duplicate_grpc_request', { id });
|
||||
},
|
||||
onSettled: () => trackEvent('GrpcRequest', 'Duplicate'),
|
||||
onSuccess: async (request) => {
|
||||
queryClient.setQueryData<GrpcRequest[]>(
|
||||
grpcRequestsQueryKey({ workspaceId: request.workspaceId }),
|
||||
(requests) => [...(requests ?? []), request],
|
||||
);
|
||||
if (navigateAfter && activeWorkspaceId !== null) {
|
||||
routes.navigate('request', {
|
||||
workspaceId: activeWorkspaceId,
|
||||
requestId: request.id,
|
||||
environmentId: activeEnvironmentId ?? undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { httpRequestsQueryKey } from './useHttpRequests';
|
||||
|
||||
export function useDuplicateRequest({
|
||||
export function useDuplicateHttpRequest({
|
||||
id,
|
||||
navigateAfter,
|
||||
}: {
|
||||
@@ -3,8 +3,7 @@ import { invoke } from '@tauri-apps/api';
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { emit, listen } from '@tauri-apps/api/event';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { GrpcRequest } from '../lib/models';
|
||||
import type { GrpcConnection, GrpcMessage, GrpcRequest } from '../lib/models';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
interface ReflectResponseService {
|
||||
@@ -12,12 +11,6 @@ interface ReflectResponseService {
|
||||
methods: { name: string; schema: string; serverStreaming: boolean; clientStreaming: boolean }[];
|
||||
}
|
||||
|
||||
export interface GrpcMessage {
|
||||
message: string;
|
||||
timestamp: string;
|
||||
type: 'server' | 'client' | 'info';
|
||||
}
|
||||
|
||||
export function useGrpc(url: string | null, requestId: string | null) {
|
||||
const messages = useKeyValue<GrpcMessage[]>({
|
||||
namespace: 'debug',
|
||||
@@ -25,54 +18,30 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
||||
defaultValue: [],
|
||||
});
|
||||
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
|
||||
const unlisten = useRef<UnlistenFn | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveConnectionId(null);
|
||||
unlisten.current?.();
|
||||
}, [requestId]);
|
||||
|
||||
const unary = useMutation<string, string, GrpcRequest>({
|
||||
const unary = useMutation<GrpcMessage, string, string>({
|
||||
mutationKey: ['grpc_unary', url],
|
||||
mutationFn: async ({ service, method, message, url }) => {
|
||||
if (url === null) throw new Error('No URL provided');
|
||||
return (await invoke('cmd_grpc_call_unary', {
|
||||
endpoint: url,
|
||||
service,
|
||||
method,
|
||||
message,
|
||||
})) as string;
|
||||
mutationFn: async (id) => {
|
||||
const message = (await invoke('cmd_grpc_call_unary', {
|
||||
requestId: id,
|
||||
})) as GrpcMessage;
|
||||
await messages.set([message]);
|
||||
console.log('MESSAGE', message);
|
||||
return message;
|
||||
},
|
||||
});
|
||||
|
||||
const serverStreaming = useMutation<void, string, GrpcRequest>({
|
||||
const serverStreaming = useMutation<void, string, string>({
|
||||
mutationKey: ['grpc_server_streaming', url],
|
||||
mutationFn: async ({ service, method, message, url }) => {
|
||||
mutationFn: async (requestId) => {
|
||||
if (url === null) throw new Error('No URL provided');
|
||||
await messages.set([
|
||||
{
|
||||
type: 'client',
|
||||
message: JSON.stringify(JSON.parse(message)),
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
]);
|
||||
const id: string = await invoke('cmd_grpc_server_streaming', {
|
||||
endpoint: url,
|
||||
service,
|
||||
method,
|
||||
message,
|
||||
});
|
||||
unlisten.current = await listen(`grpc_server_msg_${id}`, async (event) => {
|
||||
await messages.set((prev) => [
|
||||
...prev,
|
||||
{
|
||||
message: tryFormatJson(event.payload as string, false),
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'server',
|
||||
},
|
||||
]);
|
||||
});
|
||||
setActiveConnectionId(id);
|
||||
await messages.set([]);
|
||||
const c = (await invoke('cmd_grpc_server_streaming', { requestId })) as GrpcConnection;
|
||||
setActiveConnectionId(c.id);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -86,20 +55,8 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
||||
method,
|
||||
message,
|
||||
});
|
||||
await messages.set([
|
||||
{ type: 'info', message: `Started connection ${id}`, timestamp: new Date().toISOString() },
|
||||
]);
|
||||
await messages.set([]);
|
||||
setActiveConnectionId(id);
|
||||
unlisten.current = await listen(`grpc_server_msg_${id}`, (event) => {
|
||||
messages.set((prev) => [
|
||||
...prev,
|
||||
{
|
||||
message: tryFormatJson(event.payload as string, false),
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'server',
|
||||
},
|
||||
]);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -107,9 +64,10 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
||||
mutationKey: ['grpc_send', url],
|
||||
mutationFn: async ({ message }: { message: string }) => {
|
||||
if (activeConnectionId == null) throw new Error('No active connection');
|
||||
await messages.set((m) => {
|
||||
return [...m, { type: 'client', message, timestamp: new Date().toISOString() }];
|
||||
});
|
||||
await messages.set([]);
|
||||
// await messages.set((m) => {
|
||||
// return [...m, { type: 'client', message, timestamp: new Date().toISOString() }];
|
||||
// });
|
||||
await emit(`grpc_client_msg_${activeConnectionId}`, { Message: message });
|
||||
},
|
||||
});
|
||||
@@ -118,12 +76,7 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
||||
mutationKey: ['grpc_cancel', url],
|
||||
mutationFn: async () => {
|
||||
setActiveConnectionId(null);
|
||||
unlisten.current?.();
|
||||
await emit('grpc_message_in', 'Cancel');
|
||||
await messages.set((m) => [
|
||||
...m,
|
||||
{ type: 'info', message: 'Cancelled by client', timestamp: new Date().toISOString() },
|
||||
]);
|
||||
await emit(`grpc_client_msg_${activeConnectionId}`, 'Cancel');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -141,7 +94,6 @@ export function useGrpc(url: string | null, requestId: string | null) {
|
||||
bidiStreaming,
|
||||
services: reflect.data,
|
||||
cancel,
|
||||
messages,
|
||||
isStreaming: activeConnectionId !== null,
|
||||
send,
|
||||
};
|
||||
|
||||
23
src-web/hooks/useGrpcConnections.ts
Normal file
23
src-web/hooks/useGrpcConnections.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { GrpcConnection } from '../lib/models';
|
||||
|
||||
export function grpcConnectionsQueryKey({ requestId }: { requestId: string }) {
|
||||
return ['grpc_connections', { requestId }];
|
||||
}
|
||||
|
||||
export function useGrpcConnections(requestId: string | null) {
|
||||
return (
|
||||
useQuery<GrpcConnection[]>({
|
||||
enabled: requestId !== null,
|
||||
initialData: [],
|
||||
queryKey: grpcConnectionsQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||
queryFn: async () => {
|
||||
return (await invoke('cmd_list_grpc_connections', {
|
||||
requestId,
|
||||
limit: 200,
|
||||
})) as GrpcConnection[];
|
||||
},
|
||||
}).data ?? []
|
||||
);
|
||||
}
|
||||
23
src-web/hooks/useGrpcMessages.ts
Normal file
23
src-web/hooks/useGrpcMessages.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { GrpcMessage } from '../lib/models';
|
||||
|
||||
export function grpcMessagesQueryKey({ connectionId }: { connectionId: string }) {
|
||||
return ['grpc_messages', { connectionId }];
|
||||
}
|
||||
|
||||
export function useGrpcMessages(connectionId: string | null) {
|
||||
return (
|
||||
useQuery<GrpcMessage[]>({
|
||||
enabled: connectionId !== null,
|
||||
initialData: [],
|
||||
queryKey: grpcMessagesQueryKey({ connectionId: connectionId ?? 'n/a' }),
|
||||
queryFn: async () => {
|
||||
return (await invoke('cmd_list_grpc_messages', {
|
||||
connectionId,
|
||||
limit: 200,
|
||||
})) as GrpcMessage[];
|
||||
},
|
||||
}).data ?? []
|
||||
);
|
||||
}
|
||||
@@ -2,18 +2,18 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
|
||||
export function responsesQueryKey({ requestId }: { requestId: string }) {
|
||||
export function httpResponsesQueryKey({ requestId }: { requestId: string }) {
|
||||
return ['http_responses', { requestId }];
|
||||
}
|
||||
|
||||
export function useResponses(requestId: string | null) {
|
||||
export function useHttpResponses(requestId: string | null) {
|
||||
return (
|
||||
useQuery<HttpResponse[]>({
|
||||
enabled: requestId !== null,
|
||||
initialData: [],
|
||||
queryKey: responsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||
queryKey: httpResponsesQueryKey({ requestId: requestId ?? 'n/a' }),
|
||||
queryFn: async () => {
|
||||
return (await invoke('cmd_list_responses', { requestId, limit: 200 })) as HttpResponse[];
|
||||
return (await invoke('cmd_list_http_responses', { requestId, limit: 200 })) as HttpResponse[];
|
||||
},
|
||||
}).data ?? []
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { useResponses } from './useResponses';
|
||||
import { useHttpResponses } from './useHttpResponses';
|
||||
|
||||
export function useLatestResponse(requestId: string | null): HttpResponse | null {
|
||||
const responses = useResponses(requestId);
|
||||
const responses = useHttpResponses(requestId);
|
||||
return responses[0] ?? null;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ export const AUTH_TYPE_BEARER = 'bearer';
|
||||
export type Model =
|
||||
| Settings
|
||||
| Workspace
|
||||
| GrpcConnection
|
||||
| GrpcRequest
|
||||
| GrpcMessage
|
||||
| HttpRequest
|
||||
| HttpResponse
|
||||
| KeyValue
|
||||
@@ -114,6 +116,24 @@ export interface GrpcRequest extends BaseModel {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface GrpcMessage extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly requestId: string;
|
||||
readonly connectionId: string;
|
||||
readonly model: 'grpc_message';
|
||||
message: string;
|
||||
isServer: boolean;
|
||||
isInfo: boolean;
|
||||
}
|
||||
|
||||
export interface GrpcConnection extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly requestId: string;
|
||||
readonly model: 'grpc_connection';
|
||||
service: string;
|
||||
method: string;
|
||||
}
|
||||
|
||||
export interface HttpRequest extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly model: 'http_request';
|
||||
|
||||
Reference in New Issue
Block a user