diff --git a/src-tauri/yaak-sync/index.ts b/src-tauri/yaak-sync/index.ts index d033b8e5..96f08f54 100644 --- a/src-tauri/yaak-sync/index.ts +++ b/src-tauri/yaak-sync/index.ts @@ -6,7 +6,7 @@ import { SyncOp } from './bindings/sync'; import { WatchEvent, WatchResult } from './bindings/watch'; export async function calculateSync(workspace: Workspace) { - if (!workspace.settingSyncDir) throw new Error('Workspace sync dir not configured'); + if (!workspace.settingSyncDir) return; return invoke('plugin:yaak-sync|calculate', { workspaceId: workspace.id, @@ -15,6 +15,8 @@ export async function calculateSync(workspace: Workspace) { } export async function applySync(workspace: Workspace, syncOps: SyncOp[]) { + if (!workspace.settingSyncDir) return; + return invoke('plugin:yaak-sync|apply', { workspaceId: workspace.id, dir: workspace.settingSyncDir, @@ -23,22 +25,24 @@ export async function applySync(workspace: Workspace, syncOps: SyncOp[]) { } export function useWatchWorkspace(workspace: Workspace | null, callback: (e: WatchEvent) => void) { - const workspaceId = workspace?.id ?? null; - useEffect(() => { - if (workspaceId == null) return; + if (workspace == null) return; + if (!workspace.settingSyncDir) return; const channel = new Channel(); channel.onmessage = callback; - const promise = invoke('plugin:yaak-sync|watch', { workspaceId, channel }); + const promise = invoke('plugin:yaak-sync|watch', { + workspaceId: workspace.id, + channel, + }); return () => { promise .then(({ unlistenEvent }) => { - console.log('Cancelling workspace watch', workspaceId, unlistenEvent); + console.log('Cancelling workspace watch', workspace.id, unlistenEvent); return emit(unlistenEvent); }) .catch(console.error); }; - }, [workspaceId]); + }, [workspace]); } diff --git a/src-web/components/CommandPaletteDialog.tsx b/src-web/components/CommandPaletteDialog.tsx index f35468f0..ccb69840 100644 --- a/src-web/components/CommandPaletteDialog.tsx +++ b/src-web/components/CommandPaletteDialog.tsx @@ -5,7 +5,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; import { useActiveRequest } from '../hooks/useActiveRequest'; -import { useCommands } from '../hooks/useCommands'; import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest'; import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest'; @@ -27,6 +26,7 @@ import { useScrollIntoView } from '../hooks/useScrollIntoView'; import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useWorkspaces } from '../hooks/useWorkspaces'; +import { createFolder } from '../lib/commands'; import { showDialog, toggleDialog } from '../lib/dialog'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { router } from '../lib/router'; @@ -69,7 +69,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { const [recentRequests] = useRecentRequests(); const openWorkspace = useOpenWorkspace(); const createHttpRequest = useCreateHttpRequest(); - const { createFolder } = useCommands(); const activeCookieJar = useActiveCookieJar(); const createGrpcRequest = useCreateGrpcRequest(); const createEnvironment = useCreateEnvironment(); @@ -188,7 +187,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { activeRequest, baseEnvironment, createEnvironment, - createFolder, createGrpcRequest, createHttpRequest, createWorkspace, diff --git a/src-web/components/CookieDropdown.tsx b/src-web/components/CookieDropdown.tsx index 64a57578..4ec93c09 100644 --- a/src-web/components/CookieDropdown.tsx +++ b/src-web/components/CookieDropdown.tsx @@ -1,12 +1,13 @@ +import { useAtomValue } from 'jotai'; import { memo, useMemo } from 'react'; -import { setActiveCookieJar, useActiveCookieJar } from '../hooks/useActiveCookieJar'; +import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; import { cookieJarsAtom } from '../hooks/useCookieJars'; import { useCreateCookieJar } from '../hooks/useCreateCookieJar'; import { useDeleteCookieJar } from '../hooks/useDeleteCookieJar'; -import { usePrompt } from '../hooks/usePrompt'; import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar'; import { showDialog } from '../lib/dialog'; -import { jotaiStore } from '../lib/jotai'; +import { showPrompt } from '../lib/prompt'; +import {setWorkspaceSearchParams} from "../lib/setWorkspaceSearchParams"; import { CookieDialog } from './CookieDialog'; import { Dropdown, type DropdownItem } from './core/Dropdown'; import { Icon } from './core/Icon'; @@ -18,18 +19,19 @@ export const CookieDropdown = memo(function CookieDropdown() { const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null); const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null); const createCookieJar = useCreateCookieJar(); - const prompt = usePrompt(); + const cookieJars = useAtomValue(cookieJarsAtom); const items = useMemo((): DropdownItem[] => { - const cookieJars = jotaiStore.get(cookieJarsAtom) ?? []; return [ - ...cookieJars.map((j) => ({ + ...(cookieJars ?? []).map((j) => ({ key: j.id, label: j.name, leftSlot: , - onSelect: () => setActiveCookieJar(j), + onSelect: () => { + setWorkspaceSearchParams({ cookie_jar_id: j.id }); + }, })), - ...((cookieJars.length > 0 && activeCookieJar != null + ...(((cookieJars ?? []).length > 0 && activeCookieJar != null ? [ { type: 'separator', label: activeCookieJar.name }, { @@ -51,7 +53,7 @@ export const CookieDropdown = memo(function CookieDropdown() { label: 'Rename', leftSlot: , onSelect: async () => { - const name = await prompt({ + const name = await showPrompt({ id: 'rename-cookie-jar', title: 'Rename Cookie Jar', description: ( @@ -68,7 +70,7 @@ export const CookieDropdown = memo(function CookieDropdown() { updateCookieJar.mutate({ name }); }, }, - ...((cookieJars.length > 1 // Never delete the last one + ...(((cookieJars ?? []).length > 1 // Never delete the last one ? [ { key: 'delete', @@ -89,7 +91,7 @@ export const CookieDropdown = memo(function CookieDropdown() { onSelect: () => createCookieJar.mutate(), }, ]; - }, [activeCookieJar, createCookieJar, deleteCookieJar, prompt, updateCookieJar]); + }, [activeCookieJar, cookieJars, createCookieJar, deleteCookieJar, updateCookieJar]); return ( diff --git a/src-web/components/CreateWorkspaceDialog.tsx b/src-web/components/CreateWorkspaceDialog.tsx index 781e5fb4..46ea8d0c 100644 --- a/src-web/components/CreateWorkspaceDialog.tsx +++ b/src-web/components/CreateWorkspaceDialog.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useCommands } from '../hooks/useCommands'; +import {createWorkspace} from "../lib/commands"; import { Button } from './core/Button'; import { PlainInput } from './core/PlainInput'; import { VStack } from './core/Stacks'; @@ -14,7 +14,6 @@ export function CreateWorkspaceDialog({ hide }: Props) { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [settingSyncDir, setSettingSyncDir] = useState(null); - const { createWorkspace } = useCommands(); return ( - {dialogs.map(({ render, onClose, id, ...props }: DialogInstance) => ( - { - onClose?.(); - hideDialog(id); - }} - {...props} - > - {render({ hide: () => hideDialog(id) })} - + {dialogs.map(({ id, ...props }) => ( + ))} ); } + +function DialogInstance({ render, onClose, id, ...props }: DialogInstance) { + const children = render({ hide: () => hideDialog(id) }); + return ( + { + onClose?.(); + hideDialog(id); + }} + {...props} + > + {children} + + ); +} diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index 1ebd8691..da7cd6a0 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -6,8 +6,8 @@ import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment'; import { useEnvironments } from '../hooks/useEnvironments'; import { useKeyValue } from '../hooks/useKeyValue'; -import { usePrompt } from '../hooks/usePrompt'; import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment'; +import { showPrompt } from '../lib/prompt'; import { Banner } from './core/Banner'; import { Button } from './core/Button'; import { ContextMenu } from './core/Dropdown'; @@ -212,7 +212,6 @@ function SidebarButton({ rightSlot?: ReactNode; environment: Environment | null; }) { - const prompt = usePrompt(); const updateEnvironment = useUpdateEnvironment(environment?.id ?? null); const deleteEnvironment = useDeleteEnvironment(environment); const [showContextMenu, setShowContextMenu] = useState<{ @@ -260,7 +259,7 @@ function SidebarButton({ label: 'Rename', leftSlot: , onSelect: async () => { - const name = await prompt({ + const name = await showPrompt({ id: 'rename-environment', title: 'Rename Environment', description: ( diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 4ddb64a8..4dec3481 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -2,34 +2,18 @@ import { emit } from '@tauri-apps/api/event'; import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugins'; import { useWatchWorkspace } from '@yaakapp-internal/sync'; import type { ShowToastRequest } from '@yaakapp/api'; -import { - useEnsureActiveCookieJar, - useSubscribeActiveCookieJarId, -} from '../hooks/useActiveCookieJar'; -import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment'; -import { getActiveRequest, useActiveRequest } from '../hooks/useActiveRequest'; -import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId'; import { useActiveWorkspace, useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace'; import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast'; -import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest'; -import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest'; import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss'; -import { useHotKey } from '../hooks/useHotKey'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { useNotificationToast } from '../hooks/useNotificationToast'; -import { usePrompt } from '../hooks/usePrompt'; -import { useSubscribeRecentCookieJars } from '../hooks/useRecentCookieJars'; -import { useSubscribeRecentEnvironments } from '../hooks/useRecentEnvironments'; -import { useSubscribeRecentRequests } from '../hooks/useRecentRequests'; -import { useSubscribeRecentWorkspaces } from '../hooks/useRecentWorkspaces'; import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting'; import { useSyncModelStores } from '../hooks/useSyncModelStores'; import { useSyncWorkspace } from '../hooks/useSyncWorkspace'; import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels'; -import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle'; import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting'; import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions'; -import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette'; +import { showPrompt } from '../lib/prompt'; import { showToast } from '../lib/toast'; export function GlobalHooks() { @@ -37,17 +21,8 @@ export function GlobalHooks() { useSyncZoomSetting(); useSyncFontSizeSetting(); useGenerateThemeCss(); - useSyncWorkspaceRequestTitle(); useSubscribeActiveWorkspaceId(); - useSubscribeActiveRequestId(); - useSubscribeActiveEnvironmentId(); - useSubscribeActiveCookieJarId(); - - useSubscribeRecentRequests(); - useSubscribeRecentWorkspaces(); - useSubscribeRecentEnvironments(); - useSubscribeRecentCookieJars(); useSyncWorkspaceChildModels(); useSubscribeTemplateFunctions(); @@ -55,7 +30,6 @@ export function GlobalHooks() { // Other useful things useNotificationToast(); useActiveWorkspaceChangedToast(); - useEnsureActiveCookieJar(); // Listen for toasts useListenToTauriEvent('show_toast', (event) => { @@ -68,32 +42,10 @@ export function GlobalHooks() { useListenToTauriEvent('upserted_model', debouncedSync); useWatchWorkspace(activeWorkspace, debouncedSync); - const activeRequest = useActiveRequest(); - const duplicateHttpRequest = useDuplicateHttpRequest({ - id: activeRequest?.id ?? null, - navigateAfter: true, - }); - const duplicateGrpcRequest = useDuplicateGrpcRequest({ - id: activeRequest?.id ?? null, - navigateAfter: true, - }); - useHotKey('http_request.duplicate', async () => { - const activeRequest = getActiveRequest(); - if (activeRequest?.model === 'http_request') { - await duplicateHttpRequest.mutateAsync(); - } else { - await duplicateGrpcRequest.mutateAsync(); - } - }); - - const toggleCommandPalette = useToggleCommandPalette(); - useHotKey('command_palette.toggle', toggleCommandPalette); - - const prompt = usePrompt(); useListenToTauriEvent<{ replyId: string; args: PromptTextRequest }>( 'show_prompt', async (event) => { - const value = await prompt(event.payload.args); + const value = await showPrompt(event.payload.args); const result: PromptTextResponse = { value }; await emit(event.payload.replyId, result); }, diff --git a/src-web/components/MarkdownEditor.tsx b/src-web/components/MarkdownEditor.tsx index 94becbc8..c277b5ad 100644 --- a/src-web/components/MarkdownEditor.tsx +++ b/src-web/components/MarkdownEditor.tsx @@ -42,7 +42,7 @@ export function MarkdownEditor({ className, defaultValue, onChange, name, ...edi ( () => [ { @@ -40,7 +39,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({ label: 'CUSTOM', leftSlot: , onSelect: async () => { - const newMethod = await prompt({ + const newMethod = await showPrompt({ id: 'custom-method', label: 'Http Method', defaultValue: '', @@ -54,7 +53,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({ }, }, ], - [onChange, prompt], + [onChange], ); return ( diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index e0272ac9..ab352439 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -298,7 +298,6 @@ export const RequestPane = memo(function RequestPane({ ); const activeTab = activeTabs?.[activeRequestId]; - console.log('ACTIVE TAB', activeTab); const setActiveTab = useCallback( (tab: string) => { setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 375f789b..ff4103cb 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -15,6 +15,7 @@ import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { router } from '../lib/router'; +import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams'; import { ContextMenu } from './core/Dropdown'; import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms'; import type { SidebarItemProps } from './SidebarItem'; @@ -151,11 +152,7 @@ export function Sidebar({ className }: Props) { } e.preventDefault(); - await router.navigate({ - to: '/workspaces/$workspaceId', - params: { workspaceId: activeWorkspace?.id ?? null }, - search: (prev) => ({ ...prev, request_id: selected.id }), - }); + setWorkspaceSearchParams({ request_id: selected.id }); }); useKey( diff --git a/src-web/components/Workspace.tsx b/src-web/components/Workspace.tsx index b3ff3912..6d28174d 100644 --- a/src-web/components/Workspace.tsx +++ b/src-web/components/Workspace.tsx @@ -2,13 +2,25 @@ import classNames from 'classnames'; import { motion } from 'framer-motion'; import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react'; -import { useActiveRequest } from '../hooks/useActiveRequest'; +import {useEnsureActiveCookieJar, useSubscribeActiveCookieJarId} from "../hooks/useActiveCookieJar"; +import {useSubscribeActiveEnvironmentId} from "../hooks/useActiveEnvironment"; +import {getActiveRequest, useActiveRequest} from '../hooks/useActiveRequest'; +import {useSubscribeActiveRequestId} from "../hooks/useActiveRequestId"; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; +import {useDuplicateGrpcRequest} from "../hooks/useDuplicateGrpcRequest"; +import {useDuplicateHttpRequest} from "../hooks/useDuplicateHttpRequest"; import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden'; +import {useHotKey} from "../hooks/useHotKey"; import { useImportData } from '../hooks/useImportData'; +import {useSubscribeRecentCookieJars} from "../hooks/useRecentCookieJars"; +import {useSubscribeRecentEnvironments} from "../hooks/useRecentEnvironments"; +import {useSubscribeRecentRequests} from "../hooks/useRecentRequests"; +import {useSubscribeRecentWorkspaces} from "../hooks/useRecentWorkspaces"; import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarWidth } from '../hooks/useSidebarWidth'; +import {useSyncWorkspaceRequestTitle} from "../hooks/useSyncWorkspaceRequestTitle"; +import {useToggleCommandPalette} from "../hooks/useToggleCommandPalette"; import { useWorkspaces } from '../hooks/useWorkspaces'; import { Banner } from './core/Banner'; import { Button } from './core/Button'; @@ -31,6 +43,9 @@ const body = { gridArea: 'body' }; const drag = { gridArea: 'drag' }; export function Workspace() { + // First, subscribe to some things applicable to workspaces + useGlobalWorkspaceHooks(); + const workspaces = useWorkspaces(); const { setWidth, width, resetWidth } = useSidebarWidth(); const [sidebarHidden, setSidebarHidden] = useSidebarHidden(); @@ -202,3 +217,40 @@ function WorkspaceBody() { return ; } + +function useGlobalWorkspaceHooks() { + useEnsureActiveCookieJar(); + + useSubscribeActiveRequestId(); + useSubscribeActiveEnvironmentId(); + useSubscribeActiveCookieJarId(); + + useSubscribeRecentRequests(); + useSubscribeRecentWorkspaces(); + useSubscribeRecentEnvironments(); + useSubscribeRecentCookieJars(); + + useSyncWorkspaceRequestTitle(); + + const toggleCommandPalette = useToggleCommandPalette(); + useHotKey('command_palette.toggle', toggleCommandPalette); + + const activeRequest = useActiveRequest(); + const duplicateHttpRequest = useDuplicateHttpRequest({ + id: activeRequest?.id ?? null, + navigateAfter: true, + }); + const duplicateGrpcRequest = useDuplicateGrpcRequest({ + id: activeRequest?.id ?? null, + navigateAfter: true, + }); + + useHotKey('http_request.duplicate', async () => { + const activeRequest = getActiveRequest(); + if (activeRequest?.model === 'http_request') { + await duplicateHttpRequest.mutateAsync(); + } else { + await duplicateGrpcRequest.mutateAsync(); + } + }); +} diff --git a/src-web/components/WorkspaceActionsDropdown.tsx b/src-web/components/WorkspaceActionsDropdown.tsx index 7b3963f6..6c92c88e 100644 --- a/src-web/components/WorkspaceActionsDropdown.tsx +++ b/src-web/components/WorkspaceActionsDropdown.tsx @@ -5,7 +5,6 @@ import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory'; import { useOpenWorkspace } from '../hooks/useOpenWorkspace'; import { useSettings } from '../hooks/useSettings'; -import { useSyncWorkspace } from '../hooks/useSyncWorkspace'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { showDialog } from '../lib/dialog'; import { getWorkspace } from '../lib/store'; @@ -31,7 +30,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ const settings = useSettings(); const openWorkspace = useOpenWorkspace(); const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null; - const { sync } = useSyncWorkspace(activeWorkspace); const orderedWorkspaces = useMemo( () => [...workspaces].sort((a, b) => (a.name.localeCompare(b.name) > 0 ? 1 : -1)), @@ -66,13 +64,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ }); }, }, - { - key: 'sync', - label: 'Sync Workspace', - leftSlot: , - hidden: !activeWorkspace?.settingSyncDir, - onSelect: sync, - }, { key: 'delete-responses', label: 'Clear Send History', @@ -89,14 +80,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ ]; return { workspaceItems, extraItems }; - }, [ - orderedWorkspaces, - activeWorkspace?.settingSyncDir, - activeWorkspace?.id, - sync, - deleteSendHistory, - createWorkspace, - ]); + }, [orderedWorkspaces, activeWorkspace?.id, deleteSendHistory, createWorkspace]); const handleChange = useCallback( async (workspaceId: string | null) => { diff --git a/src-web/hooks/Alert.tsx b/src-web/components/core/Alert.tsx similarity index 79% rename from src-web/hooks/Alert.tsx rename to src-web/components/core/Alert.tsx index 70d2ee99..fbb8368f 100644 --- a/src-web/hooks/Alert.tsx +++ b/src-web/components/core/Alert.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; -import { Button } from '../components/core/Button'; -import { HStack, VStack } from '../components/core/Stacks'; +import { Button } from './Button'; +import { HStack, VStack } from './Stacks'; export interface AlertProps { onHide: () => void; diff --git a/src-web/hooks/Confirm.tsx b/src-web/components/core/Confirm.tsx similarity index 85% rename from src-web/hooks/Confirm.tsx rename to src-web/components/core/Confirm.tsx index 5e36c99a..41ec457e 100644 --- a/src-web/hooks/Confirm.tsx +++ b/src-web/components/core/Confirm.tsx @@ -1,6 +1,6 @@ -import type { ButtonProps } from '../components/core/Button'; -import { Button } from '../components/core/Button'; -import { HStack } from '../components/core/Stacks'; +import type { ButtonProps } from './Button'; +import { Button } from './Button'; +import { HStack } from './Stacks'; export interface ConfirmProps { onHide: () => void; diff --git a/src-web/components/core/PairEditor.tsx b/src-web/components/core/PairEditor.tsx index 0c953d85..947168bc 100644 --- a/src-web/components/core/PairEditor.tsx +++ b/src-web/components/core/PairEditor.tsx @@ -12,9 +12,9 @@ import { } from 'react'; import type { XYCoord } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd'; -import { usePrompt } from '../../hooks/usePrompt'; import { useToggle } from '../../hooks/useToggle'; import { generateId } from '../../lib/generateId'; +import { showPrompt } from '../../lib/prompt'; import { DropMarker } from '../DropMarker'; import { SelectFile } from '../SelectFile'; import { Button } from './Button'; @@ -556,7 +556,6 @@ function FileActionsDropdown({ onChangeContentType: (contentType: string) => void; onDelete: () => void; }) { - const prompt = usePrompt(); const onChange = useCallback( (v: string) => { if (v === 'file') onChangeFile({ filePath: '' }); @@ -573,7 +572,7 @@ function FileActionsDropdown({ leftSlot: , hidden: !pair.isFile, onSelect: async () => { - const contentType = await prompt({ + const contentType = await showPrompt({ id: 'content-type', require: false, title: 'Override Content-Type', @@ -604,7 +603,7 @@ function FileActionsDropdown({ leftSlot: , }, ], - [onChangeContentType, onChangeFile, onDelete, pair.contentType, pair.isFile, prompt], + [onChangeContentType, onChangeFile, onDelete, pair.contentType, pair.isFile], ); return ( diff --git a/src-web/hooks/Prompt.tsx b/src-web/components/core/Prompt.tsx similarity index 89% rename from src-web/hooks/Prompt.tsx rename to src-web/components/core/Prompt.tsx index cffe2ae7..7607e85d 100644 --- a/src-web/hooks/Prompt.tsx +++ b/src-web/components/core/Prompt.tsx @@ -1,9 +1,9 @@ import type { PromptTextRequest } from '@yaakapp-internal/plugins'; import type { FormEvent, ReactNode } from 'react'; import { useCallback, useState } from 'react'; -import { Button } from '../components/core/Button'; -import { PlainInput } from '../components/core/PlainInput'; -import { HStack } from '../components/core/Stacks'; +import {PlainInput} from "./PlainInput"; +import { HStack } from './Stacks'; +import { Button } from './Button'; export type PromptProps = Omit & { description?: ReactNode; diff --git a/src-web/hooks/useActiveCookieJar.ts b/src-web/hooks/useActiveCookieJar.ts index fc7131d7..21cd0347 100644 --- a/src-web/hooks/useActiveCookieJar.ts +++ b/src-web/hooks/useActiveCookieJar.ts @@ -3,34 +3,26 @@ import type { CookieJar } from '@yaakapp-internal/models'; import { atom, useAtomValue } from 'jotai/index'; import { useEffect } from 'react'; import { jotaiStore } from '../lib/jotai'; -import { router } from '../lib/router'; +import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams'; import { cookieJarsAtom, useCookieJars } from './useCookieJars'; export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id'; -export const activeCookieJarIdAtom = atom(); - -export const activeCookieJarAtom = atom((get) => { - const activeId = get(activeCookieJarIdAtom); - return get(cookieJarsAtom)?.find((e) => e.id === activeId) ?? null; -}); - -export function setActiveCookieJar(cookieJar: CookieJar) { - router.navigate({ - from: '/workspaces/$workspaceId', - search: (prev) => ({ ...prev, cookie_jar_id: cookieJar.id }), - }); -} +export const activeCookieJarAtom = atom(null); export function useActiveCookieJar() { return useAtomValue(activeCookieJarAtom); } export function useSubscribeActiveCookieJarId() { - const { cookie_jar_id } = useSearch({ strict: false }); + const search = useSearch({ strict: false }); + const cookieJarId = search.cookie_jar_id; + const cookieJars = useAtomValue(cookieJarsAtom); useEffect(() => { - jotaiStore.set(activeCookieJarIdAtom, cookie_jar_id ?? undefined); - }, [cookie_jar_id]); + if (search == null) return; // Happens during Vite hot reload + const activeCookieJar = cookieJars?.find((j) => j.id == cookieJarId) ?? null; + jotaiStore.set(activeCookieJarAtom, activeCookieJar); + }, [cookieJarId, cookieJars, search]); } export function getActiveCookieJar() { @@ -39,12 +31,12 @@ export function getActiveCookieJar() { export function useEnsureActiveCookieJar() { const cookieJars = useCookieJars(); - const activeCookieJar = useActiveCookieJar(); + const { cookie_jar_id: activeCookieJarId } = useSearch({ from: '/workspaces/$workspaceId/' }); // Set the active cookie jar to the first one, if none set useEffect(() => { if (cookieJars == null) return; // Hasn't loaded yet - if (cookieJars.find((j) => j.id === activeCookieJar?.id)) { + if (cookieJars.find((j) => j.id === activeCookieJarId)) { return; // There's an active jar } @@ -55,7 +47,7 @@ export function useEnsureActiveCookieJar() { } // There's no active jar, so set it to the first one - console.log('Setting active cookie jar to', firstJar.id); - setActiveCookieJar(firstJar); - }, [activeCookieJar?.id, cookieJars]); + console.log('Setting active cookie jar to', cookieJars, activeCookieJarId, firstJar.id); + setWorkspaceSearchParams({ cookie_jar_id: firstJar.id }); + }, [activeCookieJarId, cookieJars]); } diff --git a/src-web/hooks/useActiveEnvironment.ts b/src-web/hooks/useActiveEnvironment.ts index 1e6807dc..55323ea2 100644 --- a/src-web/hooks/useActiveEnvironment.ts +++ b/src-web/hooks/useActiveEnvironment.ts @@ -4,7 +4,7 @@ import { useAtomValue } from 'jotai'; import { atom } from 'jotai/index'; import { useCallback, useEffect } from 'react'; import { jotaiStore } from '../lib/jotai'; -import { router } from '../lib/router'; +import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams'; import { environmentsAtom } from './useEnvironments'; export const QUERY_ENVIRONMENT_ID = 'environment_id'; @@ -18,11 +18,7 @@ export const activeEnvironmentAtom = atom((get) => { export function useActiveEnvironment() { const setId = useCallback( - (id: string | null) => - router.navigate({ - from: '/workspaces/$workspaceId', - search: (prev) => ({ ...prev, environment_id: id }), - }), + (id: string | null) => setWorkspaceSearchParams({ environment_id: id }), [], ); const environment = useAtomValue(activeEnvironmentAtom); diff --git a/src-web/hooks/useCommands.ts b/src-web/hooks/useCommands.ts deleted file mode 100644 index b84ba8fb..00000000 --- a/src-web/hooks/useCommands.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { Folder, Workspace } from '@yaakapp-internal/models'; -import { useMemo } from 'react'; -import { trackEvent } from '../lib/analytics'; -import { router } from '../lib/router'; -import { invokeCmd } from '../lib/tauri'; -import { getActiveWorkspaceId } from './useActiveWorkspace'; -import { createFastMutation } from './useFastMutation'; -import { usePrompt } from './usePrompt'; - -function makeCommands({ prompt }: { prompt: ReturnType }) { - return { - createWorkspace: createFastMutation>({ - mutationKey: ['create_workspace'], - mutationFn: (patch) => invokeCmd('cmd_update_workspace', { workspace: patch }), - onSuccess: async (workspace) => { - await router.navigate({ - to: '/workspaces/$workspaceId', - params: { workspaceId: workspace.id }, - }); - }, - onSettled: () => trackEvent('workspace', 'create'), - }), - - createFolder: createFastMutation< - Folder | null, - void, - Partial> - >({ - mutationKey: ['create_folder'], - mutationFn: async (patch) => { - const workspaceId = getActiveWorkspaceId(); - if (workspaceId == null) { - throw new Error("Cannot create folder when there's no active workspace"); - } - - if (!patch.name) { - const name = await prompt({ - id: 'new-folder', - label: 'Name', - defaultValue: 'Folder', - title: 'New Folder', - confirmText: 'Create', - placeholder: 'Name', - }); - if (name == null) return null; - - patch.name = name; - } - - patch.sortPriority = patch.sortPriority || -Date.now(); - return invokeCmd('cmd_update_folder', { folder: { workspaceId, ...patch } }); - }, - onSettled: () => trackEvent('folder', 'create'), - }), - } as const; -} - -export function useCommands() { - const prompt = usePrompt(); - return useMemo(() => makeCommands({ prompt }), [prompt]); -} diff --git a/src-web/hooks/useConfirm.ts b/src-web/hooks/useConfirm.ts deleted file mode 100644 index 418a2e0c..00000000 --- a/src-web/hooks/useConfirm.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useCallback } from 'react'; -import type { DialogProps } from '../components/core/Dialog'; -import { showDialog } from '../lib/dialog'; -import type { ConfirmProps } from './Confirm'; -import { Confirm } from './Confirm'; - -export function useConfirm() { - return useCallback( - ({ - id, - title, - description, - variant, - confirmText, - }: { - id: string; - title: DialogProps['title']; - description?: DialogProps['description']; - variant?: ConfirmProps['variant']; - confirmText?: ConfirmProps['confirmText']; - }) => - new Promise((onResult: ConfirmProps['onResult']) => { - showDialog({ - id, - title, - description, - hideX: true, - size: 'sm', - render: ({ hide }) => Confirm({ onHide: hide, variant, onResult, confirmText }), - }); - }), - [], - ); -} diff --git a/src-web/hooks/useCreateCookieJar.ts b/src-web/hooks/useCreateCookieJar.ts index ed408f6a..586ce84d 100644 --- a/src-web/hooks/useCreateCookieJar.ts +++ b/src-web/hooks/useCreateCookieJar.ts @@ -1,13 +1,11 @@ import type { CookieJar } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; +import { showPrompt } from '../lib/prompt'; import { invokeCmd } from '../lib/tauri'; import { getActiveWorkspaceId } from './useActiveWorkspace'; import { useFastMutation } from './useFastMutation'; -import { usePrompt } from './usePrompt'; export function useCreateCookieJar() { - const prompt = usePrompt(); - return useFastMutation({ mutationKey: ['create_cookie_jar'], mutationFn: async () => { @@ -15,7 +13,7 @@ export function useCreateCookieJar() { if (workspaceId == null) { throw new Error("Cannot create cookie jar when there's no active workspace"); } - const name = await prompt({ + const name = await showPrompt({ id: 'new-cookie-jar', title: 'New CookieJar', placeholder: 'My Jar', diff --git a/src-web/hooks/useCreateDropdownItems.tsx b/src-web/hooks/useCreateDropdownItems.tsx index df108434..ce45c1be 100644 --- a/src-web/hooks/useCreateDropdownItems.tsx +++ b/src-web/hooks/useCreateDropdownItems.tsx @@ -1,10 +1,10 @@ import { useMemo } from 'react'; import type { DropdownItem } from '../components/core/Dropdown'; import { Icon } from '../components/core/Icon'; +import { createFolder } from '../lib/commands'; import { generateId } from '../lib/generateId'; import { BODY_TYPE_GRAPHQL } from '../lib/model_util'; import { getActiveRequest } from './useActiveRequest'; -import { useCommands } from './useCommands'; import { useCreateGrpcRequest } from './useCreateGrpcRequest'; import { useCreateHttpRequest } from './useCreateHttpRequest'; @@ -19,7 +19,6 @@ export function useCreateDropdownItems({ } = {}): DropdownItem[] { const { mutate: createHttpRequest } = useCreateHttpRequest(); const { mutate: createGrpcRequest } = useCreateGrpcRequest(); - const { createFolder } = useCommands(); return useMemo((): DropdownItem[] => { const folderId = @@ -66,5 +65,5 @@ export function useCreateDropdownItems({ }, ]) as DropdownItem[]), ]; - }, [createFolder, createGrpcRequest, createHttpRequest, folderIdOption, hideFolder, hideIcons]); + }, [createGrpcRequest, createHttpRequest, folderIdOption, hideFolder, hideIcons]); } diff --git a/src-web/hooks/useCreateEnvironment.ts b/src-web/hooks/useCreateEnvironment.ts index 24057c37..8d0b4cf4 100644 --- a/src-web/hooks/useCreateEnvironment.ts +++ b/src-web/hooks/useCreateEnvironment.ts @@ -1,14 +1,13 @@ import type { Environment } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; +import { showPrompt } from '../lib/prompt'; import { invokeCmd } from '../lib/tauri'; import { useActiveEnvironment } from './useActiveEnvironment'; import { getActiveWorkspaceId } from './useActiveWorkspace'; import { useFastMutation } from './useFastMutation'; -import { usePrompt } from './usePrompt'; export function useCreateEnvironment() { const [, setActiveEnvironmentId] = useActiveEnvironment(); - const prompt = usePrompt(); return useFastMutation({ mutationKey: ['create_environment'], @@ -18,7 +17,7 @@ export function useCreateEnvironment() { } const workspaceId = getActiveWorkspaceId(); - const name = await prompt({ + const name = await showPrompt({ id: 'new-environment', title: 'New Environment', description: 'Create multiple environments with different sets of variables', diff --git a/src-web/hooks/useDeleteActiveWorkspace.tsx b/src-web/hooks/useDeleteActiveWorkspace.tsx index fb35e026..f142bcd9 100644 --- a/src-web/hooks/useDeleteActiveWorkspace.tsx +++ b/src-web/hooks/useDeleteActiveWorkspace.tsx @@ -1,20 +1,18 @@ import type { Workspace } from '@yaakapp-internal/models'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { router } from '../lib/router'; import { invokeCmd } from '../lib/tauri'; import { getActiveWorkspace } from './useActiveWorkspace'; -import { useConfirm } from './useConfirm'; import { useFastMutation } from './useFastMutation'; export function useDeleteActiveWorkspace() { - const confirm = useConfirm(); - return useFastMutation({ mutationKey: ['delete_workspace'], mutationFn: async () => { const workspace = getActiveWorkspace(); - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-workspace', title: 'Delete Workspace', variant: 'delete', diff --git a/src-web/hooks/useDeleteAnyGrpcRequest.tsx b/src-web/hooks/useDeleteAnyGrpcRequest.tsx index e6f00133..47aa7d3e 100644 --- a/src-web/hooks/useDeleteAnyGrpcRequest.tsx +++ b/src-web/hooks/useDeleteAnyGrpcRequest.tsx @@ -1,22 +1,20 @@ import type { GrpcRequest } from '@yaakapp-internal/models'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { getGrpcRequest } from '../lib/store'; import { invokeCmd } from '../lib/tauri'; -import { useConfirm } from './useConfirm'; import { useFastMutation } from './useFastMutation'; export function useDeleteAnyGrpcRequest() { - const confirm = useConfirm(); - return useFastMutation({ mutationKey: ['delete_any_grpc_request'], mutationFn: async (id) => { const request = await getGrpcRequest(id); if (request == null) return null; - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-grpc-request', title: 'Delete Request', variant: 'delete', diff --git a/src-web/hooks/useDeleteAnyHttpRequest.tsx b/src-web/hooks/useDeleteAnyHttpRequest.tsx index 2760548e..150feb52 100644 --- a/src-web/hooks/useDeleteAnyHttpRequest.tsx +++ b/src-web/hooks/useDeleteAnyHttpRequest.tsx @@ -1,22 +1,20 @@ import type { HttpRequest } from '@yaakapp-internal/models'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { getHttpRequest } from '../lib/store'; import { invokeCmd } from '../lib/tauri'; -import { useConfirm } from './useConfirm'; import { useFastMutation } from './useFastMutation'; export function useDeleteAnyHttpRequest() { - const confirm = useConfirm(); - return useFastMutation({ mutationKey: ['delete_any_http_request'], mutationFn: async (id) => { const request = await getHttpRequest(id); if (request == null) return null; - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-request', title: 'Delete Request', variant: 'delete', diff --git a/src-web/hooks/useDeleteCookieJar.tsx b/src-web/hooks/useDeleteCookieJar.tsx index b4212e2d..cd1aa310 100644 --- a/src-web/hooks/useDeleteCookieJar.tsx +++ b/src-web/hooks/useDeleteCookieJar.tsx @@ -1,21 +1,20 @@ -import { useFastMutation } from './useFastMutation'; import type { CookieJar } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { invokeCmd } from '../lib/tauri'; -import { useConfirm } from './useConfirm'; import { cookieJarsAtom } from './useCookieJars'; +import { useFastMutation } from './useFastMutation'; import { removeModelById } from './useSyncModelStores'; export function useDeleteCookieJar(cookieJar: CookieJar | null) { - const confirm = useConfirm(); const setCookieJars = useSetAtom(cookieJarsAtom); return useFastMutation({ mutationKey: ['delete_cookie_jar', cookieJar?.id], mutationFn: async () => { - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-cookie-jar', title: 'Delete CookieJar', variant: 'delete', diff --git a/src-web/hooks/useDeleteEnvironment.tsx b/src-web/hooks/useDeleteEnvironment.tsx index 40da61d0..c695baec 100644 --- a/src-web/hooks/useDeleteEnvironment.tsx +++ b/src-web/hooks/useDeleteEnvironment.tsx @@ -1,21 +1,20 @@ -import { useFastMutation } from './useFastMutation'; import type { Environment } from '@yaakapp-internal/models'; -import {useSetAtom} from "jotai"; +import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { invokeCmd } from '../lib/tauri'; -import { useConfirm } from './useConfirm'; -import {environmentsAtom} from "./useEnvironments"; -import {removeModelById} from "./useSyncModelStores"; +import { environmentsAtom } from './useEnvironments'; +import { useFastMutation } from './useFastMutation'; +import { removeModelById } from './useSyncModelStores'; export function useDeleteEnvironment(environment: Environment | null) { - const confirm = useConfirm(); const setEnvironments = useSetAtom(environmentsAtom); return useFastMutation({ mutationKey: ['delete_environment', environment?.id], mutationFn: async () => { - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-environment', title: 'Delete Environment', variant: 'delete', @@ -33,6 +32,6 @@ export function useDeleteEnvironment(environment: Environment | null) { if (environment == null) return; setEnvironments(removeModelById(environment)); - } + }, }); } diff --git a/src-web/hooks/useDeleteFolder.tsx b/src-web/hooks/useDeleteFolder.tsx index 8e5ba04f..fb949a02 100644 --- a/src-web/hooks/useDeleteFolder.tsx +++ b/src-web/hooks/useDeleteFolder.tsx @@ -2,22 +2,21 @@ import type { Folder } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; +import { showConfirm } from '../lib/confirm'; import { getFolder } from '../lib/store'; import { invokeCmd } from '../lib/tauri'; -import { useConfirm } from './useConfirm'; +import { useFastMutation } from './useFastMutation'; import { foldersAtom } from './useFolders'; import { removeModelById } from './useSyncModelStores'; -import { useFastMutation } from './useFastMutation'; export function useDeleteFolder(id: string | null) { - const confirm = useConfirm(); const setFolders = useSetAtom(foldersAtom); return useFastMutation({ mutationKey: ['delete_folder', id], mutationFn: async () => { const folder = await getFolder(id); - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-folder', title: 'Delete Folder', variant: 'delete', diff --git a/src-web/hooks/useDeleteSendHistory.tsx b/src-web/hooks/useDeleteSendHistory.tsx index 4607381d..aa7a7ebf 100644 --- a/src-web/hooks/useDeleteSendHistory.tsx +++ b/src-web/hooks/useDeleteSendHistory.tsx @@ -1,15 +1,14 @@ import { useSetAtom } from 'jotai/index'; import { showAlert } from '../lib/alert'; +import { showConfirm } from '../lib/confirm'; import { pluralizeCount } from '../lib/pluralize'; import { invokeCmd } from '../lib/tauri'; import { getActiveWorkspaceId } from './useActiveWorkspace'; -import { useConfirm } from './useConfirm'; import { useFastMutation } from './useFastMutation'; import { useGrpcConnections } from './useGrpcConnections'; import { httpResponsesAtom, useHttpResponses } from './useHttpResponses'; export function useDeleteSendHistory() { - const confirm = useConfirm(); const setHttpResponses = useSetAtom(httpResponsesAtom); const httpResponses = useHttpResponses(); const grpcConnections = useGrpcConnections(); @@ -30,7 +29,7 @@ export function useDeleteSendHistory() { return; } - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'delete-send-history', title: 'Clear Send History', variant: 'delete', diff --git a/src-web/hooks/usePrompt.ts b/src-web/hooks/usePrompt.ts deleted file mode 100644 index 0f8e12ee..00000000 --- a/src-web/hooks/usePrompt.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { DialogProps } from '../components/core/Dialog'; -import { showDialog } from '../lib/dialog'; -import type { PromptProps } from './Prompt'; -import { Prompt } from './Prompt'; - -type Props = Pick & - Omit & { id: string }; - -export function usePrompt() { - return ({ id, title, description, ...props }: Props) => - new Promise((resolve: PromptProps['onResult']) => { - showDialog({ - id, - title, - description, - hideX: true, - size: 'sm', - onClose: () => { - // Click backdrop, close, or escape - resolve(null); - }, - render: ({ hide }) => - Prompt({ - onCancel: () => { - // Click cancel button within dialog - resolve(null); - hide(); - }, - onResult: (v) => { - resolve(v); - hide(); - }, - ...props, - }), - }); - }); -} diff --git a/src-web/hooks/useRecentCookieJars.ts b/src-web/hooks/useRecentCookieJars.ts index 62bb88dd..c44797c4 100644 --- a/src-web/hooks/useRecentCookieJars.ts +++ b/src-web/hooks/useRecentCookieJars.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo } from 'react'; import { jotaiStore } from '../lib/jotai'; import { getKeyValue, setKeyValue } from '../lib/keyValueStore'; -import { activeCookieJarIdAtom } from './useActiveCookieJar'; +import {activeCookieJarAtom} from "./useActiveCookieJar"; import { activeWorkspaceIdAtom, useActiveWorkspace } from './useActiveWorkspace'; import { useCookieJars } from './useCookieJars'; import { useKeyValue } from './useKeyValue'; @@ -29,9 +29,9 @@ export function useRecentCookieJars() { export function useSubscribeRecentCookieJars() { useEffect(() => { - return jotaiStore.sub(activeCookieJarIdAtom, async () => { + return jotaiStore.sub(activeCookieJarAtom, async () => { const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom); - const activeCookieJarId = jotaiStore.get(activeCookieJarIdAtom); + const activeCookieJarId = jotaiStore.get(activeCookieJarAtom)?.id ?? null; if (activeWorkspaceId == null) return; if (activeCookieJarId == null) return; diff --git a/src-web/hooks/useRenameRequest.tsx b/src-web/hooks/useRenameRequest.tsx index 3e61c7a2..d377c278 100644 --- a/src-web/hooks/useRenameRequest.tsx +++ b/src-web/hooks/useRenameRequest.tsx @@ -1,13 +1,12 @@ -import { useFastMutation } from './useFastMutation'; import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models'; import { InlineCode } from '../components/core/InlineCode'; -import { usePrompt } from './usePrompt'; +import {showPrompt} from "../lib/prompt"; +import { useFastMutation } from './useFastMutation'; import { useRequests } from './useRequests'; import { useUpdateAnyGrpcRequest } from './useUpdateAnyGrpcRequest'; import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest'; export function useRenameRequest(requestId: string | null) { - const prompt = usePrompt(); const updateHttpRequest = useUpdateAnyHttpRequest(); const updateGrpcRequest = useUpdateAnyGrpcRequest(); const requests = useRequests(); @@ -18,7 +17,7 @@ export function useRenameRequest(requestId: string | null) { const request = requests.find((r) => r.id === requestId); if (request == null) return; - const name = await prompt({ + const name = await showPrompt({ id: 'rename-request', title: 'Rename Request', description: diff --git a/src-web/hooks/useSyncWorkspace.tsx b/src-web/hooks/useSyncWorkspace.tsx index 33059c51..b551eab6 100644 --- a/src-web/hooks/useSyncWorkspace.tsx +++ b/src-web/hooks/useSyncWorkspace.tsx @@ -4,9 +4,9 @@ import { applySync, calculateSync } from '@yaakapp-internal/sync'; import { useCallback, useMemo } from 'react'; import { InlineCode } from '../components/core/InlineCode'; import { VStack } from '../components/core/Stacks'; +import {showConfirm} from "../lib/confirm"; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { pluralizeCount } from '../lib/pluralize'; -import { useConfirm } from './useConfirm'; export function useSyncWorkspace( workspace: Workspace | null, @@ -16,12 +16,10 @@ export function useSyncWorkspace( debounceMillis?: number; } = {}, ) { - const confirm = useConfirm(); - const sync = useCallback(async () => { - if (workspace == null) return; + if (workspace == null || workspace.settingSyncDir) return; - const ops = await calculateSync(workspace); + const ops = await calculateSync(workspace) ?? []; if (ops.length === 0) { return; } @@ -33,7 +31,7 @@ export function useSyncWorkspace( return; } - const confirmed = await confirm({ + const confirmed = await showConfirm({ id: 'commit-sync', title: 'Filesystem Changes Detected', confirmText: 'Apply Changes', @@ -92,7 +90,7 @@ export function useSyncWorkspace( if (confirmed) { await applySync(workspace, ops); } - }, [confirm, workspace]); + }, [workspace]); const debouncedSync = useMemo(() => { return debounce(sync, debounceMillis); diff --git a/src-web/lib/alert.ts b/src-web/lib/alert.ts index 9f387239..8c0f6537 100644 --- a/src-web/lib/alert.ts +++ b/src-web/lib/alert.ts @@ -1,6 +1,6 @@ +import type { AlertProps } from '../components/core/Alert'; +import { Alert } from '../components/core/Alert'; import type { DialogProps } from '../components/core/Dialog'; -import type { AlertProps } from '../hooks/Alert'; -import { Alert } from '../hooks/Alert'; import { showDialog } from './dialog'; interface AlertArgs { diff --git a/src-web/lib/commands.ts b/src-web/lib/commands.ts new file mode 100644 index 00000000..7d768007 --- /dev/null +++ b/src-web/lib/commands.ts @@ -0,0 +1,51 @@ +import type { Folder, Workspace } from '@yaakapp-internal/models'; +import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace'; +import { createFastMutation } from '../hooks/useFastMutation'; +import { trackEvent } from './analytics'; +import { showPrompt } from './prompt'; +import { router } from './router'; +import { invokeCmd } from './tauri'; + +export const createWorkspace = createFastMutation>({ + mutationKey: ['create_workspace'], + mutationFn: (patch) => invokeCmd('cmd_update_workspace', { workspace: patch }), + onSuccess: async (workspace) => { + await router.navigate({ + to: '/workspaces/$workspaceId', + params: { workspaceId: workspace.id }, + }); + }, + onSettled: () => trackEvent('workspace', 'create'), +}); + +export const createFolder = createFastMutation< + Folder | null, + void, + Partial> +>({ + mutationKey: ['create_folder'], + mutationFn: async (patch) => { + const workspaceId = getActiveWorkspaceId(); + if (workspaceId == null) { + throw new Error("Cannot create folder when there's no active workspace"); + } + + if (!patch.name) { + const name = await showPrompt({ + id: 'new-folder', + label: 'Name', + defaultValue: 'Folder', + title: 'New Folder', + confirmText: 'Create', + placeholder: 'Name', + }); + if (name == null) return null; + + patch.name = name; + } + + patch.sortPriority = patch.sortPriority || -Date.now(); + return invokeCmd('cmd_update_folder', { folder: { workspaceId, ...patch } }); + }, + onSettled: () => trackEvent('folder', 'create'), +}); diff --git a/src-web/lib/confirm.ts b/src-web/lib/confirm.ts new file mode 100644 index 00000000..86096357 --- /dev/null +++ b/src-web/lib/confirm.ts @@ -0,0 +1,25 @@ +import type { ConfirmProps } from '../components/core/Confirm'; +import { Confirm } from '../components/core/Confirm'; +import type { DialogProps } from '../components/core/Dialog'; +import { showDialog } from './dialog'; + +interface ConfirmArgs { + id: string; + title: DialogProps['title']; + description?: DialogProps['description']; + variant?: ConfirmProps['variant']; + confirmText?: ConfirmProps['confirmText']; +} + +export async function showConfirm({ id, title, description, variant, confirmText }: ConfirmArgs) { + return new Promise((onResult: ConfirmProps['onResult']) => { + showDialog({ + id, + title, + description, + hideX: true, + size: 'sm', + render: ({ hide }) => Confirm({ onHide: hide, variant, onResult, confirmText }), + }); + }); +} diff --git a/src-web/lib/prompt.ts b/src-web/lib/prompt.ts new file mode 100644 index 00000000..b55a3647 --- /dev/null +++ b/src-web/lib/prompt.ts @@ -0,0 +1,36 @@ +import type { DialogProps } from '../components/core/Dialog'; +import type { PromptProps } from '../components/core/Prompt'; +import { Prompt } from '../components/core/Prompt'; +import { showDialog } from './dialog'; + +type PromptArgs = Pick & + Omit & { id: string }; + +export async function showPrompt({ id, title, description, ...props }: PromptArgs) { + return new Promise((resolve: PromptProps['onResult']) => { + showDialog({ + id, + title, + description, + hideX: true, + size: 'sm', + onClose: () => { + // Click backdrop, close, or escape + resolve(null); + }, + render: ({ hide }) => + Prompt({ + onCancel: () => { + // Click cancel button within dialog + resolve(null); + hide(); + }, + onResult: (v) => { + resolve(v); + hide(); + }, + ...props, + }), + }); + }); +} diff --git a/src-web/lib/setWorkspaceSearchParams.ts b/src-web/lib/setWorkspaceSearchParams.ts new file mode 100644 index 00000000..3f725904 --- /dev/null +++ b/src-web/lib/setWorkspaceSearchParams.ts @@ -0,0 +1,20 @@ +import { router } from './router.js'; + +/** + * Setting search params using "from" on the global router instance in tanstack router does not + * currently behave very well, so this is a wrapper function that gives a typesafe interface + * for the same thing. + */ +export function setWorkspaceSearchParams( + search: Partial<{ + cookie_jar_id: string | null; + environment_id: string | null; + request_id: string | null; + }>, +) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (router as any).navigate({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + search: (prev: any) => ({ ...prev, ...search }), + }); +} diff --git a/src-web/routes/workspaces/$workspaceId/index.tsx b/src-web/routes/workspaces/$workspaceId/index.tsx index 37e976ec..a925683d 100644 --- a/src-web/routes/workspaces/$workspaceId/index.tsx +++ b/src-web/routes/workspaces/$workspaceId/index.tsx @@ -1,10 +1,10 @@ -import { createFileRoute } from '@tanstack/react-router' -import { Workspace } from '../../../components/Workspace' +import { createFileRoute } from '@tanstack/react-router'; +import { Workspace } from '../../../components/Workspace'; interface WorkspaceSearchSchema { - request_id?: string | null - environment_id?: string | null - cookie_jar_id?: string | null + request_id?: string | null; + environment_id?: string | null; + cookie_jar_id?: string | null; } export const Route = createFileRoute('/workspaces/$workspaceId/')({ @@ -14,8 +14,8 @@ export const Route = createFileRoute('/workspaces/$workspaceId/')({ environment_id: search.environment_id as string, cookie_jar_id: search.cookie_jar_id as string, }), -}) +}); function RouteComponent() { - return + return ; }