basic frontend structure

This commit is contained in:
Markus Schubert 2020-03-12 19:37:37 +01:00
parent c3ece37824
commit f2ec466e2d
25 changed files with 81 additions and 1516 deletions
client
package.json
public
src
actions
components
AppConfig
AppHeader
AppRouter.js
FileList
FileUpload
NumericInput
PostLabourIndex
QuestionList
Scoreboard
TotoObjectControls
index.js
pages
ConfigPage
ControlsPage
DashboardPage
FilesPage
ScoreboardPage
index.js
reducers
services

View file

@ -27,5 +27,5 @@
"not ie <= 11", "not ie <= 11",
"not op_mini all" "not op_mini all"
], ],
"proxy": "http://localhost:3001" "proxy": "http://localhost:9051"
} }

Binary file not shown.

Before

Width: 64px  |  Height: 64px  |  Size: 17 KiB

After

Width: 64px  |  Height: 64px  |  Size: 17 KiB

Binary file not shown.

After

(image error) Size: 285 KiB

View file

@ -22,7 +22,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>NWAP - Stand der Dinge</title> <title>Quarantale</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View file

@ -1,218 +1,22 @@
import ApiService from '../services/ApiService'; import ApiService from '../services/ApiService';
import { import {
CONFIG_LOADED, QUESTIONS_LOADED,
CONFIG_LOAD_ERROR, QUESTIONS_LOAD_ERROR,
CONFIG_UPDATED,
CONFIG_SAVED,
OBJECT_STARTED,
OBJECT_PAUSED,
OBJECT_STOPPED,
OBJECT_STATUS_LOADED,
FILE_UPLOADED,
FILES_LOADED,
FILE_DELETED,
PARAMS_LOADED,
PARAMS_LOAD_ERROR,
PLAYERS_LOADED,
PLAYERS_LOAD_ERROR
} from './types'; } from './types';
export const loadQuestions = (userId) => dispatch => {
export const loadConfig = (onSuccess, onError) => dispatch => {
const service = new ApiService(); const service = new ApiService();
const scb = (data) => { const scb = (data) => {
dispatch({ dispatch({
type: CONFIG_LOADED, type: QUESTIONS_LOADED,
config: data data: data
}); });
if (onSuccess) onSuccess(data);
} }
const ecb = (error) => { const ecb = (error) => {
dispatch({ dispatch({
type: CONFIG_LOAD_ERROR, type: QUESTIONS_LOAD_ERROR,
error: error error: error
}); });
if (onError) onError(error);
} }
service.getConfig(scb, ecb); service.getQuestions(userId, scb, ecb);
};
export const updateConfig = (requestData, onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: CONFIG_UPDATED,
config: data
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.udpateConfig(requestData, scb, ecb);
};
export const saveConfig = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: CONFIG_SAVED,
config: data
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.saveConfig(scb, ecb);
};
export const getObjectConfig = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: OBJECT_STATUS_LOADED,
status: data.status
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.getObjectStatus(scb, ecb);
};
export const startObject = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: OBJECT_STARTED,
status: data.status
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.startObject(scb, ecb);
};
export const pauseObject = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: OBJECT_PAUSED,
status: data.status
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.pauseObject(scb, ecb);
};
export const stopObject = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: OBJECT_STOPPED,
status: data.status
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.stopObject(scb, ecb);
};
export const fileUploaded = (fileData) => dispatch => {
dispatch({
type: FILE_UPLOADED,
file: fileData
});
}
export const loadFiles = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: FILES_LOADED,
files: data
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.getFiles(scb, ecb);
};
export const uploadFile = (formData, onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: FILE_UPLOADED,
file: data
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.uploadFile(formData, scb, ecb);
};
export const deleteFile = (name, onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: FILE_DELETED,
fileName: name
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
if (onError) onError(error);
}
service.deleteFile(name, scb, ecb);
};
export const loadParams = (onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: PARAMS_LOADED,
params: data
});
if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
dispatch({
type: PARAMS_LOAD_ERROR,
error: error
});
if (onError) onError(error);
}
service.getParams(scb, ecb);
};
export const loadPlayers = (showId, onSuccess, onError) => dispatch => {
const service = new ApiService();
const scb = (data) => {
dispatch({
type: PLAYERS_LOADED,
players: data
});
//if (onSuccess) onSuccess(data);
}
const ecb = (error) => {
dispatch({
type: PLAYERS_LOAD_ERROR,
error: error
});
//if (onError) onError(error);
}
service.getPlayers(showId, scb, ecb);
}; };

View file

