commit e190a2a9802ddcf0b23ffe9880e33426de3d14e2 (patch)
parent 7bc03463b1acdfd80b0fff360efe315bfd842f72
Author: Chris Karle <chriskarle@hotmail.com>
Date: Fri, 29 May 2020 01:56:14 -0400
TableList, Lobby
Create the TableList widget with clickable-tile entries for
each table on the server. Also presents the CreateTable modal
and will join user to a new table per modal details. Lobby updates
to use TableList, dropping text input.
Diffstat:
4 files changed, 310 insertions(+), 35 deletions(-)
diff --git a/assets/app.js b/assets/app.js
@@ -7,6 +7,7 @@ import { w3cwebsocket as W3CWebSocket } from 'websocket';
// const client = new W3CWebSocket('ws://localhost:3000/play');
let client = null;
+let socketAddr, tablesAddr;
// additional fakeClient sockets, only used on tableDebug
let fc1, fc2, fc3 = null;
@@ -24,23 +25,52 @@ class App extends React.Component {
tableName: initialTable,
showTable: false,
uniqueError: false,
- firstMsg: null
+ firstMsg: null,
+ tableList: []
};
const host = window.location.host;
- const clientAddr = 'ws://' + host + '/play';
- client = new W3CWebSocket(clientAddr);
- client.onmessage = (event) => this.processResponse(event);
+ socketAddr = 'ws://' + host + '/play';
+ tablesAddr = 'http://' + host + '/tables';
if (tableDebug) {
+ client = new W3CWebSocket(socketAddr);
+ client.onmessage = (event) => this.processResponse(event);
+ this.pingTimer = setInterval(() => { this.sendPing(); }, 5000);
// on tableDebug send join plus add 3 fakeClient players that join+sit
- fc1 = new W3CWebSocket(clientAddr);
- fc2 = new W3CWebSocket(clientAddr);
- fc3 = new W3CWebSocket(clientAddr);
+ fc1 = new W3CWebSocket(socketAddr);
+ fc2 = new W3CWebSocket(socketAddr);
+ fc3 = new W3CWebSocket(socketAddr);
// wait 1 second so sockets establish connection
setTimeout(()=>{
this.setFakeGame(initialName, initialTable);
}, 1000);
}
- this.pingTimer = setInterval(() => { client.send(JSON.stringify({action:'ping'})) }, 5000);
+ }
+
+ componentDidMount () {
+ this.fetchTables();
+ }
+
+ sendPing = () => {
+ if (client) {
+ client.send(JSON.stringify({action:'ping'}));
+ }
+ }
+
+ fetchTables = () => {
+ fetch (tablesAddr).then ((response) => {
+ if (response.ok){
+ response.json().then((data) => {
+ console.log(data);
+ this.setState({
+ tableList: data.tables
+ });
+ });
+ } else {
+ console.log('BadResponse:', response);
+ }
+ }).catch ((error) => {
+ console.log('Caught error:', error);
+ });
}
setPlayerName = name => {
@@ -92,7 +122,8 @@ class App extends React.Component {
client.onmessage = (event) => this.processResponse(event);
this.setState({
tableName: '',
- showTable: false
+ showTable: false,
+ firstMsg: null
}, () => {
client.send(JSON.stringify({
action:'leave_table'
@@ -100,25 +131,33 @@ class App extends React.Component {
});
}
- chooseTable = tableName => {
- if (!tableName || tableName == ''){
+ joinTable = tableInfo => {
+ let clientConnectTimeout = 0;
+ if (client == null){
+ client = new W3CWebSocket(socketAddr);
client.onmessage = (event) => this.processResponse(event);
- this.setState({
- firstMsg: null
- });
+ this.pingTimer = setInterval(() => { this.sendPing(); }, 5000);
+ clientConnectTimeout = 1000;
+ }
+ const tableName = tableInfo.table;
+ if (!tableName || tableName == ''){
+ console.log('Empty table name!!');
};
- this.setState( {
- tableName: tableName,
- showTable: false
- }, () => {
- if (tableName && tableName != ''){
- client.send(JSON.stringify({
- action:'join_table',
- player_name: this.state.playerName,
- table: tableName
- }));
- }
- });
+ setTimeout(()=>{
+ this.setState( {
+ tableName: tableName,
+ showTable: false
+ }, () => {
+ if (tableName && tableName != ''){
+ tableInfo.action = 'join_table';
+ client.send(JSON.stringify(tableInfo));
+ }
+ });
+ }, clientConnectTimeout);
+ }
+
+ createTable = tableObj => {
+ console.log('App.createTable:', tableObj);
}
setFakeGame = (initialName, initialTable) => {
@@ -132,15 +171,18 @@ class App extends React.Component {
}
render () {
- const {showTable, playerName, tableName, firstMsg, uniqueError} = this.state;
+ const {showTable, playerName, tableName, firstMsg, uniqueError, tableList} = this.state;
return (
<div id="top-app">
{!showTable && (
<Lobby
setName={this.setPlayerName}
- chooseTable={this.chooseTable}
+ joinTable={this.joinTable}
name={playerName}
uniqueError={uniqueError}
+ tableList={tableList}
+ refreshTables={this.fetchTables}
+ createTable={this.createTable}
/>
)}
{showTable && (
diff --git a/assets/app.scss b/assets/app.scss
@@ -319,6 +319,48 @@
.tb__left {
padding-left: 2rem;
}
+.tlist__title {
+ font-size: 1.75rem;
+ font-weight: 400;
+ padding-right: 0.5rem;
+}
+.tlist__title__row {
+ display: flex;
+ align-items: center;
+}
+.tlist__main {
+ border: 3px solid darkgray;
+ margin-right: 4rem;
+}
+.tlist__cntl {
+ border-bottom: 1px solid;
+}
+.tlist__none {
+ margin: 1rem;
+}
+.tlist__holder {
+ height: 45vh;
+ overflow: hidden;
+}
+.tlist__list {
+ height: 45vh;
+ overflow: auto;
+ .bx--tile{
+ margin-bottom: 5px;
+ }
+}
+.table__name {
+ font-size: 1.2rem;
+ font-weight: 600;
+}
+.table__name--locked {
+ color: red;
+}
+.table__conflict {
+ font-style: italic;
+ font-weight: 600;
+ color: red;
+}
// .hd__left {
// background-color: rosybrown;
diff --git a/assets/components/Lobby.js b/assets/components/Lobby.js
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import {Button, TextInput} from 'carbon-components-react';
import {Login32, CheckmarkOutline32} from '@carbon/icons-react';
+import TableList from './TableList';
export default class Lobby extends React.Component {
@@ -24,7 +25,7 @@ export default class Lobby extends React.Component {
componentDidUpdate (prevProps) {
const { name } = this.props;
if (name && (name != prevProps.name)){
- this.tableText.focus();
+ this.setButton.blur();
}
}
@@ -52,7 +53,7 @@ export default class Lobby extends React.Component {
}
render () {
- const {name, uniqueError} = this.props;
+ const {name, uniqueError, tableList} = this.props;
const {nameIn, nameError, tableIn, tableError} = this.state;
return (
<div id="lobby" className="lobby__outer">
@@ -66,6 +67,7 @@ export default class Lobby extends React.Component {
className="lobby__name__input"
placeholder="Name to display at table"
size="xl"
+ labelText=""
invalidText="Sorry, letters A-Z a-z and spaces only"
invalid={nameError}
onChange={this.handlePlayerIn}
@@ -79,6 +81,7 @@ export default class Lobby extends React.Component {
iconDescription="set name"
tooltipPosition="bottom"
disabled={nameError}
+ ref={(button) => {this.setButton = button;}}
/>
</div>
<br/><br/>
@@ -86,12 +89,23 @@ export default class Lobby extends React.Component {
<div>
<h3>Welcome, {name}!</h3>
<p>You can change that name if you wish by entering a new one above.</p>
- <p>Next tell us the name of the table you'd like to join -- use the same table name as the users you would like to play.</p>
+ <br/><br/>
+ <div>
+ <TableList
+ tables={tableList}
+ playerName={name}
+ joinTable={this.props.joinTable}
+ refresh={this.props.refreshTables}
+ />
+ </div>
+ {/* <br/><br/>
+ <p>...or type a table name (deprecated, soon to be removed)</p>
<div className="textRow">
<TextInput
id="lobby__table"
className="lobby__table__input"
size="xl"
+ labelText=""
placeholder="Table choice?"
invalidText="Sorry, letters A-Z a-z and spaces only"
invalid={tableError}
@@ -101,14 +115,15 @@ export default class Lobby extends React.Component {
<Button
className="table__button"
hasIconOnly
- onClick={()=>this.props.chooseTable(tableIn)}
+ onClick={()=>this.props.joinTable({table: tableIn, player_name: name})}
renderIcon={Login32}
iconDescription="go!"
tooltipPosition="bottom"
disabled={tableError || !name || name==''}
/>
- </div>
+ </div> */}
</div>
+
)}
{uniqueError && (
<div className="unique__error">
@@ -123,7 +138,10 @@ export default class Lobby extends React.Component {
Lobby.propTypes = {
setName: PropTypes.func,
- chooseTable: PropTypes.func,
+ joinTable: PropTypes.func,
name: PropTypes.string,
- uniqueError: PropTypes.bool
+ uniqueError: PropTypes.bool,
+ tableList: PropTypes.array,
+ refreshTables: PropTypes.func,
+ createTable: PropTypes.func
}
\ No newline at end of file
diff --git a/assets/components/TableList.js b/assets/components/TableList.js
@@ -0,0 +1,172 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ClickableTile, Button, ModalWrapper, TextInput, Checkbox } from 'carbon-components-react';
+import {Renew16, Locked16} from '@carbon/icons-react';
+
+export default class TableList extends React.Component {
+
+ conflictHandler = (tableName) => {
+ const {playerName} = this.props;
+ alert('The "' + tableName + '" table already has a player named ' + playerName
+ + ' -- you must change your name to join this table, or choose another table.');
+ }
+
+ handleJoin = (tableName, hasPwd) => {
+ console.log('handleJoin, hasPwd=', hasPwd);
+ const {playerName} = this.props;
+ let joinInfo = {
+ table: tableName,
+ player_name: playerName
+ };
+ this.props.joinTable(joinInfo);
+ }
+
+ handleCreate = () => {
+ const {playerName} = this.props;
+ console.log('handleCreate, optHpick:', this.optHpick.value);
+ const tableName = this.ctName.value;
+ if (!tableName || tableName == ''){
+ alert('Unnamed tables are not permitted');
+ } else {
+ const pwd = this.ctPwd.value;
+ let joinInfo = {
+ table: tableName,
+ player_name: playerName
+ }
+ if (pwd && pwd != ''){
+ joinInfo.password = pwd;
+ }
+ this.props.joinTable(joinInfo);
+ }
+ }
+
+ handleRefresh = () => {
+ this.refreshButton.blur();
+ this.props.refresh();
+ }
+
+ renderCards = () => {
+ const {tables, playerName} = this.props;
+ let retVal = [];
+ if (tables){
+ tables.forEach(table => {
+ const conflict = table.players.indexOf(playerName) > -1
+ || table.spectators.indexOf(playerName) > -1;
+ const conflictWarning = conflict ?
+ (<div className="table__conflict">A user named "{playerName}" is at this table</div>) : null;
+ const clickHandler = conflict ? this.conflictHandler : this.handleJoin;
+ let seated = '';
+ for (let ni = 0; ni < 4; ni++) {
+ if (table.players[ni] != 'Empty'){
+ if (seated != ''){
+ seated += ', ';
+ }
+ seated += table.players[ni];
+ }
+ }
+ let specs = '';
+ table.spectators.forEach(spName => {
+ if (specs != ''){
+ specs += ', ';
+ }
+ specs += spName;
+ })
+ const lockIcon = table.has_password ? (<Locked16 fill="red" description="locked table"/>) : null;
+ const nameClass = table.has_password ? "table__name table__name--locked" : "table__name";
+ retVal.push(
+ <ClickableTile
+ key={table.name}
+ handleClick={() => clickHandler(table.name, table.has_password)}
+ >
+ <div className={nameClass}>{lockIcon}{table.name}</div>
+ {conflictWarning}
+ <div className="table__players">Seated: {seated}</div>
+ <div className="table__spectators">Spectators: {specs}</div>
+ </ClickableTile>
+ )
+ });
+ }
+ if (retVal.length == 0) {
+ retVal = (
+ <div className="tlist__none">
+ No tables yet -- create one or refresh if expecting one...
+ </div>
+ );
+ }
+ return retVal;
+ }
+
+ render () {
+ const {tables} = this.props;
+ const showCards = tables.length > 0;
+ const cards = this.renderCards();
+ return (
+ <div className="tlist__outer">
+ <div className="tlist__header">
+ <div className="tlist__title__row">
+ <div className="tlist__title">Click a table to join it... or</div>
+ <ModalWrapper
+ className="create__modal"
+ buttonTriggerText="Create a New Table"
+ primaryButtonText="Create"
+ modalHeading="New Table"
+ handleSubmit={this.handleCreate}
+ shouldCloseAfterSubmit={true}
+ >
+ <TextInput
+ id="ct__name"
+ placeholder="name your table"
+ labelText="Table Name"
+ ref={(input) => {this.ctName = input;}}
+ />
+ <br/>
+ <TextInput
+ id="ct__pwd"
+ placeholder="leave blank for no password"
+ labelText="Table Password (optional)"
+ ref={(input) => {this.ctPwd = input;}}
+ />
+ <br/>
+ <fieldset className="ct__optSet">
+ <legend className="ct__optLabel">Game Options</legend>
+ <Checkbox className="opt__hpick" id="opt__hpick" ref={(input) => {this.optHpick = input}}
+ defaultChecked labelText="Dealer must have suit to pick up"/>
+ <Checkbox className="opt__horder" id="opt__horder" ref={(input) => {this.optHorder = input}}
+ defaultChecked labelText="Must play alone if ordering partner"/>
+ <Checkbox className="opt__stick" id="opt__stick" ref={(input) => {this.optStick = input}}
+ labelText="Stick the dealer" />
+ </fieldset>
+ </ModalWrapper>
+ </div>
+ {/* planned: filterByTableName, filterByPlayerName */}
+ </div>
+ <div className="tlist__main">
+ <div className="tlist__cntl">
+ <Button
+ className="tlist__refresh"
+ hasIconOnly
+ onClick={this.handleRefresh}
+ renderIcon={Renew16}
+ size="small"
+ iconDescription="Refresh List"
+ tooltipPosition="bottom"
+ ref={(button) => {this.refreshButton = button;}}
+ />
+ </div>
+ <div className="tlist__holder">
+ <div className="tlist__list">
+ {cards}
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+}
+TableList.propTypes = {
+ tables: PropTypes.array,
+ playerName: PropTypes.string,
+ joinTable: PropTypes.func,
+ refresh: PropTypes.func
+}
+\ No newline at end of file