diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 40bea7f..cf088af 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -103,6 +103,7 @@
"style-loader": "^0.21.0",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
+ "typeface-changa": "^0.0.56",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-loader": "^1.0.1",
"webfontloader": "^1.6.28",
diff --git a/packages/frontend/src/website/App.jsx b/packages/frontend/src/website/App.jsx
index 84fa793..bf5d30d 100644
--- a/packages/frontend/src/website/App.jsx
+++ b/packages/frontend/src/website/App.jsx
@@ -4,10 +4,16 @@ import { Provider } from 'react-redux';
import store from './redux';
import MessageBoxes from './containers/MessageBoxes';
+import ServerTableWrapper from './components/ServerTableWrapper';
+
+import style from './App.styl';
const App = () => (
-
+
+
+
+
);
diff --git a/packages/frontend/src/website/App.styl b/packages/frontend/src/website/App.styl
new file mode 100644
index 0000000..9288069
--- /dev/null
+++ b/packages/frontend/src/website/App.styl
@@ -0,0 +1,5 @@
+@import '~typeface-changa'
+
+.app
+ font-family: Changa, sans-serif
+ font-size: 16px
diff --git a/packages/frontend/src/website/components/AnimatedEllipsis.jsx b/packages/frontend/src/website/components/AnimatedEllipsis.jsx
new file mode 100644
index 0000000..5f5b731
--- /dev/null
+++ b/packages/frontend/src/website/components/AnimatedEllipsis.jsx
@@ -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 (
+
+ {'.'.repeat(this.state.dots)}
+
+ );
+ }
+}
diff --git a/packages/frontend/src/website/components/ServerTable.jsx b/packages/frontend/src/website/components/ServerTable.jsx
new file mode 100644
index 0000000..1fe8bdc
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTable.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+import style from './ServerTable.styl';
+
+const ServerTable = ({ servers }) => (
+
+
+
+ Server Name |
+ Variant |
+ Map |
+ Players |
+ Server Address |
+
+
+
+ {
+ servers.map(server => (
+
+ {server.name} |
+ {server.variant} {server.variantType} |
+ {server.map} |
+ {server.numPlayers} / {server.maxPlayers} |
+ {server.ip}:{server.port} |
+
+ ))
+ }
+
+
+);
+
+export default ServerTable;
diff --git a/packages/frontend/src/website/components/ServerTable.styl b/packages/frontend/src/website/components/ServerTable.styl
new file mode 100644
index 0000000..5d64d5f
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTable.styl
@@ -0,0 +1,2 @@
+.serverTable
+ width: 100%
diff --git a/packages/frontend/src/website/components/ServerTableButtons.jsx b/packages/frontend/src/website/components/ServerTableButtons.jsx
new file mode 100644
index 0000000..1743c9c
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTableButtons.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import style from './ServerTableButtons.styl';
+
+const ServerTableButtons = ({
+ disableRefreshButton,
+ hideCancelButton,
+ dispatchRefresh,
+ dispatchCancelRefresh,
+}) => (
+
+
+
+
+);
+
+export default ServerTableButtons;
diff --git a/packages/frontend/src/website/components/ServerTableButtons.styl b/packages/frontend/src/website/components/ServerTableButtons.styl
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTableButtons.styl
@@ -0,0 +1 @@
+
diff --git a/packages/frontend/src/website/components/ServerTableStatus.jsx b/packages/frontend/src/website/components/ServerTableStatus.jsx
new file mode 100644
index 0000000..3d852ad
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTableStatus.jsx
@@ -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 }) => (
+
+
+ {state === serverlistLoader.states.LOADING
+ ? Loading
+ : 'Idle'}
+
+
+ {serverCount} servers
+
+
+);
+
+export default ServerTableStatus;
diff --git a/packages/frontend/src/website/components/ServerTableWrapper.jsx b/packages/frontend/src/website/components/ServerTableWrapper.jsx
new file mode 100644
index 0000000..e93ecda
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTableWrapper.jsx
@@ -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 (
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/packages/frontend/src/website/components/ServerTableWrapper.styl b/packages/frontend/src/website/components/ServerTableWrapper.styl
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/packages/frontend/src/website/components/ServerTableWrapper.styl
@@ -0,0 +1 @@
+
diff --git a/packages/frontend/src/website/containers/ServerTable.js b/packages/frontend/src/website/containers/ServerTable.js
new file mode 100644
index 0000000..03c8c6d
--- /dev/null
+++ b/packages/frontend/src/website/containers/ServerTable.js
@@ -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;
diff --git a/packages/frontend/src/website/containers/ServerTableButtons.js b/packages/frontend/src/website/containers/ServerTableButtons.js
new file mode 100644
index 0000000..07f1635
--- /dev/null
+++ b/packages/frontend/src/website/containers/ServerTableButtons.js
@@ -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;
diff --git a/packages/frontend/src/website/containers/ServerTableStatus.js b/packages/frontend/src/website/containers/ServerTableStatus.js
new file mode 100644
index 0000000..a4a63fc
--- /dev/null
+++ b/packages/frontend/src/website/containers/ServerTableStatus.js
@@ -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;
diff --git a/packages/frontend/src/website/index.jsx b/packages/frontend/src/website/index.jsx
index a99ca83..727a8ed 100644
--- a/packages/frontend/src/website/index.jsx
+++ b/packages/frontend/src/website/index.jsx
@@ -37,9 +37,9 @@ window.addEventListener('load', () => {
), rootContainer);
- setTimeout(() => {
- store.dispatch(serverlist_loader.actions.updateGameServers());
- }, 500);
+ // setTimeout(() => {
+ // store.dispatch(serverlist_loader.actions.updateGameServers());
+ // }, 15000);
// setTimeout(() => {
// store.dispatch(ui.actions.add({
diff --git a/packages/frontend/src/website/redux/base.js b/packages/frontend/src/website/redux/base.js
index 17fdb0b..1572507 100644
--- a/packages/frontend/src/website/redux/base.js
+++ b/packages/frontend/src/website/redux/base.js
@@ -1,7 +1,11 @@
-import { combineReducers } from 'redux';
+// import { combineReducers } from 'redux';
const REDUX_PATH_SEPARATOR = '/';
+function normalizeNamespace(name) {
+ return name.replace(/_(.)/, match => match[1].toUpperCase());
+}
+
export default class ReduxGenerator {
constructor(...id) {
this._id = id;
@@ -60,11 +64,22 @@ export default class ReduxGenerator {
.reduce((current, [
name,
{
- params,
+ params = {},
reducer,
},
]) => {
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 = {
...current,
actions: {
@@ -74,24 +89,24 @@ export default class ReduxGenerator {
const err = Object.entries(payload)
.map(([key, value]) => {
const validator = params[key];
- if (!validator) {
+ if (validator === undefined || validator === null) {
console.warn(`${id}: extra argument ${JSON.stringify(key)}, ignoring`);
return undefined;
}
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) {
- case 'boolean':
+ case 'boolean': // validator returns either true on success or false on error
if (!result) {
result = new Error(`${JSON.stringify(key)}: invalid value: ${JSON.stringify(value)}`);
} else {
result = null;
}
break;
- default:
- if (result instanceof Error) {
+ default: // error or string (or something else?)
+ if (result instanceof Error) { // modify message to include key
result.message = `${JSON.stringify(key)}: ${result.message}`;
- } else {
+ } else { // use value as part of error reaosn
result = new Error(`${JSON.stringify(key)}: ${result}`);
}
}
@@ -122,14 +137,14 @@ export default class ReduxGenerator {
[name]: (state, { type, payload }) => {
// Ignore non-matching actions
if (type !== id) {
- console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
+ // console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
return state;
}
// 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);
- console.debug(' =>', result);
+ // console.debug(' =>', result);
return result;
},
};
@@ -142,9 +157,9 @@ export default class ReduxGenerator {
reducers: Object.entries(reducers)
.reduce((current, [name, reducer]) => ({
[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);
- console.debug(' =>', result);
+ // console.debug(' =>', result);
return result;
},
}), {}),
@@ -157,23 +172,24 @@ export default class ReduxGenerator {
[name]: (state, ...args) => {
const namespaceState = this._id
.slice(1) // ignore root id
+ .map(normalizeNamespace)
.reduce(
(currentState, namespaceName) =>
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);
- console.debug(' =>', result);
+ // console.debug(' =>', result);
return result;
},
}), selectors);
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) {
- 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;
- console.debug(' =>', result);
+ // console.debug(' =>', 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;
};
@@ -196,7 +212,7 @@ export default class ReduxGenerator {
// retval.initialState = initialState;
// }
- console.debug('generated redux module:', retval);
+ // console.debug('generated redux module:', retval);
return retval;
}
}
diff --git a/packages/frontend/src/website/redux/index.js b/packages/frontend/src/website/redux/index.js
index bc167e9..d268d5a 100644
--- a/packages/frontend/src/website/redux/index.js
+++ b/packages/frontend/src/website/redux/index.js
@@ -7,7 +7,7 @@ import {
import createSagaMiddleware from 'redux-saga';
import {
all,
- fork,
+ // fork,
} from 'redux-saga/effects';
import * as serverlist from './serverlist';
@@ -22,7 +22,7 @@ const reduxModules = Object.entries({
mastersync,
});
-console.debug('all redux modules', reduxModules);
+// console.debug('all redux modules', reduxModules);
const reducers =
reduxModules
@@ -33,7 +33,7 @@ const reducers =
[name]: m.reducer,
};
}, {});
-console.debug('combined reducers:', reducers);
+// console.debug('combined reducers:', reducers);
const reducer = combineReducers(reducers);
@@ -50,10 +50,10 @@ const reducer = combineReducers(reducers);
// create the saga middleware
function* rootSaga() {
- const sagas = Object.values(reduxModules)
- .reduce((current, [name, m]) => {
+ const sagas = Object.values(reduxModules) // TODO - !?
+ .reduce((current, [, m]) => {
if (!m.sagas) {
- console.debug(`Module ${name} does not have any sagas`);
+ // console.debug(`Module ${name} does not have any sagas`);
return current;
}
return [
@@ -61,9 +61,9 @@ function* rootSaga() {
...m.sagas,
];
}, []);
- console.debug('Will run these sagas from root:', sagas);
+ // console.debug('Will run these sagas from root:', sagas);
yield all(sagas);
- console.debug('Root saga done');
+ // console.debug('Root saga done');
}
const sagaMiddleware = createSagaMiddleware();
diff --git a/packages/frontend/src/website/redux/mastersync/index.js b/packages/frontend/src/website/redux/mastersync/index.js
index 2a13282..39c3d2a 100644
--- a/packages/frontend/src/website/redux/mastersync/index.js
+++ b/packages/frontend/src/website/redux/mastersync/index.js
@@ -18,7 +18,6 @@ export const states = {
};
function* doDewritoJsonUpdateRequest() {
- console.debug('mastersync doDewritoJsonUpdateRequest saga called');
try {
const response = yield call(fetch, 'https://raw.githubusercontent.com/ElDewrito/ElDorito/master/dist/mods/dewrito.json');
if (response.status !== 200) {
@@ -33,16 +32,15 @@ function* doDewritoJsonUpdateRequest() {
err,
}));
}
- console.debug('mastersync doDewritoJsonUpdateRequest: done');
}
-function* storeMasters({
- payload: {
- err,
- dewritoJson,
- },
-}) {
- console.debug('mastersync storeMasters saga called');
+function* storeMasters(x) {
+ const {
+ payload: {
+ err,
+ dewritoJson,
+ },
+ } = x;
if (err) {
yield put(ui.actions.add({
text: `Failed to fetch master server information: ${err.message}`,
@@ -54,7 +52,6 @@ function* storeMasters({
masterServers,
} = dewritoJson;
yield put(myRedux.actions.setMasters({ masters: masterServers }));
- console.debug('mastersync storeMasters: done');
}
export const initialState = {
diff --git a/packages/frontend/src/website/redux/serverlist/index.js b/packages/frontend/src/website/redux/serverlist/index.js
index 01263ce..6f1d464 100644
--- a/packages/frontend/src/website/redux/serverlist/index.js
+++ b/packages/frontend/src/website/redux/serverlist/index.js
@@ -33,16 +33,16 @@ const myRedux = reduxGenerator.all({
variantType: check.maybe.string,
teamScores: check.maybe.array.of.number,
status: check.string,
- numPlayers: check.positiveInteger,
+ numPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
mods: check.array, // TODO - fine tune this
- maxPlayers: check.positiveInteger,
+ maxPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
passworded: check.boolean,
isDedicated: check.boolean,
gameVersion: check.string,
eldewritoVersion: check.maybe.string,
xnkid: check.maybe.string,
xnaddr: check.maybe.string,
- players: check.maybe.array.of.like({
+ players: v => check.maybe.array.of.like(v, {
name: '',
serviceTag: '',
team: 0,
@@ -59,16 +59,19 @@ const myRedux = reduxGenerator.all({
bestStreak: 0,
}),
},
+
reducer(current, payload) {
- // do we have this server on the list already?
- if (current.servers.find(server => server.ip === payload.ip)) {
- // skip this server
+ // do we have this server on the list already?
+ if (current.servers.find(server =>
+ server.ip === payload.ip
+ && server.port === payload.port)) {
+ // skip this server
return current;
}
// add server
return {
- current,
+ ...current,
servers: [
...current.servers,
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: {
reducer: () => initialState,
},
@@ -86,19 +110,22 @@ const myRedux = reduxGenerator.all({
selectors: {
/*
Example:
- filter(state, {
+ getServers(state, {
name: {
$in: ["substring"],
}
gameType: "slayer",
})
*/
- filterServers({ servers }, filter, { order } = {}) {
- let filtered = obop.where(servers, filter);
- if (order) {
- filtered = obop.order(filtered, order);
+ getServers({ servers }, filter, { order } = {}) {
+ let finalServers = servers;
+ if (filter) {
+ finalServers = obop.where(finalServers, filter);
}
- return filtered;
+ if (order) {
+ finalServers = obop.order(finalServers, order);
+ }
+ return finalServers;
},
},
});
diff --git a/packages/frontend/src/website/redux/serverlist_loader/index.js b/packages/frontend/src/website/redux/serverlist_loader/index.js
index 285c2a8..dbab72c 100644
--- a/packages/frontend/src/website/redux/serverlist_loader/index.js
+++ b/packages/frontend/src/website/redux/serverlist_loader/index.js
@@ -4,8 +4,10 @@ import {
call,
select,
all,
+ race,
put,
} from 'redux-saga/effects';
+import { delay } from 'redux-saga';
import fetch from 'node-fetch';
import check from '../check';
@@ -23,6 +25,7 @@ export const states = {
export const initialState = {
state: states.IDLE,
+ bufferedServers: [],
};
function* inspectGameServer(addr) {
@@ -41,21 +44,23 @@ function* inspectGameServer(addr) {
throw new Error(`Unexpected HTTP error code ${response.status}: ${response.statusText}`);
}
const serverInfo = yield call(response.json.bind(response));
- check.assert.string(serverInfo.name);
- check.assert(check.port(serverInfo.port));
- check.assert.maybe.string(serverInfo.hostPlayer);
- check.assert.maybe.string(serverInfo.map);
- check.assert.maybe.string(serverInfo.mapFile);
- check.assert.maybe.string(serverInfo.variant);
- check.assert.maybe.string(serverInfo.variantType);
- check.assert.integer(serverInfo.maxPlayers);
- check.assert.integer(serverInfo.numPlayers);
- check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
- check.assert.array(serverInfo.players);
- yield put(serverlist.actions.add({
- ip,
- port,
- ...serverInfo,
+ // check.assert.string(serverInfo.name);
+ // check.assert(check.port(serverInfo.port));
+ // check.assert.maybe.string(serverInfo.hostPlayer);
+ // check.assert.maybe.string(serverInfo.map);
+ // check.assert.maybe.string(serverInfo.mapFile);
+ // check.assert.maybe.string(serverInfo.variant);
+ // check.assert.maybe.string(serverInfo.variantType);
+ // check.assert.integer(serverInfo.maxPlayers);
+ // check.assert.integer(serverInfo.numPlayers);
+ // check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
+ // check.assert.array(serverInfo.players);
+ yield put(myRedux.actions.updateGameServersBuffer({
+ server: {
+ ip,
+ port,
+ ...serverInfo,
+ },
}));
} catch (e) {
console.error(`Request to game server ${addr} failed:`, e);
@@ -83,7 +88,9 @@ function* updateGameServersFromMaster(masterListUrl) {
if (code !== 0) {
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;
default:
throw new Error(`Unsupported list version: ${listVersion}`);
@@ -94,7 +101,7 @@ function* updateGameServersFromMaster(masterListUrl) {
}
function* updateGameServers() {
- yield put(mastersync.actions.updateDewritoJsonStarted());
+ yield put(myRedux.actions.updateGameServersStarted());
// First let's ensure we have master servers at all
if (!(yield select(mastersync.selectors.hasMasters))) {
@@ -102,7 +109,7 @@ function* updateGameServers() {
yield put(mastersync.actions.updateDewritoJson());
const { err } = yield take(mastersync.actionTypes.updateDewritoJsonFinished);
if (err) {
- yield put(mastersync.actions.updateDewritoJsonFinished({ err }));
+ yield put(myRedux.actions.updateGameServersFinished({ err }));
return;
}
}
@@ -111,15 +118,38 @@ function* updateGameServers() {
yield put(serverlist.actions.reset());
const masterListUrls = (yield select(mastersync.selectors.getMasters))
.map(server => server.list);
- yield all(masterListUrls
- .map(url => call(updateGameServersFromMaster, url)));
- yield put(mastersync.actions.updateDewritoJsonFinished({}));
+ yield race([
+ all(masterListUrls.map(url => call(updateGameServersFromMaster, url))),
+ 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({
initialState,
actions: {
+ cancelUpdateGameServers: {},
+
updateGameServers: {},
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: {
reducer: state => ({
...state,
@@ -140,11 +186,13 @@ myRedux = root.sub('serverlist_loader').all({
// selectors are all function(state, ...parameters)
selectors: {
getState: ({ state }) => state,
+ getBufferedServers: ({ bufferedServers }) => bufferedServers,
},
});
export const sagas = [
takeEvery(myRedux.actionTypes.updateGameServers, updateGameServers),
+ takeEvery(myRedux.actionTypes.updateGameServersBuffer, pushBufferToGameServersList),
];
export const {
diff --git a/packages/frontend/webpack.config.babel.js b/packages/frontend/webpack.config.babel.js
index 9e4fee0..4c2ee3f 100644
--- a/packages/frontend/webpack.config.babel.js
+++ b/packages/frontend/webpack.config.babel.js
@@ -64,8 +64,8 @@ export default (options) => {
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
const cssOutputFileName = baseOutputFilename
- .replace(/\[ext(.*?)\]/g, 'css')
- .replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
+ .replace(/\[ext(.*?)\]/g, 'css');
+ // .replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
// const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
const cssOutputRebasePath = '/';
diff --git a/yarn.lock b/yarn.lock
index 6bfeae9..a3863b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8383,6 +8383,10 @@ typedarray@^0.0.6:
version "0.0.6"
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:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"