copy old frontend

This commit is contained in:
2018-05-10 22:23:58 +02:00
parent 85717752ab
commit 76fbd0c972
57 changed files with 8860 additions and 194 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
target/

14
Dockerfile Normal file
View 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" ]

View File

@@ -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
View File

@@ -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
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

View File

@@ -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;

View File

@@ -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;

View 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);
}
}

View 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();
}
}
});
}
}

View 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);
}
}

View 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
});
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}
});
}
}

View File

@@ -0,0 +1,2 @@
<label for="{{name}}">{{label}}</label>
<input type="color" class="{{name}} ColorPicker" name="{{name}}">

View 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)
}];
} */
}

View File

@@ -0,0 +1,2 @@
<label>{{label}}</label>
<input type="range" name="{{name}}" value="{{value}}" min="{{min}}" max="{{max}}">

View 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));
}
}

View 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>

View 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));
}
}

View File

@@ -0,0 +1,2 @@
<label for="{{name}}">{{label}}</label>
<input type="text" class="{{name}}" name="{{name}}" placeholder="{{placeholder}}">

View 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));
}
}

View 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
View 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
View 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
View 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 || {});
};
}
};
}

View 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 || {}));
};
}
};
}

View 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];
}
};
}
}

View 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;
}
}

View File

@@ -0,0 +1,9 @@
export default class DataStore {
constructor(mediator){
this.mediator = mediator;
}
load(entry){}
save(entry){}
add(entry){}
delete(entry){}
}

View 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);
}
}

View File

@@ -0,0 +1,6 @@
export default {
LOAD: 'load',
SAVE: 'save',
ADD: 'add',
DELETE: 'delete'
}

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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

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
View 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
View 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
View 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
View 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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
.ColorPicker {
flex: none !important;
background-color: transparent;
border: 0;
height: 50px;
width: 75px;
}

View 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"] {
}
}

View 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;
}
}
}

View 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;
}

View 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
View File

@@ -0,0 +1,5 @@
.heading {
font-size: 1.2em;
display: block;
margin-bottom: @default-margin;
}

11
src/styles/main.less Normal file
View 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
View 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;

View File

@@ -1,6 +0,0 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

View File

@@ -1,5 +0,0 @@
extends layout
block content
h1= title
p Welcome to #{title}

View File

@@ -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
View 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']
}
};