diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/src/components/gameList.js | 20 | ||||
-rw-r--r-- | frontend/src/components/gamesList.js | 17 | ||||
-rw-r--r-- | frontend/src/components/playerList.js | 16 | ||||
-rw-r--r-- | frontend/src/containers/gameBrowser.js | 13 | ||||
-rw-r--r-- | frontend/src/containers/lobby.js | 32 | ||||
-rw-r--r-- | frontend/src/redux/games.js | 38 | ||||
-rw-r--r-- | frontend/src/redux/players.js | 57 | ||||
-rw-r--r-- | frontend/src/routes.js | 4 | ||||
-rw-r--r-- | frontend/src/sagas/gameBrowser.js | 40 | ||||
-rw-r--r-- | frontend/src/sagas/usernameChoice.js | 4 | ||||
-rw-r--r-- | frontend/src/schemas/games.js | 4 |
11 files changed, 164 insertions, 81 deletions
diff --git a/frontend/src/components/gameList.js b/frontend/src/components/gameList.js new file mode 100644 index 00000000..c8720b26 --- /dev/null +++ b/frontend/src/components/gameList.js @@ -0,0 +1,20 @@ +import React from 'react' +import { Flex } from 'reflexbox' +import { Text, Space, Button } from 'rebass' + +const GameList = (props) => ( + <div> + {props.games.map((game, index) => { + + const joinGame = () => props.joinGame(game.get('id')) + + return (<Flex key={index}> + <Text>{game.get('name')}</Text> + <Space auto /> + <Button onClick={joinGame}>Join</Button> + </Flex>) + })} + </div> +) + +export default GameList diff --git a/frontend/src/components/gamesList.js b/frontend/src/components/gamesList.js deleted file mode 100644 index 0519e7d6..00000000 --- a/frontend/src/components/gamesList.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Flex } from 'reflexbox' -import { Text, Space } from 'rebass' - -const GameBrowser = (props) => ( - <div> - {props.games.valueSeq().map((game, index) => { - return (<Flex key={index}> - <Text>{game.get('name')}</Text> - <Space auto /> - <a href="#">Join</a> - </Flex>) - })} - </div> -) - -export default GameBrowser diff --git a/frontend/src/components/playerList.js b/frontend/src/components/playerList.js new file mode 100644 index 00000000..30384d53 --- /dev/null +++ b/frontend/src/components/playerList.js @@ -0,0 +1,16 @@ +import React from 'react' +import { Flex } from 'reflexbox' +import { Text } from 'rebass' + +const PlayerList = (props) => ( + <div> + {props.players.map((player, index) => { + return (<Flex key={index}> + <Text>{player.get('displayName')}</Text> + <Text>({player.get('username')})</Text> + </Flex>) + })} + </div> +) + +export default PlayerList diff --git a/frontend/src/containers/gameBrowser.js b/frontend/src/containers/gameBrowser.js index 8c48f071..6a6b3ce2 100644 --- a/frontend/src/containers/gameBrowser.js +++ b/frontend/src/containers/gameBrowser.js @@ -6,9 +6,9 @@ import { Text } from 'rebass' import { Flex } from 'reflexbox' -import GamesList from '../components/gamesList' +import GameList from '../components/gameList' -class App extends Component { +class GameBrowser extends Component { createGame = (e) => { e.preventDefault() @@ -33,7 +33,7 @@ class App extends Component { <Text><b>Username:</b> {this.props.currentPlayer.get('displayName')}</Text> <Space x={1} /> </Flex> - <GamesList games={this.props.games} /> + <GameList games={this.props.games} joinGame={this.props.joinGame}/> </div> ) } @@ -41,14 +41,15 @@ class App extends Component { import { getCurrentPlayer } from '../redux/players' import { getAllGames, actions } from '../redux/games' + const mapStateToProps = (state) => ({ currentPlayer: getCurrentPlayer(state), games: getAllGames(state) }) - const mapDispatchToProps = { - createGame: actions.createGame + createGame: actions.requestCreateGame, + joinGame: actions.requestJoinGame } -export default connect(mapStateToProps, mapDispatchToProps)(App) +export default connect(mapStateToProps, mapDispatchToProps)(GameBrowser) diff --git a/frontend/src/containers/lobby.js b/frontend/src/containers/lobby.js new file mode 100644 index 00000000..c36c3263 --- /dev/null +++ b/frontend/src/containers/lobby.js @@ -0,0 +1,32 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { List } from 'immutable' +import { Text } from 'rebass' +import PlayerList from '../components/playerList' + +class Lobby extends Component { + + render() { + return ( + <div> + {this.props.currentGame && <Text>{this.props.currentGame.name}</Text>} + <PlayerList players={this.props.players}/> + </div> + ) + } +} + +import { getPlayers } from '../redux/players' +import { getCurrentGame } from '../redux/games' + +const mapStateToProps = (state) => { + const game = getCurrentGame(state) + return ({ + currentGame: game, + players: game ? getPlayers(state, game.get('players')) : List() + }) +} + +const mapDispatchToProps = {} + +export default connect(mapStateToProps, mapDispatchToProps)(Lobby) diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js index 4115323c..5ad6f8d2 100644 --- a/frontend/src/redux/games.js +++ b/frontend/src/redux/games.js @@ -1,29 +1,39 @@ -import { Map } from 'immutable' +import {fromJS} from 'immutable' export const types = { - CREATE_OR_UPDATE_GAMES: 'GAME/CREATE_OR_UPDATE_GAMES', - ENTER_GAME: 'GAME/ENTER_GAME', - JOIN_GAME: 'GAME/JOIN_GAME', - CREATE_GAME: 'GAME/CREATE_GAME', + UPDATE_GAMES: 'GAME/UPDATE_GAMES', + REQUEST_CREATE_GAME: 'GAME/REQUEST_CREATE_GAME', + REQUEST_JOIN_GAME: 'GAME/REQUEST_JOIN_GAME', + ENTER_LOBBY: 'GAME/ENTER_LOBBY', } export const actions = { - createOrUpdateGame: (games) => ({ type: types.CREATE_OR_UPDATE_GAMES, games }), - enterGame: (username) => ({ type: types.ENTER_GAME, username }), - joinGame: (id) => ({ type: types.JOIN_GAME, id }), - createGame: (name) => ({ type: types.CREATE_GAME, name }), + updateGames: (games) => ({ type: types.UPDATE_GAMES, games }), + requestJoinGame: (id) => ({ type: types.REQUEST_JOIN_GAME, id }), + requestCreateGame: (name) => ({ type: types.REQUEST_CREATE_GAME, name }), + enterLobby: (lobby) => ({ type: types.ENTER_LOBBY, lobby }), } - -const initialState = Map({}) +const initialState = fromJS({ + all: {}, + current: '' +}) export default (state = initialState, action) => { switch (action.type) { - case types.CREATE_OR_UPDATE_GAMES: - return state.mergeDeep(action.games) + case types.UPDATE_GAMES: + return state.setIn(['all'], state.get('all').mergeDeep(action.games)) + case types.ENTER_LOBBY: + return state.set('current', action.lobby.get('id')) default: return state } } -export const getAllGames = state => state.get('games') +const getState = globalState => globalState.get('games') + +export const getAllGamesById = globalState => getState(globalState).get('all') +export const getAllGames = globalState => getAllGamesById(globalState).toList() +export const getGame = (globalState, id) => getAllGamesById(globalState).get(id) +export const getCurrentGameId = globalState => getState(globalState).get('current') +export const getCurrentGame = globalState => getGame(globalState, getCurrentGameId(globalState)) diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js index 967d93b6..85b5d042 100644 --- a/frontend/src/redux/players.js +++ b/frontend/src/redux/players.js @@ -1,23 +1,26 @@ -import { fromJS, Map } from 'immutable' +import {fromJS, Map, Set} from 'immutable' export const types = { - SET_USERNAME: 'USER/SET_USERNAME', - SET_USERNAMES: 'USER/SET_USERNAMES', - CHOOSE_USERNAME: 'USER/CHOOSE_USERNAME' + REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME', + SET_CURRENT_PLAYER: 'USER/SET_CURRENT_PLAYER', + UPDATE_PLAYERS: 'USER/UPDATE_PLAYERS' } export const actions = { - setUsername: (username, displayName, index) => ({ - type: types.SET_USERNAME, - username, - index, - displayName + chooseUsername: (username) => ({ + type: types.REQUEST_CHOOSE_USERNAME, + username + }), + setCurrentPlayer: (player) => ({ + type: types.SET_CURRENT_PLAYER, + player + }), + updatePlayers: (players) => ({ + type: types.UPDATE_PLAYERS, + players }), - setPlayers: (players) => ({ type: types.SET_USERNAMES, players }), - chooseUsername: (username) => ({ type: types.CHOOSE_USERNAME, username }), } - const initialState = fromJS({ all: {}, current: '' @@ -25,20 +28,28 @@ const initialState = fromJS({ export default (state = initialState, action) => { switch (action.type) { - case types.SET_USERNAME: - const user = fromJS({ - username: action.username, - displayName: action.displayName, - index: action.index, - }) - return state.setIn(['all', user.get('username')], user).set('current', user.get('username')) - case types.SET_USERNAMES: + case types.SET_CURRENT_PLAYER: + const player = action.player + const username = player.get('username') + return state.setIn(['all', username], player).set('current', username) + case types.UPDATE_PLAYERS: return state.setIn(['all'], state.get('all').mergeDeep(action.players)) default: return state } } -export const getCurrentPlayerUserName = state => state.get('players').get('current') -export const getAllPlayers = state => state.get('players').get('all') -export const getCurrentPlayer = (state) => getAllPlayers(state).get(getCurrentPlayerUserName(state), Map()) +const getState = globalState => globalState.get('players') + +function keyIn(...keys) { + return (v, k) => Set(keys).has(k) +} + +export const getAllPlayersByUsername = globalState => getState(globalState).get('all') +export const getAllPlayers = globalState => getAllPlayersByUsername(globalState).toList() +export const getPlayers = (globalState, usernames) => getAllPlayersByUsername(globalState) + .filter(keyIn(usernames)) + .toList() +export const getCurrentPlayerUsername = globalState => getState(globalState).get('current') +export const getCurrentPlayer = globalState => getAllPlayersByUsername(globalState) + .get(getCurrentPlayerUsername(globalState), Map()) diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 2e95abda..16800736 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -14,6 +14,7 @@ export const makeSagaRoutes = wsConnection => ({ import { HomeLayout, LobbyLayout } from './layouts' import HomePage from './containers/home' import GameBrowser from './containers/gameBrowser' +import Lobby from './containers/lobby' import Error404 from './components/errors/Error404' export const routes = [ @@ -26,7 +27,8 @@ export const routes = [ path: '/', component: LobbyLayout, childRoutes: [ - { path: '/games', component: GameBrowser } + { path: '/games', component: GameBrowser }, + { path: '/lobby', component: Lobby } ] }, { diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js index 2dbd4040..596da428 100644 --- a/frontend/src/sagas/gameBrowser.js +++ b/frontend/src/sagas/gameBrowser.js @@ -1,10 +1,10 @@ -import { call, put, take } from 'redux-saga/effects' -import { eventChannel } from 'redux-saga' +import { call, put, take, apply } from 'redux-saga/effects' +import { eventChannel} from 'redux-saga' import { fromJS } from 'immutable' import { push } from 'react-router-redux' import { normalize } from 'normalizr' -import gameSchema from '../schemas/games' +import { game, gameList } from '../schemas/games' import { actions as gameActions, types } from '../redux/games' import { actions as playerActions } from '../redux/players' @@ -13,12 +13,12 @@ function gameBrowserChannel(socket) { return eventChannel(emit => { const makeHandler = type => event => { - const response = fromJS(JSON.parse(event.body)) + const response = JSON.parse(event.body) emit({ type, response }) } - const newGame = socket.subscribe('/topic/games', makeHandler(types.CREATE_OR_UPDATE_GAMES)) - const joinGame = socket.subscribe('/user/queue/lobby/joined', makeHandler(types.JOIN_GAME)) + const newGame = socket.subscribe('/topic/games', makeHandler(types.UPDATE_GAMES)) + const joinGame = socket.subscribe('/user/queue/lobby/joined', makeHandler(types.ENTER_LOBBY)) return () => { newGame.unsubscribe() @@ -35,15 +35,16 @@ export function *watchGames({ socket }) { const { type, response } = yield take(socketChannel) switch (type) { - case types.CREATE_OR_UPDATE_GAMES: - const normalizedResponse = normalize(response.toJS(), gameSchema) - yield put(playerActions.setPlayers(fromJS(normalizedResponse.entities.players))) - yield put(gameActions.createOrUpdateGame(fromJS(normalizedResponse.entities.games))) + case types.UPDATE_GAMES: + const normGameList = normalize(response, gameList) + yield put(playerActions.updatePlayers(fromJS(normGameList.entities.players))) + yield put(gameActions.updateGames(fromJS(normGameList.entities.games))) break - case types.JOIN_GAME: - yield put(gameActions.joinGame(response)) + case types.ENTER_LOBBY: + const normGame = normalize(response, game) + yield put(gameActions.enterLobby(fromJS(normGame.entities.games[normGame.result]))) socketChannel.close() - yield put(push(`/lobby/${response.id}`)) + yield put(push('/lobby')) break default: console.error('Unknown type') @@ -55,15 +56,22 @@ export function *watchGames({ socket }) { } export function *createGame({ socket }) { - const { name } = yield take(types.CREATE_GAME) + const { name } = yield take(types.REQUEST_CREATE_GAME) - socket.send("/app/lobby/create", JSON.stringify({ gameName: name }), {}) + yield apply(socket, socket.send, ["/app/lobby/create", JSON.stringify({ gameName: name }), {}]) +} + +export function *joinGame({ socket }) { + const { id } = yield take(types.REQUEST_JOIN_GAME) + + yield apply(socket, socket.send, ["/app/lobby/join", JSON.stringify({ gameId: id }), {}]) } export function *gameBrowserSaga(socketConnection) { yield [ call(watchGames, socketConnection), - call(createGame, socketConnection) + call(createGame, socketConnection), + call(joinGame, socketConnection) ] } diff --git a/frontend/src/sagas/usernameChoice.js b/frontend/src/sagas/usernameChoice.js index da720e9f..ad5b5341 100644 --- a/frontend/src/sagas/usernameChoice.js +++ b/frontend/src/sagas/usernameChoice.js @@ -17,13 +17,13 @@ function usernameValidationChannel(socket) { function *usernameValidation({ socket }) { const usernameChannel = yield call(usernameValidationChannel, socket) const user = yield take(usernameChannel) - yield put(actions.setUsername(user.get('username'), user.get('displayName'), user.get('index'))) + yield put(actions.setCurrentPlayer(user)) usernameChannel.close() yield put(push('/games')) } function *sendUsername({ socket }) { - const { username } = yield take(types.CHOOSE_USERNAME) + const { username } = yield take(types.REQUEST_CHOOSE_USERNAME) yield socket.send('/app/chooseName', JSON.stringify({ playerName: username diff --git a/frontend/src/schemas/games.js b/frontend/src/schemas/games.js index 180c9e69..f7a9ffb8 100644 --- a/frontend/src/schemas/games.js +++ b/frontend/src/schemas/games.js @@ -4,8 +4,8 @@ const player = new schema.Entity('players', {}, { idAttribute: 'username' }) -const game = new schema.Entity('games', { +export const game = new schema.Entity('games', { players: [ player ] }) -export default [ game ] +export const gameList = [ game ] |