>(
'requestPaneActiveTabs',
{},
@@ -102,12 +115,12 @@ export const RequestPane = memo(function RequestPane({
id: generateId(),
});
}
- await updateRequest.mutateAsync({ id: activeRequest.id, update: { headers } });
+ await updateRequestAsync({ id: activeRequest.id, update: { headers } });
// Force update header editor so any changed headers are reflected
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
},
- [activeRequest, updateRequest],
+ [activeRequest, updateRequestAsync],
);
const toast = useToast();
@@ -206,7 +219,7 @@ export const RequestPane = memo(function RequestPane({
showMethodToast(patch.method);
}
- await updateRequest.mutateAsync({ id: activeRequestId, update: patch });
+ await updateRequestAsync({ id: activeRequestId, update: patch });
if (newContentType !== undefined) {
await handleContentTypeChange(newContentType);
@@ -247,7 +260,7 @@ export const RequestPane = memo(function RequestPane({
token: authentication.token ?? '',
};
}
- await updateRequest.mutateAsync({
+ updateRequest({
id: activeRequestId,
update: { authenticationType, authentication },
});
@@ -267,25 +280,26 @@ export const RequestPane = memo(function RequestPane({
numParams,
toast,
updateRequest,
+ updateRequestAsync,
urlParameterPairs.length,
],
);
- const sendRequest = useSendAnyHttpRequest();
- const { activeResponse } = usePinnedHttpResponse(activeRequestId);
- const cancelResponse = useCancelHttpResponse(activeResponse?.id ?? null);
const isLoading = useIsResponseLoading(activeRequestId);
+ const { mutate: sendRequest } = useSendAnyHttpRequest();
+ const { activeResponse } = usePinnedHttpResponse(activeRequestId);
+ const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
const { updateKey } = useRequestUpdateKey(activeRequestId);
- const importCurl = useImportCurl();
- const importQuerystring = useImportQuerystring(activeRequestId);
+ const { mutate: importCurl } = useImportCurl();
+ const { mutate: importQuerystring } = useImportQuerystring(activeRequestId);
const handleBodyChange = useCallback(
- (body: HttpRequest['body']) => updateRequest.mutate({ id: activeRequestId, update: { body } }),
+ (body: HttpRequest['body']) => updateRequest({ id: activeRequestId, update: { body } }),
[activeRequestId, updateRequest],
);
const handleBodyTextChange = useCallback(
- (text: string) => updateRequest.mutate({ id: activeRequestId, update: { body: { text } } }),
+ (text: string) => updateRequest({ id: activeRequestId, update: { body: { text } } }),
[activeRequestId, updateRequest],
);
@@ -301,20 +315,48 @@ export const RequestPane = memo(function RequestPane({
setActiveTab(TAB_PARAMS);
});
- const autocomplete: GenericCompletionConfig = {
- minMatch: 3,
- options:
- requests.length > 0
- ? [
- ...requests
- .filter((r) => r.id !== activeRequestId)
- .map((r): GenericCompletionOption => ({ type: 'constant', label: r.url })),
- ]
- : [
- { label: 'http://', type: 'constant' },
- { label: 'https://', type: 'constant' },
- ],
- };
+ const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom);
+
+ const autocomplete: GenericCompletionConfig = useMemo(
+ () => ({
+ minMatch: 3,
+ options:
+ autocompleteUrls.length > 0
+ ? autocompleteUrls
+ : [
+ { label: 'http://', type: 'constant' },
+ { label: 'https://', type: 'constant' },
+ ],
+ }),
+ [autocompleteUrls],
+ );
+
+ const handlePaste = useCallback(
+ (text: string) => {
+ if (text.startsWith('curl ')) {
+ importCurl({ overwriteRequestId: activeRequestId, command: text });
+ } else {
+ // Only import query if pasted text contains entire querystring
+ importQuerystring(text);
+ }
+ },
+ [activeRequestId, importCurl, importQuerystring],
+ );
+
+ const handleSend = useCallback(
+ () => sendRequest(activeRequest.id ?? null),
+ [activeRequest.id, sendRequest],
+ );
+
+ const handleMethodChange = useCallback(
+ (method: string) => updateRequest({ id: activeRequestId, update: { method } }),
+ [activeRequestId, updateRequest],
+ );
+
+ const handleUrlChange = useCallback(
+ (url: string) => updateRequest({ id: activeRequestId, update: { url } }),
+ [activeRequestId, updateRequest],
+ );
return (
{
- if (text.startsWith('curl ')) {
- importCurl.mutate({ overwriteRequestId: activeRequestId, command: text });
- } else {
- // Only import query if pasted text contains entire querystring
- importQuerystring.mutate(text);
- }
- }}
+ onPasteOverwrite={handlePaste}
autocomplete={autocomplete}
- onSend={() => sendRequest.mutateAsync(activeRequest.id ?? null)}
- onCancel={cancelResponse.mutate}
- onMethodChange={(method) =>
- updateRequest.mutate({ id: activeRequestId, update: { method } })
- }
- onUrlChange={(url: string) =>
- updateRequest.mutate({ id: activeRequestId, update: { url } })
- }
+ onSend={handleSend}
+ onCancel={cancelResponse}
+ onMethodChange={handleMethodChange}
+ onUrlChange={handleUrlChange}
forceUpdateKey={updateKey}
isLoading={isLoading}
/>
@@ -372,9 +403,7 @@ export const RequestPane = memo(function RequestPane({
- updateRequest.mutate({ id: activeRequestId, update: { headers } })
- }
+ onChange={(headers) => updateRequest({ id: activeRequestId, update: { headers } })}
/>
@@ -383,7 +412,7 @@ export const RequestPane = memo(function RequestPane({
forceUpdateKey={forceUpdateKey + urlParametersKey}
pairs={urlParameterPairs}
onChange={(urlParameters) =>
- updateRequest.mutate({ id: activeRequestId, update: { urlParameters } })
+ updateRequest({ id: activeRequestId, update: { urlParameters } })
}
/>
@@ -437,9 +466,7 @@ export const RequestPane = memo(function RequestPane({
requestId={activeRequest.id}
contentType={contentType}
body={activeRequest.body}
- onChange={(body) =>
- updateRequest.mutate({ id: activeRequestId, update: { body } })
- }
+ onChange={(body) => updateRequest({ id: activeRequestId, update: { body } })}
onChangeContentType={handleContentTypeChange}
/>
) : typeof activeRequest.bodyType === 'string' ? (
@@ -467,9 +494,7 @@ export const RequestPane = memo(function RequestPane({
className="font-sans !text-xl !px-0"
containerClassName="border-0"
placeholder={activeRequest.id}
- onChange={(name) =>
- updateRequest.mutate({ id: activeRequestId, update: { name } })
- }
+ onChange={(name) => updateRequest({ id: activeRequestId, update: { name } })}
/>
- updateRequest.mutate({ id: activeRequestId, update: { description } })
+ updateRequest({ id: activeRequestId, update: { description } })
}
/>
diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx
index 274ef962..c39f23b5 100644
--- a/src-web/components/Sidebar.tsx
+++ b/src-web/components/Sidebar.tsx
@@ -317,7 +317,7 @@ export function Sidebar({ className }: Props) {
setShowMainContextMenu({ x: e.clientX, y: e.clientY });
}, []);
- const mainContextMenuItems = useCreateDropdownItems();
+ const mainContextMenuItems = useCreateDropdownItems({ folderId: null });
// Not ready to render yet
if (tree == null) {
diff --git a/src-web/components/SidebarAtoms.ts b/src-web/components/SidebarAtoms.ts
index 4724613c..d706576c 100644
--- a/src-web/components/SidebarAtoms.ts
+++ b/src-web/components/SidebarAtoms.ts
@@ -1,13 +1,12 @@
-import deepEqual from '@gilbarbara/deep-equal';
import type { Folder, GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
import { atom } from 'jotai';
-import { selectAtom } from 'jotai/utils';
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
import { foldersAtom } from '../hooks/useFolders';
import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
import { httpRequestsAtom } from '../hooks/useHttpRequests';
+import { deepEqualAtom } from '../lib/atoms';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { SidebarTreeNode } from './Sidebar';
@@ -27,11 +26,7 @@ const allPotentialChildrenAtom = atom((get) => {
}));
});
-const memoAllPotentialChildrenAtom = selectAtom(
- allPotentialChildrenAtom,
- (v) => v,
- (a, b) => deepEqual(a, b),
-);
+const memoAllPotentialChildrenAtom = deepEqualAtom(allPotentialChildrenAtom);
export const sidebarTreeAtom = atom<{
tree: SidebarTreeNode | null;
diff --git a/src-web/components/SidebarItemContextMenu.tsx b/src-web/components/SidebarItemContextMenu.tsx
index 05e56dd2..b2e92278 100644
--- a/src-web/components/SidebarItemContextMenu.tsx
+++ b/src-web/components/SidebarItemContextMenu.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useCallback } from 'react';
import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems';
import { useDeleteFolder } from '../hooks/useDeleteFolder';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
@@ -42,7 +42,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
folderId: child.model === 'folder' ? child.id : null,
});
- const items = useMemo(() => {
+ const items = useCallback((): DropdownItem[] => {
if (child.model === 'folder') {
return [
{
@@ -77,7 +77,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
onSelect: () => deleteFolder.mutate(),
},
{ type: 'separator' },
- ...createDropdownItems,
+ ...createDropdownItems(),
];
} else {
const requestItems: DropdownItem[] =
diff --git a/src-web/components/WorkspaceHeader.tsx b/src-web/components/WorkspaceHeader.tsx
index 1a3fefd6..5494afbc 100644
--- a/src-web/components/WorkspaceHeader.tsx
+++ b/src-web/components/WorkspaceHeader.tsx
@@ -1,8 +1,6 @@
import classNames from 'classnames';
import React, { memo } from 'react';
-import { useActiveRequest } from '../hooks/useActiveRequest';
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
-import { fallbackRequestName } from '../lib/fallbackRequestName';
import { CookieDropdown } from './CookieDropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
@@ -21,7 +19,6 @@ interface Props {
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
const togglePalette = useToggleCommandPalette();
- const activeRequest = useActiveRequest();
return (
-
+
diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx
index 79c098c8..0312577a 100644
--- a/src-web/components/core/Dropdown.tsx
+++ b/src-web/components/core/Dropdown.tsx
@@ -176,7 +176,7 @@ export const Dropdown = forwardRef
(function Dropdown
interface ContextMenuProps {
triggerPosition: { x: number; y: number } | null;
className?: string;
- items: DropdownItem[];
+ items: DropdownProps['items'];
onClose: () => void;
}
@@ -201,7 +201,7 @@ export const ContextMenu = forwardRef(function Co
isOpen={true} // Always open because we return null if not
className={className}
ref={ref}
- items={items}
+ items={typeof items === 'function' ? items() : items}
onClose={onClose}
triggerShape={triggerShape}
/>
diff --git a/src-web/components/core/Tabs/Tabs.tsx b/src-web/components/core/Tabs/Tabs.tsx
index c5cce8ed..1996c781 100644
--- a/src-web/components/core/Tabs/Tabs.tsx
+++ b/src-web/components/core/Tabs/Tabs.tsx
@@ -67,7 +67,7 @@ export function Tabs({
ref={ref}
className={classNames(
className,
- 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 overflow-x-hidden',
+ 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 ',
)}
>
{
- const activeRequest = get(activeRequestAtom);
- return fallbackRequestName(activeRequest);
-});
-
export function useActiveRequest(
model?: T | undefined,
): TypeMap[T] | null {
diff --git a/src-web/hooks/useCreateDropdownItems.tsx b/src-web/hooks/useCreateDropdownItems.tsx
index 70901a43..47597297 100644
--- a/src-web/hooks/useCreateDropdownItems.tsx
+++ b/src-web/hooks/useCreateDropdownItems.tsx
@@ -1,8 +1,9 @@
-import { useMemo } from 'react';
+import { useCallback } from 'react';
import type { DropdownItem } from '../components/core/Dropdown';
import { Icon } from '../components/core/Icon';
import { generateId } from '../lib/generateId';
import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
+import { getActiveRequest } from './useActiveRequest';
import { useCreateFolder } from './useCreateFolder';
import { useCreateGrpcRequest } from './useCreateGrpcRequest';
import { useCreateHttpRequest } from './useCreateHttpRequest';
@@ -14,19 +15,26 @@ export function useCreateDropdownItems({
}: {
hideFolder?: boolean;
hideIcons?: boolean;
- folderId?: string | null;
-} = {}): DropdownItem[] {
+ folderId?: string | null | 'active-folder';
+} = {}): () => DropdownItem[] {
const { mutate: createHttpRequest } = useCreateHttpRequest();
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
const { mutate: createFolder } = useCreateFolder();
- return useMemo(
- () => [
+ return useCallback(
+ (): DropdownItem[] => [
{
key: 'create-http-request',
label: 'HTTP Request',
leftSlot: hideIcons ? undefined : ,
- onSelect: () => createHttpRequest({ folderId }),
+ onSelect: () => {
+ const args = { folderId };
+ if (folderId === 'active-folder') {
+ const activeRequest = getActiveRequest();
+ args.folderId = activeRequest?.folderId ?? undefined;
+ }
+ createHttpRequest(args);
+ },
},
{
key: 'create-graphql-request',
diff --git a/src-web/lib/atoms.ts b/src-web/lib/atoms.ts
new file mode 100644
index 00000000..b2665a17
--- /dev/null
+++ b/src-web/lib/atoms.ts
@@ -0,0 +1,11 @@
+import deepEqual from '@gilbarbara/deep-equal';
+import type { Atom } from 'jotai';
+import { selectAtom } from 'jotai/utils';
+
+export function deepEqualAtom(a: Atom) {
+ return selectAtom(
+ a,
+ (v) => v,
+ (a, b) => deepEqual(a, b),
+ );
+}