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

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:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  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))