mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-17 06:29:38 +02:00
gRPC Support (#20)
This commit is contained in:
@@ -7,31 +7,37 @@ 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';
|
||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
||||
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||
import { useDeleteAnyGrpcRequest } from '../hooks/useDeleteAnyGrpcRequest';
|
||||
import { useDeleteAnyHttpRequest } from '../hooks/useDeleteAnyHttpRequest';
|
||||
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';
|
||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||
import { useLatestGrpcConnection } from '../hooks/useLatestGrpcConnection';
|
||||
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useSendManyRequests } from '../hooks/useSendFolder';
|
||||
import { useSendRequest } from '../hooks/useSendRequest';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
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';
|
||||
import type { Folder, HttpRequest, Workspace } from '../lib/models';
|
||||
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { ContextMenu } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
@@ -48,7 +54,7 @@ enum ItemTypes {
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
item: Workspace | Folder | HttpRequest;
|
||||
item: Workspace | Folder | HttpRequest | GrpcRequest;
|
||||
children: TreeNode[];
|
||||
depth: number;
|
||||
}
|
||||
@@ -56,18 +62,28 @@ 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 requests = useRequests();
|
||||
const httpRequests = useHttpRequests();
|
||||
const grpcRequests = useGrpcRequests();
|
||||
const folders = useFolders();
|
||||
const deleteAnyRequest = useDeleteAnyRequest();
|
||||
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
|
||||
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
|
||||
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);
|
||||
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
|
||||
const updateAnyRequest = useUpdateAnyRequest();
|
||||
const updateAnyHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateAnyGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const updateAnyFolder = useUpdateAnyFolder();
|
||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||
const [hoveredTree, setHoveredTree] = useState<TreeNode | null>(null);
|
||||
@@ -78,8 +94,12 @@ export function Sidebar({ className }: Props) {
|
||||
namespace: NAMESPACE_NO_SYNC,
|
||||
});
|
||||
|
||||
useHotKey('request.duplicate', () => {
|
||||
duplicateRequest.mutate();
|
||||
useHotKey('http_request.duplicate', async () => {
|
||||
if (activeRequest?.model === 'http_request') {
|
||||
await duplicateHttpRequest.mutateAsync();
|
||||
} else {
|
||||
await duplicateGrpcRequest.mutateAsync();
|
||||
}
|
||||
});
|
||||
|
||||
const isCollapsed = useCallback(
|
||||
@@ -110,7 +130,7 @@ export function Sidebar({ className }: Props) {
|
||||
|
||||
// Put requests and folders into a tree structure
|
||||
const next = (node: TreeNode): TreeNode => {
|
||||
const childItems = [...requests, ...folders].filter((f) =>
|
||||
const childItems = [...httpRequests, ...grpcRequests, ...folders].filter((f) =>
|
||||
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
|
||||
);
|
||||
|
||||
@@ -119,7 +139,7 @@ export function Sidebar({ className }: Props) {
|
||||
for (const item of childItems) {
|
||||
treeParentMap[item.id] = node;
|
||||
node.children.push(next({ item, children: [], depth }));
|
||||
if (item.model === 'http_request') {
|
||||
if (item.model !== 'folder') {
|
||||
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
|
||||
}
|
||||
}
|
||||
@@ -129,7 +149,7 @@ export function Sidebar({ className }: Props) {
|
||||
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
||||
|
||||
return { tree, treeParentMap, selectableRequests };
|
||||
}, [activeWorkspace, requests, folders]);
|
||||
}, [activeWorkspace, httpRequests, grpcRequests, folders]);
|
||||
|
||||
const focusActiveRequest = useCallback(
|
||||
(
|
||||
@@ -142,9 +162,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;
|
||||
}
|
||||
@@ -156,11 +177,11 @@ export function Sidebar({ className }: Props) {
|
||||
sidebarRef.current?.focus();
|
||||
}
|
||||
},
|
||||
[activeRequestId, treeParentMap],
|
||||
[activeRequest, treeParentMap],
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(id: string) => {
|
||||
async (id: string) => {
|
||||
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const node = children.find((m) => m.item.id === id) ?? null;
|
||||
@@ -171,7 +192,7 @@ export function Sidebar({ className }: Props) {
|
||||
const { item } = node;
|
||||
|
||||
if (item.model === 'folder') {
|
||||
collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
|
||||
await collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
|
||||
} else {
|
||||
routes.navigate('request', {
|
||||
requestId: id,
|
||||
@@ -205,9 +226,10 @@ export function Sidebar({ className }: Props) {
|
||||
|
||||
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||
if (selected == null) return;
|
||||
deleteAnyRequest.mutate(selected.id);
|
||||
deleteAnyHttpRequest.mutate(selected.id);
|
||||
deleteAnyGrpcRequest.mutate(selected.id);
|
||||
},
|
||||
[deleteAnyRequest, hasFocus, selectableRequests, selectedId],
|
||||
[deleteAnyHttpRequest, deleteAnyGrpcRequest, hasFocus, selectableRequests, selectedId],
|
||||
);
|
||||
|
||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||
@@ -226,7 +248,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;
|
||||
}
|
||||
|
||||
@@ -339,9 +361,12 @@ export function Sidebar({ className }: Props) {
|
||||
if (child.item.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
return updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
||||
} else if (child.item.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
} else if (child.item.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
return updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -350,20 +375,24 @@ export function Sidebar({ className }: Props) {
|
||||
if (child.item.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
await updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
|
||||
} else if (child.item.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
} else if (child.item.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
await updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest });
|
||||
}
|
||||
}
|
||||
setDraggingId(null);
|
||||
},
|
||||
[
|
||||
hoveredIndex,
|
||||
hoveredTree,
|
||||
handleClearSelected,
|
||||
hoveredTree,
|
||||
hoveredIndex,
|
||||
treeParentMap,
|
||||
updateAnyFolder,
|
||||
updateAnyRequest,
|
||||
updateAnyGrpcRequest,
|
||||
updateAnyHttpRequest,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -454,7 +483,9 @@ function SidebarItems({
|
||||
itemId={child.item.id}
|
||||
itemName={child.item.name}
|
||||
itemFallbackName={
|
||||
child.item.model === 'http_request' ? fallbackRequestName(child.item) : 'New Folder'
|
||||
child.item.model === 'http_request' || child.item.model === 'grpc_request'
|
||||
? fallbackRequestName(child.item)
|
||||
: 'New Folder'
|
||||
}
|
||||
itemModel={child.item.model}
|
||||
onMove={handleMove}
|
||||
@@ -524,16 +555,18 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
ref: ForwardedRef<HTMLLIElement>,
|
||||
) {
|
||||
const activeRequest = useActiveRequest();
|
||||
const createRequest = useCreateRequest();
|
||||
const createRequest = useCreateHttpRequest();
|
||||
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 sendAndDownloadRequest = useSendRequest(itemId, { download: true });
|
||||
const sendManyRequests = useSendManyRequests();
|
||||
const latestResponse = useLatestResponse(itemId);
|
||||
const updateRequest = useUpdateRequest(itemId);
|
||||
const latestHttpResponse = useLatestHttpResponse(itemId);
|
||||
const latestGrpcConnection = useLatestGrpcConnection(itemId);
|
||||
const updateHttpRequest = useUpdateHttpRequest(itemId);
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(itemId);
|
||||
const updateAnyFolder = useUpdateAnyFolder();
|
||||
const prompt = usePrompt();
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
@@ -541,10 +574,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) => {
|
||||
@@ -570,7 +608,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
);
|
||||
|
||||
const handleStartEditing = useCallback(() => {
|
||||
if (itemModel !== 'http_request') return;
|
||||
if (itemModel !== 'http_request' && itemModel !== 'grpc_request') return;
|
||||
setEditing(true);
|
||||
}, [setEditing, itemModel]);
|
||||
|
||||
@@ -649,23 +687,29 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: 'sendRequest',
|
||||
label: 'Send',
|
||||
hotKeyAction: 'request.send',
|
||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||
leftSlot: <Icon icon="sendHorizontal" />,
|
||||
onSelect: () => sendRequest.mutate(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
...((itemModel === 'http_request'
|
||||
? [
|
||||
{
|
||||
key: 'sendRequest',
|
||||
label: 'Send',
|
||||
hotKeyAction: 'http_request.send',
|
||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||
leftSlot: <Icon icon="sendHorizontal" />,
|
||||
onSelect: () => sendRequest.mutate(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
]
|
||||
: []) as DropdownItem[]),
|
||||
{
|
||||
key: 'duplicateRequest',
|
||||
label: 'Duplicate',
|
||||
hotKeyAction: 'request.duplicate',
|
||||
hotKeyAction: 'http_request.duplicate',
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => {
|
||||
duplicateRequest.mutate();
|
||||
itemModel === 'http_request'
|
||||
? duplicateHttpRequest.mutate()
|
||||
: duplicateGrpcRequest.mutate();
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -717,15 +761,21 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
) : (
|
||||
<span className="truncate">{itemName || itemFallbackName}</span>
|
||||
)}
|
||||
{latestResponse && (
|
||||
{latestGrpcConnection ? (
|
||||
<div className="ml-auto">
|
||||
{isResponseLoading(latestResponse) ? (
|
||||
<Icon spin size="sm" icon="update" />
|
||||
) : (
|
||||
<StatusTag className="text-2xs dark:opacity-80" response={latestResponse} />
|
||||
{latestGrpcConnection.elapsed === 0 && (
|
||||
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
) : latestHttpResponse ? (
|
||||
<div className="ml-auto">
|
||||
{isResponseLoading(latestHttpResponse) ? (
|
||||
<Icon spin size="sm" icon="update" className="text-gray-400" />
|
||||
) : (
|
||||
<StatusTag className="text-2xs dark:opacity-80" response={latestHttpResponse} />
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user