import classNames from 'classnames'; import type { ReactNode } from 'react'; import { useCallback, useMemo } from 'react'; import { createGlobalState } from 'react-use'; import { useCopy } from '../../hooks/useCopy'; import { useDebouncedValue } from '../../hooks/useDebouncedValue'; import { useFilterResponse } from '../../hooks/useFilterResponse'; import { useToggle } from '../../hooks/useToggle'; import { tryFormatJson, tryFormatXml } from '../../lib/formatters'; import { CopyButton } from '../CopyButton'; import { Banner } from '../core/Banner'; import { Button } from '../core/Button'; import type { EditorProps } from '../core/Editor'; import { Editor } from '../core/Editor'; import { hyperlink } from '../core/Editor/hyperlink/extension'; import { IconButton } from '../core/IconButton'; import { InlineCode } from '../core/InlineCode'; import { Input } from '../core/Input'; import { SizeTag } from '../core/SizeTag'; import { HStack } from '../core/Stacks'; const extraExtensions = [hyperlink]; const LARGE_RESPONSE_BYTES = 2 * 1000 * 1000; interface Props { pretty: boolean; className?: string; text: string; language: EditorProps['language']; responseId: string; onSaveResponse: () => void; } const useFilterText = createGlobalState>({}); export function TextViewer({ language, text, responseId, pretty, className, onSaveResponse, }: Props) { const [filterTextMap, setFilterTextMap] = useFilterText(); const [showLargeResponse, toggleShowLargeResponse] = useToggle(); const filterText = filterTextMap[responseId] ?? null; const copy = useCopy(); const debouncedFilterText = useDebouncedValue(filterText, 200); const setFilterText = useCallback( (v: string | null) => { setFilterTextMap((m) => ({ ...m, [responseId]: v })); }, [setFilterTextMap, responseId], ); const isSearching = filterText != null; const filteredResponse = useFilterResponse({ filter: debouncedFilterText ?? '', responseId }); const toggleSearch = useCallback(() => { if (isSearching) { setFilterText(null); } else { setFilterText(''); } }, [isSearching, setFilterText]); const canFilter = language === 'json' || language === 'xml' || language === 'html'; const actions = useMemo(() => { const nodes: ReactNode[] = []; if (!canFilter) return nodes; if (isSearching) { nodes.push(
e.key === 'Escape' && toggleSearch()} onChange={setFilterText} />
, ); } nodes.push( , ); return nodes; }, [ canFilter, filterText, filteredResponse.error, isSearching, language, responseId, setFilterText, toggleSearch, ]); if (!showLargeResponse && text.length > LARGE_RESPONSE_BYTES) { return (

Showing responses over{' '} {' '} may impact performance

copy(text)} text={text} />
); } const formattedBody = pretty && language === 'json' ? tryFormatJson(text) : pretty && (language === 'xml' || language === 'html') ? tryFormatXml(text) : text; let body; if (isSearching && filterText?.length > 0) { if (filteredResponse.error) { body = ''; } else { body = filteredResponse.data != null ? filteredResponse.data : ''; } } else { body = formattedBody; } return ( ); }