diff --git a/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts b/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts index b45b2ec932..8cd17e46c2 100644 --- a/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts +++ b/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts @@ -1,4 +1,5 @@ -import fs from 'fs'; +import { stat } from 'node:fs/promises'; + import NeDB from 'nedb'; import path from 'path'; @@ -8,7 +9,9 @@ import type { BaseModel } from '../models/types'; const neDbAdapter: DbAdapter = async (dir, filterTypes) => { // Confirm if db files exist - if (!fs.existsSync(path.join(dir, 'insomnia.Workspace.db'))) { + try { + await stat(path.join(dir, 'insomnia.Workspace.db')); + } catch (err) { return null; } diff --git a/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts b/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts index 29cc3ccaae..ddd5895024 100644 --- a/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/graphql.test.ts @@ -58,7 +58,7 @@ test('can send GraphQL requests after editing and prettifying query', async ({ a await page.getByRole('button', { name: 'GraphQL request' }).click(); // Edit and prettify query - await page.locator('pre[role="presentation"]:has-text("hello,")').click(); + await page.locator('pre[role="presentation"]:has-text("bearer")').click(); await page.locator('.app').press('Enter'); await page.locator('text=Prettify GraphQL').click(); await page.click('[data-testid="request-pane"] >> text=Send'); diff --git a/packages/insomnia/src/common/render.ts b/packages/insomnia/src/common/render.ts index 143147fecb..54dc40204a 100644 --- a/packages/insomnia/src/common/render.ts +++ b/packages/insomnia/src/common/render.ts @@ -317,7 +317,6 @@ export async function getRenderContext( const project = ancestors.find(isProject); const workspace = ancestors.find(isWorkspace); - console.log('getRenderContext', { request, environmentId, ancestors, purpose, extraInfo, project, workspace }); if (!workspace) { throw new Error('Failed to render. Could not find workspace'); } diff --git a/packages/insomnia/src/models/helpers/get-status-candidates.ts b/packages/insomnia/src/models/helpers/get-status-candidates.ts deleted file mode 100644 index 6d9332469c..0000000000 --- a/packages/insomnia/src/models/helpers/get-status-candidates.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { StatusCandidate } from '../../sync/types'; -import { BaseModel, canSync } from '..'; - -const toStatusCandidate = (doc: BaseModel): StatusCandidate => ({ - key: doc._id, - name: doc.name || '', - document: doc, -}); - -export const getStatusCandidates = (docs: BaseModel[]) => docs.filter(canSync).map(toStatusCandidate); diff --git a/packages/insomnia/src/models/request-version.ts b/packages/insomnia/src/models/request-version.ts index 4e4e960a43..e6b4d717a4 100644 --- a/packages/insomnia/src/models/request-version.ts +++ b/packages/insomnia/src/models/request-version.ts @@ -53,6 +53,10 @@ export function getById(id: string) { return db.get(type, id); } +export function findByParentId(parentId: string) { + return db.find(type, { parentId }); +} + export async function create(request: Request | WebSocketRequest | GrpcRequest) { if (!isRequest(request) && !isWebSocketRequest(request)) { throw new Error(`New ${type} was not given a valid ${request.type} instance`); diff --git a/packages/insomnia/src/models/response.ts b/packages/insomnia/src/models/response.ts index 6cb22ad5d9..51e25a52f1 100644 --- a/packages/insomnia/src/models/response.ts +++ b/packages/insomnia/src/models/response.ts @@ -109,6 +109,10 @@ export function getById(id: string) { return db.get(type, id); } +export function findByParentId(parentId: string) { + return db.find(type, { parentId: parentId }); +} + export async function all() { return db.all(type); } diff --git a/packages/insomnia/src/models/websocket-response.ts b/packages/insomnia/src/models/websocket-response.ts index 7e324d605d..31cf2a0a96 100644 --- a/packages/insomnia/src/models/websocket-response.ts +++ b/packages/insomnia/src/models/websocket-response.ts @@ -82,6 +82,10 @@ export function getById(id: string) { return db.get(type, id); } +export function findByParentId(parentId: string) { + return db.find(type, { parentId: parentId }); +} + export async function all() { return db.all(type); } diff --git a/packages/insomnia/src/sync/vcs/initialize-backend-project.ts b/packages/insomnia/src/sync/vcs/initialize-backend-project.ts index 603459bfa5..262b339aa9 100644 --- a/packages/insomnia/src/sync/vcs/initialize-backend-project.ts +++ b/packages/insomnia/src/sync/vcs/initialize-backend-project.ts @@ -1,9 +1,10 @@ import { database } from '../../common/database'; import * as models from '../../models'; -import { getStatusCandidates } from '../../models/helpers/get-status-candidates'; +import { BaseModel, canSync } from '../../models'; import { Project } from '../../models/project'; import { Workspace } from '../../models/workspace'; import { WorkspaceMeta } from '../../models/workspace-meta'; +import { StatusCandidate } from '../types'; import { VCS } from './vcs'; const blankStage = {}; @@ -13,7 +14,11 @@ export const initializeLocalBackendProjectAndMarkForSync = async ({ vcs, workspa await vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name); // Everything unstaged - const candidates = getStatusCandidates(await database.withDescendants(workspace)); + const candidates = (await database.withDescendants(workspace)).filter(canSync).map((doc: BaseModel): StatusCandidate => ({ + key: doc._id, + name: doc.name || '', + document: doc, + })); const status = await vcs.status(candidates, blankStage); // Stage everything diff --git a/packages/insomnia/src/ui/components/cookie-list.tsx b/packages/insomnia/src/ui/components/cookie-list.tsx index fed791dec1..19fc8f0ae2 100644 --- a/packages/insomnia/src/ui/components/cookie-list.tsx +++ b/packages/insomnia/src/ui/components/cookie-list.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import { Cookie as ToughCookie } from 'tough-cookie'; import { v4 as uuidv4 } from 'uuid'; @@ -6,7 +6,6 @@ import { cookieToString } from '../../common/cookies'; import { Cookie } from '../../models/cookie-jar'; import { Dropdown, DropdownButton, DropdownItem, ItemContent } from './base/dropdown'; import { PromptButton } from './base/prompt-button'; -import { showModal } from './modals'; import { CookieModifyModal } from './modals/cookie-modify-modal'; import { RenderedText } from './rendered-text'; @@ -27,6 +26,7 @@ const CookieRow: FC<{ index: number; deleteCookie: (cookie: Cookie) => void; }> = ({ cookie, index, deleteCookie }) => { + const [isCookieModalOpen, setIsCookieModalOpen] = useState(false); const c = ToughCookie.fromJSON(cookie); const cookieString = c ? cookieToString(c) : ''; return @@ -39,7 +39,7 @@ const CookieRow: FC<{ { }} className="text-right no-wrap"> - - + + + +
+ +
+
+
+ + )} + + + + + + ); }); -CookieModifyModal.displayName = 'CookieModifyModal'; diff --git a/packages/insomnia/src/ui/components/modals/cookies-modal.tsx b/packages/insomnia/src/ui/components/modals/cookies-modal.tsx index c5d4698cb5..8fb6e971b3 100644 --- a/packages/insomnia/src/ui/components/modals/cookies-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/cookies-modal.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { OverlayContainer } from 'react-aria'; import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom'; import { fuzzyMatch } from '../../../common/misc'; @@ -10,12 +11,8 @@ import { ModalBody } from '../base/modal-body'; import { ModalFooter } from '../base/modal-footer'; import { ModalHeader } from '../base/modal-header'; import { CookieList } from '../cookie-list'; -import { showModal } from '.'; -export interface CookiesModalHandle { - show: () => void; - hide: () => void; -} -export const CookiesModal = forwardRef((_, ref) => { + +export const CookiesModal = ({ onHide }: ModalProps) => { const modalRef = useRef(null); const { handleRender } = useNunjucks(); const [filter, setFilter] = useState(''); @@ -23,15 +20,10 @@ export const CookiesModal = forwardRef((_, ref) const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData; const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>(); const updateCookieJarFetcher = useFetcher(); + useEffect(() => { + modalRef.current?.show(); + }, []); - useImperativeHandle(ref, () => ({ - hide: () => { - modalRef.current?.hide(); - }, - show: () => { - modalRef.current?.show(); - }, - }), []); const updateCookieJar = async (cookieJarId: string, patch: CookieJar) => { updateCookieJarFetcher.submit(JSON.stringify({ patch, cookieJarId }), { encType: 'application/json', @@ -41,80 +33,79 @@ export const CookiesModal = forwardRef((_, ref) }; const filteredCookies = visibleCookieIndexes ? (activeCookieJar?.cookies || []).filter((_, i) => visibleCookieIndexes.includes(i)) : (activeCookieJar?.cookies || []); return ( - - Manage Cookies - - {activeCookieJar && ( -
-
-
-
+ + ); -}); -RequestGroupSettingsModal.displayName = 'RequestGroupSettingsModal'; +}; diff --git a/packages/insomnia/src/ui/components/modals/request-render-error-modal.tsx b/packages/insomnia/src/ui/components/modals/request-render-error-modal.tsx index b3c4817281..024b484620 100644 --- a/packages/insomnia/src/ui/components/modals/request-render-error-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/request-render-error-modal.tsx @@ -4,15 +4,12 @@ import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react' import { docsTemplateTags } from '../../../common/documentation'; import { GrpcRequest } from '../../../models/grpc-request'; import { Request } from '../../../models/request'; -import { isRequest } from '../../../models/request'; import { WebSocketRequest } from '../../../models/websocket-request'; import { RenderError } from '../../../templating'; import { Link } from '../base/link'; import { Modal, type ModalHandle, ModalProps } from '../base/modal'; import { ModalBody } from '../base/modal-body'; import { ModalHeader } from '../base/modal-header'; -import { RequestSettingsModal } from '../modals/request-settings-modal'; -import { showModal } from './index'; export interface RequestRenderErrorModalOptions { error: RenderError | null; request: Request | WebSocketRequest | GrpcRequest | null; @@ -55,17 +52,6 @@ export const RequestRenderErrorModal = forwardRef{fullPath} prior to sending

