import type { HttpRequest, HttpResponse } from '@yaakapp-internal/models'; import classNames from 'classnames'; import type { CSSProperties, ReactNode } from 'react'; import React, { memo, useCallback, useMemo } from 'react'; import { useLocalStorage } from 'react-use'; import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { useResponseViewMode } from '../hooks/useResponseViewMode'; import { isResponseLoading } from '../lib/model_util'; import { Banner } from './core/Banner'; import { CountBadge } from './core/CountBadge'; import { DurationTag } from './core/DurationTag'; import { HotKeyList } from './core/HotKeyList'; import { Icon } from './core/Icon'; import { SizeTag } from './core/SizeTag'; import { HStack } from './core/Stacks'; import { StatusTag } from './core/StatusTag'; import type { TabItem } from './core/Tabs/Tabs'; import { TabContent, Tabs } from './core/Tabs/Tabs'; import { EmptyStateText } from './EmptyStateText'; import { RecentResponsesDropdown } from './RecentResponsesDropdown'; import { ResponseHeaders } from './ResponseHeaders'; import { ResponseInfo } from './ResponseInfo'; import { AudioViewer } from './responseViewers/AudioViewer'; import { CsvViewer } from './responseViewers/CsvViewer'; import { EventStreamViewer } from './responseViewers/EventStreamViewer'; import { HTMLOrTextViewer } from './responseViewers/HTMLOrTextViewer'; import { ImageViewer } from './responseViewers/ImageViewer'; import { PdfViewer } from './responseViewers/PdfViewer'; import { VideoViewer } from './responseViewers/VideoViewer'; interface Props { style?: CSSProperties; className?: string; activeRequest: HttpRequest; } const TAB_BODY = 'body'; const TAB_HEADERS = 'headers'; const TAB_INFO = 'info'; const DEFAULT_TAB = TAB_BODY; export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) { const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequest); const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId); const [activeTabs, setActiveTabs] = useLocalStorage>( 'responsePaneActiveTabs', {}, ); const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null); const activeTab = activeTabs?.[activeRequest.id] ?? DEFAULT_TAB; const setActiveTab = useCallback( (tab: string) => { setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); }, [activeRequest.id, setActiveTabs], ); const tabs = useMemo( () => [ { value: TAB_BODY, label: 'Preview Mode', options: { value: viewMode, onChange: setViewMode, items: [ { label: 'Pretty', value: 'pretty' }, ...(contentType?.startsWith('image') ? [] : [{ label: 'Raw', value: 'raw' }]), ], }, }, { value: TAB_HEADERS, label: (
Headers h.name && h.value).length ?? 0} />
), }, { value: TAB_INFO, label: 'Info', }, ], [activeResponse?.headers, contentType, setViewMode, viewMode], ); const isLoading = isResponseLoading(activeResponse); return (
{activeResponse == null ? ( ) : (
{activeResponse && ( {isLoading && }
)}
{activeResponse?.error ? ( {activeResponse.error} ) : ( {!activeResponse.contentLength ? (
Empty Body
) : contentType?.match(/^text\/event-stream$/i) && viewMode === 'pretty' ? ( ) : contentType?.match(/^image/i) ? ( ) : contentType?.match(/^audio/i) ? ( ) : contentType?.match(/^video/i) ? ( ) : contentType?.match(/pdf/i) ? ( ) : contentType?.match(/csv|tab-separated/i) ? ( ) : ( // ) : viewMode === 'pretty' && contentType?.includes('json') ? ( // )}
)}
)}
); }); function EnsureCompleteResponse({ response, render, }: { response: HttpResponse; render: (v: { bodyPath: string }) => ReactNode; }) { if (response.bodyPath === null) { return
Empty response body
; } // Wait until the response has been fully-downloaded if (response.state !== 'closed') { return ( ); } return render({ bodyPath: response.bodyPath }); }