chore: initial
This commit is contained in:
commit
a16a54f4d7
33
.eslint.defaults.js
Normal file
33
.eslint.defaults.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
globals: {
|
||||||
|
process: true,
|
||||||
|
AVOCADO_CLIENT: true,
|
||||||
|
AVOCADO_SERVER: true,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'babel/object-curly-spacing': 'off',
|
||||||
|
'brace-style': ['error', 'stroustrup'],
|
||||||
|
'no-bitwise': ['error', {int32Hint: true}],
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
'padded-blocks': ['error', {classes: 'always'}],
|
||||||
|
yoda: 'off',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
webpack: {
|
||||||
|
config: `${__dirname}/webpack.config.js`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('client' === side) {
|
||||||
|
config.rules['jsx-a11y/label-has-associated-control'] = [2, {
|
||||||
|
'assert': 'either',
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config;
|
8
.eslintrc.js
Normal file
8
.eslintrc.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
// Have to default side for IDE eslint.
|
||||||
|
process.env.SIDE = process.env.SIDE || 'client';
|
||||||
|
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
module.exports = neutrino(require(`./.neutrinorc.${side}`)).eslintrc();
|
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Neutrino build directory
|
||||||
|
build
|
5
.mocharc.js
Normal file
5
.mocharc.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
module.exports = neutrino(require(`./.neutrinorc.${side}`)).mocha();
|
80
.neutrinorc.client.js
Normal file
80
.neutrinorc.client.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
const path = require('path');
|
||||||
|
const react = require('@neutrinojs/react');
|
||||||
|
const styles = require('@neutrinojs/style-loader');
|
||||||
|
const globImporter = require('node-sass-glob-importer');
|
||||||
|
const {DefinePlugin} = require('webpack');
|
||||||
|
|
||||||
|
const {afterPlatform, initial} = require('./middleware');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
options: {
|
||||||
|
root: __dirname,
|
||||||
|
},
|
||||||
|
use: [
|
||||||
|
initial({
|
||||||
|
environmentDefines: ['FRONTEND_ORIGIN'],
|
||||||
|
scwpPaths: [/^@avocado/],
|
||||||
|
}),
|
||||||
|
react({
|
||||||
|
devServer: {
|
||||||
|
contentBase: path.resolve(__dirname, 'resource'),
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
test: /\.(css|sass|scss)$/,
|
||||||
|
modulesTest: /\.module\.(css|sass|scss)$/,
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
useId: 'postcss',
|
||||||
|
options: {
|
||||||
|
config: {
|
||||||
|
path: __dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
useId: 'sass',
|
||||||
|
options: {
|
||||||
|
importer: globImporter(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
html: {
|
||||||
|
template: `${__dirname}/src/client/index.ejs`,
|
||||||
|
title: 'Persea',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
afterPlatform({
|
||||||
|
babelPaths: [/^@avocado/, /^contempo/, /^scwp/],
|
||||||
|
}),
|
||||||
|
(neutrino) => {
|
||||||
|
neutrino.config.module.rule('style')
|
||||||
|
.oneOf('raw')
|
||||||
|
.before('normal')
|
||||||
|
.test(/\.raw\.(css|sass|scss)$/)
|
||||||
|
.use('raw')
|
||||||
|
.loader('raw-loader')
|
||||||
|
.end()
|
||||||
|
.use('postcss')
|
||||||
|
.loader('postcss-loader')
|
||||||
|
.options({config: {path: __dirname}})
|
||||||
|
.end()
|
||||||
|
.use('sass')
|
||||||
|
.loader('sass-loader')
|
||||||
|
.options({importer: globImporter()})
|
||||||
|
.end()
|
||||||
|
neutrino.config.resolve.modules
|
||||||
|
.add(`${neutrino.options.source}/client/scss`);
|
||||||
|
neutrino.config
|
||||||
|
.plugin('avocado-define')
|
||||||
|
.use(DefinePlugin, [
|
||||||
|
{
|
||||||
|
AVOCADO_CLIENT: true,
|
||||||
|
AVOCADO_SERVER: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
38
.neutrinorc.server.js
Normal file
38
.neutrinorc.server.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const {spawn} = require('child_process');
|
||||||
|
|
||||||
|
const copy = require('@neutrinojs/copy');
|
||||||
|
const node = require('@neutrinojs/node');
|
||||||
|
const {DefinePlugin} = require('webpack');
|
||||||
|
|
||||||
|
const {afterPlatform, initial} = require('./middleware');
|
||||||
|
|
||||||
|
if (!process.argv.find((arg) => arg === 'production')) {
|
||||||
|
spawn('docker', ['run', '-p', '11346:6379', 'redis'], {stdio: 'inherit'});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
options: {
|
||||||
|
root: __dirname,
|
||||||
|
},
|
||||||
|
use: [
|
||||||
|
initial({
|
||||||
|
environmentDefines: ['REDIS_ORIGIN'],
|
||||||
|
scwpPaths: [/^@avocado/],
|
||||||
|
}),
|
||||||
|
node(),
|
||||||
|
afterPlatform({
|
||||||
|
babelPaths: [/^@avocado/],
|
||||||
|
externalMatcher: /(?:@avocado|@pixi|scwp|webpack)/,
|
||||||
|
}),
|
||||||
|
(neutrino) => {
|
||||||
|
neutrino.config
|
||||||
|
.plugin('avocado-define')
|
||||||
|
.use(DefinePlugin, [
|
||||||
|
{
|
||||||
|
AVOCADO_CLIENT: false,
|
||||||
|
AVOCADO_SERVER: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
// "preLaunchTask": "Both",
|
||||||
|
"request": "attach",
|
||||||
|
"name": "Terrible",
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 43000,
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
79
.vscode/tasks.json
vendored
Normal file
79
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Client",
|
||||||
|
"type": "shell",
|
||||||
|
"isBackground": true,
|
||||||
|
"command": "yarn",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"client"
|
||||||
|
],
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "custom",
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "__________"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "(Compiling|Project is running)",
|
||||||
|
"endsPattern": "(?:Compiled successfully|Failed to compile)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Server",
|
||||||
|
"type": "shell",
|
||||||
|
"isBackground": true,
|
||||||
|
"command": "yarn",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"server",
|
||||||
|
"--inspect=127.0.0.1:43000"
|
||||||
|
],
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "custom",
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "You need to restart the application"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "custom",
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "__________"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "(Hash:|Updated modules)",
|
||||||
|
"endsPattern": "(Built at:|Update applied)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Both",
|
||||||
|
"isBackground": true,
|
||||||
|
"dependsOn": [
|
||||||
|
"Client",
|
||||||
|
"Server"
|
||||||
|
],
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"command": "yarn",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"lint"
|
||||||
|
],
|
||||||
|
"problemMatcher": [
|
||||||
|
"$eslint-stylish"
|
||||||
|
],
|
||||||
|
"label": "yarn run lint"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FROM node:14
|
||||||
|
EXPOSE 11344
|
||||||
|
COPY ./build /var/www
|
||||||
|
COPY ./node_modules /var/www/server/node_modules
|
||||||
|
CMD ["node", "/var/www/server/index.js"]
|
1
fixtures/kitty.entity.json
Normal file
1
fixtures/kitty.entity.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"traits":{"alive":{},"animated":{"params":{"animations":{"idle":{"offset":[0,-3],"uri":"/kitty.animation.json"}}}},"audible":{"params":{"sounds":{"deathSound":{"uri":"/ded.sound.json"}}}},"behaved":{"params":{"routines":{"type":"routines","routines":{"initial":{"type":"routine","routine":{"type":"actions","traversals":[{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}],"value":{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"floor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0},{"type":"literal","value":4}]}]}]}]}},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"moveFor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"Vector"},{"type":"key","key":"fromDirection"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0.25},{"type":"literal","value":2.5}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"isAnimating"}],"value":{"type":"literal","value":false}},{"type":"traversal","steps":[{"type":"key","key":"Timing"},{"type":"key","key":"wait"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":1},{"type":"literal","value":4}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}],"value":{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"floor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0},{"type":"literal","value":4}]}]}]}]}},{"type":"traversal","steps":[{"type":"key","key":"Timing"},{"type":"key","key":"wait"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0.5},{"type":"literal","value":3}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"isAnimating"}],"value":{"type":"literal","value":true}}]}}}}}},"collider":{"params":{"collidesWithGroups":["default","environmental","projectile"]}},"directional":{"params":{"directionCount":4},"state":{"direction":2}},"emitter":{},"existent":{"state":{"name":"Kitty"}},"layered":{},"listed":{},"lootable":{"params":{"table":[{"perc":70,"json":{"uri":"/yarn-ball.entity.json"}},{"perc":80,"json":{"uri":"/yarn-ball.entity.json"}},{"perc":90,"json":{"uri":"/yarn-ball.entity.json"}}]}},"mobile":{"state":{"speed":40}},"physical":{},"positioned":{},"roomed":{},"shaped":{"params":{"shape":{"type":"rectangle","position":[0,0],"size":[8,4]}}},"visible":{"state":{"visibleScale":[1,1]}},"vulnerable":{"params":{"types":["bio"]}}}}
|
5
inspect.js
Normal file
5
inspect.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
neutrino(require(`./.neutrinorc.${side}`)).inspect();
|
132
middleware.js
Normal file
132
middleware.js
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
const path = require('path');
|
||||||
|
const airbnb = require('@neutrinojs/airbnb');
|
||||||
|
const airbnbBase = require('@neutrinojs/airbnb-base');
|
||||||
|
const mocha = require('@neutrinojs/mocha');
|
||||||
|
const scwp = require('scwp/neutrino');
|
||||||
|
const {DefinePlugin} = require('webpack');
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
const avocadoPackages = {};
|
||||||
|
const pkg = require(`${__dirname}/package.json`);
|
||||||
|
|
||||||
|
const gatherPackagePaths = (root, packageMatchers) => {
|
||||||
|
return packageMatchers.reduce((r, packageMatcher) => (
|
||||||
|
r.concat([
|
||||||
|
(pkg.dependencies || {}),
|
||||||
|
(pkg.devDependencies || {}),
|
||||||
|
].reduce((r, deps) => {
|
||||||
|
const packageNames = Object.keys(deps);
|
||||||
|
const packages = [];
|
||||||
|
for (let i = 0; i < packageNames.length; i++) {
|
||||||
|
const packageName = packageNames[i];
|
||||||
|
if (packageName.match(packageMatcher)) {
|
||||||
|
packages.push(path.relative(root, path.dirname(require.resolve(packageName))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.concat(packages);
|
||||||
|
}, []))
|
||||||
|
), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.initial = (options) => (neutrino) => {
|
||||||
|
const {
|
||||||
|
environmentDefines = [],
|
||||||
|
scwpPaths = [],
|
||||||
|
} = options;
|
||||||
|
neutrino.options.mains.index = `${side}/index`;
|
||||||
|
neutrino.options.output = `build/${side}`;
|
||||||
|
neutrino.config.resolve.modules
|
||||||
|
.add(`${neutrino.options.root}/node_modules`);
|
||||||
|
neutrino.config.resolveLoader.modules
|
||||||
|
.add(`${neutrino.options.root}/node_modules`);
|
||||||
|
neutrino.config.resolve.alias
|
||||||
|
.set('~', neutrino.options.source);
|
||||||
|
('server' === side ? airbnbBase : airbnb)({
|
||||||
|
eslint: {
|
||||||
|
cache: false,
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
baseConfig: require('./.eslint.defaults'),
|
||||||
|
},
|
||||||
|
})(neutrino);
|
||||||
|
mocha({
|
||||||
|
spec: `src/+(${side}|common)/**/*.spec.js`,
|
||||||
|
})(neutrino);
|
||||||
|
scwp({
|
||||||
|
paths: [
|
||||||
|
'./src/common',
|
||||||
|
`./src/${side}`,
|
||||||
|
].concat(
|
||||||
|
gatherPackagePaths(neutrino.options.root, scwpPaths.concat(/^scwp/))
|
||||||
|
),
|
||||||
|
})(neutrino);
|
||||||
|
neutrino.config
|
||||||
|
.plugin('environment-define')
|
||||||
|
.use(DefinePlugin, [
|
||||||
|
environmentDefines.reduce((r, k) => ({...r, [k]: JSON.stringify(process.env[k])}), {})
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.afterPlatform = (options) => (neutrino) => {
|
||||||
|
const {
|
||||||
|
babelPaths = [],
|
||||||
|
externalMatcher = [],
|
||||||
|
} = options;
|
||||||
|
const allBabelPaths = gatherPackagePaths(neutrino.options.root, babelPaths);
|
||||||
|
neutrino.config.module
|
||||||
|
.rule('compile')
|
||||||
|
.use('babel')
|
||||||
|
.tap((options) => {
|
||||||
|
options.only = [
|
||||||
|
neutrino.options.source,
|
||||||
|
].concat(allBabelPaths);
|
||||||
|
options.ignore = [];
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
allBabelPaths.forEach((babelPath) => {
|
||||||
|
neutrino.config.module
|
||||||
|
.rule('compile')
|
||||||
|
.include
|
||||||
|
.add(path.resolve(neutrino.options.root, babelPath));
|
||||||
|
});
|
||||||
|
neutrino.config.module
|
||||||
|
.rule('compile')
|
||||||
|
.use('babel')
|
||||||
|
.get('options').plugins.push(
|
||||||
|
[
|
||||||
|
'babel-plugin-webpack-alias',
|
||||||
|
{
|
||||||
|
config: `${__dirname}/webpack.config.js`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if ('client' === side) {
|
||||||
|
neutrino.config.node.delete('Buffer');
|
||||||
|
}
|
||||||
|
else /* if ('server' === side) */ {
|
||||||
|
neutrino.config.stats('normal');
|
||||||
|
if ('production' !== process.env.NODE_ENV) {
|
||||||
|
neutrino.config
|
||||||
|
.plugin('start-server')
|
||||||
|
.tap((args) => {
|
||||||
|
const options = args[0];
|
||||||
|
const inspectArg = process.argv.find((arg) => -1 !== arg.indexOf('--inspect'));
|
||||||
|
if (inspectArg) {
|
||||||
|
options.nodeArgs.push(inspectArg);
|
||||||
|
}
|
||||||
|
const profArg = process.argv.find((arg) => -1 !== arg.indexOf('--prof'));
|
||||||
|
if (profArg) {
|
||||||
|
options.nodeArgs.push(profArg);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
neutrino.config
|
||||||
|
.externals(nodeExternals({
|
||||||
|
whitelist: externalMatcher,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
71
package.json
Normal file
71
package.json
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"name": "persea",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build:client": "SIDE=CLIENT webpack --mode production --config webpack.config.js",
|
||||||
|
"build:docker": "yarn run build:client && yarn run build:server && docker build",
|
||||||
|
"build:server": "SIDE=SERVER webpack --mode production --config webpack.config.js",
|
||||||
|
"client": "SIDE=CLIENT webpack-dev-server --verbose --disable-host-check --host persea.cha0sdev --port 11345 --mode development --config webpack.config.js",
|
||||||
|
"inspect": "node ./inspect.js",
|
||||||
|
"lint": "eslint --cache --format codeframe --ext mjs,jsx,js src",
|
||||||
|
"server": "SIDE=SERVER webpack --watch --mode development --config webpack.config.js",
|
||||||
|
"test:client": "NODE_PRESERVE_SYMLINKS=1 SIDE=client mocha --watch",
|
||||||
|
"test:server": "NODE_PRESERVE_SYMLINKS=1 SIDE=server mocha --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@avocado/behavior": "1.x",
|
||||||
|
"@avocado/core": "1.x",
|
||||||
|
"@avocado/entity": "1.x",
|
||||||
|
"@avocado/graphics": "1.x",
|
||||||
|
"@avocado/input": "1.x",
|
||||||
|
"@avocado/math": "1.x",
|
||||||
|
"@avocado/net": "1.x",
|
||||||
|
"@avocado/physics": "1.x",
|
||||||
|
"@avocado/resource": "1.x",
|
||||||
|
"@avocado/sound": "1.x",
|
||||||
|
"@avocado/timing": "1.x",
|
||||||
|
"@avocado/topdown": "1.x",
|
||||||
|
"@reduxjs/toolkit": "1.3.6",
|
||||||
|
"ansi-html": "0.0.7",
|
||||||
|
"classnames": "2.2.6",
|
||||||
|
"contempo": "1.x",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"express-session": "^1.17.1",
|
||||||
|
"express-socket.io-session": "^1.3.5",
|
||||||
|
"html-entities": "1.3.1",
|
||||||
|
"memorystore": "^1.6.2",
|
||||||
|
"prop-types": "^15",
|
||||||
|
"react": "16.8.6",
|
||||||
|
"react-dom": "16.8.6",
|
||||||
|
"react-hot-loader": "^4.12.21",
|
||||||
|
"react-markdown": "^4.3.1",
|
||||||
|
"react-tabs": "^3.1.1",
|
||||||
|
"scwp": "1.x",
|
||||||
|
"socket.io-redis": "^5.3.0",
|
||||||
|
"source-map-support": "^0.5.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/airbnb": "^9.1.0",
|
||||||
|
"@neutrinojs/airbnb-base": "^9.1.0",
|
||||||
|
"@neutrinojs/copy": "^9.2.0",
|
||||||
|
"@neutrinojs/mocha": "^9.1.0",
|
||||||
|
"@neutrinojs/node": "^9.1.0",
|
||||||
|
"@neutrinojs/react": "^9.1.0",
|
||||||
|
"autoprefixer": "9.8.0",
|
||||||
|
"babel-plugin-webpack-alias": "^2.1.2",
|
||||||
|
"eslint": "^6",
|
||||||
|
"eslint-import-resolver-webpack": "^0.12.1",
|
||||||
|
"mocha": "^7",
|
||||||
|
"neutrino": "^9.1.0",
|
||||||
|
"node-sass": "4.12.0",
|
||||||
|
"node-sass-glob-importer": "5.3.2",
|
||||||
|
"postcss-loader": "3.0.0",
|
||||||
|
"raw-loader": "1.x",
|
||||||
|
"sass-loader": "7.1.0",
|
||||||
|
"v8-natives": "^1.1.0",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3",
|
||||||
|
"webpack-dev-server": "^3"
|
||||||
|
}
|
||||||
|
}
|
5
postcss.config.js
Normal file
5
postcss.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
8
side.js
Normal file
8
side.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const side = (process.env.SIDE || '').toLowerCase();
|
||||||
|
if (-1 === ['client', 'server'].indexOf(side)) {
|
||||||
|
throw new Error(
|
||||||
|
"You must define the SIDE environment variable as 'server' or 'client' when building!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = side;
|
23
src/client/components/entity.jsx
Normal file
23
src/client/components/entity.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import {compose} from '@avocado/core';
|
||||||
|
import contempo from 'contempo';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Traits from './traits';
|
||||||
|
|
||||||
|
const decorate = compose(
|
||||||
|
contempo(require('./entity.raw.scss')),
|
||||||
|
);
|
||||||
|
|
||||||
|
const kitty = require('~/../fixtures/kitty.entity.json');
|
||||||
|
|
||||||
|
const Entity = () => {
|
||||||
|
const entity = kitty;
|
||||||
|
const {traits} = entity;
|
||||||
|
return (
|
||||||
|
<div className="entity">
|
||||||
|
<Traits traits={traits} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default decorate(Entity);
|
0
src/client/components/entity.raw.scss
Normal file
0
src/client/components/entity.raw.scss
Normal file
10
src/client/components/persea.jsx
Normal file
10
src/client/components/persea.jsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {hot} from 'react-hot-loader';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Entity from './entity';
|
||||||
|
|
||||||
|
const Persea = () => (
|
||||||
|
<Entity />
|
||||||
|
);
|
||||||
|
|
||||||
|
export default hot(module)(Persea);
|
89
src/client/components/properties/properties.hooks.jsx
Normal file
89
src/client/components/properties/properties.hooks.jsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const propertyPropTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string,
|
||||||
|
options: PropTypes.shape({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}) => (
|
||||||
|
<label>
|
||||||
|
<span className="text">{label}</span>
|
||||||
|
<div className="invisible-separator" />
|
||||||
|
<input name={name} type="checkbox" checked={value} />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
bool.propTypes = {
|
||||||
|
...propertyPropTypes,
|
||||||
|
value: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const number = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
}) => (
|
||||||
|
<label>
|
||||||
|
<span className="text">{label}</span>
|
||||||
|
<div className="invisible-separator" />
|
||||||
|
{
|
||||||
|
options
|
||||||
|
? (
|
||||||
|
<select name={name} value={value}>
|
||||||
|
{Object.entries(options).map(([optionValue, optionLabel]) => (
|
||||||
|
<option value={optionValue}>{optionLabel}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
: <input name={name} size={5} type="text" value={value} />
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
number.propTypes = {
|
||||||
|
...propertyPropTypes,
|
||||||
|
value: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const string = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
}) => (
|
||||||
|
<label>
|
||||||
|
<span className="text">{label}</span>
|
||||||
|
<div className="invisible-separator" />
|
||||||
|
{
|
||||||
|
options
|
||||||
|
? (
|
||||||
|
<select>
|
||||||
|
{ /* eslint-disable-next-line jsx-a11y/control-has-associated-label */ }
|
||||||
|
{options.map((option) => <option value={option} />)}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
: <input name={name} size={5} type="text" value={value} />
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
string.propTypes = {
|
||||||
|
...propertyPropTypes,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export function propertyComponents() {
|
||||||
|
return {
|
||||||
|
bool,
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
};
|
||||||
|
}
|
139
src/client/components/traits.jsx
Normal file
139
src/client/components/traits.jsx
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import {compose, mapObject} from '@avocado/core';
|
||||||
|
import {all} from '@avocado/entity/trait/trait-components.scwp';
|
||||||
|
import {lookupTrait} from '@avocado/entity';
|
||||||
|
import contempo from 'contempo';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
// import ReactMarkdown from 'react-markdown';
|
||||||
|
import {
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
} from 'react-tabs';
|
||||||
|
import {createSelector} from '@reduxjs/toolkit';
|
||||||
|
import {invokeHookFlat} from 'scwp';
|
||||||
|
|
||||||
|
const SCROLL_MAG = 80;
|
||||||
|
|
||||||
|
const decorate = compose(
|
||||||
|
contempo(require('./traits.raw.scss')),
|
||||||
|
);
|
||||||
|
|
||||||
|
// const fence = (code) => `\`\`\`json\n\n${code}\n\`\`\``;
|
||||||
|
|
||||||
|
// const markdown = (object) => (
|
||||||
|
// object && (
|
||||||
|
// <ReactMarkdown
|
||||||
|
// source={fence(JSON.stringify(object, null, 2))}
|
||||||
|
// escapeHtml={false}
|
||||||
|
// />
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
let TraitComponents;
|
||||||
|
const ensureTraitComponents = () => {
|
||||||
|
if (!TraitComponents) {
|
||||||
|
TraitComponents = Object.values(all()).reduce((r, M) => {
|
||||||
|
const {default: TraitComponent} = M;
|
||||||
|
return {...r, [TraitComponent.type]: TraitComponent};
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let PropertyComponents;
|
||||||
|
const ensurePropertyComponents = () => {
|
||||||
|
if (!PropertyComponents) {
|
||||||
|
const results = invokeHookFlat('propertyComponents');
|
||||||
|
PropertyComponents = results.reduce((r, map) => ({...r, ...map}), {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeTabSelector = (type) => createSelector(
|
||||||
|
(_) => _,
|
||||||
|
(trait) => {
|
||||||
|
ensurePropertyComponents();
|
||||||
|
ensureTraitComponents();
|
||||||
|
const {params: paramsRaw, state: stateRaw} = trait;
|
||||||
|
const {[type]: TraitComponent} = TraitComponents;
|
||||||
|
const Trait = lookupTrait(type);
|
||||||
|
const params = Trait ? {...Trait.defaultParams(), ...paramsRaw} : paramsRaw;
|
||||||
|
const state = Trait ? {...Trait.defaultState(), ...stateRaw} : stateRaw;
|
||||||
|
const stateDescription = Trait?.describeState() || {};
|
||||||
|
const Components = Object.values(mapObject(stateDescription, (description, key) => {
|
||||||
|
const {label, options, type: componentType} = description;
|
||||||
|
const Component = PropertyComponents[componentType];
|
||||||
|
return Component
|
||||||
|
? (
|
||||||
|
<Component
|
||||||
|
key={key}
|
||||||
|
label={label}
|
||||||
|
name={key}
|
||||||
|
options={options}
|
||||||
|
value={state[key]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
}));
|
||||||
|
return TraitComponent
|
||||||
|
? <TraitComponent params={params} state={state} />
|
||||||
|
: <form>{Components}</form>;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabSelectorMap = new WeakMap();
|
||||||
|
|
||||||
|
const Traits = (props) => {
|
||||||
|
const {traits} = props;
|
||||||
|
if (!tabSelectorMap.has(traits)) {
|
||||||
|
tabSelectorMap.set(traits, {});
|
||||||
|
}
|
||||||
|
const selectors = tabSelectorMap.get(traits);
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
|
let listRef;
|
||||||
|
const entries = Object.entries(traits);
|
||||||
|
const tabs = [];
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
const [type, trait] = entries[i];
|
||||||
|
trait.type = type;
|
||||||
|
if (!selectors[type]) {
|
||||||
|
selectors[type] = makeTabSelector(type);
|
||||||
|
}
|
||||||
|
tabs.push([type, selectors[type](trait)]);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="traits">
|
||||||
|
<Tabs
|
||||||
|
domRef={(node) => {
|
||||||
|
listRef = node && node.querySelector('ul');
|
||||||
|
}}
|
||||||
|
selectedIndex={tabIndex}
|
||||||
|
onSelect={(index) => setTabIndex(index)}
|
||||||
|
>
|
||||||
|
<TabList
|
||||||
|
onWheel={(event) => {
|
||||||
|
const {deltaY} = event;
|
||||||
|
if (listRef) {
|
||||||
|
listRef.scrollLeft += SCROLL_MAG * (deltaY / Math.abs(deltaY));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map(([type]) => <Tab key={type}>{type}</Tab>)}
|
||||||
|
</TabList>
|
||||||
|
{tabs.map(([type, content]) => <TabPanel key={type}>{content}</TabPanel>)}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Traits.propTypes = {
|
||||||
|
traits: PropTypes.shape({}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default decorate(Traits);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept(['@avocado/entity/trait/trait-components.scwp', () => {
|
||||||
|
TraitComponents = undefined;
|
||||||
|
PropertyComponents = undefined;
|
||||||
|
}]);
|
||||||
|
}
|
27
src/client/components/traits.raw.scss
Normal file
27
src/client/components/traits.raw.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@import '~/client/scss/tabs.scss';
|
||||||
|
@import '~/client/scss/trait.scss';
|
||||||
|
|
||||||
|
.react-tabs {
|
||||||
|
@extend %tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab-panel--selected {
|
||||||
|
height: calc(100vh - 2.7em);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab-panel input {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
@extend %trait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invisible-separator {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
13
src/client/index.ejs
Normal file
13
src/client/index.ejs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= htmlWebpackPlugin.options.lang %>">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="<%= htmlWebpackPlugin.options.appMountId %>">
|
||||||
|
<div class="debug-container"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
src/client/index.jsx
Normal file
13
src/client/index.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {render} from 'react-dom';
|
||||||
|
|
||||||
|
import Persea from '~/client/components/persea';
|
||||||
|
|
||||||
|
render(
|
||||||
|
(
|
||||||
|
<Persea />
|
||||||
|
),
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
142
src/client/index.scss
Normal file
142
src/client/index.scss
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article, aside, details, figcaption, figure,
|
||||||
|
footer, header, hgroup, menu, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote, q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before, q:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
ins {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background-color: #212121;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #777 #333;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #777;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 3px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #272727;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-family: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.6em;
|
||||||
|
min-height: 3em;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5em 0.5em 0.5em 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
@media(min-width: 32em) {
|
||||||
|
label {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media(min-width: 64em) {
|
||||||
|
label {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label:nth-of-type(2n+1) {
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: #333;
|
||||||
|
border: 1px solid black;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
background-color: #151515;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
top: -0.5em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input[type="checkbox"], input[type="checkbox"] + label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background: #222222;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 1em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:focus {
|
||||||
|
box-shadow: 0 0 2px 0 #d68030ff;
|
||||||
|
outline: none;
|
||||||
|
}
|
44
src/client/scss/tabs.scss
Normal file
44
src/client/scss/tabs.scss
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
%tabs {
|
||||||
|
|
||||||
|
.react-tabs__tab-list {
|
||||||
|
background-color: #272727;
|
||||||
|
font-family: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #212121;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #777;
|
||||||
|
border-radius: 0;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #aaaaaa;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1em 3em;
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-right: 1px solid #282828;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tabs__tab--selected[class] {
|
||||||
|
background-color: #212121;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
src/client/scss/trait.scss
Normal file
4
src/client/scss/trait.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
%trait {
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
6
src/common/environment.js
Normal file
6
src/common/environment.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
|
||||||
|
const withDefault = (variable, value) => ('undefined' !== typeof variable ? variable : value);
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export const redisOrigin = withDefault(REDIS_ORIGIN, 'localhost:11346');
|
27
src/server/app.js
Normal file
27
src/server/app.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import express from 'express';
|
||||||
|
import httpSession from 'express-session';
|
||||||
|
|
||||||
|
const MemoryStore = require('memorystore')(httpSession);
|
||||||
|
|
||||||
|
let insideSession;
|
||||||
|
|
||||||
|
export default function createApp() {
|
||||||
|
const app = express();
|
||||||
|
app.use(express.urlencoded({
|
||||||
|
extended: false,
|
||||||
|
}));
|
||||||
|
insideSession = httpSession({
|
||||||
|
resave: true,
|
||||||
|
saveUninitialized: true,
|
||||||
|
secret: process.env.COOKIE_SECRET || 'foobar',
|
||||||
|
store: new MemoryStore({
|
||||||
|
checkPeriod: 86400000,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
app.use(insideSession);
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function session() {
|
||||||
|
return insideSession;
|
||||||
|
}
|
57
src/server/http.js
Normal file
57
src/server/http.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import express from 'express';
|
||||||
|
import http, {ServerResponse} from 'http';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
import httpProxy from 'http-proxy';
|
||||||
|
|
||||||
|
import createApp from './app';
|
||||||
|
|
||||||
|
let app;
|
||||||
|
|
||||||
|
export function createHttpServer() {
|
||||||
|
app = createApp();
|
||||||
|
const httpServer = http.createServer(app);
|
||||||
|
httpServer.listen(11344, '0.0.0.0');
|
||||||
|
if ('production' !== process.env.NODE_ENV) {
|
||||||
|
const proxy = httpProxy.createProxyServer({
|
||||||
|
secure: false,
|
||||||
|
target: 'http://192.168.0.190:11345',
|
||||||
|
});
|
||||||
|
proxy.on('error', (err, req, res) => {
|
||||||
|
if (!(res instanceof ServerResponse)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(500, {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
});
|
||||||
|
res.end('Bad Proxy');
|
||||||
|
});
|
||||||
|
httpServer.proxy = proxy;
|
||||||
|
}
|
||||||
|
return httpServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyHttpServer(httpServer) {
|
||||||
|
if (!httpServer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
httpServer.close();
|
||||||
|
if ('production' !== process.env.NODE_ENV) {
|
||||||
|
httpServer.proxy.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function proxyHttpServer(httpServer) {
|
||||||
|
if ('production' === process.env.NODE_ENV) {
|
||||||
|
app.use(express.static(join(__dirname, '..', 'client')));
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, '..', 'client', 'index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
httpServer.proxy.web(req, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
42
src/server/index.js
Normal file
42
src/server/index.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {createHttpServer, destroyHttpServer, proxyHttpServer} from './http';
|
||||||
|
import {createSocketServer, destroySocketServer} from './sockets';
|
||||||
|
|
||||||
|
let httpServer;
|
||||||
|
let socketServer;
|
||||||
|
|
||||||
|
const connectionSet = new Set();
|
||||||
|
function trackConnections() {
|
||||||
|
httpServer.on('connection', (connection) => {
|
||||||
|
connectionSet.add(connection);
|
||||||
|
connection.once('close', () => {
|
||||||
|
connectionSet.delete(connection);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function releaseConnections() {
|
||||||
|
const connections = Array.from(connectionSet.values());
|
||||||
|
for (let i = 0; i < connections.length; i++) {
|
||||||
|
connections[i].destroy();
|
||||||
|
}
|
||||||
|
connectionSet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartListening() {
|
||||||
|
// Destroy...
|
||||||
|
destroySocketServer(socketServer);
|
||||||
|
destroyHttpServer(httpServer);
|
||||||
|
releaseConnections();
|
||||||
|
// Create...
|
||||||
|
httpServer = createHttpServer();
|
||||||
|
trackConnections();
|
||||||
|
socketServer = createSocketServer(httpServer);
|
||||||
|
proxyHttpServer(httpServer);
|
||||||
|
}
|
||||||
|
restartListening();
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot?.accept([
|
||||||
|
'./src/server/http/index.js',
|
||||||
|
'./src/server/sockets/index.js',
|
||||||
|
], restartListening);
|
||||||
|
}
|
9
src/server/repl.js
Normal file
9
src/server/repl.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import repl from 'repl';
|
||||||
|
|
||||||
|
export default function createReplServer() {
|
||||||
|
return repl.start({
|
||||||
|
prompt: 'terrible> ',
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
}
|
31
src/server/sockets/index.js
Normal file
31
src/server/sockets/index.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import redisAdapter from 'socket.io-redis';
|
||||||
|
|
||||||
|
import {SocketServer} from '@avocado/net/server/socket';
|
||||||
|
import socketSession from 'express-socket.io-session';
|
||||||
|
|
||||||
|
import {redisOrigin} from '~/common/environment';
|
||||||
|
|
||||||
|
import {session} from '~/server/app';
|
||||||
|
|
||||||
|
// const debug = require('debug')('persea:server:sockets');
|
||||||
|
|
||||||
|
export function createSocketServer(httpServer) {
|
||||||
|
const [redisHost, redisPort] = redisOrigin.split(':');
|
||||||
|
const socketServer = new SocketServer(httpServer, {
|
||||||
|
adapter: redisAdapter({
|
||||||
|
host: redisHost || 'localhost',
|
||||||
|
port: redisPort || 11346,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
socketServer.io.use(socketSession(session()));
|
||||||
|
return socketServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroySocketServer(socketServer) {
|
||||||
|
if (!socketServer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socketServer.removeTicker();
|
||||||
|
socketServer.close();
|
||||||
|
}
|
9
webpack.config.js
Normal file
9
webpack.config.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Whilst the configuration object can be modified here, the recommended way of making
|
||||||
|
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
|
||||||
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const side = require('./side');
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-dynamic-require
|
||||||
|
module.exports = neutrino(require(`./.neutrinorc.${side}`)).webpack();
|
Loading…
Reference in New Issue
Block a user