mirror of
https://gitlab.com/wirelos/sprocket-ui.git
synced 2025-12-14 14:01:28 +01:00
copy old frontend
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
target/
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:latest
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/app
|
||||
WORKDIR /usr/app
|
||||
VOLUME /usr/app/config
|
||||
|
||||
# Install app
|
||||
COPY . /usr/app
|
||||
RUN yarn install
|
||||
RUN yarn build:frontend
|
||||
|
||||
EXPOSE 8080
|
||||
#CMD [ "yarn", "app" ]
|
||||
@@ -1,3 +0,0 @@
|
||||
This project is created from a GitLab [Project Template](https://docs.gitlab.com/ce/gitlab-basics/create-project.html)
|
||||
|
||||
Additions and changes to the project can be proposed [on the original project](https://gitlab.com/gitlab-org/project-templates/express)
|
||||
46
app.js
46
app.js
@@ -1,46 +0,0 @@
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var favicon = require('serve-favicon');
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
var index = require('./routes/index');
|
||||
var users = require('./routes/users');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
// uncomment after placing your favicon in /public
|
||||
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
|
||||
app.use(logger('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', index);
|
||||
app.use('/users', users);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
90
bin/www
90
bin/www
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('demo:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
7384
package-lock.json
generated
Normal file
7384
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
@@ -1,17 +1,55 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"name": "websprocket",
|
||||
"version": "1.0.0",
|
||||
"description": "template project for websprockets",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
"clear": "rimraf target",
|
||||
"setup": "yarn clear && mkdirp target",
|
||||
"test": "echo \"no test specified\" && exit 0",
|
||||
"dev": "yarn setup && yarn build:frontend && npm-run-all --parallel watch:*",
|
||||
"build:js": "webpack",
|
||||
"build:css": "lessc src/styles/main.less target/styles.css",
|
||||
"build:html": "cp -r src/pages/* target/",
|
||||
"build:frontend": "yarn setup && yarn build:css && yarn build:html && yarn build:js",
|
||||
"watch:html": "nodemon -q -w src/pages/ --ext \".\" --exec \"npm run build:html\"",
|
||||
"watch:css": "nodemon -q -w src/styles --ext \".\" --exec \"npm run build:css\"",
|
||||
"watch:js": "nodemon -q -w src/app --ext \".\" --exec \"npm run build:js\"",
|
||||
"watch:livereload": "cd target && live-server --port=3000",
|
||||
"docker:build": "docker build -t wirelos/sprocket-ui .",
|
||||
"docker:run": "docker run -p 8080:8080 -d registry.gitlab.com/wirelos/sprocket-ui",
|
||||
"docker:release": "docker tag wirelos/sprocket-ui registry.gitlab.com/wirelos/sprocket-ui && docker push registry.gitlab.com/wirelos/sprocket-ui"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/0x1d/websprocket.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "SEE LICENSE FILE",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0x1d/websprocket/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0x1d/websprocket#readme",
|
||||
"devDependencies": {
|
||||
"babel": "latest",
|
||||
"babel-core": "latest",
|
||||
"babel-loader": "latest",
|
||||
"babel-plugin-transform-runtime": "latest",
|
||||
"babel-preset-env": "latest",
|
||||
"babel-preset-minify": "^0.3.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"less": "latest",
|
||||
"live-server": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nodemon": "latest",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"webpack": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "~1.17.1",
|
||||
"cookie-parser": "~1.4.3",
|
||||
"debug": "~2.6.3",
|
||||
"express": "~4.15.2",
|
||||
"jade": "~1.11.0",
|
||||
"morgan": "~1.8.1",
|
||||
"serve-favicon": "~2.4.2"
|
||||
"jquery": "^3.3.1",
|
||||
"mustache": "^2.3.0",
|
||||
"ws": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,9 +0,0 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
8
src/app/components/Clock.js
Normal file
8
src/app/components/Clock.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import $ from 'jquery';
|
||||
import Component from '../core/Component';
|
||||
|
||||
export default class Clock extends Component {
|
||||
constructor(ctx, node, template) {
|
||||
//super(ctx, node, template);
|
||||
}
|
||||
}
|
||||
40
src/app/components/LedStripPatternSwitch.js
Normal file
40
src/app/components/LedStripPatternSwitch.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import $ from 'jquery';
|
||||
import Switch from './base/Switch/Switch';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class LedStripPatternSwitch extends Switch {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
this.store = new Store(this.config.endpoint);
|
||||
this.modes = ["init", "Color", "Pattern", "octoPrint"];
|
||||
this.patterns = [ "none", "Rainbow", "Scanner", "ColorWipe", "TheaterChase"];
|
||||
}
|
||||
|
||||
onClick(evt) {
|
||||
// FIXME separate mode from pattern
|
||||
let payload = {
|
||||
id: this.patterns.indexOf(this.config.name),
|
||||
pattern: this.config.name.substring(0,1),
|
||||
group: this.config.group,
|
||||
mode: 'P',
|
||||
state: this.state
|
||||
};
|
||||
this.ctx.mediator.trigger('/ledStripPreset/switched', payload);
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.ctx.mediator.on('/ledStripPreset/switched', (payload) => {
|
||||
this.store.save(payload);
|
||||
if (payload.state
|
||||
&& payload.id !== this.patterns.indexOf(this.config.name)
|
||||
&& payload.group === this.config.group) {
|
||||
if (this.state) {
|
||||
this.state = false;
|
||||
this.node.find('input').click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
27
src/app/components/ParamColor.js
Normal file
27
src/app/components/ParamColor.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import $ from 'jquery';
|
||||
import ColorPicker from './base/ColorPicker/ColorPicker';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class ParamColor extends ColorPicker {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
//this.store = new Store(this.config.endpoint);
|
||||
this.ws = new WebSocket(this.config.endpoint.indexOf('/') > -1 ? "ws://" + window.location.host + this.config.endpoint : this.config.endpoint );
|
||||
this.ws.onopen = (event) => {
|
||||
console.log('open ' + this.config.endpoint);
|
||||
};
|
||||
this.ws.onmessage = (event) => {
|
||||
console.log(event.data);
|
||||
};
|
||||
}
|
||||
|
||||
onChange(evt) {
|
||||
/*this.store.save({
|
||||
rgb: parseInt(this.value.replace('#', '0x'))
|
||||
});*/
|
||||
let cmd = this.config.id + parseInt(this.value.replace('#', '0x'));
|
||||
this.ws.send(cmd);
|
||||
}
|
||||
|
||||
}
|
||||
19
src/app/components/ParamSlider.js
Normal file
19
src/app/components/ParamSlider.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import $ from 'jquery';
|
||||
import Slider from './base/Slider/Slider';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class ParamSlider extends Slider {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
this.store = new Store(this.config.endpoint);
|
||||
}
|
||||
|
||||
onChange(evt) {
|
||||
this.store.save({
|
||||
param: this.config.name,
|
||||
value: this.value
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
19
src/app/components/ParamText.js
Normal file
19
src/app/components/ParamText.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import $ from 'jquery';
|
||||
import TextInput from './base/TextInput/TextInput';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class ParamText extends TextInput {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
this.store = new Store(this.config.endpoint);
|
||||
}
|
||||
|
||||
onInput(evt) {
|
||||
let obj = {};
|
||||
obj[this.config.name] = this.value;
|
||||
//this.store.save(obj);
|
||||
console.log(this.value);
|
||||
}
|
||||
|
||||
}
|
||||
37
src/app/components/ParamWs.js
Normal file
37
src/app/components/ParamWs.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import $ from 'jquery';
|
||||
import TextInput from './base/TextInput/TextInput';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class ParamWs extends TextInput {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
//this.store = new Store(this.config.endpoint);
|
||||
|
||||
this.ws = new WebSocket(this.config.endpoint.indexOf('/') > -1 ? "ws://" + window.location.host + this.config.endpoint : this.config.endpoint );
|
||||
this.ws.onopen = (event) => {
|
||||
console.log('open ' + this.config.endpoint);
|
||||
};
|
||||
this.ws.onmessage = (event) => {
|
||||
//console.log(event.data);
|
||||
this.node.find('input').val(event.data);
|
||||
};
|
||||
|
||||
this.ctx.mediator.on(this.config.endpoint, this.onMessage.bind(this));
|
||||
}
|
||||
|
||||
onMessage(msg) {
|
||||
//console.log('onMsg: ' + msg);
|
||||
//this.node.val(msg);
|
||||
}
|
||||
|
||||
onInput(evt) {
|
||||
let obj = {};
|
||||
obj[this.config.name] = this.value;
|
||||
//this.store.save(obj);
|
||||
console.log(this.value);
|
||||
this.ws.send(this.value);
|
||||
this.ctx.mediator.trigger(this.config.endpoint, this.value);
|
||||
}
|
||||
|
||||
}
|
||||
35
src/app/components/PresetSwitch.js
Normal file
35
src/app/components/PresetSwitch.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import $ from 'jquery';
|
||||
import Switch from './base/Switch/Switch';
|
||||
import Store from '../core/store/RestStore';
|
||||
|
||||
export default class PresetSwitch extends Switch {
|
||||
|
||||
constructor(ctx, node) {
|
||||
super(ctx, node);
|
||||
this.store = new Store(this.config.endpoint);
|
||||
}
|
||||
|
||||
onClick(evt) {
|
||||
let payload = {
|
||||
preset: this.config.name,
|
||||
group: this.config.group,
|
||||
state: this.state
|
||||
};
|
||||
this.store.save(payload);
|
||||
this.ctx.mediator.trigger('/preset/switched', payload);
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.ctx.mediator.on('/preset/switched', (payload) => {
|
||||
if (payload.state
|
||||
&& payload.preset !== this.config.name
|
||||
&& payload.group === this.config.group) {
|
||||
if (this.state) {
|
||||
this.state = false;
|
||||
this.node.find('input').click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
2
src/app/components/base/ColorPicker/ColorPicker.html
Normal file
2
src/app/components/base/ColorPicker/ColorPicker.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<label for="{{name}}">{{label}}</label>
|
||||
<input type="color" class="{{name}} ColorPicker" name="{{name}}">
|
||||
34
src/app/components/base/ColorPicker/ColorPicker.js
Normal file
34
src/app/components/base/ColorPicker/ColorPicker.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import $ from 'jquery';
|
||||
import Component from '../../../core/Component';
|
||||
import markup from './ColorPicker.html';
|
||||
|
||||
export default class ColorPicker extends Component {
|
||||
|
||||
constructor(ctx, node, template) {
|
||||
super(ctx, node, template || markup);
|
||||
this.render(this.config);
|
||||
}
|
||||
|
||||
onChange(evt) {}
|
||||
|
||||
inputChange(evt) {
|
||||
this.value = evt.target.value;
|
||||
this.onChange(evt);
|
||||
}
|
||||
|
||||
|
||||
subscribe() {
|
||||
// change to event input when ws is implemented
|
||||
this.node.delegate('input', 'input',
|
||||
this.inputChange.bind(this));
|
||||
}
|
||||
|
||||
/* delegate() {
|
||||
return [{
|
||||
selector: 'input',
|
||||
event: 'input',
|
||||
handler: this.inputChange.bind(this)
|
||||
}];
|
||||
} */
|
||||
|
||||
}
|
||||
2
src/app/components/base/Slider/Slider.html
Normal file
2
src/app/components/base/Slider/Slider.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<label>{{label}}</label>
|
||||
<input type="range" name="{{name}}" value="{{value}}" min="{{min}}" max="{{max}}">
|
||||
25
src/app/components/base/Slider/Slider.js
Normal file
25
src/app/components/base/Slider/Slider.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import $ from 'jquery';
|
||||
import Component from '../../../core/Component';
|
||||
import markup from './Slider.html';
|
||||
|
||||
export default class Slider extends Component {
|
||||
|
||||
constructor(ctx, node, template) {
|
||||
super(ctx, node, template || markup);
|
||||
this.render(this.config);
|
||||
}
|
||||
|
||||
onChange(evt) {}
|
||||
|
||||
sliderChange(evt) {
|
||||
this.value = evt.target.value;
|
||||
this.onChange(evt);
|
||||
}
|
||||
|
||||
|
||||
subscribe() {
|
||||
this.node.delegate('input', 'input',
|
||||
this.sliderChange.bind(this));
|
||||
}
|
||||
|
||||
}
|
||||
5
src/app/components/base/Switch/Switch.html
Normal file
5
src/app/components/base/Switch/Switch.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<label for="{{name}}">{{label}}</label>
|
||||
<label class="switch {{name}}" name="{{name}}">
|
||||
<input type="checkbox" name="{{name}}">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
30
src/app/components/base/Switch/Switch.js
Normal file
30
src/app/components/base/Switch/Switch.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import $ from 'jquery';
|
||||
import Component from '../../../core/Component';
|
||||
import markup from './Switch.html';
|
||||
|
||||
export default class Switch extends Component {
|
||||
|
||||
constructor(ctx, node, template) {
|
||||
super(ctx, node, template || markup);
|
||||
this.state = false;
|
||||
this.render(this.config);
|
||||
this._subscribe();
|
||||
}
|
||||
|
||||
onClick(evt) {}
|
||||
|
||||
switchState(evt) {
|
||||
this.state = !this.state;
|
||||
}
|
||||
|
||||
clickSwitch(evt) {
|
||||
this.switchState(evt);
|
||||
this.onClick(evt);
|
||||
}
|
||||
|
||||
_subscribe() {
|
||||
this.node.delegate('.slider', 'click',
|
||||
this.clickSwitch.bind(this));
|
||||
}
|
||||
|
||||
}
|
||||
2
src/app/components/base/TextInput/TextInput.html
Normal file
2
src/app/components/base/TextInput/TextInput.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<label for="{{name}}">{{label}}</label>
|
||||
<input type="text" class="{{name}}" name="{{name}}" placeholder="{{placeholder}}">
|
||||
25
src/app/components/base/TextInput/TextInput.js
Normal file
25
src/app/components/base/TextInput/TextInput.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import $ from 'jquery';
|
||||
import Component from '../../../core/Component';
|
||||
import markup from './TextInput.html';
|
||||
|
||||
export default class TextInput extends Component {
|
||||
|
||||
constructor(ctx, node, template) {
|
||||
super(ctx, node, template || markup);
|
||||
this.render(this.config);
|
||||
}
|
||||
|
||||
onChange(evt) {}
|
||||
|
||||
inputChange(evt) {
|
||||
this.value = evt.target.value;
|
||||
this.onInput(evt);
|
||||
}
|
||||
|
||||
|
||||
subscribe() {
|
||||
this.node.delegate('input', 'input',
|
||||
this.inputChange.bind(this));
|
||||
}
|
||||
|
||||
}
|
||||
10
src/app/components/exports.js
Normal file
10
src/app/components/exports.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export { default as Switch } from './base/Switch/Switch'
|
||||
export { default as Slider } from './base/Slider/Slider'
|
||||
export { default as TextInput } from './base/TextInput/TextInput'
|
||||
export { default as ColorPicker } from './base/ColorPicker/ColorPicker'
|
||||
export { default as PresetSwitch } from './PresetSwitch'
|
||||
export { default as ParamSlider } from './ParamSlider'
|
||||
export { default as ParamText } from './ParamText'
|
||||
export { default as ParamColor } from './ParamColor'
|
||||
export { default as ParamWs } from './ParamWs'
|
||||
export { default as LedStripPatternSwitch } from './LedStripPatternSwitch'
|
||||
82
src/app/core/App.js
Normal file
82
src/app/core/App.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import $ from 'jquery';
|
||||
import Mediator from './Mediator';
|
||||
|
||||
export default class App {
|
||||
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.dataStores = [];
|
||||
this.templates = [];
|
||||
this.mediator = new Mediator();
|
||||
this.ws = {};
|
||||
}
|
||||
|
||||
withDataStore(dataStore) {
|
||||
this.dataStores[dataStore.constructor.name] = dataStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
components(components) {
|
||||
this.components = [];
|
||||
for (let c in components) {
|
||||
let nodes = this.ctx.find('.' + c);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let component = new components[c](this, $(nodes[i]));
|
||||
this.components.push(component);
|
||||
};
|
||||
}
|
||||
this._beforeInitComponents();
|
||||
this._initComponents();
|
||||
return this;
|
||||
}
|
||||
|
||||
websocket(websocket) {
|
||||
this.ws = websocket;
|
||||
}
|
||||
|
||||
getStore(dataStore) {
|
||||
return this.dataStores[dataStore];
|
||||
}
|
||||
|
||||
run(ctx) {
|
||||
//this._loadComponents(ctx);
|
||||
this._beforeInitComponents();
|
||||
this._initComponents();
|
||||
return this;
|
||||
}
|
||||
|
||||
_loadComponents(ctx) {
|
||||
this.components = this.components || [];
|
||||
for (let c in Components) {
|
||||
let nodes = ctx ? ctx.find('.' + c) : [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let component = new Components[c](this, $(nodes[i]));
|
||||
this.components.push(component);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_initComponents() {
|
||||
this.components.forEach(this._initComponent.bind(this));
|
||||
}
|
||||
|
||||
_beforeInitComponents() {
|
||||
this.components.forEach(this._beforeInitComponent.bind(this));
|
||||
}
|
||||
|
||||
_initComponent(component) {
|
||||
component.init();
|
||||
}
|
||||
|
||||
_beforeInitComponent(component) {
|
||||
component.beforeInit();
|
||||
}
|
||||
|
||||
render() {
|
||||
for (let component in this.components) {
|
||||
this.components[component].render();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
40
src/app/core/Component.js
Normal file
40
src/app/core/Component.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import Mustache from 'mustache';
|
||||
import $ from 'jquery';
|
||||
import DataField from './data/DataField';
|
||||
|
||||
|
||||
export default class Component {
|
||||
|
||||
constructor(ctx, node, template) {
|
||||
this.ctx = ctx;
|
||||
this.node = node;
|
||||
this.component = this.constructor.name;
|
||||
this.config = this.node.data();
|
||||
this.data = {};
|
||||
this.markup = template;
|
||||
}
|
||||
|
||||
beforeInit() {
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
init() { }
|
||||
subscribe() { }
|
||||
templateHelpers() { return {}; }
|
||||
|
||||
bindData() {
|
||||
let _this = this;
|
||||
this.node.find('[data-bind]').each(function () {
|
||||
var field = $(this);
|
||||
_this.data[field.data('bind')] = new DataField(field);
|
||||
});
|
||||
}
|
||||
|
||||
render(data) {
|
||||
if (data) data.helpers = this.templateHelpers();
|
||||
let rendered = Mustache.render(this.markup, data);
|
||||
this.node.html(rendered);
|
||||
this.bindData();
|
||||
}
|
||||
|
||||
}
|
||||
20
src/app/core/Mediator.js
Normal file
20
src/app/core/Mediator.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default class Mediator {
|
||||
|
||||
constructor() {
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
on(event, callback, context){
|
||||
this.events[event] = this.events[event] || [];
|
||||
this.events[event].push(context ? callback.bind(context) : callback);
|
||||
};
|
||||
|
||||
trigger(event, args){
|
||||
if(this.events[event]){
|
||||
for (var i = this.events[event].length - 1; i >= 0; i--) {
|
||||
this.events[event][i](args || {});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
30
src/app/core/WsMediator.js
Normal file
30
src/app/core/WsMediator.js
Normal file
@@ -0,0 +1,30 @@
|
||||
export default class WsMediator {
|
||||
|
||||
// FIXME mediator should manage all endpoints?
|
||||
constructor(endpoint) {
|
||||
this.events = [];
|
||||
this.endpoint = endpoint;
|
||||
this.ws = new WebSocket("ws://" + this.endpoint);
|
||||
this.ws.onopen = (event) => {
|
||||
console.log('init WsMediator');
|
||||
};
|
||||
this.ws.onmessage = (event) => {
|
||||
console.log(event.data);
|
||||
this.trigger(this.endpoint, event.data);
|
||||
};
|
||||
}
|
||||
|
||||
on(event, callback, context){
|
||||
this.events[event] = this.events[event] || [];
|
||||
this.events[event].push(context ? callback.bind(context) : callback);
|
||||
};
|
||||
|
||||
trigger(event, args){
|
||||
if(this.events[event]){
|
||||
for (var i = this.events[event].length - 1; i >= 0; i--) {
|
||||
this.ws.send(this.events[event][i](args || {}));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
28
src/app/core/data/DataBinding.js
Normal file
28
src/app/core/data/DataBinding.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
export default class DataBinding {
|
||||
|
||||
inputChange(node, model = {}) {
|
||||
node.on('keyup', function() {
|
||||
model.value = this.value;
|
||||
});
|
||||
return model;
|
||||
}
|
||||
|
||||
inputHandler() {
|
||||
return {
|
||||
set: function(target, prop, newValue) {
|
||||
if (prop == 'value' && target.id) {
|
||||
target[prop] = newValue;
|
||||
$('[data-bind="' + target.id + '"]').val(newValue);
|
||||
return true;
|
||||
} else return false;
|
||||
|
||||
},
|
||||
get: function(target, name) {
|
||||
return target[name];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
23
src/app/core/data/DataField.js
Normal file
23
src/app/core/data/DataField.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import DataBinding from './DataBinding';
|
||||
|
||||
export default class DataField {
|
||||
|
||||
constructor(node, data) {
|
||||
this.node = node;
|
||||
this.data = {
|
||||
id: this.node.data('bind')
|
||||
};
|
||||
this.bind();
|
||||
}
|
||||
bind() {
|
||||
this.dataBinding = new DataBinding();
|
||||
this.dataBinding.inputChange(this.node, this.data);
|
||||
this.proxy = new Proxy(this.data, this.dataBinding.inputHandler());
|
||||
}
|
||||
get value() {
|
||||
return this.proxy.value;
|
||||
}
|
||||
set value(newValue) {
|
||||
this.proxy.value = newValue;
|
||||
}
|
||||
}
|
||||
9
src/app/core/store/DataStore.js
Normal file
9
src/app/core/store/DataStore.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default class DataStore {
|
||||
constructor(mediator){
|
||||
this.mediator = mediator;
|
||||
}
|
||||
load(entry){}
|
||||
save(entry){}
|
||||
add(entry){}
|
||||
delete(entry){}
|
||||
}
|
||||
37
src/app/core/store/RestStore.js
Normal file
37
src/app/core/store/RestStore.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import $ from 'jquery';
|
||||
import Mediator from '../Mediator';
|
||||
import DataStore from './DataStore';
|
||||
import StoreAction from './StoreAction';
|
||||
|
||||
export default class RestStore extends DataStore {
|
||||
constructor(endpoint, mediator = new Mediator()){
|
||||
super(mediator);
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
load(entry){
|
||||
return this.request(StoreAction.LOAD, 'GET', entry);
|
||||
}
|
||||
save(entry){
|
||||
return this.request(StoreAction.SAVE, 'POST', entry);
|
||||
}
|
||||
add(entry){
|
||||
return this.request(StoreAction.ADD, 'PUT', entry);
|
||||
}
|
||||
delete(entry){
|
||||
return this.request(StoreAction.DELETE, 'POST', entry);
|
||||
}
|
||||
request(event, type, payload){
|
||||
return $.ajax({
|
||||
url: this.endpoint,
|
||||
type: type,
|
||||
data: payload
|
||||
})
|
||||
.then(JSON.parse)
|
||||
.then((response) => {
|
||||
this.mediator.trigger(event, response);
|
||||
});
|
||||
}
|
||||
on(event, subscriber, context){
|
||||
this.mediator.on(event, subscriber, context);
|
||||
}
|
||||
}
|
||||
6
src/app/core/store/StoreAction.js
Normal file
6
src/app/core/store/StoreAction.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
LOAD: 'load',
|
||||
SAVE: 'save',
|
||||
ADD: 'add',
|
||||
DELETE: 'delete'
|
||||
}
|
||||
10
src/app/jscolor.js
Normal file
10
src/app/jscolor.js
Normal file
File diff suppressed because one or more lines are too long
99
src/app/main.js
Normal file
99
src/app/main.js
Normal file
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
|
||||
import $ from 'jquery';
|
||||
import App from './core/App';
|
||||
import * as components from './components/exports';
|
||||
|
||||
$(() => {
|
||||
new App($('body'))
|
||||
.components(components);
|
||||
/* .websocket({
|
||||
led: new WebSocket('ws://ledstrip/')
|
||||
}); */
|
||||
});
|
||||
|
||||
|
||||
/* let click = element => element.click();
|
||||
let check = element => element.setAttribute('checked', 'checked');
|
||||
let uncheck = element => element.removeAttribute('checked');
|
||||
|
||||
let endpoint = "http://192.168.1.134";
|
||||
|
||||
let switchElementState = element => {
|
||||
let state = element.getAttribute('data-state') == 'false';
|
||||
element.setAttribute('data-state', state);
|
||||
return state;
|
||||
};
|
||||
let bindData = (element, attribute, data) => {
|
||||
element.setAttribute('data-' + attribute, data);
|
||||
};
|
||||
let leadingZero = (time) => {
|
||||
return (time < 10 ? "0" : "") + time;
|
||||
};
|
||||
let setMode = (mode) => {
|
||||
var formData = new FormData();
|
||||
formData.append("preset", mode);
|
||||
Sui.http.ajax({
|
||||
endpoint: endpoint + '/matrix/mode',
|
||||
method: 'POST',
|
||||
data: formData
|
||||
});
|
||||
};
|
||||
|
||||
let setScrollText = (txt) => {
|
||||
var formData = new FormData();
|
||||
formData.append("scrollText", txt);
|
||||
Sui.http.ajax({
|
||||
endpoint: endpoint + '/matrix/text',
|
||||
method: 'POST',
|
||||
data: formData
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Sui.ready(() => {
|
||||
let debugResponse = (data) => {
|
||||
document.querySelector('#response').innerHTML = data;
|
||||
};
|
||||
|
||||
Sui.http.ajax({
|
||||
endpoint: '/wifiConfig',
|
||||
method: 'GET'
|
||||
}, (data) => {
|
||||
Sui.select('[name="ssid"]').forEach((el) => {
|
||||
el.value = data;
|
||||
});
|
||||
});
|
||||
|
||||
// init collapsible containers
|
||||
Sui.select('.collapsible').forEach((container) => {
|
||||
container.querySelector('.heading').addEventListener('click', (item) => {
|
||||
container.classList.toggle('open');
|
||||
});
|
||||
});
|
||||
|
||||
let switchState = (evt) => { //evt.preventDefault();
|
||||
let state = !(evt.target.getAttribute('data-state') == 'true');
|
||||
evt.target.setAttribute('data-state', state);
|
||||
setMode(el.getAttribute('name'));
|
||||
};
|
||||
|
||||
let elClick = (el) => {
|
||||
el.addEventListener('click', switchState);
|
||||
return el;
|
||||
};
|
||||
Sui.select('.switch.demo').forEach(elClick);
|
||||
|
||||
setInterval(() => {
|
||||
let time = new Date();
|
||||
let txt = leadingZero(time.getHours()) + ':' + leadingZero(time.getMinutes());
|
||||
var formData = new FormData();
|
||||
formData.append("staticText", txt);
|
||||
Sui.http.ajax({
|
||||
endpoint: endpoint + '/matrix/text',
|
||||
method: 'POST',
|
||||
data: formData
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
}); */
|
||||
88
src/app/pmjq.js
Normal file
88
src/app/pmjq.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Poor Man's JQuery
|
||||
* Lightweight DOM manipulation utility
|
||||
*/
|
||||
var $ = function(selector){
|
||||
|
||||
var that = this;
|
||||
var element = selector.nodeType === 1 ? selector : {};
|
||||
|
||||
// utility functions
|
||||
this.util = {
|
||||
// build a fragment from a given html string
|
||||
buildFragment: function(html){
|
||||
// set the html to a temporary element
|
||||
var nodeHolder = document.createElement('div');
|
||||
nodeHolder.innerHTML = html;
|
||||
// create a document fragment and append all input nodes
|
||||
var fragment = document.createDocumentFragment();
|
||||
while(nodeHolder.firstChild){
|
||||
fragment.appendChild(nodeHolder.firstChild);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
};
|
||||
// check if the input selector is already an element or a css selector
|
||||
if(selector.nodeType === 1){ // is element
|
||||
if(selector.is$ ? selector.is$() : false){ // check if the element is already extended
|
||||
return selector;
|
||||
}
|
||||
element = selector; // set the element to be extended
|
||||
} else { // the element is in fact a css selector
|
||||
element = document.querySelector(selector); // search for the element
|
||||
}
|
||||
|
||||
// overload the innerHTML attribute
|
||||
element.html = function(val){
|
||||
this.innerHTML = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
// overload the value attribute
|
||||
element.val = function(val){
|
||||
this.value = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
// append the given string as child fragment
|
||||
element.append = function(html){
|
||||
var fragment = that.util.buildFragment(html);
|
||||
this.appendChild(fragment);
|
||||
return this;
|
||||
};
|
||||
|
||||
// prepend the given string as child fragment
|
||||
element.prepend = function(html){
|
||||
var fragment = that.util.buildFragment(html);
|
||||
this.insertBefore(fragment,this.firstChild);
|
||||
return this;
|
||||
};
|
||||
|
||||
// search for an element inside of an element
|
||||
element.find = function(what){
|
||||
var found = $(what);
|
||||
if(found){
|
||||
return $(found);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get parent of the current element
|
||||
element.parent = function(){
|
||||
if(this.parentElement){
|
||||
return $(this.parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
// indicates that this element is a $ function
|
||||
element.is$ = function(){
|
||||
return true;
|
||||
}
|
||||
|
||||
element.on = function(event, func, useCapture) {
|
||||
this.addEventListener(event, func, useCapture);
|
||||
return this;
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
73
src/app/sui.js
Normal file
73
src/app/sui.js
Normal file
@@ -0,0 +1,73 @@
|
||||
var Sui = {
|
||||
ready: (callback) => {
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
callback();
|
||||
}, false);
|
||||
},
|
||||
select: (selector) => {
|
||||
return document.querySelectorAll(selector);
|
||||
},
|
||||
link: (node) => {
|
||||
return (actuator) => {
|
||||
let update = actuator.handler || function(actuator) {
|
||||
Sui.http.ajax({
|
||||
method: actuator.method,
|
||||
endpoint: actuator.api,
|
||||
data: actuator.data ?
|
||||
actuator.data.call(this) : [this.value],
|
||||
cache: false
|
||||
}, actuator.onResponse || null);
|
||||
};
|
||||
Sui.select(actuator.selector).forEach( (domEl) =>{
|
||||
let handle = function(event) {
|
||||
update.call(this, actuator);
|
||||
}
|
||||
domEl.addEventListener(actuator.event, handle)
|
||||
});
|
||||
};
|
||||
},
|
||||
util: {
|
||||
|
||||
/**
|
||||
* serialize a flat json object
|
||||
*/
|
||||
serialize: (obj) => {
|
||||
var str = [];
|
||||
for(var p in obj){
|
||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
||||
}
|
||||
return str.join("&");
|
||||
}
|
||||
},
|
||||
http: {
|
||||
|
||||
/**
|
||||
* ajax request
|
||||
* {
|
||||
* method: <'GET', 'POST', whatever >,
|
||||
* endpoint: <URL to the service endpoint>,
|
||||
* async: <true or false>, (default true)
|
||||
* data: <json object with data to transmit>,
|
||||
* cache: <true or false> (default false)
|
||||
* }
|
||||
*/
|
||||
ajax: (config, callback) => {
|
||||
var cache = config.cache || false;
|
||||
var data = config.data || {};
|
||||
if(!cache) {
|
||||
data['_'] = new Date().getTime();
|
||||
}
|
||||
var serializedData = data; //Sui.util.serialize(data);
|
||||
var endPointUrl = (config.method === 'GET' || config.method === 'DELETE') && data ? config.endpoint+'?'+serializedData : config.endpoint;
|
||||
var postData = config.method === 'POST' || config.method === 'PUT' ? serializedData : null;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open(config.method, endPointUrl, config.async || true);
|
||||
request.onreadystatechange = function () {
|
||||
// TODO handle response properly
|
||||
callback ? callback(request.responseText, request.status) : undefined;
|
||||
};
|
||||
request.send(postData);
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
src/pages/favicon-32x32.png
Normal file
BIN
src/pages/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/pages/favicon.ico
Normal file
BIN
src/pages/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
100
src/pages/index.html
Normal file
100
src/pages/index.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>ESP Kit</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-32x32.png">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<script src="script.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="sui">
|
||||
<div class="content">
|
||||
<!-- <form class="param-control container open collapsible">
|
||||
<span class="heading">Debug</span>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li class="form-row ParamWs"
|
||||
data-name="scrollText"
|
||||
data-label="Audio"
|
||||
data-placeholder="some scroll text"
|
||||
data-endpoint="audiosprocket.lan/audio">
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
</form> -->
|
||||
<form class="param-control container collapsible open" action="#" method="POST">
|
||||
<span class="heading">Strip</span>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li class="form-row ParamColor"
|
||||
data-id="C"
|
||||
data-name="color"
|
||||
data-label="Color"
|
||||
data-endpoint="/patterns">
|
||||
</li>
|
||||
<li class="form-row ParamColor"
|
||||
data-id="D"
|
||||
data-name="color2"
|
||||
data-label="Color 2"
|
||||
data-endpoint="/patterns">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="Rainbow"
|
||||
data-label="Rainbow"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="Scanner"
|
||||
data-label="Scanner"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="TheaterChase"
|
||||
data-label="Theater Chase"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
<div class="settings container collapsible open">
|
||||
<span class="heading">WiFi Settings</span>
|
||||
<div class="content">
|
||||
<form action="/wifiConfig" method="POST">
|
||||
<!-- <li class="form-row">
|
||||
<label for="ap">AP Mode</label>
|
||||
<label class="switch ap-mode">
|
||||
<input type="checkbox" name="apMode">
|
||||
<span class="slider round" data-bind="apMode" data-state="false"></span>
|
||||
</label>
|
||||
</li -->
|
||||
<li class="form-row">
|
||||
<label for="ssid">SSID</label>
|
||||
<input type="text" name="ssid" placeholder="Default AP: Th1ngs4P">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<label for="password">PW</label>
|
||||
<input type="password" name="password" placeholder="Default: th3r31sn0sp00n">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<label for="hostName">Hostname</label>
|
||||
<input type="text" name="hostName" placeholder="Default: 192.168.1.143">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<button type="submit">Save</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
93
src/pages/matrix.html
Normal file
93
src/pages/matrix.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>ESP Kit</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-32x32.png">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<script src="script.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="sui">
|
||||
<div class="content">
|
||||
<form class="param-control container open collapsible">
|
||||
<span class="heading">Debug</span>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li class="form-row ParamWs"
|
||||
data-name="scrollText"
|
||||
data-label="Audio"
|
||||
data-placeholder="some scroll text"
|
||||
data-endpoint="audiosprocket.lan/audio">
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
</form>
|
||||
<form class="param-control container collapsible open" action="#" method="POST">
|
||||
<span class="heading">Strip</span>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li class="form-row ParamColor"
|
||||
data-name="color"
|
||||
data-label="Color"
|
||||
data-endpoint="/patterns">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="Rainbow"
|
||||
data-label="Rainbow"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="Scanner"
|
||||
data-label="Scanner"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
<li class="form-row LedStripPatternSwitch"
|
||||
data-group="stripPattern"
|
||||
data-name="TheaterChase"
|
||||
data-label="Theater Chase"
|
||||
data-endpoint="/strip/pattern">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
<div class="settings container collapsible open">
|
||||
<span class="heading">WiFi Settings</span>
|
||||
<div class="content">
|
||||
<form action="/wifiConfig" method="POST">
|
||||
<!-- <li class="form-row">
|
||||
<label for="ap">AP Mode</label>
|
||||
<label class="switch ap-mode">
|
||||
<input type="checkbox" name="apMode">
|
||||
<span class="slider round" data-bind="apMode" data-state="false"></span>
|
||||
</label>
|
||||
</li -->
|
||||
<li class="form-row">
|
||||
<label for="ssid">SSID</label>
|
||||
<input type="text" name="ssid" placeholder="Default AP: Th1ngs4P">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<label for="password">PW</label>
|
||||
<input type="password" name="password" placeholder="Default: th3r31sn0sp00n">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<label for="hostName">Hostname</label>
|
||||
<input type="text" name="hostName" placeholder="Default: 192.168.1.143">
|
||||
</li>
|
||||
<li class="form-row">
|
||||
<button type="submit">Save</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
41
src/styles/base.less
Normal file
41
src/styles/base.less
Normal file
@@ -0,0 +1,41 @@
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.sui {
|
||||
background: @color-black;
|
||||
color: @color-main-light;
|
||||
font-family: @default-font;
|
||||
font-size: @default-font-size;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
> .content {
|
||||
padding: @default-padding;
|
||||
}
|
||||
|
||||
label {
|
||||
color: @color-grey;
|
||||
}
|
||||
|
||||
button {
|
||||
background: @color-main;
|
||||
color: @color-white;
|
||||
font-size: 0.9em;
|
||||
border: 0;
|
||||
padding: .8em;
|
||||
margin: 0 .4em;
|
||||
}
|
||||
}
|
||||
26
src/styles/container.less
Normal file
26
src/styles/container.less
Normal file
@@ -0,0 +1,26 @@
|
||||
.container {
|
||||
background: @color-container;
|
||||
padding: @default-padding;
|
||||
border-radius: @default-border-radius;
|
||||
border: solid 1px @default-border-color;
|
||||
margin-bottom: @default-margin-small;
|
||||
&.collapsible {
|
||||
> .heading {
|
||||
margin-bottom: 0;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
> .content {
|
||||
.hidden;
|
||||
}
|
||||
&.open {
|
||||
> .heading {
|
||||
margin-bottom: @default-margin;
|
||||
}
|
||||
> .content {
|
||||
.shown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/styles/form/color.less
Normal file
7
src/styles/form/color.less
Normal file
@@ -0,0 +1,7 @@
|
||||
.ColorPicker {
|
||||
flex: none !important;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
height: 50px;
|
||||
width: 75px;
|
||||
}
|
||||
24
src/styles/form/input.less
Normal file
24
src/styles/form/input.less
Normal file
@@ -0,0 +1,24 @@
|
||||
.sui {
|
||||
|
||||
input {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"] {
|
||||
height: @input-text-height;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: @color-white;
|
||||
&:focus, &:hover {
|
||||
outline: none;
|
||||
border-bottom: solid 1px @color-main;
|
||||
box-shadow: 3px @color-main;
|
||||
}
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
transform: scale(@input-checkbox-scale);
|
||||
}
|
||||
input[type="range"] {
|
||||
|
||||
}
|
||||
}
|
||||
48
src/styles/form/layout.less
Normal file
48
src/styles/form/layout.less
Normal file
@@ -0,0 +1,48 @@
|
||||
form {
|
||||
.form-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
padding: .2em;
|
||||
|
||||
}
|
||||
.form-row > label {
|
||||
padding: .5em 1em .5em 0;
|
||||
flex: 1;
|
||||
}
|
||||
.form-row > label + label {
|
||||
flex: 0;
|
||||
}
|
||||
.form-row > label.switch + label {
|
||||
flex: 1;
|
||||
}
|
||||
.form-row > input {
|
||||
flex: 2;
|
||||
|
||||
}
|
||||
.form-row > span {
|
||||
flex: 2;
|
||||
text-align: right;
|
||||
}
|
||||
.form-row input[type="checkbox"] {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.form-row input[type="range"] {
|
||||
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.form-row > input, .form-row > span {
|
||||
flex: 3;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 992px) {
|
||||
.form-row > input, .form-row > span {
|
||||
flex: 4;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1200px) {
|
||||
.form-row > input, .form-row > span {
|
||||
flex: 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/styles/form/slider.less
Normal file
87
src/styles/form/slider.less
Normal file
@@ -0,0 +1,87 @@
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
margin: 8.4px 0;
|
||||
padding: 0 !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 3.2px;
|
||||
cursor: pointer;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
background: #097479;
|
||||
border-radius: 1.3px;
|
||||
border: 0.2px solid #010101;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
|
||||
border: 0.4px solid #000000;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50px;
|
||||
background: #0eb8c0;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
margin-top: -8.6px;
|
||||
}
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: #0eb4bb;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 3.2px;
|
||||
cursor: pointer;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
background: #097479;
|
||||
border-radius: 1.3px;
|
||||
border: 0.2px solid #010101;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
|
||||
border: 0.4px solid #000000;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50px;
|
||||
background: #0eb8c0;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
height: 3.2px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
input[type=range]::-ms-fill-lower {
|
||||
background: #043437;
|
||||
border: 0.2px solid #010101;
|
||||
border-radius: 2.6px;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-fill-upper {
|
||||
background: #097479;
|
||||
border: 0.2px solid #010101;
|
||||
border-radius: 2.6px;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
input[type=range]::-ms-thumb {
|
||||
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
|
||||
border: 0.4px solid #000000;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50px;
|
||||
background: #0eb8c0;
|
||||
cursor: pointer;
|
||||
height: 3.2px;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
background: #097479;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #0eb4bb;
|
||||
}
|
||||
61
src/styles/form/switch.less
Normal file
61
src/styles/form/switch.less
Normal file
@@ -0,0 +1,61 @@
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline;
|
||||
|
||||
input {
|
||||
display:none;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: @color-grey2;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
margin-left: -50px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: @color-main;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px @color-main-light;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
height: 34px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
5
src/styles/heading.less
Normal file
5
src/styles/heading.less
Normal file
@@ -0,0 +1,5 @@
|
||||
.heading {
|
||||
font-size: 1.2em;
|
||||
display: block;
|
||||
margin-bottom: @default-margin;
|
||||
}
|
||||
11
src/styles/main.less
Normal file
11
src/styles/main.less
Normal file
@@ -0,0 +1,11 @@
|
||||
@import "theme.less";
|
||||
|
||||
@import "base.less";
|
||||
@import "heading.less";
|
||||
@import "container.less";
|
||||
|
||||
@import "form/slider.less";
|
||||
@import "form/switch.less";
|
||||
@import "form/layout.less";
|
||||
@import "form/input.less";
|
||||
@import "form/color.less";
|
||||
19
src/styles/theme.less
Normal file
19
src/styles/theme.less
Normal file
@@ -0,0 +1,19 @@
|
||||
@default-font: "Open Sans";
|
||||
|
||||
@color-black: #000000;
|
||||
@color-white: #eeeeee;
|
||||
@color-grey: #b3b2b2;
|
||||
@color-grey2: #7b7b7b;
|
||||
@color-main: #097479;
|
||||
@color-main-light: #0eb8c0;
|
||||
@color-container: #333333;
|
||||
|
||||
@default-padding: 16px;
|
||||
@default-margin: 16px;
|
||||
@default-margin-small: 8px;
|
||||
@default-border-radius: 2px;
|
||||
@default-border-color: #555555;
|
||||
@default-font-size: 16px;
|
||||
|
||||
@input-text-height: 16px;
|
||||
@input-checkbox-scale: 2;
|
||||
@@ -1,6 +0,0 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
@@ -1,5 +0,0 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= title
|
||||
p Welcome to #{title}
|
||||
@@ -1,7 +0,0 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
body
|
||||
block content
|
||||
24
webpack.config.js
Normal file
24
webpack.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
entry: './src/app/main.js',
|
||||
output: {
|
||||
filename: './target/script.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['env'],
|
||||
plugins: ['transform-runtime']
|
||||
}
|
||||
}],
|
||||
rules: [{
|
||||
test: /\.html$/,
|
||||
use: 'raw-loader'
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.json']
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user