From 49da0e5aa80c4e6f1dc7d82de5ce86f63b90b2e6 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 16 Mar 2024 09:46:11 -0700 Subject: [PATCH] Stubbed out global commands helper --- src-web/components/AppRouter.tsx | 18 ++---- src-web/components/DefaultLayout.tsx | 12 ++++ src-web/components/GlobalHooks.tsx | 3 +- .../components/RedirectToLatestWorkspace.tsx | 2 +- .../components/WorkspaceActionsDropdown.tsx | 4 +- src-web/hooks/useCommands.ts | 41 +++++++++++++ ...reateWorkspace.ts => useGlobalCommands.ts} | 22 +++---- src-web/lib/analytics.ts | 61 ++++++++++--------- 8 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 src-web/components/DefaultLayout.tsx create mode 100644 src-web/hooks/useCommands.ts rename src-web/hooks/{useCreateWorkspace.ts => useGlobalCommands.ts} (58%) diff --git a/src-web/components/AppRouter.tsx b/src-web/components/AppRouter.tsx index 7e919036..40939be4 100644 --- a/src-web/components/AppRouter.tsx +++ b/src-web/components/AppRouter.tsx @@ -1,10 +1,9 @@ -import { createBrowserRouter, Navigate, Outlet, RouterProvider, useParams } from 'react-router-dom'; +import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom'; import { routePaths, useAppRoutes } from '../hooks/useAppRoutes'; -import { DialogProvider } from './DialogContext'; -import { GlobalHooks } from './GlobalHooks'; +import { DefaultLayout } from './DefaultLayout'; +import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace'; import RouteError from './RouteError'; import Workspace from './Workspace'; -import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace'; const router = createBrowserRouter([ { @@ -58,7 +57,7 @@ function RedirectLegacyEnvironmentURLs() { }>(); const environmentId = rawEnvironmentId === '__default__' ? undefined : rawEnvironmentId; - let to = '/'; + let to; if (workspaceId != null && requestId != null) { to = routes.paths.request({ workspaceId, environmentId, requestId }); } else if (workspaceId != null) { @@ -69,12 +68,3 @@ function RedirectLegacyEnvironmentURLs() { return ; } - -function DefaultLayout() { - return ( - - - - - ); -} diff --git a/src-web/components/DefaultLayout.tsx b/src-web/components/DefaultLayout.tsx new file mode 100644 index 00000000..31e80c1a --- /dev/null +++ b/src-web/components/DefaultLayout.tsx @@ -0,0 +1,12 @@ +import { Outlet } from 'react-router-dom'; +import { DialogProvider } from './DialogContext'; +import { GlobalHooks } from './GlobalHooks'; + +export function DefaultLayout() { + return ( + + + + + ); +} diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index dccb6baa..f9991c02 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -3,6 +3,7 @@ import { appWindow } from '@tauri-apps/api/window'; import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { cookieJarsQueryKey } from '../hooks/useCookieJars'; +import { useGlobalCommands } from '../hooks/useGlobalCommands'; import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections'; import { grpcEventsQueryKey } from '../hooks/useGrpcEvents'; import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests'; @@ -32,8 +33,8 @@ export function GlobalHooks() { useRecentRequests(); useSyncAppearance(); - useSyncWindowTitle(); + useGlobalCommands(); const queryClient = useQueryClient(); const { wasUpdatedExternally } = useRequestUpdateKey(null); diff --git a/src-web/components/RedirectToLatestWorkspace.tsx b/src-web/components/RedirectToLatestWorkspace.tsx index e80af3c2..6b561972 100644 --- a/src-web/components/RedirectToLatestWorkspace.tsx +++ b/src-web/components/RedirectToLatestWorkspace.tsx @@ -24,7 +24,7 @@ export function RedirectToLatestWorkspace() { navigate(routes.paths.workspace({ workspaceId, environmentId })); } })(); - }, [navigate, routes.paths, workspaces, workspaces.length]); + }, [navigate, recentWorkspaces, routes.paths, workspaces, workspaces.length]); return <>; } diff --git a/src-web/components/WorkspaceActionsDropdown.tsx b/src-web/components/WorkspaceActionsDropdown.tsx index 08ce07a1..2795aeee 100644 --- a/src-web/components/WorkspaceActionsDropdown.tsx +++ b/src-web/components/WorkspaceActionsDropdown.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { memo, useMemo } from 'react'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useAppRoutes } from '../hooks/useAppRoutes'; -import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; +import { useCommand } from '../hooks/useCommands'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { usePrompt } from '../hooks/usePrompt'; import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; @@ -30,7 +30,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ const activeWorkspaceId = activeWorkspace?.id ?? null; const updateWorkspace = useUpdateWorkspace(activeWorkspaceId); const deleteWorkspace = useDeleteWorkspace(activeWorkspace); - const createWorkspace = useCreateWorkspace({ navigateAfter: true }); + const createWorkspace = useCommand('workspace.create'); const dialog = useDialog(); const prompt = usePrompt(); const routes = useAppRoutes(); diff --git a/src-web/hooks/useCommands.ts b/src-web/hooks/useCommands.ts new file mode 100644 index 00000000..3e3ab2af --- /dev/null +++ b/src-web/hooks/useCommands.ts @@ -0,0 +1,41 @@ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { createGlobalState } from 'react-use'; +import type { TrackAction, TrackResource } from '../lib/analytics'; +import type { Workspace } from '../lib/models'; + +interface CommandInstance extends UseMutationOptions { + track?: [TrackResource, TrackAction]; + name: string; +} + +export type Commands = { + 'workspace.create': CommandInstance>, Workspace>; +}; + +const useCommandState = createGlobalState(); + +export function useRegisterCommand(action: K, command: Commands[K]) { + const [, setState] = useCommandState(); + + useEffect(() => { + setState((commands) => { + return { ...commands, [action]: command }; + }); + + // Remove action when it goes out of scope + return () => { + setState((commands) => { + return { ...commands, [action]: undefined }; + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [action]); +} + +export function useCommand(action: K) { + const [commands] = useCommandState(); + const cmd = commands[action]; + return useMutation({ ...cmd }); +} diff --git a/src-web/hooks/useCreateWorkspace.ts b/src-web/hooks/useGlobalCommands.ts similarity index 58% rename from src-web/hooks/useCreateWorkspace.ts rename to src-web/hooks/useGlobalCommands.ts index 2c785341..f9ed95b6 100644 --- a/src-web/hooks/useCreateWorkspace.ts +++ b/src-web/hooks/useGlobalCommands.ts @@ -1,14 +1,18 @@ -import { useMutation } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; -import { trackEvent } from '../lib/analytics'; -import type { Workspace } from '../lib/models'; import { useAppRoutes } from './useAppRoutes'; +import { useRegisterCommand } from './useCommands'; import { usePrompt } from './usePrompt'; -export function useCreateWorkspace({ navigateAfter }: { navigateAfter: boolean }) { - const routes = useAppRoutes(); +export function useGlobalCommands() { const prompt = usePrompt(); - return useMutation>>({ + const routes = useAppRoutes(); + + useRegisterCommand('workspace.create', { + name: 'New Workspace', + track: ['workspace', 'create'], + onSuccess: async (workspace) => { + routes.navigate('workspace', { workspaceId: workspace.id }); + }, mutationFn: async ({ name: patchName }) => { const name = patchName ?? @@ -23,11 +27,5 @@ export function useCreateWorkspace({ navigateAfter }: { navigateAfter: boolean } })); return invoke('cmd_create_workspace', { name }); }, - onSettled: () => trackEvent('workspace', 'create'), - onSuccess: async (workspace) => { - if (navigateAfter) { - routes.navigate('workspace', { workspaceId: workspace.id }); - } - }, }); } diff --git a/src-web/lib/analytics.ts b/src-web/lib/analytics.ts index 69263cb1..a8e00b8c 100644 --- a/src-web/lib/analytics.ts +++ b/src-web/lib/analytics.ts @@ -1,35 +1,38 @@ import { invoke } from '@tauri-apps/api'; -export function trackEvent( - resource: - | 'app' - | 'cookie_jar' - | 'dialog' - | 'environment' - | 'folder' - | 'grpc_connection' - | 'grpc_event' - | 'grpc_request' - | 'http_request' - | 'http_response' - | 'key_value' - | 'setting' - | 'sidebar' - | 'workspace', - action: - | 'cancel' - | 'commit' - | 'create' - | 'delete' - | 'delete_many' - | 'duplicate' - | 'hide' - | 'launch' - | 'send' - | 'show' - | 'toggle' - | 'update', +export type TrackResource = + | 'app' + | 'cookie_jar' + | 'dialog' + | 'environment' + | 'folder' + | 'grpc_connection' + | 'grpc_event' + | 'grpc_request' + | 'http_request' + | 'http_response' + | 'key_value' + | 'setting' + | 'sidebar' + | 'workspace'; +export type TrackAction = + | 'cancel' + | 'commit' + | 'create' + | 'delete' + | 'delete_many' + | 'duplicate' + | 'hide' + | 'launch' + | 'send' + | 'show' + | 'toggle' + | 'update'; + +export function trackEvent( + resource: TrackResource, + action: TrackAction, attributes: Record = {}, ) { invoke('cmd_track_event', {