diff options
-rw-r--r-- | src/main/js/package.json | 8 | ||||
-rw-r--r-- | src/main/js/src/components/modals/username.js | 40 | ||||
-rw-r--r-- | src/main/js/src/containers/App/actions.js | 5 | ||||
-rw-r--r-- | src/main/js/src/containers/App/constants.js | 1 | ||||
-rw-r--r-- | src/main/js/src/containers/App/index.js | 92 | ||||
-rw-r--r-- | src/main/js/src/containers/App/saga.js | 28 | ||||
-rw-r--r-- | src/main/js/src/containers/GameBrowser/actions.js | 16 | ||||
-rw-r--r-- | src/main/js/src/containers/GameBrowser/constants.js | 3 | ||||
-rw-r--r-- | src/main/js/src/containers/GameBrowser/index.js | 33 | ||||
-rw-r--r-- | src/main/js/src/containers/GameBrowser/reducer.js | 16 | ||||
-rw-r--r-- | src/main/js/src/containers/GameBrowser/saga.js | 70 | ||||
-rw-r--r-- | src/main/js/src/reducers.js | 7 | ||||
-rw-r--r-- | src/main/js/src/sagas.js | 24 | ||||
-rw-r--r-- | src/main/js/src/utils/createWebSocketConnection.js | 14 | ||||
-rw-r--r-- | src/main/js/yarn.lock | 21 |
15 files changed, 361 insertions, 17 deletions
diff --git a/src/main/js/package.json b/src/main/js/package.json index 75b33982..b3ad0282 100644 --- a/src/main/js/package.json +++ b/src/main/js/package.json @@ -12,9 +12,12 @@ "react-redux": "^5.0.1", "react-router": "4.0.0-alpha.6", "rebass": "^0.3.3", + "reflexbox": "^2.2.3", "redux": "^3.6.0", "redux-saga": "^0.13.0", - "reflexbox": "^2.2.3" + "sockjs-client": "latest", + "webstomp-client": "^1.0.3", + "redux-saga": "^0.13.0" }, "scripts": { "start": "react-scripts start", @@ -24,5 +27,6 @@ }, "eslintConfig": { "extends": "react-app" - } + }, + "proxy": "ws://localhost:8080" } diff --git a/src/main/js/src/components/modals/username.js b/src/main/js/src/components/modals/username.js new file mode 100644 index 00000000..61b52114 --- /dev/null +++ b/src/main/js/src/components/modals/username.js @@ -0,0 +1,40 @@ +import React from 'react' +import { + Overlay, + Panel, + PanelHeader, + PanelFooter, + Button, + Input, + Close, + Space +} from 'rebass' + +const Modal = ({ modalOpen, toggleModal }) => ( + <Overlay open={modalOpen} onDismiss={toggleModal('usernameModal')}> + <Panel theme="info"> + <PanelHeader> + What's your username ? + <Space auto /> + <Close onClick={toggleModal('usernameModal')} /> + </PanelHeader> + <Input + label="Username" + name="username" + placeholder="Cesar92" + rounded + type="text" + /> + <PanelFooter> + <Space auto /> + <Button + theme="success" + onClick={toggleModal('usernameModal')} + children="Ok" + /> + </PanelFooter> + </Panel> + </Overlay> +) + +export default Modal diff --git a/src/main/js/src/containers/App/actions.js b/src/main/js/src/containers/App/actions.js new file mode 100644 index 00000000..cfb617d5 --- /dev/null +++ b/src/main/js/src/containers/App/actions.js @@ -0,0 +1,5 @@ +import { INITIALIZE_WS } from "./constants" + +export const initializeWs = () => ({ + type: INITIALIZE_WS +}) diff --git a/src/main/js/src/containers/App/constants.js b/src/main/js/src/containers/App/constants.js new file mode 100644 index 00000000..be31f8cc --- /dev/null +++ b/src/main/js/src/containers/App/constants.js @@ -0,0 +1 @@ +export const INITIALIZE_WS = 'app/INITIALIZE_WS' diff --git a/src/main/js/src/containers/App/index.js b/src/main/js/src/containers/App/index.js index e7eef332..70f99b6b 100644 --- a/src/main/js/src/containers/App/index.js +++ b/src/main/js/src/containers/App/index.js @@ -1,9 +1,83 @@ -import React from 'react' -import { Link } from 'react-router' - -export default () => <div> - <h1>Hello World</h1> - <Link to="/counter">Go to counter</Link> - <br></br> - <Link to="/404">Go to 404</Link> - </div>
\ No newline at end of file +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { + Banner, + Heading, + Space, + Button, + InlineForm, + Text +} from 'rebass' +import { Flex } from 'reflexbox' +import Modal from '../../components/modals/username' +import GameBrowser from '../GameBrowser' + +class App extends Component { + state = { + usernameModal: false, + } + + componentDidMount() { + + } + + toggleModal = (key) => { + return (e) => { + const val = !this.state[key] + this.setState({ [key]: val }) + } + } + + createGame = (e) => { + e.preventDefault() + if (this._gameName !== undefined) { + this.props.createGame(this._gameName) + } + } + + render() { + return ( + <div> + <Banner + align="center" + style={{minHeight: '30vh'}} + backgroundImage="https://images.unsplash.com/photo-1431207446535-a9296cf995b1?dpr=1&auto=format&fit=crop&w=1199&h=799&q=80&cs=tinysrgb&crop=" + > + <Heading level={1}>Seven Wonders</Heading> + </Banner> + <Flex align="center" p={1}> + <InlineForm + buttonLabel="Create Game" + label="Game name" + name="game_name" + onChange={(e) => this._gameName = e.target.value} + onClick={this.createGame} + > + + </InlineForm> + <Space auto /> + <Text><b>Username:</b> Cesar92</Text> + <Space x={1} /> + <Button + onClick={this.toggleModal('usernameModal')} + children="Change"/> + </Flex> + <GameBrowser /> + <Modal toggleModal={this.toggleModal} modalOpen={this.state.usernameModal} /> + </div> + ) + } +} + +const mapStateToProps = (state) => ({ + +}) + +import { initializeWs } from "./actions"; +import { createGame } from '../GameBrowser/actions' +const mapDispatchToProps = { + initializeWs, + createGame +} + +export default connect(mapStateToProps, mapDispatchToProps)(App) diff --git a/src/main/js/src/containers/App/saga.js b/src/main/js/src/containers/App/saga.js new file mode 100644 index 00000000..f1f72dfd --- /dev/null +++ b/src/main/js/src/containers/App/saga.js @@ -0,0 +1,28 @@ +import { put, take } from 'redux-saga/effects' +import { eventChannel } from 'redux-saga' + +function createSocketChannel(socket) { + return eventChannel(emit => { + const errorHandler = event => emit(JSON.parse(event)) + + const userErrors = socket.subscribe('/user/queue/errors', errorHandler) + + const unsubscribe = () => { + userErrors.unsubscribe() + } + + return unsubscribe + }) +} + +export function* watchOnErrors(socketConnection) { + const { socket } = socketConnection + const socketChannel = createSocketChannel(socket) + + while (true) { + const payload = yield take(socketChannel) + yield put({ type: 'USER_ERROR', payload }) + } +} + +export default watchOnErrors diff --git a/src/main/js/src/containers/GameBrowser/actions.js b/src/main/js/src/containers/GameBrowser/actions.js new file mode 100644 index 00000000..376973b4 --- /dev/null +++ b/src/main/js/src/containers/GameBrowser/actions.js @@ -0,0 +1,16 @@ +import { NEW_GAME, JOIN_GAME, CREATE_GAME } from './constants' + +export const newGame = (game) => ({ + type: NEW_GAME, + game +}) + +export const joinGame = (id) => ({ + type: JOIN_GAME, + id +}) + +export const createGame = (name) => ({ + type: CREATE_GAME, + name +}) diff --git a/src/main/js/src/containers/GameBrowser/constants.js b/src/main/js/src/containers/GameBrowser/constants.js new file mode 100644 index 00000000..36f701b7 --- /dev/null +++ b/src/main/js/src/containers/GameBrowser/constants.js @@ -0,0 +1,3 @@ +export const NEW_GAME = 'gameBrowser/NEW_GAME' +export const JOIN_GAME = 'gameBrowser/JOIN_GAME' +export const CREATE_GAME = 'gameBrowser/CREATE_GAME' diff --git a/src/main/js/src/containers/GameBrowser/index.js b/src/main/js/src/containers/GameBrowser/index.js new file mode 100644 index 00000000..f9b3b29c --- /dev/null +++ b/src/main/js/src/containers/GameBrowser/index.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Flex } from 'reflexbox' +import { Text, Space } from 'rebass' + +class GameBrowser extends Component { + + listGames = (games) => { + return Object.keys(games).map(key => { + const game = games[key] + console.log('game', game, key) + return (<Flex key={key}> + <Text>{game.name}</Text> + <Space auto /> + <a href="#">Join</a> + </Flex>) + }) + } + + render() { + return ( + <div> + {this.listGames(this.props.games)} + </div> + ) + } +} + +const mapStateToProps = (state) => ({ + games: state.games +}) + +export default connect(mapStateToProps, {})(GameBrowser) diff --git a/src/main/js/src/containers/GameBrowser/reducer.js b/src/main/js/src/containers/GameBrowser/reducer.js new file mode 100644 index 00000000..5f98bfad --- /dev/null +++ b/src/main/js/src/containers/GameBrowser/reducer.js @@ -0,0 +1,16 @@ +import { NEW_GAME } from './constants' + +const initialState = { +} + +export default function reducer(state = initialState, action) { + switch (action.type) { + case NEW_GAME: + return { + ...state, + [action.game.id]: action.game + } + default: + return state + } +} diff --git a/src/main/js/src/containers/GameBrowser/saga.js b/src/main/js/src/containers/GameBrowser/saga.js new file mode 100644 index 00000000..8f947683 --- /dev/null +++ b/src/main/js/src/containers/GameBrowser/saga.js @@ -0,0 +1,70 @@ +import { call, put, take } from 'redux-saga/effects' +import { eventChannel } from 'redux-saga' + +import { NEW_GAME, JOIN_GAME, CREATE_GAME } from './constants' +import { newGame, joinGame } from './actions' + +function createSocketChannel(socket) { + return eventChannel(emit => { + const makeHandler = (type) => (event) => { + const response = JSON.parse(event.body) + emit({ + type, + response + }) + } + + const newGameHandler = makeHandler(NEW_GAME) + const joinGameHandler = makeHandler(JOIN_GAME) + + const newGame = socket.subscribe('/topic/games', newGameHandler) + const joinGame = socket.subscribe('/user/queue/join-game', joinGameHandler) + + const unsubscribe = () => { + newGame.unsubscribe() + joinGame.unsubscribe() + } + + return unsubscribe + }) +} + +export function* watchGames(socketConnection) { + + const { socket } = socketConnection + const socketChannel = createSocketChannel(socket) + + while (true) { + const { type, response } = yield take(socketChannel) + + switch (type) { + case NEW_GAME: + yield put(newGame(response)) + break; + case JOIN_GAME: + yield put(joinGame(response)) + break; + default: + console.error('Unknown type') + } + } +} + +export function* createGame(socketConnection) { + const { name } = yield take(CREATE_GAME) + const { socket } = socketConnection + console.log(socket) + socket.send("/app/lobby/create-game", JSON.stringify({ + 'gameName': name, + 'playerName': 'Cesar92' + }), {}) +} + +export function* gameBrowserSaga(socketConnection) { + yield [ + call(watchGames, socketConnection), + call(createGame, socketConnection) + ] +} + +export default gameBrowserSaga diff --git a/src/main/js/src/reducers.js b/src/main/js/src/reducers.js index 79014615..4bfaf7c1 100644 --- a/src/main/js/src/reducers.js +++ b/src/main/js/src/reducers.js @@ -1,8 +1,11 @@ import { combineReducers } from 'redux' import counterReducer from './containers/Counter/reducer' +import gamesReducer from './containers/GameBrowser/reducer' + export default function createReducer() { return combineReducers({ - counter: counterReducer + counter: counterReducer, + games: gamesReducer, }) -}
\ No newline at end of file +} diff --git a/src/main/js/src/sagas.js b/src/main/js/src/sagas.js index 24776bf2..92235434 100644 --- a/src/main/js/src/sagas.js +++ b/src/main/js/src/sagas.js @@ -1,7 +1,27 @@ -import { fork } from 'redux-saga/effects' +import { fork, call } from 'redux-saga/effects' + +import createWsConnection from './utils/createWebSocketConnection' import counterSaga from './containers/Counter/saga' +import errorSaga from './containers/App/saga' +import newGamesSaga from './containers/GameBrowser/saga' + +function* wsAwareSagas() { + let wsConnection + try { + wsConnection = yield call(createWsConnection) + } catch (error) { + console.error('Could not connect to socket') + return + } + + yield fork(errorSaga, wsConnection) + yield fork(newGamesSaga, wsConnection) +} export default function* rootSaga() { - yield fork(counterSaga) + yield [ + call(counterSaga), + call(wsAwareSagas) + ] } diff --git a/src/main/js/src/utils/createWebSocketConnection.js b/src/main/js/src/utils/createWebSocketConnection.js new file mode 100644 index 00000000..b0924976 --- /dev/null +++ b/src/main/js/src/utils/createWebSocketConnection.js @@ -0,0 +1,14 @@ +import SockJS from 'sockjs-client' +import Stomp from 'webstomp-client' +const wsURL = 'http://localhost:8080/seven-wonders-websocket' + +const createConnection = (headers = {}) => new Promise((resolve, reject) => { + let socket = Stomp.over(new SockJS(wsURL), { + debug: process.env.NODE_ENV !== "production" + }) + socket.connect(headers, (frame) => { + return resolve({ frame, socket }) + }, reject) +}) + +export default createConnection diff --git a/src/main/js/yarn.lock b/src/main/js/yarn.lock index 486bb724..5eeb79b4 100644 --- a/src/main/js/yarn.lock +++ b/src/main/js/yarn.lock @@ -1944,7 +1944,7 @@ events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" -eventsource@^0.1.3: +eventsource@^0.1.3, eventsource@~0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" dependencies: @@ -2039,6 +2039,12 @@ faye-websocket@^0.10.0: dependencies: websocket-driver ">=0.5.1" +faye-websocket@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.0.tgz#d9ccf0e789e7db725d74bc4877d23aa42972ac50" + dependencies: + websocket-driver ">=0.5.1" + faye-websocket@~0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.7.3.tgz#cc4074c7f4a4dfd03af54dd65c354b135132ce11" @@ -4711,6 +4717,17 @@ sockjs-client@^1.0.3, sockjs-client@1.0.3: json3 "^3.3.2" url-parse "^1.0.1" +sockjs-client@latest: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.1.tgz#284843e9a9784d7c474b1571b3240fca9dda4bb0" + dependencies: + debug "^2.2.0" + eventsource "~0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.1" + sockjs@^0.3.15: version "0.3.18" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207" @@ -5078,7 +5095,7 @@ url-loader@0.5.7: loader-utils "0.2.x" mime "1.2.x" -url-parse@^1.0.1: +url-parse@^1.0.1, url-parse@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a" dependencies: |