From bc9a623742524d8e7e16ae3d463d1cf79ec4b386 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 22 Apr 2023 21:53:04 +0800 Subject: [PATCH] Very basic CSV viewer --- package-lock.json | 30 ++++++++++++++ package.json | 2 + src-web/components/ResponsePane.tsx | 3 ++ .../components/responseViewers/CsvViewer.tsx | 39 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 src-web/components/responseViewers/CsvViewer.tsx 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} +
+
+ ); +}