1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
import * as React from "react";
import { ReactElement, useContext, useEffect } from "react";
import styles from "~/components/App/App.module.scss";
import RequestDetails from "~/components/RequestDetails/RequestDetails";
import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
import classNames from "classnames";
import Sliders from "~/components/Icons/Sliders";
import Sun from "~/components/Icons/Sun";
import Moon from "~/components/Icons/Moon";
import Trash from "~/components/Icons/Trash";
import { SettingsContext } from "~/contexts/Settings";
import RequestList from "~/components/RequestList/RequestList";
import { ReadyState } from "~/types";
import { ConnectionContext } from "~/contexts/Connection";
interface SettingsMenu {
icon: ReactElement;
label: string;
onClick: () => void;
}
type ReadyStateMap = {
[ReadyState.CONNECTING]: string;
[ReadyState.OPEN]: string;
[ReadyState.CLOSING]: string;
[ReadyState.CLOSED]: string;
};
const statusIconMap: ReadyStateMap = {
[ReadyState.CONNECTING]: "🔴",
[ReadyState.OPEN]: "🟢",
[ReadyState.CLOSING]: "🔴",
[ReadyState.CLOSED]: "🔴",
};
const statusTextMap: ReadyStateMap = {
[ReadyState.CONNECTING]: "Connecting...",
[ReadyState.OPEN]: "Connected",
[ReadyState.CLOSING]: "Closing...",
[ReadyState.CLOSED]: "Closed",
};
export default function App() {
const { darkMode, setSetting } = useContext(SettingsContext);
const { config, setSelectedCall, readyState, clear } =
useContext(ConnectionContext);
useEffect(() => {
const url = new URL(config?.url ?? "https://loading...");
document.title = `${statusIconMap[readyState]} ${url.host} | TTUN`;
}, [readyState, config?.url]);
const settingsMenu: (SettingsMenu | null)[] = [
{
onClick: () => setSetting("darkMode", !darkMode),
icon: darkMode ? <Sun /> : <Moon />,
label: darkMode ? "Light mode" : "Dark mode",
},
null,
{
onClick: () => {
setSelectedCall(null);
clear();
},
icon: <Trash />,
label: "Clear",
},
];
return (
config && (
<div className={styles.app}>
<Navbar bg="dark" variant="dark" expand as="header">
<Container fluid>
<div>
<Navbar.Brand>TTUN</Navbar.Brand>
<Navbar.Text>
{`${statusIconMap[readyState]} ${statusTextMap[readyState]}`}
</Navbar.Text>
</div>
<div className="d-flex">
<Navbar.Text>
<a href={config.url} target="_blank">
{config.url}
</a>
</Navbar.Text>
<Navbar.Toggle aria-controls="settings" />
<Navbar.Collapse id="settings" className="ms-2" role="button">
<Nav>
<NavDropdown align="end" title={<Sliders />}>
{settingsMenu.map((item, index) => {
if (item !== null) {
const { onClick, icon, label } = item;
return (
<NavDropdown.Item
key={label}
onClick={onClick}
className="d-flex align-items-center"
>
{icon}
<span className="ms-3">{label}</span>
</NavDropdown.Item>
);
} else {
return <NavDropdown.Divider key={`item-${index}`} />;
}
})}
</NavDropdown>
</Nav>
</Navbar.Collapse>
</div>
</Container>
</Navbar>
<main className={styles.main}>
<div className={classNames("border-end", styles.sidebar)}>
<RequestList />
</div>
<div className={styles.details}>
<RequestDetails />
</div>
</main>
</div>
)
);
}
|