mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-18 23:43:55 +01:00
Preserve JSON/XPath filter (Closes #22)
This commit is contained in:
@@ -195,7 +195,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
placeholderCompartment.current.of(
|
||||
placeholderExt(placeholderElFromText(placeholder ?? '')),
|
||||
),
|
||||
wrapLinesCompartment.current.of([]),
|
||||
wrapLinesCompartment.current.of(wrapLines ? [EditorView.lineWrapping] : []),
|
||||
...getExtensions({
|
||||
container,
|
||||
readOnly,
|
||||
@@ -357,8 +357,7 @@ function getExtensions({
|
||||
blur: () => {
|
||||
onBlur.current?.();
|
||||
},
|
||||
keydown: (e, cm) => {
|
||||
console.log('KEY DOWN', e, cm);
|
||||
keydown: (e) => {
|
||||
onKeyDown.current?.(e);
|
||||
},
|
||||
paste: (e) => {
|
||||
|
||||
@@ -9,7 +9,7 @@ export function FormattedError({ children }: Props) {
|
||||
return (
|
||||
<pre
|
||||
className={classNames(
|
||||
'w-full select-auto cursor-text bg-gray-100 p-3 rounded',
|
||||
'w-full select-auto cursor-text bg-background-highlight-secondary p-3 rounded',
|
||||
'whitespace-pre-wrap border border-fg-danger border-dashed overflow-x-auto',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import { useContentTypeFromHeaders } from '../../hooks/useContentTypeFromHeaders';
|
||||
import { useDebouncedState } from '../../hooks/useDebouncedState';
|
||||
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
|
||||
import { useFilterResponse } from '../../hooks/useFilterResponse';
|
||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||
import { useToggle } from '../../hooks/useToggle';
|
||||
import { tryFormatJson, tryFormatXml } from '../../lib/formatters';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
import { Editor } from '../core/Editor';
|
||||
@@ -21,25 +21,42 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const useFilterText = createGlobalState<Record<string, string | null>>({});
|
||||
|
||||
export function TextViewer({ response, pretty, className }: Props) {
|
||||
const [isSearching, toggleIsSearching] = useToggle();
|
||||
const [filterText, setDebouncedFilterText, setFilterText] = useDebouncedState<string>('', 400);
|
||||
const [filterTextMap, setFilterTextMap] = useFilterText();
|
||||
const filterText = filterTextMap[response.id] ?? null;
|
||||
const debouncedFilterText = useDebouncedValue(filterText, 300);
|
||||
const setFilterText = useCallback(
|
||||
(v: string | null) => {
|
||||
setFilterTextMap((m) => ({ ...m, [response.id]: v }));
|
||||
},
|
||||
[setFilterTextMap, response],
|
||||
);
|
||||
|
||||
const contentType = useContentTypeFromHeaders(response.headers);
|
||||
const rawBody = useResponseBodyText(response) ?? '';
|
||||
const isSearching = filterText != null;
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
? tryFormatJson(rawBody)
|
||||
: pretty && contentType?.includes('xml')
|
||||
? tryFormatXml(rawBody)
|
||||
: rawBody;
|
||||
const filteredResponse = useFilterResponse({ filter: filterText, responseId: response.id });
|
||||
|
||||
const body = filteredResponse ?? formattedBody;
|
||||
const clearSearch = useCallback(() => {
|
||||
toggleIsSearching();
|
||||
setFilterText('');
|
||||
}, [setFilterText, toggleIsSearching]);
|
||||
const filteredResponse = useFilterResponse({
|
||||
filter: debouncedFilterText ?? '',
|
||||
responseId: response.id,
|
||||
});
|
||||
|
||||
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (isSearching) {
|
||||
setFilterText(null);
|
||||
} else {
|
||||
setFilterText('');
|
||||
}
|
||||
}, [isSearching, setFilterText]);
|
||||
|
||||
const isJson = contentType?.includes('json');
|
||||
const isXml = contentType?.includes('xml') || contentType?.includes('html');
|
||||
@@ -54,16 +71,17 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
result.push(
|
||||
<div key="input" className="w-full !opacity-100">
|
||||
<Input
|
||||
key={response.id}
|
||||
hideLabel
|
||||
autoFocus
|
||||
containerClassName="bg-gray-100 dark:bg-gray-50"
|
||||
containerClassName="bg-background"
|
||||
size="sm"
|
||||
placeholder={isJson ? 'JSONPath expression' : 'XPath expression'}
|
||||
label="Filter expression"
|
||||
name="filter"
|
||||
defaultValue={filterText}
|
||||
onKeyDown={(e) => e.key === 'Escape' && clearSearch()}
|
||||
onChange={setDebouncedFilterText}
|
||||
onKeyDown={(e) => e.key === 'Escape' && toggleSearch()}
|
||||
onChange={setFilterText}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
@@ -75,13 +93,16 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
size="sm"
|
||||
icon={isSearching ? 'x' : 'filter'}
|
||||
title={isSearching ? 'Close filter' : 'Filter response'}
|
||||
onClick={clearSearch}
|
||||
className={classNames(isSearching && '!opacity-100')}
|
||||
onClick={toggleSearch}
|
||||
className={classNames(
|
||||
'bg-background border !border-background-highlight',
|
||||
isSearching && '!opacity-100',
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
|
||||
return result;
|
||||
}, [canFilter, clearSearch, filterText, isJson, isSearching, setDebouncedFilterText]);
|
||||
}, [canFilter, filterText, isJson, isSearching, setFilterText, toggleSearch]);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
|
||||
Reference in New Issue
Block a user