mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-20 08:33:52 +01:00
Response viewer for PDF (#48)
This PR adds a response viewer for PDF files using `react-pdf`
This commit is contained in:
@@ -23,6 +23,7 @@ import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { AudioViewer } from './responseViewers/AudioViewer';
|
||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { PdfViewer } from './responseViewers/PdfViewer';
|
||||
import { TextViewer } from './responseViewers/TextViewer';
|
||||
import { VideoViewer } from './responseViewers/VideoViewer';
|
||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||
@@ -164,6 +165,8 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
<WebPageViewer response={activeResponse} />
|
||||
) : contentType?.match(/csv|tab-separated/) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : contentType?.match(/pdf/) ? (
|
||||
<PdfViewer response={activeResponse} />
|
||||
) : (
|
||||
<TextViewer
|
||||
className="-mr-2" // Pull to the right
|
||||
|
||||
3
src-web/components/responseViewers/PdfViewer.css
Normal file
3
src-web/components/responseViewers/PdfViewer.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.react-pdf__Document * {
|
||||
@apply select-text;
|
||||
}
|
||||
61
src-web/components/responseViewers/PdfViewer.tsx
Normal file
61
src-web/components/responseViewers/PdfViewer.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import 'react-pdf/dist/Page/TextLayer.css';
|
||||
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import { useDebouncedState } from '../../hooks/useDebouncedState';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
import './PdfViewer.css';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
const options = {
|
||||
cMapUrl: '/cmaps/',
|
||||
standardFontDataUrl: '/standard_fonts/',
|
||||
};
|
||||
|
||||
export function PdfViewer({ response }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerWidth, setContainerWidth] = useDebouncedState<number>(0, 100);
|
||||
const [numPages, setNumPages] = useState<number>();
|
||||
|
||||
useResizeObserver(containerRef.current ?? null, (v) => {
|
||||
setContainerWidth(v.contentRect.width);
|
||||
});
|
||||
|
||||
const onDocumentLoadSuccess = ({ numPages: nextNumPages }: PDFDocumentProxy): void => {
|
||||
setNumPages(nextNumPages);
|
||||
};
|
||||
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
}
|
||||
|
||||
const src = convertFileSrc(response.bodyPath);
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full overflow-y-auto">
|
||||
<Document
|
||||
file={src}
|
||||
options={options}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
externalLinkTarget="_blank"
|
||||
externalLinkRel="noopener noreferrer"
|
||||
>
|
||||
{Array.from(new Array(numPages), (_, index) => (
|
||||
<Page
|
||||
className="mb-6 select-all"
|
||||
renderTextLayer
|
||||
renderAnnotationLayer
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={containerWidth}
|
||||
/>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -35,21 +35,14 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
);
|
||||
|
||||
const contentType = useContentTypeFromHeaders(response.headers);
|
||||
const rawBody = useResponseBodyText(response) ?? '';
|
||||
const rawBody = useResponseBodyText(response) ?? null;
|
||||
const isSearching = filterText != null;
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
? tryFormatJson(rawBody)
|
||||
: pretty && contentType?.includes('xml')
|
||||
? tryFormatXml(rawBody)
|
||||
: rawBody;
|
||||
|
||||
const filteredResponse = useFilterResponse({
|
||||
filter: debouncedFilterText ?? '',
|
||||
responseId: response.id,
|
||||
});
|
||||
|
||||
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
|
||||
const toggleSearch = useCallback(() => {
|
||||
if (isSearching) {
|
||||
setFilterText(null);
|
||||
@@ -104,6 +97,18 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
return result;
|
||||
}, [canFilter, filterText, isJson, isSearching, response.id, setFilterText, toggleSearch]);
|
||||
|
||||
if (rawBody == null) {
|
||||
return 'bad';
|
||||
}
|
||||
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
? tryFormatJson(rawBody)
|
||||
: pretty && contentType?.includes('xml')
|
||||
? tryFormatXml(rawBody)
|
||||
: rawBody;
|
||||
const body = isSearching && filterText?.length > 0 ? filteredResponse : formattedBody;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
readOnly
|
||||
|
||||
Reference in New Issue
Block a user