Initial commit.
commit
9ab0432a5b
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["babel-preset-env", {
|
||||||
|
"targets": {
|
||||||
|
"node": true,
|
||||||
|
"uglify": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel-plugin-transform-class-properties",
|
||||||
|
"babel-plugin-transform-runtime",
|
||||||
|
"babel-plugin-transform-object-rest-spread",
|
||||||
|
"babel-plugin-dynamic-import-node"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# Deployed apps should consider commenting this line out:
|
||||||
|
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||||
|
**/node_modules/**
|
||||||
|
!**/node_modules/.gitkeep
|
||||||
|
|
||||||
|
# Webpack output
|
||||||
|
packages/*/dist
|
||||||
|
packages/*/lib
|
||||||
|
|
||||||
|
# Intermediate build files (cache, etc.)
|
||||||
|
/build
|
|
@ -0,0 +1,30 @@
|
||||||
|
extends: airbnb
|
||||||
|
|
||||||
|
parser: babel-eslint
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- babel
|
||||||
|
- json
|
||||||
|
|
||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
node: true
|
||||||
|
es6: true
|
||||||
|
|
||||||
|
rules:
|
||||||
|
no-console: off
|
||||||
|
no-plusplus:
|
||||||
|
- error
|
||||||
|
- allowForLoopAfterthoughts: true
|
||||||
|
no-underscore-dangle: 'off'
|
||||||
|
react/no-array-index-key: 0
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
- files:
|
||||||
|
- "config/**"
|
||||||
|
- "**/webpack.config*.js"
|
||||||
|
- "**/.babelrc*"
|
||||||
|
rules:
|
||||||
|
import/no-extraneous-dependencies:
|
||||||
|
- error
|
||||||
|
- devDependencies: true
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# Deployed apps should consider commenting this line out:
|
||||||
|
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||||
|
**/node_modules/**
|
||||||
|
!**/node_modules/.gitkeep
|
||||||
|
|
||||||
|
# Webpack output
|
||||||
|
packages/*/dist
|
||||||
|
packages/*/lib
|
||||||
|
|
||||||
|
# Intermediate build files (cache, etc.)
|
||||||
|
/build
|
|
@ -0,0 +1,4 @@
|
||||||
|
rules:
|
||||||
|
import/no-extraneous-dependencies:
|
||||||
|
- error
|
||||||
|
- devDependencies: true
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { argv, exit } from 'process';
|
||||||
|
import { packageManager, execute } from './tool_env';
|
||||||
|
|
||||||
|
const args = [];
|
||||||
|
|
||||||
|
if (packageManager !== 'npm') {
|
||||||
|
args.push(`--npm-client=${packageManager}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(...argv.splice(2));
|
||||||
|
|
||||||
|
execute('lerna', args)
|
||||||
|
.then(exit)
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
exit(-1);
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
import spawn from 'execa';
|
||||||
|
|
||||||
|
function detectPackageManager() {
|
||||||
|
// @TODO - use a better data source than the user-configurable user agent
|
||||||
|
const userAgent = process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
// Yarn?
|
||||||
|
if (/\byarn\/[\d.]+\b/.test(userAgent)) {
|
||||||
|
return 'yarn';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const packageManager = detectPackageManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a given command with given argument array and then resolves the
|
||||||
|
* promise with whatever exit code the process returns.
|
||||||
|
*/
|
||||||
|
export function execute(name, args, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// console.log('Environment:', env);
|
||||||
|
console.log('Executing:', [name, ...args]
|
||||||
|
.map(v => (v.includes(' ') ? JSON.stringify(v) : v))
|
||||||
|
.join(' '));
|
||||||
|
|
||||||
|
const proc = spawn(name, args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
// cwd: undefined,
|
||||||
|
// env,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
proc.on('error', reject);
|
||||||
|
proc.on('close', resolve);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
import autoprefixer from 'autoprefixer';
|
||||||
|
|
||||||
|
export default class Environment {
|
||||||
|
constructor(options) {
|
||||||
|
this.development = true;
|
||||||
|
this.production = false;
|
||||||
|
this.server = false;
|
||||||
|
|
||||||
|
this.autoprefixerTargets = [
|
||||||
|
'chrome >= 58',
|
||||||
|
];
|
||||||
|
this.locales = ['en'];
|
||||||
|
|
||||||
|
if (options !== undefined && options !== null) {
|
||||||
|
this.input(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input(options) {
|
||||||
|
if (options) {
|
||||||
|
switch (true) {
|
||||||
|
case typeof (options) === 'string': // string
|
||||||
|
this.inputString(options);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Array.isArray(options): // array
|
||||||
|
options.forEach((arg) => { this.input(arg); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // object
|
||||||
|
Object.keys(options).forEach((k) => {
|
||||||
|
this[k] = options[k] || this[k];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (process.env.NODE_ENV) {
|
||||||
|
this.inputString(process.env.NODE_ENV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputString(env) {
|
||||||
|
switch (env.toLowerCase()) {
|
||||||
|
case 'development':
|
||||||
|
this.development = true;
|
||||||
|
this.production = false;
|
||||||
|
break;
|
||||||
|
case 'production':
|
||||||
|
this.development = false;
|
||||||
|
this.production = true;
|
||||||
|
break;
|
||||||
|
case 'server':
|
||||||
|
this.server = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('Unknown environment:', env);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styleLoaders(...preprocessingLoaders) {
|
||||||
|
const {
|
||||||
|
production,
|
||||||
|
autoprefixerTargets,
|
||||||
|
server,
|
||||||
|
|
||||||
|
ExtractTextPlugin, // @HACK
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (!ExtractTextPlugin) {
|
||||||
|
throw new Error('Need a valid ExtractTextPlugin fed into the environment object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let cssLoaders = [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
sourceMap: true,
|
||||||
|
modules: true,
|
||||||
|
localIdentName: production
|
||||||
|
? '[name]__[local]--[hash:base64:5]'
|
||||||
|
: '[name]__[local]--[hash:base64:5]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
ident: 'postcss',
|
||||||
|
plugins: [
|
||||||
|
autoprefixer({
|
||||||
|
browsers: autoprefixerTargets,
|
||||||
|
grid: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
].filter(loader => loader !== false);
|
||||||
|
|
||||||
|
if (preprocessingLoaders && preprocessingLoaders.length > 0) {
|
||||||
|
cssLoaders.push(
|
||||||
|
{
|
||||||
|
loader: 'resolve-url-loader',
|
||||||
|
options: {
|
||||||
|
fail: true,
|
||||||
|
silent: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...preprocessingLoaders.map(loader => Object.assign({}, loader, {
|
||||||
|
options: Object.assign({}, loader.options || {}, {
|
||||||
|
sourceMap: true,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
const fallback = cssLoaders.shift();
|
||||||
|
cssLoaders = ExtractTextPlugin.extract({
|
||||||
|
fallback,
|
||||||
|
use: cssLoaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssLoaders;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"lerna": "2.1.0",
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "@eldewrito-menu/root",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"bootstrap": "yon-lerna bootstrap --hoist",
|
||||||
|
"build": "yon-lerna run build",
|
||||||
|
"clean": "yon-lerna run clean",
|
||||||
|
"lerna": "yon-lerna.js",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"postinstall": "yon run -s bootstrap",
|
||||||
|
"start": "yon-lerna run start --stream --parallel",
|
||||||
|
"test:coverage": "yon-lerna run test:coverage",
|
||||||
|
"test:watch": "yon-lerna run test:watch",
|
||||||
|
"test": "yon-lerna run test"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://git@git.icedream.tech:2222/icedream/eldewrito-menu.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"website",
|
||||||
|
"vizon",
|
||||||
|
"rizon",
|
||||||
|
"draw",
|
||||||
|
"countdown"
|
||||||
|
],
|
||||||
|
"author": "Carl Kittelberger <icedream@icedream.pw>",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^7.1.4",
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-eslint": "^7.2.3",
|
||||||
|
"babel-plugin-dynamic-import-node": "^1.0.2",
|
||||||
|
"babel-plugin-dynamic-import-webpack": "^1.0.1",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
|
"babel-plugin-transform-react-constant-elements": "^6.23.0",
|
||||||
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
|
"babel-preset-env": "^1.6.0",
|
||||||
|
"chalk": "^2.1.0",
|
||||||
|
"debug": "^3.0.1",
|
||||||
|
"eslint": "^4.6.1",
|
||||||
|
"eslint-config-airbnb": "^15.1.0",
|
||||||
|
"eslint-plugin-babel": "^4.1.2",
|
||||||
|
"eslint-plugin-import": "^2.7.0",
|
||||||
|
"eslint-plugin-json": "^1.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^5.1.1",
|
||||||
|
"eslint-plugin-react": "^7.3.0",
|
||||||
|
"execa": "^0.8.0",
|
||||||
|
"lerna": "^2.1.2",
|
||||||
|
"nodemon": "^1.12.0",
|
||||||
|
"rimraf": "^2.6.1",
|
||||||
|
"slash": "^1.0.0",
|
||||||
|
"yon-tools": "^0.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"performance-now": "^2.1.0"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["babel-preset-env", {
|
||||||
|
"targets": {
|
||||||
|
"node": true,
|
||||||
|
"uglify": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel-plugin-dynamic-import-node",
|
||||||
|
"babel-plugin-syntax-export-extensions",
|
||||||
|
"babel-plugin-transform-class-properties",
|
||||||
|
"babel-plugin-transform-export-extensions",
|
||||||
|
"babel-plugin-transform-object-rest-spread",
|
||||||
|
"babel-plugin-transform-runtime"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# Deployed apps should consider commenting this line out:
|
||||||
|
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||||
|
node_modules/**
|
||||||
|
!node_modules/.gitkeep
|
||||||
|
|
||||||
|
# Webpack/Babel output
|
||||||
|
/dist
|
||||||
|
/lib
|
||||||
|
|
||||||
|
# Intermediate build files (cache, etc.)
|
||||||
|
/build
|
|
@ -0,0 +1,12 @@
|
||||||
|
overrides:
|
||||||
|
- files:
|
||||||
|
- src/website/**/*
|
||||||
|
rules:
|
||||||
|
import/no-extraneous-dependencies:
|
||||||
|
- error
|
||||||
|
- devDependencies: true
|
||||||
|
|
||||||
|
- files:
|
||||||
|
- src/**/*
|
||||||
|
rules:
|
||||||
|
no-console: error
|
|
@ -0,0 +1,2 @@
|
||||||
|
package-lock=false
|
||||||
|
prune=false
|
|
@ -0,0 +1,116 @@
|
||||||
|
{
|
||||||
|
"name": "@eldewrito-menu/frontend",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"browser": "./lib/website/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build:website:development": "yon run -s build:website -- --env development",
|
||||||
|
"build:website:production": "yon run -s build:website -- --env production",
|
||||||
|
"build:website:watch": "run-p build:website -- --watch",
|
||||||
|
"build:website": "webpack",
|
||||||
|
"build:production": "bnr build:production",
|
||||||
|
"build": "run-p build:website",
|
||||||
|
"clean": "rimraf build lib",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"prepublishOnly": "yon run -s build:production",
|
||||||
|
"start": "webpack-dev-server --mode development --env development --hot --inline"
|
||||||
|
},
|
||||||
|
"betterScripts": {
|
||||||
|
"build:production": {
|
||||||
|
"command": "yon run -s build",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://git@git.icedream.tech:2222/icedream/eldewrito-menu.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"website"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"author": "Carl Kittelberger <icedream@icedream.pw>",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^8.3.0",
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-core": "^6.26.0",
|
||||||
|
"babel-eslint": "^8.2.3",
|
||||||
|
"babel-loader": "^7.1.4",
|
||||||
|
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||||
|
"babel-plugin-dynamic-import-webpack": "^1.0.2",
|
||||||
|
"babel-plugin-syntax-export-extensions": "^6.13.0",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
|
"babel-plugin-transform-export-extensions": "^6.22.0",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
|
"babel-plugin-transform-react-constant-elements": "^6.23.0",
|
||||||
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
|
"better-npm-run": "^0.1.0",
|
||||||
|
"browserslist": "^3.2.4",
|
||||||
|
"case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||||
|
"chalk": "^2.4.0",
|
||||||
|
"check-types": "^7.3.0",
|
||||||
|
"css-loader": "^0.28.11",
|
||||||
|
"debug": "^3.1.0",
|
||||||
|
"eslint": "^4.19.1",
|
||||||
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
|
"eslint-plugin-babel": "^5.1.0",
|
||||||
|
"eslint-plugin-import": "^2.11.0",
|
||||||
|
"eslint-plugin-json": "^1.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
|
"eslint-plugin-react": "^7.7.0",
|
||||||
|
"eventsource-polyfill": "^0.9.6",
|
||||||
|
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
||||||
|
"file-loader": "^1.1.11",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"json-loader": "^0.5.7",
|
||||||
|
"node-fetch": "^2.1.2",
|
||||||
|
"node-sass": "^4.8.3",
|
||||||
|
"nodemon": "^1.17.3",
|
||||||
|
"normalize-scss": "^7.0.1",
|
||||||
|
"npm-run-all": "^4.1.2",
|
||||||
|
"obop": "^0.0.12",
|
||||||
|
"offline-plugin": "^4.9.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
|
"postcss-loader": "^2.1.4",
|
||||||
|
"progress-bar-webpack-plugin": "^1.11.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"react": "^16.3.2",
|
||||||
|
"react-dom": "^16.3.2",
|
||||||
|
"react-helmet": "^5.2.0",
|
||||||
|
"react-hot-loader": "^4.1.1",
|
||||||
|
"react-redux": "^5.0.7",
|
||||||
|
"redux": "^4.0.0",
|
||||||
|
"redux-actions": "^2.3.0",
|
||||||
|
"redux-promise": "^0.5.3",
|
||||||
|
"redux-promises": "^1.0.0",
|
||||||
|
"redux-saga": "^0.16.0",
|
||||||
|
"reselect": "^3.0.1",
|
||||||
|
"resolve-url-loader": "^2.3.0",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"sass-loader": "^7.0.1",
|
||||||
|
"shortid": "^2.2.8",
|
||||||
|
"slash": "^2.0.0",
|
||||||
|
"spinkit": "^1.2.5",
|
||||||
|
"style-loader": "^0.21.0",
|
||||||
|
"stylus": "^0.54.5",
|
||||||
|
"stylus-loader": "^3.0.2",
|
||||||
|
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||||
|
"url-loader": "^1.0.1",
|
||||||
|
"webfontloader": "^1.6.28",
|
||||||
|
"webpack": "^4.6.0",
|
||||||
|
"webpack-cli": "^2.0.15",
|
||||||
|
"webpack-dev-server": "^3.1.3",
|
||||||
|
"webpack-hot-middleware": "^2.22.1",
|
||||||
|
"webpack-merge": "^4.1.2",
|
||||||
|
"yon-tools": "^0.0.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["babel-preset-env", {
|
||||||
|
"targets": {
|
||||||
|
"browsers": ">1%",
|
||||||
|
"uglify": false
|
||||||
|
},
|
||||||
|
"modules": false
|
||||||
|
}],
|
||||||
|
"babel-preset-react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel-plugin-dynamic-import-node",
|
||||||
|
"babel-plugin-syntax-export-extensions",
|
||||||
|
"babel-plugin-transform-class-properties",
|
||||||
|
"babel-plugin-transform-export-extensions",
|
||||||
|
"babel-plugin-transform-react-constant-elements",
|
||||||
|
["babel-plugin-transform-runtime", {
|
||||||
|
"helpers": false,
|
||||||
|
"polyfill": false,
|
||||||
|
"regenerator": true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import store from './redux';
|
||||||
|
|
||||||
|
import MessageBoxes from './containers/MessageBoxes';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<MessageBoxes />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getTypeName } from '../redux/ui';
|
||||||
|
|
||||||
|
import style from './MessageBox.styl';
|
||||||
|
|
||||||
|
import MessageBoxButton from './MessageBoxButton';
|
||||||
|
|
||||||
|
const MessageBox = ({
|
||||||
|
children,
|
||||||
|
type,
|
||||||
|
progress,
|
||||||
|
buttons,
|
||||||
|
dispatchClose,
|
||||||
|
}) => (
|
||||||
|
<div className={[
|
||||||
|
style.message,
|
||||||
|
style[`message-${getTypeName(type)}`],
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
<div className={style['message-body']}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
buttons
|
||||||
|
? (
|
||||||
|
<div className={style.messageButtons}>
|
||||||
|
{
|
||||||
|
buttons.map(({ modifiers, close, text }, index) => (
|
||||||
|
<MessageBoxButton
|
||||||
|
modifiers={modifiers}
|
||||||
|
onClick={
|
||||||
|
close
|
||||||
|
? dispatchClose
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</MessageBoxButton>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MessageBox;
|
|
@ -0,0 +1,7 @@
|
||||||
|
.message,
|
||||||
|
.message-error,
|
||||||
|
.message-warning,
|
||||||
|
.message-info,
|
||||||
|
.message-status,
|
||||||
|
.message-question
|
||||||
|
display: block
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getTypeName } from '../redux/ui';
|
||||||
|
|
||||||
|
import style from './MessageBoxButton.styl';
|
||||||
|
|
||||||
|
const MessageBoxButton = ({
|
||||||
|
children,
|
||||||
|
modifiers = [],
|
||||||
|
onClick,
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
className={[
|
||||||
|
style['message-button'],
|
||||||
|
...modifiers.map(m => style[`message-button--${m}`]),
|
||||||
|
]}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MessageBoxButton;
|
|
@ -0,0 +1,6 @@
|
||||||
|
.message-button
|
||||||
|
display: inline-block
|
||||||
|
|
||||||
|
.message-button--primary
|
||||||
|
color: white
|
||||||
|
background: #113388
|
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import MessageBox from '../containers/MessageBox';
|
||||||
|
|
||||||
|
const MessageBoxCollection = ({
|
||||||
|
messageIds,
|
||||||
|
}) => ([
|
||||||
|
...messageIds.map(id => (
|
||||||
|
<MessageBox key={id} messageId={id} />
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default MessageBoxCollection;
|
|
@ -0,0 +1,2 @@
|
||||||
|
.messageBoxes
|
||||||
|
display: block
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { selectors, actions } from '../redux/ui';
|
||||||
|
|
||||||
|
import Component from '../components/MessageBox';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { messageId }) => {
|
||||||
|
const { text, ...props } = selectors.getMessage(state, messageId);
|
||||||
|
return ({
|
||||||
|
children: text,
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { messageId }) => ({
|
||||||
|
dispatchClose: () => {
|
||||||
|
dispatch(actions.closeMessage({ id: messageId }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConnectedMessageBoxes = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(Component);
|
||||||
|
|
||||||
|
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { selectors } from '../redux/ui';
|
||||||
|
|
||||||
|
import Component from '../components/MessageBoxes';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
messageIds: selectors.getMessages(state).map(m => m.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ConnectedMessageBoxes = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
)(Component);
|
||||||
|
|
||||||
|
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,21 @@
|
||||||
|
$debug: false
|
||||||
|
|
||||||
|
@if ($debug)
|
||||||
|
*
|
||||||
|
background-color: rgba(255, 0, 255, 0.1)
|
||||||
|
border: red 1px solid
|
||||||
|
box-sizing: border-box
|
||||||
|
position: relative
|
||||||
|
&:after
|
||||||
|
content: attr(class)
|
||||||
|
z-index: 99999
|
||||||
|
box-shadow: 0 0 10px black
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
top: 0
|
||||||
|
background: white
|
||||||
|
border: black 1px solid
|
||||||
|
padding: 4px
|
||||||
|
font-size: 12px
|
||||||
|
font-weight: bold
|
||||||
|
font-family: monospace
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>Icedream's ElDewrito Menu</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>This website requires JavaScript, please enable it.</noscript>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
|
import store from './redux';
|
||||||
|
|
||||||
|
import * as ui from './redux/ui';
|
||||||
|
import * as serverlist_loader from './redux/serverlist_loader';
|
||||||
|
|
||||||
|
// import * as OfflinePluginRuntime from 'offline-plugin/runtime';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
import './index.sass';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`%cThis is a ${process.env.NODE_ENV} build!`,
|
||||||
|
'font-family: sans-serif; color: red; font-size: 2.5em; font-weight: bold',
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
'%cThis code MUST not be deployed in production!',
|
||||||
|
'font-family: sans-serif; color: red; font-weight: bold; font-size: 1.5em',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offline caching runtime
|
||||||
|
// OfflinePluginRuntime.install();
|
||||||
|
|
||||||
|
// Create div node to render app in
|
||||||
|
const rootContainer = document.createElement('div');
|
||||||
|
document.body.appendChild(rootContainer);
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
render((
|
||||||
|
<App />
|
||||||
|
), rootContainer);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
store.dispatch(serverlist_loader.actions.updateGameServers());
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// store.dispatch(ui.actions.add({
|
||||||
|
// text: 'Hello world!',
|
||||||
|
// type: ui.messageTypes.INFO,
|
||||||
|
// }));
|
||||||
|
// }, 1000);
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
@import ~normalize-scss/sass/normalize
|
||||||
|
|
||||||
|
@import debug
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
const REDUX_PATH_SEPARATOR = '/';
|
||||||
|
|
||||||
|
export default class ReduxGenerator {
|
||||||
|
constructor(...id) {
|
||||||
|
this._id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub(name) {
|
||||||
|
return new ReduxGenerator(...this._id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
actionType(name) {
|
||||||
|
return [...this._id, name]
|
||||||
|
.join(REDUX_PATH_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
actionObject(name, payload) {
|
||||||
|
return {
|
||||||
|
type: this.actionType(name),
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
selector(selector) {
|
||||||
|
return (state, ...params) => {
|
||||||
|
const selectedState = this._id
|
||||||
|
.slice(1) // ignore root id
|
||||||
|
.reduce((current, id) => current[id], state);
|
||||||
|
|
||||||
|
return selector(selectedState, ...params);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
all({
|
||||||
|
actions = {},
|
||||||
|
selectors = {},
|
||||||
|
reducers = {},
|
||||||
|
initialState = null,
|
||||||
|
}) {
|
||||||
|
/*
|
||||||
|
Descriptor = {
|
||||||
|
someName: {
|
||||||
|
params: {
|
||||||
|
param1: (v) => typeof(value) === "string",
|
||||||
|
},
|
||||||
|
reducer(currentState, payload) {
|
||||||
|
// ... do stuff
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
...newStateDiff,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const retval = Object.entries(actions)
|
||||||
|
.reduce((current, [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
reducer,
|
||||||
|
},
|
||||||
|
]) => {
|
||||||
|
const id = this.actionType(name);
|
||||||
|
const target = {
|
||||||
|
...current,
|
||||||
|
actions: {
|
||||||
|
...current.actions,
|
||||||
|
[name]: (payload = {}) => {
|
||||||
|
// Validate all payload values
|
||||||
|
const err = Object.entries(payload)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
const validator = params[key];
|
||||||
|
if (!validator) {
|
||||||
|
console.warn(`${id}: extra argument ${JSON.stringify(key)}, ignoring`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let result = validator(value);
|
||||||
|
console.debug(`${id}: verifying ${JSON.stringify(key)} result:`, result);
|
||||||
|
switch (typeof result) {
|
||||||
|
case 'boolean':
|
||||||
|
if (!result) {
|
||||||
|
result = new Error(`${JSON.stringify(key)}: invalid value: ${JSON.stringify(value)}`);
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (result instanceof Error) {
|
||||||
|
result.message = `${JSON.stringify(key)}: ${result.message}`;
|
||||||
|
} else {
|
||||||
|
result = new Error(`${JSON.stringify(key)}: ${result}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.freeze(result);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.find(v => !!v);
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create action object
|
||||||
|
return {
|
||||||
|
type: id,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actionTypes: {
|
||||||
|
...current.actionTypes,
|
||||||
|
[name]: id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reducer) {
|
||||||
|
target.reducers = {
|
||||||
|
...current.reducers,
|
||||||
|
[name]: (state, { type, payload }) => {
|
||||||
|
// Ignore non-matching actions
|
||||||
|
if (type !== id) {
|
||||||
|
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 });
|
||||||
|
const result = reducer(state, payload);
|
||||||
|
console.debug(' =>', result);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}, {
|
||||||
|
actions: {},
|
||||||
|
actionTypes: {},
|
||||||
|
reducers: Object.entries(reducers)
|
||||||
|
.reduce((current, [name, reducer]) => ({
|
||||||
|
[name]: (state, ...args) => {
|
||||||
|
console.debug(`${this._id.join('/', `reducer:${name}`)}: called`, { state, args });
|
||||||
|
const result = reducer(state, ...args);
|
||||||
|
console.debug(' =>', result);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}), {}),
|
||||||
|
selectors: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
retval.selectors = Object.entries(selectors)
|
||||||
|
.reduce((current, [name, selector]) => ({
|
||||||
|
...current,
|
||||||
|
[name]: (state, ...args) => {
|
||||||
|
const namespaceState = this._id
|
||||||
|
.slice(1) // ignore root id
|
||||||
|
.reduce(
|
||||||
|
(currentState, namespaceName) =>
|
||||||
|
currentState[namespaceName], state);
|
||||||
|
console.debug(`${[...this._id, `selector:${name}`].join('/')}: called`, { state: namespaceState, args });
|
||||||
|
const result = selector(namespaceState, ...args);
|
||||||
|
console.debug(' =>', result);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}), selectors);
|
||||||
|
|
||||||
|
retval.reducer = (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 });
|
||||||
|
const result = initialState;
|
||||||
|
console.debug(' =>', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not using combineReducers here as we want all reducers to act on the same namespace
|
||||||
|
|
||||||
|
if (retval.reducers) {
|
||||||
|
return Object.values(retval.reducers)
|
||||||
|
.filter(value => typeof (value) === 'function')
|
||||||
|
.reduce(
|
||||||
|
(current, reducer) => reducer(current, ...args),
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(`${this._id.join('/')}: no reducers for this namespace, skipping`);
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (initialState) {
|
||||||
|
// retval.initialState = initialState;
|
||||||
|
// }
|
||||||
|
|
||||||
|
console.debug('generated redux module:', retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import check from 'check-types';
|
||||||
|
|
||||||
|
function ipv4(v) {
|
||||||
|
if (!check.string(v)) {
|
||||||
|
return new Error('not a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = v.split('.');
|
||||||
|
if (segments.length !== 4) {
|
||||||
|
return new Error('not a valid IPv4');
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments
|
||||||
|
.map(segmentValue => parseInt(segmentValue, 10))
|
||||||
|
.every(segmentValue =>
|
||||||
|
check.integer(segmentValue) && check.inRange(segmentValue, 0x00, 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
function port(v) {
|
||||||
|
return check.integer(v) &&
|
||||||
|
check.inRange(v, 0x0001, 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
function positiveInteger(v) {
|
||||||
|
return check.integer(v) && check.positive(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...check,
|
||||||
|
ipv4,
|
||||||
|
port,
|
||||||
|
positiveInteger,
|
||||||
|
};
|
|
@ -0,0 +1,81 @@
|
||||||
|
import {
|
||||||
|
createStore,
|
||||||
|
applyMiddleware,
|
||||||
|
combineReducers,
|
||||||
|
compose,
|
||||||
|
} from 'redux';
|
||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
import {
|
||||||
|
all,
|
||||||
|
fork,
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import * as serverlist from './serverlist';
|
||||||
|
import * as serverlistLoader from './serverlist_loader';
|
||||||
|
import * as ui from './ui';
|
||||||
|
import * as mastersync from './mastersync';
|
||||||
|
|
||||||
|
const reduxModules = Object.entries({
|
||||||
|
serverlist,
|
||||||
|
serverlistLoader,
|
||||||
|
ui,
|
||||||
|
mastersync,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.debug('all redux modules', reduxModules);
|
||||||
|
|
||||||
|
const reducers =
|
||||||
|
reduxModules
|
||||||
|
.reduce((current, [name, m]) => {
|
||||||
|
if (!m.reducer) { return current; }
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
[name]: m.reducer,
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
console.debug('combined reducers:', reducers);
|
||||||
|
|
||||||
|
const reducer = combineReducers(reducers);
|
||||||
|
|
||||||
|
// const initialState =
|
||||||
|
// reduxModules
|
||||||
|
// .reduce((current, [name, m]) => {
|
||||||
|
// if (!m.initialState) { return current; }
|
||||||
|
// return {
|
||||||
|
// ...current,
|
||||||
|
// [name]: m.initialState,
|
||||||
|
// };
|
||||||
|
// }, {});
|
||||||
|
// console.debug('combined initial state:', initialState);
|
||||||
|
|
||||||
|
// create the saga middleware
|
||||||
|
function* rootSaga() {
|
||||||
|
const sagas = Object.values(reduxModules)
|
||||||
|
.reduce((current, [name, m]) => {
|
||||||
|
if (!m.sagas) {
|
||||||
|
console.debug(`Module ${name} does not have any sagas`);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...current,
|
||||||
|
...m.sagas,
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
console.debug('Will run these sagas from root:', sagas);
|
||||||
|
yield all(sagas);
|
||||||
|
console.debug('Root saga done');
|
||||||
|
}
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
||||||
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
export default createStore(
|
||||||
|
reducer,
|
||||||
|
// initialState,
|
||||||
|
composeEnhancers(
|
||||||
|
applyMiddleware(
|
||||||
|
sagaMiddleware,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
sagaMiddleware.run(rootSaga);
|
|
@ -0,0 +1,139 @@
|
||||||
|
import {
|
||||||
|
takeEvery,
|
||||||
|
put,
|
||||||
|
call,
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
import * as ui from '../ui';
|
||||||
|
|
||||||
|
import check from '../check';
|
||||||
|
import root from '../root';
|
||||||
|
|
||||||
|
let myRedux;
|
||||||
|
|
||||||
|
export const states = {
|
||||||
|
IDLE: 0,
|
||||||
|
LOADING: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throw new Error(`unexpected HTTP error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const dewritoJson = yield call(response.json.bind(response));
|
||||||
|
yield put(myRedux.actions.updateDewritoJsonFinished({
|
||||||
|
dewritoJson,
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
yield put(myRedux.actions.updateDewritoJsonFinished({
|
||||||
|
err,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
console.debug('mastersync doDewritoJsonUpdateRequest: done');
|
||||||
|
}
|
||||||
|
|
||||||
|
function* storeMasters({
|
||||||
|
payload: {
|
||||||
|
err,
|
||||||
|
dewritoJson,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
console.debug('mastersync storeMasters saga called');
|
||||||
|
if (err) {
|
||||||
|
yield put(ui.actions.add({
|
||||||
|
text: `Failed to fetch master server information: ${err.message}`,
|
||||||
|
type: ui.messageTypes.ERROR,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
masterServers,
|
||||||
|
} = dewritoJson;
|
||||||
|
yield put(myRedux.actions.setMasters({ masters: masterServers }));
|
||||||
|
console.debug('mastersync storeMasters: done');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
state: states.IDLE,
|
||||||
|
masters: [],
|
||||||
|
caches: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
myRedux = root.sub('mastersync').all({
|
||||||
|
initialState,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
updateDewritoJson: {},
|
||||||
|
|
||||||
|
updateDewritoJsonStarted: {
|
||||||
|
reducer: state => ({
|
||||||
|
...state,
|
||||||
|
state: states.LOADING,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDewritoJsonFinished: {
|
||||||
|
params: {
|
||||||
|
err: v => v == null || v instanceof Error,
|
||||||
|
dewritoJson: v => v === undefined ||
|
||||||
|
check.like(v, {
|
||||||
|
masterServers: [
|
||||||
|
{
|
||||||
|
list: '',
|
||||||
|
announce: '',
|
||||||
|
stats: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
reducer: state => ({
|
||||||
|
...state,
|
||||||
|
state: states.IDLE,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
setMasters: {
|
||||||
|
params: {
|
||||||
|
masters: v => check.array(v),
|
||||||
|
},
|
||||||
|
reducer: (state, { masters }) => ({
|
||||||
|
...state,
|
||||||
|
masters,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
setCaches: {
|
||||||
|
params: {
|
||||||
|
masters: v => check.array(v),
|
||||||
|
},
|
||||||
|
reducer: (state, { caches }) => ({
|
||||||
|
...state,
|
||||||
|
caches,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// selectors are all function(state, ...parameters)
|
||||||
|
selectors: {
|
||||||
|
hasMasters: ({ masters }) => masters.length > 0,
|
||||||
|
getMasters: ({ masters }) => ([...masters]),
|
||||||
|
hasCaches: ({ caches }) => caches.length > 0,
|
||||||
|
getCaches: ({ caches }) => ([...caches]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sagas = [
|
||||||
|
takeEvery(myRedux.actionTypes.updateDewritoJson, doDewritoJsonUpdateRequest),
|
||||||
|
takeEvery(myRedux.actionTypes.updateDewritoJsonFinished, storeMasters),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const {
|
||||||
|
actions,
|
||||||
|
actionTypes,
|
||||||
|
reducer,
|
||||||
|
selectors,
|
||||||
|
} = myRedux;
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ReduxBase from './base';
|
||||||
|
|
||||||
|
export default new ReduxBase('@eldewrito-menu');
|
|
@ -0,0 +1,111 @@
|
||||||
|
import obop from 'obop';
|
||||||
|
|
||||||
|
import check from '../check';
|
||||||
|
import root from '../root';
|
||||||
|
|
||||||
|
const reduxGenerator = root.sub('serverlist');
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
servers: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const myRedux = reduxGenerator.all({
|
||||||
|
initialState,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add: {
|
||||||
|
params: {
|
||||||
|
ip: check.ipv4, // server ip
|
||||||
|
ping: check.number, // server ping
|
||||||
|
|
||||||
|
name: check.string, // server name
|
||||||
|
port: check.port, // server port
|
||||||
|
hostPlayer: check.string, // hosting player
|
||||||
|
sprintEnabled: v => check.includes(['0', '1'], v),
|
||||||
|
sprintUnlimitedEnabled: v => check.includes(['0', '1'], v),
|
||||||
|
dualWielding: v => check.includes(['0', '1'], v),
|
||||||
|
assassinationEnabled: v => check.includes(['0', '1'], v),
|
||||||
|
votingEnabled: check.boolean,
|
||||||
|
teams: check.boolean,
|
||||||
|
map: check.string,
|
||||||
|
mapFile: check.string,
|
||||||
|
variant: check.maybe.string,
|
||||||
|
variantType: check.maybe.string,
|
||||||
|
teamScores: check.maybe.array.of.number,
|
||||||
|
status: check.string,
|
||||||
|
numPlayers: check.positiveInteger,
|
||||||
|
mods: check.array, // TODO - fine tune this
|
||||||
|
maxPlayers: check.positiveInteger,
|
||||||
|
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({
|
||||||
|
name: '',
|
||||||
|
serviceTag: '',
|
||||||
|
team: 0,
|
||||||
|
uid: '',
|
||||||
|
primaryColor: '#000000',
|
||||||
|
isAlive: true,
|
||||||
|
score: 0,
|
||||||
|
kills: 0,
|
||||||
|
assists: 0,
|
||||||
|
deaths: 0,
|
||||||
|
betrayals: 0,
|
||||||
|
timeSpentAlive: 0,
|
||||||
|
suicides: 0,
|
||||||
|
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
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add server
|
||||||
|
return {
|
||||||
|
current,
|
||||||
|
servers: [
|
||||||
|
...current.servers,
|
||||||
|
payload,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: {
|
||||||
|
reducer: () => initialState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// selectors are all function(state, ...parameters)
|
||||||
|
selectors: {
|
||||||
|
/*
|
||||||
|
Example:
|
||||||
|
filter(state, {
|
||||||
|
name: {
|
||||||
|
$in: ["substring"],
|
||||||
|
}
|
||||||
|
gameType: "slayer",
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
filterServers({ servers }, filter, { order } = {}) {
|
||||||
|
let filtered = obop.where(servers, filter);
|
||||||
|
if (order) {
|
||||||
|
filtered = obop.order(filtered, order);
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
actions,
|
||||||
|
actionTypes,
|
||||||
|
reducer,
|
||||||
|
selectors,
|
||||||
|
} = myRedux;
|
|
@ -0,0 +1,156 @@
|
||||||
|
import {
|
||||||
|
takeEvery,
|
||||||
|
take,
|
||||||
|
call,
|
||||||
|
select,
|
||||||
|
all,
|
||||||
|
put,
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
import check from '../check';
|
||||||
|
import root from '../root';
|
||||||
|
|
||||||
|
import * as serverlist from '../serverlist';
|
||||||
|
import * as mastersync from '../mastersync';
|
||||||
|
|
||||||
|
let myRedux;
|
||||||
|
|
||||||
|
export const states = {
|
||||||
|
IDLE: 0,
|
||||||
|
LOADING: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
state: states.IDLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
function* inspectGameServer(addr) {
|
||||||
|
check.assert.string(addr);
|
||||||
|
|
||||||
|
const addrSplit = addr.split(':');
|
||||||
|
const ip = addrSplit[0];
|
||||||
|
check.assert(check.ipv4(ip));
|
||||||
|
|
||||||
|
const port = parseInt(addrSplit[1], 10);
|
||||||
|
check.assert(check.port(port));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = yield call(fetch, `http://${ip}:${port}`);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Request to game server ${addr} failed:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateGameServersFromMaster(masterListUrl) {
|
||||||
|
check.assert.string(masterListUrl);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = yield call(fetch, masterListUrl);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Server returned HTTP error code ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
listVersion,
|
||||||
|
result: {
|
||||||
|
code,
|
||||||
|
msg,
|
||||||
|
servers,
|
||||||
|
},
|
||||||
|
} = yield call(response.json.bind(response));
|
||||||
|
switch (listVersion) {
|
||||||
|
case 1:
|
||||||
|
if (code !== 0) {
|
||||||
|
throw new Error(`Server returned error code ${code}: ${msg}`);
|
||||||
|
}
|
||||||
|
yield all(servers.map(addr => call(inspectGameServer, addr)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported list version: ${listVersion}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Update from ${masterListUrl} failed:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateGameServers() {
|
||||||
|
yield put(mastersync.actions.updateDewritoJsonStarted());
|
||||||
|
|
||||||
|
// First let's ensure we have master servers at all
|
||||||
|
if (!(yield select(mastersync.selectors.hasMasters))) {
|
||||||
|
// Fetch master servers and hope for success
|
||||||
|
yield put(mastersync.actions.updateDewritoJson());
|
||||||
|
const { err } = yield take(mastersync.actionTypes.updateDewritoJsonFinished);
|
||||||
|
if (err) {
|
||||||
|
yield put(mastersync.actions.updateDewritoJsonFinished({ err }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have master servers, let's check all game servers!
|
||||||
|
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({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
myRedux = root.sub('serverlist_loader').all({
|
||||||
|
initialState,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
updateGameServers: {},
|
||||||
|
|
||||||
|
updateGameServersStarted: {
|
||||||
|
reducer: state => ({
|
||||||
|
...state,
|
||||||
|
state: states.LOADING,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
updateGameServersFinished: {
|
||||||
|
reducer: state => ({
|
||||||
|
...state,
|
||||||
|
state: states.IDLE,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// selectors are all function(state, ...parameters)
|
||||||
|
selectors: {
|
||||||
|
getState: ({ state }) => state,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sagas = [
|
||||||
|
takeEvery(myRedux.actionTypes.updateGameServers, updateGameServers),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const {
|
||||||
|
actions,
|
||||||
|
actionTypes,
|
||||||
|
reducer,
|
||||||
|
selectors,
|
||||||
|
} = myRedux;
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import shortid from 'shortid';
|
||||||
|
|
||||||
|
import check from '../check';
|
||||||
|
import root from '../root';
|
||||||
|
|
||||||
|
export const messageTypes = {
|
||||||
|
STATUS: 0,
|
||||||
|
INFO: 1,
|
||||||
|
WARNING: 2,
|
||||||
|
ERROR: 3,
|
||||||
|
QUESTION: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getTypeName(type) {
|
||||||
|
const matchingTypeItem = Object.entries(messageTypes)
|
||||||
|
.find(item => item[1] === type);
|
||||||
|
if (!matchingTypeItem) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return matchingTypeItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const myRedux = root.sub('ui').all({
|
||||||
|
initialState,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add: {
|
||||||
|
params: {
|
||||||
|
id: v => v === undefined || check.string(v),
|
||||||
|
text: v => check.string(v),
|
||||||
|
type: v => v in Object.values(messageTypes),
|
||||||
|
buttons: v => v === undefined || check.array(v),
|
||||||
|
},
|
||||||
|
reducer: (state, {
|
||||||
|
id,
|
||||||
|
buttons,
|
||||||
|
...payload
|
||||||
|
}) => {
|
||||||
|
let finalId = id;
|
||||||
|
|
||||||
|
// generate new unique id if none given
|
||||||
|
if (finalId === undefined) {
|
||||||
|
const hasMatchingId = m => m.id === finalId;
|
||||||
|
do {
|
||||||
|
finalId = shortid.generate();
|
||||||
|
} while (state.messages.find(hasMatchingId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// no buttons => default to "OK" button
|
||||||
|
let finalButtons = buttons;
|
||||||
|
if (!finalButtons) {
|
||||||
|
finalButtons = [
|
||||||
|
{
|
||||||
|
text: 'OK', // TODO - localization
|
||||||
|
close: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ({
|
||||||
|
...state,
|
||||||
|
messages: [
|
||||||
|
...state.messages,
|
||||||
|
{
|
||||||
|
...payload,
|
||||||
|
id: finalId,
|
||||||
|
buttons: finalButtons,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
params: {
|
||||||
|
id: v => check.string(v),
|
||||||
|
text: v => v === undefined || check.string(v),
|
||||||
|
progress: v => v === undefined || check.inRange(v, 0, 1),
|
||||||
|
},
|
||||||
|
reducer: (state, { id, ...payload }) => ({
|
||||||
|
...state,
|
||||||
|
messages: state.messages.map(m =>
|
||||||
|
(m.id === id
|
||||||
|
? { ...m, ...payload }
|
||||||
|
: m),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
closeMessage: {
|
||||||
|
params: {
|
||||||
|
id: v => check.string(v),
|
||||||
|
},
|
||||||
|
reducer: (state, { id }) => ({
|
||||||
|
...state,
|
||||||
|
messages: state.messages.filter(m => m.id !== id),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: {
|
||||||
|
reducer: () => initialState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
generateMessageId: ({ messages }) => {
|
||||||
|
let id;
|
||||||
|
const anyMessageWithId = m => m.id === id;
|
||||||
|
do {
|
||||||
|
id = shortid.generate();
|
||||||
|
} while (messages.find(anyMessageWithId));
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
|
||||||
|
getMessages: ({ messages }) => ([...messages]),
|
||||||
|
|
||||||
|
getMessage: ({ messages }, id) => messages.find(m => m.id === id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
actions,
|
||||||
|
actionTypes,
|
||||||
|
reducer,
|
||||||
|
selectors,
|
||||||
|
} = myRedux;
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
import {
|
||||||
|
DefinePlugin,
|
||||||
|
NamedModulesPlugin,
|
||||||
|
HashedModuleIdsPlugin,
|
||||||
|
LoaderOptionsPlugin,
|
||||||
|
HotModuleReplacementPlugin,
|
||||||
|
NoEmitOnErrorsPlugin,
|
||||||
|
|
||||||
|
optimize,
|
||||||
|
} from 'webpack';
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { isatty } from 'tty';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import _debug from 'debug';
|
||||||
|
// import slash from 'slash';
|
||||||
|
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
|
||||||
|
import ExtractTextPlugin from 'extract-text-webpack-plugin';
|
||||||
|
import HtmlPlugin from 'html-webpack-plugin';
|
||||||
|
import ProgressBarPlugin from 'progress-bar-webpack-plugin';
|
||||||
|
import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
|
||||||
|
|
||||||
|
import Environment from '../../config/webpack/environment';
|
||||||
|
|
||||||
|
const debug = _debug('webpack:config');
|
||||||
|
debug.generated = _debug('webpack:config:generated');
|
||||||
|
|
||||||
|
debug.generated('filename:', __filename);
|
||||||
|
debug.generated('dirname:', __dirname);
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
ModuleConcatenationPlugin,
|
||||||
|
} = optimize;
|
||||||
|
|
||||||
|
const websiteSubfolder = 'website';
|
||||||
|
|
||||||
|
export default (options) => {
|
||||||
|
const environment = new Environment({
|
||||||
|
ExtractTextPlugin, // @HACK
|
||||||
|
});
|
||||||
|
|
||||||
|
environment.input(options);
|
||||||
|
|
||||||
|
debug.generated(environment);
|
||||||
|
|
||||||
|
const {
|
||||||
|
development,
|
||||||
|
production,
|
||||||
|
server,
|
||||||
|
autoprefixerTargets,
|
||||||
|
} = environment;
|
||||||
|
|
||||||
|
const baseOutputFilename = development
|
||||||
|
? 'assets/dev/[name].dev.[ext]'
|
||||||
|
// Always use a hash (in production) to prevent files with the same name causing issues
|
||||||
|
: 'assets/prod/[chunkhash:2]/[name].[chunkhash:8].[ext]';
|
||||||
|
|
||||||
|
const webpackChunkFilename = baseOutputFilename
|
||||||
|
.replace(/\[ext(.*?)\]/g, 'js');
|
||||||
|
const webpackOutputFilename = webpackChunkFilename;
|
||||||
|
|
||||||
|
const assetOutputFilename = baseOutputFilename
|
||||||
|
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
|
||||||
|
|
||||||
|
const cssOutputFileName = baseOutputFilename
|
||||||
|
.replace(/\[ext(.*?)\]/g, 'css')
|
||||||
|
.replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
||||||
|
// const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
|
||||||
|
const cssOutputRebasePath = '/';
|
||||||
|
|
||||||
|
// Default options for url-loader
|
||||||
|
const urlLoaderOptions = {
|
||||||
|
limit: 1, // Don't inline anything (but empty files) by default
|
||||||
|
name: assetOutputFilename,
|
||||||
|
publicPath: cssOutputRebasePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'frontend',
|
||||||
|
target: 'web',
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
// inline: true,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
// historyApiFallback: {
|
||||||
|
// index: '/',
|
||||||
|
// },
|
||||||
|
hot: true,
|
||||||
|
noInfo: true,
|
||||||
|
overlay: true,
|
||||||
|
publicPath: '',
|
||||||
|
quiet: false,
|
||||||
|
watchOptions: {
|
||||||
|
ignored: /node_modules/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.jsx?/i,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
// Look for babel configuration in project directory
|
||||||
|
babelrc: false,
|
||||||
|
// Cache transformations to the filesystem (in default temp dir)
|
||||||
|
cacheDirectory: true,
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
['babel-preset-env', {
|
||||||
|
targets: {
|
||||||
|
browsers: autoprefixerTargets,
|
||||||
|
uglify: false,
|
||||||
|
},
|
||||||
|
// spec: true,
|
||||||
|
// debug: development,
|
||||||
|
modules: false, // do not transpile modules, webpack 2+ does that
|
||||||
|
}],
|
||||||
|
'babel-preset-react',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'babel-plugin-transform-react-constant-elements',
|
||||||
|
'babel-plugin-transform-class-properties',
|
||||||
|
'babel-plugin-transform-object-rest-spread',
|
||||||
|
['babel-plugin-transform-runtime', {
|
||||||
|
helpers: false,
|
||||||
|
polyfill: false,
|
||||||
|
regenerator: true,
|
||||||
|
}],
|
||||||
|
'babel-plugin-dynamic-import-webpack',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.json?/i,
|
||||||
|
loader: 'json-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
...[
|
||||||
|
/\.(gif|png|webp)$/i, // graphics
|
||||||
|
/\.svg$/i, // svg
|
||||||
|
/\.jpe?g$/i, // jpeg
|
||||||
|
/\.(mp4|ogg|webm)$/i, // video
|
||||||
|
/\.(eot|otf|ttf|woff|woff2)$/i, // fonts
|
||||||
|
/\.(wav|mp3|m4a|aac|oga)$/i, // audio
|
||||||
|
].map(test => ({
|
||||||
|
test,
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: urlLoaderOptions,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: environment.styleLoaders(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/,
|
||||||
|
use: environment.styleLoaders(
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
use: environment.styleLoaders(
|
||||||
|
{
|
||||||
|
loader: 'stylus-loader',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
strictExportPresence: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
output: {
|
||||||
|
filename: webpackOutputFilename,
|
||||||
|
chunkFilename: webpackChunkFilename,
|
||||||
|
path: path.join(__dirname, 'lib', websiteSubfolder),
|
||||||
|
publicPath: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
// Show progress as a bar during build
|
||||||
|
isatty(1) && new ProgressBarPlugin({
|
||||||
|
complete: chalk.white('\u2588'),
|
||||||
|
incomplete: chalk.grey('\u2591'),
|
||||||
|
format: `:bar ${chalk.cyan.bold(':percent')} Webpack build: ${chalk.grey(':msg')}`,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Enforce case-sensitive import paths
|
||||||
|
new CaseSensitivePathsPlugin(),
|
||||||
|
// Replace specified expressions with values
|
||||||
|
new DefinePlugin({
|
||||||
|
'process.env.__DEV__': JSON.stringify(development),
|
||||||
|
'process.env.__PROD__': JSON.stringify(production),
|
||||||
|
'process.env.__SERVER__': JSON.stringify(server),
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Dev server build
|
||||||
|
...[
|
||||||
|
// Hot module reloading
|
||||||
|
new HotModuleReplacementPlugin(),
|
||||||
|
new NoEmitOnErrorsPlugin(),
|
||||||
|
|
||||||
|
// Use paths as names when serving
|
||||||
|
new NamedModulesPlugin(),
|
||||||
|
].filter(() => server),
|
||||||
|
|
||||||
|
// If we're not serving, we're creating a static build
|
||||||
|
...[// Extract imported stylesheets out into .css files
|
||||||
|
new ExtractTextPlugin({
|
||||||
|
allChunks: true,
|
||||||
|
filename: cssOutputFileName,
|
||||||
|
}),
|
||||||
|
].filter(() => !server),
|
||||||
|
|
||||||
|
// Move modules imported from node_modules/ into a vendor chunk when enabled
|
||||||
|
// FIXME - this plugin has been removed, use new option from Webpack 4
|
||||||
|
// new CommonsChunkPlugin({
|
||||||
|
// name: 'vendor',
|
||||||
|
// minChunks(module) {
|
||||||
|
// return (module.resource && module.resource.includes('node_modules'));
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
|
||||||
|
// If we're generating an HTML file, we must be building a web app, so
|
||||||
|
// configure deterministic hashing for long-term caching.
|
||||||
|
|
||||||
|
// Generate stable module ids instead of having Webpack assign integers.
|
||||||
|
// NamedModulesPlugin allows for easier debugging and
|
||||||
|
// HashedModuleIdsPlugin does this without adding too much to bundle
|
||||||
|
// size.
|
||||||
|
development
|
||||||
|
? new NamedModulesPlugin()
|
||||||
|
: new HashedModuleIdsPlugin(),
|
||||||
|
|
||||||
|
// Production builds
|
||||||
|
...[
|
||||||
|
// JavaScript minification
|
||||||
|
new LoaderOptionsPlugin({ debug: false, minimize: true }),
|
||||||
|
|
||||||
|
// Hoisting
|
||||||
|
new ModuleConcatenationPlugin(),
|
||||||
|
].filter(() => production),
|
||||||
|
|
||||||
|
new HtmlPlugin({
|
||||||
|
template: path.join(__dirname, 'src', websiteSubfolder, 'index.html'),
|
||||||
|
filename: 'index.html',
|
||||||
|
hash: false,
|
||||||
|
inject: true,
|
||||||
|
compile: true,
|
||||||
|
favicon: false,
|
||||||
|
minify: production ? {
|
||||||
|
removeComments: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
removeScriptTypeAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
// includeAutoGeneratedTags: false,
|
||||||
|
collapseWhitespace: true,
|
||||||
|
// conservativeCollapse: true,
|
||||||
|
} : false,
|
||||||
|
cache: true,
|
||||||
|
showErrors: true,
|
||||||
|
chunks: 'all',
|
||||||
|
excludeChunks: [],
|
||||||
|
title: 'VIzon Countdown',
|
||||||
|
xhtml: false,
|
||||||
|
chunksSortMode: 'dependency',
|
||||||
|
}),
|
||||||
|
|
||||||
|
].filter(plugin => plugin !== false),
|
||||||
|
resolve: {
|
||||||
|
extensions: [
|
||||||
|
'.js', '.json', '.jsx',
|
||||||
|
],
|
||||||
|
alias: {
|
||||||
|
'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolveLoader: {
|
||||||
|
modules: ['node_modules', 'nwb'],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: production,
|
||||||
|
minimizer: [
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
parallel: true,
|
||||||
|
uglifyOptions: {
|
||||||
|
compress: {
|
||||||
|
warnings: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
comments: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourceMap: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devtool: server ? 'cheap-module-source-map' : 'source-map',
|
||||||
|
entry: {
|
||||||
|
app: [
|
||||||
|
...[
|
||||||
|
'eventsource-polyfill',
|
||||||
|
'react-hot-loader/patch',
|
||||||
|
'webpack-hot-middleware/client',
|
||||||
|
].filter(() => server).map(require.resolve),
|
||||||
|
path.join(__dirname, 'src', websiteSubfolder),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
debug.generated(config);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue