Skip to content

Commit 6d36167

Browse files
WIP: admin chat [ci skip]
1 parent c459bf6 commit 6d36167

5 files changed

Lines changed: 388 additions & 17 deletions

File tree

src/App.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,19 @@ tr.offline {
183183
outline: none;
184184
}
185185

186+
.button:disabled {
187+
cursor: not-allowed;
188+
opacity: 50%;
189+
}
190+
191+
.button:hover:disabled {
192+
filter: none;
193+
}
194+
195+
.button:active:disabled {
196+
transform: none;
197+
}
198+
186199
.button.danger {
187200
background: var(--color-danger);
188201
}

src/api/index.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,19 @@ async function doSend(path, method, body, isText = false) {
2828
body: JSON.stringify(body)
2929
});
3030

31-
if(response.status === 400) {
32-
const body = await response.json();
33-
if(body.message !== undefined)
34-
throw new Error(body.message);
31+
if(response.status >= 400 && response.status < 500) {
32+
let message;
33+
try {
34+
const body = await response.json();
35+
if(body.message !== undefined) {
36+
message = body.message;
37+
}
38+
} catch(e) {
39+
// Nothing.
40+
}
41+
if(message !== undefined) {
42+
throw new Error(message);
43+
}
3544
}
3645

3746
if(!response.ok) {
@@ -49,9 +58,27 @@ async function doDelete(path) {
4958
'X-Requested-With': 'XMLHttpRequest'
5059
},
5160
});
61+
62+
if(response.status >= 400 && response.status < 500) {
63+
let message;
64+
try {
65+
const body = await response.json();
66+
if(body.message !== undefined) {
67+
message = body.message;
68+
}
69+
} catch(e) {
70+
// Nothing.
71+
}
72+
if(message !== undefined) {
73+
throw new Error(message);
74+
}
75+
}
76+
5277
if(!response.ok) {
5378
throw new Error(response.statusText);
5479
}
80+
81+
return await response.json();
5582
}
5683

5784
export async function tryLogin(username, password) {
@@ -97,6 +124,11 @@ export function kickUserByUid(uid) { return doDelete(`/users/${uid}`); }
97124
export function changeUser(sessionId, userId, changes) { return doSend(`/sessions/${sessionId}/${userId}`, 'PUT', changes); }
98125
export function kickUserFromSession(sessionId, userId) { return doDelete(`/sessions/${sessionId}/${userId}`); }
99126

127+
export function connectChat(sessionId, message) { return doSend(`/sessions/${encodeURIComponent(sessionId)}/chat`, 'POST', {message}); }
128+
export function disconnectChat(sessionId, message) { return doDelete(`/sessions/${encodeURIComponent(sessionId)}/chat?message=${encodeURIComponent(message)}`); }
129+
export function sendChatMessage(sessionId, message, offset) { return doSend(`/sessions/${encodeURIComponent(sessionId)}/chat`, 'PUT', {message, offset}); }
130+
export function getChatMessages(sessionId, offset) { return doGet(`/sessions/${encodeURIComponent(sessionId)}/chat?offset=${encodeURIComponent(`${offset}`)}`); }
131+
100132
export function getAccounts() { return doGet('/accounts/'); }
101133
export function createAccount(account) { return doSend('/accounts/', 'POST', account); }
102134
export function changeAccount(accountId, changes) { return doSend(`/accounts/${accountId}`, 'PUT', changes); }

src/components/form.css

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
width: 90%;
4848
}
4949

50+
.input-text.fullwidth, .input-readonly.fullwidth {
51+
width: 100%;
52+
}
53+
5054
.input-text[type=number] {
5155
text-align: right;
5256
width: 4em;
@@ -56,3 +60,40 @@
5660
display: block;
5761
}
5862

63+
.chat {
64+
background-color: #eff0f1;
65+
border: 1px solid #7f8c8d;
66+
height: 20em;
67+
margin-bottom: 0.5em;
68+
overflow-y: scroll;
69+
padding: 0.5em;
70+
}
71+
72+
.chat-message {
73+
padding-top: 0.5em;
74+
padding-bottom: 0.5em;
75+
white-space: pre-wrap;
76+
}
77+
78+
.chat-message-admin {
79+
font-style: italic;
80+
}
81+
82+
.chat-message-admin > .chat-message-sender {
83+
text-decoration: underline;
84+
}
85+
86+
.chat-message-user > .chat-message-sender {
87+
font-weight: bold;
88+
}
89+
90+
.chat-grid {
91+
display: grid;
92+
grid-template-columns: auto minmax(4em, 10%);
93+
grid-gap: 0.5em;
94+
}
95+
96+
.chat-grid > button.small {
97+
margin: 0;
98+
height: 100%;
99+
}