@ -1,15 +1,2 @@
export const CONFIG_LOADED = 'CONFIG_LOADED'; export const QUESTIONS_LOADED = 'QUESTIONS_LOADED';
export const CONFIG_LOAD_ERROR = 'CONFIG_LOAD_ERROR'; export const QUESTIONS_LOAD_ERROR = 'QUESTIONS_LOADED';
export const CONFIG_UPDATED = 'CONFIG_SAVED';
export const CONFIG_SAVED = 'CONFIG_SAVED';
export const OBJECT_STARTED = 'OBJECT_STARTED';
export const OBJECT_PAUSED = 'OBJECT_PAUSED';
export const OBJECT_STOPPED = 'OBJECT_STOPPED';
export const OBJECT_STATUS_LOADED = 'OBJECT_STATUS_LOADED';
export const FILE_UPLOADED = 'FILE_UPLOADED';
export const FILES_LOADED = 'FILES_LOADED';
export const FILE_DELETED = 'FILE_DELETED';
export const PARAMS_LOADED = 'PARAMS_LOADED';
export const PARAMS_LOAD_ERROR = 'PARAMS_LOAD_ERROR';
export const PLAYERS_LOADED = 'PLAYERS_LOADED';
export const PLAYERS_LOAD_ERROR = 'PLAYERS_LOAD_ERROR';

View file

