From 6df7f6864db9cdf4c5bc6e953889197a68be2986 Mon Sep 17 00:00:00 2001 From: Titouan Rigoudy Date: Mon, 23 May 2016 13:42:39 +0200 Subject: [PATCH] Huge move to react-router. --- src/components/LoginStatusPane.js | 105 +++++++++----------------- src/components/Room.js | 18 +++-- src/components/RoomChat.js | 19 +++-- src/components/RoomChatMessageList.js | 6 +- src/components/RoomList.js | 1 - src/components/SolsticeApp.js | 50 +++--------- src/containers/App.js | 38 ---------- src/containers/ConnectPage.js | 95 +++++++++++++++++++++++ src/containers/Footer.js | 5 +- src/containers/RoomsPane.js | 60 +++++++++------ src/createRoutes.js | 33 ++++++++ src/index.js | 10 ++- src/reducers/rooms.js | 39 ++++------ 13 files changed, 262 insertions(+), 217 deletions(-) delete mode 100644 src/containers/App.js create mode 100644 src/containers/ConnectPage.js create mode 100644 src/createRoutes.js diff --git a/src/components/LoginStatusPane.js b/src/components/LoginStatusPane.js index a748aee..73b4450 100644 --- a/src/components/LoginStatusPane.js +++ b/src/components/LoginStatusPane.js @@ -10,97 +10,66 @@ import { LOGIN_STATUS_FAILURE } from "../constants/login"; -const ID = "login-status-pane"; - class LoginStatusPane extends React.Component { constructor(props) { super(props); } - componentDidMount() { - const { status, loginActions } = this.props; - if (status == LOGIN_STATUS_UNKNOWN) { - loginActions.getStatus(); - } - } + render() { + let statusText; + let motd; + let reason; - render_unknown() { - return ( -
- Login status: unknown -
- ); - } + switch (this.props.status) { + case LOGIN_STATUS_UNKNOWN: + statusText = "unknown"; + break; - render_getting() { - return ( -
- Login status: fetching... -
- ); - } + case LOGIN_STATUS_GETTING: + statusText = "fetching"; + break; - render_pending() { - return ( -
- Logging in as {this.props.username}... -
- ); - } + case LOGIN_STATUS_PENDING: + statusText = `logging in as ${this.props.username}`; + break; - render_success() { - let motd_element; - if (this.props.motd) { - motd_element = ( - - MOTD: {this.props.motd} - - ); + case LOGIN_STATUS_SUCCESS: + statusText = `logged in as ${this.props.username}`; + motd = ( + + MOTD: {this.props.motd} + + ); + break; + + case LOGIN_STATUS_FAILURE: + statusText = `failed to log in as ${this.props.username}`; + reason = ( + + Reason: {this.props.reason} + + ); + break; } - return ( -
- Logged in as {this.props.username} - {motd_element} -
- ); - } - render_failure() { return ( -
- Failed to log in as {this.props.username} - - Reason: {this.props.reason} +
+ + Login status: {statusText} + {motd} + {reason}
); } - - render() { - switch (this.props.status) { - case LOGIN_STATUS_UNKNOWN: - return this.render_unknown(); - case LOGIN_STATUS_GETTING: - return this.render_getting(); - case LOGIN_STATUS_PENDING: - return this.render_pending(); - case LOGIN_STATUS_SUCCESS: - return this.render_success(); - case LOGIN_STATUS_FAILURE: - return this.render_failure(); - } - } } LoginStatusPane.propTypes = { status: propTypeSymbol.isRequired, username: PropTypes.string, motd: PropTypes.string, - reason: PropTypes.string, - loginActions: PropTypes.shape({ - getStatus: PropTypes.func.isRequired - }).isRequired + reason: PropTypes.string }; export default LoginStatusPane; diff --git a/src/components/Room.js b/src/components/Room.js index db17a59..b30d3c4 100644 --- a/src/components/Room.js +++ b/src/components/Room.js @@ -1,23 +1,27 @@ import React, { PropTypes } from "react"; +import { Link } from "react-router"; -const Room = ({ isSelected, name, onClick, room }) => { +const Room = ({ name, onClick, room }) => { const classes = ["room"]; - if (isSelected) { - classes.push("room-selected"); - } if (room.membership == "Member") { classes.push("room-joined"); } + + const base64Name = btoa(encodeURIComponent(name)); + const path = `/app/rooms/${base64Name}`; + return ( - + {name} ({room.user_count}) - + ); }; Room.propTypes = { - isSelected: PropTypes.bool.isRequired, name: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, room: PropTypes.shape({ diff --git a/src/components/RoomChat.js b/src/components/RoomChat.js index 63e3a0f..e062dcf 100644 --- a/src/components/RoomChat.js +++ b/src/components/RoomChat.js @@ -1,9 +1,13 @@ import React, { PropTypes } from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; import ImmutablePropTypes from "react-immutable-proptypes"; -import RoomChatForm from "./RoomChatForm"; -import RoomChatHeader from "./RoomChatHeader"; -import RoomChatMessageList from "./RoomChatMessageList"; +import RoomActions from "../actions/RoomActions"; + +import RoomChatForm from "../components/RoomChatForm"; +import RoomChatHeader from "../components/RoomChatHeader"; +import RoomChatMessageList from "../components/RoomChatMessageList"; const ID = "room-chat"; @@ -28,7 +32,7 @@ class RoomChat extends React.Component { } render() { - const { login_user_name, room, roomActions } = this.props; + const { loginUserName, room, roomActions } = this.props; if (!room) { return ( @@ -66,7 +70,7 @@ class RoomChat extends React.Component {
{header} { +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 == login_user_name) { + if (user_name == loginUserName) { children.push(
  • {message}
    @@ -27,7 +27,7 @@ const RoomChatMessageList = ({ login_user_name, messages }) => { }; RoomChatMessageList.propTypes = { - login_user_name: PropTypes.string.isRequired, + loginUserName: PropTypes.string.isRequired, messages: ImmutablePropTypes.listOf( PropTypes.shape({ user_name: PropTypes.string.isRequired, diff --git a/src/components/RoomList.js b/src/components/RoomList.js index 64b0970..45dadf0 100644 --- a/src/components/RoomList.js +++ b/src/components/RoomList.js @@ -28,7 +28,6 @@ class RoomList extends React.Component { onClick={onClick} name={room_name} room={room_data} - isSelected={selected == room_name} />
  • ); diff --git a/src/components/SolsticeApp.js b/src/components/SolsticeApp.js index b23378d..f0d2334 100644 --- a/src/components/SolsticeApp.js +++ b/src/components/SolsticeApp.js @@ -1,51 +1,21 @@ import React, {PropTypes} from "react"; -import { STATE_OPEN } from "../constants/socket"; -import { LOGIN_STATUS_SUCCESS } from "../constants/login"; - -import ConnectForm from "./ConnectForm"; import Header from "./Header"; -import LoginStatusPane from "./LoginStatusPane"; import Footer from "../containers/Footer"; -import RoomsPane from "../containers/RoomsPane"; - -const ID = "solstice-app"; - -const SolsticeApp = (props) => { - const { actions, login, socket } = props; - if (socket.state !== STATE_OPEN ) { - return ( -
    - -
    - ); - } - if (login.status !== LOGIN_STATUS_SUCCESS) { - return ( -
    - - -
    - ); - } - - return ( -
    -
    -
    - -
    -
    -
    - ); -}; +const SolsticeApp = ({ children }) => ( +
    +
    +
    + {children} +
    +
    +
    +); SolsticeApp.propTypes = { - actions: PropTypes.object.isRequired, - login: PropTypes.object.isRequired, - socket: PropTypes.object.isRequired + children: PropTypes.element }; export default SolsticeApp; diff --git a/src/containers/App.js b/src/containers/App.js deleted file mode 100644 index d8691d2..0000000 --- a/src/containers/App.js +++ /dev/null @@ -1,38 +0,0 @@ -// This file bootstraps the app with the boilerplate necessary -// to support hot reloading in Redux -import React, {PropTypes} from "react"; -import { bindActionCreators } from "redux"; -import { connect } from "react-redux"; - -import LoginActions from "../actions/LoginActions"; -import RoomActions from "../actions/RoomActions"; -import SocketActions from "../actions/SocketActions"; -import SocketHandlerActions from "../actions/SocketHandlerActions"; - -import SolsticeApp from "../components/SolsticeApp"; - -const App = (props) => (); - -App.propTypes = { - actions: PropTypes.object.isRequired, - login: PropTypes.object.isRequired, - socket: PropTypes.object.isRequired -}; - -const mapStateToProps = ({ socket, login }) => ({ socket, login }); - -function mapDispatchToProps(dispatch) { - return { - actions: { - login: bindActionCreators(LoginActions, dispatch), - room: bindActionCreators(RoomActions, dispatch), - socket: bindActionCreators(SocketActions, dispatch), - socketHandlers: bindActionCreators(SocketHandlerActions, dispatch) - } - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(App); diff --git a/src/containers/ConnectPage.js b/src/containers/ConnectPage.js new file mode 100644 index 0000000..3898dc0 --- /dev/null +++ b/src/containers/ConnectPage.js @@ -0,0 +1,95 @@ +import React, { PropTypes } from "react"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { hashHistory, withRouter } from "react-router"; + +import LoginActions from "../actions/LoginActions"; +import SocketActions from "../actions/SocketActions"; +import SocketHandlerActions from "../actions/SocketHandlerActions"; + +import { STATE_OPEN } from "../constants/socket"; +import { + LOGIN_STATUS_SUCCESS, + LOGIN_STATUS_UNKNOWN +} from "../constants/login"; + +import ConnectForm from "../components/ConnectForm"; +import LoginStatusPane from "../components/LoginStatusPane"; + +class ConnectPage extends React.Component { + constructor(props) { + super(props); + } + + componentDidMount() { + this.getLoginStatusOrRedirect(this.props); + } + + componentWillReceiveProps(nextProps) { + this.getLoginStatusOrRedirect(nextProps); + } + + getLoginStatusOrRedirect(props) { + const { actions, login, router, socket } = props; + if (socket.state === STATE_OPEN) + { + switch (login.status) { + case LOGIN_STATUS_UNKNOWN: + actions.login.getStatus(); + break; + + case LOGIN_STATUS_SUCCESS: + router.push("/app/rooms"); + break; + } + } + } + + render() { + const { actions, login, socket } = this.props; + + let loginStatusPane; + + if (socket.state === STATE_OPEN && + login.status === LOGIN_STATUS_UNKNOWN) + { + loginStatusPane = ( + + ); + } + + return ( +
    + + {loginStatusPane} +
    + ); + } +} + +ConnectPage.propTypes = { + actions: PropTypes.shape({ + login: PropTypes.object.isRequired, + socket: PropTypes.object.isRequired, + socketHandlers: PropTypes.object.isRequired + }).isRequired, + + login: PropTypes.object.isRequired, + router: PropTypes.object.isRequired, + socket: PropTypes.object.isRequired +}; + +const mapStateToProps = ({ login, socket }) => ({ login, socket }); + +const mapDispatchToProps = (dispatch) => ({ + actions: { + login: bindActionCreators(LoginActions, dispatch), + socket: bindActionCreators(SocketActions, dispatch), + socketHandlers: bindActionCreators(SocketHandlerActions, dispatch) + } +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withRouter(ConnectPage)); diff --git a/src/containers/Footer.js b/src/containers/Footer.js index 89da9fa..bfbeaaf 100644 --- a/src/containers/Footer.js +++ b/src/containers/Footer.js @@ -4,17 +4,16 @@ import { connect } from "react-redux"; import LoginStatusPane from "../components/LoginStatusPane"; import SocketStatusPane from "../components/SocketStatusPane"; -const Footer = ({ actions, login, socket }) => { +const Footer = ({ login, socket }) => { return (
    - +
    ); }; Footer.propTypes = { - actions: PropTypes.object.isRequired, login: PropTypes.object.isRequired, socket: PropTypes.object.isRequired }; diff --git a/src/containers/RoomsPane.js b/src/containers/RoomsPane.js index fa2eee9..5debe7d 100644 --- a/src/containers/RoomsPane.js +++ b/src/containers/RoomsPane.js @@ -3,6 +3,8 @@ import { connect } from "react-redux"; import { bindActionCreators } from "redux"; import ImmutablePropTypes from "react-immutable-proptypes"; +import RoomActions from "../actions/RoomActions"; + import RoomChat from "../components/RoomChat"; import RoomList from "../components/RoomList"; import RoomUserList from "../components/RoomUserList"; @@ -13,19 +15,28 @@ class RoomsPane extends React.Component { } render() { - const { login_user_name, rooms, roomActions, selected } = this.props; + const { loginUserName, params, rooms, roomActions } = this.props; - let room; - if (selected) { - room = { - ...rooms.get(selected), - name: selected - }; - } + let roomName; + let roomChat; - let roomUserList; - if (room && room.showUsers) { - roomUserList = ; + if (params && params.roomName) { + roomName = decodeURIComponent(atob(params.roomName)); + + const { membership, messages, showUsers } = rooms.get(roomName); + const room = { + name: roomName, + membership, + messages, + showUsers + }; + roomChat = ( + + ); } return ( @@ -33,15 +44,10 @@ class RoomsPane extends React.Component {
    - - {roomUserList} + {roomChat}
    ); @@ -49,18 +55,24 @@ class RoomsPane extends React.Component { } RoomsPane.propTypes = { - login_user_name: PropTypes.string, - rooms: ImmutablePropTypes.orderedMap.isRequired, - roomActions: PropTypes.object.isRequired, - selected: PropTypes.string + loginUserName: PropTypes.string.isRequired, + params: PropTypes.shape({ + roomName: PropTypes.string + }), + rooms: ImmutablePropTypes.orderedMap.isRequired, + roomActions: PropTypes.object.isRequired }; const mapStateToProps = (state) => ({ - login_user_name: state.login.username, rooms: state.rooms.rooms, - selected: state.rooms.selected + loginUserName: state.login.username +}); + +const mapDispatchToProps = (dispatch) => ({ + roomActions: bindActionCreators(RoomActions, dispatch) }); export default connect( mapStateToProps, + mapDispatchToProps )(RoomsPane); diff --git a/src/createRoutes.js b/src/createRoutes.js new file mode 100644 index 0000000..17e6a47 --- /dev/null +++ b/src/createRoutes.js @@ -0,0 +1,33 @@ +import React, { PropTypes } from 'react'; +import { Route, IndexRoute } from 'react-router'; + +import ConnectPage from "./containers/ConnectPage"; +import RoomsPane from "./containers/RoomsPane"; + +import SolsticeApp from "./components/SolsticeApp"; + +import { STATE_OPEN } from "./constants/socket"; +import { LOGIN_STATUS_SUCCESS } from "./constants/login"; + +const createRoutes = (store) => { + const requireLoggedIn = (nextState, replaceState) => { + let { socket, login } = store.getState(); + if (socket.state !== STATE_OPEN || + login.status !== LOGIN_STATUS_SUCCESS) + { + replaceState({}, "/"); + } + }; + + return ( + + + + + + + + ); +}; + +export default createRoutes; diff --git a/src/index.js b/src/index.js index bc95648..b48626d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,20 @@ import React from 'react'; import {render} from 'react-dom'; import { Provider } from 'react-redux'; -import App from './containers/App'; +import { Router, hashHistory } from "react-router"; + import configureStore from './store/configureStore'; +import createRoutes from "./createRoutes"; + import './styles/styles.scss'; //Yep, that's right. You can import SASS/CSS files too! Webpack will run the associated loader and plug this into the page. const store = configureStore(); +const routes = createRoutes(store); render( - + + {routes} + , document.getElementById('app') ); diff --git a/src/reducers/rooms.js b/src/reducers/rooms.js index b017a6b..26ee3e7 100644 --- a/src/reducers/rooms.js +++ b/src/reducers/rooms.js @@ -4,46 +4,43 @@ import { ROOM_JOIN, ROOM_LEAVE, ROOM_MESSAGE, - ROOM_SELECT, ROOM_SHOW_USERS, ROOM_HIDE_USERS, SOCKET_RECEIVE_MESSAGE } from "../constants/ActionTypes"; const initialState = { - rooms: Immutable.OrderedMap(), - selected: null + rooms: Immutable.OrderedMap() }; -const reduceRoomList = (old_rooms, room_list) => { +const reduceRoomList = (oldRoomMap, roomList) => { // First sort the room list by room name - room_list.sort((room_pair_1, room_pair_2) => { - const name_1 = room_pair_1[0]; - const name_2 = room_pair_2[0]; - if (name_1 < name_2) { + roomList.sort(([ roomName1 ], [ roomName2 ]) => { + if (roomName1 < roomName2) { return -1; - } else if (name_1 > name_2) { + } else if (roomName1 > roomName2) { return 1; } return 0; }); // Then build the new rooms map - let new_rooms = Immutable.OrderedMap(); - for (const [ room_name, room_data ] of room_list) { + let newRoomMap = Immutable.OrderedMap(); + + for (const [ roomName, newRoomData ] of roomList) { // Transform room_data.messages to an immutable list. - room_data.messages = Immutable.List(room_data.messages); + newRoomData.messages = Immutable.List(newRoomData.messages); // Get the old room data. - const old_data = old_rooms.get(room_name); + const oldRoomData = oldRoomMap.get(roomName); // Merge the old data and the new data, overwriting with new data if // conflicting. - const new_data = { - ...old_data, - ...room_data + const mergedRoomData = { + ...oldRoomData, + ...newRoomData }; - new_rooms = new_rooms.set(room_name, new_data); + newRoomMap = newRoomMap.set(roomName, mergedRoomData); } - return new_rooms; + return newRoomMap; }; const reduceReceiveMessage = (rooms, { variant, data }) => { @@ -103,12 +100,6 @@ export default (state = initialState, action) => { case ROOM_MESSAGE: return state; - case ROOM_SELECT: - return { - ...state, - selected: payload - }; - case ROOM_JOIN: { const rooms = state.rooms.set(payload, {