src/pages/sessions/modals.js

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import React, { useState, useContext } from 'react';
22
import { useHistory } from 'react-router-dom';
33

4-
import { changeSessions, changeSession, terminateSession, changeUser, kickUserFromSession } from '../../api';
4+
import { changeSessions, changeSession, terminateSession, changeUser, kickUserFromSession, connectChat, disconnectChat } from '../../api';
55

66
/** Modal building blocks */
77
const ModalContext = React.createContext({});
88

99
const ModalHeader = ({children}) => <h2>{children}</h2>
1010
const ModalButtons = ({children}) => <p>{children}</p>
1111

12-
const OkButton = ({label, func, children}) => {
12+
const OkButton = ({label, func, errorFunc, disabled}) => {
1313
const ctx = useContext(ModalContext);
1414
function clickHandler() {
1515
// TODO disable button while processing
16-
func().then(ctx.closeFunc);
16+
func().then(ctx.closeFunc).catch(err => {
17+
console.error("Error", err);
18+
if(errorFunc) {
19+
errorFunc(err);
20+
}
21+
});
1722
}
18-
return <button onClick={clickHandler} className="danger button">{label}</button>
23+
return <button onClick={clickHandler} className="danger button" disabled={!!disabled}>{label}</button>
1924
}
2025

2126
const CancelButton = ({label="Cancel"}) => {
@@ -36,7 +41,7 @@ function SetPasswordModal({targetSetting, title}) {
3641
return <>
3742
<ModalHeader>Set session {title}</ModalHeader>
3843
<input
39-
type="password"
44+
type="password"
4045
className="input-text"
4146
onChange={e => setPasswd(e.target.value)}
4247
/>
@@ -114,6 +119,64 @@ function MessageModal() {
114119
</>
115120
}
116121

122+
function ChatConnectModal() {
123+
const [message, setMessage] = useState('');
124+
const [error, setError] = useState('');
125+
const ctx = useContext(ModalContext);
126+
127+
function connect() {
128+
setError('');
129+
return connectChat(ctx.sessionId, message);
130+
}
131+
132+
function catchError(err) {
133+
setError(`${err}`.replace(/^Error:\s*/, ''));
134+
}
135+
136+
return <>
137+
<ModalHeader>Initial message (required):</ModalHeader>
138+
{error && <p><strong>Error:</strong> {error}</p>}
139+
<textarea
140+
className="input-text message-area"
141+
rows="5"
142+
onChange={e => setMessage(e.target.value)}
143+
/>
144+
<ModalButtons>
145+
<OkButton func={connect} errorFunc={catchError} label="Connect" disabled={message.trim() === ''} />
146+
<CancelButton />
147+
</ModalButtons>
148+
</>
149+
}
150+
151+
function ChatDisconnectModal() {
152+
const [message, setMessage] = useState('');
153+
const [error, setError] = useState('');
154+
const ctx = useContext(ModalContext);
155+
156+
function disconnect() {
157+
setError('');
158+
return disconnectChat(ctx.sessionId, message);
159+
}
160+
161+
function catchError(err) {
162+
setError(`${err}`.replace(/^Error:\s*/, ''));
163+
}
164+
165+
return <>
166+
<ModalHeader>Disconnect message (optional):</ModalHeader>
167+
{error && <p><strong>Error:</strong> {error}</p>}
168+
<textarea
169+
className="input-text message-area"
170+
rows="5"
171+
onChange={e => setMessage(e.target.value)}
172+
/>
173+
<ModalButtons>
174+
<OkButton func={disconnect} errorFunc={catchError} label="Disconnect" />
175+
<CancelButton />
176+
</ModalButtons>
177+
</>
178+
}
179+
117180
export function ModalContent({modal, closeFunc}) {
118181
let m;
119182
switch(modal.active) {
@@ -122,6 +185,8 @@ export function ModalContent({modal, closeFunc}) {
122185
case 'terminate': m = <TerminateSessionModal />; break;
123186
case 'message': m = <MessageModal />; break;
124187
case 'kick': m = <KickUserModal />; break;
188+
case 'chatConnect': m = <ChatConnectModal />; break;
189+
case 'chatDisconnect': m = <ChatDisconnectModal />; break;
125190
default: return null;
126191
}
127192

0 commit comments

Comments
 (0)