diff --git a/package-lock.json b/package-lock.json
index bd551272..83f16845 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,6 +34,7 @@
"focus-trap-react": "^10.1.1",
"format-graphql": "^1.4.0",
"framer-motion": "^9.0.4",
+ "papaparse": "^5.4.1",
"parse-color": "^1.0.0",
"react": "^18.2.0",
"react-dnd": "^16.0.1",
@@ -48,6 +49,7 @@
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tauri-apps/cli": "^1.2.2",
"@types/node": "^18.7.10",
+ "@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",
"@types/parse-json": "^4.0.0",
"@types/react": "^18.0.31",
@@ -2118,6 +2120,15 @@
"integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==",
"devOptional": true
},
+ "node_modules/@types/papaparse": {
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.7.tgz",
+ "integrity": "sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/parse-color": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/parse-color/-/parse-color-1.0.1.tgz",
@@ -5829,6 +5840,11 @@
"node": ">=6"
}
},
+ "node_modules/papaparse": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
+ "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -8886,6 +8902,15 @@
"integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==",
"devOptional": true
},
+ "@types/papaparse": {
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.7.tgz",
+ "integrity": "sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/parse-color": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/parse-color/-/parse-color-1.0.1.tgz",
@@ -11587,6 +11612,11 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
+ "papaparse": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
+ "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
+ },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
diff --git a/package.json b/package.json
index 1f1aaca5..6f251f19 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"focus-trap-react": "^10.1.1",
"format-graphql": "^1.4.0",
"framer-motion": "^9.0.4",
+ "papaparse": "^5.4.1",
"parse-color": "^1.0.0",
"react": "^18.2.0",
"react-dnd": "^16.0.1",
@@ -56,6 +57,7 @@
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tauri-apps/cli": "^1.2.2",
"@types/node": "^18.7.10",
+ "@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",
"@types/parse-json": "^4.0.0",
"@types/react": "^18.0.31",
diff --git a/src-web/components/ResponsePane.tsx b/src-web/components/ResponsePane.tsx
index d45f5ad5..1c743fa9 100644
--- a/src-web/components/ResponsePane.tsx
+++ b/src-web/components/ResponsePane.tsx
@@ -25,6 +25,7 @@ import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { ResponseHeaders } from './ResponseHeaders';
+import { CsvViewer } from './responseViewers/CsvViewer';
import { ImageViewer } from './responseViewers/ImageViewer';
import { TextViewer } from './responseViewers/TextViewer';
import { WebPageViewer } from './responseViewers/WebPageViewer';
@@ -184,6 +185,8 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
) : contentType?.startsWith('image') ? (
+ ) : contentType?.match(/csv|tab-separated/) ? (
+
) : (
)}
diff --git a/src-web/components/responseViewers/CsvViewer.tsx b/src-web/components/responseViewers/CsvViewer.tsx
new file mode 100644
index 00000000..df84f320
--- /dev/null
+++ b/src-web/components/responseViewers/CsvViewer.tsx
@@ -0,0 +1,39 @@
+import classnames from 'classnames';
+import Papa from 'papaparse';
+import { useMemo } from 'react';
+import { useResponseBodyText } from '../../hooks/useResponseBodyText';
+import type { HttpResponse } from '../../lib/models';
+
+interface Props {
+ response: HttpResponse;
+ className?: string;
+}
+
+export function CsvViewer({ response, className }: Props) {
+ const body = useResponseBodyText(response);
+
+ const parsed = useMemo(() => {
+ if (body === null) return null;
+ return Papa.parse(body);
+ }, [body]);
+
+ if (parsed === null) return null;
+
+ return (
+
+
+
+ {parsed.data.map((row, i) => (
+ 0 && 'border-b')}>
+ {row.map((col, j) => (
+ |
+ {col}
+ |
+ ))}
+
+ ))}
+
+
+
+ );
+}