| @ -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; | |||
| @ -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; | |||
| @ -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); | |||
| @ -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; | |||
| @ -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); | |||
| @ -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; | |||
| @ -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; | |||
| @ -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; | |||