Browse Source

Implement simple chat UI.

main
Titouan Rigoudy 4 years ago
parent
commit
9b27c59d58
10 changed files with 229 additions and 249 deletions
  1. +0
    -75
      src/components/RoomChat.js
  2. +38
    -0
      src/components/RoomChat.tsx
  3. +0
    -41
      src/components/RoomChatForm.js
  4. +53
    -0
      src/components/RoomChatForm.tsx
  5. +0
    -48
      src/components/RoomChatHeader.js
  6. +66
    -0
      src/components/RoomChatHeader.tsx
  7. +0
    -39
      src/components/RoomChatMessageList.js
  8. +35
    -0
      src/components/RoomChatMessageList.tsx
  9. +29
    -38
      src/containers/RoomsPane.tsx
  10. +8
    -8
      src/modules/room/slice.ts

+ 0
- 75
src/components/RoomChat.js View File

@ -1,75 +0,0 @@
import React, { PropTypes } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import RoomActions from "../actions/RoomActions";
import RoomChatForm from "../components/RoomChatForm";
import RoomChatHeader from "../components/RoomChatHeader";
import RoomChatMessageList from "../components/RoomChatMessageList";
const ID = "room-chat";
class RoomChat extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.join_if_non_member(this.props);
}
componentWillReceiveProps(props) {
this.join_if_non_member(props);
}
join_if_non_member(props) {
const { room, roomActions } = props;
if (room && room.membership == "NonMember") {
roomActions.join(room.name);
}
}
render() {
const { loginUserName, room, roomActions } = this.props;
if (!room) {
return (
<div id={ID}>
<RoomChatHeader roomActions={roomActions} />
</div>
);
}
const { name, membership, messages, showUsers } = room;
const header = (
<RoomChatHeader
room={{
membership,
name,
showUsers,
}}
roomActions={roomActions}
/>
);
if (membership != "Member") {
return <div id={ID}>{header}</div>;
}
// room.membership == "Member"
return (
<div id={ID}>
{header}
<RoomChatMessageList
loginUserName={loginUserName}
messages={messages}
/>
<RoomChatForm roomName={name} sendMessage={roomActions.sendMessage} />
</div>
);
}
}
export default RoomChat;

+ 38
- 0
src/components/RoomChat.tsx View File

@ -0,0 +1,38 @@
import { FC, useEffect } from "react";
import { useDispatch } from "react-redux";
import { Room, RoomMembership, roomSetMembership } from "../modules/room/slice";
import RoomChatForm from "../components/RoomChatForm";
import RoomChatHeader from "../components/RoomChatHeader";
import RoomChatMessageList from "../components/RoomChatMessageList";
interface Props {
loginUserName: string;
room?: Room;
}
const RoomChat: FC<Props> = ({ loginUserName, room }) => {
const dispatch = useDispatch();
useEffect(() => {
if (room !== undefined && room.membership === RoomMembership.Left) {
dispatch(roomSetMembership([room.name, RoomMembership.Joining]));
}
});
const header = <RoomChatHeader room={room} />;
if (room === undefined || room.membership !== RoomMembership.Joined) {
return <div id="room-chat">{header}</div>;
}
const { name, messages } = room;
return (
<div id="room-chat">
{header}
<RoomChatMessageList loginUserName={loginUserName} messages={messages} />
<RoomChatForm loginUserName={loginUserName} roomName={name} />
</div>
);
};
export default RoomChat;

+ 0
- 41
src/components/RoomChatForm.js View File

@ -1,41 +0,0 @@
import React, { PropTypes } from "react";
import { reduxForm } from "redux-form";
const RoomChatForm = (props) => {
const {
fields: { message },
handleSubmit,
resetForm,
roomName,
sendMessage,
} = props;
const onSubmit = handleSubmit((values) => {
sendMessage(roomName, values.message);
resetForm();
});
return (
<div id="room-chat-form">
<form onSubmit={onSubmit}>
<input type="text" placeholder="Type a message..." {...message} />
<button type="submit">Send</button>
</form>
</div>
);
};
RoomChatForm.propTypes = {
fields: PropTypes.shape({
message: PropTypes.object.isRequired,
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
roomName: PropTypes.string,
sendMessage: PropTypes.func.isRequired,
};
export default reduxForm({
form: "chat",
fields: ["message"],
})(RoomChatForm);

+ 53
- 0
src/components/RoomChatForm.tsx View File

@ -0,0 +1,53 @@
import { FC } from "react";
import { Form, Field } from "react-final-form";
import { useDispatch } from "react-redux";
import { roomMessage } from "../modules/room/slice";
interface Props {
roomName: string;
loginUserName: string;
}
interface Fields {
message: string;
}
const RoomChatForm: FC<Props> = ({ roomName, loginUserName }) => {
const dispatch = useDispatch();
const onSubmit = ({ message }: Fields) => {
dispatch(
roomMessage({
roomName,
userName: loginUserName,
message,
})
);
// TODO: reset form.
};
return (
<div id="room-chat-form">
<Form
onSubmit={onSubmit}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Field
name="message"
component="input"
type="text"
placeholder="Type a message..."
required
/>
<button type="submit" disabled={submitting}>
Send
</button>
</form>
)}
/>
</div>
);
};
export default RoomChatForm;

