From 0949de66bfda349e7598d9edcf6a920ebab935f5 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 16 Mar 2023 09:24:28 -0700 Subject: [PATCH] Hotkeys and view mode kvs --- package-lock.json | 148 +++++++++++++++++++++++--- package.json | 7 +- src-tauri/src/main.rs | 4 + src-web/components/App.tsx | 21 +++- src-web/components/AppRouter.tsx | 7 +- src-web/components/RequestPane.tsx | 8 +- src-web/components/ResponsePane.tsx | 8 +- src-web/components/UrlBar.tsx | 1 + src-web/hooks/useDeleteResponses.ts | 3 +- src-web/hooks/useIsResponseLoading.ts | 8 ++ src-web/hooks/useKeyValues.ts | 39 +++++-- src-web/hooks/useResponseViewMode.ts | 15 +++ src-web/hooks/useSendRequest.ts | 2 +- src-web/lib/pluralize.ts | 6 ++ 14 files changed, 239 insertions(+), 38 deletions(-) create mode 100644 src-web/hooks/useIsResponseLoading.ts create mode 100644 src-web/hooks/useResponseViewMode.ts create mode 100644 src-web/lib/pluralize.ts diff --git a/package-lock.json b/package-lock.json index 9d644caa..a23bbb41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@radix-ui/react-separator": "^1.0.1", "@radix-ui/react-tabs": "^1.0.3", "@tanstack/react-query": "^4.24.10", + "@tanstack/react-query-devtools": "^4.26.1", "@tauri-apps/api": "^1.2.0", "@vitejs/plugin-react": "^3.1.0", "classnames": "^2.3.2", @@ -1987,21 +1988,36 @@ "postcss": "^8.2.14" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.7.6", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz", + "integrity": "sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, "node_modules/@tanstack/query-core": { - "version": "4.24.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.10.tgz", - "integrity": "sha512-2QywqXEAGBIUoTdgn1lAB4/C8QEqwXHj2jrCLeYTk2xVGtLiPEUD8jcMoeB2noclbiW2mMt4+Fq7fZStuz3wAQ==", + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.26.1.tgz", + "integrity": "sha512-Zrx2pVQUP4ndnsu6+K/m8zerXSVY8QM+YSbxA1/jbBY21GeCd5oKfYl92oXPK0hPEUtoNuunIdiq0ZMqLos+Zg==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "4.24.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.24.10.tgz", - "integrity": "sha512-FY1DixytOcNNCydPQXLxuKEV7VSST32CAuJ55BjhDNqASnMLZn+6c30yQBMrODjmWMNwzfjMZnq0Vw7C62Fwow==", + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.26.1.tgz", + "integrity": "sha512-i3dnz4TOARGIXrXQ5P7S25Zfi4noii/bxhcwPurh2nrf5EUCcAt/95TB2HSmMweUBx206yIMWUMEQ7ptd6zwDg==", "dependencies": { - "@tanstack/query-core": "4.24.10", + "@tanstack/query-core": "4.26.1", "use-sync-external-store": "^1.2.0" }, "funding": { @@ -2022,6 +2038,25 @@ } } }, + "node_modules/@tanstack/react-query-devtools": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.26.1.tgz", + "integrity": "sha512-ts2mA+fyFYFRi3Cee4xBk8Fx6waSFOM+yCkFqwJfGQRGjjTIMYMZPJv4wkv7vy12IVi1SYhL8au22LRKlXS1Zg==", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.26.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@tauri-apps/api": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz", @@ -3418,6 +3453,20 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/copy-anything": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.3.tgz", + "integrity": "sha512-fpW2W/BqEzqPp29QS+MwwfisHCQZtiduTe/m8idFo0xbti9fIZ2WVhAsCv4ggFVH3AgCkVdpoOCtQC6gBrdhjw==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -5411,6 +5460,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz", + "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -6597,6 +6657,11 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7107,6 +7172,17 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, + "node_modules/superjson": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.2.tgz", + "integrity": "sha512-ugvUo9/WmvWOjstornQhsN/sR9mnGtWGYeTxFuqLb4AiT4QdUavjGFRALCPKWWnAiUJ4HTpytj5e0t5HoMRkXg==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9263,17 +9339,35 @@ } } }, + "@tanstack/match-sorter-utils": { + "version": "8.7.6", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz", + "integrity": "sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==", + "requires": { + "remove-accents": "0.4.2" + } + }, "@tanstack/query-core": { - "version": "4.24.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.10.tgz", - "integrity": "sha512-2QywqXEAGBIUoTdgn1lAB4/C8QEqwXHj2jrCLeYTk2xVGtLiPEUD8jcMoeB2noclbiW2mMt4+Fq7fZStuz3wAQ==" + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.26.1.tgz", + "integrity": "sha512-Zrx2pVQUP4ndnsu6+K/m8zerXSVY8QM+YSbxA1/jbBY21GeCd5oKfYl92oXPK0hPEUtoNuunIdiq0ZMqLos+Zg==" }, "@tanstack/react-query": { - "version": "4.24.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.24.10.tgz", - "integrity": "sha512-FY1DixytOcNNCydPQXLxuKEV7VSST32CAuJ55BjhDNqASnMLZn+6c30yQBMrODjmWMNwzfjMZnq0Vw7C62Fwow==", + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.26.1.tgz", + "integrity": "sha512-i3dnz4TOARGIXrXQ5P7S25Zfi4noii/bxhcwPurh2nrf5EUCcAt/95TB2HSmMweUBx206yIMWUMEQ7ptd6zwDg==", "requires": { - "@tanstack/query-core": "4.24.10", + "@tanstack/query-core": "4.26.1", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.26.1.tgz", + "integrity": "sha512-ts2mA+fyFYFRi3Cee4xBk8Fx6waSFOM+yCkFqwJfGQRGjjTIMYMZPJv4wkv7vy12IVi1SYhL8au22LRKlXS1Zg==", + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", "use-sync-external-store": "^1.2.0" } }, @@ -10226,6 +10320,14 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "copy-anything": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.3.tgz", + "integrity": "sha512-fpW2W/BqEzqPp29QS+MwwfisHCQZtiduTe/m8idFo0xbti9fIZ2WVhAsCv4ggFVH3AgCkVdpoOCtQC6gBrdhjw==", + "requires": { + "is-what": "^4.1.8" + } + }, "copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -11711,6 +11813,11 @@ "get-intrinsic": "^1.1.1" } }, + "is-what": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz", + "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==" + }, "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -12545,6 +12652,11 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12928,6 +13040,14 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, + "superjson": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.2.tgz", + "integrity": "sha512-ugvUo9/WmvWOjstornQhsN/sR9mnGtWGYeTxFuqLb4AiT4QdUavjGFRALCPKWWnAiUJ4HTpytj5e0t5HoMRkXg==", + "requires": { + "copy-anything": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 748994b1..bd76b7ca 100644 --- a/package.json +++ b/package.json @@ -33,18 +33,19 @@ "@radix-ui/react-separator": "^1.0.1", "@radix-ui/react-tabs": "^1.0.3", "@tanstack/react-query": "^4.24.10", + "@tanstack/react-query-devtools": "^4.26.1", "@tauri-apps/api": "^1.2.0", "@vitejs/plugin-react": "^3.1.0", "classnames": "^2.3.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.8.1", "cm6-graphql": "^0.0.4-canary-b30a2325.0", "codemirror": "^6.0.1", "format-graphql": "^1.4.0", "framer-motion": "^9.0.4", "parse-color": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", + "react-router-dom": "^6.8.1", "react-use": "^17.4.0" }, "devDependencies": { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 32ca35e5..c952da42 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -375,6 +375,8 @@ fn main() { let default_menu = Menu::os_default("Yaak".to_string().as_str()); let submenu = Submenu::new("Test Menu", Menu::new() + .add_item(CustomMenuItem::new("refresh".to_string(), "Refresh").accelerator("CmdOrCtrl+Shift+r")) + .add_item(CustomMenuItem::new("send_request".to_string(), "Send Request").accelerator("CmdOrCtrl+r")) .add_item(CustomMenuItem::new("zoom_reset".to_string(), "Zoom to Actual Size").accelerator("CmdOrCtrl+0")) .add_item(CustomMenuItem::new("zoom_in".to_string(), "Zoom In").accelerator("CmdOrCtrl+Plus")) .add_item(CustomMenuItem::new("zoom_out".to_string(), "Zoom Out").accelerator("CmdOrCtrl+-")), @@ -438,6 +440,8 @@ fn main() { "zoom_reset" => event.window().emit("zoom", 0).unwrap(), "zoom_in" => event.window().emit("zoom", 1).unwrap(), "zoom_out" => event.window().emit("zoom", -1).unwrap(), + "refresh" => event.window().emit("refresh", true).unwrap(), + "send_request" => event.window().emit("send_request", true).unwrap(), _ => {} }; }) diff --git a/src-web/components/App.tsx b/src-web/components/App.tsx index c9163e23..af0dbe9c 100644 --- a/src-web/components/App.tsx +++ b/src-web/components/App.tsx @@ -1,19 +1,22 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { invoke } from '@tauri-apps/api'; import { listen } from '@tauri-apps/api/event'; import { MotionConfig } from 'framer-motion'; import { HelmetProvider } from 'react-helmet-async'; +import { matchPath } from 'react-router-dom'; import { keyValueQueryKey } from '../hooks/useKeyValues'; import { requestsQueryKey } from '../hooks/useRequests'; import { responsesQueryKey } from '../hooks/useResponses'; import { DEFAULT_FONT_SIZE } from '../lib/constants'; import type { HttpRequest, HttpResponse, KeyValue } from '../lib/models'; import { convertDates } from '../lib/models'; -import { AppRouter } from './AppRouter'; +import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter'; const queryClient = new QueryClient(); await listen('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => { - queryClient.setQueryData(keyValueQueryKey(keyValue.namespace, keyValue.key), keyValue); + queryClient.setQueryData(keyValueQueryKey(keyValue), keyValue); }); await listen('updated_request', ({ payload: request }: { payload: HttpRequest }) => { @@ -66,6 +69,19 @@ await listen('updated_response', ({ payload: response }: { payload: HttpResponse ); }); +await listen('send_request', async () => { + const params = matchPath(WORKSPACE_REQUEST_PATH, window.location.pathname); + const requestId = params?.params.requestId; + if (typeof requestId !== 'string') { + return; + } + await invoke('send_request', { requestId }); +}); + +await listen('refresh', () => { + location.reload(); +}); + await listen('zoom', ({ payload: zoomDelta }: { payload: number }) => { const fontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize); @@ -87,6 +103,7 @@ export function App() { + diff --git a/src-web/components/AppRouter.tsx b/src-web/components/AppRouter.tsx index ad7977a6..d3b4f28e 100644 --- a/src-web/components/AppRouter.tsx +++ b/src-web/components/AppRouter.tsx @@ -5,6 +5,9 @@ const Workspaces = lazy(() => import('./Workspaces')); const Workspace = lazy(() => import('./Workspace')); const RouteError = lazy(() => import('./RouteError')); +export const WORKSPACE_PATH = '/workspaces/:workspaceId'; +export const WORKSPACE_REQUEST_PATH = '/workspaces/:workspaceId/requests/:requestId'; + const router = createBrowserRouter([ { path: '/', @@ -15,11 +18,11 @@ const router = createBrowserRouter([ element: , }, { - path: '/workspaces/:workspaceId', + path: WORKSPACE_PATH, element: , }, { - path: '/workspaces/:workspaceId/requests/:requestId', + path: WORKSPACE_REQUEST_PATH, element: , }, ], diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index 064a2912..f6da2c14 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -1,12 +1,13 @@ import classnames from 'classnames'; import { useActiveRequest } from '../hooks/useActiveRequest'; +import { useIsResponseLoading } from '../hooks/useIsResponseLoading'; import { useSendRequest } from '../hooks/useSendRequest'; import { useUpdateRequest } from '../hooks/useUpdateRequest'; import { tryFormatJson } from '../lib/formatters'; import { Editor } from './core/Editor'; +import { PairEditor } from './core/PairEditor'; import { TabContent, Tabs } from './core/Tabs/Tabs'; import { GraphQLEditor } from './editors/GraphQLEditor'; -import { PairEditor } from './core/PairEditor'; import { UrlBar } from './UrlBar'; interface Props { @@ -18,6 +19,7 @@ export function RequestPane({ fullHeight, className }: Props) { const activeRequest = useActiveRequest(); const updateRequest = useUpdateRequest(activeRequest); const sendRequest = useSendRequest(activeRequest); + const responseLoading = useIsResponseLoading(); if (activeRequest === null) return null; @@ -27,10 +29,10 @@ export function RequestPane({ fullHeight, className }: Props) { key={activeRequest.id} method={activeRequest.method} url={activeRequest.url} - loading={sendRequest.isLoading} onMethodChange={(method) => updateRequest.mutate({ method })} onUrlChange={(url) => updateRequest.mutate({ url })} - sendRequest={sendRequest.mutate} + sendRequest={sendRequest} + loading={responseLoading} /> (null); - const [viewMode, setViewMode] = useState<'pretty' | 'raw'>('pretty'); const responses = useResponses(); const activeResponse: HttpResponse | null = activeResponseId ? responses.find((r) => r.id === activeResponseId) ?? null : responses[responses.length - 1] ?? null; + const [viewMode, toggleViewMode] = useResponseViewMode(activeResponse?.requestId); const deleteResponse = useDeleteResponse(activeResponse); const deleteAllResponses = useDeleteResponses(activeResponse?.requestId); @@ -74,7 +76,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) { items={[ { label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified', - onSelect: () => setViewMode((m) => (m === 'pretty' ? 'raw' : 'pretty')), + onSelect: toggleViewMode, }, '-----', { @@ -83,7 +85,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) { disabled: responses.length === 0, }, { - label: 'Clear All Responses', + label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`, onSelect: deleteAllResponses.mutate, disabled: responses.length === 0, }, diff --git a/src-web/components/UrlBar.tsx b/src-web/components/UrlBar.tsx index cac86574..60f18a4a 100644 --- a/src-web/components/UrlBar.tsx +++ b/src-web/components/UrlBar.tsx @@ -1,3 +1,4 @@ +import { useSendRequest } from '../hooks/useSendRequest'; import { Button } from './core/Button'; import { DropdownMenuRadio, DropdownMenuTrigger } from './core/Dropdown'; import { IconButton } from './core/IconButton'; diff --git a/src-web/hooks/useDeleteResponses.ts b/src-web/hooks/useDeleteResponses.ts index 7804692f..9c9134eb 100644 --- a/src-web/hooks/useDeleteResponses.ts +++ b/src-web/hooks/useDeleteResponses.ts @@ -1,5 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; +import { responsesQueryKey } from './useResponses'; export function useDeleteResponses(requestId?: string) { const queryClient = useQueryClient(); @@ -10,7 +11,7 @@ export function useDeleteResponses(requestId?: string) { }, onSuccess: () => { if (!requestId) return; - queryClient.setQueryData(['responses', { requestId: requestId }], []); + queryClient.setQueryData(responsesQueryKey(requestId), []); }, }); } diff --git a/src-web/hooks/useIsResponseLoading.ts b/src-web/hooks/useIsResponseLoading.ts new file mode 100644 index 00000000..bca79c5c --- /dev/null +++ b/src-web/hooks/useIsResponseLoading.ts @@ -0,0 +1,8 @@ +import { useResponses } from './useResponses'; + +export function useIsResponseLoading(): boolean { + const responses = useResponses(); + const response = responses[responses.length - 1]; + if (!response) return false; + return !(response.body || response.error); +} diff --git a/src-web/hooks/useKeyValues.ts b/src-web/hooks/useKeyValues.ts index 55b7b9b2..14948a29 100644 --- a/src-web/hooks/useKeyValues.ts +++ b/src-web/hooks/useKeyValues.ts @@ -2,25 +2,46 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; import type { KeyValue } from '../lib/models'; -export function keyValueQueryKey(namespace: string, key: string) { - return ['key_value', { namespace, key }]; +const DEFAULT_NAMESPACE = 'app'; + +export function keyValueQueryKey({ + namespace = DEFAULT_NAMESPACE, + key, +}: { + namespace: string; + key: string | string[]; +}) { + return ['key_value', { namespace, key: buildKey(key) }]; } -export function useKeyValues(namespace: string, key: string) { +export function useKeyValues({ + namespace = DEFAULT_NAMESPACE, + key, + initialValue, +}: { + namespace: string; + key: string | string[]; + initialValue: string; +}) { const query = useQuery({ initialData: null, - queryKey: keyValueQueryKey(namespace, key), - queryFn: async () => invoke('get_key_value', { namespace, key }), + queryKey: keyValueQueryKey({ namespace, key }), + queryFn: async () => invoke('get_key_value', { namespace, key: buildKey(key) }), }); - const mutate = useMutation({ + const mutate = useMutation({ mutationFn: (value) => { - return invoke('set_key_value', { namespace, key, value }); + return invoke('set_key_value', { namespace, key: buildKey(key), value }); }, }); return { - value: query.data?.value ?? null, - set: (value: KeyValue['value']) => mutate.mutate(value), + value: query.data?.value ?? initialValue, + set: (value: string) => mutate.mutate(value), }; } + +function buildKey(key: string | string[]): string { + if (typeof key === 'string') return key; + return key.join('::'); +} diff --git a/src-web/hooks/useResponseViewMode.ts b/src-web/hooks/useResponseViewMode.ts new file mode 100644 index 00000000..3c434d30 --- /dev/null +++ b/src-web/hooks/useResponseViewMode.ts @@ -0,0 +1,15 @@ +import { useKeyValues } from './useKeyValues'; + +export function useResponseViewMode(requestId?: string): [string, () => void] { + const v = useKeyValues({ + namespace: 'app', + key: ['response_view_mode', requestId ?? 'n/a'], + initialValue: 'pretty', + }); + + const toggle = () => { + v.set(v.value === 'pretty' ? 'raw' : 'pretty'); + }; + + return [v.value, toggle]; +} diff --git a/src-web/hooks/useSendRequest.ts b/src-web/hooks/useSendRequest.ts index e35b48c6..7b6c3261 100644 --- a/src-web/hooks/useSendRequest.ts +++ b/src-web/hooks/useSendRequest.ts @@ -14,5 +14,5 @@ export function useSendRequest(request: HttpRequest | null) { if (request == null) return; await queryClient.invalidateQueries(responsesQueryKey(request.id)); }, - }); + }).mutate; } diff --git a/src-web/lib/pluralize.ts b/src-web/lib/pluralize.ts new file mode 100644 index 00000000..2bdf9784 --- /dev/null +++ b/src-web/lib/pluralize.ts @@ -0,0 +1,6 @@ +export function pluralize(word: string, count: number): string { + if (count === 1) { + return word; + } + return `${word}s`; +}