Skip to content

Commit

Permalink
Make it less convinient to swap users
Browse files Browse the repository at this point in the history
  • Loading branch information
jder committed Feb 8, 2020
1 parent dab3492 commit 2a39ad0
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 108 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"autoscroll-react": "^3.2.0",
"http-proxy-middleware": "^0.20.0",
"react": "^16.12.0",
"react-cookie": "^4.0.3",
"react-dom": "^16.12.0",
"react-scripts": "3.3.1",
"typescript": "~3.7.2",
Expand Down
2 changes: 1 addition & 1 deletion client/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
margin: 100px auto;
}

.ChatPane {
.ChatHistory {
background-color: lightblue;
padding: 10px;
overflow-y: scroll;
Expand Down
49 changes: 21 additions & 28 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import './App.css';
import ChatPane from './ChatPane';
import { ServerMessage } from './Messages';
import { ChatSocket } from './ChatSocket';
import uuid from 'uuid/v4';
import { withCookies, useCookies } from 'react-cookie';

const App = () => {
const [text, setText] = useState("");
const [messages, setMessages] = useState([] as ServerMessage[]);
const [socket, setSocket] = useState(null as ChatSocket | null);

useEffect(() => {
const loc = document.location;
const protocol = loc.protocol === 'https' ? 'wss' : 'ws';
let s = new ChatSocket(`${protocol}://${loc.host}/api/socket`);
s.onmessage = (message: ServerMessage) => {
setMessages(prev => prev.concat([message]));
};
setSocket(s);
}, [])

const [cookies, setCookie] = useCookies(['username']);
const [newUsername, setNewUsername] = useState("");

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();

socket!.send({text: text, id: uuid()});
setText("");
setCookie('username', newUsername, { path: '/' });
}

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value);
setNewUsername(event.target.value);
event.preventDefault();
}

function body(username: string | undefined) {
if (username) {
return <ChatPane username={username} />
} else {
return <form onSubmit={handleSubmit}>
<label htmlFor="username">Username: </label>
<input name="username" autoFocus type="text" value={newUsername} onChange={handleChange} placeholder="frank" />
<input type="submit" value="Login" />
</form>
}
}

return (
<div className="App">
<ChatPane messages={messages} />
<form onSubmit={handleSubmit}>
<input className="mainInput" autoFocus type="text" value={text} onChange={handleChange} />
<input type="submit" disabled={!socket} value="Send" />
</form>
{body(cookies.username)}
</div>
);
}

export default App;
export default withCookies(App);
20 changes: 20 additions & 0 deletions client/src/ChatHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useEffect, useLayoutEffect } from 'react';
import autoscroll from 'autoscroll-react';

import { ChatRowContent } from './Messages';
import ChatHistoryRow from './ChatHistoryRow';

class ChatHistory extends React.Component<{rows: ChatRowContent[]}> {

render() {
const { rows, ...props } = this.props;

return (
<div className="ChatHistory" { ...props } >
{rows.map((m) => <ChatHistoryRow content={m} key={m.id} />)}
</div>
);
}
}

export default autoscroll(ChatHistory, {isScrolledDownThreshold: 10});
13 changes: 13 additions & 0 deletions client/src/ChatHistoryRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { } from 'react';
import { ChatRowContent } from './Messages';

const ChatHistoryRow = (props: { content: ChatRowContent }) => {

return (
<div className="ChatHistoryRow">
{props.content.text}
</div>
);
}

export default ChatHistoryRow;
60 changes: 47 additions & 13 deletions client/src/ChatPane.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,54 @@
import React, { useEffect, useLayoutEffect } from 'react';
import autoscroll from 'autoscroll-react';
import React, { useState, useEffect } from 'react';
import ChatHistory from './ChatHistory';
import { ToClientMessage, isTellMessage, isBacklogMessage, ChatRowContent, SayMessage } from './Messages';
import { ChatSocket } from './ChatSocket';
import uuid from 'uuid/v4';

