This commit is contained in:
Martin Donnelly 2021-03-12 07:55:43 +00:00
commit e1d9657e87
31 changed files with 40944 additions and 0 deletions

55
.eslintrc Executable file
View File

@ -0,0 +1,55 @@
{
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"jsx": false
}
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"arrow-spacing": "error",
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": ["error", "stroustrup", {}],
"camelcase": "error",
"comma-dangle": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": [1, "last"],
"consistent-this": [1, "_this"],
"curly": [1, "multi"],
"eol-last": 1,
"eqeqeq": 1,
"func-names": 1,
"indent": ["error", 2, { "SwitchCase": 1 }],
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
"max-len": [1, 180, 2, { "ignoreTemplateLiterals": true, "ignoreStrings": true, "ignoreUrls": true, "ignoreTrailingComments": true, "ignoreComments": true }], // 2 spaces per tab, max 80 chars per line
"new-cap": 1,
"newline-before-return": "error",
"no-array-constructor": 1,
"no-inner-declarations": [1, "both"],
"no-mixed-spaces-and-tabs": 1,
"no-multi-spaces": 2,
"no-new-object": 1,
"no-shadow-restricted-names": 1,
"object-curly-spacing": ["error", "always"],
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
"prefer-const": "error",
"prefer-template": "error",
"one-var": 0,
"quote-props": ["error", "always"],
"quotes": [1, "single"],
"radix": 1,
"semi": [1, "always"],
"space-before-blocks": [1, "always"],
"space-infix-ops": 1,
"vars-on-top": 1,
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
"spaced-comment": ["error", "always", { "markers": ["/"] }]
}
}

166
.gitignore vendored Executable file
View File