+ 0
- 48
src/components/RoomChatHeader.js View File

@ -1,48 +0,0 @@
import React, { PropTypes } from "react";
import { withRouter } from "react-router";
const make_header = (title, showUsersButton, leaveButton) => (
<div id="room-chat-header">
<div id="room-chat-header-title">{title}</div>
{showUsersButton}
{leaveButton}
</div>
);
const RoomChatHeader = ({ room, roomActions, router }) => {
if (!room) {
return make_header("Select a room");
}
switch (room.membership) {
case "Member": {
const onClickLeave = (event) => {
router.push("/app/rooms");
roomActions.leave(room.name);
};
const leaveButton = <button onClick={onClickLeave}>Leave</button>;
let toggleUsersButton;
if (room.showUsers) {
const onClick = (event) => roomActions.hideUsers(room.name);
toggleUsersButton = <button onClick={onClick}>Hide users</button>;
} else {
const onClick = (event) => roomActions.showUsers(room.name);
toggleUsersButton = <button onClick={onClick}>Show users</button>;
}
return make_header(room.name, toggleUsersButton, leaveButton);
}
case "NonMember":
return make_header(`Not a member of ${room.name}`);
case "Joining":
return make_header(`Joining ${room.name}`);
case "Leaving":
return make_header(`Leaving ${room.name}`);
}
};
export default withRouter(RoomChatHeader);

+ 66
- 0
src/components/RoomChatHeader.tsx View File

@ -0,0 +1,66 @@
import { FC, ReactEventHandler } from "react";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router";
import { Room, RoomMembership, roomSetMembership } from "../modules/room/slice";
interface InnerProps {
title: string;
}
const InnerHeader: FC<InnerProps> = ({ title, children }) => (
<div id="room-chat-header">
<div id="room-chat-header-title">{title}</div>
{children}
</div>
);
interface Props {
room?: Room;
}
const RoomChatHeader: FC<Props> = ({ room }) => {
const history = useHistory();
const dispatch = useDispatch();
if (room === undefined) {
return <InnerHeader title="Select a room" />;
}
switch (room.membership) {
case RoomMembership.Joined: {
const onClickLeave: ReactEventHandler = (event) => {
history.push("/rooms");
dispatch(roomSetMembership([room.name, RoomMembership.Leaving]));
};
/*
let toggleUsersButton;
if (room.showUsers) {
const onClick = (event) => roomActions.hideUsers(room.name);
toggleUsersButton = <button onClick={onClick}>Hide users</button>;
} else {
const onClick = (event) => roomActions.showUsers(room.name);
toggleUsersButton = <button onClick={onClick}>Show users</button>;
}
*/
return (
<InnerHeader title={room.name}>
<button onClick={onClickLeave}>Leave</button>
</InnerHeader>
);
}
case RoomMembership.Left:
return <InnerHeader title={`Not a member of ${room.name}`} />;
case RoomMembership.Joining:
return <InnerHeader title={`Joining ${room.name}`} />;
case RoomMembership.Leaving:
return <InnerHeader title={`Leaving ${room.name}`} />;
}
};
export default RoomChatHeader;

+ 0
- 39
src/components/RoomChatMessageList.js View File

@ -1,39 +0,0 @@
import React, { PropTypes } from "react";
import ImmutablePropTypes from "react-immutable-proptypes";
const RoomChatMessageList = ({ loginUserName, messages }) => {
// Append all messages in the chat room.
const children = [];
let i = 0;
for (const { user_name, message } of messages) {
if (user_name == loginUserName) {
children.push(
<li key={i} className="room-chat-message room-chat-message-me">
<div className="room-chat-message-text">{message}</div>
</li>
);
} else {
children.push(
<li key={i} className="room-chat-message">
<div className="room-chat-message-user">{user_name}</div>
<div className="room-chat-message-text">{message}</div>
</li>
);
}
i++;
}
return <ul id="room-chat-message-list">{children}</ul>;
};
RoomChatMessageList.propTypes = {
loginUserName: PropTypes.string.isRequired,
messages: ImmutablePropTypes.listOf(
PropTypes.shape({
user_name: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
}).isRequired
).isRequired,
};
export default RoomChatMessageList;