@ -1,395 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { loadConfig, updateConfig, saveConfig } from '../../actions/appActions';
import { Form, Switch, Select, Input, Button, Icon, Card, Tooltip } from 'antd';
import FormItem from 'antd/lib/form/FormItem';
import { NumericInput } from '../';
class AppConfig extends Component {
constructor(props) {
super(props);
this.state = {
config: {},
loadingConfig: true,
updatingConfig: false,
savingConfig: false,
configLoadError: false,
configUpdateError: false,
configSaveError: false
}
}
componentWillMount() {
let scb = (data) => {
if (data.custom === undefined) {
data.custom = {};
};
if (data.custom.printer === undefined) {
data.custom.printer = {};
}
if (data.custom.printer.margins === undefined) {
data.custom.printer.margins = {
top: 0,
bottom: 0,
left: 10,
right: 10
};
}
this.setState({
config: data,
loadingConfig: false
});
}
let ecb = (error) => {
this.setState({
configLoadError: error,
loadingConfig: false
});
}
this.props.loadConfig(scb, ecb);
}
updateConfig = (e) => {
e.preventDefault();
this.setState({
updatingConfig: true
});
let scb = (data) => {
this.setState({
config: data,
updatingConfig: false
});
}
let ecb = (error) => {
this.setState({
configUpdateError: error,
updatingConfig: false
});
}
this.props.updateConfig(this.state.config, scb, ecb);
};
saveConfig = (e) => {
e.preventDefault();
this.setState({
savingConfig: true
});
let scb = (data) => {
this.setState({
config: data,
savingConfig: false
});
}
let ecb = (error) => {
this.setState({
configSaveError: error,
savingConfig: false
});
}
this.props.saveConfig(scb, ecb);
};
handleChange = name => event => {
let v = 0;
let c = JSON.parse(JSON.stringify(this.state.config));
switch (name) {
case 'key':
c.objectOptions.key = event.target.value;
break;
case 'objectType':
c.objectType = event;
break;
case 'secret':
c.objectOptions.secret = event.target.value;
break;
case 'server':
c.objectOptions.server = event.target.value;
break;
case 'dataDir':
c.dataDir = event.target.value;
break;
case 'tempDir':
c.tempDir = event.target.value;
break;
case 'polling':
const pi = parseInt(event, 10);
c.objectOptions.pollingInterval = pi;
break;
case 'start':
c.startsImmediately = !c.startsImmediately;
break;
case 'printermargintop':
v = parseInt(event, 10);
c.custom.printer.margins.top = v;
break;
case 'printermarginbottom':
v = parseInt(event, 10);
c.custom.printer.margins.bottom = v;
break;
case 'printermarginleft':
v = parseInt(event, 10);
c.custom.printer.margins.left = v;
break;
case 'printermarginright':
v = parseInt(event, 10);
c.custom.printer.margins.right = v;
break;
case 'printerAdditionalCmdArgs':
c.custom.printer.additionalCmdArgs = event.target.value;
break;
default:
// do nothing
}
this.setState({
config: c
});
};
render() {
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
let updateButtonDisabled = JSON.stringify(this.props.config) === JSON.stringify(this.state.config);
let saveButtonDisabled = true;
if (updateButtonDisabled === true && this.state.config.saved === false) saveButtonDisabled = false;
let error = '';
if (this.state.configUpdateError) error = this.state.configUpdateError;
if (this.state.configSaveError) error = this.state.configSaveError;
const Option = Select.Option;
return (
<Card title="application config">
{this.state.loadingConfig &&
<pre>loading config...</pre>
}
{!this.state.loadingConfig &&
<Form>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Determines if the server should start polling immediately after bootup.'>
<span>starts immediately</span>
</Tooltip>
)}>
<Switch
checked={this.state.config.startsImmediately}
onChange={this.handleChange('start')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Select the type of the object.'>
<span>object type</span>
</Tooltip>
)}>
<Select defaultValue={this.state.config.objectType} style={{ width: 150 }} onChange={this.handleChange('objectType')}>
<Option value='default'>Default</Option>
<Option value='osc'>OSC</Option>
<Option value="printer">Printer</Option>
</Select>
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter the path to the data directory.'>
<span>data directory</span>
</Tooltip>
)}>
<Input
value={this.state.config.dataDir}
onChange={this.handleChange('dataDir')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter the path to the temp directory.'>
<span>temp directory</span>
</Tooltip>
)}>
<Input
value={this.state.config.tempDir}
onChange={this.handleChange('tempDir')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter the url of the toto object service instance.'>
<span>server</span>
</Tooltip>
)}>
<Input
value={this.state.config.objectOptions.server}
onChange={this.handleChange('server')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter the key of the toto object.'>
<span>object key</span>
</Tooltip>
)}>
<Input
value={this.state.config.objectOptions.key}
onChange={this.handleChange('key')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter the secret of the toto object.'>
<span>object secret</span>
</Tooltip>
)}>
<Input
value={this.state.config.objectOptions.secret}
onChange={this.handleChange('secret')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='How often should the object ask for new payloads.'>
<span>polling interval</span>
</Tooltip>
)}>
<NumericInput
style={{ width: 80 }}
value={this.state.config.objectOptions.pollingInterval}
onChange={this.handleChange('polling')} />
</Form.Item>
<FormItem {...formItemLayout}
label={(
<Tooltip title='If it is saved on the object so it will automatically load when start the device up.'>
<span>saved</span>
</Tooltip>
)}>
{this.state.config.saved &&
<Icon type="check" />
}
</FormItem>
<hr />
<Form.Item {...formItemLayout}
label={(
<Tooltip title='custom printer processor properties'>
<span>custom printer</span>
</Tooltip>
)}>
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Margin top.'>
<span>margin top</span>
</Tooltip>
)}>
<NumericInput
style={{ width: 80 }}
value={this.state.config.custom.printer.margins.top}
onChange={this.handleChange('printermargintop')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Margin bottom.'>
<span>margin bottom</span>
</Tooltip>
)}>
<NumericInput
style={{ width: 80 }}
value={this.state.config.custom.printer.margins.bottom}
onChange={this.handleChange('printermarginbottom')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Margin left.'>
<span>margin left</span>
</Tooltip>
)}>
<NumericInput
style={{ width: 80 }}
value={this.state.config.custom.printer.margins.left}
onChange={this.handleChange('printermarginleft')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Margin right.'>
<span>margin right</span>
</Tooltip>
)}>
<NumericInput
style={{ width: 80 }}
value={this.state.config.custom.printer.margins.right}
onChange={this.handleChange('printermarginright')} />
</Form.Item>
<Form.Item {...formItemLayout}
label={(
<Tooltip title='Enter additional command line arguments.'>
<span>additional cmd args</span>
</Tooltip>
)}>
<Input
value={this.state.config.custom.printer.additionalCmdArgs}
onChange={this.handleChange('printerAdditionalCmdArgs')} />
</Form.Item>
<hr />
<Form.Item {...tailFormItemLayout}>
<Button
style={{ width: 100 }}
disabled={updateButtonDisabled}
type="primary"
onClick={this.updateConfig}>
update
</Button>
<Button
style={{ marginLeft: 20, width: 100 }}
disabled={saveButtonDisabled}
type="primary"
onClick={this.saveConfig}>
save
</Button>
</Form.Item>
<FormItem {...tailFormItemLayout}
validateStatus="error"
help={error.message}>
</FormItem>
</Form>
}
</Card>
)
};
}
AppConfig.propTypes = {
loadConfig: PropTypes.func.isRequired,
updateConfig: PropTypes.func.isRequired,
saveConfig: PropTypes.func.isRequired,
config: PropTypes.object
}
const mapStateToProps = state => ({
config: state.appData.config,
configLoadError: state.appData.configLoadError
});
export default connect(mapStateToProps, { loadConfig, updateConfig, saveConfig })(AppConfig);

View file

