mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-06-26 03:46:28 +02:00
2024.5.0 (#39)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import React from 'react';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function AudioViewer({ response }: Props) {
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
}
|
||||
|
||||
const src = convertFileSrc(response.bodyPath);
|
||||
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
return <audio className="w-full" controls src={src}></audio>;
|
||||
}
|
||||
@@ -20,13 +20,9 @@ export function ImageViewer({ response, className }: Props) {
|
||||
if (!show) {
|
||||
return (
|
||||
<>
|
||||
<div className="text-sm italic text-gray-500">
|
||||
<div className="italic text-fg-subtler">
|
||||
Response body is too large to preview.{' '}
|
||||
<button
|
||||
className="cursor-pointer underline hover:text-gray-800"
|
||||
color="gray"
|
||||
onClick={() => setShow(true)}
|
||||
>
|
||||
<button className="cursor-pointer underline hover:text-fg" onClick={() => setShow(true)}>
|
||||
Show anyway
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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';
|
||||
@@ -18,27 +18,45 @@ const extraExtensions = [hyperlink];
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
pretty: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TextViewer({ response, pretty }: Props) {
|
||||
const [isSearching, toggleIsSearching] = useToggle();
|
||||
const [filterText, setDebouncedFilterText, setFilterText] = useDebouncedState<string>('', 400);
|
||||
const useFilterText = createGlobalState<Record<string, string | null>>({});
|
||||
|
||||
export function TextViewer({ response, pretty, className }: Props) {
|
||||
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');
|
||||
@@ -53,16 +71,17 @@ export function TextViewer({ response, pretty }: 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>,
|
||||
);
|
||||
@@ -74,18 +93,21 @@ export function TextViewer({ response, pretty }: 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
|
||||
readOnly
|
||||
className="bg-gray-50 dark:!bg-gray-100"
|
||||
className={className}
|
||||
forceUpdateKey={body}
|
||||
defaultValue={body}
|
||||
contentType={contentType}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import React from 'react';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function VideoViewer({ response }: Props) {
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
}
|
||||
|
||||
const src = convertFileSrc(response.bodyPath);
|
||||
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
return <video className="w-full" controls src={src}></video>;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export function WebPageViewer({ response }: Props) {
|
||||
title="Response preview"
|
||||
srcDoc={contentForIframe}
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
className="h-full w-full rounded border border-highlightSecondary"
|
||||
className="h-full w-full rounded border border-background-highlight-secondary"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user