+ 35
- 0
src/components/RoomChatMessageList.tsx View File

@ -0,0 +1,35 @@
import { FC } from "react";
import { RoomMessage } from "../modules/room/slice";
interface Props {
loginUserName: string;
messages: RoomMessage[];
}
const RoomChatMessageList: FC<Props> = ({ loginUserName, messages }) => {
// Append all messages in the chat room.
const children = [];
let i = 0;
for (const { userName, message } of messages) {
if (userName === loginUserName) {
children.push(
<li key={i} className="room-chat-message room-chat-message-me">
<div className="room-chat-message-text">{message}</div>
</li>
);
} else {
children.push(
<li key={i} className="room-chat-message">
<div className="room-chat-message-user">{userName}</div>
<div className="room-chat-message-text">{message}</div>
</li>
);
}
i++;
}
return <ul id="room-chat-message-list">{children}</ul>;
};
export default RoomChatMessageList;

+ 29
- 38
src/containers/RoomsPane.tsx View File

@ -1,54 +1,45 @@
import { FC } from "react";
import { useSelector } from "react-redux";
import { useRouteMatch } from "react-router";
import { useParams, useRouteMatch } from "react-router";
import { Switch, Route } from "react-router-dom";
//import RoomChat from "../components/RoomChat";
import RoomChat from "../components/RoomChat";
import RoomList from "../components/RoomList";
import { selectAllRooms } from "../modules/room/slice";
import { selectLogin } from "../modules/login/slice";
import { RoomMap, selectAllRooms } from "../modules/room/slice";
const RoomsPane: FC = () => {
const { path, url } = useRouteMatch();
const rooms = useSelector(selectAllRooms);
let roomChat;
/*
if (params && params.roomNameHash) {
const roomName = rooms.getNameByHash(params.roomNameHash);
interface ChatProps {
loginUserName: string;
rooms: RoomMap;
}
const roomData = rooms.getByName(roomName);
interface UrlParams {
roomId: string;
}
if (roomData) {
const room = {
name: roomName,
membership: roomData.membership,
messages: roomData.messages,
showUsers: roomData.showUsers,
};
const RoomChatPane: FC<ChatProps> = ({ loginUserName, rooms }) => {
const { roomId } = useParams<UrlParams>();
return <RoomChat loginUserName={loginUserName} room={rooms[roomId]} />;
};
roomChat = (
<RoomChat
loginUserName={loginUserName}
room={room}
roomActions={roomActions}
/>
);
}
}
*/
const RoomsPane: FC<{}> = () => {
const { path } = useRouteMatch();
const login = useSelector(selectLogin);
const rooms = useSelector(selectAllRooms);
return (
<div id="rooms-pane">
<RoomList rooms={rooms} />
<Switch>
<Route exact path={path}>
<div id="room-selected-pane">Pick a room.</div>
</Route>
<Route path={`${path}/:roomId`}>
<div id="room-selected-pane">{roomChat}</div>
</Route>
</Switch>
<div id="room-selected-pane">
<Switch>
<Route exact path={path}>
<RoomChatPane rooms={rooms} loginUserName={login.username!} />
</Route>
<Route path={`${path}/:roomId`}>
<RoomChatPane rooms={rooms} loginUserName={login.username!} />
</Route>
</Switch>
</div>
</div>
);
};


+ 8
- 8
src/modules/room/slice.ts View File

@ -10,7 +10,7 @@ export enum RoomMembership {
}
export interface RoomMessage {
user_name: string;
userName: string;
message: string;
}
@ -68,8 +68,8 @@ const initialState: RoomSliceState = {
};
export interface RoomMessagePayload {
room_name: string;
user_name: string;
roomName: string;
userName: string;
message: string;
}
@ -94,17 +94,17 @@ export const roomSlice = createSlice({
state: RoomSliceState,
action: PayloadAction<RoomMessagePayload>
) => {
const { room_name, user_name, message } = action.payload;
const room = state.rooms[room_name];
const { roomName, userName, message } = action.payload;
const room = state.rooms[roomName];
if (room === undefined) {
console.log(
`Unknown room ${room_name} received message from ` +
`${user_name}: ${message}`
`Unknown room ${roomName} received message from ` +
`${userName}: ${message}`
);
return;
}
room.messages.push({ user_name, message });
room.messages.push({ userName, message });
},
roomSetAll: (state: RoomSliceState, action: PayloadAction<Room[]>) => {
state.rooms = {};


Loading…
Cancel
Save