From 7c48533571e9f9d3731a59433a56cc8d6e008123 Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Sat, 12 Feb 2022 10:59:19 +0100 Subject: Added search and filter options --- src/components/App/App.module.scss | 9 - src/components/App/App.tsx | 54 +----- src/components/Content/Content.tsx | 1 - src/components/Details/Details.module.scss | 18 -- src/components/Details/Details.tsx | 162 ---------------- src/components/Icons/Filter.tsx | 10 + .../RequestDetails/RequestDetails.module.scss | 26 +++ src/components/RequestDetails/RequestDetails.tsx | 166 +++++++++++++++++ src/components/RequestList/RequestList.module.scss | 20 ++ src/components/RequestList/RequestList.tsx | 203 +++++++++++++++++++++ 10 files changed, 431 insertions(+), 238 deletions(-) delete mode 100644 src/components/Details/Details.module.scss delete mode 100644 src/components/Details/Details.tsx create mode 100644 src/components/Icons/Filter.tsx create mode 100644 src/components/RequestDetails/RequestDetails.module.scss create mode 100644 src/components/RequestDetails/RequestDetails.tsx create mode 100644 src/components/RequestList/RequestList.module.scss create mode 100644 src/components/RequestList/RequestList.tsx (limited to 'src/components') diff --git a/src/components/App/App.module.scss b/src/components/App/App.module.scss index 088a4f6..61e399e 100644 --- a/src/components/App/App.module.scss +++ b/src/components/App/App.module.scss @@ -16,7 +16,6 @@ .sidebar { width: calc((6 / 16) * 100%); height: 100%; - overflow-y: auto; } .details { @@ -24,11 +23,3 @@ overflow: hidden; height: 100%; } - -.noRequest, .noRequestSelected { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 55ab5b4..e08c84f 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -6,8 +6,7 @@ import useRequests, { } from "../../hooks/useRequests"; import styles from './App.module.scss'; -import Details from "../Details/Details"; -import RequestSummary from "../RequestSummary/RequestSummary"; +import RequestDetails from "../RequestDetails/RequestDetails"; import {getHost} from "../../utils"; import {Container, ListGroup, Nav, Navbar, NavDropdown} from "react-bootstrap"; import classNames from "classnames"; @@ -16,6 +15,7 @@ import {Sun} from "../Icons/Sun"; import {Moon} from "../Icons/Moon"; import Trash from "../Icons/Trash"; import {DarkModeContext} from "../../contexts/DarkMode"; +import RequestList from "../RequestList/RequestList"; interface Config { url: string @@ -143,53 +143,11 @@ export default function App() {
- - { - calls.length > 0 - ? ( - calls.slice(0).reverse().map((requestResponse, index) => { - const selected = selectedRequestIndex === calls.length - index - 1; - return ( - setSelectedRequestIndex(calls.length - index - 1)} - key={`request-${index}`} - className={classNames({ - 'bg-primary': selected, - 'text-light': selected, - 'border-bottom': true - })} - > - - - ) - }) - ) - : ( -
-

No requests

-
- ) - } -
+
+ +
- { - selectedRequest !== null - ? ( -
- ) : ( -
-

Select a request to inspect it

-
- ) - } +
diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index 3d747b2..c7a5f58 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -7,7 +7,6 @@ import {Button, Col, Container, Row} from "react-bootstrap"; import {DarkModeContext} from "../../contexts/DarkMode"; function getHeader(headers: Headers, key: string, unit?: string): string | null { - console.log(headers, key) try { const [_, value] = headers.find(([headerKey]) => headerKey.toLowerCase() === key.toLowerCase()) return unit !== undefined diff --git a/src/components/Details/Details.module.scss b/src/components/Details/Details.module.scss deleted file mode 100644 index 146b5d8..0000000 --- a/src/components/Details/Details.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.details { - height: 100%; - display: flex; - flex-flow: column nowrap; - align-items: stretch; - overflow: hidden; -} - -.header { - flex-shrink: 0; - flex-grow: 0; -} - -.content { - flex-grow: 1; - flex-shrink: 1; - overflow-y: auto; -} diff --git a/src/components/Details/Details.tsx b/src/components/Details/Details.tsx deleted file mode 100644 index 96f0790..0000000 --- a/src/components/Details/Details.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import * as React from "react"; -import {useCallback, useMemo, useState} from "react"; -import {RequestResponse, Headers} from "~hooks/useRequests"; -import styles from "./Details.module.scss"; -import RequestSummary from "../RequestSummary/RequestSummary"; -import Content from "../Content/Content"; -import {getHost} from "../../utils"; -import {Button, Card, Col, Container, Nav, Row, Table} from "react-bootstrap"; - - -interface TimingProps { - timing: number; -} - - -function Timing({ timing }: TimingProps) { - const value = useMemo(() => (Math.round(timing * 1000) / 1000), [timing]); - const showSeconds = useMemo(() => value > 1, [value]); - - return !Number.isNaN(value) - ? ( - <> - {`${showSeconds ? value : value * 1000}${ showSeconds ? 's' : 'ms' }`} - - ) - : null; -} - -interface HeadersProps { - title: string - headers: Headers -} - -function Headers({ title, headers }: HeadersProps) { - return ( - - - - - - - - - { - headers.map(([key, value]) => ( - - - - - )) - } - -
{ title }
{key}{value}
-
- ) -} - -interface DetailsProps { - requestResponse: RequestResponse -} - -type Tab = 'headers' | 'request' | 'response' - -export default function Details({ requestResponse }: DetailsProps) { - const [tab, selectTab] = useState('headers'); - const [raw, setRaw] = useState(false); - - const resend = useCallback(async () => fetch( - `http://${getHost()}/resend/`, - { - method: 'POST', - body: JSON.stringify({ - ...requestResponse.request, - id: undefined - }) - } - ), [requestResponse]); - - return ( -
-
- - - - - - - - - - - - - - - - -
-
- { - tab === 'headers' && ( - <> - - { - requestResponse.response && ( - - ) - } - - ) - } - { - tab === 'request' && ( - - ) - } - { - tab === 'response' && requestResponse.response !== undefined && ( - - ) - } -
-
- ) -} diff --git a/src/components/Icons/Filter.tsx b/src/components/Icons/Filter.tsx new file mode 100644 index 0000000..a14183d --- /dev/null +++ b/src/components/Icons/Filter.tsx @@ -0,0 +1,10 @@ +import * as React from "react"; + +export function Filter() { + return + + ; +} diff --git a/src/components/RequestDetails/RequestDetails.module.scss b/src/components/RequestDetails/RequestDetails.module.scss new file mode 100644 index 0000000..8a872f7 --- /dev/null +++ b/src/components/RequestDetails/RequestDetails.module.scss @@ -0,0 +1,26 @@ +.details { + height: 100%; + display: flex; + flex-flow: column nowrap; + align-items: stretch; + overflow: hidden; +} + +.header { + flex-shrink: 0; + flex-grow: 0; +} + +.content { + flex-grow: 1; + flex-shrink: 1; + overflow-y: auto; +} + +.noRequestSelected { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/RequestDetails/RequestDetails.tsx b/src/components/RequestDetails/RequestDetails.tsx new file mode 100644 index 0000000..34e572c --- /dev/null +++ b/src/components/RequestDetails/RequestDetails.tsx @@ -0,0 +1,166 @@ +import * as React from "react"; +import {useCallback, useMemo, useState} from "react"; +import {RequestResponse, Headers} from "../../hooks/useRequests"; +import styles from "./RequestDetails.module.scss"; +import RequestSummary from "../RequestSummary/RequestSummary"; +import Content from "../Content/Content"; +import {getHost} from "../../utils"; +import {Button, Card, Col, Container, Nav, Row, Table} from "react-bootstrap"; + + +interface TimingProps { + timing: number; +} + + +function Timing({ timing }: TimingProps) { + const value = useMemo(() => (Math.round(timing * 1000) / 1000), [timing]); + const showSeconds = useMemo(() => value > 1, [value]); + + return !Number.isNaN(value) + ? ( + <> + {`${showSeconds ? value : value * 1000}${ showSeconds ? 's' : 'ms' }`} + + ) + : null; +} + +interface HeadersProps { + title: string + headers: Headers +} + +function Headers({ title, headers }: HeadersProps) { + return ( + + + + + + + + + { + headers.map(([key, value]) => ( + + + + + )) + } + +
{ title }
{key}{value}
+
+ ) +} + +interface DetailsProps { + requestResponse: RequestResponse | null +} + +type Tab = 'headers' | 'request' | 'response' + +export default function RequestDetails({ requestResponse }: DetailsProps) { + const [tab, selectTab] = useState('headers'); + const [raw, setRaw] = useState(false); + + const resend = useCallback(async () => requestResponse !== null && fetch( + `http://${getHost()}/resend/`, + { + method: 'POST', + body: JSON.stringify({ + ...requestResponse.request, + id: undefined + }) + } + ), [requestResponse]); + + return requestResponse !== null ? ( +
+
+ + + + + + + + + + + + + + + + +
+
+ { + tab === 'headers' && ( + <> + + { + requestResponse.response && ( + + ) + } + + ) + } + { + tab === 'request' && ( + + ) + } + { + tab === 'response' && requestResponse.response !== undefined && ( + + ) + } +
+
+ ) : ( +
+

Select a request to inspect it

+
+ ) +} diff --git a/src/components/RequestList/RequestList.module.scss b/src/components/RequestList/RequestList.module.scss new file mode 100644 index 0000000..47f36c8 --- /dev/null +++ b/src/components/RequestList/RequestList.module.scss @@ -0,0 +1,20 @@ +.noRequest { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.list { + //height: 100%; + overflow-y: auto; +} + +.listContainer { + height: 100%; + overflow-y: hidden; + display: flex; + flex-flow: column nowrap; + +} diff --git a/src/components/RequestList/RequestList.tsx b/src/components/RequestList/RequestList.tsx new file mode 100644 index 0000000..95c5b75 --- /dev/null +++ b/src/components/RequestList/RequestList.tsx @@ -0,0 +1,203 @@ +import { + Button, + Dropdown, + DropdownButton, + Form, + InputGroup, + ListGroup, + Offcanvas, +} from "react-bootstrap"; +import classNames from "classnames"; +import styles from "./RequestList.module.scss"; +import RequestSummary from "../RequestSummary/RequestSummary"; +import * as React from "react"; +import {useCallback, useContext, useMemo, useState} from "react"; +import {Method, RequestResponse} from "../../hooks/useRequests"; +import {DarkModeContext} from "../../contexts/DarkMode"; +import {Filter} from "../Icons/Filter"; + +interface ListProps { + requests: RequestResponse[], + selectedRequestIndex: number | null + setSelectedRequestIndex: (index: number) => void +} + +type EnabledMethods = { + [method in Method]: boolean +} + + +export default function RequestList({ requests, selectedRequestIndex, setSelectedRequestIndex }: ListProps) { + const {darkMode} = useContext(DarkModeContext) + const [showFilterOptions, setShowFilterOptions] = useState(false); + const [search, setSearch] = useState(''); + const [enableRegex, setEnableRegex] = useState(false); + + const [methods, setMethods] = useState({ + GET: true, + POST: true, + PUT: true, + PATCH: true, + DELETE: true, + }); + + const toggleMethods = useCallback((method: Method | null) => { + const enabled = method == null; + const methods = { + GET: enabled, + POST: enabled, + PUT: enabled, + PATCH: enabled, + DELETE: enabled, + } + + if (!enabled) { + methods[method] = true; + } + + setMethods(methods); + }, []); + + const enabledMethods: Method[] = useMemo(() => (Object + .entries(methods) + .filter(([method, enabled]) => enabled) + .map(([method]) => method) + ), [methods]); + + const methodLabel = useMemo(() => { + if (enabledMethods.length == 1) { + return enabledMethods[0] + } else if (enabledMethods.length === 5) { + return 'ANY' + } else { + return 'CUSTOM' + } + }, [enabledMethods]); + + + + const filteredRequests = useMemo(() => { + let searchRegex = new RegExp(''); + try { + searchRegex = new RegExp( + enableRegex + ? search + : search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), + 'i'); + } catch { + + } + + return ( + ( + requests + .map((request, index) => [index, request]) + .reverse() as [number, RequestResponse][] + ) + .filter(([index, request]) => ( + enabledMethods.length > 0 === null || enabledMethods.includes(request.request.method) + )) + .filter(([index, request]) => ( + search === '' || searchRegex.test(`${request.request.method} ${request.request.path}`) + )) + ) + }, [requests, search, enabledMethods, enableRegex]) + + return ( +
+ + + + + toggleMethods(null)}>ANY + + toggleMethods('GET')}>GET + toggleMethods('POST')}>POST + toggleMethods('PUT')}>PUT + toggleMethods('PATCH')}>PATCH + toggleMethods('DELETE')}>DELETE + + setSearch(target.value)}/> + + + + + + { + filteredRequests.length > 0 + ? ( + filteredRequests.map(([index, requestResponse]) => { + const selected = selectedRequestIndex === index; + return ( + setSelectedRequestIndex(index)} + key={`request-${index}`} + className={classNames({ + 'bg-primary': selected, + 'text-light': selected, + 'border-bottom': true + })} + > + + + ) + }) + ) + : ( +
+

No requests

+
+ ) + } +
+ setShowFilterOptions(false)}> + + + Filter Options + + + + + Search + setSearch(target.value)}/> + setEnableRegex(!enableRegex)} /> + + + Method + {Object.entries(methods).map(([method, enabled]) => ( + setMethods({...methods, [method]: !enabled})} /> + ))} + + + +
+ ) +} -- cgit v1.2.3