@ -1,10 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; //import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { loadParams } from '../../actions/appActions'; //import { loadParams } from '../../actions/appActions';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { Layout, Menu } from 'antd'; import { Layout } from 'antd';
import 'antd/dist/antd.css'; import 'antd/dist/antd.css';
const { Header } = Layout; const { Header } = Layout;
@ -15,7 +15,7 @@ const styles = {
color: 'white', color: 'white',
fontSize: '1.4em', fontSize: '1.4em',
marginRight: 30, marginRight: 30,
fontFamily: 'Super-FamiFont' fontFamily: 'Helvetica'
}, },
logoImage: { logoImage: {
@ -40,60 +40,25 @@ class AppHeader extends Component {
render() { render() {
let selectedKeys = [];
if (this.props.location.pathname === '/controls') selectedKeys = (['controls']);
if (this.props.location.pathname === '/config') selectedKeys = (['config']);
if (this.props.location.pathname === '/files') selectedKeys = (['files']);
let menu;
if (selectedKeys.length > 0) {
menu =
<Menu
theme="dark"
mode="horizontal"
selectedKeys={selectedKeys}
style={{ lineHeight: '64px' }}>
<Menu.Item key="controls">
<Link to="/controls">controls</Link>
</Menu.Item>
<Menu.Item key="config">
<Link to="/config">config</Link>
</Menu.Item>
<Menu.Item key="files">
<Link to="/files">files</Link>
</Menu.Item>
</Menu>
}
let currentShow = this.props.params.find((param) => {
return param.key === 'current show';
});
if (currentShow) {
currentShow = currentShow.value;
} else {
currentShow = '';
}
return ( return (
<Header style={{ position: 'fixed', zIndex: 1, width: '100%', background: '#3399cc' }}> <Header style={{ position: 'fixed', zIndex: 1, width: '100%', background: '#3399cc' }}>
<div style={styles.logo}> <div style={styles.logo}>
<img style={styles.logoImage} src='images/nwap_logo.png' alt='' onClick={this.reloadParams} /> <img style={styles.logoImage} src='images/logo.jpg' alt='' />
<Link style={styles.logo} to="/">Post Labour Index</Link> <Link style={styles.logo} to="/">Quarantale</Link>
<span style={styles.hiddenShowId}>{currentShow}</span>
</div> </div>
{menu}
</Header> </Header>
) )
} }
}; };
AppHeader.propTypes = { AppHeader.propTypes = {
loadParams: PropTypes.func.isRequired, // loadParams: PropTypes.func.isRequired,
params: PropTypes.array // params: PropTypes.array
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
params: state.appData.params, // params: state.appData.params,
paramsLoadError: state.appData.paramsLoadError // paramsLoadError: state.appData.paramsLoadError
}); });
export default withRouter(connect(mapStateToProps, { loadParams })(AppHeader)); export default withRouter(connect(mapStateToProps, { })(AppHeader));

View file

@ -1,9 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; //import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { loadParams } from '../actions/appActions'; //import { loadParams } from '../actions/appActions';
import { BrowserRouter as Router, Route } from 'react-router-dom'; import { BrowserRouter as Router, Route } from 'react-router-dom';
import { ConfigPage, ControlsPage, FilesPage, ScoreboardPage } from '../pages'; import { DashboardPage } from '../pages';
import { Layout } from 'antd'; import { Layout } from 'antd';
import { AppHeader } from '../components'; import { AppHeader } from '../components';
import 'antd/dist/antd.css'; import 'antd/dist/antd.css';
@ -13,7 +13,7 @@ const { Content } = Layout;
class AppRouter extends Component { class AppRouter extends Component {
componentWillMount() { componentWillMount() {
this.props.loadParams();
}; };
render() { render() {
@ -24,10 +24,7 @@ class AppRouter extends Component {
<AppHeader /> <AppHeader />
<Content style={{ padding: '0 50px', marginTop: 64 }}> <Content style={{ padding: '0 50px', marginTop: 64 }}>
<div style={{ background: '#fff', padding: 24, minHeight: '100%' }}> <div style={{ background: '#fff', padding: 24, minHeight: '100%' }}>
<Route exact path='/' component={ScoreboardPage} /> <Route exact path='/' component={DashboardPage} />
<Route exact path='/controls' component={ControlsPage} />
<Route exact path='/config' component={ConfigPage} />
<Route exact path='/files' component={FilesPage} />
</div> </div>
</Content> </Content>
</Layout> </Layout>
@ -37,13 +34,9 @@ class AppRouter extends Component {
}; };
AppRouter.propTypes = { AppRouter.propTypes = {
loadParams: PropTypes.func.isRequired,
params: PropTypes.array
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
params: state.appData.players,
paramsLoadError: state.appData.paramsLoadError
}); });
export default connect(mapStateToProps, { loadParams })(AppRouter); export default connect(mapStateToProps, { })(AppRouter);

View file

@ -1,71 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { loadFiles, deleteFile } from '../../actions/appActions';
import { Card, Table, Button, notification } from 'antd';
class FileList extends Component {
componentWillMount() {
this.props.loadFiles();
};
openNotificationWithIcon = (type, title, description) => {
notification[type]({
message: title,
description: description,
});
};
_deleteFile(record) {
let _this = this;
const scb = (data) => {
_this.openNotificationWithIcon('success', 'File deleted', '');
}
const ecb = (error) => {
_this.openNotificationWithIcon('error', 'Error deleting file', error.message);
}
this.props.deleteFile(record.name, scb, ecb);
};
render() {
const _this = this;
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
}, {
title: 'Action',
key: 'action',
render: (text, record) => (
<Button
type='danger'
size='small'
onClick={() => _this._deleteFile(record)}>
delete
</Button>
)
}];
return (
<Card title='files'>
<Table dataSource={this.props.files} columns={columns} />
</Card>
)
};
};
FileList.propTypes = {
loadFiles: PropTypes.func.isRequired,
deleteFile: PropTypes.func.isRequired,
files: PropTypes.array
}
const mapStateToProps = state => ({
files: state.appData.files
});
export default connect(mapStateToProps, { loadFiles, deleteFile })(FileList);

