From 0bf423172ffb2e4030b521b0985d133cb5c61dd9 Mon Sep 17 00:00:00 2001 From: Victor Chabbert Date: Sun, 28 May 2017 20:42:21 +0200 Subject: Move to immutable with Records --- frontend/flow-typed/npm/redux-immutable_vx.x.x.js | 74 +++++++++++++++++++++++ frontend/package.json | 4 +- frontend/src/components/gameList.js | 9 +-- frontend/src/components/playerList.js | 5 +- frontend/src/containers/gameBrowser.js | 4 +- frontend/src/containers/lobby.js | 7 ++- frontend/src/models/errors.js | 34 +++++++++++ frontend/src/models/games.js | 41 +++++++++++++ frontend/src/models/players.js | 26 ++++++++ frontend/src/reducers.js | 3 +- frontend/src/redux/app.js | 2 +- frontend/src/redux/errors.js | 22 +------ frontend/src/redux/games.js | 29 ++++----- frontend/src/redux/players.js | 21 +++---- frontend/src/store.js | 4 +- frontend/yarn.lock | 8 +++ 16 files changed, 222 insertions(+), 71 deletions(-) create mode 100644 frontend/flow-typed/npm/redux-immutable_vx.x.x.js create mode 100644 frontend/src/models/errors.js create mode 100644 frontend/src/models/games.js create mode 100644 frontend/src/models/players.js diff --git a/frontend/flow-typed/npm/redux-immutable_vx.x.x.js b/frontend/flow-typed/npm/redux-immutable_vx.x.x.js new file mode 100644 index 00000000..7bc76710 --- /dev/null +++ b/frontend/flow-typed/npm/redux-immutable_vx.x.x.js @@ -0,0 +1,74 @@ +// flow-typed signature: f0465fe5b273048a19e890db9b35b2e6 +// flow-typed version: <>/redux-immutable_v^4.0.0/flow_v0.46.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'redux-immutable' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'redux-immutable' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'redux-immutable/benchmarks/index' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/combineReducers' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/index' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/utilities/getStateName' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/utilities/getUnexpectedInvocationParameterMessage' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/utilities/index' { + declare module.exports: any; +} + +declare module 'redux-immutable/dist/utilities/validateNextState' { + declare module.exports: any; +} + +// Filename aliases +declare module 'redux-immutable/benchmarks/index.js' { + declare module.exports: $Exports<'redux-immutable/benchmarks/index'>; +} +declare module 'redux-immutable/dist/combineReducers.js' { + declare module.exports: $Exports<'redux-immutable/dist/combineReducers'>; +} +declare module 'redux-immutable/dist/index.js' { + declare module.exports: $Exports<'redux-immutable/dist/index'>; +} +declare module 'redux-immutable/dist/utilities/getStateName.js' { + declare module.exports: $Exports<'redux-immutable/dist/utilities/getStateName'>; +} +declare module 'redux-immutable/dist/utilities/getUnexpectedInvocationParameterMessage.js' { + declare module.exports: $Exports<'redux-immutable/dist/utilities/getUnexpectedInvocationParameterMessage'>; +} +declare module 'redux-immutable/dist/utilities/index.js' { + declare module.exports: $Exports<'redux-immutable/dist/utilities/index'>; +} +declare module 'redux-immutable/dist/utilities/validateNextState.js' { + declare module.exports: $Exports<'redux-immutable/dist/utilities/validateNextState'>; +} diff --git a/frontend/package.json b/frontend/package.json index 7278ded0..e7701703 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "react-scripts": "1.0.5" }, "dependencies": { + "immutable": "^3.8.1", "normalizr": "^3.2.3", "react": "^15.5.4", "react-dom": "^15.5.4", @@ -19,12 +20,11 @@ "react-router-redux": "^4.0.8", "rebass": "^0.3.3", "redux": "^3.6.0", + "redux-immutable": "^4.0.0", "redux-saga": "^0.13.0", "redux-saga-router": "^2.1.0", - "redux-seamless-immutable": "^0.3.0", "reflexbox": "^2.2.3", "reselect": "^3.0.1", - "seamless-immutable": "^7.1.2", "sockjs-client": "^1.1.4", "webstomp-client": "^1.0.6" }, diff --git a/frontend/src/components/gameList.js b/frontend/src/components/gameList.js index f3a0d381..17dad16f 100644 --- a/frontend/src/components/gameList.js +++ b/frontend/src/components/gameList.js @@ -1,18 +1,15 @@ import React from 'react'; import { Flex } from 'reflexbox'; import { Text, Space, Button } from 'rebass'; -import Immutable from 'seamless-immutable'; -const GameList = props => ( +const GameList = ({ games, joinGame }) => (
- {Immutable.asMutable(props.games).map((game, index) => { - const joinGame = () => props.joinGame(game.id); - + {games.map((game, index) => { return ( {game.name} - + ); })} diff --git a/frontend/src/components/playerList.js b/frontend/src/components/playerList.js index 1a68b067..bc2c768e 100644 --- a/frontend/src/components/playerList.js +++ b/frontend/src/components/playerList.js @@ -1,11 +1,10 @@ import React from 'react'; import { Flex } from 'reflexbox'; import { Text } from 'rebass'; -import Immutable from 'seamless-immutable'; -const PlayerList = props => ( +const PlayerList = ({ players }) => (
- {Immutable.asMutable(props.players).map(player => { + {players.map(player => { return ( {player.displayName} diff --git a/frontend/src/containers/gameBrowser.js b/frontend/src/containers/gameBrowser.js index 643dbebc..ac3f1504 100644 --- a/frontend/src/containers/gameBrowser.js +++ b/frontend/src/containers/gameBrowser.js @@ -41,8 +41,8 @@ class GameBrowser extends Component { } const mapStateToProps = state => ({ - currentPlayer: getCurrentPlayer(state) || { displayName: '[ERROR]' }, - games: getAllGames(state), + currentPlayer: getCurrentPlayer(state.get('players')), + games: getAllGames(state.get('games')), }); const mapDispatchToProps = { diff --git a/frontend/src/containers/lobby.js b/frontend/src/containers/lobby.js index 90714aec..fc762056 100644 --- a/frontend/src/containers/lobby.js +++ b/frontend/src/containers/lobby.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; +import { List } from 'immutable'; import { connect } from 'react-redux'; -import Immutable from 'seamless-immutable'; import { Button } from 'rebass'; import PlayerList from '../components/playerList'; @@ -28,10 +28,11 @@ class Lobby extends Component { } const mapStateToProps = state => { - const game = getCurrentGame(state); + const game = getCurrentGame(state.get('games')); + console.info(game); return { currentGame: game, - players: game ? getPlayers(state, game.players) : Immutable([]), + players: game ? getPlayers(state.get('players'), game.players) : new List(), }; }; diff --git a/frontend/src/models/errors.js b/frontend/src/models/errors.js new file mode 100644 index 00000000..c00954cd --- /dev/null +++ b/frontend/src/models/errors.js @@ -0,0 +1,34 @@ +import { Record, List } from 'immutable'; + +const ErrorsRecord = Record({ + nextId: 0, + history: new List(), +}); + +export default class ErrorsState extends ErrorsRecord { + addError(error) { + const errorObject = new Error({ id: this.nextId, error: new ErrorBag(error) }); + return this.set('history', this.history.push(errorObject)).set('nextId', this.nextId + 1); + } +} + +const ErrorRecord = Record({ + id: -1, + timestamp: new Date(), + error: new ErrorsRecord(), +}); + +export class Error extends ErrorRecord {} + +const ErrorBagRecord = Record({ + type: '', + position: 'bottom-left', + options: { + icon: 'error', + removeOnHover: true, + showCloseButton: true, + }, + title: 'Unknown Error', +}); + +export class ErrorBag extends ErrorBagRecord {} diff --git a/frontend/src/models/games.js b/frontend/src/models/games.js new file mode 100644 index 00000000..95bf8015 --- /dev/null +++ b/frontend/src/models/games.js @@ -0,0 +1,41 @@ +import { Record, Map, List } from 'immutable'; + +const SettingsRecord = Record({ + initialGold: 3, + lostPointsPerDefeat: 1, + timeLimitInSeconds: 45, + randomSeedForTests: -1, + discardedCardGold: 3, + defaultTradingCost: 2, + wonPointsPerVictoryPerAge: { + '1': 1, + '2': 3, + '3': 5, + }, + wonderSidePickMethod: 'EACH_RANDOM', + pointsPer3Gold: 1, +}); +export class Settings extends SettingsRecord {} + +const GameRecord = Record({ + id: -1, + name: null, + players: new List(), + settings: new Settings(), + state: 'LOBBY', +}); +export class Game extends GameRecord {} + +const GamesRecord = Record({ + all: new Map(), + current: '', +}); +export default class GamesState extends GamesRecord { + addGame(g) { + const game = new Game(g); + return this.mergeDeepIn(['all', game.id], game); + } + addGames(games) { + return this.mergeIn(['all'], games.map(game => new Game(game))); + } +} diff --git a/frontend/src/models/players.js b/frontend/src/models/players.js new file mode 100644 index 00000000..3df32c57 --- /dev/null +++ b/frontend/src/models/players.js @@ -0,0 +1,26 @@ +import { Record, Map } from 'immutable'; + +const PlayerRecord = Record({ + username: null, + displayName: null, + index: 0, + ready: false, +}); +export class Player extends PlayerRecord {} + +const PlayersRecord = Record({ + all: new Map(), + current: '', +}); +export default class PlayerState extends PlayersRecord { + addPlayer(p) { + const player = new Player(p); + const playerMap = new Map({ [player.username]: player }); + return this.addPlayers(playerMap).set('current', player.username); + } + + addPlayers(p) { + const players = new Map(p); + return this.mergeIn(['all'], players.map(player => new Player(player))); + } +} diff --git a/frontend/src/reducers.js b/frontend/src/reducers.js index a7bba09a..4b98a9ca 100644 --- a/frontend/src/reducers.js +++ b/frontend/src/reducers.js @@ -1,4 +1,5 @@ -import { combineReducers, routerReducer } from 'redux-seamless-immutable'; +import { combineReducers } from 'redux-immutable'; +import { routerReducer } from 'react-router-redux'; import { reducer as toastrReducer } from 'react-redux-toastr'; import errorsReducer from './redux/errors'; diff --git a/frontend/src/redux/app.js b/frontend/src/redux/app.js index d24fbbfd..614e7d93 100644 --- a/frontend/src/redux/app.js +++ b/frontend/src/redux/app.js @@ -1,5 +1,5 @@ export const makeSelectLocationState = () => { return state => { - return state.routing; + return state.get('routing'); }; }; diff --git a/frontend/src/redux/errors.js b/frontend/src/redux/errors.js index ec1e30b6..ad1e2795 100644 --- a/frontend/src/redux/errors.js +++ b/frontend/src/redux/errors.js @@ -1,4 +1,4 @@ -import Immutable from 'seamless-immutable'; +import ErrorsState from '../models/errors'; export const types = { ERROR_RECEIVED_ON_WS: 'ERROR/RECEIVED_ON_WS', @@ -11,27 +11,11 @@ export const actions = { }), }; -const initialState = Immutable.from({ - nextId: 0, - history: [], -}); - -export default (state = initialState, action) => { +export default (state = new ErrorsState(), action) => { switch (action.type) { case types.ERROR_RECEIVED_ON_WS: - let error = Object.assign({ id: state.nextId, timestamp: new Date() }, action.error); - let newState = state.set('nextId', state.nextId + 1); - newState = addErrorToHistory(newState, error); - return newState; + return state.addError(action.error); default: return state; } }; - -function addErrorToHistory(state, error) { - return addToArray(state, 'history', error); -} - -function addToArray(state, arrayKey, element) { - return state.set(arrayKey, state[arrayKey].concat([element])); -} diff --git a/frontend/src/redux/games.js b/frontend/src/redux/games.js index 9ef0e7cd..d5953db1 100644 --- a/frontend/src/redux/games.js +++ b/frontend/src/redux/games.js @@ -1,4 +1,5 @@ -import Immutable from 'seamless-immutable'; +import { fromJS } from 'immutable'; +import GamesState from '../models/games'; export const types = { UPDATE_GAMES: 'GAME/UPDATE_GAMES', @@ -10,37 +11,29 @@ export const types = { }; export const actions = { - updateGames: games => ({ type: types.UPDATE_GAMES, games: Immutable(games) }), + updateGames: games => ({ type: types.UPDATE_GAMES, games: fromJS(games) }), requestJoinGame: gameId => ({ type: types.REQUEST_JOIN_GAME, gameId }), requestCreateGame: gameName => ({ type: types.REQUEST_CREATE_GAME, gameName, }), requestStartGame: () => ({ type: types.REQUEST_START_GAME }), - enterLobby: lobby => ({ type: types.ENTER_LOBBY, lobby: Immutable(lobby) }), + enterLobby: lobby => ({ type: types.ENTER_LOBBY, lobby: fromJS(lobby) }), enterGame: () => ({ type: types.ENTER_GAME }), }; -const initialState = Immutable.from({ - all: {}, - current: '', -}); - -export default (state = initialState, action) => { +export default (state = new GamesState(), action) => { switch (action.type) { case types.UPDATE_GAMES: - return Immutable.merge(state, { all: action.games }, { deep: true }); + return state.addGames(action.games); case types.ENTER_LOBBY: - return state.set('current', action.lobby.id); + return state.set('current', action.lobby.get('id')); default: return state; } }; -export const getAllGamesById = state => state.games.all; -export const getAllGames = state => { - let gamesById = getAllGamesById(state); - return Object.keys(gamesById).map(k => gamesById[k]); -}; -export const getGame = (state, id) => getAllGamesById(state)[id]; -export const getCurrentGame = state => getGame(state, state.games.current); +export const getAllGamesById = games => games.all; +export const getAllGames = games => getAllGamesById(games).toList(); +export const getGame = (games, id) => getAllGamesById(games).get(`${id}`); +export const getCurrentGame = games => getGame(games, games.current); diff --git a/frontend/src/redux/players.js b/frontend/src/redux/players.js index b11e920f..85b579f3 100644 --- a/frontend/src/redux/players.js +++ b/frontend/src/redux/players.js @@ -1,4 +1,4 @@ -import Immutable from 'seamless-immutable'; +import PlayerState, { Player } from '../models/players'; export const types = { REQUEST_CHOOSE_USERNAME: 'USER/REQUEST_CHOOSE_USERNAME', @@ -21,24 +21,17 @@ export const actions = { }), }; -const initialState = Immutable.from({ - all: {}, - current: '', -}); - -export default (state = initialState, action) => { +export default (state = new PlayerState(), action) => { switch (action.type) { case types.SET_CURRENT_PLAYER: - const player = action.player; - const withNewPlayer = state.setIn(['all', player.username], player); - return Immutable.set(withNewPlayer, 'current', player.username); + return state.addPlayer(action.player); case types.UPDATE_PLAYERS: - return Immutable.merge(state, { all: action.players }, { deep: true }); + return state.addPlayers(action.players); default: return state; } }; -export const getCurrentPlayer = state => state.players.all && state.players.all[state.players.current]; -export const getPlayer = (state, username) => state.players.all[username]; -export const getPlayers = (state, usernames) => usernames.map(u => getPlayer(state, u)); +export const getCurrentPlayer = players => players.all.get(players.current, new Player({ displayName: '[ERROR]' })); +export const getPlayer = (players, username) => players.all.get(username); +export const getPlayers = (players, usernames) => usernames.map(u => getPlayer(players, u)); diff --git a/frontend/src/store.js b/frontend/src/store.js index 4bd22184..57f43a4f 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -1,7 +1,7 @@ import { createStore, applyMiddleware, compose } from 'redux'; import { browserHistory } from 'react-router'; import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux'; -import Immutable from 'seamless-immutable'; +import { fromJS } from 'immutable'; import createReducer from './reducers'; import createSagaMiddleware from 'redux-saga'; @@ -21,7 +21,7 @@ export default function configureStore(initialState = {}) { ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose; - const store = createStore(createReducer(), Immutable.from(initialState), composeEnhancers(...enhancers)); + const store = createStore(createReducer(), fromJS(initialState), composeEnhancers(...enhancers)); sagaMiddleware.run(rootSaga, browserHistory); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 562508ba..902ee743 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3035,6 +3035,10 @@ ignore@^3.2.0, ignore@^3.2.7: version "3.3.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.0.tgz#3812d22cbe9125f2c2b4915755a1b8abd745a001" +immutable@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -5327,6 +5331,10 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" +redux-immutable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-4.0.0.tgz#3a1a32df66366462b63691f0e1dc35e472bbc9f3" + redux-saga-router@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/redux-saga-router/-/redux-saga-router-2.1.0.tgz#d049962ad6a44d227f0308aedb75e39c13f58413" -- cgit