mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
Refactor hooks to be easier to use
This commit is contained in:
@@ -3,7 +3,7 @@ import { listen } from '@tauri-apps/api/event';
|
|||||||
import { MotionConfig } from 'framer-motion';
|
import { MotionConfig } from 'framer-motion';
|
||||||
import { HelmetProvider } from 'react-helmet-async';
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
import { AppRouter } from './components/AppRouter';
|
import { AppRouter } from './components/AppRouter';
|
||||||
import { requestsQueryKey } from './hooks/useRequest';
|
import { requestsQueryKey } from './hooks/useRequests';
|
||||||
import { responsesQueryKey } from './hooks/useResponses';
|
import { responsesQueryKey } from './hooks/useResponses';
|
||||||
import { DEFAULT_FONT_SIZE } from './lib/constants';
|
import { DEFAULT_FONT_SIZE } from './lib/constants';
|
||||||
import type { HttpRequest, HttpResponse } from './lib/models';
|
import type { HttpRequest, HttpResponse } from './lib/models';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRequestUpdate } from '../hooks/useRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import type { HttpHeader, HttpRequest } from '../lib/models';
|
import type { HttpHeader, HttpRequest } from '../lib/models';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { Input } from './Input';
|
import { Input } from './Input';
|
||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
type PairWithId = { header: Partial<HttpHeader>; id: string };
|
type PairWithId = { header: Partial<HttpHeader>; id: string };
|
||||||
|
|
||||||
export function HeaderEditor({ request, className }: Props) {
|
export function HeaderEditor({ request, className }: Props) {
|
||||||
const updateRequest = useRequestUpdate(request);
|
const updateRequest = useUpdateRequest(request);
|
||||||
const saveHeaders = (pairs: PairWithId[]) => {
|
const saveHeaders = (pairs: PairWithId[]) => {
|
||||||
const headers = pairs.map((p) => ({ name: '', value: '', ...p.header }));
|
const headers = pairs.map((p) => ({ name: '', value: '', ...p.header }));
|
||||||
updateRequest.mutate({ headers });
|
updateRequest.mutate({ headers });
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useRequestUpdate, useSendRequest } from '../hooks/useRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import { Editor } from './Editor';
|
import { Editor } from './Editor';
|
||||||
import { HeaderEditor } from './HeaderEditor';
|
import { HeaderEditor } from './HeaderEditor';
|
||||||
import { TabContent, Tabs } from './Tabs';
|
import { TabContent, Tabs } from './Tabs';
|
||||||
import { UrlBar } from './UrlBar';
|
import { UrlBar } from './UrlBar';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
request: HttpRequest;
|
|
||||||
fullHeight: boolean;
|
fullHeight: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestPane({ fullHeight, request, className }: Props) {
|
export function RequestPane({ fullHeight, className }: Props) {
|
||||||
const updateRequest = useRequestUpdate(request ?? null);
|
const activeRequest = useActiveRequest();
|
||||||
const sendRequest = useSendRequest(request ?? null);
|
const updateRequest = useUpdateRequest(activeRequest);
|
||||||
|
const sendRequest = useSendRequest(activeRequest);
|
||||||
|
|
||||||
|
if (activeRequest === null) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, 'py-2 grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}>
|
<div className={classnames(className, 'py-2 grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}>
|
||||||
<div className="pl-2">
|
<div className="pl-2">
|
||||||
<UrlBar
|
<UrlBar
|
||||||
key={request.id}
|
key={activeRequest.id}
|
||||||
method={request.method}
|
method={activeRequest.method}
|
||||||
url={request.url}
|
url={activeRequest.url}
|
||||||
loading={sendRequest.isLoading}
|
loading={sendRequest.isLoading}
|
||||||
onMethodChange={(method) => updateRequest.mutate({ method })}
|
onMethodChange={(method) => updateRequest.mutate({ method })}
|
||||||
onUrlChange={(url) => updateRequest.mutate({ url })}
|
onUrlChange={(url) => updateRequest.mutate({ url })}
|
||||||
@@ -41,15 +45,15 @@ export function RequestPane({ fullHeight, request, className }: Props) {
|
|||||||
label="Request body"
|
label="Request body"
|
||||||
>
|
>
|
||||||
<TabContent value="headers" className="pl-2">
|
<TabContent value="headers" className="pl-2">
|
||||||
<HeaderEditor key={request.id} request={request} />
|
<HeaderEditor key={activeRequest.id} request={activeRequest} />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="body">
|
<TabContent value="body">
|
||||||
<Editor
|
<Editor
|
||||||
key={request.id}
|
key={activeRequest.id}
|
||||||
className="!bg-gray-50"
|
className="!bg-gray-50"
|
||||||
heightMode={fullHeight ? 'full' : 'auto'}
|
heightMode={fullHeight ? 'full' : 'auto'}
|
||||||
useTemplating
|
useTemplating
|
||||||
defaultValue={request.body ?? ''}
|
defaultValue={activeRequest.body ?? ''}
|
||||||
contentType="application/graphql+json"
|
contentType="application/graphql+json"
|
||||||
onChange={(body) => updateRequest.mutate({ body })}
|
onChange={(body) => updateRequest.mutate({ body })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDeleteAllResponses, useDeleteResponse, useResponses } from '../hooks/useResponses';
|
import { useDeleteResponses } from '../hooks/useDeleteResponses';
|
||||||
|
import { useDeleteResponse } from '../hooks/useResponseDelete';
|
||||||
|
import { useResponses } from '../hooks/useResponses';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import { Dropdown } from './Dropdown';
|
import { Dropdown } from './Dropdown';
|
||||||
import { Editor } from './Editor';
|
import { Editor } from './Editor';
|
||||||
@@ -11,23 +13,22 @@ import { StatusColor } from './StatusColor';
|
|||||||
import { Webview } from './Webview';
|
import { Webview } from './Webview';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
requestId: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ResponsePane = memo(function ResponsePane({ requestId, className }: Props) {
|
export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||||
const [activeResponseId, setActiveResponseId] = useState<string | null>(null);
|
const [activeResponseId, setActiveResponseId] = useState<string | null>(null);
|
||||||
const [viewMode, setViewMode] = useState<'pretty' | 'raw'>('pretty');
|
const [viewMode, setViewMode] = useState<'pretty' | 'raw'>('pretty');
|
||||||
const responses = useResponses(requestId);
|
const responses = useResponses();
|
||||||
const response = activeResponseId
|
const response = activeResponseId
|
||||||
? responses.data.find((r) => r.id === activeResponseId)
|
? responses.find((r) => r.id === activeResponseId)
|
||||||
: responses.data[responses.data.length - 1];
|
: responses[responses.length - 1];
|
||||||
const deleteResponse = useDeleteResponse(response);
|
const deleteResponse = useDeleteResponse(response);
|
||||||
const deleteAllResponses = useDeleteAllResponses(response?.requestId);
|
const deleteAllResponses = useDeleteResponses(response?.requestId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveResponseId(null);
|
setActiveResponseId(null);
|
||||||
}, [responses.data?.length]);
|
}, [responses.length]);
|
||||||
|
|
||||||
const contentType = useMemo(
|
const contentType = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -35,10 +36,6 @@ export const ResponsePane = memo(function ResponsePane({ requestId, className }:
|
|||||||
[response],
|
[response],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div
|
<div
|
||||||
@@ -51,56 +48,56 @@ export const ResponsePane = memo(function ResponsePane({ requestId, className }:
|
|||||||
>
|
>
|
||||||
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
|
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
|
||||||
{/*</HStack>*/}
|
{/*</HStack>*/}
|
||||||
|
<HStack
|
||||||
|
alignItems="center"
|
||||||
|
className="italic text-gray-700 text-sm w-full mb-1 flex-shrink-0 pl-2"
|
||||||
|
>
|
||||||
|
{response && response.status > 0 && (
|
||||||
|
<div className="whitespace-nowrap">
|
||||||
|
<StatusColor statusCode={response.status}>
|
||||||
|
{response.status}
|
||||||
|
{response.statusReason && ` ${response.statusReason}`}
|
||||||
|
</StatusColor>
|
||||||
|
•
|
||||||
|
{response.elapsed}ms •
|
||||||
|
{Math.round(response.body.length / 1000)} KB
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HStack alignItems="center" className="ml-auto h-8">
|
||||||
|
<IconButton
|
||||||
|
icon={viewMode === 'pretty' ? 'eye' : 'code'}
|
||||||
|
size="sm"
|
||||||
|
className="ml-1"
|
||||||
|
onClick={() => setViewMode((m) => (m === 'pretty' ? 'raw' : 'pretty'))}
|
||||||
|
/>
|
||||||
|
<Dropdown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: 'Clear Response',
|
||||||
|
onSelect: deleteResponse.mutate,
|
||||||
|
disabled: responses.length === 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Clear All Responses',
|
||||||
|
onSelect: deleteAllResponses.mutate,
|
||||||
|
disabled: responses.length === 0,
|
||||||
|
},
|
||||||
|
'-----',
|
||||||
|
...responses.slice(0, 10).map((r) => ({
|
||||||
|
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||||
|
leftSlot: response?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||||
|
onSelect: () => setActiveResponseId(r.id),
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<IconButton icon="clock" className="ml-auto" size="sm" />
|
||||||
|
</Dropdown>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
{response && (
|
{response && (
|
||||||
<>
|
<>
|
||||||
<HStack
|
|
||||||
alignItems="center"
|
|
||||||
className="italic text-gray-700 text-sm w-full mb-1 flex-shrink-0 pl-2"
|
|
||||||
>
|
|
||||||
{response.status > 0 && (
|
|
||||||
<div className="whitespace-nowrap">
|
|
||||||
<StatusColor statusCode={response.status}>
|
|
||||||
{response.status}
|
|
||||||
{response.statusReason && ` ${response.statusReason}`}
|
|
||||||
</StatusColor>
|
|
||||||
•
|
|
||||||
{response.elapsed}ms •
|
|
||||||
{Math.round(response.body.length / 1000)} KB
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HStack alignItems="center" className="ml-auto h-8">
|
|
||||||
<IconButton
|
|
||||||
icon={viewMode === 'pretty' ? 'eye' : 'code'}
|
|
||||||
size="sm"
|
|
||||||
className="ml-1"
|
|
||||||
onClick={() => setViewMode((m) => (m === 'pretty' ? 'raw' : 'pretty'))}
|
|
||||||
/>
|
|
||||||
<Dropdown
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
label: 'Clear Response',
|
|
||||||
onSelect: deleteResponse.mutate,
|
|
||||||
disabled: responses.data.length === 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Clear All Responses',
|
|
||||||
onSelect: deleteAllResponses.mutate,
|
|
||||||
disabled: responses.data.length === 0,
|
|
||||||
},
|
|
||||||
'-----',
|
|
||||||
...responses.data.slice(0, 10).map((r) => ({
|
|
||||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
|
||||||
leftSlot: response?.id === r.id ? <Icon icon="check" /> : <></>,
|
|
||||||
onSelect: () => setActiveResponseId(r.id),
|
|
||||||
})),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<IconButton icon="clock" className="ml-auto" size="sm" />
|
|
||||||
</Dropdown>
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{response?.error ? (
|
{response?.error ? (
|
||||||
<div className="p-1">
|
<div className="p-1">
|
||||||
<div className="text-white bg-red-500 px-3 py-2 rounded">{response.error}</div>
|
<div className="text-white bg-red-500 px-3 py-2 rounded">{response.error}</div>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRequestCreate, useRequestUpdate } from '../hooks/useRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
|
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||||
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { ButtonLink } from './ButtonLink';
|
import { ButtonLink } from './ButtonLink';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
@@ -9,14 +12,13 @@ import { HStack, VStack } from './Stacks';
|
|||||||
import { WindowDragRegion } from './WindowDragRegion';
|
import { WindowDragRegion } from './WindowDragRegion';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
workspaceId: string;
|
|
||||||
requests: HttpRequest[];
|
|
||||||
activeRequestId?: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({ className, activeRequestId, workspaceId, requests }: Props) {
|
export function Sidebar({ className }: Props) {
|
||||||
const createRequest = useRequestCreate({ workspaceId, navigateAfter: true });
|
const requests = useRequests();
|
||||||
|
const activeRequest = useActiveRequest();
|
||||||
|
const createRequest = useCreateRequest({ navigateAfter: true });
|
||||||
const { appearance, toggleAppearance } = useTheme();
|
const { appearance, toggleAppearance } = useTheme();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -36,7 +38,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests }: P
|
|||||||
</HStack>
|
</HStack>
|
||||||
<VStack as="ul" className="py-3 px-2 overflow-auto h-full" space={1}>
|
<VStack as="ul" className="py-3 px-2 overflow-auto h-full" space={1}>
|
||||||
{requests.map((r) => (
|
{requests.map((r) => (
|
||||||
<SidebarItem key={r.id} request={r} active={r.id === activeRequestId} />
|
<SidebarItem key={r.id} request={r} active={r.id === activeRequest?.id} />
|
||||||
))}
|
))}
|
||||||
{/*<Colors />*/}
|
{/*<Colors />*/}
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests }: P
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
||||||
const updateRequest = useRequestUpdate(request);
|
const updateRequest = useUpdateRequest(request);
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
const handleSubmitNameEdit = async (el: HTMLInputElement) => {
|
const handleSubmitNameEdit = async (el: HTMLInputElement) => {
|
||||||
await updateRequest.mutate({ name: el.value });
|
await updateRequest.mutate({ name: el.value });
|
||||||
|
|||||||
20
src-web/hooks/useActiveRequest.ts
Normal file
20
src-web/hooks/useActiveRequest.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { useRequests } from './useRequests';
|
||||||
|
|
||||||
|
export function useActiveRequest(): HttpRequest | null {
|
||||||
|
const params = useParams<{ requestId?: string }>();
|
||||||
|
const requests = useRequests();
|
||||||
|
const [activeRequest, setActiveRequest] = useState<HttpRequest | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (requests.length === 0) {
|
||||||
|
setActiveRequest(null);
|
||||||
|
} else {
|
||||||
|
setActiveRequest(requests.find((r) => r.id === params.requestId) ?? null);
|
||||||
|
}
|
||||||
|
}, [requests, params.requestId]);
|
||||||
|
|
||||||
|
return activeRequest;
|
||||||
|
}
|
||||||
20
src-web/hooks/useActiveWorkspace.ts
Normal file
20
src-web/hooks/useActiveWorkspace.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import type { Workspace } from '../lib/models';
|
||||||
|
import { useWorkspaces } from './useWorkspaces';
|
||||||
|
|
||||||
|
export function useActiveWorkspace(): Workspace | null {
|
||||||
|
const params = useParams<{ workspaceId?: string }>();
|
||||||
|
const workspaces = useWorkspaces();
|
||||||
|
const [activeWorkspace, setActiveWorkspace] = useState<Workspace | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
setActiveWorkspace(null);
|
||||||
|
} else {
|
||||||
|
setActiveWorkspace(workspaces.find((w) => w.id === params.workspaceId) ?? null);
|
||||||
|
}
|
||||||
|
}, [workspaces, params.workspaceId]);
|
||||||
|
|
||||||
|
return activeWorkspace;
|
||||||
|
}
|
||||||
23
src-web/hooks/useCreateRequest.ts
Normal file
23
src-web/hooks/useCreateRequest.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||||
|
|
||||||
|
export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean }) {
|
||||||
|
const workspace = useActiveWorkspace();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return useMutation<string, unknown, Pick<HttpRequest, 'name'>>({
|
||||||
|
mutationFn: async (patch) => {
|
||||||
|
if (workspace === null) {
|
||||||
|
throw new Error("Cannot create request when there's no active workspace");
|
||||||
|
}
|
||||||
|
return invoke('create_request', { ...patch, workspaceId: workspace?.id });
|
||||||
|
},
|
||||||
|
onSuccess: async (requestId) => {
|
||||||
|
if (navigateAfter) {
|
||||||
|
navigate(`/workspaces/${workspace?.id}/requests/${requestId}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
18
src-web/hooks/useDeleteRequest.ts
Normal file
18
src-web/hooks/useDeleteRequest.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { requestsQueryKey } from './useRequests';
|
||||||
|
|
||||||
|
export function useDeleteRequest(request: HttpRequest | null) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<void, string>({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (request == null) return;
|
||||||
|
await invoke('delete_request', { requestId: request.id });
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
if (request == null) return;
|
||||||
|
await queryClient.invalidateQueries(requestsQueryKey(request.workspaceId));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
16
src-web/hooks/useDeleteResponses.ts
Normal file
16
src-web/hooks/useDeleteResponses.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
|
||||||
|
export function useDeleteResponses(requestId?: string) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (requestId == null) return;
|
||||||
|
await invoke('delete_all_responses', { requestId });
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
if (requestId == null) return;
|
||||||
|
queryClient.setQueryData(['responses', { requestId: requestId }], []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { invoke } from '@tauri-apps/api';
|
|
||||||
import { useNavigate, useNavigation } from 'react-router-dom';
|
|
||||||
import type { HttpRequest } from '../lib/models';
|
|
||||||
import { convertDates } from '../lib/models';
|
|
||||||
import { responsesQueryKey } from './useResponses';
|
|
||||||
|
|
||||||
export function requestsQueryKey(workspaceId: string) {
|
|
||||||
return ['requests', { workspaceId }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRequests(workspaceId: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: requestsQueryKey(workspaceId),
|
|
||||||
queryFn: async () => {
|
|
||||||
const requests = (await invoke('requests', { workspaceId })) as HttpRequest[];
|
|
||||||
return requests.map(convertDates);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRequestUpdate(request: HttpRequest | null) {
|
|
||||||
return useMutation<void, unknown, Partial<HttpRequest>>({
|
|
||||||
mutationFn: async (patch) => {
|
|
||||||
if (request == null) {
|
|
||||||
throw new Error("Can't update a null request");
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRequest = { ...request, ...patch };
|
|
||||||
console.log('UPDATE REQUEST', updatedRequest.url);
|
|
||||||
|
|
||||||
await invoke('update_request', {
|
|
||||||
request: {
|
|
||||||
...updatedRequest,
|
|
||||||
createdAt: updatedRequest.createdAt.toISOString().replace('Z', ''),
|
|
||||||
updatedAt: updatedRequest.updatedAt.toISOString().replace('Z', ''),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRequestCreate({
|
|
||||||
workspaceId,
|
|
||||||
navigateAfter,
|
|
||||||
}: {
|
|
||||||
workspaceId: string;
|
|
||||||
navigateAfter: boolean;
|
|
||||||
}) {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return useMutation<string, unknown, Pick<HttpRequest, 'name'>>({
|
|
||||||
mutationFn: async (patch) => invoke('create_request', { ...patch, workspaceId }),
|
|
||||||
onSuccess: async (requestId) => {
|
|
||||||
if (navigateAfter) {
|
|
||||||
navigate(`/workspaces/${workspaceId}/requests/${requestId}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSendRequest(request: HttpRequest | null) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation<void, string>({
|
|
||||||
mutationFn: async () => {
|
|
||||||
if (request == null) return;
|
|
||||||
await invoke('send_request', { requestId: request.id });
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
if (request == null) return;
|
|
||||||
await queryClient.invalidateQueries(responsesQueryKey(request.id));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteRequest(request: HttpRequest | null) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation<void, string>({
|
|
||||||
mutationFn: async () => {
|
|
||||||
if (request == null) return;
|
|
||||||
await invoke('delete_request', { requestId: request.id });
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
if (request == null) return;
|
|
||||||
await queryClient.invalidateQueries(requestsQueryKey(request.workspaceId));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
24
src-web/hooks/useRequests.ts
Normal file
24
src-web/hooks/useRequests.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { convertDates } from '../lib/models';
|
||||||
|
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||||
|
|
||||||
|
export function requestsQueryKey(workspaceId: string) {
|
||||||
|
return ['requests', { workspaceId }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRequests() {
|
||||||
|
const workspace = useActiveWorkspace();
|
||||||
|
return (
|
||||||
|
useQuery({
|
||||||
|
enabled: workspace != null,
|
||||||
|
queryKey: requestsQueryKey(workspace?.id ?? 'n/a'),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (workspace == null) return [];
|
||||||
|
const requests = (await invoke('requests', { workspaceId: workspace.id })) as HttpRequest[];
|
||||||
|
return requests.map(convertDates);
|
||||||
|
},
|
||||||
|
}).data ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
20
src-web/hooks/useResponseDelete.ts
Normal file
20
src-web/hooks/useResponseDelete.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { HttpResponse } from '../lib/models';
|
||||||
|
|
||||||
|
export function useDeleteResponse(response?: HttpResponse) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (response == null) return;
|
||||||
|
await invoke('delete_response', { id: response.id });
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
if (response == null) return;
|
||||||
|
queryClient.setQueryData(
|
||||||
|
['responses', { requestId: response.requestId }],
|
||||||
|
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,50 +1,26 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { convertDates } from '../lib/models';
|
import { convertDates } from '../lib/models';
|
||||||
|
import { useActiveRequest } from './useActiveRequest';
|
||||||
|
|
||||||
export function responsesQueryKey(requestId: string) {
|
export function responsesQueryKey(requestId: string) {
|
||||||
return ['responses', { requestId }];
|
return ['responses', { requestId }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useResponses(requestId: string) {
|
export function useResponses() {
|
||||||
return useQuery<HttpResponse[]>({
|
const activeRequest = useActiveRequest();
|
||||||
initialData: [],
|
return (
|
||||||
queryKey: responsesQueryKey(requestId),
|
useQuery<HttpResponse[]>({
|
||||||
queryFn: async () => {
|
enabled: activeRequest != null,
|
||||||
const responses = (await invoke('responses', { requestId })) as HttpResponse[];
|
initialData: [],
|
||||||
return responses.map(convertDates);
|
queryKey: responsesQueryKey(activeRequest?.id ?? 'n/a'),
|
||||||
},
|
queryFn: async () => {
|
||||||
});
|
const responses = (await invoke('responses', {
|
||||||
}
|
requestId: activeRequest?.id,
|
||||||
|
})) as HttpResponse[];
|
||||||
export function useDeleteResponse(response?: HttpResponse) {
|
return responses.map(convertDates);
|
||||||
const queryClient = useQueryClient();
|
},
|
||||||
return useMutation({
|
}).data ?? []
|
||||||
mutationFn: async () => {
|
);
|
||||||
if (response == null) return;
|
|
||||||
await invoke('delete_response', { id: response.id });
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
if (response == null) return;
|
|
||||||
queryClient.setQueryData(
|
|
||||||
['responses', { requestId: response.requestId }],
|
|
||||||
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteAllResponses(requestId?: string) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
if (requestId == null) return;
|
|
||||||
await invoke('delete_all_responses', { requestId });
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
if (requestId == null) return;
|
|
||||||
queryClient.setQueryData(['responses', { requestId: requestId }], []);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
src-web/hooks/useSendRequest.ts
Normal file
18
src-web/hooks/useSendRequest.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { responsesQueryKey } from './useResponses';
|
||||||
|
|
||||||
|
export function useSendRequest(request: HttpRequest | null) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<void, string>({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (request == null) return;
|
||||||
|
await invoke('send_request', { requestId: request.id });
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
if (request == null) return;
|
||||||
|
await queryClient.invalidateQueries(responsesQueryKey(request.id));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
23
src-web/hooks/useUpdateRequest.ts
Normal file
23
src-web/hooks/useUpdateRequest.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
|
||||||
|
export function useUpdateRequest(request: HttpRequest | null) {
|
||||||
|
return useMutation<void, unknown, Partial<HttpRequest>>({
|
||||||
|
mutationFn: async (patch) => {
|
||||||
|
if (request == null) {
|
||||||
|
throw new Error("Can't update a null request");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedRequest = { ...request, ...patch };
|
||||||
|
|
||||||
|
await invoke('update_request', {
|
||||||
|
request: {
|
||||||
|
...updatedRequest,
|
||||||
|
createdAt: updatedRequest.createdAt.toISOString().replace('Z', ''),
|
||||||
|
updatedAt: updatedRequest.updatedAt.toISOString().replace('Z', ''),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import { convertDates } from '../lib/models';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
export function useWorkspaces() {
|
export function useWorkspaces() {
|
||||||
return useQuery(['workspaces'], async () => {
|
return (
|
||||||
const workspaces = (await invoke('workspaces')) as Workspace[];
|
useQuery(['workspaces'], async () => {
|
||||||
return workspaces.map(convertDates);
|
const workspaces = (await invoke('workspaces')) as Workspace[];
|
||||||
});
|
return workspaces.map(convertDates);
|
||||||
|
}).data ?? []
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,40 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import { HeaderEditor } from '../components/HeaderEditor';
|
|
||||||
import { RequestPane } from '../components/RequestPane';
|
import { RequestPane } from '../components/RequestPane';
|
||||||
import { ResponsePane } from '../components/ResponsePane';
|
import { ResponsePane } from '../components/ResponsePane';
|
||||||
import { Sidebar } from '../components/Sidebar';
|
import { Sidebar } from '../components/Sidebar';
|
||||||
import { HStack } from '../components/Stacks';
|
import { HStack } from '../components/Stacks';
|
||||||
import { WindowDragRegion } from '../components/WindowDragRegion';
|
import { WindowDragRegion } from '../components/WindowDragRegion';
|
||||||
import { useRequests } from '../hooks/useRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
|
|
||||||
type Params = {
|
|
||||||
workspaceId: string;
|
|
||||||
requestId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Workspace() {
|
export default function Workspace() {
|
||||||
const params = useParams<Params>();
|
const activeRequest = useActiveRequest();
|
||||||
const workspaceId = params?.workspaceId ?? '';
|
|
||||||
const { data: requests } = useRequests(workspaceId);
|
|
||||||
const request = requests?.find((r) => r.id === params?.requestId);
|
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
const isH = width > 900;
|
const isH = width > 900;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-[auto_1fr] grid-rows-1 h-full text-gray-900">
|
<div className="grid grid-cols-[auto_1fr] grid-rows-1 h-full text-gray-900">
|
||||||
<Sidebar
|
<Sidebar />
|
||||||
requests={requests ?? []}
|
<div className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
|
||||||
workspaceId={workspaceId}
|
<HStack
|
||||||
activeRequestId={params?.requestId}
|
as={WindowDragRegion}
|
||||||
/>
|
className="px-3 bg-gray-50 text-gray-900 border-b border-b-gray-200 pt-[1px]"
|
||||||
{request && (
|
alignItems="center"
|
||||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
|
>
|
||||||
<HStack
|
{activeRequest?.name}
|
||||||
as={WindowDragRegion}
|
</HStack>
|
||||||
className="px-3 bg-gray-50 text-gray-900 border-b border-b-gray-200 pt-[1px]"
|
<div
|
||||||
alignItems="center"
|
className={classnames(
|
||||||
>
|
'grid',
|
||||||
{request.name}
|
isH
|
||||||
</HStack>
|
? 'grid-cols-[1fr_1fr] grid-rows-1'
|
||||||
<div
|
: 'grid-cols-1 grid-rows-[minmax(0,auto)_minmax(0,100%)]',
|
||||||
className={classnames(
|
)}
|
||||||
'grid',
|
>
|
||||||
isH
|
<RequestPane fullHeight={isH} className={classnames(!isH && 'pr-2 pb-0')} />
|
||||||
? 'grid-cols-[1fr_1fr] grid-rows-1'
|
<ResponsePane />
|
||||||
: 'grid-cols-1 grid-rows-[minmax(0,auto)_minmax(0,100%)]',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<RequestPane
|
|
||||||
fullHeight={isH}
|
|
||||||
request={request}
|
|
||||||
className={classnames(!isH && 'pr-2 pb-0')}
|
|
||||||
/>
|
|
||||||
<ResponsePane requestId={request.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function Workspaces() {
|
|||||||
return (
|
return (
|
||||||
<VStack as="ul" className="p-12">
|
<VStack as="ul" className="p-12">
|
||||||
<Heading>Workspaces</Heading>
|
<Heading>Workspaces</Heading>
|
||||||
{workspaces.data?.map((w) => (
|
{workspaces.map((w) => (
|
||||||
<ButtonLink key={w.id} color="gray" to={`/workspaces/${w.id}`}>
|
<ButtonLink key={w.id} color="gray" to={`/workspaces/${w.id}`}>
|
||||||
{w.name}
|
{w.name}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
|
|||||||
Reference in New Issue
Block a user