diff options
Diffstat (limited to 'src/components/App')
| -rw-r--r-- | src/components/App/App.tsx | 189 |
1 files changed, 96 insertions, 93 deletions
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index e08c84f..1d231ae 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx | |||
| @@ -1,81 +1,88 @@ | |||
| 1 | import * as React from "react"; | 1 | import * as React from "react"; |
| 2 | import {ReactElement, useContext, useEffect, useMemo, useState} from "react"; | 2 | import { ReactElement, useContext, useEffect, useMemo, useState } from "react"; |
| 3 | import useRequests, { | 3 | import useRequests, { |
| 4 | ReadyState, | 4 | ReadyState, |
| 5 | RequestResponse | 5 | RequestResponse, |
| 6 | } from "../../hooks/useRequests"; | 6 | } from "../../hooks/useRequests"; |
| 7 | 7 | ||
| 8 | import styles from './App.module.scss'; | 8 | import styles from "./App.module.scss"; |
| 9 | import RequestDetails from "../RequestDetails/RequestDetails"; | 9 | import RequestDetails from "../RequestDetails/RequestDetails"; |
| 10 | import {getHost} from "../../utils"; | 10 | import { getHost } from "../../utils"; |
| 11 | import {Container, ListGroup, Nav, Navbar, NavDropdown} from "react-bootstrap"; | 11 | import { |
| 12 | Container, | ||
| 13 | ListGroup, | ||
| 14 | Nav, | ||
| 15 | Navbar, | ||
| 16 | NavDropdown, | ||
| 17 | } from "react-bootstrap"; | ||
| 12 | import classNames from "classnames"; | 18 | import classNames from "classnames"; |
| 13 | import {Sliders} from "../Icons/Sliders"; | 19 | import { Sliders } from "../Icons/Sliders"; |
| 14 | import {Sun} from "../Icons/Sun"; | 20 | import { Sun } from "../Icons/Sun"; |
| 15 | import {Moon} from "../Icons/Moon"; | 21 | import { Moon } from "../Icons/Moon"; |
| 16 | import Trash from "../Icons/Trash"; | 22 | import Trash from "../Icons/Trash"; |
| 17 | import {DarkModeContext} from "../../contexts/DarkMode"; | 23 | import { DarkModeContext } from "../../contexts/DarkMode"; |
| 18 | import RequestList from "../RequestList/RequestList"; | 24 | import RequestList from "../RequestList/RequestList"; |
| 19 | 25 | ||
| 20 | interface Config { | 26 | interface Config { |
| 21 | url: string | 27 | url: string; |
| 22 | } | 28 | } |
| 23 | 29 | ||
| 24 | interface SettingsMenu { | 30 | interface SettingsMenu { |
| 25 | icon: ReactElement, | 31 | icon: ReactElement; |
| 26 | label: string, | 32 | label: string; |
| 27 | onClick: () => void, | 33 | onClick: () => void; |
| 28 | } | 34 | } |
| 29 | 35 | ||
| 30 | type ReadyStateMap = { | 36 | type ReadyStateMap = { |
| 31 | [ReadyState.CONNECTING]: string, | 37 | [ReadyState.CONNECTING]: string; |
| 32 | [ReadyState.OPEN]: string, | 38 | [ReadyState.OPEN]: string; |
| 33 | [ReadyState.CLOSING]: string, | 39 | [ReadyState.CLOSING]: string; |
| 34 | [ReadyState.CLOSED]: string, | 40 | [ReadyState.CLOSED]: string; |
| 35 | } | 41 | }; |
| 36 | 42 | ||
| 37 | const statusIconMap: ReadyStateMap = { | 43 | const statusIconMap: ReadyStateMap = { |
| 38 | [ReadyState.CONNECTING]: '🔴', | 44 | [ReadyState.CONNECTING]: "🔴", |
| 39 | [ReadyState.OPEN]: '🟢', | 45 | [ReadyState.OPEN]: "🟢", |
| 40 | [ReadyState.CLOSING]: '🔴', | 46 | [ReadyState.CLOSING]: "🔴", |
| 41 | [ReadyState.CLOSED]: '🔴', | 47 | [ReadyState.CLOSED]: "🔴", |
| 42 | } | 48 | }; |
| 43 | 49 | ||
| 44 | const statusTextMap: ReadyStateMap = { | 50 | const statusTextMap: ReadyStateMap = { |
| 45 | [ReadyState.CONNECTING]: 'Connecting...', | 51 | [ReadyState.CONNECTING]: "Connecting...", |
| 46 | [ReadyState.OPEN]: 'Connected', | 52 | [ReadyState.OPEN]: "Connected", |
| 47 | [ReadyState.CLOSING]: 'Closing...', | 53 | [ReadyState.CLOSING]: "Closing...", |
| 48 | [ReadyState.CLOSED]: 'Closed', | 54 | [ReadyState.CLOSED]: "Closed", |
| 49 | } | 55 | }; |
| 50 | 56 | ||
| 51 | export default function App() { | 57 | export default function App() { |
| 52 | const { darkMode, toggle } = useContext(DarkModeContext); | 58 | const { darkMode, toggle } = useContext(DarkModeContext); |
| 53 | const [config, setConfig]= useState<Config | null>(null) | 59 | const [config, setConfig] = useState<Config | null>(null); |
| 54 | 60 | ||
| 55 | const { calls, readyState, clear } = useRequests({ | 61 | const { calls, readyState, clear } = useRequests({ |
| 56 | onConnect: async () => { | 62 | onConnect: async () => { |
| 57 | const response = await fetch(`http://${getHost()}/config/`) | 63 | const response = await fetch(`http://${getHost()}/config/`); |
| 58 | const config = await response.json() | 64 | const config = await response.json(); |
| 59 | setConfig(config) | 65 | setConfig(config); |
| 60 | } | 66 | }, |
| 61 | }); | 67 | }); |
| 62 | 68 | ||
| 63 | useEffect(() => { | 69 | useEffect(() => { |
| 64 | const url = new URL(config?.url ?? 'https://loading...'); | 70 | const url = new URL(config?.url ?? "https://loading..."); |
| 65 | document.title = `${statusIconMap[readyState]} ${url.host} | TTUN`; | 71 | document.title = `${statusIconMap[readyState]} ${url.host} | TTUN`; |
| 66 | }, [readyState, config?.url]) | 72 | }, [readyState, config?.url]); |
| 67 | 73 | ||
| 68 | const [selectedRequestIndex, setSelectedRequestIndex] = useState<number | null>(null); | 74 | const [selectedRequestIndex, setSelectedRequestIndex] = useState< |
| 69 | const selectedRequest = useMemo<RequestResponse | null>(() => ( | 75 | number | null |
| 70 | selectedRequestIndex === null | 76 | >(null); |
| 71 | ? null | 77 | const selectedRequest = useMemo<RequestResponse | null>( |
| 72 | : calls[selectedRequestIndex] | 78 | () => (selectedRequestIndex === null ? null : calls[selectedRequestIndex]), |
| 73 | ), [selectedRequestIndex, calls]); | 79 | [selectedRequestIndex, calls] |
| 80 | ); | ||
| 74 | 81 | ||
| 75 | const settingsMenu: (SettingsMenu | null)[] = [ | 82 | const settingsMenu: (SettingsMenu | null)[] = [ |
| 76 | { | 83 | { |
| 77 | onClick: toggle, | 84 | onClick: toggle, |
| 78 | icon: darkMode ? <Sun />: <Moon />, | 85 | icon: darkMode ? <Sun /> : <Moon />, |
| 79 | label: darkMode ? "Light mode" : "Dark mode", | 86 | label: darkMode ? "Light mode" : "Dark mode", |
| 80 | }, | 87 | }, |
| 81 | null, | 88 | null, |
| @@ -85,40 +92,32 @@ export default function App() { | |||
| 85 | clear(); | 92 | clear(); |
| 86 | }, | 93 | }, |
| 87 | icon: <Trash />, | 94 | icon: <Trash />, |
| 88 | label: "Clear" | 95 | label: "Clear", |
| 89 | } | 96 | }, |
| 90 | ]; | 97 | ]; |
| 91 | 98 | ||
| 92 | return config && ( | 99 | return ( |
| 93 | <div className={styles.app}> | 100 | config && ( |
| 94 | <Navbar | 101 | <div className={styles.app}> |
| 95 | bg="dark" | 102 | <Navbar bg="dark" variant="dark" expand as="header"> |
| 96 | variant="dark" | 103 | <Container fluid> |
| 97 | expand | 104 | <div> |
| 98 | as="header" | 105 | <Navbar.Brand>TTUN</Navbar.Brand> |
| 99 | > | 106 | <Navbar.Text> |
| 100 | <Container fluid> | 107 | {`${statusIconMap[readyState]} ${statusTextMap[readyState]}`} |
| 101 | <div> | 108 | </Navbar.Text> |
| 102 | <Navbar.Brand> | 109 | </div> |
| 103 | TTUN | 110 | <div className="d-flex"> |
| 104 | </Navbar.Brand> | 111 | <Navbar.Text> |
| 105 | <Navbar.Text> | 112 | <a href={config.url} target="_blank"> |
| 106 | {`${statusIconMap[readyState]} ${statusTextMap[readyState]}`} | 113 | {config.url} |
| 107 | </Navbar.Text> | 114 | </a> |
| 108 | </div> | 115 | </Navbar.Text> |
| 109 | <div className="d-flex"> | 116 | <Navbar.Toggle aria-controls="settings" /> |
| 110 | <Navbar.Text> | 117 | <Navbar.Collapse id="settings" className="ms-2"> |
| 111 | <a href={config.url} target="_blank">{config.url}</a> | 118 | <Nav> |
| 112 | </Navbar.Text> | 119 | <NavDropdown align="end" title={<Sliders />}> |
| 113 | <Navbar.Toggle aria-controls="settings"/> | 120 | {settingsMenu.map((item) => { |
| 114 | <Navbar.Collapse id="settings" className="ms-2"> | ||
| 115 | <Nav> | ||
| 116 | <NavDropdown | ||
| 117 | align="end" | ||
| 118 | title={<Sliders/>} | ||
| 119 | > | ||
| 120 | { | ||
| 121 | settingsMenu.map((item) => { | ||
| 122 | if (item !== null) { | 121 | if (item !== null) { |
| 123 | const { onClick, icon, label } = item; | 122 | const { onClick, icon, label } = item; |
| 124 | return ( | 123 | return ( |
| @@ -129,27 +128,31 @@ export default function App() { | |||
| 129 | {icon} | 128 | {icon} |
| 130 | <span className="ms-3">{label}</span> | 129 | <span className="ms-3">{label}</span> |
| 131 | </NavDropdown.Item> | 130 | </NavDropdown.Item> |
| 132 | ) | 131 | ); |
| 133 | } else { | 132 | } else { |
| 134 | return <NavDropdown.Divider /> | 133 | return <NavDropdown.Divider />; |
| 135 | } | 134 | } |
| 136 | }) | 135 | })} |
| 137 | } | 136 | </NavDropdown> |
| 138 | </NavDropdown> | 137 | </Nav> |
| 139 | </Nav> | 138 | </Navbar.Collapse> |
| 140 | </Navbar.Collapse> | 139 | </div> |
| 141 | </div> | 140 | </Container> |
| 142 | </Container> | 141 | </Navbar> |
| 143 | </Navbar> | ||
| 144 | 142 | ||
| 145 | <main className={styles.main}> | 143 | <main className={styles.main}> |
| 146 | <div className={classNames("border-end", styles.sidebar)}> | 144 | <div className={classNames("border-end", styles.sidebar)}> |
| 147 | <RequestList requests={calls} selectedRequestIndex={selectedRequestIndex} setSelectedRequestIndex={setSelectedRequestIndex}/> | 145 | <RequestList |
| 148 | </div> | 146 | requests={calls} |
| 149 | <div className={styles.details}> | 147 | selectedRequestIndex={selectedRequestIndex} |
| 150 | <RequestDetails requestResponse={selectedRequest} /> | 148 | setSelectedRequestIndex={setSelectedRequestIndex} |
| 151 | </div> | 149 | /> |
| 152 | </main> | 150 | </div> |
| 153 | </div> | 151 | <div className={styles.details}> |
| 152 | <RequestDetails requestResponse={selectedRequest} /> | ||
| 153 | </div> | ||