View file

@ -1,77 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { uploadFile, fileUploaded } from '../../actions/appActions';
import { Button, Icon, Card, Upload, notification } from 'antd';
class FileUpload extends Component {
openNotificationWithIcon = (type, title, description) => {
notification[type]({
message: title,
description: description,
});
};
render() {
let _this = this;
const uploadProps = {
action: '/api/files',
multiple: false,
headers: {},
onStart(file) {
},
onSuccess(ret, file) {
_this.openNotificationWithIcon('success', 'Upload successful', '');
},
onError(err) {
_this.openNotificationWithIcon('error', 'Upload failed', err.message);
},
onProgress({ percent }, file) {
console.log('onProgress', `${percent}%`, file.name);
},
customRequest({
action,
data,
file,
filename,
headers,
onError,
onProgress,
onSuccess,
}) {
const formData = new FormData();
formData.append(filename, file);
_this.props.uploadFile(formData, onSuccess, onError);
return {
abort() {
console.log('upload progress is aborted.');
},
};
},
};
return (
<Card title='file upload'>
<Upload {...uploadProps}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
</Card>
)
}
};
FileUpload.propTypes = {
uploadFile: PropTypes.func.isRequired,
fileUploaded: PropTypes.func.isRequired
}
const mapStateToProps = state => ({});
export default connect(mapStateToProps, {uploadFile, fileUploaded })(FileUpload);

View file

