From f45c898be0240fe538c24d2a37f97adf174d4fc0 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 13 Feb 2024 17:21:54 -0800 Subject: [PATCH] Better recent work/env/req logic --- src-tauri/src/main.rs | 12 ++++- src-web/components/AppRouter.tsx | 37 ++------------ src-web/components/GraphQLEditor.tsx | 2 +- src-web/components/RecentRequestsDropdown.tsx | 2 +- .../components/RedirectToLatestWorkspace.tsx | 29 +++++++++++ src-web/components/SidebarActions.tsx | 7 +++ .../components/WorkspaceActionsDropdown.tsx | 21 ++++++-- src-web/components/Workspaces.tsx | 25 ---------- src-web/hooks/useCreateGrpcRequest.ts | 10 +--- src-web/hooks/useCreateHttpRequest.ts | 10 +--- src-web/hooks/useRecentRequests.ts | 50 +++++++++++++++---- src-web/hooks/useRecentWorkspaces.ts | 35 ++++++++++--- 12 files changed, 138 insertions(+), 102 deletions(-) create mode 100644 src-web/components/RedirectToLatestWorkspace.tsx delete mode 100644 src-web/components/Workspaces.tsx diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bacd0bd3..ec8aeac5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -299,6 +299,7 @@ async fn cmd_grpc_go( match maybe_msg { Some(Ok(msg)) => { + println!("Message: {:?}", msg); upsert_grpc_message( &w, &GrpcMessage { @@ -329,7 +330,11 @@ async fn cmd_grpc_go( } let mut stream = match maybe_stream { - Some(Ok(Ok(s))) => s.into_inner(), + Some(Ok(Ok(s))) => { + // TODO: Store metadata on... connection? Or in a message + println!("METADATA: {:?}", s.metadata()); + s.into_inner() + } Some(Ok(Err(e))) => { // TODO: Make into error, and use status println!("Connection status error: {:?}", e); @@ -829,6 +834,8 @@ async fn cmd_create_http_request( name: &str, sort_priority: f64, folder_id: Option<&str>, + method: Option<&str>, + body_type: Option<&str>, w: Window, ) -> Result { upsert_http_request( @@ -836,8 +843,9 @@ async fn cmd_create_http_request( HttpRequest { workspace_id: workspace_id.to_string(), name: name.to_string(), - method: "GET".to_string(), folder_id: folder_id.map(|s| s.to_string()), + body_type: body_type.map(|s| s.to_string()), + method: method.map(|s| s.to_string()).unwrap_or("GET".to_string()), sort_priority, ..Default::default() }, diff --git a/src-web/components/AppRouter.tsx b/src-web/components/AppRouter.tsx index 8e55d76e..7e919036 100644 --- a/src-web/components/AppRouter.tsx +++ b/src-web/components/AppRouter.tsx @@ -1,13 +1,10 @@ import { createBrowserRouter, Navigate, Outlet, RouterProvider, useParams } from 'react-router-dom'; -import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId'; import { routePaths, useAppRoutes } from '../hooks/useAppRoutes'; -import { useHttpRequests } from '../hooks/useHttpRequests'; -import { useRecentRequests } from '../hooks/useRecentRequests'; import { DialogProvider } from './DialogContext'; import { GlobalHooks } from './GlobalHooks'; import RouteError from './RouteError'; import Workspace from './Workspace'; -import Workspaces from './Workspaces'; +import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace'; const router = createBrowserRouter([ { @@ -17,17 +14,17 @@ const router = createBrowserRouter([ children: [ { path: '/', - element: , + element: , }, { path: routePaths.workspaces(), - element: , + element: , }, { path: routePaths.workspace({ workspaceId: ':workspaceId', }), - element: , + element: , }, { path: routePaths.request({ @@ -48,32 +45,6 @@ export function AppRouter() { return ; } -function WorkspaceOrRedirect() { - const recentRequests = useRecentRequests(); - const requests = useHttpRequests(); - const request = requests.find((r) => r.id === recentRequests[0]); - const routes = useAppRoutes(); - - // Keep environment if it's in the query params - const environmentId = useActiveEnvironmentId() ?? undefined; - - if (request === undefined) { - return ; - } - - const { id: requestId, workspaceId } = request; - - return ( - - ); -} - function RedirectLegacyEnvironmentURLs() { const routes = useAppRoutes(); const { diff --git a/src-web/components/GraphQLEditor.tsx b/src-web/components/GraphQLEditor.tsx index 2b08f6cd..210d730e 100644 --- a/src-web/components/GraphQLEditor.tsx +++ b/src-web/components/GraphQLEditor.tsx @@ -38,7 +38,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi const operationName = p.operationName; return { query, variables, operationName }; } catch (err) { - return { query: 'failed to parse' }; + return { query: '' }; } }, [defaultValue]); diff --git a/src-web/components/RecentRequestsDropdown.tsx b/src-web/components/RecentRequestsDropdown.tsx index 97253cc2..9d143734 100644 --- a/src-web/components/RecentRequestsDropdown.tsx +++ b/src-web/components/RecentRequestsDropdown.tsx @@ -54,7 +54,7 @@ export function RecentRequestsDropdown({ className }: Pick(() => { if (activeWorkspaceId === null) return []; - const recentRequestItems: DropdownItem[] = [{ type: 'separator', label: 'Recent Requests' }]; + const recentRequestItems: DropdownItem[] = []; for (const id of recentRequestIds) { const request = requests.find((r) => r.id === id); if (request === undefined) continue; diff --git a/src-web/components/RedirectToLatestWorkspace.tsx b/src-web/components/RedirectToLatestWorkspace.tsx new file mode 100644 index 00000000..7a7cf52d --- /dev/null +++ b/src-web/components/RedirectToLatestWorkspace.tsx @@ -0,0 +1,29 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAppRoutes } from '../hooks/useAppRoutes'; +import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; +import { getRecentRequests } from '../hooks/useRecentRequests'; +import { getRecentWorkspaces } from '../hooks/useRecentWorkspaces'; +import { useWorkspaces } from '../hooks/useWorkspaces'; + +export function RedirectToLatestWorkspace() { + const navigate = useNavigate(); + const routes = useAppRoutes(); + const workspaces = useWorkspaces(); + + useEffect(() => { + (async function () { + const workspaceId = (await getRecentWorkspaces())[0] ?? workspaces[0]?.id ?? 'n/a'; + const environmentId = (await getRecentEnvironments(workspaceId))[0]; + const requestId = (await getRecentRequests(workspaceId))[0]; + + if (workspaceId != null && requestId != null) { + navigate(routes.paths.request({ workspaceId, environmentId, requestId })); + } else { + navigate(routes.paths.workspace({ workspaceId, environmentId })); + } + })(); + }, [navigate, routes.paths, workspaces, workspaces.length]); + + return <>; +} diff --git a/src-web/components/SidebarActions.tsx b/src-web/components/SidebarActions.tsx index c0e83312..ddfca943 100644 --- a/src-web/components/SidebarActions.tsx +++ b/src-web/components/SidebarActions.tsx @@ -4,6 +4,7 @@ import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest'; import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { trackEvent } from '../lib/analytics'; +import { BODY_TYPE_GRAPHQL } from '../lib/models'; import { Dropdown } from './core/Dropdown'; import { IconButton } from './core/IconButton'; import { HStack } from './core/Stacks'; @@ -44,6 +45,12 @@ export const SidebarActions = memo(function SidebarActions() { label: 'GRPC Request', onSelect: () => createGrpcRequest.mutate({}), }, + { + key: 'create-graphql-request', + label: 'GraphQL Request', + onSelect: () => + createHttpRequest.mutate({ bodyType: BODY_TYPE_GRAPHQL, method: 'POST' }), + }, { key: 'create-folder', label: 'Folder', diff --git a/src-web/components/WorkspaceActionsDropdown.tsx b/src-web/components/WorkspaceActionsDropdown.tsx index 728da6c0..c6ac19ec 100644 --- a/src-web/components/WorkspaceActionsDropdown.tsx +++ b/src-web/components/WorkspaceActionsDropdown.tsx @@ -7,6 +7,7 @@ import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { usePrompt } from '../hooks/usePrompt'; import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; +import { getRecentRequests } from '../hooks/useRecentRequests'; import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace'; import { useWorkspaces } from '../hooks/useWorkspaces'; import type { ButtonProps } from './core/Button'; @@ -63,7 +64,12 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ onClick={async () => { hide(); const environmentId = (await getRecentEnvironments(w.id))[0]; - routes.navigate('workspace', { workspaceId: w.id, environmentId }); + const requestId = (await getRecentRequests(w.id))[0]; + if (requestId != null) { + routes.navigate('request', { workspaceId: w.id, environmentId, requestId }); + } else { + routes.navigate('workspace', { workspaceId: w.id, environmentId }); + } }} > This Window @@ -75,9 +81,16 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ onClick={async () => { hide(); const environmentId = (await getRecentEnvironments(w.id))[0]; - await invoke('cmd_new_window', { - url: routes.paths.workspace({ workspaceId: w.id, environmentId }), - }); + const requestId = (await getRecentRequests(w.id))[0]; + const path = + requestId != null + ? routes.paths.request({ + workspaceId: w.id, + environmentId, + requestId, + }) + : routes.paths.workspace({ workspaceId: w.id, environmentId }); + await invoke('cmd_new_window', { url: path }); }} > New Window diff --git a/src-web/components/Workspaces.tsx b/src-web/components/Workspaces.tsx deleted file mode 100644 index f8759bca..00000000 --- a/src-web/components/Workspaces.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Navigate } from 'react-router-dom'; -import { useAppRoutes } from '../hooks/useAppRoutes'; -import { useWorkspaces } from '../hooks/useWorkspaces'; -import { Heading } from './core/Heading'; -import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; - -export default function Workspaces() { - const routes = useAppRoutes(); - const recentWorkspaceIds = useRecentWorkspaces(); - const workspaces = useWorkspaces(); - - const loading = workspaces.length === 0 && recentWorkspaceIds.length === 0; - if (loading) { - return null; - } - - const workspaceId = recentWorkspaceIds[0] ?? workspaces[0]?.id ?? null; - - if (workspaceId === null) { - return There are no workspaces; - } - - // TODO: Somehow get recent environmentId for the workspace in here too - return ; -} diff --git a/src-web/hooks/useCreateGrpcRequest.ts b/src-web/hooks/useCreateGrpcRequest.ts index 2b1b049c..37c35ca6 100644 --- a/src-web/hooks/useCreateGrpcRequest.ts +++ b/src-web/hooks/useCreateGrpcRequest.ts @@ -1,19 +1,16 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; import { trackEvent } from '../lib/analytics'; import type { GrpcRequest } from '../lib/models'; import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useAppRoutes } from './useAppRoutes'; -import { grpcRequestsQueryKey } from './useGrpcRequests'; export function useCreateGrpcRequest() { const workspaceId = useActiveWorkspaceId(); const activeEnvironmentId = useActiveEnvironmentId(); - // const activeRequest = useActiveRequest(); const activeRequest = null; const routes = useAppRoutes(); - const queryClient = useQueryClient(); return useMutation< GrpcRequest, @@ -38,11 +35,6 @@ export function useCreateGrpcRequest() { }, onSettled: () => trackEvent('GrpcRequest', 'Create'), onSuccess: async (request) => { - queryClient.setQueryData( - grpcRequestsQueryKey({ workspaceId: request.workspaceId }), - (requests) => [...(requests ?? []), request], - ); - // TODO: This should navigate to the new request routes.navigate('request', { workspaceId: request.workspaceId, requestId: request.id, diff --git a/src-web/hooks/useCreateHttpRequest.ts b/src-web/hooks/useCreateHttpRequest.ts index 19daee46..14313ffc 100644 --- a/src-web/hooks/useCreateHttpRequest.ts +++ b/src-web/hooks/useCreateHttpRequest.ts @@ -1,4 +1,4 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; import { trackEvent } from '../lib/analytics'; import type { HttpRequest } from '../lib/models'; @@ -6,19 +6,17 @@ import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useActiveRequest } from './useActiveRequest'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useAppRoutes } from './useAppRoutes'; -import { httpRequestsQueryKey } from './useHttpRequests'; export function useCreateHttpRequest() { const workspaceId = useActiveWorkspaceId(); const activeEnvironmentId = useActiveEnvironmentId(); const activeRequest = useActiveRequest(); const routes = useAppRoutes(); - const queryClient = useQueryClient(); return useMutation< HttpRequest, unknown, - Partial> + Partial> >({ mutationFn: (patch) => { if (workspaceId === null) { @@ -38,10 +36,6 @@ export function useCreateHttpRequest() { }, onSettled: () => trackEvent('HttpRequest', 'Create'), onSuccess: async (request) => { - queryClient.setQueryData( - httpRequestsQueryKey({ workspaceId: request.workspaceId }), - (requests) => [...(requests ?? []), request], - ); routes.navigate('request', { workspaceId: request.workspaceId, requestId: request.id, diff --git a/src-web/hooks/useRecentRequests.ts b/src-web/hooks/useRecentRequests.ts index 3eebf982..96feea03 100644 --- a/src-web/hooks/useRecentRequests.ts +++ b/src-web/hooks/useRecentRequests.ts @@ -1,30 +1,45 @@ -import { useEffect } from 'react'; -import { createGlobalState, useEffectOnce, useLocalStorage } from 'react-use'; +import { useEffect, useMemo } from 'react'; +import { createGlobalState, useEffectOnce } from 'react-use'; +import { getKeyValue, NAMESPACE_GLOBAL } from '../lib/keyValueStore'; import { useActiveRequestId } from './useActiveRequestId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; +import { useGrpcRequests } from './useGrpcRequests'; +import { useHttpRequest } from './useHttpRequest'; +import { useHttpRequests } from './useHttpRequests'; +import { useKeyValue } from './useKeyValue'; const useHistoryState = createGlobalState([]); +const kvKey = (workspaceId: string) => 'recent_requests::' + workspaceId; +const namespace = NAMESPACE_GLOBAL; +const defaultValue: string[] = []; + export function useRecentRequests() { + const httpRequests = useHttpRequests(); + const grpcRequests = useGrpcRequests(); + const requests = useMemo(() => [...httpRequests, ...grpcRequests], [httpRequests, grpcRequests]); const activeWorkspaceId = useActiveWorkspaceId(); const activeRequestId = useActiveRequestId(); + const [history, setHistory] = useHistoryState(); - const [lsState, setLSState] = useLocalStorage( - 'recent_requests::' + activeWorkspaceId, - [], - ); + const kv = useKeyValue({ + key: kvKey(activeWorkspaceId ?? 'n/a'), + namespace, + defaultValue, + }); // Load local storage state on initial render useEffectOnce(() => { - if (lsState) { - setHistory(lsState); + if (kv.value) { + setHistory(kv.value); } }); // Update local storage state when history changes useEffect(() => { - setLSState(history); - }, [history, setLSState]); + kv.set(history); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [history]); // Set history when active request changes useEffect(() => { @@ -35,5 +50,18 @@ export function useRecentRequests() { }); }, [activeRequestId, setHistory]); - return history; + const onlyValidIds = useMemo( + () => history.filter((id) => requests.some((r) => r.id === id)), + [history, requests], + ); + + return onlyValidIds; +} + +export async function getRecentRequests(workspaceId: string) { + return getKeyValue({ + namespace, + key: kvKey(workspaceId), + fallback: defaultValue, + }); } diff --git a/src-web/hooks/useRecentWorkspaces.ts b/src-web/hooks/useRecentWorkspaces.ts index e4567981..4e71b85b 100644 --- a/src-web/hooks/useRecentWorkspaces.ts +++ b/src-web/hooks/useRecentWorkspaces.ts @@ -1,34 +1,45 @@ import { useEffect, useMemo } from 'react'; -import { createGlobalState, useEffectOnce, useLocalStorage } from 'react-use'; +import { createGlobalState, useEffectOnce } from 'react-use'; +import { getKeyValue, NAMESPACE_GLOBAL } from '../lib/keyValueStore'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; +import { useKeyValue } from './useKeyValue'; import { useWorkspaces } from './useWorkspaces'; const useHistoryState = createGlobalState([]); +const kvKey = () => 'recent_workspaces'; +const namespace = NAMESPACE_GLOBAL; +const defaultValue: string[] = []; + export function useRecentWorkspaces() { const workspaces = useWorkspaces(); const activeWorkspaceId = useActiveWorkspaceId(); const [history, setHistory] = useHistoryState(); - const [lsState, setLSState] = useLocalStorage('recent_workspaces', []); + const kv = useKeyValue({ + key: kvKey(), + namespace, + defaultValue, + }); // Load local storage state on initial render useEffectOnce(() => { - if (lsState) { - setHistory(lsState); + if (kv.value) { + setHistory(kv.value); } }); // Update local storage state when history changes useEffect(() => { - setLSState(history); - }, [history, setLSState]); + kv.set(history); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [history]); // Set history when active request changes useEffect(() => { setHistory((currentHistory: string[]) => { if (activeWorkspaceId === null) return currentHistory; - const withoutCurrentWorkspace = currentHistory.filter((id) => id !== activeWorkspaceId); - return [activeWorkspaceId, ...withoutCurrentWorkspace]; + const withoutCurrent = currentHistory.filter((id) => id !== activeWorkspaceId); + return [activeWorkspaceId, ...withoutCurrent]; }); }, [activeWorkspaceId, setHistory]); @@ -39,3 +50,11 @@ export function useRecentWorkspaces() { return onlyValidIds; } + +export async function getRecentWorkspaces() { + return getKeyValue({ + namespace, + key: kvKey(), + fallback: defaultValue, + }); +}