import classnames from 'classnames'; import type { CSSProperties } from 'react'; import { memo, useEffect, useMemo, useState } from 'react'; import { createGlobalState } from 'react-use'; import { useActiveRequestId } from '../hooks/useActiveRequestId'; import { useDeleteResponse } from '../hooks/useDeleteResponse'; import { useDeleteResponses } from '../hooks/useDeleteResponses'; import { useResponses } from '../hooks/useResponses'; import { useResponseViewMode } from '../hooks/useResponseViewMode'; import { tryFormatJson } from '../lib/formatters'; import type { HttpResponse } from '../lib/models'; import { isResponseLoading } from '../lib/models'; import { pluralize } from '../lib/pluralize'; import { Banner } from './core/Banner'; import { CountBadge } from './core/CountBadge'; import { Dropdown } from './core/Dropdown'; import { Editor } from './core/Editor'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; 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 { Webview } from './core/Webview'; import { EmptyStateText } from './EmptyStateText'; import { ImageView } from './ImageView'; import { ResponseHeaders } from './ResponseHeaders'; interface Props { style?: CSSProperties; className?: string; } const useActiveTab = createGlobalState('body'); export const ResponsePane = memo(function ResponsePane({ style, className }: Props) { const [pinnedResponseId, setPinnedResponseId] = useState(null); const activeRequestId = useActiveRequestId(); const responses = useResponses(activeRequestId); const activeResponse: HttpResponse | null = pinnedResponseId ? responses.find((r) => r.id === pinnedResponseId) ?? null : responses[responses.length - 1] ?? null; const [viewMode, toggleViewMode] = useResponseViewMode(activeResponse?.requestId); const deleteResponse = useDeleteResponse(activeResponse?.id ?? null); const deleteAllResponses = useDeleteResponses(activeResponse?.requestId); const [activeTab, setActiveTab] = useActiveTab(); // Unset pinned response when a new one comes in useEffect(() => setPinnedResponseId(null), [responses.length]); const contentType = useMemo( () => activeResponse?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? 'text/plain', [activeResponse], ); const tabs: TabItem[] = useMemo( () => [ { value: 'body', label: 'Preview', options: { value: viewMode, onChange: toggleViewMode, items: [ { label: 'Pretty', value: 'pretty' }, { label: 'Raw', value: 'raw' }, ], }, }, { label: (
Headers h.name && h.value).length ?? 0} />
), value: 'headers', }, ], [activeResponse?.headers, toggleViewMode, viewMode], ); // Don't render until we know the view mode if (viewMode === undefined) return null; return (
{activeResponse?.error && {activeResponse.error}} {activeResponse && !activeResponse.error && !isResponseLoading(activeResponse) && ( <> {activeResponse && (
{activeResponse.elapsed > 0 && <> • {activeResponse.elapsed}ms} {activeResponse.body.length > 0 && ( <> • {(activeResponse.body.length / 1000).toFixed(1)} KB )}
({ label: r.status + ' - ' + r.elapsed + ' ms', leftSlot: activeResponse?.id === r.id ? : <>, onSelect: () => setPinnedResponseId(r.id), })), ]} >
)}
{ {!activeResponse.body ? ( No Response ) : viewMode === 'pretty' && contentType.includes('html') ? ( ) : viewMode === 'pretty' && contentType.includes('json') ? ( ) : contentType.startsWith('image') ? ( ) : activeResponse?.body ? ( ) : null} } )}
); });