@ -0,0 +1,166 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 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
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://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/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# artefacts/screenshots/*.png
artefacts/*.txt
artefacts/*.json
# artefacts/*.html
# artefacts/*
/tests/*.zip
/output/
# /dist/
!/tests/data/
/tests/sink/
/debug/
/update.sh
/setup/web/
/backup/
/db/
/archive.tar.gz
/user/
/zip
!/artefacts/
menu.db
menu.db.backup

2
.prettierignore Executable file
View File

@ -0,0 +1,2 @@
node_modules/*
public

8
.prettierrc Executable file
View File

@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}

50
README.md Normal file
View File

@ -0,0 +1,50 @@
# FSB Backbone
Based upon the FSB React test. Built against Node v14.15.5.
When unpacked `cd` into the folder, run the command:
```shell
npm install
```
This will install all dependencies and build the application.
To run the application, run this following command:
```shell
npm start:server
```
This should start the server running on [http://localhost:3001](http://localhost:3001).
To rebuild the source code at any time, run the following command:
```shell
npm build
```
Built code is located in the live folder.
## Notes
I tried to stick to about 4 hours for this test and sadly did not get any user interaction working. I was concentrating on getting the data onto the page and updating from the Socket.
An issue was discovered with `server/selectionPriceChange.js`, this was sending `selection.price` as the id, which prevented the socket from updated correctly. Unsure if this was intentional or a 'gotcha'.
There was a note about making it all run from the same port, I have implemented that here. Building to `/live` and having the server use that as static content.
I have used Gulp here as I have more experience using Gulp with Bacbone than Grunt.
Uglify is turned off in the build so that compiled code can be seen.
### Things to update
- Get the user interaction working.
- Write tests, I didn't have time to even start any simple tests.
- Everything else from the original test that I have missed because I don't have the 'test text'.

28
app/app.js Normal file
View File

@ -0,0 +1,28 @@
const $ = require('jquery');
const _ = require('underscore');
const Backbone = require('backbone');
const io = require('socket.io-client');
const socket = io('http://localhost:3001');
const EventItem = require('./components/EventItem');
const EventCollection = require('./components/EventCollection');
const EventListModel = require('./components/EventListModel');
const EventItemView = require('./components/EventItemView');
const EventListView = require('./components/EventListView');
socket.on('selectionPriceUpdate', data => console.log('selectionPriceUpdate', data));
socket.on('SelectionStateUpdate', data => console.log('selectionStateUpdate', data));
socket.on('eventStateUpdate', data => console.log('eventStateUpdate', data));
(function() {
console.log('Go!');
const eventCollection = new EventCollection({ 'model':EventItem });
const eventList = new EventListModel({ 'eventCollection':eventCollection });
const newsListView = new EventListView({ 'model': eventList, 'el':'#eventList' });
socket.on('selectionPriceUpdate', data => eventList.updatedItem( data));
socket.on('SelectionStateUpdate', data => eventList.updatedState( data));
socket.on('eventStateUpdate', data => eventList.updatedState( data));
})();

View File

@ -0,0 +1,7 @@
const Backbone = require('backbone');
const EventCollection = Backbone.Collection.extend({
});
module.exports = EventCollection;

View File

@ -0,0 +1,7 @@
const Backbone = require('backbone');
const EventItem = Backbone.Model.extend({
});
module.exports = EventItem;

View File

@ -0,0 +1,32 @@
const _ = require('underscore');
const Backbone = require('backbone');
const templates = require('./templates');
const EventItemView = Backbone.View.extend({
'tagName': 'div',
'className': 'itemRow mui-row',
'template': _.template('<div>Not set</div>'),
'initialize': function() {
if (templates.hasOwnProperty(this.model.get('type')))
this.template = templates[this.model.get('type')];
this.listenTo(this.model, 'change', this.onChange);
this.render();
},
'onChange': function() {
this.render();
},
'render': function() {
this.$el.html(this.template(this.model.toJSON()));
},
'updateVisibility': function() {
let visibleClass = '';
if (active !== null)
visibleClass = (active === true) ? 'active' : 'notActive' ;
this.model.set('visibleClass', visibleClass) ;
}
});
module.exports = EventItemView;

View File

@ -0,0 +1,75 @@
const Backbone = require('backbone');
const EventListModel = Backbone.Model.extend({
'initialize': function(options) {
this.eventCollection = options.eventCollection;
this.listenTo(this, 'change', this.onChange);
this.listenTo(this.eventCollection, 'change', this.onCollectionChange);
this.getEvents();
},
'onChange': function() {
// console.log('EventListModel Changed');
},
'onCollectionChange': function() {
// console.log('eventCollection Changed');
},
'getEvents': async function () {
const data = await fetch('http://localhost:3001/api/selections/')
.then((response) => {
if (response.ok)
return response.json();
throw response;
})
.then((data) => {
return data.hasOwnProperty('category') ? data.category : [];
})
.catch((error) => {
console.error('Error fetching');
});
if (data && data.length > 0)
this.store(data);
},
'store' : function(data) {
const newItems = [];
data.forEach((topItem) => {
newItems.push({ ...this.getItem(topItem), 'type':'topItem' });
if (topItem.hasOwnProperty('subcat'))
topItem.subcat.forEach((subItem) => {
newItems.push({ ...this.getItem(subItem), 'type':'subItem' });
if (subItem.hasOwnProperty('event'))
subItem.event.forEach((eventItem) => {
newItems.push({ ...this.getItem(eventItem), 'type':'eventItem' });
if(eventItem.hasOwnProperty('selection'))
eventItem.selection.forEach((selItem) => {
newItems.push({ ...this.getItem(selItem), 'pid':eventItem.id, 'type':'selItem' });
});
});
});
});
this.eventCollection.reset(newItems);
},
'getItem': function({ id, name, active = null, price = null }) {
return { id, name, active, price };
},
'updatedItem': function(newItem) {
const foundData = this.eventCollection.findWhere({ 'id':newItem.id });
if (foundData)
foundData.set('price', newItem.newPrice);
},
'updatedState': function(newItem) {
const foundData = this.eventCollection.findWhere({ 'id':newItem.id });
if (foundData)
foundData.set({ 'active': newItem.active, 'visibleClass':(newItem.active === true) ? 'active' : 'notActive' });
}
});
module.exports = EventListModel;

View File

@ -0,0 +1,32 @@
const _ = require('underscore');
const Backbone = require('backbone');
const EventItemView = require('./EventItemView');
const EventListView = Backbone.View.extend({
'tagName': 'div',
'className' : 'newsItem',
'template': _.template('<div><%=name%></div>'),
'initialize': function() {
console.log('EventListView::Init');
this.listenTo(this.model.eventCollection, 'change', this.onChange);
this.model.eventCollection.bind('reset', this.render, this);
},
'render': function() {
this.$el.html(this.template(this.model.toJSON()));
this.model.eventCollection.each(item => {
const active = item.get('active');
let visibleClass = '';
if (active !== null)
visibleClass = (active === true) ? 'active' : 'notActive' ;
item.set('visibleClass', visibleClass) ;
const eView = new EventItemView({ 'model': item, 'templates': this.templates });
this.$el.append(eView.el);
});
}
});
module.exports = EventListView;

View File

@ -0,0 +1,11 @@
const _ = require('underscore');
const templates = {
'empty': _.template('<div></div>'),
'topItem': _.template('<div class="offset-1-col"><%=id%> <%=name%></div>'),
'subItem': _.template('<div class="offset-2-col"><%=id%> <%=name%></div>'),
'eventItem': _.template('<div class="offset-3-col <%=visibleClass%>"><%=id%> <%=name%></div>'),
'selItem': _.template('<div class="offset-4-col <%=visibleClass%>"><%=id%> <%=name%></div>')
};
module.exports = templates ;

473
app/css/index.css Normal file
View File

@ -0,0 +1,473 @@
@import url('https://fonts.googleapis.com/css?family=Roboto');
/* Global Styles */
:root {
--primary-color: #dc3545;
--dark-color: #333333;
--light-color: #f4f4f4;
--danger-color: #dc3545;
--success-color: #28a745;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: #fff;
color: #333;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
color: #666;
}
ul {
list-style: none;
}
img {
width: 100%;
}
/* Utilities */
.container {
max-width: 1100px;
margin: auto;
overflow: hidden;
padding: 0 2rem;
}
/* Text Styles*/
.x-large {
font-size: 4rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.large {
font-size: 3rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.lead {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-primary {
color: var(--primary-color);
}
.text-dark {
color: var(--dark-color);
}
.text-success {
color: var(--success-color);
}
.text-danger {
color: var(--danger-color);
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
/* Center All */
.all-center {
display: flex;
flex-direction: column;
width: 100%;
margin: auto;
justify-content: center;
align-items: center;
text-align: center;
}
/* Cards */
.card {
padding: 1rem;
border: #ccc 1px dotted;
margin: 0.7rem 0;
}
/* List */
.list {
margin: 0.5rem 0;
}
.list li {
padding-bottom: 0.3rem;
}
/* Padding */
.p {
padding: 0.5rem;
}
.p-1 {
padding: 1rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
.py {
padding: 0.5rem 0;
}
.py-1 {
padding: 1rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* Margin */
.m {
margin: 0.5rem;
}
.m-1 {
margin: 1rem;
}
.m-2 {
margin: 2rem;
}
.m-3 {
margin: 3rem;
}
.my {
margin: 0.5rem 0;
}
.my-1 {
margin: 1rem 0;
}
.my-2 {
margin: 2rem 0;
}
.my-3 {
margin: 3rem 0;
}
/* Grid */
.grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 1rem;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 1rem;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 1rem;
}
.btn {
display: inline-block;
background: var(--light-color);
color: #333;
padding: 0.4rem 1.3rem;
font-size: 1rem;
border: none;
cursor: pointer;
margin-right: 0.5rem;
transition: opacity 0.2s ease-in;
outline: none;
}
.btn-link {
background: none;
padding: 0;
margin: 0;
}
.btn-block {
display: block;
width: 100%;
}
.btn-sm {
font-size: 0.8rem;
padding: 0.3rem 1rem;
margin-right: 0.2rem;
}
.badge {
display: inline-block;
font-size: 0.8rem;
padding: 0.2rem 0.7rem;
text-align: center;
margin: 0.3rem;
background: var(--light-color);
color: #333;
border-radius: 5px;
}
.alert {
padding: 0.7rem;
margin: 1rem 0;
opacity: 0.9;
background: var(--light-color);
color: #333;
}
.btn-primary,
.bg-primary,
.badge-primary,
.alert-primary {
background: var(--primary-color);
color: #fff;
}
.btn-light,
.bg-light,
.badge-light,
.alert-light {
background: var(--light-color);
color: #333;
}
.btn-dark,
.bg-dark,
.badge-dark,
.alert-dark {
background: var(--dark-color);
color: #fff;
}
.btn-danger,
.bg-danger,
.badge-danger,
.alert-danger {
background: var(--danger-color);
color: #fff;
}
.btn-success,
.bg-success,
.badge-success,
.alert-success {
background: var(--success-color);
color: #fff;
}
.btn-white,
.bg-white,
.badge-white,
.alert-white {
background: #fff;
color: #333;
border: #ccc solid 1px;
}
.btn:hover {
opacity: 0.8;
}
.bg-light,
.badge-light {
border: #ccc solid 1px;
}
.round-img {
border-radius: 50%;
}
/* Forms */
input {
margin: 1.2rem 0;
}
.form-text {
display: block;
margin-top: 0.3rem;
color: #888;
}
input[type='text'],
input[type='email'],
input[type='password'],
input[type='date'],
select,
textarea {
display: block;
width: 100%;
padding: 0.4rem;
font-size: 1.2rem;
border: 1px solid #ccc;
}
input[type='submit'],
button {
font: inherit;
}
table th,
table td {
padding: 1rem;
text-align: left;
}
table th {
background: var(--light-color);
}
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 2rem;
z-index: 1;
width: 100%;
opacity: 0.9;
margin-bottom: 1rem;
}
.navbar ul {
display: flex;
}
.navbar a {
color: #fff;
padding: 0.45rem;
margin: 0 0.25rem;
}
.navbar a:hover {
color: var(--light-color);
}
.navbar .welcome span {
margin-right: 0.6rem;
}
/* Offsets */
.offset-1-col {
margin-left: 8.66666666667%;
}
.offset-2-col {
margin-left: 17.3333333333%;
}
.offset-3-col {
margin-left: 26%;
}
.offset-4-col {
margin-left: 34.6666666667%;
}
.offset-5-col {
margin-left: 43.3333333333%;
}
.offset-6-col {
margin-left: 52%;
}
.offset-7-col {
margin-left: 60.6666666667%;
}
.offset-8-col {
margin-left: 69.3333333333%;
}
.offset-9-col {
margin-left: 78.0%;
}
.offset-10-col {
margin-left: 86.6666666667%;
}
.offset-11-col {
margin-left: 95.3333333333%;
}
.active {
/*background-color: #28a745;*/
}
.notActive {
background-color: #f4f4f4;
color: #cccccc;
}
.notActive > span.text-danger {
background-color: #f4f4f4;
color: var(--light-dark);
}
/* Mobile Styles */
@media (max-width: 700px) {
.hide-sm {
display: none;
}
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr;
}
/* Text Styles */
.x-large {
font-size: 3rem;
}
.large {
font-size: 2rem;
}
.lead {
font-size: 1rem;
}
/* Navbar */
.navbar {
display: block;
text-align: center;
}
.navbar ul {
text-align: center;
justify-content: center;
}
}

166
dev/app.js Normal file
View File

@ -0,0 +1,166 @@
const $ = require('jquery');
const _ = require('underscore');
const Backbone = require('backbone');
const io = require('socket.io-client');
const socket = io('http://localhost:3001');
socket.on('selectionPriceUpdate', data => console.log('selectionPriceUpdate', data));
socket.on('SelectionStateUpdate', data => console.log('selectionStateUpdate', data));
socket.on('eventStateUpdate', data => console.log('eventStateUpdate', data));
(function() {
console.log('Go!');
const templates = {
'empty': _.template('<div></div>'),
'topItem': _.template('<div class="offset-1-col"><%=id%> <%=name%></div>'),
'subItem': _.template('<div class="offset-2-col"><%=id%> <%=name%></div>'),
'eventItem': _.template('<div class="offset-3-col <%=visibleClass%>"><%=id%> <%=name%></div>'),
'selItem': _.template('<div class="offset-4-col <%=visibleClass%>"><%=id%> <%=name%> <span class="text-danger">Price:<%=price%></span></div>')
};
console.log(templates);
const EventItem = Backbone.Model.extend({
});
const EventCollection = Backbone.Collection.extend({
'model': EventItem
});
const eventCollection = new EventCollection();
const EventListModel = Backbone.Model.extend({
'initialize': function() {
this.eventCollection = eventCollection;
this.listenTo(this, 'change', this.onChange);
this.listenTo(this.eventCollection, 'change', this.onCollectionChange);
this.getEvents();
},
'onChange': function() {
// console.log('EventListModel Changed');
},
'onCollectionChange': function() {
// console.log('eventCollection Changed');
},
'getEvents': async function () {
const data = await fetch('http://localhost:3001/api/selections/')
.then((response) => {
if (response.ok)
return response.json();
throw response;
})
.then((data) => {
return data.hasOwnProperty('category') ? data.category : [];
})
.catch((error) => {
console.error('Error fetching');
});
if (data && data.length > 0)
this.store(data);
},
'store' : function(data) {
const newItems = [];
data.forEach((topItem) => {
newItems.push({ ...this.getItem(topItem), 'type':'topItem' });
if (topItem.hasOwnProperty('subcat'))
topItem.subcat.forEach((subItem) => {
newItems.push({ ...this.getItem(subItem), 'type':'subItem' });
if (subItem.hasOwnProperty('event'))
subItem.event.forEach((eventItem) => {
newItems.push({ ...this.getItem(eventItem), 'type':'eventItem' });
if(eventItem.hasOwnProperty('selection'))
eventItem.selection.forEach((selItem) => {
newItems.push({ ...this.getItem(selItem), 'pid':eventItem.id, 'type':'selItem' });
});
});
});
});
this.eventCollection.reset(newItems);
},
'getItem': function({ id, name, active = null, price = null }) {
return { id, name, active, price };
},
'updatedItem': function(newItem) {
const foundData = this.eventCollection.findWhere({ 'id':newItem.id });
if (foundData)
foundData.set('price', newItem.newPrice);
},
'updatedState': function(newItem) {
const foundData = this.eventCollection.findWhere({ 'id':newItem.id });
if (foundData)
foundData.set({ 'active': newItem.active, 'visibleClass':(newItem.active === true) ? 'active' : 'notActive' });
}
});
const EventItemView = Backbone.View.extend({
'tagName': 'div',
'className': 'itemRow mui-row',
'template': _.template('<div>Not set</div>'),
'initialize': function() {
if (templates.hasOwnProperty(this.model.get('type')))
this.template = templates[this.model.get('type')];
this.listenTo(this.model, 'change', this.onChange);
this.render();
},
'onChange': function() {
this.render();
},
'render': function() {
this.$el.html(this.template(this.model.toJSON()));
},
'updateVisibility': function() {
let visibleClass = '';
if (active !== null)
visibleClass = (active === true) ? 'active' : 'notActive' ;
this.model.set('visibleClass', visibleClass) ;
}
});
const EventListView = Backbone.View.extend({
'tagName': 'div',
'className' : 'newsItem',
'template': _.template('<div><%=name%></div>'),
'initialize': function() {
console.log('EventListView::Init');
this.listenTo(this.model.eventCollection, 'change', this.onChange);
this.model.eventCollection.bind('reset', this.render, this);
},
'render': function() {
this.$el.html(this.template(this.model.toJSON()));
this.model.eventCollection.each(item => {
const active = item.get('active');
let visibleClass = '';
if (active !== null)
visibleClass = (active === true) ? 'active' : 'notActive' ;
item.set('visibleClass', visibleClass) ;
const eView = new EventItemView({ 'model': item });
this.$el.append(eView.el);
});
}
});
const eventList = new EventListModel;
const newsListView = new EventListView({ 'model': eventList, 'el':'#eventList' });
socket.on('selectionPriceUpdate', data => eventList.updatedItem( data));
socket.on('SelectionStateUpdate', data => eventList.updatedState( data));
socket.on('eventStateUpdate', data => eventList.updatedState( data));
})();

144
gulpfile.js Normal file
View File

@ -0,0 +1,144 @@
const autoprefixer = require('autoprefixer');
const browsersync = require('browser-sync').create();
const cp = require('child_process');
const cssnano = require('cssnano');
const del = require('del');
const eslint = require('gulp-eslint');
const gulp = require('gulp');
const imagemin = require('gulp-imagemin');
const newer = require('gulp-newer');
const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss');
const rename = require('gulp-rename');
const sass = require('gulp-sass');
const webpack = require('webpack');
const webpackconfig = require('./webpack.config.js');
const webpackstream = require('webpack-stream');
const uglify = require('gulp-uglify');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const sourcemaps = require('gulp-sourcemaps');
const gutil = require('gulp-util');
// Using: https://gist.github.com/jeromecoupe/0b807b0c1050647eb340360902c3203a
// BrowserSync
function browserSync(done) {
browsersync.init({
'server': {
'baseDir': './live/'
},
'port': 3000
});
done();
}
// BrowserSync Reload
function browserSyncReload(done) {
browsersync.reload();
done();
}
// Clean assets
function clean() {
return del(['./live/']);
}
// Lint scripts
function scriptsLint() {
return gulp
.src(['./app/**/*.js', './gulpfile.js'])
.pipe(plumber())
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
}
function bb() {
const b = browserify({
'debug': true,
'entries': './app/app.js'
});
return b.bundle().pipe(source('app.js'))
.pipe(buffer())
// .pipe(stripDebug())
.pipe(rename('bundle.js'))
.pipe(sourcemaps.init({ 'loadMaps': true }))
// Add transformation tasks to the pipeline here.
// .pipe(uglify())
.on('error', gutil.log)
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./live/js'));
}
function scripts() {
return (
gulp
.src(['./app/app.js'])
.pipe(plumber())
.pipe(webpackstream(webpackconfig), webpack)
// .pipe(uglify())
// folder only, filename is specified in webpack config
.pipe(gulp.dest('./live/js/'))
.pipe(browsersync.stream())
);
}
// CSS task
function css() {
return (
gulp
.src('./app/css/**/*.css')
.pipe(plumber())
.pipe(sass({ 'outputStyle': 'expanded' }))
.pipe(gulp.dest('./live/css/'))
.pipe(rename({ 'suffix': '.min' }))
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(gulp.dest('./live/css/'))
.pipe(browsersync.stream())
);
}
function copy() {
return(
gulp.src(['./public/**/*']).pipe(gulp.dest('./live/'))
);
}
// Watch files
function watchFiles() {
gulp.watch('./app/css/**/*', css);
gulp.watch('./app/**/*.js', gulp.series(scriptsLint, bb));
/* gulp.watch(
[
"./_includes/!**!/!*",
"./_layouts/!**!/!*",
"./_pages/!**!/!*",
"./_posts/!**!/!*",
"./_projects/!**!/!*"
],
gulp.series(jekyll, browserSyncReload)
);
gulp.watch("./assets/img/!**!/!*", images);*/
}
// define complex tasks
const js = gulp.series(scriptsLint, bb);
const build = gulp.series(clean, gulp.parallel(copy, css, js /* images, jekyll,*/ ));
const watch = gulp.parallel(watchFiles, browserSync);
// export tasks
// exports.images = images;
exports.copy = copy;
exports.css = css;
exports.js = js;
// exports.jekyll = jekyll;
exports.clean = clean;
exports.build = build;
exports.watch = watch;
exports.default = build;
exports.bb = bb;

480
live/css/index.css Normal file
View File

@ -0,0 +1,480 @@
@import url("https://fonts.googleapis.com/css?family=Roboto");
/* Global Styles */
:root {
--primary-color: #dc3545;
--dark-color: #333333;
--light-color: #f4f4f4;
--danger-color: #dc3545;
--success-color: #28a745;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: #fff;
color: #333;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
color: #666;
}
ul {
list-style: none;
}
img {
width: 100%;
}
/* Utilities */
.container {
max-width: 1100px;
margin: auto;
overflow: hidden;
padding: 0 2rem;
}
/* Text Styles*/
.x-large {
font-size: 4rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.large {
font-size: 3rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.lead {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-primary {
color: var(--primary-color);
}
.text-dark {
color: var(--dark-color);
}
.text-success {
color: var(--success-color);
}
.text-danger {
color: var(--danger-color);
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
/* Center All */
.all-center {
display: flex;
flex-direction: column;
width: 100%;
margin: auto;
justify-content: center;
align-items: center;
text-align: center;
}
/* Cards */
.card {
padding: 1rem;
border: #ccc 1px dotted;
margin: 0.7rem 0;
}
/* List */
.list {
margin: 0.5rem 0;
}
.list li {
padding-bottom: 0.3rem;
}
/* Padding */
.p {
padding: 0.5rem;
}
.p-1 {
padding: 1rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
.py {
padding: 0.5rem 0;
}
.py-1 {
padding: 1rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* Margin */
.m {
margin: 0.5rem;
}
.m-1 {
margin: 1rem;
}
.m-2 {
margin: 2rem;
}
.m-3 {
margin: 3rem;
}
.my {
margin: 0.5rem 0;
}
.my-1 {
margin: 1rem 0;
}
.my-2 {
margin: 2rem 0;
}
.my-3 {
margin: 3rem 0;
}
/* Grid */
.grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 1rem;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 1rem;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 1rem;
}
.btn {
display: inline-block;
background: var(--light-color);
color: #333;
padding: 0.4rem 1.3rem;
font-size: 1rem;
border: none;
cursor: pointer;
margin-right: 0.5rem;
transition: opacity 0.2s ease-in;
outline: none;
}
.btn-link {
background: none;
padding: 0;
margin: 0;
}
.btn-block {
display: block;
width: 100%;
}
.btn-sm {
font-size: 0.8rem;
padding: 0.3rem 1rem;
margin-right: 0.2rem;
}
.badge {
display: inline-block;
font-size: 0.8rem;
padding: 0.2rem 0.7rem;
text-align: center;
margin: 0.3rem;
background: var(--light-color);
color: #333;
border-radius: 5px;
}
.alert {
padding: 0.7rem;
margin: 1rem 0;
opacity: 0.9;
background: var(--light-color);
color: #333;
}
.btn-primary,
.bg-primary,
.badge-primary,
.alert-primary {
background: var(--primary-color);
color: #fff;
}
.btn-light,
.bg-light,
.badge-light,
.alert-light {
background: var(--light-color);
color: #333;
}
.btn-dark,
.bg-dark,
.badge-dark,
.alert-dark {
background: var(--dark-color);
color: #fff;
}
.btn-danger,
.bg-danger,
.badge-danger,
.alert-danger {
background: var(--danger-color);
color: #fff;
}
.btn-success,
.bg-success,
.badge-success,
.alert-success {
background: var(--success-color);
color: #fff;
}
.btn-white,
.bg-white,
.badge-white,
.alert-white {
background: #fff;
color: #333;
border: #ccc solid 1px;
}
.btn:hover {
opacity: 0.8;
}
.bg-light,
.badge-light {
border: #ccc solid 1px;
}
.round-img {
border-radius: 50%;
}
/* Forms */
input {
margin: 1.2rem 0;
}
.form-text {
display: block;
margin-top: 0.3rem;
color: #888;
}
input[type='text'],
input[type='email'],
input[type='password'],
input[type='date'],
select,
textarea {
display: block;
width: 100%;
padding: 0.4rem;
font-size: 1.2rem;
border: 1px solid #ccc;
}
input[type='submit'],
button {
font: inherit;
}
table th,
table td {
padding: 1rem;
text-align: left;
}
table th {
background: var(--light-color);
}
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 2rem;
z-index: 1;
width: 100%;
opacity: 0.9;
margin-bottom: 1rem;
}
.navbar ul {
display: flex;
}
.navbar a {
color: #fff;
padding: 0.45rem;
margin: 0 0.25rem;
}
.navbar a:hover {
color: var(--light-color);
}
.navbar .welcome span {
margin-right: 0.6rem;
}
/* Offsets */
.offset-1-col {
margin-left: 8.66666666667%;
}
.offset-2-col {
margin-left: 17.3333333333%;
}
.offset-3-col {
margin-left: 26%;
}
.offset-4-col {
margin-left: 34.6666666667%;
}
.offset-5-col {
margin-left: 43.3333333333%;
}
.offset-6-col {
margin-left: 52%;
}
.offset-7-col {
margin-left: 60.6666666667%;
}
.offset-8-col {
margin-left: 69.3333333333%;
}
.offset-9-col {
margin-left: 78.0%;
}
.offset-10-col {
margin-left: 86.6666666667%;
}
.offset-11-col {
margin-left: 95.3333333333%;
}
.active {
/*background-color: #28a745;*/
}
.notActive {
background-color: #f4f4f4;
color: #cccccc;
}
.notActive > span.text-danger {
background-color: #f4f4f4;
color: var(--light-dark);
}
/* Mobile Styles */
@media (max-width: 700px) {
.hide-sm {
display: none;
}
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr;
}
/* Text Styles */
.x-large {
font-size: 3rem;
}
.large {
font-size: 2rem;
}
.lead {
font-size: 1rem;
}
/* Navbar */
.navbar {
display: block;
text-align: center;
}
.navbar ul {
text-align: center;
justify-content: center;
}
}

1
live/css/index.min.css vendored Normal file
View File

@ -0,0 +1 @@
@import url("https://fonts.googleapis.com/css?family=Roboto");:root{--primary-color:#dc3545;--dark-color:#333;--light-color:#f4f4f4;--danger-color:#dc3545;--success-color:#28a745}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Roboto,sans-serif;font-size:1rem;line-height:1.6;background-color:#fff;color:#333}a{color:var(--primary-color);text-decoration:none}a:hover{color:#666}ul{list-style:none}img{width:100%}.container{max-width:1100px;margin:auto;overflow:hidden;padding:0 2rem}.x-large{font-size:4rem}.large,.x-large{line-height:1.2;margin-bottom:1rem}.large{font-size:3rem}.lead{font-size:1.5rem;margin-bottom:1rem}.text-primary{color:var(--primary-color)}.text-dark{color:var(--dark-color)}.text-success{color:var(--success-color)}.text-danger{color:var(--danger-color)}.text-center{text-align:center}.text-right{text-align:right}.text-left{text-align:left}.all-center{display:flex;flex-direction:column;width:100%;margin:auto;justify-content:center;align-items:center;text-align:center}.card{padding:1rem;border:1px dotted #ccc;margin:.7rem 0}.list{margin:.5rem 0}.list li{padding-bottom:.3rem}.p{padding:.5rem}.p-1{padding:1rem}.p-2{padding:2rem}.p-3{padding:3rem}.py{padding:.5rem 0}.py-1{padding:1rem 0}.py-2{padding:2rem 0}.py-3{padding:3rem 0}.m{margin:.5rem}.m-1{margin:1rem}.m-2{margin:2rem}.m-3{margin:3rem}.my{margin:.5rem 0}.my-1{margin:1rem 0}.my-2{margin:2rem 0}.my-3{margin:3rem 0}.grid-2{display:grid;grid-template-columns:repeat(2,1fr);grid-gap:1rem}.grid-3{display:grid;grid-template-columns:repeat(3,1fr);grid-gap:1rem}.grid-4{display:grid;grid-template-columns:repeat(4,1fr);grid-gap:1rem}.btn{display:inline-block;background:var(--light-color);color:#333;padding:.4rem 1.3rem;font-size:1rem;border:none;cursor:pointer;margin-right:.5rem;transition:opacity .2s ease-in;outline:none}.btn-link{background:none;padding:0;margin:0}.btn-block{display:block;width:100%}.btn-sm{font-size:.8rem;padding:.3rem 1rem;margin-right:.2rem}.badge{display:inline-block;font-size:.8rem;padding:.2rem .7rem;text-align:center;margin:.3rem;border-radius:5px}.alert,.badge{background:var(--light-color);color:#333}.alert{padding:.7rem;margin:1rem 0;opacity:.9}.alert-primary,.badge-primary,.bg-primary,.btn-primary{background:var(--primary-color);color:#fff}.alert-light,.badge-light,.bg-light,.btn-light{background:var(--light-color);color:#333}.alert-dark,.badge-dark,.bg-dark,.btn-dark{background:var(--dark-color);color:#fff}.alert-danger,.badge-danger,.bg-danger,.btn-danger{background:var(--danger-color);color:#fff}.alert-success,.badge-success,.bg-success,.btn-success{background:var(--success-color);color:#fff}.alert-white,.badge-white,.bg-white,.btn-white{background:#fff;color:#333;border:1px solid #ccc}.btn:hover{opacity:.8}.badge-light,.bg-light{border:1px solid #ccc}.round-img{border-radius:50%}input{margin:1.2rem 0}.form-text{display:block;margin-top:.3rem;color:#888}input[type=date],input[type=email],input[type=password],input[type=text],select,textarea{display:block;width:100%;padding:.4rem;font-size:1.2rem;border:1px solid #ccc}button,input[type=submit]{font:inherit}table td,table th{padding:1rem;text-align:left}table th{background:var(--light-color)}.navbar{justify-content:space-between;align-items:center;padding:.7rem 2rem;z-index:1;width:100%;opacity:.9;margin-bottom:1rem}.navbar,.navbar ul{display:flex}.navbar a{color:#fff;padding:.45rem;margin:0 .25rem}.navbar a:hover{color:var(--light-color)}.navbar .welcome span{margin-right:.6rem}.offset-1-col{margin-left:8.66666666667%}.offset-2-col{margin-left:17.3333333333%}.offset-3-col{margin-left:26%}.offset-4-col{margin-left:34.6666666667%}.offset-5-col{margin-left:43.3333333333%}.offset-6-col{margin-left:52%}.offset-7-col{margin-left:60.6666666667%}.offset-8-col{margin-left:69.3333333333%}.offset-9-col{margin-left:78%}.offset-10-col{margin-left:86.6666666667%}.offset-11-col{margin-left:95.3333333333%}.notActive{background-color:#f4f4f4;color:#ccc}.notActive>span.text-danger{background-color:#f4f4f4;color:var(--light-dark)}@media (max-width:700px){.hide-sm{display:none}.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}.x-large{font-size:3rem}.large{font-size:2rem}.lead{font-size:1rem}.navbar{display:block;text-align:center}.navbar ul{text-align:center;justify-content:center}}

24
live/index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FSB</title>
<link href="./css/index.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<nav class="navbar bg-primary">
<h1>
FSB
</h1>
</nav>
<div class="app">
<div id="eventList"></div>
</div>
<script src="./js/bundle.js"></script>
</body>
</html>

22185
live/js/bundle.js Normal file

File diff suppressed because it is too large Load Diff

1
live/js/bundle.js.map Normal file

File diff suppressed because one or more lines are too long

7
npm-shrinkwrap Normal file
View File

@ -0,0 +1,7 @@
{
"dependencies": {
"graceful-fs": {
"version": "4.2.2"
}
}
}

16577
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

53
package.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "fsb_backbone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "gulp default",
"start:server": "node ./server/server.js",
"preinstall": "npx npm-force-resolutions",
"postinstall": "npm run build"
},
"resolutions": {
"graceful-fs": "^4.2.4"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"backbone": "^1.4.0"
},
"devDependencies": {
"amd-define-factory-patcher-loader": "^1.0.0",
"autoprefixer": "^10.2.4",
"browser-sync": "^2.26.14",
"browserify": "^17.0.0",
"child_process": "^1.0.2",
"cssnano": "^4.1.10",
"del": "^6.0.0",
"eslint": "^7.20.0",
"express": "^4.17.1",
"gulp": "^4.0.2",
"gulp-eslint": "^6.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-newer": "^1.4.0",
"gulp-plumber": "^1.2.1",
"gulp-postcss": "^9.0.0",
"gulp-rename": "^2.0.0",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^3.0.0",
"gulp-uglify": "^3.0.2",
"gulp-util": "^3.0.8",
"jquery": "^3.5.1",
"postcss": "^8.2.6",
"prettier": "^2.2.1",
"socket.io": "^3.1.2",
"socket.io-client": "^3.1.2",
"underscore": "^1.12.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"webpack": "^5.24.2",
"webpack-stream": "^6.1.2"
}
}

24
public/index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FSB</title>
<link href="./css/index.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<nav class="navbar bg-primary">
<h1>
FSB
</h1>
</nav>
<div class="app">
<div id="eventList"></div>
</div>
<script src="./js/bundle.js"></script>
</body>
</html>

178
server/data.json Normal file
View File

@ -0,0 +1,178 @@
{
"category": [
{
"subcat": [
{
"event": [
{
"selection": [
{
"price": 1.5,
"id": 1111,
"name": "KB Stars",
"active": true
},
{
"price": 2.25,
"id": 1112,
"name": "Suwon Kepco Vixtorm",
"active": true
}
],
"id": 111,
"name": "KB Stars v Suwon Kepco Vixtorm",
"active" : true
}
],
"id": 11,
"name": "South Korea V League"
}
],
"id": 1,
"name": "Volleyball"
},
{
"subcat": [
{
"event": [
{
"selection": [
{
"price": 11.0,
"id": 2111,
"name": "Sakellaridis M / Sakellaridis S",
"active": true
},
{
"price": 1.01,
"id": 2112,
"name": "Hazawa S / Yamanaka T",
"active": false
}
],
"id": 211,
"active": true,
"name": "Sakellaridis M / Sakellaridis S v Hazawa S / Yamanaka T"
}
],
"id": 21,
"name": "ITF Turkey F1"
},
{
"event": [
{
"selection": [
{
"price": 15.0,
"id": 2211,
"name": "Hazawa M / Sakellaridis",
"active": true
},
{
"price": 1.61,
"id": 2212,
"name": "Hazawa S / Yamanaka T",
"active": false
}
],
"id": 221,
"active": true,
"name": "Sakellaridis M / Sakellaridis S v Hazawa S / Yamanaka T"
}
],
"id": 22,
"name": "ITF Turkey F2"
},
{
"event": [
{
"selection": [
{
"price": 4.1,
"id": 2311,
"name": "Yamanaka M / Sakellaridis S",
"active": true
},
{
"price": 3.01,
"id": 2312,
"name": "Sakellaridis S / Yamanaka T",
"active": false
}
],
"id": 231,
"active": true,
"name": "Yamanaka M / Sakellaridis S v Sakellaridis S / Yamanaka T"
}
],
"id": 23,
"name": "ITF Turkey F3"
}
],
"id": 2,
"name": "Tennis"
},
{
"subcat": [
{
"event": [
{
"selection": [
{
"price": 1.8,
"id": 3311,
"name": "Morocco",
"active": true
},
{
"price": 3.8,
"id": 3312,
"name": "Draw",
"active": true
},
{
"price": 3.8,
"id": 3313,
"name": "Tunisia",
"active": true
}
],
"id": 331,
"active": false,
"name": "Morocco v Tunisia"
},
{
"selection": [
{
"price": 1.8,
"id": 3321,
"name": "Portugal",
"active": true
},
{
"price": 3.8,
"id": 3322,
"name": "Draw",
"active": true
},
{
"price": 3.8,
"id": 3323,
"name": "Spain",
"active": true
}
],
"id": 332,
"active": true,
"name": "Portugal v Spain"
}
],
"id": 33,
"name": "World cup"
}
],
"id": 3,
"name": "Football"
}
]
}

View File

@ -0,0 +1,21 @@
module.exports = (data, socket) => {
const events = [];
data.category.forEach(category => {
category.subcat.forEach(subcat => {
subcat.event.forEach(event => {
events.push({
active: Math.random() >= 0.5,
id: event.id
});
});
});
});
events.forEach(event =>
setTimeout(() => { socket.emit('eventStateUpdate', event) }, 1000 * (Math.floor(Math.random() * 25) + 1))
);
};

View File

@ -0,0 +1,24 @@
module.exports = (data, socket) => {
const selections = [];
data.category.forEach(category => {
category.subcat.forEach(subcat => {
subcat.event.forEach(event => {
event.selection.forEach(selection => {
selections.push({
'newPrice': Math.floor(Math.random() * 9) + 1,
// id : selection.price
'id' : selection.id
});
});
});
});
});
selections.forEach(selection =>
setTimeout(() => {
socket.emit('selectionPriceUpdate', selection);
}, 1000 * (Math.floor(Math.random() * 15) + 1))
);
};

View File

@ -0,0 +1,25 @@
module.exports = (data, socket) => {
const selections = [];
data.category.forEach(category => {
category.subcat.forEach(subcat => {
subcat.event.forEach(event => {
event.selection.forEach(selection => {
const { id, price } = selection;
selections.push({
active: Math.random() >= 0.5,
price,
id
});
});
});
});
});
selections.forEach(selection =>
setTimeout(() => { socket.emit('selectionStateUpdate', selection) }, 1000 * (Math.floor(Math.random() * 25) + 1))
);
};

42
server/server.js Normal file
View File

@ -0,0 +1,42 @@
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const fs = require('fs');
const path = require('path');
const selectionPriceChange = require('./selectionPriceChange');
const selectionStateChange = require('./selectionStateChange');
const eventStateChange = require('./eventStateChange');
const sitePath = '../live';
const JSON_DATA = JSON.parse(fs.readFileSync('./server/data.json', 'utf-8'));
console.log(path.join(__dirname, sitePath));
app.use(express.static(path.join(__dirname, sitePath)));
app
.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,PUT');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
next();
})
.route('/api/selections').get((req, res) => res.status(200).json(JSON_DATA));
io.on('connection', socket => {
setInterval(() => {
selectionPriceChange(JSON_DATA, socket);
}, 20000);
setInterval(() => {
selectionStateChange(JSON_DATA, socket);
}, 30000);
setInterval(() => {
eventStateChange(JSON_DATA, socket);
}, 30000);
});
http.listen(3001, () => console.log('running'));

31
server/stateChange.js Normal file
View File

@ -0,0 +1,31 @@
const ramdonBoolean = () => Math.random() >= 0.5;
module.exports = (data, socket) => {
console.log(data)
};
module.exports = (data, socket) => {
const selections = [];
data.category.forEach(category => {
category.subcat.forEach(subcat => {
subcat.event.forEach(event => {
event.selection.forEach(selection => {
const { id, price } = selection;
selections.push({
newPrice: Math.floor(Math.random() * 9) + 1,
price,
id
});
});
});
});
});
selections.forEach(selection => socket.emit('price-update', selection));
};

15
webpack.config.js Normal file
View File

@ -0,0 +1,15 @@
const path = require('path');
const config = {
'entry': {
'app':['./app/app.js']
},
'output': {
'path': path.resolve(__dirname, 'live/js'),
'filename': 'bundle.js'
},
'mode': 'development'
};
module.exports = config;