mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-23 18:01:08 +01:00
Initial frontend for gRPC UI
This commit is contained in:
@@ -72,7 +72,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
const dialog = useDialog();
|
||||
|
||||
return (
|
||||
<div className="pb-2 h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<Editor
|
||||
contentType="application/graphql"
|
||||
defaultValue={query ?? ''}
|
||||
@@ -125,7 +125,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<Separator variant="primary" />
|
||||
<p className="pt-1 text-gray-500 text-sm">Variables</p>
|
||||
<p className="py-1 text-gray-500 text-sm">Variables</p>
|
||||
<Editor
|
||||
format={tryFormatJson}
|
||||
contentType="application/json"
|
||||
|
||||
81
src-web/components/GrpcConnectionLayout.tsx
Normal file
81
src-web/components/GrpcConnectionLayout.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { Props } from 'focus-trap-react';
|
||||
import type { CSSProperties, FormEvent } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { Editor } from './core/Editor';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { GrpcEditor } from './GrpcEditor';
|
||||
import { UrlBar } from './UrlBar';
|
||||
|
||||
interface Props {
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
export function GrpcConnectionLayout({ style }: Props) {
|
||||
const url = useKeyValue<string>({ namespace: 'debug', key: 'grpc_url', defaultValue: '' });
|
||||
const message = useKeyValue<string>({
|
||||
namespace: 'debug',
|
||||
key: 'grpc_message',
|
||||
defaultValue: '',
|
||||
});
|
||||
const [resp, setResp] = useState<string>('');
|
||||
const grpc = useGrpc(url.value ?? null);
|
||||
const handleConnect = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setResp(
|
||||
await grpc.callUnary.mutateAsync({
|
||||
service: 'helloworld.Greeter',
|
||||
method: 'SayHello',
|
||||
message: message.value ?? '',
|
||||
}),
|
||||
);
|
||||
},
|
||||
[grpc.callUnary, message.value],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('REFLECT SCHEMA', grpc.schema);
|
||||
}, [grpc.schema]);
|
||||
|
||||
if (url.isLoading || url.value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SplitLayout
|
||||
style={style}
|
||||
leftSlot={() => (
|
||||
<VStack space={2}>
|
||||
<UrlBar
|
||||
id="foo"
|
||||
url={url.value ?? ''}
|
||||
method={null}
|
||||
placeholder="localhost:50051"
|
||||
onSubmit={handleConnect}
|
||||
isLoading={false}
|
||||
onUrlChange={url.set}
|
||||
forceUpdateKey={''}
|
||||
/>
|
||||
<GrpcEditor
|
||||
url={url.value ?? ''}
|
||||
defaultValue={message.value}
|
||||
onChange={message.set}
|
||||
className="bg-gray-50"
|
||||
/>
|
||||
</VStack>
|
||||
)}
|
||||
rightSlot={() => (
|
||||
<Editor
|
||||
className="bg-gray-50 border border-highlight"
|
||||
contentType="application/json"
|
||||
defaultValue={resp}
|
||||
readOnly
|
||||
forceUpdateKey={resp}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
40
src-web/components/GrpcEditor.tsx
Normal file
40
src-web/components/GrpcEditor.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { EditorView } from 'codemirror';
|
||||
import { updateSchema } from 'codemirror-json-schema';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { EditorProps } from './core/Editor';
|
||||
import { Editor } from './core/Editor';
|
||||
|
||||
type Props = Pick<
|
||||
EditorProps,
|
||||
'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'forceUpdateKey'
|
||||
> & {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export function GrpcEditor({ url, defaultValue, ...extraEditorProps }: Props) {
|
||||
const editorViewRef = useRef<EditorView>(null);
|
||||
const { schema } = useGrpc(url);
|
||||
|
||||
useEffect(() => {
|
||||
if (editorViewRef.current == null || schema == null) return;
|
||||
const foo = schema[0].methods[0].schema;
|
||||
console.log('UPDATE SCHEMA', foo);
|
||||
updateSchema(editorViewRef.current, JSON.parse(foo));
|
||||
}, [schema]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<Editor
|
||||
contentType="application/grpc"
|
||||
defaultValue={defaultValue}
|
||||
format={tryFormatJson}
|
||||
heightMode="auto"
|
||||
placeholder="..."
|
||||
ref={editorViewRef}
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src-web/components/HttpRequestLayout.tsx
Normal file
21
src-web/components/HttpRequestLayout.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import React from 'react';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { RequestPane } from './RequestPane';
|
||||
import { ResponsePane } from './ResponsePane';
|
||||
|
||||
interface Props {
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
export function HttpRequestLayout({ style }: Props) {
|
||||
return (
|
||||
<SplitLayout
|
||||
style={style}
|
||||
leftSlot={({ orientation, style }) => (
|
||||
<RequestPane style={style} fullHeight={orientation === 'horizontal'} />
|
||||
)}
|
||||
rightSlot={({ style }) => <ResponsePane style={style} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { CSSProperties, FormEvent } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useSendRequest } from '../hooks/useSendRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { HttpHeader, HttpRequest, HttpUrlParameter } from '../lib/models';
|
||||
@@ -33,7 +35,7 @@ import { UrlBar } from './UrlBar';
|
||||
import { UrlParametersEditor } from './UrlParameterEditor';
|
||||
|
||||
interface Props {
|
||||
style?: CSSProperties;
|
||||
style: CSSProperties;
|
||||
fullHeight: boolean;
|
||||
className?: string;
|
||||
}
|
||||
@@ -178,6 +180,27 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
[updateRequest],
|
||||
);
|
||||
|
||||
const sendRequest = useSendRequest(activeRequest?.id ?? null);
|
||||
const handleSend = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
await sendRequest.mutateAsync();
|
||||
},
|
||||
[sendRequest],
|
||||
);
|
||||
|
||||
const handleMethodChange = useCallback(
|
||||
(method: string) => updateRequest.mutate({ method }),
|
||||
[updateRequest],
|
||||
);
|
||||
const handleUrlChange = useCallback(
|
||||
(url: string) => updateRequest.mutate({ url }),
|
||||
[updateRequest],
|
||||
);
|
||||
|
||||
const isLoading = useIsResponseLoading(activeRequestId ?? null);
|
||||
const { updateKey } = useRequestUpdateKey(activeRequestId ?? null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
@@ -190,6 +213,12 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
id={activeRequest.id}
|
||||
url={activeRequest.url}
|
||||
method={activeRequest.method}
|
||||
placeholder="https://example.com"
|
||||
onSubmit={handleSend}
|
||||
onMethodChange={handleMethodChange}
|
||||
onUrlChange={handleUrlChange}
|
||||
forceUpdateKey={updateKey}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
import type { EditorView } from 'codemirror';
|
||||
import type { FormEvent } from 'react';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import { memo, useRef, useState } from 'react';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useSendRequest } from '../hooks/useSendRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { Input } from './core/Input';
|
||||
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
||||
|
||||
type Props = Pick<HttpRequest, 'id' | 'url' | 'method'> & {
|
||||
type Props = Pick<HttpRequest, 'id' | 'url'> & {
|
||||
className?: string;
|
||||
method: HttpRequest['method'] | null;
|
||||
placeholder: string;
|
||||
onSubmit: (e: FormEvent) => void;
|
||||
onUrlChange: (url: string) => void;
|
||||
onMethodChange?: (method: string) => void;
|
||||
isLoading: boolean;
|
||||
forceUpdateKey: string;
|
||||
};
|
||||
|
||||
export const UrlBar = memo(function UrlBar({ id: requestId, url, method, className }: Props) {
|
||||
export const UrlBar = memo(function UrlBar({
|
||||
forceUpdateKey,
|
||||
onUrlChange,
|
||||
url,
|
||||
method,
|
||||
placeholder,
|
||||
className,
|
||||
onSubmit,
|
||||
onMethodChange,
|
||||
isLoading,
|
||||
}: Props) {
|
||||
const inputRef = useRef<EditorView>(null);
|
||||
const sendRequest = useSendRequest(requestId);
|
||||
const updateRequest = useUpdateRequest(requestId);
|
||||
const [isFocused, setIsFocused] = useState<boolean>(false);
|
||||
const handleMethodChange = useCallback(
|
||||
(method: string) => updateRequest.mutate({ method }),
|
||||
[updateRequest],
|
||||
);
|
||||
const handleUrlChange = useCallback(
|
||||
(url: string) => updateRequest.mutate({ url }),
|
||||
[updateRequest],
|
||||
);
|
||||
const loading = useIsResponseLoading(requestId);
|
||||
const { updateKey } = useRequestUpdateKey(requestId);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
sendRequest.mutate();
|
||||
},
|
||||
[sendRequest],
|
||||
);
|
||||
|
||||
useHotKey('urlBar.focus', () => {
|
||||
const head = inputRef.current?.state.doc.length ?? 0;
|
||||
@@ -48,7 +41,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={className}>
|
||||
<form onSubmit={onSubmit} className={className}>
|
||||
<Input
|
||||
autocompleteVariables
|
||||
ref={inputRef}
|
||||
@@ -60,19 +53,22 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
className="px-0 py-0.5"
|
||||
name="url"
|
||||
label="Enter URL"
|
||||
forceUpdateKey={updateKey}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
containerClassName="shadow shadow-gray-100 dark:shadow-gray-50"
|
||||
onChange={handleUrlChange}
|
||||
onChange={onUrlChange}
|
||||
defaultValue={url}
|
||||
placeholder="https://example.com"
|
||||
placeholder={placeholder}
|
||||
leftSlot={
|
||||
<RequestMethodDropdown
|
||||
method={method}
|
||||
onChange={handleMethodChange}
|
||||
className="mx-0.5 my-0.5"
|
||||
/>
|
||||
method != null &&
|
||||
onMethodChange != null && (
|
||||
<RequestMethodDropdown
|
||||
method={method}
|
||||
onChange={onMethodChange}
|
||||
className="mx-0.5 my-0.5"
|
||||
/>
|
||||
)
|
||||
}
|
||||
rightSlot={
|
||||
<IconButton
|
||||
@@ -81,8 +77,8 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
title="Send Request"
|
||||
type="submit"
|
||||
className="w-8 mr-0.5 my-0.5"
|
||||
icon={loading ? 'update' : 'sendHorizontal'}
|
||||
spin={loading}
|
||||
icon={isLoading ? 'update' : 'sendHorizontal'}
|
||||
spin={isLoading}
|
||||
hotkeyAction="request.send"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@ import type {
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useWindowSize } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { Button } from './core/Button';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
||||
import { HttpRequestLayout } from './HttpRequestLayout';
|
||||
import { Overlay } from './Overlay';
|
||||
import { RequestResponse } from './RequestResponse';
|
||||
import { ResizeHandle } from './ResizeHandle';
|
||||
import { Sidebar } from './Sidebar';
|
||||
import { SidebarActions } from './SidebarActions';
|
||||
@@ -31,6 +33,7 @@ const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
|
||||
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);
|
||||
@@ -163,7 +166,11 @@ export default function Workspace() {
|
||||
>
|
||||
<WorkspaceHeader className="pointer-events-none" />
|
||||
</HeaderSize>
|
||||
<RequestResponse style={body} />
|
||||
{activeRequest?.name.includes('gRPC') ? (
|
||||
<GrpcConnectionLayout style={body} />
|
||||
) : (
|
||||
<HttpRequestLayout style={body} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { json } from '@codemirror/lang-json';
|
||||
import { xml } from '@codemirror/lang-xml';
|
||||
import type { LanguageSupport } from '@codemirror/language';
|
||||
import {
|
||||
bracketMatching,
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
HighlightStyle,
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
} from '@codemirror/view';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
||||
import { jsonSchema } from 'codemirror-json-schema';
|
||||
import type { Environment, Workspace } from '../../../lib/models';
|
||||
import type { EditorProps } from './index';
|
||||
import { text } from './text/extension';
|
||||
@@ -83,6 +83,7 @@ export const myHighlightStyle = HighlightStyle.define([
|
||||
// ]);
|
||||
|
||||
const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||
'application/grpc': jsonSchema() as any, // TODO: Fix this
|
||||
'application/graphql': graphqlLanguageSupport(),
|
||||
'application/json': json(),
|
||||
'application/javascript': javascript(),
|
||||
@@ -119,7 +120,6 @@ export const baseExtensions = [
|
||||
history(),
|
||||
dropCursor(),
|
||||
drawSelection(),
|
||||
bracketMatching(),
|
||||
// TODO: Figure out how to debounce showing of autocomplete in a good way
|
||||
// debouncedAutocompletionDisplay({ millis: 1000 }),
|
||||
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { clamp } from '../lib/clamp';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { RequestPane } from './RequestPane';
|
||||
import { ResizeHandle } from './ResizeHandle';
|
||||
import { ResponsePane } from './ResponsePane';
|
||||
import { useActiveRequestId } from '../../hooks/useActiveRequestId';
|
||||
import { useActiveWorkspaceId } from '../../hooks/useActiveWorkspaceId';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { ResizeHandle } from '../ResizeHandle';
|
||||
import { HotKeyList } from './HotKeyList';
|
||||
|
||||
interface Props {
|
||||
interface SlotProps {
|
||||
orientation: 'horizontal' | 'vertical';
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
const rqst = { gridArea: 'rqst' };
|
||||
const resp = { gridArea: 'resp' };
|
||||
const drag = { gridArea: 'drag' };
|
||||
interface Props {
|
||||
style: CSSProperties;
|
||||
leftSlot: (props: SlotProps) => ReactNode;
|
||||
rightSlot: (props: SlotProps) => ReactNode;
|
||||
}
|
||||
|
||||
const areaL = { gridArea: 'left' };
|
||||
const areaR = { gridArea: 'right' };
|
||||
const areaD = { gridArea: 'drag' };
|
||||
|
||||
const DEFAULT = 0.5;
|
||||
const MIN_WIDTH_PX = 10;
|
||||
const MIN_HEIGHT_PX = 30;
|
||||
const STACK_VERTICAL_WIDTH = 700;
|
||||
|
||||
export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const activeRequest = useActiveRequest();
|
||||
const [vertical, setVertical] = useState<boolean>(false);
|
||||
const [widthRaw, setWidth] = useLocalStorage<number>(`body_width::${useActiveWorkspaceId()}`);
|
||||
const [heightRaw, setHeight] = useLocalStorage<number>(`body_height::${useActiveWorkspaceId()}`);
|
||||
@@ -46,13 +50,13 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
...style,
|
||||
gridTemplate: vertical
|
||||
? `
|
||||
' ${rqst.gridArea}' minmax(0,${1 - height}fr)
|
||||
' ${drag.gridArea}' 0
|
||||
' ${resp.gridArea}' minmax(0,${height}fr)
|
||||
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||
' ${areaD.gridArea}' 0
|
||||
' ${areaR.gridArea}' minmax(0,${height}fr)
|
||||
/ 1fr
|
||||
`
|
||||
: `
|
||||
' ${rqst.gridArea} ${drag.gridArea} ${resp.gridArea}' minmax(0,1fr)
|
||||
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||
/ ${1 - width}fr 0 ${width}fr
|
||||
`,
|
||||
}),
|
||||
@@ -117,15 +121,16 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
[width, height, vertical, setHeight, setWidth],
|
||||
);
|
||||
|
||||
if (activeRequest === null) {
|
||||
const activeRequestId = useActiveRequestId();
|
||||
if (activeRequestId === null) {
|
||||
return <HotKeyList hotkeys={['request.create', 'sidebar.toggle']} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="grid gap-1.5 w-full h-full p-3" style={styles}>
|
||||
<RequestPane style={rqst} fullHeight={!vertical} />
|
||||
{leftSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
<ResizeHandle
|
||||
style={drag}
|
||||
style={areaD}
|
||||
isResizing={isResizing}
|
||||
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
||||
onResizeStart={handleResizeStart}
|
||||
@@ -133,7 +138,7 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
side={vertical ? 'top' : 'left'}
|
||||
justify="center"
|
||||
/>
|
||||
<ResponsePane style={resp} />
|
||||
{rightSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import type { CookieJar } from '../lib/models';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { cookieJarsQueryKey } from './useCookieJars';
|
||||
import { usePrompt } from './usePrompt';
|
||||
import { requestsQueryKey } from './useRequests';
|
||||
|
||||
export function useCreateCookieJar() {
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const queryClient = useQueryClient();
|
||||
const prompt = usePrompt();
|
||||
|
||||
return useMutation<HttpRequest>({
|
||||
return useMutation<CookieJar>({
|
||||
mutationFn: async () => {
|
||||
if (workspaceId === null) {
|
||||
throw new Error("Cannot create cookie jar when there's no active workspace");
|
||||
@@ -26,10 +26,10 @@ export function useCreateCookieJar() {
|
||||
return invoke('create_cookie_jar', { workspaceId, name });
|
||||
},
|
||||
onSettled: () => trackEvent('CookieJar', 'Create'),
|
||||
onSuccess: async (request) => {
|
||||
queryClient.setQueryData<HttpRequest[]>(
|
||||
requestsQueryKey({ workspaceId: request.workspaceId }),
|
||||
(requests) => [...(requests ?? []), request],
|
||||
onSuccess: async (cookieJar) => {
|
||||
queryClient.setQueryData<CookieJar[]>(
|
||||
cookieJarsQueryKey({ workspaceId: cookieJar.workspaceId }),
|
||||
(items) => [...(items ?? []), cookieJar],
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
35
src-web/hooks/useGrpc.ts
Normal file
35
src-web/hooks/useGrpc.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
|
||||
export function useGrpc(url: string | null) {
|
||||
const callUnary = useMutation<
|
||||
string,
|
||||
unknown,
|
||||
{ service: string; method: string; message: string }
|
||||
>({
|
||||
mutationKey: ['grpc_call_reflect', url],
|
||||
mutationFn: async ({ service, method, message }) => {
|
||||
if (url === null) throw new Error('No URL provided');
|
||||
return (await invoke('grpc_call_unary', {
|
||||
endpoint: url,
|
||||
service,
|
||||
method,
|
||||
message,
|
||||
})) as string;
|
||||
},
|
||||
});
|
||||
|
||||
const reflect = useQuery<string | null>({
|
||||
queryKey: ['grpc_reflect', url ?? ''],
|
||||
queryFn: async () => {
|
||||
if (url === null) return null;
|
||||
console.log('GETTING SCHEMA', url);
|
||||
return (await invoke('grpc_reflect', { endpoint: url })) as string;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
callUnary,
|
||||
schema: reflect.data,
|
||||
};
|
||||
}
|
||||
@@ -11,20 +11,6 @@ import { setAppearanceOnDocument } from './lib/theme/window';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import { type } from '@tauri-apps/api/os';
|
||||
|
||||
try {
|
||||
const services: any = await invoke('grpc_reflect', { endpoint: 'http://localhost:50051' });
|
||||
console.log('SERVICES', services);
|
||||
const response = await invoke('grpc_call_unary', {
|
||||
endpoint: 'http://localhost:50051',
|
||||
service: services[0].name,
|
||||
method: services[0].methods[0].name,
|
||||
message: '{"name": "Greg"}',
|
||||
});
|
||||
console.log('RESPONSE', response);
|
||||
} catch (err) {
|
||||
console.log('ERROR', err);
|
||||
}
|
||||
|
||||
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
||||
const osType = await type();
|
||||
if (osType !== 'Darwin') {
|
||||
|
||||
Reference in New Issue
Block a user