import { ServerMessage } from './Messages';
import ChatPaneMessage from './ChatPaneMessage';
const ChatPane = (props: {username: string}) => {
const [text, setText] = useState("");
const [rows, setRows] = useState([] as ChatRowContent[]);
const [socket, setSocket] = useState(null as ChatSocket | null);

class ChatPane extends React.Component<{messages: ServerMessage[]}> {
useEffect(() => {
const loc = document.location;
const protocol = loc.protocol === 'https' ? 'wss' : 'ws';
let s = new ChatSocket(`${protocol}://${loc.host}/api/socket`, props.username);
s.onmessage = (message: ToClientMessage) => {
setRows((prev) => {
if (isTellMessage(message)) {
return prev.concat([message.content]);
} else if (isBacklogMessage(message)) {
return message.history;
} else {
console.error("Unrecognized message", message);
return prev;
}
});
};
setSocket(s);
}, [])

render() {
const { messages, ...props } = this.props;
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();

return (
<div className="ChatPane" { ...props } >
{messages.map((m) => <ChatPaneMessage message={m} key={m.id} />)}
</div>
);
socket!.send(new SayMessage(text));
setText("");
}

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value);
event.preventDefault();
}

return (
<div className="ChatPane">
<ChatHistory rows={rows} />
<form onSubmit={handleSubmit}>
<input className="mainInput" autoFocus type="text" value={text} onChange={handleChange} />
<input type="submit" disabled={!socket} value="Send" />
</form>
</div>
);
}

export default autoscroll(ChatPane, {isScrolledDownThreshold: 10});
export default ChatPane;
13 changes: 0 additions & 13 deletions client/src/ChatPaneMessage.tsx

This file was deleted.

16 changes: 9 additions & 7 deletions client/src/ChatSocket.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ServerMessage, ClientMessage } from './Messages';
import { ToServerMessage, ToClientMessage, LoginMessage } from './Messages';

export class ChatSocket {
private url: string;
private ws: WebSocket;
private username: String;
private username: string;

onmessage?: (message: ServerMessage) => void;
buffer: ClientMessage[] = [];
onmessage?: (message: ToClientMessage) => void;
buffer: ToServerMessage[] = [];

constructor(url: string, username: String) {
constructor(url: string, username: string) {
this.url = url;
this.ws = this.connect();
this.username = username;
Expand All @@ -20,7 +20,7 @@ export class ChatSocket {
if (this.onmessage) {
const parsed = JSON.parse(event.data);
console.log("got message", parsed);
this.onmessage(parsed as ServerMessage);
this.onmessage(parsed as ToClientMessage);
}
}
this.ws.onerror = (event: Event) => {
Expand All @@ -32,6 +32,8 @@ export class ChatSocket {
this.ws.onopen = (event: Event) => {
console.log("websocket open");

this.send(new LoginMessage(this.username));

const toSend = this.buffer;
this.buffer = [];

Expand All @@ -42,7 +44,7 @@ export class ChatSocket {
return this.ws;
}

send(message: ClientMessage) {
send(message: ToServerMessage) {
if (this.ws.readyState !== WebSocket.OPEN) {
console.warn("Tried to send message when websocket is closed; buffering");
this.buffer.push(message);
Expand Down
46 changes: 21 additions & 25 deletions client/src/Messages.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,49 @@
// From server
export class ToServerMessage {
type: String;
type: string;

constructor(type: String) {
constructor(type: string) {
this.type = type;
}
}

export class LoginMessage extends ToServerMessage {
username: String;
username: string;

constructor(username: String) {
constructor(username: string) {
super("Login")
this.username = username;
}
}

export class SayMessage extends ToServerMessage {
text: String;
text: string;

constructor(text: String) {
constructor(text: string) {
super("Say")
this.text = text;
}
}

export class ToClientMessage {
type: String;

constructor(type: String) {
this.type = type;
}
}
export type ToClientMessage = { type: string; };
export type TellMessage = {type: string, content: ChatRowContent};
export type BacklogMessage = {type: string, history: [ChatRowContent] };

export class TellMessage extends ToClientMessage {
text: String;
export function isTellMessage(m: ToClientMessage): m is TellMessage {
return m.type == "Tell";
}

constructor(text: String) {
super("Tell");
this.text = text;
}
export function isBacklogMessage(m: ToClientMessage): m is BacklogMessage {
return m.type == "Backlog";
}

export class BacklogMessage extends ToClientMessage {
history: [String];
// A row to display. Someday could include HTML, actions, styling, etc.
export class ChatRowContent {
id: string;
text: string;

constructor(history: [String]) {
super("Backlog");
this.history = history;
constructor(id: string, text: string) {
this.id = id;
this.text = text;
}

}
Loading

0 comments on commit 2a39ad0

Please sign in to comment.