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