@ -1,42 +0,0 @@
import React, { Component } from 'react';
import { Input } from 'antd';
class NumericInput extends Component {
formatNumber = (value) => {
value += '';
const list = value.split('.');
const prefix = list[0].charAt(0) === '-' ? '-' : '';
let num = prefix ? list[0].slice(1) : list[0];
let result = '';
while (num.length > 3) {
result = `,${num.slice(-3)}${result}`;
num = num.slice(0, num.length - 3);
}
if (num) {
result = num + result;
}
return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}`;
}
onChange = (e) => {
const { value } = e.target;
const reg = /^-?(0|[1-9][0-9]*)(\.[0-9]*)?$/;
if ((!Number.isNaN(value) && reg.test(value)) || value === '' || value === '-') {
this.props.onChange(value);
}
}
render() {
return (
<Input
{...this.props}
onChange={this.onChange}
placeholder="Input a number"
maxLength={25}
/>
);
}
};
export default NumericInput;

View file

@ -1,166 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from "react-router";
import { loadPlayers, loadParams } from '../../actions/appActions';
import { Avatar } from 'antd';
const styles = {
rowGrundig: {
backgroundColor: '#333',
color: '#eee',
border: '1px solid lightgrey'
},
rowNormal: {
backgroundColor: 'white',
color: '#111',
border: '1px solid lightgrey'
},
tbl: {
border: '1px solid lightgrey',
cellPadding: '4',
cellSpacing: '4'
},
hiddenText: {
color: 'white'
},
textGrundig: {
fontSize: '1.8em',
fontWeight: 'bold'
}
};
class PostLabourIndex extends Component {
constructor(props) {
super(props);
this.state = {
players: [],
playersLoadError: false,
params: []
};
this.loadPlayerInterval = null;
}
componentDidMount() {
this.loadPlayerInterval = setInterval(
() => this.loadPlayers(),
3000
);
};
componentWillUnmount() {
clearInterval(this.loadPlayerInterval);
};
loadPlayers = () => {
let currentShow = this.props.params.find((param) => {
return param.key === 'current show';
});
if (currentShow) {
this.props.loadPlayers(currentShow.value);
}
};
render() {
const tableHeight = window.innerHeight - 150;
const tableContentStyle = {
tblContent: {
overflow: 'scroll',
height: tableHeight
}
}
let orderedPlayers = this.props.players.sort(function (a, b) {
return b.values.score - a.values.score;
});
for (var i = 0; i < orderedPlayers.length; i++) {
orderedPlayers[i].standing = i + 1;
}
const rows = orderedPlayers.map((player) => {
const standing = <td>{player.standing}</td>
const avatar = <td><Avatar size={64} src={'https://platform.thegameisover.de/v2/account/' + player.playerId + '/avatar?v=s'} /></td>
const name = <td>{player.name}</td>
if (player.group === 'grundig') {
return (
<tr style={styles.rowGrundig} key={player.standing}>
{standing}
{avatar}
{name}
<td colSpan="6" align='center'><span style={styles.textGrundig}>G.R.UN.DIG.</span></td>
</tr>
)
} else {
return (
<tr style={styles.rowNormal} key={player.standing}>
{standing}
{avatar}
{name}
<td>{player.cluster}</td>
<td style={styles.hiddenText}>{player.values.lust}</td>
<td>{player.values.power}</td>
<td>{player.values.psyche}</td>
<td>{player.values.structure}</td>
<td><span style={{ fontWeight: 'bold', fontSize: '1.2em' }}>{player.values.score}</span></td>
</tr>
)
}
});
return (
<div>
<table width='100%' cellPadding='5' border='0' style={styles.tbl}>
<thead>
<tr>
<th width="50px"></th>
<th width="100px">Avatar</th>
<th width="25%">Name</th>
<th width="25%">Berufsfeld</th>
<th width="50px"></th>
<th width="100px">Macht</th>
<th width="100px">Psyche</th>
<th width="100px">Struktur</th>
<th width="150px">Post Labour Score</th>
</tr>
</thead>
</table>
<div style={tableContentStyle.tblContent}>
<table width='100%' border='0' cellPadding='5' style={styles.tbl}>
<tbody>
{rows}
<tr style={{ color: 'white' }}>
<td width="50px"></td>
<td width="100px">&nbsp;</td>
<td width="25%">&nbsp;</td>
<td width="25%">num player: {orderedPlayers.length}</td>
<td width="50px">&nbsp;</td>
<td width="100px">&nbsp;</td>
<td width="100px">&nbsp;</td>
<td width="100px">&nbsp;</td>
<td width="150px">&nbsp;</td>
</tr>
</tbody>
</table>
</div>
</div>
)
}
}
PostLabourIndex.propTypes = {
loadPlayers: PropTypes.func.isRequired,
players: PropTypes.array,
loadParams: PropTypes.func.isRequired,
params: PropTypes.array
};
const mapStateToProps = state => ({
players: state.appData.players,
playersLoadError: state.appData.playersLoadError,
params: state.appData.params
});
export default withRouter(connect(mapStateToProps, { loadPlayers, loadParams })(PostLabourIndex));

View file

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { loadQuestions } from '../../actions/appActions';
import { connect } from 'react-redux';
class QuestionList extends Component {
render() {
return (
<h1>Questions</h1>
)
};
};
QuestionList.propTypes = {
loadQuestions: PropTypes.func.isRequired,
questions: PropTypes.array,
};
const mapStateToProps = state => ({
questions: state.appData.questions,
questionsLoadError: state.appData.questionsLoadError
});
export default connect(mapStateToProps, { loadQuestions })(QuestionList);

View file

@ -1,126 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { loadPlayers, loadParams } from '../../actions/appActions';
import { Avatar, Table } from 'antd';
class Scoreboard extends Component {
constructor(props) {
super(props);
this.state = {
players: [],
playersLoadError: false,
params: []
};
this.loadPlayerInterval = null;
}
componentDidMount() {
this.loadPlayerInterval = setInterval(
() => this.loadPlayers(),
3000
);
};
componentWillUnmount() {
clearInterval(this.loadPlayerInterval);
};
loadPlayers = () => {
let currentShow = this.props.params.find((param) => {
return param.key === 'current show';
});
if (currentShow) {
this.props.loadPlayers(currentShow.value);
}
};
render() {
let orderedPlayers = this.props.players.sort(function (a, b) {
return b.values.score - a.values.score;
});
for (var i = 0; i < orderedPlayers.length; i++) {
orderedPlayers[i].standing = i + 1;
}
const columns = [
{
title: '',
dataIndex: 'standing',
key: 'standing',
width: 50
}, {
title: 'Avatar',
dataIndex: 'playerId',
key: 'playerId',
width: 100,
render: (playerId) => (
<Avatar size={64} src={'https://platform.thegameisover.de/v2/account/' + playerId + '/avatar?v=s'} />
)
}, {
title: 'Name',
dataIndex: 'name',
key: 'name',
style: 'tableHeader'
}, {
title: 'Berufsfeld',
dataIndex: 'cluster',
key: 'cluster'
}, {
title: 'Macht',
dataIndex: 'values.power',
key: 'macht',
width: 100
}, {
title: 'Psyche',
dataIndex: 'values.psyche',
key: 'psyche',
width: 100
}, {
title: 'Struktur',
dataIndex: 'values.structure',
key: 'structure',
width: 100
}, {
title: 'Post Labour Score',
dataIndex: 'values.score',
key: 'score',
width: 200,
render: (score) => (
<span style={{ fontWeight: 'bold', fontSize: '1.2em' }}>{score}</span>
)
}
]
return (
<div>
<Table
columns={columns}
dataSource={orderedPlayers}
pagination={false}
rowKey='id'
size="small"
bordered
/>
</div>
)
}
}
Scoreboard.propTypes = {
loadPlayers: PropTypes.func.isRequired,
players: PropTypes.array,
loadParams: PropTypes.func.isRequired,
params: PropTypes.array
};
const mapStateToProps = state => ({
players: state.appData.players,
playersLoadError: state.appData.playersLoadError,
params: state.appData.params
});
export default connect(mapStateToProps, { loadPlayers, loadParams })(Scoreboard);

View file

@ -1,101 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Card, Button, notification } from 'antd';
import { startObject, pauseObject, stopObject, getObjectConfig } from '../../actions/appActions';
class TotoObjectControls extends Component {
componentWillMount() {
this.props.getObjectConfig();
}
openNotificationWithIcon = (type, title, description) => {
notification[type]({
message: title,
description: description,
});
};
_startObject = () => {
const scb = (data) => {
this.openNotificationWithIcon('success', 'object started', '');
}
const ecb = (error) => {
this.openNotificationWithIcon('error', 'error starting object', error.message);
}
this.props.startObject(scb, ecb);
}
_pauseObject = () => {
const scb = (data) => {
this.openNotificationWithIcon('success', 'object paused', '');
}
const ecb = (error) => {
this.openNotificationWithIcon('error', 'error pausing object', error.message);
}
this.props.pauseObject(scb, ecb);
}
_stopObject = () => {
const scb = (data) => {
this.openNotificationWithIcon('success', 'object stopped', '');
}
const ecb = (error) => {
this.openNotificationWithIcon('error', 'error stopping object', error.message);
}
this.props.stopObject(scb, ecb);
}
render() {
const startObjectDisabled = this.props.status === 'running';
const pauseObjectDisabled = this.props.status === 'paused' || this.props.status === 'stopped';
const stopObjectDisabled = this.props.status === 'stopped';
return (
<Card title="toto-object controlls">
<div style={{marginBottom: 30}}>
<h4>status: {this.props.status}</h4>
</div>
<div>
<Button
style={{ width: 120 }}
onClick={this._startObject}
type='primary'
disabled={startObjectDisabled}>
start
</Button>
<Button
style={{ width: 120, marginLeft: 20 }}
onClick={this._pauseObject}
type='primary'
disabled={pauseObjectDisabled}>
pause
</Button>
<Button
style={{ width: 120, marginLeft: 20 }}
onClick={this._stopObject}
type='primary'
disabled={stopObjectDisabled}>
stop
</Button>
</div>
</Card>
)
}
}
TotoObjectControls.propTypes = {
getObjectConfig: PropTypes.func.isRequired,
startObject: PropTypes.func.isRequired,
pauseObject: PropTypes.func.isRequired,
stopObject: PropTypes.func.isRequired,
status: PropTypes.string
}
const mapStateToProps = state => ({
status: state.appData.status
});
export default connect(mapStateToProps, { startObject, pauseObject, stopObject, getObjectConfig })(TotoObjectControls);

View file

@ -1,19 +1,7 @@
import AppConfig from './AppConfig/AppConfig';
import AppHeader from './AppHeader/AppHeader'; import AppHeader from './AppHeader/AppHeader';
import FileList from './FileList/FileList'; import QuestionList from './QuestionList/QuestionList';
import FileUpload from './FileUpload/FileUpload';
import NumericInput from './NumericInput/NumericInput';
import PostLabourIndex from './PostLabourIndex/PostLabourIndex';
import Scoreboard from './Scoreboard/Scoreboard';
import TotoObjectControls from './TotoObjectControls/TotoObjectControls';
export { export {
AppConfig,
AppHeader, AppHeader,
FileList, QuestionList
FileUpload,
NumericInput,
PostLabourIndex,
Scoreboard,
TotoObjectControls
}; };

View file

@ -1,15 +0,0 @@
import React, { Component } from 'react';
import { AppConfig } from '../../components';
class ConfigPage extends Component {
render() {
return (
<div>
<AppConfig />
</div>
)
}
}
export default ConfigPage;

View file

@ -1,15 +0,0 @@
import React, { Component } from 'react';
import { TotoObjectControls } from '../../components';
class ControlsPage extends Component {
render() {
return (
<div>
<TotoObjectControls />
</div>
)
}
}
export default ControlsPage;

View file

@ -0,0 +1,15 @@
import React, { Component } from 'react';
//import { QuestionList } from '../../components/QuestionList';
class DashboardPage extends Component {
render() {
return (
<div>
QuestionList
</div>
)
}
}
export default DashboardPage;

View file

@ -1,17 +0,0 @@
import React, { Component } from 'react';
import { FileList, FileUpload } from '../../components';
class FilesPage extends Component {
render() {
return (
<div>
<FileUpload />
<br/>
<FileList />
</div>
);
}
};
export default FilesPage;

View file

@ -1,16 +0,0 @@
import React, { Component } from 'react';
import { PostLabourIndex } from '../../components';
class ScoreboardPage extends Component {
render() {
return (
<div>
<PostLabourIndex />
</div>
)
}
}
export default ScoreboardPage;

View file

@ -1,11 +1,5 @@
import ConfigPage from './ConfigPage/ConfigPage'; import DashboardPage from './DashboardPage/DashboardPage';
import ControlsPage from './ControlsPage/ControlsPage';
import FilesPage from './FilesPage/FilesPage';
import ScoreboardPage from './ScoreboardPage/ScoreboardPage';
export { export {
ConfigPage, DashboardPage
ControlsPage,
FilesPage,
ScoreboardPage
}; };

View file

@ -1,134 +1,25 @@
import { import {
CONFIG_LOADED, QUESTIONS_LOADED,
CONFIG_LOAD_ERROR, QUESTIONS_LOAD_ERROR
CONFIG_UPDATED,
CONFIG_SAVED,
OBJECT_STARTED,
OBJECT_PAUSED,
OBJECT_STOPPED,
OBJECT_STATUS_LOADED,
FILE_UPLOADED,
FILES_LOADED,
FILE_DELETED,
PARAMS_LOADED,
PARAMS_LOAD_ERROR,
PLAYERS_LOADED,
PLAYERS_LOAD_ERROR
} from '../actions/types'; } from '../actions/types';
const initialState = { const initialState = {
config: undefined, questions: []
params: [],
files: [],
players: []
}; };
export default function (state = initialState, action) { export default function (state = initialState, action) {
switch (action.type) { switch (action.type) {
case CONFIG_LOADED: { case QUESTIONS_LOADED: {
return { return {
...state, ...state,
config: action.config questions: action.data
} }
} }
case CONFIG_UPDATED: { case QUESTIONS_LOAD_ERROR: {
return { return {
...state, ...state,
config: action.config questionsLoadError: action.error
}
}
case CONFIG_SAVED: {
return {
...state,
config: action.config
}
}
case CONFIG_LOAD_ERROR: {
return {
...state,
configLoadError: action.error
}
}
case OBJECT_STARTED: {
return {
...state,
status: action.status
}
}
case OBJECT_PAUSED: {
return {
...state,
status: action.status
}
}
case OBJECT_STOPPED: {
return {
...state,
status: action.status
}
}
case OBJECT_STATUS_LOADED: {
return {
...state,
status: action.status
};
}
case FILE_UPLOADED: {
let files = JSON.parse(JSON.stringify(state.files));
files.push({
key: action.file.name,
name: action.file.name
});
return {
...state,
files: files
};
}
case FILES_LOADED: {
let files = action.files.map((file) => {
return {
key: file.name,
name: file.name
}
});
return {
...state,
files: files
};
}
case FILE_DELETED: {
let files = state.files.filter((file) => {
return file.name !== action.fileName
});
return {
...state,
files: files
};
}
case PLAYERS_LOADED: {
return {
...state,
players: action.players
};
}
case PLAYERS_LOAD_ERROR: {
return {
...state,
players: [],
playersLoadError: action.error
};
}
case PARAMS_LOADED: {
return {
...state,
params: action.params
};
}
case PARAMS_LOAD_ERROR: {
return {
...state,
paramsLoadError: action.error
} }
} }
default: default:

View file

@ -1,65 +1,9 @@
class ApiService { class ApiService {
getConfig(onSuccess, onError) { getQuestions(userId, onSuccess, onError) {
this._get('/api/config', onSuccess, onError); this._get('/api/questions' + userId, onSuccess, onError);
}; };
udpateConfig(requestData, onSuccess, onError) {
this._put('/api/config/update', requestData, onSuccess, onError);
};
saveConfig(onSuccess, onError) {
this._post('/api/config/save', {}, onSuccess, onError);
};
startObject(onSuccess, onError) {
this._post('/api/obj/start', {}, onSuccess, onError);
};
pauseObject(onSuccess, onError) {
this._post('/api/obj/pause', {}, onSuccess, onError);
};
stopObject(onSuccess, onError) {
this._post('/api/obj/stop', {}, onSuccess, onError);
};
getObjectStatus(onSuccess, onError) {
this._get('/api/obj/status', onSuccess, onError);
};
getFiles(onSuccess, onError) {
this._get('/api/files', onSuccess, onError);
};
getParams(onSuccess, onError) {
this._get('/api/nwap/params', onSuccess, onError);
};
getPlayers(showId, onSuccess, onError) {
this._get('/api/nwap/show/' + showId, onSuccess, onError);
};
uploadFile(formData, onSuccess, onError) {
fetch('/api/files', {
method: 'POST',
body: formData
})
.then(this._handleResponse(onSuccess, onError))
.catch((error) => {
onError({ error: error.message });
});
};
deleteFile(name, onSuccess, onError) {
const url = '/api/files/' + name;
fetch(url, { method: 'DELETE' })
.then(this._handleResponse(onSuccess, onError))
.catch((error) => {
onError({ error: error.message });
});
}
_get(url, onSuccess, onError) { _get(url, onSuccess, onError) {
fetch(url) fetch(url)
.then(this._handleResponse(onSuccess, onError)) .then(this._handleResponse(onSuccess, onError))