Get to the point of working proof-of-work.
parent
9ab0432a5b
commit
adfcfb0ae0
|
@ -103,6 +103,7 @@
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"stylus": "^0.54.5",
|
"stylus": "^0.54.5",
|
||||||
"stylus-loader": "^3.0.2",
|
"stylus-loader": "^3.0.2",
|
||||||
|
"typeface-changa": "^0.0.56",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||||
"url-loader": "^1.0.1",
|
"url-loader": "^1.0.1",
|
||||||
"webfontloader": "^1.6.28",
|
"webfontloader": "^1.6.28",
|
||||||
|
|
|
@ -4,10 +4,16 @@ import { Provider } from 'react-redux';
|
||||||
import store from './redux';
|
import store from './redux';
|
||||||
|
|
||||||
import MessageBoxes from './containers/MessageBoxes';
|
import MessageBoxes from './containers/MessageBoxes';
|
||||||
|
import ServerTableWrapper from './components/ServerTableWrapper';
|
||||||
|
|
||||||
|
import style from './App.styl';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<div className={style.app}>
|
||||||
<MessageBoxes />
|
<MessageBoxes />
|
||||||
|
<ServerTableWrapper />
|
||||||
|
</div>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import '~typeface-changa'
|
||||||
|
|
||||||
|
.app
|
||||||
|
font-family: Changa, sans-serif
|
||||||
|
font-size: 16px
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class AnimatedEllipsis extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
dots: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timer = null;
|
||||||
|
|
||||||
|
this.componentDidMount = this.componentDidMount.bind(this);
|
||||||
|
this.componentWillUnmount = this.componentWillUnmount.bind(this);
|
||||||
|
this.startDotTimer = this.startDotTimer.bind(this);
|
||||||
|
this.stopDotTimer = this.stopDotTimer.bind(this);
|
||||||
|
this.incrementDots = this.incrementDots.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.startDotTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.stopDotTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
startDotTimer() {
|
||||||
|
if (this.timer === null) {
|
||||||
|
this.timer = setInterval(this.incrementDots, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopDotTimer() {
|
||||||
|
if (this.timer !== null) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementDots() {
|
||||||
|
this.setState(state => ({
|
||||||
|
dots: (state.dots + 1) % 4,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{'.'.repeat(this.state.dots)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import style from './ServerTable.styl';
|
||||||
|
|
||||||
|
const ServerTable = ({ servers }) => (
|
||||||
|
<table className={style.serverTable}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Server Name</th>
|
||||||
|
<th>Variant</th>
|
||||||
|
<th>Map</th>
|
||||||
|
<th>Players</th>
|
||||||
|
<th>Server Address</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
servers.map(server => (
|
||||||
|
<tr key={`${server.ip}:${server.port}`}>
|
||||||
|
<td>{server.name}</td>
|
||||||
|
<td>{server.variant}<br /><small>{server.variantType}</small></td>
|
||||||
|
<td>{server.map}</td>
|
||||||
|
<td>{server.numPlayers} / {server.maxPlayers}</td>
|
||||||
|
<td>{server.ip}:{server.port}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServerTable;
|
|
@ -0,0 +1,2 @@
|
||||||
|
.serverTable
|
||||||
|
width: 100%
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import style from './ServerTableButtons.styl';
|
||||||
|
|
||||||
|
const ServerTableButtons = ({
|
||||||
|
disableRefreshButton,
|
||||||
|
hideCancelButton,
|
||||||
|
dispatchRefresh,
|
||||||
|
dispatchCancelRefresh,
|
||||||
|
}) => (
|
||||||
|
<div className={style.serverTableButtons}>
|
||||||
|
<button
|
||||||
|
disabled={disableRefreshButton}
|
||||||
|
onClick={dispatchRefresh}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={hideCancelButton}
|
||||||
|
onClick={dispatchCancelRefresh}
|
||||||
|
>
|
||||||
|
Cancel refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServerTableButtons;
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||||
|
|
||||||
|
import AnimatedEllipsis from './AnimatedEllipsis';
|
||||||
|
|
||||||
|
import style from './ServerTable.styl';
|
||||||
|
|
||||||
|
|
||||||
|
const ServerTableStatus = ({ serverCount, state }) => (
|
||||||
|
<div className={[
|
||||||
|
style.serverTableStatus,
|
||||||
|
state === serverlistLoader.states.LOADING
|
||||||
|
? style.serverTableStatusLoading
|
||||||
|
: style.serverTableStatusIdle,
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{state === serverlistLoader.states.LOADING
|
||||||
|
? <span>Loading<AnimatedEllipsis /></span>
|
||||||
|
: 'Idle'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{serverCount} servers
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ServerTableStatus;
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import style from './ServerTableWrapper.styl';
|
||||||
|
|
||||||
|
import ServerTable from '../containers/ServerTable';
|
||||||
|
import ServerTableStatus from '../containers/ServerTableStatus';
|
||||||
|
import ServerTableButtons from '../containers/ServerTableButtons';
|
||||||
|
|
||||||
|
export default class ServerTableWrapper extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
query: undefined,
|
||||||
|
queryText: '',
|
||||||
|
};
|
||||||
|
this.handleQueryChange = this.handleQueryChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleQueryChange(e) {
|
||||||
|
const queryText = e.target.value;
|
||||||
|
this.setState({
|
||||||
|
queryText,
|
||||||
|
});
|
||||||
|
if (queryText && queryText.length > 0) {
|
||||||
|
this.setState({
|
||||||
|
query: item => item.name.toLowerCase().includes(queryText.toLowerCase()),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
query: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={style.serverTableWrapper}>
|
||||||
|
<ServerTableStatus />
|
||||||
|
<ServerTableButtons />
|
||||||
|
<input type="text" value={this.state.queryText} onChange={this.handleQueryChange} />
|
||||||
|
<ServerTable filter={this.state.query} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { selectors } from '../redux/serverlist';
|
||||||
|
|
||||||
|
import Component from '../components/ServerTable';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { filter }) => {
|
||||||
|
// TODO - filter
|
||||||
|
const servers = selectors.getServers(state, filter);
|
||||||
|
return ({
|
||||||
|
servers: servers.slice(0, 25),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectedMessageBoxes = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
)(Component);
|
||||||
|
|
||||||
|
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||||
|
|
||||||
|
import Component from '../components/ServerTableButtons';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
hideCancelButton: serverlistLoader.selectors.getState(state) === serverlistLoader.states.IDLE,
|
||||||
|
disableRefreshButton: serverlistLoader.selectors.getState(state) !== serverlistLoader.states.IDLE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
dispatchRefresh: () => {
|
||||||
|
dispatch(serverlistLoader.actions.updateGameServers());
|
||||||
|
},
|
||||||
|
dispatchCancelRefresh: () => {
|
||||||
|
dispatch(serverlistLoader.actions.cancelUpdateGameServers());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConnectedMessageBoxes = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Component);
|
||||||
|
|
||||||
|
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import * as serverlist from '../redux/serverlist';
|
||||||
|
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||||
|
|
||||||
|
import Component from '../components/ServerTableStatus';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
state: serverlistLoader.selectors.getState(state),
|
||||||
|
serverCount: serverlist.selectors.getServers(state).length,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConnectedMessageBoxes = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
)(Component);
|
||||||
|
|
||||||
|
export default ConnectedMessageBoxes;
|
|
@ -37,9 +37,9 @@ window.addEventListener('load', () => {
|
||||||
<App />
|
<App />
|
||||||
), rootContainer);
|
), rootContainer);
|
||||||
|
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
store.dispatch(serverlist_loader.actions.updateGameServers());
|
// store.dispatch(serverlist_loader.actions.updateGameServers());
|
||||||
}, 500);
|
// }, 15000);
|
||||||
|
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// store.dispatch(ui.actions.add({
|
// store.dispatch(ui.actions.add({
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { combineReducers } from 'redux';
|
// import { combineReducers } from 'redux';
|
||||||
|
|
||||||
const REDUX_PATH_SEPARATOR = '/';
|
const REDUX_PATH_SEPARATOR = '/';
|
||||||
|
|
||||||
|
function normalizeNamespace(name) {
|
||||||
|
return name.replace(/_(.)/, match => match[1].toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
export default class ReduxGenerator {
|
export default class ReduxGenerator {
|
||||||
constructor(...id) {
|
constructor(...id) {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
|
@ -60,11 +64,22 @@ export default class ReduxGenerator {
|
||||||
.reduce((current, [
|
.reduce((current, [
|
||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
params,
|
params = {},
|
||||||
reducer,
|
reducer,
|
||||||
},
|
},
|
||||||
]) => {
|
]) => {
|
||||||
const id = this.actionType(name);
|
const id = this.actionType(name);
|
||||||
|
|
||||||
|
// Validate params, they should all have a validator function as a value
|
||||||
|
if (params) {
|
||||||
|
const keysWithNonFunctionValidator = Object.keys(params)
|
||||||
|
.filter(paramName => typeof (params[paramName]) !== 'function')
|
||||||
|
.map(v => JSON.stringify(v));
|
||||||
|
if (keysWithNonFunctionValidator.length > 0) {
|
||||||
|
throw new Error(`Following params of ${JSON.stringify(id)} have a non-function value as validator: ${keysWithNonFunctionValidator.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const target = {
|
const target = {
|
||||||
...current,
|
...current,
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -74,24 +89,24 @@ export default class ReduxGenerator {
|
||||||
const err = Object.entries(payload)
|
const err = Object.entries(payload)
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
const validator = params[key];
|
const validator = params[key];
|
||||||
if (!validator) {
|
if (validator === undefined || validator === null) {
|
||||||
console.warn(`${id}: extra argument ${JSON.stringify(key)}, ignoring`);
|
console.warn(`${id}: extra argument ${JSON.stringify(key)}, ignoring`);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
let result = validator(value);
|
let result = validator(value);
|
||||||
console.debug(`${id}: verifying ${JSON.stringify(key)} result:`, result);
|
// console.debug(`${id}: verifying ${JSON.stringify(key)} result:`, result);
|
||||||
switch (typeof result) {
|
switch (typeof result) {
|
||||||
case 'boolean':
|
case 'boolean': // validator returns either true on success or false on error
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = new Error(`${JSON.stringify(key)}: invalid value: ${JSON.stringify(value)}`);
|
result = new Error(`${JSON.stringify(key)}: invalid value: ${JSON.stringify(value)}`);
|
||||||
} else {
|
} else {
|
||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default: // error or string (or something else?)
|
||||||
if (result instanceof Error) {
|
if (result instanceof Error) { // modify message to include key
|
||||||
result.message = `${JSON.stringify(key)}: ${result.message}`;
|
result.message = `${JSON.stringify(key)}: ${result.message}`;
|
||||||
} else {
|
} else { // use value as part of error reaosn
|
||||||
result = new Error(`${JSON.stringify(key)}: ${result}`);
|
result = new Error(`${JSON.stringify(key)}: ${result}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,14 +137,14 @@ export default class ReduxGenerator {
|
||||||
[name]: (state, { type, payload }) => {
|
[name]: (state, { type, payload }) => {
|
||||||
// Ignore non-matching actions
|
// Ignore non-matching actions
|
||||||
if (type !== id) {
|
if (type !== id) {
|
||||||
console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
|
// console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call original reducer with our payload
|
// Call original reducer with our payload
|
||||||
console.debug(`${type}: action-matched reducer called`, { state, payload });
|
// console.debug(`${type}: action-matched reducer called`, { state, payload });
|
||||||
const result = reducer(state, payload);
|
const result = reducer(state, payload);
|
||||||
console.debug(' =>', result);
|
// console.debug(' =>', result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -142,9 +157,9 @@ export default class ReduxGenerator {
|
||||||
reducers: Object.entries(reducers)
|
reducers: Object.entries(reducers)
|
||||||
.reduce((current, [name, reducer]) => ({
|
.reduce((current, [name, reducer]) => ({
|
||||||
[name]: (state, ...args) => {
|
[name]: (state, ...args) => {
|
||||||
console.debug(`${this._id.join('/', `reducer:${name}`)}: called`, { state, args });
|
// console.debug(`${this._id.join('/', `reducer:${name}`)}: called`, { state, args });
|
||||||
const result = reducer(state, ...args);
|
const result = reducer(state, ...args);
|
||||||
console.debug(' =>', result);
|
// console.debug(' =>', result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
}), {}),
|
}), {}),
|
||||||
|
@ -157,23 +172,24 @@ export default class ReduxGenerator {
|
||||||
[name]: (state, ...args) => {
|
[name]: (state, ...args) => {
|
||||||
const namespaceState = this._id
|
const namespaceState = this._id
|
||||||
.slice(1) // ignore root id
|
.slice(1) // ignore root id
|
||||||
|
.map(normalizeNamespace)
|
||||||
.reduce(
|
.reduce(
|
||||||
(currentState, namespaceName) =>
|
(currentState, namespaceName) =>
|
||||||
currentState[namespaceName], state);
|
currentState[namespaceName], state);
|
||||||
console.debug(`${[...this._id, `selector:${name}`].join('/')}: called`, { state: namespaceState, args });
|
// console.debug(`${[...this._id, `selector:${name}`].join('/')}: called`, { state: namespaceState, args });
|
||||||
const result = selector(namespaceState, ...args);
|
const result = selector(namespaceState, ...args);
|
||||||
console.debug(' =>', result);
|
// console.debug(' =>', result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
}), selectors);
|
}), selectors);
|
||||||
|
|
||||||
retval.reducer = (state, ...args) => {
|
retval.reducer = (state, ...args) => {
|
||||||
console.debug(`${this._id.join('/')}: combined reducer called`, { state, args });
|
// console.debug(`${this._id.join('/')}: combined reducer called`, { state, args });
|
||||||
|
|
||||||
if (state === undefined) {
|
if (state === undefined) {
|
||||||
console.debug(`${this._id.join('/')}: undefined state passed to reducer`, { state, args });
|
// console.debug(`${this._id.join('/')}: undefined state passed to reducer`, { state, args });
|
||||||
const result = initialState;
|
const result = initialState;
|
||||||
console.debug(' =>', result);
|
// console.debug(' =>', result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +204,7 @@ export default class ReduxGenerator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`${this._id.join('/')}: no reducers for this namespace, skipping`);
|
// console.debug(`${this._id.join('/')}: no reducers for this namespace, skipping`);
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -196,7 +212,7 @@ export default class ReduxGenerator {
|
||||||
// retval.initialState = initialState;
|
// retval.initialState = initialState;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
console.debug('generated redux module:', retval);
|
// console.debug('generated redux module:', retval);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
import {
|
import {
|
||||||
all,
|
all,
|
||||||
fork,
|
// fork,
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
import * as serverlist from './serverlist';
|
import * as serverlist from './serverlist';
|
||||||
|
@ -22,7 +22,7 @@ const reduxModules = Object.entries({
|
||||||
mastersync,
|
mastersync,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.debug('all redux modules', reduxModules);
|
// console.debug('all redux modules', reduxModules);
|
||||||
|
|
||||||
const reducers =
|
const reducers =
|
||||||
reduxModules
|
reduxModules
|
||||||
|
@ -33,7 +33,7 @@ const reducers =
|
||||||
[name]: m.reducer,
|
[name]: m.reducer,
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
console.debug('combined reducers:', reducers);
|
// console.debug('combined reducers:', reducers);
|
||||||
|
|
||||||
const reducer = combineReducers(reducers);
|
const reducer = combineReducers(reducers);
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ const reducer = combineReducers(reducers);
|
||||||
|
|
||||||
// create the saga middleware
|
// create the saga middleware
|
||||||
function* rootSaga() {
|
function* rootSaga() {
|
||||||
const sagas = Object.values(reduxModules)
|
const sagas = Object.values(reduxModules) // TODO - !?
|
||||||
.reduce((current, [name, m]) => {
|
.reduce((current, [, m]) => {
|
||||||
if (!m.sagas) {
|
if (!m.sagas) {
|
||||||
console.debug(`Module ${name} does not have any sagas`);
|
// console.debug(`Module ${name} does not have any sagas`);
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
@ -61,9 +61,9 @@ function* rootSaga() {
|
||||||
...m.sagas,
|
...m.sagas,
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
console.debug('Will run these sagas from root:', sagas);
|
// console.debug('Will run these sagas from root:', sagas);
|
||||||
yield all(sagas);
|
yield all(sagas);
|
||||||
console.debug('Root saga done');
|
// console.debug('Root saga done');
|
||||||
}
|
}
|
||||||
const sagaMiddleware = createSagaMiddleware();
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ export const states = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function* doDewritoJsonUpdateRequest() {
|
function* doDewritoJsonUpdateRequest() {
|
||||||
console.debug('mastersync doDewritoJsonUpdateRequest saga called');
|
|
||||||
try {
|
try {
|
||||||
const response = yield call(fetch, 'https://raw.githubusercontent.com/ElDewrito/ElDorito/master/dist/mods/dewrito.json');
|
const response = yield call(fetch, 'https://raw.githubusercontent.com/ElDewrito/ElDorito/master/dist/mods/dewrito.json');
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
|
@ -33,16 +32,15 @@ function* doDewritoJsonUpdateRequest() {
|
||||||
err,
|
err,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
console.debug('mastersync doDewritoJsonUpdateRequest: done');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* storeMasters({
|
function* storeMasters(x) {
|
||||||
|
const {
|
||||||
payload: {
|
payload: {
|
||||||
err,
|
err,
|
||||||
dewritoJson,
|
dewritoJson,
|
||||||
},
|
},
|
||||||
}) {
|
} = x;
|
||||||
console.debug('mastersync storeMasters saga called');
|
|
||||||
if (err) {
|
if (err) {
|
||||||
yield put(ui.actions.add({
|
yield put(ui.actions.add({
|
||||||
text: `Failed to fetch master server information: ${err.message}`,
|
text: `Failed to fetch master server information: ${err.message}`,
|
||||||
|
@ -54,7 +52,6 @@ function* storeMasters({
|
||||||
masterServers,
|
masterServers,
|
||||||
} = dewritoJson;
|
} = dewritoJson;
|
||||||
yield put(myRedux.actions.setMasters({ masters: masterServers }));
|
yield put(myRedux.actions.setMasters({ masters: masterServers }));
|
||||||
console.debug('mastersync storeMasters: done');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
|
|
|
@ -33,16 +33,16 @@ const myRedux = reduxGenerator.all({
|
||||||
variantType: check.maybe.string,
|
variantType: check.maybe.string,
|
||||||
teamScores: check.maybe.array.of.number,
|
teamScores: check.maybe.array.of.number,
|
||||||
status: check.string,
|
status: check.string,
|
||||||
numPlayers: check.positiveInteger,
|
numPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
|
||||||
mods: check.array, // TODO - fine tune this
|
mods: check.array, // TODO - fine tune this
|
||||||
maxPlayers: check.positiveInteger,
|
maxPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
|
||||||
passworded: check.boolean,
|
passworded: check.boolean,
|
||||||
isDedicated: check.boolean,
|
isDedicated: check.boolean,
|
||||||
gameVersion: check.string,
|
gameVersion: check.string,
|
||||||
eldewritoVersion: check.maybe.string,
|
eldewritoVersion: check.maybe.string,
|
||||||
xnkid: check.maybe.string,
|
xnkid: check.maybe.string,
|
||||||
xnaddr: check.maybe.string,
|
xnaddr: check.maybe.string,
|
||||||
players: check.maybe.array.of.like({
|
players: v => check.maybe.array.of.like(v, {
|
||||||
name: '',
|
name: '',
|
||||||
serviceTag: '',
|
serviceTag: '',
|
||||||
team: 0,
|
team: 0,
|
||||||
|
@ -59,16 +59,19 @@ const myRedux = reduxGenerator.all({
|
||||||
bestStreak: 0,
|
bestStreak: 0,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
reducer(current, payload) {
|
reducer(current, payload) {
|
||||||
// do we have this server on the list already?
|
// do we have this server on the list already?
|
||||||
if (current.servers.find(server => server.ip === payload.ip)) {
|
if (current.servers.find(server =>
|
||||||
|
server.ip === payload.ip
|
||||||
|
&& server.port === payload.port)) {
|
||||||
// skip this server
|
// skip this server
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add server
|
// add server
|
||||||
return {
|
return {
|
||||||
current,
|
...current,
|
||||||
servers: [
|
servers: [
|
||||||
...current.servers,
|
...current.servers,
|
||||||
payload,
|
payload,
|
||||||
|
@ -77,6 +80,27 @@ const myRedux = reduxGenerator.all({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addMultiple: {
|
||||||
|
params: {
|
||||||
|
servers: v => !!v, // TODO
|
||||||
|
},
|
||||||
|
reducer(current, { servers }) {
|
||||||
|
return { ...current,
|
||||||
|
servers: servers.reduce((currentServers, server) => {
|
||||||
|
// is this an actual new server?
|
||||||
|
if (!currentServers.find(registeredServer =>
|
||||||
|
server.ip === registeredServer.ip
|
||||||
|
&& server.port === registeredServer.port)) {
|
||||||
|
return [...currentServers, server];
|
||||||
|
}
|
||||||
|
|
||||||
|
// server is not new, return old list
|
||||||
|
return currentServers;
|
||||||
|
}, current.servers),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
reset: {
|
reset: {
|
||||||
reducer: () => initialState,
|
reducer: () => initialState,
|
||||||
},
|
},
|
||||||
|
@ -86,19 +110,22 @@ const myRedux = reduxGenerator.all({
|
||||||
selectors: {
|
selectors: {
|
||||||
/*
|
/*
|
||||||
Example:
|
Example:
|
||||||
filter(state, {
|
getServers(state, {
|
||||||
name: {
|
name: {
|
||||||
$in: ["substring"],
|
$in: ["substring"],
|
||||||
}
|
}
|
||||||
gameType: "slayer",
|
gameType: "slayer",
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
filterServers({ servers }, filter, { order } = {}) {
|
getServers({ servers }, filter, { order } = {}) {
|
||||||
let filtered = obop.where(servers, filter);
|
let finalServers = servers;
|
||||||
if (order) {
|
if (filter) {
|
||||||
filtered = obop.order(filtered, order);
|
finalServers = obop.where(finalServers, filter);
|
||||||
}
|
}
|
||||||
return filtered;
|
if (order) {
|
||||||
|
finalServers = obop.order(finalServers, order);
|
||||||
|
}
|
||||||
|
return finalServers;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,10 @@ import {
|
||||||
call,
|
call,
|
||||||
select,
|
select,
|
||||||
all,
|
all,
|
||||||
|
race,
|
||||||
put,
|
put,
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
|
import { delay } from 'redux-saga';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
import check from '../check';
|
import check from '../check';
|
||||||
|
@ -23,6 +25,7 @@ export const states = {
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
state: states.IDLE,
|
state: states.IDLE,
|
||||||
|
bufferedServers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function* inspectGameServer(addr) {
|
function* inspectGameServer(addr) {
|
||||||
|
@ -41,21 +44,23 @@ function* inspectGameServer(addr) {
|
||||||
throw new Error(`Unexpected HTTP error code ${response.status}: ${response.statusText}`);
|
throw new Error(`Unexpected HTTP error code ${response.status}: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
const serverInfo = yield call(response.json.bind(response));
|
const serverInfo = yield call(response.json.bind(response));
|
||||||
check.assert.string(serverInfo.name);
|
// check.assert.string(serverInfo.name);
|
||||||
check.assert(check.port(serverInfo.port));
|
// check.assert(check.port(serverInfo.port));
|
||||||
check.assert.maybe.string(serverInfo.hostPlayer);
|
// check.assert.maybe.string(serverInfo.hostPlayer);
|
||||||
check.assert.maybe.string(serverInfo.map);
|
// check.assert.maybe.string(serverInfo.map);
|
||||||
check.assert.maybe.string(serverInfo.mapFile);
|
// check.assert.maybe.string(serverInfo.mapFile);
|
||||||
check.assert.maybe.string(serverInfo.variant);
|
// check.assert.maybe.string(serverInfo.variant);
|
||||||
check.assert.maybe.string(serverInfo.variantType);
|
// check.assert.maybe.string(serverInfo.variantType);
|
||||||
check.assert.integer(serverInfo.maxPlayers);
|
// check.assert.integer(serverInfo.maxPlayers);
|
||||||
check.assert.integer(serverInfo.numPlayers);
|
// check.assert.integer(serverInfo.numPlayers);
|
||||||
check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
|
// check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
|
||||||
check.assert.array(serverInfo.players);
|
// check.assert.array(serverInfo.players);
|
||||||
yield put(serverlist.actions.add({
|
yield put(myRedux.actions.updateGameServersBuffer({
|
||||||
|
server: {
|
||||||
ip,
|
ip,
|
||||||
port,
|
port,
|
||||||
...serverInfo,
|
...serverInfo,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Request to game server ${addr} failed:`, e);
|
console.error(`Request to game server ${addr} failed:`, e);
|
||||||
|
@ -83,7 +88,9 @@ function* updateGameServersFromMaster(masterListUrl) {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
throw new Error(`Server returned error code ${code}: ${msg}`);
|
throw new Error(`Server returned error code ${code}: ${msg}`);
|
||||||
}
|
}
|
||||||
yield all(servers.map(addr => call(inspectGameServer, addr)));
|
yield all(servers
|
||||||
|
// .slice(0, 1) // DEBUG
|
||||||
|
.map(addr => call(inspectGameServer, addr)));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported list version: ${listVersion}`);
|
throw new Error(`Unsupported list version: ${listVersion}`);
|
||||||
|
@ -94,7 +101,7 @@ function* updateGameServersFromMaster(masterListUrl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateGameServers() {
|
function* updateGameServers() {
|
||||||
yield put(mastersync.actions.updateDewritoJsonStarted());
|
yield put(myRedux.actions.updateGameServersStarted());
|
||||||
|
|
||||||
// First let's ensure we have master servers at all
|
// First let's ensure we have master servers at all
|
||||||
if (!(yield select(mastersync.selectors.hasMasters))) {
|
if (!(yield select(mastersync.selectors.hasMasters))) {
|
||||||
|
@ -102,7 +109,7 @@ function* updateGameServers() {
|
||||||
yield put(mastersync.actions.updateDewritoJson());
|
yield put(mastersync.actions.updateDewritoJson());
|
||||||
const { err } = yield take(mastersync.actionTypes.updateDewritoJsonFinished);
|
const { err } = yield take(mastersync.actionTypes.updateDewritoJsonFinished);
|
||||||
if (err) {
|
if (err) {
|
||||||
yield put(mastersync.actions.updateDewritoJsonFinished({ err }));
|
yield put(myRedux.actions.updateGameServersFinished({ err }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,15 +118,38 @@ function* updateGameServers() {
|
||||||
yield put(serverlist.actions.reset());
|
yield put(serverlist.actions.reset());
|
||||||
const masterListUrls = (yield select(mastersync.selectors.getMasters))
|
const masterListUrls = (yield select(mastersync.selectors.getMasters))
|
||||||
.map(server => server.list);
|
.map(server => server.list);
|
||||||
yield all(masterListUrls
|
yield race([
|
||||||
.map(url => call(updateGameServersFromMaster, url)));
|
all(masterListUrls.map(url => call(updateGameServersFromMaster, url))),
|
||||||
yield put(mastersync.actions.updateDewritoJsonFinished({}));
|
take(myRedux.actionTypes.cancelUpdateGameServers),
|
||||||
|
]);
|
||||||
|
yield put(myRedux.actions.updateGameServersFinished());
|
||||||
|
}
|
||||||
|
|
||||||
|
function* pushBufferToGameServersList() {
|
||||||
|
// wait 750 milliseconds or until serverlist checks have all finished
|
||||||
|
const [, cancel] = yield race([
|
||||||
|
delay(250),
|
||||||
|
take(myRedux.actionTypes.updateGameServersBuffer),
|
||||||
|
take(myRedux.actionTypes.updateGameServersFinished),
|
||||||
|
]);
|
||||||
|
if (cancel) return;
|
||||||
|
|
||||||
|
const servers = yield select(myRedux.selectors.getBufferedServers);
|
||||||
|
yield put(myRedux.actions.updateGameServersBuffer({
|
||||||
|
clear: true,
|
||||||
|
}));
|
||||||
|
// console.log(`Would have added ${servers.length} new servers`);
|
||||||
|
yield put(serverlist.actions.addMultiple({
|
||||||
|
servers,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
myRedux = root.sub('serverlist_loader').all({
|
myRedux = root.sub('serverlist_loader').all({
|
||||||
initialState,
|
initialState,
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
cancelUpdateGameServers: {},
|
||||||
|
|
||||||
updateGameServers: {},
|
updateGameServers: {},
|
||||||
|
|
||||||
updateGameServersStarted: {
|
updateGameServersStarted: {
|
||||||
|
@ -129,6 +159,22 @@ myRedux = root.sub('serverlist_loader').all({
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateGameServersBuffer: {
|
||||||
|
params: {
|
||||||
|
server: () => true, // TODO
|
||||||
|
clear: check.maybe.boolean,
|
||||||
|
},
|
||||||
|
reducer: (state, { server, clear }) => ({
|
||||||
|
...state,
|
||||||
|
bufferedServers: clear
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
...state.bufferedServers,
|
||||||
|
server,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
updateGameServersFinished: {
|
updateGameServersFinished: {
|
||||||
reducer: state => ({
|
reducer: state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -140,11 +186,13 @@ myRedux = root.sub('serverlist_loader').all({
|
||||||
// selectors are all function(state, ...parameters)
|
// selectors are all function(state, ...parameters)
|
||||||
selectors: {
|
selectors: {
|
||||||
getState: ({ state }) => state,
|
getState: ({ state }) => state,
|
||||||
|
getBufferedServers: ({ bufferedServers }) => bufferedServers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sagas = [
|
export const sagas = [
|
||||||
takeEvery(myRedux.actionTypes.updateGameServers, updateGameServers),
|
takeEvery(myRedux.actionTypes.updateGameServers, updateGameServers),
|
||||||
|
takeEvery(myRedux.actionTypes.updateGameServersBuffer, pushBufferToGameServersList),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
|
|
@ -64,8 +64,8 @@ export default (options) => {
|
||||||
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
|
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
|
||||||
|
|
||||||
const cssOutputFileName = baseOutputFilename
|
const cssOutputFileName = baseOutputFilename
|
||||||
.replace(/\[ext(.*?)\]/g, 'css')
|
.replace(/\[ext(.*?)\]/g, 'css');
|
||||||
.replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
// .replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
||||||
// const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
|
// const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
|
||||||
const cssOutputRebasePath = '/';
|
const cssOutputRebasePath = '/';
|
||||||
|
|
||||||
|
|
|
@ -8383,6 +8383,10 @@ typedarray@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
|
||||||
|
typeface-changa@^0.0.56:
|
||||||
|
version "0.0.56"
|
||||||
|
resolved "https://registry.yarnpkg.com/typeface-changa/-/typeface-changa-0.0.56.tgz#df3ea2e8c9835987917bb0c5aad147be184f5d09"
|
||||||
|
|
||||||
ua-parser-js@^0.7.9:
|
ua-parser-js@^0.7.9:
|
||||||
version "0.7.17"
|
version "0.7.17"
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
||||||
|
|
Loading…
Reference in New Issue