From 47fcb8bad45c2200cc597ed4a656ad8bb48b842f Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 10 Jun 2024 21:37:41 -0700 Subject: [PATCH] Cmd Palette Improvements (#50) - Fuzzy matching - Show hotkeys - Add actions --- package-lock.json | 42 +++ package.json | 1 + src-web/components/CommandPalette.tsx | 271 +++++++++++++++--- src-web/components/GlobalHooks.tsx | 2 - .../components/WorkspaceActionsDropdown.tsx | 6 +- src-web/components/core/Dialog.tsx | 6 +- src-web/hooks/useCommandPalette.tsx | 1 + src-web/hooks/useCommands.ts | 41 --- src-web/hooks/useCreateHttpRequest.ts | 3 +- src-web/hooks/useCreateWorkspace.ts | 27 ++ src-web/hooks/useGlobalCommands.ts | 31 -- 11 files changed, 308 insertions(+), 123 deletions(-) delete mode 100644 src-web/hooks/useCommands.ts create mode 100644 src-web/hooks/useCreateWorkspace.ts delete mode 100644 src-web/hooks/useGlobalCommands.ts diff --git a/package-lock.json b/package-lock.json index 8cd32429..4fb940e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "codemirror": "^6.0.1", "codemirror-json-schema": "^0.6.1", "date-fns": "^3.3.1", + "fast-fuzzy": "^1.12.0", "focus-trap-react": "^10.1.1", "format-graphql": "^1.4.0", "framer-motion": "^9.0.4", @@ -5750,6 +5751,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fuzzy": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.12.0.tgz", + "integrity": "sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q==", + "dependencies": { + "graphemesplit": "^2.4.1" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -6410,6 +6419,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphemesplit": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.4.4.tgz", + "integrity": "sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==", + "dependencies": { + "js-base64": "^3.6.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/graphql": { "version": "16.8.1", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", @@ -7288,6 +7306,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "node_modules/js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -8737,6 +8760,11 @@ "semver": "bin/semver" } }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, "node_modules/papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", @@ -11106,6 +11134,11 @@ "node": ">=0.10.0" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -11362,6 +11395,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", diff --git a/package.json b/package.json index 2d33fdd4..1da63c59 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "codemirror": "^6.0.1", "codemirror-json-schema": "^0.6.1", "date-fns": "^3.3.1", + "fast-fuzzy": "^1.12.0", "focus-trap-react": "^10.1.1", "format-graphql": "^1.4.0", "framer-motion": "^9.0.4", diff --git a/src-web/components/CommandPalette.tsx b/src-web/components/CommandPalette.tsx index 288906cb..1c27de96 100644 --- a/src-web/components/CommandPalette.tsx +++ b/src-web/components/CommandPalette.tsx @@ -1,21 +1,40 @@ +import { invoke } from '@tauri-apps/api/core'; import classNames from 'classnames'; +import { search } from 'fast-fuzzy'; import type { KeyboardEvent, ReactNode } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; +import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId'; import { useActiveRequestId } from '../hooks/useActiveRequestId'; import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId'; import { useAppRoutes } from '../hooks/useAppRoutes'; +import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; +import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest'; +import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest'; +import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; +import { useDebouncedState } from '../hooks/useDebouncedState'; +import { useEnvironments } from '../hooks/useEnvironments'; +import type { HotkeyAction } from '../hooks/useHotKey'; +import { useHotKey } from '../hooks/useHotKey'; import { useOpenWorkspace } from '../hooks/useOpenWorkspace'; +import { useRecentEnvironments } from '../hooks/useRecentEnvironments'; import { useRecentRequests } from '../hooks/useRecentRequests'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; import { useRequests } from '../hooks/useRequests'; +import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { fallbackRequestName } from '../lib/fallbackRequestName'; +import { CookieDialog } from './CookieDialog'; +import { Button } from './core/Button'; import { Heading } from './core/Heading'; +import { HotKey } from './core/HotKey'; import { HttpMethodTag } from './core/HttpMethodTag'; import { Icon } from './core/Icon'; import { PlainInput } from './core/PlainInput'; import { HStack } from './core/Stacks'; +import { useDialog } from './DialogContext'; +import { EnvironmentEditDialog } from './EnvironmentEditDialog'; interface CommandPaletteGroup { key: string; @@ -26,22 +45,120 @@ interface CommandPaletteGroup { type CommandPaletteItem = { key: string; onSelect: () => void; + action?: HotkeyAction; } & ({ searchText: string; label: ReactNode } | { label: string }); -const MAX_PER_GROUP = 4; +const MAX_PER_GROUP = 8; export function CommandPalette({ onClose }: { onClose: () => void }) { + const [command, setCommand] = useDebouncedState('', 150); const [selectedItemKey, setSelectedItemKey] = useState(null); const routes = useAppRoutes(); const activeEnvironmentId = useActiveEnvironmentId(); const activeRequestId = useActiveRequestId(); - const activeWorkspaceId = useActiveWorkspaceId(); + const active = useActiveWorkspaceId(); const workspaces = useWorkspaces(); + const environments = useEnvironments(); + const recentEnvironments = useRecentEnvironments(); const recentWorkspaces = useRecentWorkspaces(); const requests = useRequests(); const recentRequests = useRecentRequests(); - const [command, setCommand] = useState(''); const openWorkspace = useOpenWorkspace(); + const createWorkspace = useCreateWorkspace(); + const createHttpRequest = useCreateHttpRequest(); + const { activeCookieJar } = useActiveCookieJar(); + const createGrpcRequest = useCreateGrpcRequest(); + const createEnvironment = useCreateEnvironment(); + const dialog = useDialog(); + const workspaceId = useActiveWorkspaceId(); + const activeEnvironment = useActiveEnvironment(); + const [, setSidebarHidden] = useSidebarHidden(); + + const workspaceCommands = useMemo(() => { + const commands: CommandPaletteItem[] = [ + { + key: 'settings.open', + label: 'Open Settings', + action: 'settings.show', + onSelect: async () => { + if (workspaceId == null) return; + await invoke('cmd_new_nested_window', { + url: routes.paths.workspaceSettings({ workspaceId }), + label: 'settings', + title: 'Yaak Settings', + }); + }, + }, + { + key: 'app.create', + label: 'Create Workspace', + onSelect: createWorkspace.mutate, + }, + { + key: 'http_request.create', + label: 'Create HTTP Request', + onSelect: () => createHttpRequest.mutate({}), + }, + { + key: 'cookies.show', + label: 'Show Cookies', + onSelect: async () => { + dialog.show({ + id: 'cookies', + title: 'Manage Cookies', + size: 'full', + render: () => , + }); + }, + }, + { + key: 'grpc_request.create', + label: 'Create GRPC Request', + onSelect: () => createGrpcRequest.mutate({}), + }, + { + key: 'environment.edit', + label: 'Edit Environment', + action: 'environmentEditor.toggle', + onSelect: () => { + dialog.toggle({ + id: 'environment-editor', + noPadding: true, + size: 'lg', + className: 'h-[80vh]', + render: () => , + }); + }, + }, + { + key: 'environment.create', + label: 'Create Environment', + onSelect: createEnvironment.mutate, + }, + { + key: 'sidebar.toggle', + label: 'Toggle Sidebar', + action: 'sidebar.focus', + onSelect: () => setSidebarHidden((h) => !h), + }, + ]; + return commands.sort((a, b) => + ('searchText' in a ? a.searchText : a.label).localeCompare( + 'searchText' in b ? b.searchText : b.label, + ), + ); + }, [ + activeCookieJar, + activeEnvironment, + createEnvironment.mutate, + createGrpcRequest, + createHttpRequest, + createWorkspace.mutate, + dialog, + routes.paths, + setSidebarHidden, + workspaceId, + ]); const sortedRequests = useMemo(() => { return [...requests].sort((a, b) => { @@ -60,6 +177,23 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }); }, [recentRequests, requests]); + const sortedEnvironments = useMemo(() => { + return [...environments].sort((a, b) => { + const aRecentIndex = recentEnvironments.indexOf(a.id); + const bRecentIndex = recentEnvironments.indexOf(b.id); + + if (aRecentIndex >= 0 && bRecentIndex >= 0) { + return aRecentIndex - bRecentIndex; + } else if (aRecentIndex >= 0 && bRecentIndex === -1) { + return -1; + } else if (aRecentIndex === -1 && bRecentIndex >= 0) { + return 1; + } else { + return a.createdAt.localeCompare(b.createdAt); + } + }); + }, [environments, recentEnvironments]); + const sortedWorkspaces = useMemo(() => { return [...workspaces].sort((a, b) => { const aRecentIndex = recentWorkspaces.indexOf(a.id); @@ -78,6 +212,12 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }, [recentWorkspaces, workspaces]); const groups = useMemo(() => { + const actionsGroup: CommandPaletteGroup = { + key: 'actions', + label: 'Actions', + items: workspaceCommands, + }; + const requestGroup: CommandPaletteGroup = { key: 'requests', label: 'Requests', @@ -91,10 +231,10 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { requestGroup.items.push({ key: `switch-request-${r.id}`, - searchText: `${r.method} ${r.name}`, + searchText: fallbackRequestName(r), label: ( - +
{fallbackRequestName(r)}
), @@ -108,6 +248,23 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }); } + const environmentGroup: CommandPaletteGroup = { + key: 'environments', + label: 'Environments', + items: [], + }; + + for (const e of sortedEnvironments) { + if (e.id === activeEnvironment?.id) { + continue; + } + environmentGroup.items.push({ + key: `switch-environment-${e.id}`, + label: e.name, + onSelect: () => routes.setEnvironment(e), + }); + } + const workspaceGroup: CommandPaletteGroup = { key: 'workspaces', label: 'Workspaces', @@ -115,7 +272,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }; for (const w of sortedWorkspaces) { - if (w.id === activeWorkspaceId) { + if (w.id === active) { continue; } workspaceGroup.items.push({ @@ -125,30 +282,44 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }); } - return [requestGroup, workspaceGroup]; + return [actionsGroup, requestGroup, environmentGroup, workspaceGroup]; }, [ - activeEnvironmentId, - activeRequestId, - activeWorkspaceId, - openWorkspace, - routes, + workspaceCommands, sortedRequests, + activeRequestId, + routes, + activeEnvironmentId, + sortedEnvironments, + activeEnvironment?.id, sortedWorkspaces, + active, + openWorkspace, ]); - const filteredGroups = useMemo( - () => - groups - .map((g) => { - g.items = g.items.filter((v) => { - const s = 'searchText' in v ? v.searchText : v.label; - return s.toLowerCase().includes(command.toLowerCase()); - }); - return g; + const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]); + + useEffect(() => { + setSelectedItemKey(null); + }, [command]); + + const { filteredGroups, filteredAllItems } = useMemo(() => { + const result = command + ? search(command, allItems, { + threshold: 0.4, + keySelector: (v) => ('searchText' in v ? v.searchText : v.label), }) - .filter((g) => g.items.length > 0), - [command, groups], - ); + : allItems; + + const filteredGroups = groups + .map((g) => { + g.items = result.filter((i) => g.items.includes(i)).slice(0, MAX_PER_GROUP); + return g; + }) + .filter((g) => g.items.length > 0); + + const filteredAllItems = filteredGroups.flatMap((g) => g.items); + return { filteredAllItems, filteredGroups }; + }, [allItems, command, groups]); const handleSelectAndClose = useCallback( (cb: () => void) => { @@ -158,38 +329,37 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { [onClose], ); - const { allItems, selectedItem } = useMemo(() => { - const allItems = filteredGroups.flatMap((g) => g.items); - let selectedItem = allItems.find((i) => i.key === selectedItemKey) ?? null; + const selectedItem = useMemo(() => { + let selectedItem = filteredAllItems.find((i) => i.key === selectedItemKey) ?? null; if (selectedItem == null) { - selectedItem = allItems[0] ?? null; + selectedItem = filteredAllItems[0] ?? null; } - return { selectedItem, allItems }; - }, [filteredGroups, selectedItemKey]); + return selectedItem; + }, [filteredAllItems, selectedItemKey]); const handleKeyDown = useCallback( (e: KeyboardEvent) => { - const index = allItems.findIndex((v) => v.key === selectedItem?.key); + const index = filteredAllItems.findIndex((v) => v.key === selectedItem?.key); if (e.key === 'ArrowDown' || (e.ctrlKey && e.key === 'n')) { - const next = allItems[index + 1]; + const next = filteredAllItems[index + 1] ?? filteredAllItems[0]; setSelectedItemKey(next?.key ?? null); } else if (e.key === 'ArrowUp' || (e.ctrlKey && e.key === 'k')) { - const prev = allItems[index - 1]; + const prev = filteredAllItems[index - 1] ?? filteredAllItems[filteredAllItems.length - 1]; setSelectedItemKey(prev?.key ?? null); } else if (e.key === 'Enter') { - const selected = allItems[index]; + const selected = filteredAllItems[index]; setSelectedItemKey(selected?.key ?? null); if (selected) { handleSelectAndClose(selected.onSelect); } } }, - [allItems, handleSelectAndClose, selectedItem?.key], + [filteredAllItems, handleSelectAndClose, selectedItem?.key], ); return ( -
+
void }) {
{filteredGroups.map((g) => ( -
+
{g.label} - {g.items.slice(0, MAX_PER_GROUP).map((v) => ( + {g.items.map((v) => ( handleSelectAndClose(v.onSelect)} + rightSlot={ + v.action && + } > {v.label} @@ -233,15 +406,20 @@ function CommandPaletteItem({ children, active, onClick, + rightSlot, }: { children: ReactNode; active: boolean; onClick: () => void; + rightSlot?: ReactNode; }) { return ( - + ); } + +function CommandPaletteAction({ + action, + onAction, +}: { + action: HotkeyAction; + onAction: () => void; +}) { + useHotKey(action, onAction); + return ; +} diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 7dee9a3e..f38c5902 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -6,7 +6,6 @@ import { useCommandPalette } from '../hooks/useCommandPalette'; import { cookieJarsQueryKey } from '../hooks/useCookieJars'; import { environmentsQueryKey } from '../hooks/useEnvironments'; import { foldersQueryKey } from '../hooks/useFolders'; -import { useGlobalCommands } from '../hooks/useGlobalCommands'; import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections'; import { grpcEventsQueryKey } from '../hooks/useGrpcEvents'; import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests'; @@ -42,7 +41,6 @@ export function GlobalHooks() { // Other useful things useSyncThemeToDocument(); - useGlobalCommands(); useCommandPalette(); useNotificationToast(); diff --git a/src-web/components/WorkspaceActionsDropdown.tsx b/src-web/components/WorkspaceActionsDropdown.tsx index d6a00f79..c784569f 100644 --- a/src-web/components/WorkspaceActionsDropdown.tsx +++ b/src-web/components/WorkspaceActionsDropdown.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import { memo, useMemo } from 'react'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; -import { useCommand } from '../hooks/useCommands'; +import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { useOpenWorkspace } from '../hooks/useOpenWorkspace'; import { usePrompt } from '../hooks/usePrompt'; @@ -28,7 +28,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ const activeWorkspaceId = activeWorkspace?.id ?? null; const updateWorkspace = useUpdateWorkspace(activeWorkspaceId); const deleteWorkspace = useDeleteWorkspace(activeWorkspace); - const createWorkspace = useCommand('workspace.create'); + const createWorkspace = useCreateWorkspace(); const dialog = useDialog(); const prompt = usePrompt(); const settings = useSettings(); @@ -101,7 +101,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ key: 'create-workspace', label: 'New Workspace', leftSlot: , - onSelect: () => createWorkspace.mutate({}), + onSelect: createWorkspace.mutate, }, ]; }, [ diff --git a/src-web/components/core/Dialog.tsx b/src-web/components/core/Dialog.tsx index c809e744..e7df7eb6 100644 --- a/src-web/components/core/Dialog.tsx +++ b/src-web/components/core/Dialog.tsx @@ -54,7 +54,7 @@ export function Dialog({
{title ? ( diff --git a/src-web/hooks/useCommandPalette.tsx b/src-web/hooks/useCommandPalette.tsx index 45d8dd49..ba7aad83 100644 --- a/src-web/hooks/useCommandPalette.tsx +++ b/src-web/hooks/useCommandPalette.tsx @@ -9,6 +9,7 @@ export function useCommandPalette() { id: 'command_palette', size: 'dynamic', hideX: true, + className: '!max-h-[min(30rem,calc(100vh-4rem))]', vAlign: 'top', noPadding: true, noScroll: true, diff --git a/src-web/hooks/useCommands.ts b/src-web/hooks/useCommands.ts deleted file mode 100644 index 3e3ab2af..00000000 --- a/src-web/hooks/useCommands.ts +++ /dev/null @@ -1,41 +0,0 @@ -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/useCreateHttpRequest.ts b/src-web/hooks/useCreateHttpRequest.ts index 65ca4c1c..2ac8c925 100644 --- a/src-web/hooks/useCreateHttpRequest.ts +++ b/src-web/hooks/useCreateHttpRequest.ts @@ -14,7 +14,7 @@ export function useCreateHttpRequest() { const routes = useAppRoutes(); return useMutation>({ - mutationFn: (patch) => { + mutationFn: (patch = {}) => { if (workspaceId === null) { throw new Error("Cannot create request when there's no active workspace"); } @@ -28,7 +28,6 @@ export function useCreateHttpRequest() { } } patch.folderId = patch.folderId || activeRequest?.folderId; - console.log('PATCH', patch); return invoke('cmd_create_http_request', { request: { workspaceId, ...patch } }); }, onSettled: () => trackEvent('http_request', 'create'), diff --git a/src-web/hooks/useCreateWorkspace.ts b/src-web/hooks/useCreateWorkspace.ts new file mode 100644 index 00000000..7d32d622 --- /dev/null +++ b/src-web/hooks/useCreateWorkspace.ts @@ -0,0 +1,27 @@ +import { useMutation } from '@tanstack/react-query'; +import { invoke } from '@tauri-apps/api/core'; +import type { Workspace } from '../lib/models'; +import { useAppRoutes } from './useAppRoutes'; +import { usePrompt } from './usePrompt'; + +export function useCreateWorkspace() { + const routes = useAppRoutes(); + const prompt = usePrompt(); + return useMutation({ + mutationFn: async () => { + const name = await prompt({ + id: 'new-workspace', + name: 'name', + label: 'Name', + defaultValue: 'My Workspace', + title: 'New Workspace', + confirmLabel: 'Create', + placeholder: 'My Workspace', + }); + return invoke('cmd_create_workspace', { name }); + }, + onSuccess: async (workspace) => { + routes.navigate('workspace', { workspaceId: workspace.id }); + }, + }); +} diff --git a/src-web/hooks/useGlobalCommands.ts b/src-web/hooks/useGlobalCommands.ts deleted file mode 100644 index 32ebae11..00000000 --- a/src-web/hooks/useGlobalCommands.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { invoke } from '@tauri-apps/api/core'; -import { useAppRoutes } from './useAppRoutes'; -import { useRegisterCommand } from './useCommands'; -import { usePrompt } from './usePrompt'; - -export function useGlobalCommands() { - const prompt = usePrompt(); - 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 ?? - (await prompt({ - id: 'new-workspace', - name: 'name', - label: 'Name', - defaultValue: 'My Workspace', - title: 'New Workspace', - confirmLabel: 'Create', - placeholder: 'My Workspace', - })); - return invoke('cmd_create_workspace', { name }); - }, - }); -}