euchre-live

Euchre web-app for the socially distant family
git clone git://git.alexkarle.com/euchre-live.git
Log | Files | Refs | README | LICENSE

commit 4eb3b081e0600b1a28d3b448562f10a00faddca9 (patch)
parent 24f8594070f3b458d6f3bc5500d552245483e77c
Author: Chris Karle <chriskarle@hotmail.com>
Date:   Mon,  6 Apr 2020 01:48:22 -0400

App, CardTable, SeatPicker

Integrates first websocket comms for joining a room and
choosing a seat.  Introduces new SeatPicker class with basic
sit function, unstyled.

Adds new node modules (websocket, lodash)

Diffstat:
Massets/app.js | 28+++++++++++++++++++++-------
Massets/app.scss | 15+++++++++++++++
Massets/components/CardTable.js | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Massets/components/Lobby.js | 25+++++++++++++++++++++----
Aassets/components/SeatPicker.js | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackage-lock.json | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackage.json | 4+++-
7 files changed, 286 insertions(+), 21 deletions(-)

diff --git a/assets/app.js b/assets/app.js @@ -3,15 +3,23 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Lobby from './components/Lobby'; import CardTable from './components/CardTable'; +import { w3cwebsocket as W3CWebSocket } from 'websocket'; + +const client = new W3CWebSocket('ws://localhost:3000/play'); + + +const tableDebug = false; class App extends React.Component { constructor(props) { super(props); + const initialName = tableDebug ? 'Alex' : ''; + const initialTable = tableDebug ? 'periodic' : ''; this.state = { - playerName: '', - tableName: '', - showTable: false + playerName: initialName, + tableName: initialTable, + showTable: tableDebug }; } @@ -23,12 +31,16 @@ class App extends React.Component { chooseTable = tableName => { const show = tableName && tableName != ''; - this.setState( - { + this.setState( { tableName: tableName, showTable: show - } - ); + }, () => { + client.send(JSON.stringify({ + action:'join_game', + player_name: this.state.playerName, + game_id: tableName + })); + }); } render () { @@ -47,6 +59,8 @@ class App extends React.Component { chooseTable={this.chooseTable} name={playerName} tableName={tableName} + active={showTable} + client={client} /> )} </div> diff --git a/assets/app.scss b/assets/app.scss @@ -1,8 +1,14 @@ @import 'carbon-components/scss/globals/scss/styles.scss'; +.lobby__outer { + margin-top: 3rem; + margin-left: 3rem; +} + .textRow { display: flex; align-items: flex-start; + margin-top: 1rem; margin-left: 15px; width: 30rem; .bx--btn--icon-only { @@ -10,6 +16,15 @@ } } +.exit__row { + display: flex; + justify-content: flex-end; + align-items: baseline; +} +.leave__button { + font-size: 1.3rem; +} + .table__header { height: 10vh; } diff --git a/assets/components/CardTable.js b/assets/components/CardTable.js @@ -1,11 +1,63 @@ -import React, {Component} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; +import _ from 'lodash'; import {Button, Grid, Row, Column} from 'carbon-components-react'; import {Logout32} from '@carbon/icons-react'; +import SeatPicker from './SeatPicker'; export default class CardTable extends React.Component { + constructor(props) { + super(props); + this.state = { + playerNames: [], + mySeat: -1 + }; + }; + + componentDidMount () { + const websoc = this.props.client; + websoc.onmessage = (event) => this.processResponse(event); + }; + + processResponse = (event) => { + let msg = JSON.parse(event.data); + console.log(msg); + if ('game_state' == msg.msg_type) { + if ('lobby' == msg.game.phase){ + this.processLobby(msg); + } + }; + }; + + processLobby = (msg) => { + if (msg && msg.game && msg.game.players) { + const plAr = msg.game.players; + const mySeat = plAr.findIndex( x => x == this.props.name ); + this.setState({ + playerNames: plAr, + mySeat: mySeat + }) + }; + }; + + sendSit = (index) => { + const { client } = this.props; + client.send(JSON.stringify({ + action:'take_seat', + seat: index + })); + }; + + sendStand = () => { + const { client } = this.props; + client.send(JSON.stringify({ + action:'stand_up' + })); + }; + render () { + const { playerNames, mySeat } = this.state; const {name, tableName} = this.props; const welcomeMsg = 'Welcome to the ' + tableName + ' table, ' + name + '!'; return ( @@ -16,12 +68,13 @@ export default class CardTable extends React.Component { <h3>{welcomeMsg}</h3> </Column> <Column className="hd__right" sm={1}> - Exit - <Button - className="leave-button" - hasIconOnly - onClick={()=>{this.props.chooseTable('')}} - renderIcon={Logout32}></Button> + <div className="exit__row"> + <Button + className="leave__button" + kind="ghost" + onClick={()=>{this.props.chooseTable('')}} + renderIcon={Logout32}>Exit</Button> + </div> </Column> </Row> <Row className="table__top"> @@ -40,7 +93,13 @@ export default class CardTable extends React.Component { <div>yo!</div> </Column> <Column className="tm__center" sm={2}> - <div>yo!</div> + {/* <div>yo!</div> */} + <SeatPicker + names={playerNames} + handleSit={this.sendSit} + handleStand={this.sendStand} + mySeat={mySeat} + /> </Column> <Column className="tm__right" sm={1}> <div>yo!</div> @@ -68,5 +127,7 @@ export default class CardTable extends React.Component { CardTable.propTypes = { chooseTable: PropTypes.func, name: PropTypes.string, - tableName: PropTypes.string + tableName: PropTypes.string, + active: PropTypes.bool, + client: PropTypes.object } \ No newline at end of file diff --git a/assets/components/Lobby.js b/assets/components/Lobby.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import {Button, TextInput} from 'carbon-components-react'; import {Login32, CheckmarkOutline32} from '@carbon/icons-react'; @@ -15,6 +15,19 @@ export default class Lobby extends React.Component { } } + componentDidMount () { + if (this.nameText) { + this.nameText.focus(); + } + } + + componentDidUpdate (prevProps) { + const { name } = this.props; + if (name && (name != prevProps.name)){ + this.tableText.focus(); + } + } + handlePlayerIn = event => { const value = event.target.value; const error = this.checkName(value); @@ -42,7 +55,7 @@ export default class Lobby extends React.Component { const {name} = this.props; const {nameIn, nameError, tableIn, tableError} = this.state; return ( - <div id="lobby"> + <div id="lobby" className="lobby__outer"> <h2>Welcome to the Lobby</h2> {!name && <p>First tell us the name that you'll be using for this game...</p> @@ -56,13 +69,15 @@ export default class Lobby extends React.Component { invalidText="Sorry, letters A-Z a-z and spaces only" invalid={nameError} onChange={this.handlePlayerIn} + ref={(input) => {this.nameText = input;}} /> <Button className="name__button" hasIconOnly={true} onClick={()=>this.props.setName(nameIn)} renderIcon={CheckmarkOutline32} - iconDescription="" + iconDescription="set name" + tooltipPosition="bottom" disabled={nameError} /> </div> @@ -81,13 +96,15 @@ export default class Lobby extends React.Component { invalidText="Sorry, letters A-Z a-z and spaces only" invalid={tableError} onChange={this.handleTableIn} + ref={(input) => {this.tableText = input;}} /> <Button className="table__button" hasIconOnly onClick={()=>this.props.chooseTable(tableIn)} renderIcon={Login32} - iconDescription="" + iconDescription="go!" + tooltipPosition="bottom" disabled={tableError || !name || name==''} /> </div> diff --git a/assets/components/SeatPicker.js b/assets/components/SeatPicker.js @@ -0,0 +1,68 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Button, Grid, Row, Column} from 'carbon-components-react'; +import {Package32, Export32} from '@carbon/icons-react'; + +class SeatPicker extends React.Component { + + constructor(props) { + super(props); + //this.state - unneeded? + } + + + tableSeat = (name, index) => { + const taken = name != 'Empty'; + const mine = index === this.props.mySeat; + return ( + <div className="table__seat"> + {!taken && ( + <Button + className="sit__button" + kind="ghost" + onClick={()=>{this.props.handleSit(index)}} + renderIcon={Package32}>Choose seat</Button> + )} + {taken && (<span className="spName">{name}</span>)} + {taken && mine && ( + <Button + className="stand__button" + kind="ghost" + onClick={()=>{this.props.handleStand(index)}} + renderIcon={Export32}>Stand</Button> + )} + </div> + ) + } + + render () { + const {names} = this.props; + return ( + <div id="seatPicker" className="seat__picker"> + <Grid> + <Row className="sp__top"> + {this.tableSeat(names[2], 2)} + </Row> + <Row className="sp__mid"> + <Column className="spm__left"> + {this.tableSeat(names[1], 1)} + </Column> + <Column className="spm__right"> + {this.tableSeat(names[3], 3)} + </Column> + </Row> + <Row className="sp__bot"> + {this.tableSeat(names[0], 0)} + </Row> + </Grid> + </div> + ) + } +} +SeatPicker.propTypes = { + mySeat: PropTypes.number, + names: PropTypes.arrayOf(PropTypes.string), + handleSit: PropTypes.func, + handleStand: PropTypes.func +} +export default SeatPicker; diff --git a/package-lock.json b/package-lock.json @@ -2557,6 +2557,15 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2862,6 +2871,35 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2967,6 +3005,21 @@ "homedir-polyfill": "^1.0.1" } }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4998,6 +5051,11 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7578,11 +7636,24 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -7967,6 +8038,18 @@ } } }, + "websocket": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz", + "integrity": "sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -8050,6 +8133,11 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json @@ -15,6 +15,7 @@ "carbon-icons": "^7.0.7", "css-loader": "^3.4.2", "html-webpack-plugin": "^4.0.3", + "lodash": "^4.17.15", "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.13.1", "optimize-css-assets-webpack-plugin": "^5.0.3", @@ -24,6 +25,7 @@ "terser-webpack-plugin": "^2.3.5", "webpack": "^4.42.1", "webpack-cli": "^3.3.11", - "webpack-plugin-hash-output": "^3.2.1" + "webpack-plugin-hash-output": "^3.2.1", + "websocket": "^1.0.31" } }