- {error.path?.match(/^body/) && isRequest(request) && ( - - )} Templating Documentation diff --git a/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx b/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx index 5feed187d1..9645f8c839 100644 --- a/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/request-settings-modal.tsx @@ -1,13 +1,12 @@ -import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { OverlayContainer } from 'react-aria'; import { useSelector } from 'react-redux'; import { useFetcher, useParams } from 'react-router-dom'; -import { database as db } from '../../../common/database'; import * as models from '../../../models'; import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request'; import { isRequest, Request } from '../../../models/request'; import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-request'; -import { isWorkspace, Workspace } from '../../../models/workspace'; import { invariant } from '../../../utils/invariant'; import { useRequestPatcher } from '../../hooks/use-request'; import { selectWorkspacesForActiveProject } from '../../redux/selectors'; @@ -20,413 +19,346 @@ import { MarkdownEditor } from '../markdown-editor'; export interface RequestSettingsModalOptions { request: Request | GrpcRequest | WebSocketRequest; - forceEditMode?: boolean; } interface State { - request: Request | GrpcRequest | WebSocketRequest | null; - showDescription: boolean; defaultPreviewMode: boolean; activeWorkspaceIdToCopyTo: string | null; - workspace?: Workspace; } export interface RequestSettingsModalHandle { show: (options: RequestSettingsModalOptions) => void; hide: () => void; } -export const RequestSettingsModal = forwardRef((_, ref) => { +export const RequestSettingsModal = ({ request, onHide }: ModalProps & RequestSettingsModalOptions) => { const modalRef = useRef(null); const editorRef = useRef(null); - + const workspacesForActiveProject = useSelector(selectWorkspacesForActiveProject); const [state, setState] = useState({ - request: null, - showDescription: false, - defaultPreviewMode: false, + defaultPreviewMode: !!request?.description, activeWorkspaceIdToCopyTo: null, - workspace: undefined, }); + useEffect(() => { + modalRef.current?.show(); + }, []); + const requestFetcher = useFetcher(); const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string }; const patchRequest = useRequestPatcher(); - const workspacesForActiveProject = useSelector(selectWorkspacesForActiveProject); - useImperativeHandle(ref, () => ({ - hide: () => { - modalRef.current?.hide(); - }, - show: async ({ request, forceEditMode }) => { - const hasDescription = !!request.description; - // Find this request workspace for filtering out of workspaces list - const ancestors = await db.withAncestors(request); - const workspace = workspacesForActiveProject.find(w => w._id === ancestors.find(isWorkspace)?._id); - setState(state => ({ - ...state, - request, - workspace: workspace, - activeWorkspaceIdToCopyTo: null, - showDescription: forceEditMode || hasDescription, - defaultPreviewMode: hasDescription && !forceEditMode, - })); - modalRef.current?.show(); - }, - }), [workspacesForActiveProject]); - - const updateRequest = (req: Partial) => { - invariant(state.request, 'Request is required'); - patchRequest(state.request._id, req); - }; const duplicateRequest = (r: Partial) => { - invariant(state.request, 'Request is required'); requestFetcher.submit(JSON.stringify(r), { - action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/${state.request._id}/duplicate`, + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/${request._id}/duplicate`, method: 'post', encType: 'application/json', }); }; async function handleMoveToWorkspace() { - const { activeWorkspaceIdToCopyTo } = state; - invariant(activeWorkspaceIdToCopyTo, 'Workspace ID is required'); - updateRequest({ parentId: activeWorkspaceIdToCopyTo }); + invariant(state.activeWorkspaceIdToCopyTo, 'Workspace ID is required'); + patchRequest(request._id, { parentId: state.activeWorkspaceIdToCopyTo }); modalRef.current?.hide(); } async function handleCopyToWorkspace() { - const { activeWorkspaceIdToCopyTo } = state; - invariant(activeWorkspaceIdToCopyTo, 'Workspace ID is required'); - duplicateRequest({ parentId: activeWorkspaceIdToCopyTo }); + invariant(state.activeWorkspaceIdToCopyTo, 'Workspace ID is required'); + duplicateRequest({ parentId: state.activeWorkspaceIdToCopyTo }); } - const { request, showDescription, defaultPreviewMode, activeWorkspaceIdToCopyTo, workspace } = state; + const { defaultPreviewMode, activeWorkspaceIdToCopyTo } = state; const toggleCheckBox = async (event: any) => { - updateRequest({ [event.currentTarget.name]: event.currentTarget.checked ? true : false }); - - const updated = { ...state.request, [event.currentTarget.name]: event.currentTarget.checked } as Request; - setState(state => ({ ...state, request: updated })); + patchRequest(request._id, { [event.currentTarget.name]: event.currentTarget.checked ? true : false }); }; const updateDescription = (description: string) => { - updateRequest({ description }); - const updated = { ...state.request, description } as Request; + patchRequest(request._id, { description }); setState({ ...state, - request: updated, defaultPreviewMode: false, }); }; - const updateName = (name: string) => { - updateRequest({ name }); - const updated = { ...state.request, name } as Request; - setState(state => ({ - ...state, - request: updated, - })); - }; return ( - - - Request Settings{' '} - {request ? request._id : ''} - - -
-
- + + + + Request Settings{' '} + {request ? request._id : ''} + + +
+
+ +
+ {request && isWebSocketRequest(request) && ( + <> + + <> +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ )} + {request && isGrpcRequest(request) && ( +

+ Are there any gRPC settings you expect to see? Create a{' '} + feature request! +

+ )} + {request && isRequest(request) && ( + <> + + <> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ ) + }
- {request && isWebSocketRequest(request) && ( - <> - <> - {showDescription ? ( - - ) : ( - - )} - - <> -
-
- -
-
- -
-
-
- -
- -
-
-
- -
-
- -
-
- -
-
- )} - {request && isGrpcRequest(request) && ( -

- Are there any gRPC settings you expect to see? Create a{' '} - feature request! -

- )} - {request && isRequest(request) && ( - <> - <> - {showDescription ? ( - - ) : ( - - )} - - <> -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
- -
-
-
- -
-
- -
-
- -
-
- ) - } -
- - + + + ); -}); - -RequestSettingsModal.displayName = 'RequestSettingsModal'; +}; diff --git a/packages/insomnia/src/ui/components/modals/request-switcher-modal.tsx b/packages/insomnia/src/ui/components/modals/request-switcher-modal.tsx deleted file mode 100644 index 012869461c..0000000000 --- a/packages/insomnia/src/ui/components/modals/request-switcher-modal.tsx +++ /dev/null @@ -1,458 +0,0 @@ -import classnames from 'classnames'; -import React, { forwardRef, Fragment, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom'; - -import { METHOD_GRPC } from '../../../common/constants'; -import { fuzzyMatchAll } from '../../../common/misc'; -import * as models from '../../../models'; -import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request'; -import { isRequest, Request } from '../../../models/request'; -import { isRequestGroup, RequestGroup } from '../../../models/request-group'; -import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-request'; -import { Workspace } from '../../../models/workspace'; -import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../../utils/url/querystring'; -import { useRequestMetaPatcher } from '../../hooks/use-request'; -import { selectGrpcRequestMetas, selectRequestMetas, selectWorkspaceRequestsAndRequestGroups, selectWorkspacesForActiveProject } from '../../redux/selectors'; -import { RequestLoaderData } from '../../routes/request'; -import { WorkspaceLoaderData } from '../../routes/workspace'; -import { Highlight } from '../base/highlight'; -import { Modal, ModalHandle, ModalProps } from '../base/modal'; -import { ModalBody } from '../base/modal-body'; -import { ModalHeader } from '../base/modal-header'; -import { createKeybindingsHandler, useDocBodyKeyboardShortcuts } from '../keydown-binder'; -import { GrpcTag } from '../tags/grpc-tag'; -import { MethodTag } from '../tags/method-tag'; -import { WebSocketTag } from '../tags/websocket-tag'; -import { wrapToIndex } from './utils'; -interface State { - searchString: string; - workspacesForActiveProject: Workspace[]; - matchedRequests: (Request | WebSocketRequest | GrpcRequest)[]; - matchedWorkspaces: Workspace[]; - activeIndex: number; - maxRequests: number; - maxWorkspaces: number; - disableInput: boolean; - selectOnKeyup: boolean; - hideNeverActiveRequests: boolean; - isModalVisible: boolean; - title: string | null; -} -interface RequestSwitcherModalOptions { - maxRequests?: number; - maxWorkspaces?: number; - disableInput?: boolean; - selectOnKeyup?: boolean; - hideNeverActiveRequests?: boolean; - title?: string; - openDelay?: number; -} -export interface RequestSwitcherModalHandle { - show: (options?: RequestSwitcherModalOptions) => void; - hide: () => void; -} -export const RequestSwitcherModal = forwardRef((_, ref) => { - const modalRef = useRef(null); - const [state, setState] = useState({ - searchString: '', - workspacesForActiveProject: [], - matchedRequests: [], - matchedWorkspaces: [], - activeIndex: -1, - maxRequests: 20, - maxWorkspaces: 20, - disableInput: false, - selectOnKeyup: false, - hideNeverActiveRequests: false, - isModalVisible: true, - title: null, - }); - const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>(); - const navigate = useNavigate(); - const requestData = useRouteLoaderData('request/:requestId') as RequestLoaderData | undefined; - const { activeRequest } = requestData || {}; - const { - activeWorkspace: workspace, - activeWorkspaceMeta, - } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData; - const workspacesForActiveProject = useSelector(selectWorkspacesForActiveProject); - const requestMetas = useSelector(selectRequestMetas); - const grpcRequestMetas = useSelector(selectGrpcRequestMetas); - const workspaceRequestsAndRequestGroups = useSelector(selectWorkspaceRequestsAndRequestGroups); - const patchRequestMeta = useRequestMetaPatcher(); - /** Return array of path segments for given folders */ - const pathSegments = useCallback((requestOrRequestGroup: Request | WebSocketRequest | GrpcRequest | RequestGroup): string[] => { - const folders = workspaceRequestsAndRequestGroups.filter(isRequestGroup) - .filter(g => g._id === requestOrRequestGroup.parentId); - const folderName = isRequestGroup(requestOrRequestGroup) ? `${requestOrRequestGroup.name}` : ''; - // It's the final parent - if (folders.length === 0) { - return [folderName]; - } - // Still has more parents - if (folderName) { - return [...pathSegments(folders[0]), folderName]; - } - // It's the child - return pathSegments(folders[0]); - }, [workspaceRequestsAndRequestGroups]); - const getLastActiveRequestMap = useCallback(() => { - // requestIds: lastActive datetime - const lastActiveMap: Record = {}; - - for (const meta of requestMetas) { - lastActiveMap[meta.parentId] = meta.lastActive; - } - for (const meta of grpcRequestMetas) { - lastActiveMap[meta.parentId] = meta.lastActive; - } - return lastActiveMap; - }, [grpcRequestMetas, requestMetas]); - - const isMatch = useCallback((request: Request | WebSocketRequest | GrpcRequest, searchStrings: string): number | null => { - if (request._id === searchStrings) { - return Infinity; - } - // name - const searchIndexes = [request.name]; - // url - isGrpcRequest(request) ? searchIndexes.push(request.url + request.protoMethodName) - : searchIndexes.push(joinUrlAndQueryString(request.url, buildQueryStringFromParams(request.parameters))); - // http method - const method = isRequest(request) && request.method; - method && searchIndexes.push(method); - isGrpcRequest(request) && searchIndexes.push(METHOD_GRPC); - // path segments - searchIndexes.push(pathSegments(request).join('/')); - const match = fuzzyMatchAll( - searchStrings, - searchIndexes, - { splitSpace: true }, - ); - if (!match) { - return null; - } - return match.score; - }, [pathSegments]); - const handleChangeValue = useCallback((searchString: string) => { - const { maxRequests, maxWorkspaces, hideNeverActiveRequests } = state; - const lastActiveMap = getLastActiveRequestMap(); - - // OPTIMIZATION: This only filters if we have a filter - let matchedRequests = (workspaceRequestsAndRequestGroups - .filter(child => isRequest(child) || isWebSocketRequest(child) || isGrpcRequest(child)) as (Request | WebSocketRequest | GrpcRequest)[]) - .sort((a, b) => { - const aLA = lastActiveMap[a._id] || 0; - const bLA = lastActiveMap[b._id] || 0; - // If lastActive same, go by name - if (aLA === bLA) { - return a.name > b.name ? 1 : -1; - } - return bLA - aLA; - }); - - if (hideNeverActiveRequests) { - matchedRequests = matchedRequests.filter(r => lastActiveMap[r._id]); - } - - if (searchString) { - matchedRequests = matchedRequests - .map(r => ({ - request: r, - score: isMatch(r, searchString), - })) - .filter(v => v.score !== null) - .sort((a, b) => Number(a.score || -Infinity) - Number(b.score || -Infinity)) - .map(v => v.request); - } - - const matchedWorkspaces = workspacesForActiveProject - .filter(w => w._id !== workspace._id) - .filter(w => { - const name = w.name.toLowerCase(); - const toMatch = searchString.toLowerCase(); - return name.indexOf(toMatch) !== -1; - }); - // Make sure we select the first item but we don't want to select the currently active - // one because that wouldn't make any sense. - const activeRequestId = activeRequest ? activeRequest._id : 'n/a'; - const indexOfFirstNonActiveRequest = matchedRequests.findIndex(r => r._id !== activeRequestId); - setState(state => ({ - ...state, - searchString, - activeIndex: indexOfFirstNonActiveRequest >= 0 ? indexOfFirstNonActiveRequest : 0, - matchedRequests: matchedRequests.slice(0, maxRequests), - matchedWorkspaces: matchedWorkspaces.slice(0, maxWorkspaces), - })); - }, [state, getLastActiveRequestMap, workspaceRequestsAndRequestGroups, workspacesForActiveProject, activeRequest, isMatch, workspace._id]); - - useImperativeHandle(ref, () => ({ - hide: () => { - modalRef.current?.hide(); - }, - show: options => { - if (modalRef.current?.isOpen()) { - return; - } - - setState(state => ({ - ...state, - maxRequests: options?.maxRequests ?? 20, - maxWorkspaces: options?.maxWorkspaces ?? 20, - disableInput: !!options?.disableInput, - hideNeverActiveRequests: !!options?.hideNeverActiveRequests, - selectOnKeyup: !!options?.selectOnKeyup, - title: options?.title || null, - isModalVisible: true, - })); - handleChangeValue(''); - modalRef.current?.show(); - }, - }), [handleChangeValue]); - - const activateWorkspaceAndHide = useCallback((workspace: Workspace) => { - if (!workspace) { - return; - } - console.log(`[app] Activating workspace "${workspace.name}"`); - navigate(`/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}`); - modalRef.current?.hide(); - }, [navigate, organizationId, projectId]); - - const activateRequestAndHide = useCallback((request?: Request | WebSocketRequest | GrpcRequest) => { - if (!request) { - return; - } - models.workspaceMeta.update(activeWorkspaceMeta, { activeRequestId: request._id }); - patchRequestMeta(request._id, { lastActive: Date.now() }); - modalRef.current?.hide(); - }, [activeWorkspaceMeta, patchRequestMeta]); - - const activateCurrentIndex = useCallback(async () => { - const { activeIndex, matchedRequests, matchedWorkspaces, searchString } = state; - if (activeIndex < matchedRequests.length) { - // Activate the request if there is one - const request = matchedRequests[activeIndex]; - activateRequestAndHide(request); - return; - } - if (activeIndex < matchedRequests.length + matchedWorkspaces.length) { - // Activate the workspace if there is one - const index = activeIndex - matchedRequests.length; - const workspace = matchedWorkspaces[index]; - if (workspace) { - activateWorkspaceAndHide(workspace); - } - return; - } - if (searchString) { - // Create request if no match - if (!workspace) { - return; - } - - // Create the request if nothing matched - const request = await models.request.create({ - parentId: activeRequest ? activeRequest.parentId : workspace._id, - name: state.searchString, - }); - - activateRequestAndHide(request); - } - }, [activateRequestAndHide, activateWorkspaceAndHide, activeRequest, state, workspace]); - - const handleInputKeydown = createKeybindingsHandler({ - 'ArrowUp': e => { - e.preventDefault(); - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex - 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - }, - 'Shift+Tab': e => { - e.preventDefault(); - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex - 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - }, - 'ArrowDown': e => { - e.preventDefault(); - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex + 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - }, - 'Tab': e => { - e.preventDefault(); - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex + 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - }, - 'Enter': e => { - e.preventDefault(); - activateCurrentIndex(); - }, - }); - - useDocBodyKeyboardShortcuts({ - request_showRecent: () => { - if (state.isModalVisible) { - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex + 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - } - }, - request_showRecentPrevious: () => { - if (state.isModalVisible) { - setState(state => ({ - ...state, - activeIndex: wrapToIndex(state.activeIndex - 1, state.matchedRequests.length + state.matchedWorkspaces.length), - })); - } - }, - }); - - useEffect(() => { - const handleKeyup = async (event: KeyboardEvent) => { - // Handle selection if unpresses all modifier keys. Ideally this would trigger once - // the user unpresses the hotkey that triggered this modal but we currently do not - // have the facilities to do that. - const isMetaKeyDown = event.ctrlKey || event.shiftKey || event.metaKey || event.altKey; - - if (state.selectOnKeyup && modalRef.current?.isOpen() && !isMetaKeyDown) { - await activateCurrentIndex(); - modalRef.current?.hide(); - } - }; - - document.body.addEventListener('keyup', handleKeyup); - - return () => { - document.body.removeEventListener('keyup', handleKeyup); - }; - }, [activateCurrentIndex, state.selectOnKeyup]); - - const { - searchString, - activeIndex, - matchedRequests, - matchedWorkspaces, - disableInput, - title, - isModalVisible, - } = state; - const requestGroups = workspaceRequestsAndRequestGroups.filter(isRequestGroup); - return ( - - - {title || ( - -
- -
- tab or  - ↑↓ to navigate     - ↵  to select     - esc to dismiss -
-
-
-
Quick Switch
-
- )} -
- - {!disableInput && ( -
-
- handleChangeValue(event.currentTarget.value)} - /> -
-
- )} -
    - {matchedRequests.map((r: Request | WebSocketRequest | GrpcRequest, i) => { - const requestGroup = requestGroups.find(rg => rg._id === r.parentId); - const buttonClasses = classnames( - 'btn btn--expandable-small wide text-left pad-bottom', - { - focus: activeIndex === i, - }, - ); - return ( -
  • - -
  • - ); - })} - - {matchedRequests.length > 0 && matchedWorkspaces.length > 0 && ( -
  • -
    -
  • - )} - - {matchedWorkspaces.map((w, i) => { - const buttonClasses = classnames('btn btn--super-compact wide text-left', { - focus: activeIndex - matchedRequests.length === i, - }); - return ( -
  • - -
  • - ); - })} -
- - {searchString && matchedRequests.length === 0 && matchedWorkspaces.length === 0 && ( -
-

- No matches found for {searchString} -

- - {workspace ? : null} -
- )} -
-
- ); -}); -RequestSwitcherModal.displayName = 'RequestSwitcherModal'; diff --git a/packages/insomnia/src/ui/components/modals/sync-branches-modal.tsx b/packages/insomnia/src/ui/components/modals/sync-branches-modal.tsx index b6d72b23ce..294f1d6dad 100644 --- a/packages/insomnia/src/ui/components/modals/sync-branches-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/sync-branches-modal.tsx @@ -1,5 +1,6 @@ import classnames from 'classnames'; -import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { OverlayContainer } from 'react-aria'; import { useSelector } from 'react-redux'; import { database as db, Operation } from '../../../common/database'; @@ -30,7 +31,7 @@ export interface SyncBranchesModalHandle { show: (options: SyncBranchesModalOptions) => void; hide: () => void; } -export const SyncBranchesModal = forwardRef(({ vcs }, ref) => { +export const SyncBranchesModal = ({ vcs, onHide }: Props) => { const modalRef = useRef(null); const [state, setState] = useState({ error: '', @@ -69,17 +70,11 @@ export const SyncBranchesModal = forwardRef(({ v })); } }, [vcs]); - useImperativeHandle(ref, () => ({ - hide: () => modalRef.current?.hide(), - show: ({ onHide }) => { - setState(state => ({ - ...state, - onHide, - })); - refreshState(); - modalRef.current?.show({ onHide }); - }, - }), [refreshState]); + refreshState(); + useEffect(() => { + modalRef.current?.show(); + }, []); + const syncItems = useSelector(selectSyncItems); async function handleCheckout(branch: string) { try { @@ -160,138 +155,140 @@ export const SyncBranchesModal = forwardRef(({ v const { branches, remoteBranches, currentBranch, newBranchName, error } = state; return ( - - Branches - - {error && ( -

- - {error} -

- )} -
-
-
- -
-
- + {error} +

+ )} + +
+
+ +
+
+ +
-
- + -
- - - - - - - - - {branches.map(name => ( - - - - - ))} - -
Branches 
- - {name} - - {name === currentBranch ? ( - (current) - ) : null} - {name === 'master' && } - - handleMerge(name)} - > - Merge - - handleDelete(name)} - > - Delete - - -
-
- - {remoteBranches.length > 0 && (
- + - {remoteBranches.map(name => ( + {branches.map(name => ( ))}
Remote BranchesBranches  
- {name} + + {name} + + {name === currentBranch ? ( + (current) + ) : null} {name === 'master' && } - {name !== 'master' && ( - handleRemoteDelete(name)} - > - Delete - - )} - handleMerge(name)} > - Fetch - + Merge + + handleDelete(name)} + > + Delete + +
- )} - - + + {remoteBranches.length > 0 && ( +
+ + + + + + + + + {remoteBranches.map(name => ( + + + + + ))} + +
Remote Branches 
+ {name} + {name === 'master' && } + + {name !== 'master' && ( + handleRemoteDelete(name)} + > + Delete + + )} + + Fetch + +
+
+ )} + + + ); -}); +}; SyncBranchesModal.displayName = 'SyncBranchesModal'; diff --git a/packages/insomnia/src/ui/components/modals/sync-delete-modal.tsx b/packages/insomnia/src/ui/components/modals/sync-delete-modal.tsx index 0f45ec7e36..51e964efcd 100644 --- a/packages/insomnia/src/ui/components/modals/sync-delete-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/sync-delete-modal.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { OverlayContainer } from 'react-aria'; import { useRouteLoaderData } from 'react-router-dom'; import { strings } from '../../../common/strings'; @@ -16,16 +17,9 @@ type Props = ModalProps & { interface State { error?: string; workspaceName: string; - onHide?: () => void; } -export interface SyncDeleteModalOptions { - onHide?: () => void; -} -export interface SyncDeleteModalHandle { - show: (options: SyncDeleteModalOptions) => void; - hide: () => void; -} -export const SyncDeleteModal = forwardRef(({ vcs }, ref) => { + +export const SyncDeleteModal = ({ vcs, onHide }: Props) => { const modalRef = useRef(null); const [state, setState] = useState({ error: '', @@ -35,17 +29,9 @@ export const SyncDeleteModal = forwardRef(({ vcs } activeWorkspace, } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData; - useImperativeHandle(ref, () => ({ - hide: () => modalRef.current?.hide(), - show: ({ onHide }) => { - setState({ - error: '', - workspaceName: '', - onHide, - }); - modalRef.current?.show({ onHide }); - }, - }), []); + useEffect(() => { + modalRef.current?.show(); + }, []); const onSubmit = async (event: React.SyntheticEvent) => { event.preventDefault(); try { @@ -56,7 +42,7 @@ export const SyncDeleteModal = forwardRef(({ vcs } resourceType: strings.collection.singular.toLowerCase(), }); modalRef.current?.hide(); - state.onHide?.(); + onHide?.(); } catch (err) { setState(state => ({ ...state, @@ -67,29 +53,30 @@ export const SyncDeleteModal = forwardRef(({ vcs } const { error, workspaceName } = state; return ( - - Delete {strings.collection.singular} - - {error &&

{error}

} -

- This will permanently delete the {{activeWorkspace?.name}}{' '} - {strings.collection.singular.toLowerCase()} remotely. -

-

Please type {{activeWorkspace?.name}} to confirm.

-
-
- setState(state => ({ ...state, workspaceName: event.target.value }))} - value={workspaceName} - /> - -
-
-
-
+ + + Delete {strings.collection.singular} + + {error &&

{error}

} +

+ This will permanently delete the {{activeWorkspace?.name}}{' '} + {strings.collection.singular.toLowerCase()} remotely. +

+

Please type {{activeWorkspace?.name}} to confirm.

+
+
+ setState(state => ({ ...state, workspaceName: event.target.value }))} + value={workspaceName} + /> + +
+
+
+
+
); -}); -SyncDeleteModal.displayName = 'SyncDeleteModal'; +}; diff --git a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx index c533987371..ec16ead54a 100644 --- a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames'; -import React, { FC, forwardRef, Fragment, useImperativeHandle, useRef } from 'react'; -import { ListDropTargetDelegate, ListKeyboardDelegate, mergeProps, useDraggableCollection, useDraggableItem, useDropIndicator, useDroppableCollection, useDroppableItem, useFocusRing, useListBox, useOption } from 'react-aria'; +import React, { FC, Fragment, useEffect, useRef } from 'react'; +import { ListDropTargetDelegate, ListKeyboardDelegate, mergeProps, OverlayContainer, useDraggableCollection, useDraggableItem, useDropIndicator, useDroppableCollection, useDroppableItem, useFocusRing, useListBox, useOption } from 'react-aria'; import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom'; import { DraggableCollectionState, DroppableCollectionState, Item, ListState, useDraggableCollectionState, useDroppableCollectionState, useListState } from 'react-stately'; @@ -226,11 +226,7 @@ const ReorderableListBox = props => { ); }; -export interface WorkspaceEnvironmentsEditModalHandle { - show: () => void; - hide: () => void; -} -export const WorkspaceEnvironmentsEditModal = forwardRef((props, ref) => { +export const WorkspaceEnvironmentsEditModal = (props: ModalProps) => { const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>(); const routeData = useRouteLoaderData( ':workspaceId' @@ -244,15 +240,9 @@ export const WorkspaceEnvironmentsEditModal = forwardRef ({ - hide: () => { - modalRef.current?.hide(); - }, - show: async () => { - modalRef.current?.show(); - }, - }), []); + useEffect(() => { + modalRef.current?.show(); + }, []); if (!routeData) { return null; @@ -272,10 +262,10 @@ export const WorkspaceEnvironmentsEditModal = forwardRef) => { @@ -294,11 +284,11 @@ export const WorkspaceEnvironmentsEditModal = forwardRef - Manage Environments - -
-
- -
-
-

Sub Environments

- - - - - } - > - - { - createEnvironmentFetcher.submit({ - isPrivate: false, +
- - {(environment: any) => - - {environment.name} - - } - -
-
-
-

- {baseEnvironment._id === activeEnvironment._id ? ( - ROOT_ENVIRONMENT_NAME - ) : ( - { - if (activeEnvironment._id && name) { - updateEnvironment(activeEnvironment._id, { name }); - } - }} - value={activeEnvironment.name} - /> - )} -

- - {baseEnvironment._id !== activeEnvironment._id ? ( - - updateEnvironment(activeEnvironment._id, { color: event.target.value })} - /> - - - {activeEnvironment.color && ( - - )} - Color - - } - > - - { - if (!activeEnvironment.color) { - // TODO: fix magic-number. Currently this is the `surprise` background color for the default theme, - // but we should be grabbing the actual value from the user's actual theme instead. - updateEnvironment(activeEnvironment._id, { color: '#7d69cb' }); - } - inputRef.current?.click(); - }} - /> - - - - updateEnvironment(activeEnvironment._id, { color: null })} - /> - - - - + } + }} + > + {ROOT_ENVIRONMENT_NAME} + + The variables in this environment are always available, regardless of which + sub-environment is active. Useful for storing default or fallback values. + + +
+
+

Sub Environments

+ + + + + } + > + + { + createEnvironmentFetcher.submit({ + isPrivate: false, + }, + { + encType: 'application/json', + method: 'post', + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/create`, + }); + }} + /> + + + { + createEnvironmentFetcher.submit({ + isPrivate: true, + }, + { + encType: 'application/json', + method: 'post', + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/create`, + }); + }} + /> + + +
+ + {(environment: any) => + + {environment.name} + + } + +
+
+
+

+ {baseEnvironment._id === activeEnvironment._id ? ( + ROOT_ENVIRONMENT_NAME + ) : ( + { + if (activeEnvironment._id && name) { + updateEnvironment(activeEnvironment._id, { name }); + } + }} + value={activeEnvironment.name} + /> + )} +

- {activeEnvironment._id !== baseEnvironment._id && handleDeleteEnvironment(activeEnvironment._id)} - className="btn btn--clicky" - > - - } - - ) : null} + {baseEnvironment._id !== activeEnvironment._id ? ( + + updateEnvironment(activeEnvironment._id, { color: event.target.value })} + /> + + + {activeEnvironment.color && ( + + )} + Color + + } + > + + { + if (!activeEnvironment.color) { + // TODO: fix magic-number. Currently this is the `surprise` background color for the default theme, + // but we should be grabbing the actual value from the user's actual theme instead. + updateEnvironment(activeEnvironment._id, { color: '#7d69cb' }); + } + inputRef.current?.click(); + }} + /> + + + + updateEnvironment(activeEnvironment._id, { color: null })} + /> + + + + + + {activeEnvironment._id !== baseEnvironment._id && handleDeleteEnvironment(activeEnvironment._id)} + className="btn btn--clicky" + > + + } + + ) : null} +
+
+ { + // Only save if it's valid + if (!environmentEditorRef.current || !environmentEditorRef.current?.isValid()) { + return; + } + const data = environmentEditorRef.current?.getValue(); + if (activeEnvironment && data) { + updateEnvironment(activeEnvironment._id, { + data: data.object, + dataPropertyOrder: data.propertyOrder, + }); + } + }} + /> +
-
- { - // Only save if it's valid - if (!environmentEditorRef.current || !environmentEditorRef.current?.isValid()) { - return; - } - const data = environmentEditorRef.current?.getValue(); - if (activeEnvironment && data) { - updateEnvironment(activeEnvironment._id, { - data: data.object, - dataPropertyOrder: data.propertyOrder, - }); - } - }} - /> + + +
+ * Environment data can be used for  + Nunjucks Templating in your requests
-
-
- -
- * Environment data can be used for  - Nunjucks Templating in your requests -
- -
- + + + + ); -}); -WorkspaceEnvironmentsEditModal.displayName = 'WorkspaceEnvironmentsEditModal'; +}; diff --git a/packages/insomnia/src/ui/components/modals/workspace-settings-modal.tsx b/packages/insomnia/src/ui/components/modals/workspace-settings-modal.tsx index 8d2d85fbb9..eb3a5939e2 100644 --- a/packages/insomnia/src/ui/components/modals/workspace-settings-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/workspace-settings-modal.tsx @@ -93,7 +93,7 @@ export const WorkspaceSettingsModal = ({ workspace, workspaceMeta, clientCertifi const activeWorkspaceName = workspace.name; useEffect(() => { modalRef.current?.show(); - }); + }, []); const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>(); const workspaceFetcher = useFetcher(); diff --git a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx index 8c2b6f3940..806c12f11a 100644 --- a/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/grpc-request-pane.tsx @@ -9,12 +9,12 @@ import { generateId } from '../../../common/misc'; import { getRenderContext, getRenderedGrpcRequest, getRenderedGrpcRequestMessage, render, RENDER_PURPOSE_SEND } from '../../../common/render'; import { GrpcMethodType } from '../../../main/ipc/grpc'; import * as models from '../../../models'; -import type { GrpcRequest, GrpcRequestHeader } from '../../../models/grpc-request'; +import type { GrpcRequestHeader } from '../../../models/grpc-request'; import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls'; import { useRequestPatcher } from '../../hooks/use-request'; import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version'; import { GrpcRequestState } from '../../routes/debug'; -import { RequestLoaderData } from '../../routes/request'; +import { GrpcRequestLoaderData } from '../../routes/request'; import { WorkspaceLoaderData } from '../../routes/workspace'; import { PanelContainer, TabItem, Tabs } from '../base/tabs'; import { GrpcSendButton } from '../buttons/grpc-send-button'; @@ -73,7 +73,7 @@ export const GrpcRequestPane: FunctionComponent = ({ setGrpcState, reloadRequests, }) => { - const { activeRequest } = useRouteLoaderData('request/:requestId') as RequestLoaderData; + const { activeRequest } = useRouteLoaderData('request/:requestId') as GrpcRequestLoaderData; const [isProtoModalOpen, setIsProtoModalOpen] = useState(false); const { requestMessages, running, methods } = grpcState; diff --git a/packages/insomnia/src/ui/components/panes/request-pane.tsx b/packages/insomnia/src/ui/components/panes/request-pane.tsx index f4856cfea0..dae8223d72 100644 --- a/packages/insomnia/src/ui/components/panes/request-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/request-pane.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useRef } from 'react'; +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import { useParams, useRouteLoaderData } from 'react-router-dom'; import styled from 'styled-components'; @@ -6,11 +6,9 @@ import { getContentTypeFromHeaders } from '../../../common/constants'; import { database } from '../../../common/database'; import * as models from '../../../models'; import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls'; -import { Request } from '../../../models/request'; -import { RequestMeta } from '../../../models/request-meta'; import type { Settings } from '../../../models/settings'; import { deconstructQueryStringToParams, extractQueryStringFromUrl } from '../../../utils/url/querystring'; -import { useRequestPatcher } from '../../hooks/use-request'; +import { useRequestPatcher, useSettingsPatcher } from '../../hooks/use-request'; import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version'; import { RequestLoaderData } from '../../routes/request'; import { WorkspaceLoaderData } from '../../routes/workspace'; @@ -24,7 +22,6 @@ import { RequestHeadersEditor } from '../editors/request-headers-editor'; import { RequestParametersEditor } from '../editors/request-parameters-editor'; import { ErrorBoundary } from '../error-boundary'; import { MarkdownPreview } from '../markdown-preview'; -import { showModal } from '../modals'; import { RequestSettingsModal } from '../modals/request-settings-modal'; import { RenderedQueryString } from '../rendered-query-string'; import { RequestUrlBar, RequestUrlBarHandle } from '../request-url-bar'; @@ -68,25 +65,11 @@ export const RequestPane: FC = ({ settings, setLoading, }) => { - const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; + const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; const { workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string }; const patchRequest = useRequestPatcher(); - - const handleEditDescription = useCallback((forceEditMode: boolean) => { - showModal(RequestSettingsModal, { request: activeRequest, forceEditMode }); - }, [activeRequest]); - - const handleEditDescriptionAdd = useCallback(() => { - handleEditDescription(true); - }, [handleEditDescription]); - - const handleUpdateSettingsUseBulkHeaderEditor = useCallback(() => { - models.settings.update(settings, { useBulkHeaderEditor: !settings.useBulkHeaderEditor }); - }, [settings]); - - const handleUpdateSettingsUseBulkParametersEditor = useCallback(() => { - models.settings.update(settings, { useBulkParametersEditor: !settings.useBulkParametersEditor }); - }, [settings]); + const patchSettings = useSettingsPatcher(); + const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] = useState(false); const handleImportQueryFromUrl = useCallback(() => { let query; @@ -202,7 +185,7 @@ export const RequestPane: FC = ({ @@ -222,7 +205,7 @@ export const RequestPane: FC = ({ @@ -246,8 +229,7 @@ export const RequestPane: FC = ({ {activeRequest.description ? (
- {/* @ts-expect-error -- TSCONVERSION the click handler expects a boolean prop... */} -
@@ -274,10 +256,7 @@ export const RequestPane: FC = ({

-

@@ -286,6 +265,12 @@ export const RequestPane: FC = ({ + {isRequestSettingsModalOpen && ( + setIsRequestSettingsModalOpen(false)} + /> + )} ); }; diff --git a/packages/insomnia/src/ui/components/panes/response-pane.tsx b/packages/insomnia/src/ui/components/panes/response-pane.tsx index 8386430b92..c37ea36f2c 100644 --- a/packages/insomnia/src/ui/components/panes/response-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/response-pane.tsx @@ -1,19 +1,14 @@ import fs from 'fs'; import { extension as mimeExtension } from 'mime-types'; import React, { FC, useCallback } from 'react'; -import { useSelector } from 'react-redux'; import { useRouteLoaderData } from 'react-router-dom'; import { PREVIEW_MODE_SOURCE } from '../../../common/constants'; import { getSetCookieHeaders } from '../../../common/misc'; import * as models from '../../../models'; -import type { Request } from '../../../models/request'; -import { RequestMeta } from '../../../models/request-meta'; -import type { Response } from '../../../models/response'; import { cancelRequestById } from '../../../network/cancellation'; import { jsonPrettify } from '../../../utils/prettify/json'; import { useRequestMetaPatcher } from '../../hooks/use-request'; -import { selectActiveResponse } from '../../redux/selectors'; import { RequestLoaderData } from '../../routes/request'; import { RootLoaderData } from '../../routes/root'; import { PanelContainer, TabItem, Tabs } from '../base/tabs'; @@ -39,8 +34,7 @@ interface Props { export const ResponsePane: FC = ({ runningRequests, }) => { - const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; - const response = useSelector(selectActiveResponse) as Response | null; + const { activeRequest, activeRequestMeta, activeResponse } = useRouteLoaderData('request/:requestId') as RequestLoaderData; const filterHistory = activeRequestMeta.responseFilterHistory || []; const filter = activeRequestMeta.responseFilter || ''; const patchRequestMeta = useRequestMetaPatcher(); @@ -49,10 +43,10 @@ export const ResponsePane: FC = ({ } = useRouteLoaderData('root') as RootLoaderData; const previewMode = activeRequestMeta.previewMode || PREVIEW_MODE_SOURCE; const handleSetFilter = async (responseFilter: string) => { - if (!response) { + if (!activeResponse) { return; } - const requestId = response.parentId; + const requestId = activeResponse.parentId; await patchRequestMeta(requestId, { responseFilter }); const meta = await models.requestMeta.getByParentId(requestId); if (!meta) { @@ -67,11 +61,11 @@ export const ResponsePane: FC = ({ patchRequestMeta(requestId, { responseFilterHistory }); }; const handleGetResponseBody = useCallback(() => { - if (!response) { + if (!activeResponse) { return null; } - return models.response.getBodyBuffer(response); - }, [response]); + return models.response.getBodyBuffer(activeResponse); + }, [activeResponse]); const handleCopyResponseToClipboard = useCallback(async () => { const bodyBuffer = handleGetResponseBody(); if (bodyBuffer) { @@ -79,12 +73,12 @@ export const ResponsePane: FC = ({ } }, [handleGetResponseBody]); const handleDownloadResponseBody = useCallback(async (prettify: boolean) => { - if (!response || !activeRequest) { + if (!activeResponse || !activeRequest) { console.warn('Nothing to download'); return; } - const { contentType } = response; + const { contentType } = activeResponse; const extension = mimeExtension(contentType) || 'unknown'; const { canceled, filePath: outputPath } = await window.dialog.showSaveDialog({ title: 'Save Response Body', @@ -96,7 +90,7 @@ export const ResponsePane: FC = ({ return; } - const readStream = models.response.getBodyStream(response); + const readStream = models.response.getBodyStream(activeResponse); const dataBuffers: any[] = []; if (readStream && outputPath && typeof readStream !== 'string') { @@ -123,14 +117,14 @@ export const ResponsePane: FC = ({ to.end(); }); } - }, [activeRequest, response]); + }, [activeRequest, activeResponse]); if (!activeRequest) { return ; } // If there is no previous response, show placeholder for loading indicator - if (!response) { + if (!activeResponse) { return ( {runningRequests[activeRequest._id] && = ({ ); } - const timeline = models.response.getTimeline(response); - const cookieHeaders = getSetCookieHeaders(response.headers); + const timeline = models.response.getTimeline(activeResponse); + const cookieHeaders = getSetCookieHeaders(activeResponse.headers); return ( - {!response ? null : ( + {!activeResponse ? null : (
- - - + + +
)} @@ -167,21 +160,21 @@ export const ResponsePane: FC = ({ } > = ({ title={ <> Headers - {response.headers.length > 0 && ( - {response.headers.length} + {activeResponse.headers.length > 0 && ( + {activeResponse.headers.length} )} } > - - + + @@ -213,19 +206,19 @@ export const ResponsePane: FC = ({ } > - + - + diff --git a/packages/insomnia/src/ui/components/request-url-bar.tsx b/packages/insomnia/src/ui/components/request-url-bar.tsx index 2aaae707b4..29996d13f1 100644 --- a/packages/insomnia/src/ui/components/request-url-bar.tsx +++ b/packages/insomnia/src/ui/components/request-url-bar.tsx @@ -4,7 +4,7 @@ import fs from 'fs'; import { extension as mimeExtension } from 'mime-types'; import path from 'path'; import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'; -import { useParams, useRouteLoaderData } from 'react-router-dom'; +import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom'; import { useInterval } from 'react-use'; import styled from 'styled-components'; @@ -12,8 +12,7 @@ import { database } from '../../common/database'; import { getContentDispositionHeader } from '../../common/misc'; import { getRenderContext, render, RENDER_PURPOSE_SEND } from '../../common/render'; import * as models from '../../models'; -import { isEventStreamRequest, isRequest, Request } from '../../models/request'; -import { RequestMeta } from '../../models/request-meta'; +import { isEventStreamRequest, isRequest } from '../../models/request'; import * as network from '../../network/network'; import { convert } from '../../utils/importers/convert'; import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/url/querystring'; @@ -22,7 +21,7 @@ import { useReadyState } from '../hooks/use-ready-state'; import { useRequestPatcher } from '../hooks/use-request'; import { useRequestMetaPatcher } from '../hooks/use-request'; import { useTimeoutWhen } from '../hooks/useTimeoutWhen'; -import { RequestLoaderData } from '../routes/request'; +import { ConnectActionParams, RequestLoaderData } from '../routes/request'; import { RootLoaderData } from '../routes/root'; import { WorkspaceLoaderData } from '../routes/workspace'; import { Dropdown, DropdownButton, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from './base/dropdown'; @@ -69,10 +68,9 @@ export const RequestUrlBar = forwardRef(({ settings, } = useRouteLoaderData('root') as RootLoaderData; const { hotKeyRegistry } = settings; - const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; + const { activeRequest, activeRequestMeta } = useRouteLoaderData('request/:requestId') as RequestLoaderData; const downloadPath = activeRequestMeta.downloadPath; const patchRequestMeta = useRequestMetaPatcher(); - const { requestId } = useParams() as { requestId: string }; const methodDropdownRef = useRef(null); const dropdownRef = useRef(null); const inputRef = useRef(null); @@ -191,12 +189,9 @@ export const RequestUrlBar = forwardRef(({ ), }); } finally { - // Unset active response because we just made a new one - // TODO: remove this with the redux fallback to first element - await patchRequestMeta(activeRequest._id, { activeResponseId: null }); setLoading(false); } - }, [activeEnvironment._id, activeRequest, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion, patchRequestMeta]); + }, [activeEnvironment._id, activeRequest, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion]); const handleSend = useCallback(async () => { if (!activeRequest) { @@ -241,8 +236,17 @@ export const RequestUrlBar = forwardRef(({ await patchRequestMeta(activeRequest._id, { activeResponseId: null }); setLoading(false); }, [activeEnvironment._id, activeRequest, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion, patchRequestMeta]); - - const send = useCallback(() => { + const fetcher = useFetcher(); + const { organizationId, projectId, workspaceId, requestId } = useParams() as { organizationId: string; projectId: string; workspaceId: string; requestId: string }; + const connect = (connectParams: ConnectActionParams) => { + fetcher.submit(JSON.stringify(connectParams), + { + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/${requestId}/connect`, + method: 'post', + encType: 'application/json', + }); + }; + const send = () => { setCurrentTimeout(undefined); if (downloadPath) { sendThenSetFilePath(downloadPath); @@ -262,9 +266,7 @@ export const RequestUrlBar = forwardRef(({ parameters: activeRequest.parameters.filter(p => !p.disabled), workspaceCookieJar, }, renderContext); - window.main.curl.open({ - requestId: activeRequest._id, - workspaceId, + connect({ url: joinUrlAndQueryString(rendered.url, buildQueryStringFromParams(rendered.parameters)), headers: rendered.headers, authentication: rendered.authentication, @@ -275,7 +277,7 @@ export const RequestUrlBar = forwardRef(({ return; } handleSend(); - }, [activeEnvironment._id, activeWorkspace, downloadPath, handleSend, activeRequest, sendThenSetFilePath]); + }; useInterval(send, currentInterval ? currentInterval : null); useTimeoutWhen(send, currentTimeout, !!currentTimeout); diff --git a/packages/insomnia/src/ui/components/settings/boolean-setting.tsx b/packages/insomnia/src/ui/components/settings/boolean-setting.tsx index e1be40b345..41cf3e2ded 100644 --- a/packages/insomnia/src/ui/components/settings/boolean-setting.tsx +++ b/packages/insomnia/src/ui/components/settings/boolean-setting.tsx @@ -1,9 +1,9 @@ -import React, { ChangeEventHandler, FC, ReactNode, useCallback } from 'react'; +import React, { FC, ReactNode } from 'react'; import { useRouteLoaderData } from 'react-router-dom'; import styled from 'styled-components'; import { SettingsOfType } from '../../../common/settings'; -import * as models from '../../../models/index'; +import { useSettingsPatcher } from '../../hooks/use-request'; import { RootLoaderData } from '../../routes/root'; import { HelpTooltip } from '../help-tooltip'; const Descriptions = styled.div({ @@ -36,10 +36,7 @@ export const BooleanSetting: FC<{ if (!settings.hasOwnProperty(setting)) { throw new Error(`Invalid boolean setting name ${setting}`); } - - const onChange = useCallback>(async ({ currentTarget: { checked } }) => { - await models.settings.patch({ [setting]: checked }); - }, [setting]); + const patchSettings = useSettingsPatcher(); return ( <> @@ -50,7 +47,7 @@ export const BooleanSetting: FC<{ patchSettings({ [setting]: event.currentTarget.checked })} type="checkbox" disabled={disabled} /> diff --git a/packages/insomnia/src/ui/components/settings/enum-setting.tsx b/packages/insomnia/src/ui/components/settings/enum-setting.tsx index eb5ba38371..7859fde8f3 100644 --- a/packages/insomnia/src/ui/components/settings/enum-setting.tsx +++ b/packages/insomnia/src/ui/components/settings/enum-setting.tsx @@ -1,8 +1,8 @@ -import React, { ChangeEventHandler, PropsWithChildren, ReactNode, useCallback } from 'react'; +import React, { PropsWithChildren, ReactNode } from 'react'; import { useRouteLoaderData } from 'react-router-dom'; import { SettingsOfType } from '../../../common/settings'; -import * as models from '../../../models/index'; +import { useSettingsPatcher } from '../../hooks/use-request'; import { RootLoaderData } from '../../routes/root'; import { HelpTooltip } from '../help-tooltip'; interface Props { @@ -25,9 +25,7 @@ export const EnumSetting = ({ settings, } = useRouteLoaderData('root') as RootLoaderData; - const onChange = useCallback>(async ({ currentTarget: { value } }) => { - await models.settings.patch({ [setting]: value }); - }, [setting]); + const patchSettings = useSettingsPatcher(); return (
@@ -37,7 +35,8 @@ export const EnumSetting = ({