diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/App/App.tsx | 39 | ||||
| -rw-r--r-- | src/components/Frames/Frames.module.scss | 9 | ||||
| -rw-r--r-- | src/components/Frames/Frames.tsx | 75 | ||||
| -rw-r--r-- | src/components/Icons/Sliders.tsx | 2 | ||||
| -rw-r--r-- | src/components/Icons/Trash.tsx | 2 | ||||
| -rw-r--r-- | src/components/RequestDetails/RequestDetails.tsx | 82 | ||||
| -rw-r--r-- | src/components/RequestList/RequestList.tsx | 48 | ||||
| -rw-r--r-- | src/components/RequestSummary/RequestSummary.tsx | 2 |
8 files changed, 173 insertions, 86 deletions
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 5e17388..ad36add 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx | |||
| @@ -13,7 +13,8 @@ import Moon from "~/components/Icons/Moon"; | |||
| 13 | import Trash from "~/components/Icons/Trash"; | 13 | import Trash from "~/components/Icons/Trash"; |
| 14 | import { DarkModeContext } from "~/contexts/DarkMode"; | 14 | import { DarkModeContext } from "~/contexts/DarkMode"; |
| 15 | import RequestList from "~/components/RequestList/RequestList"; | 15 | import RequestList from "~/components/RequestList/RequestList"; |
| 16 | import { ReadyState, RequestResponse } from "~/types"; | 16 | import { Call, ReadyState } from "~/types"; |
| 17 | import { ConnectionContext } from "~/contexts/Connection"; | ||
| 17 | 18 | ||
| 18 | interface Config { | 19 | interface Config { |
| 19 | url: string; | 20 | url: string; |
| @@ -48,29 +49,14 @@ const statusTextMap: ReadyStateMap = { | |||
| 48 | 49 | ||
| 49 | export default function App() { | 50 | export default function App() { |
| 50 | const { darkMode, toggle } = useContext(DarkModeContext); | 51 | const { darkMode, toggle } = useContext(DarkModeContext); |
| 51 | const [config, setConfig] = useState<Config | null>(null); | 52 | const { config, selectedCall, setSelectedCall, readyState, clear } = |
| 52 | 53 | useContext(ConnectionContext); | |
| 53 | const { calls, readyState, clear } = useRequests({ | ||
| 54 | onConnect: async () => { | ||
| 55 | const response = await fetch(`http://${getHost()}/config/`); | ||
| 56 | const config = await response.json(); | ||
| 57 | setConfig(config); | ||
| 58 | }, | ||
| 59 | }); | ||
| 60 | 54 | ||
| 61 | useEffect(() => { | 55 | useEffect(() => { |
| 62 | const url = new URL(config?.url ?? "https://loading..."); | 56 | const url = new URL(config?.url ?? "https://loading..."); |
| 63 | document.title = `${statusIconMap[readyState]} ${url.host} | TTUN`; | 57 | document.title = `${statusIconMap[readyState]} ${url.host} | TTUN`; |
| 64 | }, [readyState, config?.url]); | 58 | }, [readyState, config?.url]); |
| 65 | 59 | ||
| 66 | const [selectedRequestIndex, setSelectedRequestIndex] = useState< | ||
| 67 | number | null | ||
| 68 | >(null); | ||
| 69 | const selectedRequest = useMemo<RequestResponse | null>( | ||
| 70 | () => (selectedRequestIndex === null ? null : calls[selectedRequestIndex]), | ||
| 71 | [selectedRequestIndex, calls] | ||
| 72 | ); | ||
| 73 | |||
| 74 | const settingsMenu: (SettingsMenu | null)[] = [ | 60 | const settingsMenu: (SettingsMenu | null)[] = [ |
| 75 | { | 61 | { |
| 76 | onClick: toggle, | 62 | onClick: toggle, |
| @@ -80,7 +66,7 @@ export default function App() { | |||
| 80 | null, | 66 | null, |
| 81 | { | 67 | { |
| 82 | onClick: () => { | 68 | onClick: () => { |
| 83 | setSelectedRequestIndex(null); | 69 | setSelectedCall(null); |
| 84 | clear(); | 70 | clear(); |
| 85 | }, | 71 | }, |
| 86 | icon: <Trash />, | 72 | icon: <Trash />, |
| @@ -106,14 +92,15 @@ export default function App() { | |||
| 106 | </a> | 92 | </a> |
| 107 | </Navbar.Text> | 93 | </Navbar.Text> |
| 108 | <Navbar.Toggle aria-controls="settings" /> | 94 | <Navbar.Toggle aria-controls="settings" /> |
| 109 | <Navbar.Collapse id="settings" className="ms-2"> | 95 | <Navbar.Collapse id="settings" className="ms-2" role="button"> |
| 110 | <Nav> | 96 | <Nav> |
| 111 | <NavDropdown align="end" title={<Sliders />}> | 97 | <NavDropdown align="end" title={<Sliders />}> |
| 112 | {settingsMenu.map((item) => { | 98 | {settingsMenu.map((item, index) => { |
| 113 | if (item !== null) { | 99 | if (item !== null) { |
| 114 | const { onClick, icon, label } = item; | 100 | const { onClick, icon, label } = item; |
| 115 | return ( | 101 | return ( |
| 116 | <NavDropdown.Item | 102 | <NavDropdown.Item |
| 103 | key={label} | ||
| 117 | onClick={onClick} | 104 | onClick={onClick} |
| 118 | className="d-flex align-items-center" | 105 | className="d-flex align-items-center" |
| 119 | > | 106 | > |
| @@ -122,7 +109,7 @@ export default function App() { | |||
| 122 | </NavDropdown.Item> | 109 | </NavDropdown.Item> |
| 123 | ); | 110 | ); |
| 124 | } else { | 111 | } else { |
| 125 | return <NavDropdown.Divider />; | 112 | return <NavDropdown.Divider key={`item-${index}`} />; |
| 126 | } | 113 | } |
| 127 | })} | 114 | })} |
| 128 | </NavDropdown> | 115 | </NavDropdown> |
| @@ -134,14 +121,10 @@ export default function App() { | |||
| 134 | 121 | ||
| 135 | <main className={styles.main}> | 122 | <main className={styles.main}> |
| 136 | <div className={classNames("border-end", styles.sidebar)}> | 123 | <div className={classNames("border-end", styles.sidebar)}> |
| 137 | <RequestList | 124 | <RequestList /> |
| 138 | requests={calls} | ||
| 139 | selectedRequestIndex={selectedRequestIndex} | ||
| 140 | setSelectedRequestIndex={setSelectedRequestIndex} | ||
| 141 | /> | ||
| 142 | </div> | 125 | </div> |
| 143 | <div className={styles.details}> | 126 | <div className={styles.details}> |
| 144 | <RequestDetails requestResponse={selectedRequest} /> | 127 | <RequestDetails /> |
| 145 | </div> | 128 | </div> |
| 146 | </main> | 129 | </main> |
| 147 | </div> | 130 | </div> |
diff --git a/src/components/Frames/Frames.module.scss b/src/components/Frames/Frames.module.scss new file mode 100644 index 0000000..821f0b8 --- /dev/null +++ b/src/components/Frames/Frames.module.scss | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | .arrow { | ||
| 2 | &.outbound { | ||
| 3 | color: green; | ||
| 4 | } | ||
| 5 | |||
| 6 | &.inbound { | ||
| 7 | color: red; | ||
| 8 | } | ||
| 9 | } | ||
diff --git a/src/components/Frames/Frames.tsx b/src/components/Frames/Frames.tsx new file mode 100644 index 0000000..83c4c96 --- /dev/null +++ b/src/components/Frames/Frames.tsx | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | import { Frame } from "~/types"; | ||
| 2 | import React, { PropsWithChildren, useContext } from "react"; | ||
| 3 | import { Col, ListGroup, Row } from "react-bootstrap"; | ||
| 4 | import classNames from "classnames"; | ||
| 5 | import styles from "./Frames.module.scss"; | ||
| 6 | import dayjs from "dayjs"; | ||
| 7 | import ReactJson from "react-json-view"; | ||
| 8 | import { DarkModeContext } from "~/contexts/DarkMode"; | ||
| 9 | |||
| 10 | function isJson(data: any): boolean { | ||
| 11 | try { | ||
| 12 | JSON.parse(data); | ||
| 13 | return true; | ||
| 14 | } catch { | ||
| 15 | return false; | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | interface FramesProps { | ||
| 20 | frames: Frame[]; | ||
| 21 | } | ||
| 22 | |||
| 23 | export default function Frames({ | ||
| 24 | frames, | ||
| 25 | }: PropsWithChildren<FramesProps>): JSX.Element { | ||
| 26 | const { darkMode } = useContext(DarkModeContext); | ||
| 27 | return ( | ||
| 28 | <ListGroup variant="flush" as="ul" className={"flex-grow-1"}> | ||
| 29 | {frames.length > 0 ? ( | ||
| 30 | frames.map((frame) => { | ||
| 31 | // Inbound relative to the client | ||
| 32 | const inbound = frame.type !== "websocket_inbound"; | ||
| 33 | |||
| 34 | const body = | ||
| 35 | frame.type !== "websocket_disconnect" | ||
| 36 | ? atob(frame.payload.body) | ||
| 37 | : null; | ||
| 38 | |||
| 39 | return ( | ||
| 40 | <ListGroup.Item | ||
| 41 | as="li" | ||
| 42 | key={frame.payload.id} | ||
| 43 | className={classNames({ | ||
| 44 | "border-bottom": true, | ||
| 45 | })} | ||
| 46 | > | ||
| 47 | <Row> | ||
| 48 | <Col className="flex-grow-0 d-flex align-items-center text-nowrap text-muted"> | ||
| 49 | {dayjs(frame.payload?.timestamp).format("HH:mm:ss.SSS")} | ||
| 50 | </Col> | ||
| 51 | <Col | ||
| 52 | className={classNames( | ||
| 53 | styles.arrow, | ||
| 54 | "flex-grow-0 d-flex align-items-center flex-nowrap", | ||
| 55 | { | ||
| 56 | [styles.inbound]: inbound, | ||
| 57 | [styles.outbound]: !inbound, | ||
| 58 | } | ||
| 59 | )} | ||
| 60 | > | ||
| 61 | {inbound ? "▼" : "▲"} | ||
| 62 | </Col> | ||
| 63 | <Col className="flex-grow-1">{body}</Col> | ||
| 64 | </Row> | ||
| 65 | </ListGroup.Item> | ||
| 66 | ); | ||
| 67 | }) | ||
| 68 | ) : ( | ||
| 69 | <div className={styles.noRequest}> | ||
| 70 | <p>No messages</p> | ||
| 71 | </div> | ||
| 72 | )} | ||
| 73 | </ListGroup> | ||
| 74 | ); | ||
| 75 | } | ||
diff --git a/src/components/Icons/Sliders.tsx b/src/components/Icons/Sliders.tsx index 66d6fac..3c9b38b 100644 --- a/src/components/Icons/Sliders.tsx +++ b/src/components/Icons/Sliders.tsx | |||
| @@ -11,7 +11,7 @@ export default function Sliders() { | |||
| 11 | viewBox="0 0 16 16" | 11 | viewBox="0 0 16 16" |
| 12 | > | 12 | > |
| 13 | <path | 13 | <path |
| 14 | fill-rule="evenodd" | 14 | fillRule="evenodd" |
| 15 | d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z" | 15 | d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z" |
| 16 | /> | 16 | /> |
| 17 | </svg> | 17 | </svg> |
diff --git a/src/components/Icons/Trash.tsx b/src/components/Icons/Trash.tsx index 6c0039b..cf03546 100644 --- a/src/components/Icons/Trash.tsx +++ b/src/components/Icons/Trash.tsx | |||
| @@ -12,7 +12,7 @@ export default function Trash() { | |||
| 12 | > | 12 | > |
| 13 | <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" /> | 13 | <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" /> |
| 14 | <path | 14 | <path |
| 15 | fill-rule="evenodd" | 15 | fillRule="evenodd" |
| 16 | d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" | 16 | d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" |
| 17 | /> | 17 | /> |
| 18 | </svg> | 18 | </svg> |
diff --git a/src/components/RequestDetails/RequestDetails.tsx b/src/components/RequestDetails/RequestDetails.tsx index 9000220..22b3fe2 100644 --- a/src/components/RequestDetails/RequestDetails.tsx +++ b/src/components/RequestDetails/RequestDetails.tsx | |||
| @@ -1,11 +1,13 @@ | |||
| 1 | import * as React from "react"; | 1 | import * as React from "react"; |
| 2 | import { useCallback, useMemo, useState } from "react"; | 2 | import { useCallback, useContext, useMemo, useState } from "react"; |
| 3 | import styles from "~/components/RequestDetails/RequestDetails.module.scss"; | 3 | import styles from "~/components/RequestDetails/RequestDetails.module.scss"; |
| 4 | import RequestSummary from "~/components/RequestSummary/RequestSummary"; | 4 | import RequestSummary from "~/components/RequestSummary/RequestSummary"; |
| 5 | import Content from "~/components/Content/Content"; | 5 | import Content from "~/components/Content/Content"; |
| 6 | import { getHost } from "~/utils"; | 6 | import { getHost } from "~/utils"; |
| 7 | import { Button, Card, Col, Container, Nav, Row, Table } from "react-bootstrap"; | 7 | import { Button, Card, Col, Container, Nav, Row, Table } from "react-bootstrap"; |
| 8 | import { Headers, RequestResponse } from "~/types"; | 8 | import { Call, Headers, selectedCall } from "~/types"; |
| 9 | import { ConnectionContext } from "~/contexts/Connection"; | ||
| 10 | import Frames from "~/components/Frames/Frames"; | ||
| 9 | 11 | ||
| 10 | interface TimingProps { | 12 | interface TimingProps { |
| 11 | timing: number; | 13 | timing: number; |
| @@ -49,37 +51,35 @@ function HeaderTable({ title, headers }: HeaderTableProps) { | |||
| 49 | ); | 51 | ); |
| 50 | } | 52 | } |
| 51 | 53 | ||
| 52 | interface DetailsProps { | 54 | type Tab = "headers" | "request" | "response" | "messages"; |
| 53 | requestResponse: RequestResponse | null; | ||
| 54 | } | ||
| 55 | 55 | ||
| 56 | type Tab = "headers" | "request" | "response"; | 56 | export default function RequestDetails() { |
| 57 | const { selectedCall } = useContext(ConnectionContext); | ||
| 57 | 58 | ||
| 58 | export default function RequestDetails({ requestResponse }: DetailsProps) { | ||
| 59 | const [tab, selectTab] = useState<Tab>("headers"); | 59 | const [tab, selectTab] = useState<Tab>("headers"); |
| 60 | const [raw, setRaw] = useState(false); | 60 | const [raw, setRaw] = useState(false); |
| 61 | 61 | ||
| 62 | const resend = useCallback( | 62 | const resend = useCallback( |
| 63 | async () => | 63 | async () => |
| 64 | requestResponse !== null && | 64 | selectedCall !== null && |
| 65 | fetch(`http://${getHost()}/resend/`, { | 65 | fetch(`http://${getHost()}/resend/`, { |
| 66 | method: "POST", | 66 | method: "POST", |
| 67 | body: JSON.stringify({ | 67 | body: JSON.stringify({ |
| 68 | ...requestResponse.request, | 68 | ...selectedCall.request, |
| 69 | id: undefined, | 69 | id: undefined, |
| 70 | }), | 70 | }), |
| 71 | }), | 71 | }), |
| 72 | [requestResponse] | 72 | [selectedCall] |
| 73 | ); | 73 | ); |
| 74 | 74 | ||
| 75 | return requestResponse !== null ? ( | 75 | return selectedCall !== null ? ( |
| 76 | <div className={styles.details}> | 76 | <div className={styles.details}> |
| 77 | <div className={styles.header}> | 77 | <div className={styles.header}> |
| 78 | <Row> | 78 | <Row> |
| 79 | <Col> | 79 | <Col> |
| 80 | <Container fluid style={{ fontSize: "1.5em" }} className="py-3"> | 80 | <Container fluid style={{ fontSize: "1.5em" }} className="py-3"> |
| 81 | <RequestSummary | 81 | <RequestSummary |
| 82 | requestResponse={requestResponse} | 82 | requestResponse={selectedCall} |
| 83 | className={styles.summary} | 83 | className={styles.summary} |
| 84 | /> | 84 | /> |
| 85 | </Container> | 85 | </Container> |
| @@ -98,18 +98,29 @@ export default function RequestDetails({ requestResponse }: DetailsProps) { | |||
| 98 | <Nav.Item> | 98 | <Nav.Item> |
| 99 | <Nav.Link eventKey="request">Request</Nav.Link> | 99 | <Nav.Link eventKey="request">Request</Nav.Link> |
| 100 | </Nav.Item> | 100 | </Nav.Item> |
| 101 | <Nav.Item> | 101 | {selectedCall.type === "RequestResponse" && ( |
| 102 | <Nav.Link | 102 | <> |
| 103 | eventKey="response" | 103 | <Nav.Item> |
| 104 | disabled={requestResponse.response === undefined} | 104 | <Nav.Link |
| 105 | > | 105 | eventKey="response" |
| 106 | Response | 106 | disabled={selectedCall.response === undefined} |
| 107 | </Nav.Link> | 107 | > |
| 108 | </Nav.Item> | 108 | Response |
| 109 | </Nav.Link> | ||
| 110 | </Nav.Item> | ||
| 111 | </> | ||
| 112 | )} | ||
| 113 | {selectedCall.type === "WebsocketConnection" && ( | ||
| 114 | <> | ||
| 115 | <Nav.Item> | ||
| 116 | <Nav.Link eventKey="response">Messages</Nav.Link> | ||
| 117 | </Nav.Item> | ||
| 118 | </> | ||
| 119 | )} | ||
| 109 | </Nav> | 120 | </Nav> |
| 110 | </Col> | 121 | </Col> |
| 111 | <Col className="border-bottom px-3 " xs="auto"> | 122 | <Col className="border-bottom px-3 " xs="auto"> |
| 112 | <Timing timing={requestResponse.response?.timing ?? NaN} /> | 123 | <Timing timing={selectedCall.response?.timing ?? NaN} /> |
| 113 | <Button | 124 | <Button |
| 114 | variant="outline-primary" | 125 | variant="outline-primary" |
| 115 | onClick={() => resend()} | 126 | onClick={() => resend()} |
| @@ -125,21 +136,30 @@ export default function RequestDetails({ requestResponse }: DetailsProps) { | |||
| 125 | <> | 136 | <> |
| 126 | <HeaderTable | 137 | <HeaderTable |
| 127 | title="request headers" | 138 | title="request headers" |
| 128 | headers={requestResponse.request.headers} | 139 | headers={selectedCall.request.headers} |
| 129 | /> | 140 | /> |
| 130 | {requestResponse.response && ( | 141 | |
| 131 | <HeaderTable | 142 | {selectedCall.type === "RequestResponse" && |
| 132 | title="response headers" | 143 | selectedCall.response !== undefined && ( |
| 133 | headers={requestResponse.response.headers} | 144 | <HeaderTable |
| 134 | /> | 145 | title="response headers" |
| 135 | )} | 146 | headers={selectedCall.response.headers} |
| 147 | /> | ||
| 148 | )} | ||
| 136 | </> | 149 | </> |
| 137 | )} | 150 | )} |
| 138 | {tab === "request" && ( | 151 | {tab === "request" && ( |
| 139 | <Content data={requestResponse.request} raw={raw} setRaw={setRaw} /> | 152 | <Content data={selectedCall.request} raw={raw} setRaw={setRaw} /> |
| 140 | )} | 153 | )} |
| 141 | {tab === "response" && requestResponse.response !== undefined && ( | 154 | {selectedCall.type === "RequestResponse" && |
| 142 | <Content data={requestResponse.response} raw={raw} setRaw={setRaw} /> | 155 | tab === "response" && |
| 156 | selectedCall.response !== undefined && ( | ||
| 157 | <Content data={selectedCall.response} raw={raw} setRaw={setRaw} /> | ||
| 158 | )} | ||
| 159 | {selectedCall.type === "WebsocketConnection" && tab === "response" && ( | ||
| 160 | <Frames frames={selectedCall.frames} /> | ||
| 161 | // <Content data={selectedCall.frames} raw={false} setRaw={setRaw} /> | ||
| 162 | // <pre>{JSON.stringify(selectedCall.frames, undefined, 2)}</pre> | ||
| 143 | )} | 163 | )} |
| 144 | </div> | 164 | </div> |
| 145 | </div> | 165 | </div> |
diff --git a/src/components/RequestList/RequestList.tsx b/src/components/RequestList/RequestList.tsx index a4e9a0e..82a6b21 100644 --- a/src/components/RequestList/RequestList.tsx +++ b/src/components/RequestList/RequestList.tsx | |||
| @@ -11,27 +11,24 @@ import classNames from "classnames"; | |||
| 11 | import styles from "~/components/RequestList/RequestList.module.scss"; | 11 | import styles from "~/components/RequestList/RequestList.module.scss"; |
| 12 | import RequestSummary from "~/components/RequestSummary/RequestSummary"; | 12 | import RequestSummary from "~/components/RequestSummary/RequestSummary"; |
| 13 | import * as React from "react"; | 13 | import * as React from "react"; |
| 14 | import { useCallback, useContext, useMemo, useState } from "react"; | 14 | import { useCallback, useContext, useEffect, useMemo, useState } from "react"; |
| 15 | import { DarkModeContext } from "~/contexts/DarkMode"; | 15 | import { DarkModeContext } from "~/contexts/DarkMode"; |
| 16 | import Filter from "~/components/Icons/Filter"; | 16 | import Filter from "~/components/Icons/Filter"; |
| 17 | import { Method, RequestResponse } from "~/types"; | 17 | import { Call, Method } from "~/types"; |
| 18 | 18 | import { ConnectionContext } from "~/contexts/Connection"; | |
| 19 | interface ListProps { | ||
| 20 | requests: RequestResponse[]; | ||
| 21 | selectedRequestIndex: number | null; | ||
| 22 | setSelectedRequestIndex: (index: number) => void; | ||
| 23 | } | ||
| 24 | 19 | ||
| 25 | type EnabledMethods = { | 20 | type EnabledMethods = { |
| 26 | [method in Method]: boolean; | 21 | [method in Method]: boolean; |
| 27 | }; | 22 | }; |
| 28 | 23 | ||
| 29 | export default function RequestList({ | 24 | export default function RequestList() { |
| 30 | requests, | ||
| 31 | selectedRequestIndex, | ||
| 32 | setSelectedRequestIndex, | ||
| 33 | }: ListProps) { | ||
| 34 | const { darkMode } = useContext(DarkModeContext); | 25 | const { darkMode } = useContext(DarkModeContext); |
| 26 | const { | ||
| 27 | calls: requests, | ||
| 28 | selectedCall, | ||
| 29 | setSelectedCall, | ||
| 30 | } = useContext(ConnectionContext); | ||
| 31 | |||
| 35 | const [showFilterOptions, setShowFilterOptions] = useState(false); | 32 | const [showFilterOptions, setShowFilterOptions] = useState(false); |
| 36 | const [search, setSearch] = useState(""); | 33 | const [search, setSearch] = useState(""); |
| 37 | const [enableRegex, setEnableRegex] = useState(false); | 34 | const [enableRegex, setEnableRegex] = useState(false); |
| @@ -73,7 +70,7 @@ export default function RequestList({ | |||
| 73 | () => | 70 | () => |
| 74 | Object.entries(methods) | 71 | Object.entries(methods) |
| 75 | .filter(([method, enabled]) => enabled) | 72 | .filter(([method, enabled]) => enabled) |
| 76 | .map(([method]) => method), | 73 | .map(([method]) => method) as Method[], |
| 77 | [methods] | 74 | [methods] |
| 78 | ); | 75 | ); |
| 79 | 76 | ||
| @@ -97,16 +94,14 @@ export default function RequestList({ | |||
| 97 | } catch {} | 94 | } catch {} |
| 98 | 95 | ||
| 99 | return requests | 96 | return requests |
| 100 | .map<[number, RequestResponse]>((request, index) => [index, request]) | ||
| 101 | .reverse() | 97 | .reverse() |
| 102 | .filter( | 98 | .filter( |
| 103 | ([index, request]) => | 99 | (request) => |
| 104 | enabledMethods.length > 0 === null || | 100 | enabledMethods.length === 0 || |
| 105 | enabledMethods.includes(request.request.method) | 101 | enabledMethods.includes(request.request.method) |
| 106 | ) | 102 | ) |
| 107 | .filter( | 103 | .filter( |
| 108 | ([index, request]) => | 104 | (request) => search === "" || searchRegex.test(request.request.path) |
| 109 | search === "" || searchRegex.test(request.request.path) | ||
| 110 | ); | 105 | ); |
| 111 | }, [requests, search, enabledMethods, enableRegex]); | 106 | }, [requests, search, enabledMethods, enableRegex]); |
| 112 | 107 | ||
| @@ -124,7 +119,10 @@ export default function RequestList({ | |||
| 124 | </Dropdown.Item> | 119 | </Dropdown.Item> |
| 125 | <Dropdown.Divider /> | 120 | <Dropdown.Divider /> |
| 126 | {Object.entries(methods).map(([method, enabled]) => ( | 121 | {Object.entries(methods).map(([method, enabled]) => ( |
| 127 | <Dropdown.Item onClick={() => toggleMethods(method as Method)}> | 122 | <Dropdown.Item |
| 123 | onClick={() => toggleMethods(method as Method)} | ||
| 124 | key={method} | ||
| 125 | > | ||
| 128 | {method} | 126 | {method} |
| 129 | </Dropdown.Item> | 127 | </Dropdown.Item> |
| 130 | ))} | 128 | ))} |
| @@ -151,13 +149,14 @@ export default function RequestList({ | |||
| 151 | className={classNames(styles.list, "flex-grow-1")} | 149 | className={classNames(styles.list, "flex-grow-1")} |
| 152 | > | 150 | > |
| 153 | {filteredRequests.length > 0 ? ( | 151 | {filteredRequests.length > 0 ? ( |
| 154 | filteredRequests.map(([index, requestResponse]) => { | 152 | filteredRequests.map((requestResponse) => { |
| 155 | const selected = selectedRequestIndex === index; | 153 | const selected = |
| 154 | selectedCall?.request.id === requestResponse.request.id; | ||
| 156 | return ( | 155 | return ( |
| 157 | <ListGroup.Item | 156 | <ListGroup.Item |
| 158 | as="li" | 157 | as="li" |
| 159 | onClick={() => setSelectedRequestIndex(index)} | 158 | onClick={() => setSelectedCall?.(requestResponse)} |
| 160 | key={`request-${index}`} | 159 | key={requestResponse.request.id} |
| 161 | className={classNames({ | 160 | className={classNames({ |
| 162 | "bg-primary": selected, | 161 | "bg-primary": selected, |
| 163 | "text-light": selected, | 162 | "text-light": selected, |
| @@ -209,6 +208,7 @@ export default function RequestList({ | |||
| 209 | <Form.Label className="fw-bold">Method</Form.Label> | 208 | <Form.Label className="fw-bold">Method</Form.Label> |
| 210 | {Object.entries(methods).map(([method, enabled]) => ( | 209 | {Object.entries(methods).map(([method, enabled]) => ( |
| 211 | <Form.Check | 210 | <Form.Check |
| 211 | key={method} | ||
| 212 | type="switch" | 212 | type="switch" |
| 213 | label={method} | 213 | label={method} |
| 214 | checked={enabled} | 214 | checked={enabled} |
diff --git a/src/components/RequestSummary/RequestSummary.tsx b/src/components/RequestSummary/RequestSummary.tsx index 49e6086..4e97676 100644 --- a/src/components/RequestSummary/RequestSummary.tsx +++ b/src/components/RequestSummary/RequestSummary.tsx | |||
| @@ -7,7 +7,7 @@ import dayjs from "dayjs"; | |||
| 7 | 7 | ||
| 8 | interface RequestSummaryProps { | 8 | interface RequestSummaryProps { |
| 9 | selected?: boolean; | 9 | selected?: boolean; |
| 10 | requestResponse: RequestResponse; | 10 | requestResponse: Calls; |
| 11 | showTime?: boolean; | 11 | showTime?: boolean; |
| 12 | } | 12 | } |
| 13 | 13 | ||
