Skip to content

Commit

Permalink
OAB-18 create admin panel
Browse files Browse the repository at this point in the history
  • Loading branch information
polakowski committed Jul 17, 2018
1 parent 4c99ab5 commit 555f3fc
Show file tree
Hide file tree
Showing 17 changed files with 261 additions and 16 deletions.
2 changes: 0 additions & 2 deletions .env

This file was deleted.

3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SLACK_WEBHOOK=https://hooks.slack.com/123/456/789
SLACK_LOG_WEBHOOK=https://hooks.slack.com/123/456/789
RASPBERRY=0|1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ images/*

.DS_Store

.env
14 changes: 10 additions & 4 deletions client/components/Game.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ import socketIOClient from 'socket.io-client'
import { logResult, resultResponse } from '../modules/result'
import { ToastContainer, toast } from 'react-toastify'

import withSocket from '@wrappers/withSocket'
import AdminPanel from '@components/admin-panel/AdminPanel'

const ROLLERS = ['left', 'center', 'right']

export default class Game extends React.Component {
class Game extends React.Component {
constructor(props) {
super(props)

this.state = {}
this.socket = socketIOClient('http://localhost:3000')

ROLLERS.forEach(roller => {
this[roller] = React.createRef()
})
}

componentDidMount() {
this.socket.on('SPIN_REQUEST', forcedSpinTo => this._spinMachine(forcedSpinTo))
this.socket.on('NOTIFY', (type, message) => toast[type](message))
const { socket } = this.props;
socket.on('SPIN_REQUEST', forcedSpinTo => this._spinMachine(forcedSpinTo))
socket.on('NOTIFY', (type, message) => toast[type](message))
}

_spinMachine(forcedSpinTo) {
Expand Down Expand Up @@ -51,9 +54,12 @@ export default class Game extends React.Component {
return (
<div className="rollers">
<ToastContainer autoClose={5000} position={toast.POSITION.TOP_CENTER} />
<AdminPanel />
<div className="overlay" />
{this._createRollers()}
</div>
)
}
}

export default withSocket(Game);
28 changes: 28 additions & 0 deletions client/components/admin-panel/ActionButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import withSocket from '@wrappers/withSocket'

class ActionButton extends Component {
_onClick(message) {
this.props.socket.emit(message)
}

render() {
const { text, socketAction } = this.props

return (
<a href='#' onClick={() => this._onClick(socketAction)}>{text}</a>
)
}
}

ActionButton.propTypes = {
text: PropTypes.string.isRequired,
socketAction: PropTypes.string.isRequired,
socket: PropTypes.shape({
emit: PropTypes.func.isRequired
}).isRequired
}

export default withSocket(ActionButton)
53 changes: 53 additions & 0 deletions client/components/admin-panel/AdminPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'

import ActionButton from '@components/admin-panel/ActionButton'
import withSocket from '@wrappers/withSocket'
import '@styles/admin_panel'

class AdminPanel extends Component {
constructor(props) {
super(props)
this.state = { active: false }
this._setActive = this._setActive.bind(this)
}

componentDidMount() {
this.props.socket.on('SET_ADMIN_PANEL_ACTIVE', this._setActive)
}

_setActive(value) {
this.setState({ active: value })
}

_getWrapperClass(active) {
const classes = ['admin-panel']
if (active) { classes.push('active') }
return classes.join(' ')
}

render() {
const { active } = this.state

return (
<div className={this._getWrapperClass(active)}>
<div className='admin-panel__actions'>
<ActionButton text='Reboot' socketAction='@admin/REBOOT' />
<ActionButton text='Shutdown' socketAction='@admin/SHUTDOWN' />
<ActionButton text='Trigger servo' socketAction='@admin/TRIGGER_SERVO' />
<a className='close' onClick={() => this._setActive(false)}>
Close
</a>
</div>
</div>
)
}
}

AdminPanel.propTypes = {
socket: PropTypes.shape({
on: PropTypes.func.isRequired
}).isRequired
}

export default withSocket(AdminPanel)
43 changes: 43 additions & 0 deletions client/styles/admin_panel.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.admin-panel {
position: absolute;
width: 100%;
height: 100%;
z-index: 10;
padding: 15px;
box-sizing: border-box;
will-change: top;
transition: top 0.6s cubic-bezier(0.4, 0.2, 0, 1);
top: -100%;
background: #30373f;

&.active {
top: 0%;
}

&__actions {
margin: 0 -10px 20px;
display: flex;

a {
font-family: Helvetica, sans-serif;
margin: 0 10px 20px;
padding: 0 20px;
background: #cccccc;
border: 1px solid #b1b2b3;
color: #6d6972;
border-radius: 3px;
font-size: 0.8em;
text-transform: uppercase;
line-height: 2.25em;
font-weight: bold;
}

a, a:hover, a:active, a:focus, a:visited {
text-decoration: none;
}

a.close {
margin-left: auto;
}
}
}
1 change: 1 addition & 0 deletions client/styles/game.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ body {
display: flex;
align-items: center;
justify-content: center;
position: relative;
}

.roller-wrapper {
Expand Down
26 changes: 26 additions & 0 deletions client/wrappers/withSocket.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { Component } from 'react'
import socketIOClient from 'socket.io-client'

let socket

export default function withSocket(WrappedComponent) {
class WithSocket extends Component {
constructor(props) {
super(props)

if (!socket) {
socket = socketIOClient('http://localhost:3000')
}
}

render() {
return (
<WrappedComponent socket={socket} {...this.props} />
)
}
}

WithSocket.displayName = `WithSocket(${WrappedComponent.name})`

return WithSocket;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react-toastify": "^4.1.0",
"redis": "^2.8.0",
"serialport": "^6.2.1",
"shelljs": "^0.8.2",
"slack-webhook": "^1.0.0",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1"
Expand Down
23 changes: 14 additions & 9 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import http from 'http'
import reader, { parseData } from './rfid'
import redis from 'redis'
import moment from 'moment'
import servo from './servo'
import sendPhoto from './queue'
import Slack from './slack'
import socketIO from 'socket.io'
Expand All @@ -27,12 +26,13 @@ let readyForSpin = false
let socketClient = null
let user = null

import handleAdminPanelBroadcasts from './serverModules/handleAdminPanelBroadcasts'
import handleSpinResult from './serverModules/handleSpinResult'

app.use(webpackMiddleware(webpack(webpackConfig)))
server.listen(3000)

function _rollRequest(userId) {
user = getUser(userId)

if (user === undefined) {
slack.log(userId)
socketClient.emit('NOTIFY', 'error', 'please go to @czana')
Expand Down Expand Up @@ -82,15 +82,20 @@ io.on('connection', client => {

client.on('SPIN_ENDED', result => {
readyForSpin = true

if (result.win) {
if (result.cashPrize) servo.move()
slack.post(user.mention, result.icon, result.cashPrize ? '$$$' : '2 Kudos!')
}
handleSpinResult(result, user);
})

handleAdminPanelBroadcasts(client);
})

reader.on('data', data => {
const userId = parseData(data)
_rollRequest(userId)
user = getUser(userId)

if (user && user.admin) {
socketClient.emit('SET_ADMIN_PANEL_ACTIVE', true);
} else {
socketClient.emit('SET_ADMIN_PANEL_ACTIVE', false);
_rollRequest(userId)
}
})
32 changes: 32 additions & 0 deletions src/serverModules/handleAdminPanelBroadcasts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import shell from 'shelljs'

import raspberryOnly from '../utils/raspberryOnly'

let servo

raspberryOnly(() => {
servo = require('../servo')['default']
})

export default (client) => {
client.on('@admin/REBOOT', () => {
raspberryOnly(() => {
shell.exec('sudo reboot')
client.emit('SET_ADMIN_PANEL_ACTIVE', false);
})
})

client.on('@admin/SHUTDOWN', () => {
raspberryOnly(() => {
shell.exec('sudo shutdown -P now')
client.emit('SET_ADMIN_PANEL_ACTIVE', false);
})
})

client.on('@admin/TRIGGER_SERVO', () => {
raspberryOnly(() => {
servo.move()
client.emit('SET_ADMIN_PANEL_ACTIVE', false);
})
})
}
16 changes: 16 additions & 0 deletions src/serverModules/handleSpinResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import raspberryOnly from '../utils/raspberryOnly'

let servo

raspberryOnly(() => {
servo = require('../servo')
})

export default (result, user) => {
if (result.win) {
raspberryOnly(() => {
if (result.cashPrize) { servo.move() }
})
slack.post(user.mention, result.icon, result.cashPrize ? '$$$' : '2 Kudos!')
}
}
1 change: 1 addition & 0 deletions src/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const USERS = {
5501512322897: {
mention: 'czana',
email: '[email protected]',
admin: true
},
4402038631408: {
mention: 'bart',
Expand Down
7 changes: 7 additions & 0 deletions src/utils/raspberryOnly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require('dotenv').config()

export default (theFunction) => {
if (process.env.RASPBERRY === '1') {
theFunction()
}
}
8 changes: 7 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import HtmlWebpackPlugin from 'html-webpack-plugin'
import path from 'path'

export default {
mode: 'development',
entry: './client/index.jsx',
resolve: {
extensions: ['.js', '.jsx', '.scss']
extensions: ['.js', '.jsx', '.scss'],
alias: {
'@components': path.resolve(__dirname, 'client/components'),
'@styles': path.resolve(__dirname, 'client/styles'),
'@wrappers': path.resolve(__dirname, 'client/wrappers')
}
},
output: {
path: '/',
Expand Down
18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3344,6 +3344,10 @@ ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"

interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"

invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
Expand Down Expand Up @@ -5517,6 +5521,12 @@ readline-utils@^2.2.1, readline-utils@^2.2.3:
strip-color "^0.1.0"
window-size "^1.1.0"

rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
dependencies:
resolve "^1.1.6"

redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
Expand Down Expand Up @@ -5980,6 +5990,14 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"

shelljs@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35"
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
rechoir "^0.6.2"

signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
Expand Down

0 comments on commit 555f3fc

Please sign in to comment.