integrated webserver ... (#697)
* initial commit of webconfig * update example config with webconfig and fix format of file update debian postinst script for install example configpull/698/head^2
parent
d2f47251f5
commit
7dfb9f1967
@ -0,0 +1,609 @@
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../res/fontello.ttf') format('truetype'), url('../res/fontello.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #A6B4B4;
|
||||
background-color: #2C2C2C;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-family: 'Lucida Grande', Helvetica, Arial, Roboto, serif;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: -webkit-flex;
|
||||
-webkit-flex-direction: column;
|
||||
-webkit-flex-wrap: nowrap;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
background-color: #2C2C2C;
|
||||
min-width: 320px;
|
||||
min-height: 460px;
|
||||
}
|
||||
|
||||
.work {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid #919F9F;
|
||||
display: -webkit-flex;
|
||||
-webkit-justify-content: space-around;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.footer .button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.touchrect {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: "fontello";
|
||||
}
|
||||
|
||||
.footer .button .icon {
|
||||
font-size: 25px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.footer .button .title {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.footer .button:active,
|
||||
.footer .button.selected {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 400%;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
-webkit-transition: left 0.5s ease-in-out;
|
||||
transition: left 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.contentarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
-ms-overflow-y: auto;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
position: relative;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#color {
|
||||
display: -webkit-flex;
|
||||
-webkit-flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#colorpicker {
|
||||
position: relative;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#colorwheelbg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#pointer {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
border: 2px solid #FFFFFF;
|
||||
border-radius: 15px;
|
||||
left: calc(50% - 15px);
|
||||
top: calc(50% - 15px);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
display: -webkit-flex;
|
||||
-webkit-flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
li .delete_icon {
|
||||
font-family: "fontello";
|
||||
font-size: 20px;
|
||||
color: red;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
li .edit_icon {
|
||||
font-family: "fontello";
|
||||
font-size: 20px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.locked .edit_icon,
|
||||
.locked .delete_icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li .titlebox {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.titlebox label {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.titlebox label.title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.titlebox label.subtitle {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/*
|
||||
li:not(:last-child) {
|
||||
border-bottom: 1px solid #919F9F;
|
||||
}
|
||||
*/
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #919F9F;
|
||||
}
|
||||
|
||||
li input[type=range] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#transform li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#transform .icon {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.group,
|
||||
.grouplist {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.grouplist .header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid gray;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
font-size: 16px;
|
||||
display: -webkit-flex;
|
||||
-webkit-justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
}
|
||||
|
||||
.group ul,
|
||||
.grouplist ul {
|
||||
overflow-y: hidden;
|
||||
-webkit-transition: all 0.5s ease-in-out;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.group[collapsed=true] ul {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.group li label {
|
||||
margin: 4px 0 0 50px;
|
||||
display: block;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.group > .header {
|
||||
border-bottom: 1px solid gray;
|
||||
padding: 3px 0 3px 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.group > .header label {
|
||||
display: block;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.group > .header label:first-child {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.group > .header label:last-child {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.group > .header label:first-child:after {
|
||||
font-family: "fontello";
|
||||
content: '\e808';
|
||||
font-size: 20px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
-webkit-transform-origin: 50% 33%;
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform-origin: 50% 33%;
|
||||
transform: rotateZ(180deg);
|
||||
-ms-transform-origin: 50% 33%;
|
||||
-ms-transform: rotateZ(180deg);
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.grouplist .header .callout {
|
||||
font-family: "fontello";
|
||||
font-size: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grouplist .header .callout:active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.group[collapsed=true] > .header label:first-child:after {
|
||||
-webkit-transform: rotateZ(0deg);
|
||||
-ms-transform: rotateZ(0deg);
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: -webkit-flex;
|
||||
-webkit-align-items: center;
|
||||
}
|
||||
|
||||
.wrapper .value {
|
||||
width: 40px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: #FFFFFF;
|
||||
margin: auto 5px;
|
||||
}
|
||||
|
||||
#transform .group .slider {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.slider .track {
|
||||
border-radius: 4px;
|
||||
height: 4px;
|
||||
border: 1px solid #BDC3C7;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
}
|
||||
|
||||
.slider .thumb {
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border: 2px solid #BDC3C7;
|
||||
background-color: #FFFFFF;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.wrapper .icon {
|
||||
display: block;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.red .icon {
|
||||
color: #FF0000;
|
||||
}
|
||||
|
||||
.green .icon {
|
||||
color: #00FF00;
|
||||
}
|
||||
|
||||
.blue .icon {
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
.msg {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #D70000;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.status {
|
||||
background-color: #00A200;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.wrapper_msg {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inputline {
|
||||
display: -webkit-flex;
|
||||
-webkit-justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
#settings .inputline input {
|
||||
background: none;
|
||||
border: 1px solid rgba(100, 100, 100, 0.4);
|
||||
height: 100%;
|
||||
color: white;
|
||||
text-align: right;
|
||||
font-size: inherit;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#settings .inputline input:focus {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: auto 10px;
|
||||
border: 1px #A6B4B4 solid;
|
||||
background: none;
|
||||
color: #A6B4B4;
|
||||
padding: 6px 20px;
|
||||
}
|
||||
|
||||
button:active {
|
||||
border: 1px #FFFFFF solid;
|
||||
background-color: transparent;
|
||||
color: #FFFFFF;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
display: inline-flex;
|
||||
display: -webkit-inline-flex;
|
||||
-webkit-animation: rotation .8s infinite linear;
|
||||
animation: rotation .8s infinite linear;
|
||||
border: 6px inset #D7D7D7;
|
||||
border-radius: 50%;
|
||||
float: right;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {
|
||||
-ms-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-ms-transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
li.selected {
|
||||
background-color: rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
|
||||
#effects li {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#color #buttonctrl {
|
||||
display: -webkit-flex;
|
||||
-webkit-justify-content: space-between;
|
||||
-webkit-align-items: flex-end;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin: 10px auto 35px auto;
|
||||
}
|
||||
|
||||
#color #buttonctrl > .icon {
|
||||
font-size: 27px;
|
||||
line-height: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#color #buttonctrl > .icon:active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#color .slider {
|
||||
position: relative;
|
||||
width: 70%;
|
||||
margin: 10px auto;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#color .slider .track {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: -webkit-linear-gradient(left, #000000 0%, #FFFFFF 100%);
|
||||
background-image: linear-gradient(to right, #000000 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
#color .slider .thumb {
|
||||
background: transparent;
|
||||
border: 3px solid rgba(255, 255, 255, 1.0);
|
||||
width: 14px;
|
||||
border-radius: 4px;
|
||||
height: 28px;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
margin-left: -7px;
|
||||
}
|
||||
|
||||
#color .value {
|
||||
outline: none;
|
||||
border: 1px solid white;
|
||||
font-family: Monaco, monospace;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
width: 100px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.checkbox::before {
|
||||
display: table-cell;
|
||||
font-family: "fontello";
|
||||
content: '\e803';
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selected .checkbox::before {
|
||||
content: '\e802';
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html manifest="manifest.appcache">
|
||||
<head>
|
||||
<title>Hyperion remote control</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="format-detection" content="telephone=no"/>
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"/>
|
||||
<link href="css/index.css" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" href="res/icon_128.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="work">
|
||||
<div class="container">
|
||||
<div id="color" class="contentarea">
|
||||
<div id="colorpicker">
|
||||
<img id="colorwheelbg" src="res/colorwheel.png" width="auto" height="90%"/>
|
||||
<div id="pointer"></div>
|
||||
<div class="touchrect"></div>
|
||||
</div>
|
||||
<div class="slider" id="brightness">
|
||||
<div class="track"></div>
|
||||
<div class="thumb"></div>
|
||||
</div>
|
||||
<div id="buttonctrl">
|
||||
<div class="icon" id="clear_button"></div>
|
||||
<input type="text" class="value" autocomplete="off" autocorrect="off" autocapitalize="off"/>
|
||||
<div class="icon" id="clearall_button"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="effects" class="contentarea">
|
||||
<label class="info">Need server connection to fetch list.</label>
|
||||
<ul></ul>
|
||||
</div>
|
||||
<div id="transform" class="contentarea">
|
||||
<label class="info">Need server connection to fetch list.</label>
|
||||
<div class="values"></div>
|
||||
</div>
|
||||
<div id="settings" class="contentarea">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="button selected" id="colorButton" data-area="color">
|
||||
<div class="icon"></div>
|
||||
<div class="title">Color</div>
|
||||
<div class="touchrect"></div>
|
||||
</div>
|
||||
<div class="button" id="effectsButton" data-area="effects">
|
||||
<div class="icon"></div>
|
||||
<div class="title">Effects</div>
|
||||
<div class="touchrect"></div>
|
||||
</div>
|
||||
<div class="button" id="thresholdButton" data-area="transform">
|
||||
<div class="icon"></div>
|
||||
<div class="title">Transform</div>
|
||||
<div class="touchrect"></div>
|
||||
</div>
|
||||
<div class="button" id="settingsButton" data-area="settings">
|
||||
<div class="icon">n</div>
|
||||
<div class="title">Settings</div>
|
||||
<div class="touchrect"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/vendor/require.js" data-main="js/app/main"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,37 @@
|
||||
/*global define, chrome */
|
||||
define(['api/LocalStorage'], function (LocalStorage) {
|
||||
'use strict';
|
||||
return LocalStorage.subclass(/** @lends ChromeLocalStorage.prototype */{
|
||||
|
||||
/**
|
||||
* @class ChromeLocalStorage
|
||||
* @classdesc Chrome's persistent storage
|
||||
* @constructs
|
||||
* @extends LocalStorage
|
||||
*/
|
||||
constructor: function () {
|
||||
},
|
||||
|
||||
get: function () {
|
||||
chrome.storage.local.get('data', function (entry) {
|
||||
if (chrome.runtime.lastError) {
|
||||
this.emit('error', chrome.runtime.lastError.message);
|
||||
} else {
|
||||
this.emit('got', entry.data);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
set: function (data) {
|
||||
var entry = {};
|
||||
entry.data = data;
|
||||
chrome.storage.local.set(entry, function () {
|
||||
if (chrome.runtime.lastError) {
|
||||
this.emit('error', chrome.runtime.lastError.message);
|
||||
} else {
|
||||
this.emit('set');
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
/*global chrome */
|
||||
define(['api/Network'], function (Network) {
|
||||
'use strict';
|
||||
|
||||
return Network.subclass(/** @lends ChromeNetwork.prototype */{
|
||||
|
||||
/**
|
||||
* @class ChromeNetwork
|
||||
* @extends Network
|
||||
* @classdesc Network functions for chrome apps
|
||||
* @constructs
|
||||
*/
|
||||
constructor: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* @overrides
|
||||
* @param onSuccess
|
||||
* @param onError
|
||||
*/
|
||||
getLocalInterfaces: function (onSuccess, onError) {
|
||||
var ips = [];
|
||||
|
||||
chrome.system.network.getNetworkInterfaces(function (networkInterfaces) {
|
||||
var i;
|
||||
|
||||
if (chrome.runtime.lastError) {
|
||||
if (onError) {
|
||||
onError('Could not get network interfaces');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < networkInterfaces.length; i++) {
|
||||
// check only ipv4
|
||||
if (networkInterfaces[i].address.indexOf('.') === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ips.push(networkInterfaces[i].address);
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess(ips);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @overrides
|
||||
* @return {boolean}
|
||||
*/
|
||||
canDetectLocalAddress: function () {
|
||||
return true;
|
||||
}
|
||||
}, true);
|
||||
});
|
@ -0,0 +1,307 @@
|
||||
/*global chrome */
|
||||
define(['lib/stapes', 'api/Socket', 'utils/Tools'], function (Stapes, Socket, tools) {
|
||||
'use strict';
|
||||
return Socket.subclass(/** @lends ChromeTcpSocket.prototype */{
|
||||
DEBUG: false,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
handle: null,
|
||||
|
||||
/**
|
||||
* @type {function}
|
||||
*/
|
||||
currentResponseCallback: null,
|
||||
|
||||
/**
|
||||
* @type {function}
|
||||
*/
|
||||
currentErrorCallback: null,
|
||||
|
||||
/**
|
||||
* Temporary buffer for incoming data
|
||||
* @type {Uint8Array}
|
||||
*/
|
||||
inputBuffer: null,
|
||||
inputBufferIndex: 0,
|
||||
readBufferTimerId: null,
|
||||
|
||||
/**
|
||||
* @class ChromeTcpSocket
|
||||
* @extends Socket
|
||||
* @constructs
|
||||
*/
|
||||
constructor: function () {
|
||||
this.inputBuffer = new Uint8Array(4096);
|
||||
this.inputBufferIndex = 0;
|
||||
|
||||
chrome.sockets.tcp.onReceive.addListener(this.onDataReceived.bind(this));
|
||||
chrome.sockets.tcp.onReceiveError.addListener(this.onError.bind(this));
|
||||
},
|
||||
|
||||
create: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Creating socket...');
|
||||
}
|
||||
chrome.sockets.tcp.create({bufferSize: 4096}, function (createInfo) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket created: ' + createInfo.socketId);
|
||||
}
|
||||
this.handle = createInfo.socketId;
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
isConnected: function (resultCallback) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Checking if socket is connected...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (resultCallback) {
|
||||
resultCallback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.sockets.tcp.getInfo(this.handle, function (socketInfo) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket connected: ' + socketInfo.connected);
|
||||
}
|
||||
|
||||
if (socketInfo.connected) {
|
||||
if (resultCallback) {
|
||||
resultCallback(true);
|
||||
}
|
||||
} else {
|
||||
if (resultCallback) {
|
||||
resultCallback(false);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
connect: function (server, onSuccess, onError) {
|
||||
var timeoutHandle;
|
||||
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Connecting to peer ' + server.address + ':' + server.port);
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME for some reason chrome blocks if peer is not reachable
|
||||
timeoutHandle = setTimeout(function () {
|
||||
chrome.sockets.tcp.getInfo(this.handle, function (socketInfo) {
|
||||
if (!socketInfo.connected) {
|
||||
// let the consumer decide if to close or not?
|
||||
// this.close();
|
||||
onError('Could not connect to ' + server.address + ':' + server.port);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this), 500);
|
||||
|
||||
chrome.sockets.tcp.connect(this.handle, server.address, server.port, function (result) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Connect result: ' + result);
|
||||
}
|
||||
clearTimeout(timeoutHandle);
|
||||
|
||||
if (chrome.runtime.lastError) {
|
||||
if (onError) {
|
||||
onError('Could not connect to ' + server.address + ':' + server.port);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result !== 0) {
|
||||
if (onError) {
|
||||
onError('Could not connect to ' + server.address + ':' + server.port);
|
||||
}
|
||||
} else if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
close: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Closing socket...');
|
||||
}
|
||||
|
||||
if (this.handle) {
|
||||
chrome.sockets.tcp.close(this.handle, function () {
|
||||
this.handle = null;
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
write: function (data, onSuccess, onError) {
|
||||
var dataToSend = null, dataType = typeof (data);
|
||||
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] writing to socket...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.isConnected(function (connected) {
|
||||
if (connected) {
|
||||
if (dataType === 'string') {
|
||||
if (this.DEBUG) {
|
||||
console.log('> ' + data);
|
||||
}
|
||||
dataToSend = tools.str2ab(data);
|
||||
} else {
|
||||
if (this.DEBUG) {
|
||||
console.log('> ' + tools.ab2hexstr(data));
|
||||
}
|
||||
dataToSend = data;
|
||||
}
|
||||
|
||||
chrome.sockets.tcp.send(this.handle, tools.a2ab(dataToSend), function (sendInfo) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket write result: ' + sendInfo.resultCode);
|
||||
}
|
||||
|
||||
if (sendInfo.resultCode !== 0) {
|
||||
onError('Socket write error: ' + sendInfo.resultCode);
|
||||
} else if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
if (onError) {
|
||||
onError('No connection to peer');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
},
|
||||
|
||||
read: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] reading from socket...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.isConnected(function (connected) {
|
||||
if (!connected) {
|
||||
this.currentResponseCallback = null;
|
||||
this.currentErrorCallback = null;
|
||||
|
||||
if (onError) {
|
||||
onError('No connection to peer');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
if (onSuccess) {
|
||||
this.currentResponseCallback = onSuccess;
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
this.currentErrorCallback = onError;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Data receiption callback
|
||||
* @private
|
||||
* @param info
|
||||
*/
|
||||
onDataReceived: function (info) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] received data...');
|
||||
}
|
||||
|
||||
if (info.socketId === this.handle && info.data) {
|
||||
if (this.readBufferTimerId) {
|
||||
clearTimeout(this.readBufferTimerId);
|
||||
}
|
||||
if (this.readTimeoutTimerId) {
|
||||
clearTimeout(this.readTimeoutTimerId);
|
||||
this.readTimeoutTimerId = null;
|
||||
}
|
||||
this.inputBuffer.set(new Uint8Array(info.data), this.inputBufferIndex);
|
||||
this.inputBufferIndex += info.data.byteLength;
|
||||
|
||||
if (this.DEBUG) {
|
||||
console.log('< ' + tools.ab2hexstr(info.data));
|
||||
}
|
||||
|
||||
if (this.currentResponseCallback) {
|
||||
this.readBufferTimerId = setTimeout(function () {
|
||||
this.currentResponseCallback(this.inputBuffer.subarray(0, this.inputBufferIndex));
|
||||
this.inputBufferIndex = 0;
|
||||
this.currentResponseCallback = null;
|
||||
}.bind(this), 200);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Error callback
|
||||
* @private
|
||||
* @param info
|
||||
*/
|
||||
onError: function (info) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[ERROR]: ' + info.resultCode);
|
||||
}
|
||||
|
||||
if (info.socketId === this.handle) {
|
||||
if (this.currentErrorCallback) {
|
||||
this.currentErrorCallback(info.resultCode);
|
||||
this.currentErrorCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
});
|
@ -0,0 +1,49 @@
|
||||
/*global define */
|
||||
define(['lib/stapes'], function (Stapes) {
|
||||
'use strict';
|
||||
return Stapes.subclass(/** @lends LocalStorage.prototype */{
|
||||
|
||||
/**
|
||||
* @class LocalStorage
|
||||
* @classdesc LocalStorage handler using HTML5 localStorage
|
||||
* @constructs
|
||||
*
|
||||
* @fires got
|
||||
* @fires error
|
||||
* @fires set
|
||||
*/
|
||||
constructor: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets stored data
|
||||
*/
|
||||
get: function () {
|
||||
var data;
|
||||
|
||||
if (!window.localStorage) {
|
||||
this.emit('error', 'Local Storage not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.data) {
|
||||
data = JSON.parse(localStorage.data);
|
||||
this.emit('got', data);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores settings
|
||||
* @param {object} data - Data object to store
|
||||
*/
|
||||
set: function (data) {
|
||||
if (!window.localStorage) {
|
||||
this.emit('error', 'Local Storage not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.data = JSON.stringify(data);
|
||||
this.emit('set');
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
/*global define */
|
||||
define(['lib/stapes'], function (Stapes) {
|
||||
'use strict';
|
||||
return Stapes.subclass(/** @lends Network.prototype */{
|
||||
detectTimerId: null,
|
||||
|
||||
/**
|
||||
* @class Network
|
||||
* @classdesc Empty network functions handler
|
||||
* @constructs
|
||||
*/
|
||||
constructor: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the list of known local interfaces (ipv4)
|
||||
* @param {function(string[])} [onSuccess] - Callback to call on success
|
||||
* @param {function(error:string)} [onError] - Callback to call on error
|
||||
*/
|
||||
getLocalInterfaces: function (onSuccess, onError) {
|
||||
var ips = [], RTCPeerConnection;
|
||||
|
||||
// https://developer.mozilla.org/de/docs/Web/API/RTCPeerConnection
|
||||
RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.msRTCPeerConnection;
|
||||
|
||||
var rtc = new RTCPeerConnection({iceServers: []});
|
||||
rtc.onicecandidate = function (event) {
|
||||
var parts;
|
||||
|
||||
if (this.detectTimerId) {
|
||||
clearTimeout(this.detectTimerId);
|
||||
}
|
||||
|
||||
if (event.candidate) {
|
||||
parts = event.candidate.candidate.split(' ');
|
||||
if (ips.indexOf(parts[4]) === -1) {
|
||||
console.log(event.candidate);
|
||||
ips.push(parts[4]);
|
||||
}
|
||||
}
|
||||
|
||||
this.detectTimerId = setTimeout(function () {
|
||||
if (onSuccess) {
|
||||
onSuccess(ips);
|
||||
}
|
||||
}, 200);
|
||||
}.bind(this);
|
||||
|
||||
rtc.createDataChannel('');
|
||||
rtc.createOffer(rtc.setLocalDescription.bind(rtc), onError);
|
||||
},
|
||||
|
||||
canDetectLocalAddress: function () {
|
||||
return window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.msRTCPeerConnection;
|
||||
}
|
||||
}, true);
|
||||
});
|
@ -0,0 +1,68 @@
|
||||
/*global define */
|
||||
define(['lib/stapes'], function (Stapes) {
|
||||
'use strict';
|
||||
return Stapes.subclass(/** @lends Socket.prototype */{
|
||||
|
||||
/**
|
||||
* @class Socket
|
||||
* @abstract
|
||||
*/
|
||||
constructor: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the socket
|
||||
* @param onSuccess
|
||||
* @param onError
|
||||
* @abstract
|
||||
*/
|
||||
create: function (onSuccess, onError) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a connection is opened.
|
||||
* @abstract
|
||||
*/
|
||||
isConnected: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect to another peer
|
||||
* @param {Object} peer Port object
|
||||
* @param {function} [onSuccess] Callback to call on success
|
||||
* @param {function(error:string)} [onError] Callback to call on error
|
||||
* @abstract
|
||||
*/
|
||||
connect: function (peer, onSuccess, onError) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the current connection
|
||||
* @param {function} [onSuccess] Callback to call on success
|
||||
* @param {function(error:string)} [onError] Callback to call on error
|
||||
* @abstract
|
||||
*/
|
||||
close: function (onSuccess, onError) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Read data from the socket
|
||||
* @param {function} [onSuccess] Callback to call on success
|
||||
* @param {function(error:string)} [onError] Callback to call on error
|
||||
* @abstract
|
||||
*/
|
||||
read: function (onSuccess, onError) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes data to the socket
|
||||
* @param {string | Array} data Data to send.
|
||||
* @param {function} [onSuccess] Callback to call if data was sent successfully
|
||||
* @param {function(error:string)} [onError] Callback to call on error
|
||||
* @abstract
|
||||
*/
|
||||
write: function (data, onSuccess, onError) {
|
||||
}
|
||||
}, true);
|
||||
});
|
@ -0,0 +1,229 @@
|
||||
define(['lib/stapes', 'api/Socket', 'utils/Tools'], function (Stapes, Socket, tools) {
|
||||
'use strict';
|
||||
return Socket.subclass(/** @lends WebSocket.prototype */{
|
||||
DEBUG: false,
|
||||
|
||||
handle: null,
|
||||
|
||||
/**
|
||||
* @type {function}
|
||||
*/
|
||||
currentResponseCallback: null,
|
||||
|
||||
/**
|
||||
* @type {function}
|
||||
*/
|
||||
currentErrorCallback: null,
|
||||
|
||||
/**
|
||||
* Temporary buffer for incoming data
|
||||
* @type {Uint8Array}
|
||||
*/
|
||||
inputBuffer: null,
|
||||
inputBufferIndex: 0,
|
||||
readBufferTimerId: null,
|
||||
|
||||
/**
|
||||
* @class WebSocket
|
||||
* @extends Socket
|
||||
* @constructs
|
||||
*/
|
||||
constructor: function () {
|
||||
this.inputBuffer = new Uint8Array(4096);
|
||||
this.inputBufferIndex = 0;
|
||||
},
|
||||
|
||||
create: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Creating socket...');
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
isConnected: function (resultCallback) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Checking if socket is connected...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (resultCallback) {
|
||||
resultCallback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCallback) {
|
||||
if (this.handle.readyState === WebSocket.OPEN) {
|
||||
resultCallback(true);
|
||||
} else {
|
||||
resultCallback(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
connect: function (server, onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Connecting to peer ' + server.address + ':' + server.port);
|
||||
}
|
||||
|
||||
this.currentErrorCallback = onError;
|
||||
|
||||
this.handle = new WebSocket('ws://' + server.address + ':' + server.port);
|
||||
this.handle.onmessage = this.onDataReceived.bind(this);
|
||||
this.handle.onclose = function () {
|
||||
if (this.DEBUG) {
|
||||
console.log('onClose');
|
||||
}
|
||||
}.bind(this);
|
||||
this.handle.onerror = function () {
|
||||
if (this.DEBUG) {
|
||||
console.log('[ERROR]: ');
|
||||
}
|
||||
|
||||
if (this.currentErrorCallback) {
|
||||
this.currentErrorCallback('WebSocket error');
|
||||
this.currentErrorCallback = null;
|
||||
}
|
||||
}.bind(this);
|
||||
this.handle.onopen = function () {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
close: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Closing socket...');
|
||||
}
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.close();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
write: function (data, onSuccess, onError) {
|
||||
var dataToSend = null, dataType = typeof (data);
|
||||
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] writing to socket...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] Socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.isConnected(function (connected) {
|
||||
if (connected) {
|
||||
if (dataType === 'string') {
|
||||
if (this.DEBUG) {
|
||||
console.log('> ' + data);
|
||||
}
|
||||
//dataToSend = tools.str2ab(data);
|
||||
dataToSend = data;
|
||||
} else {
|
||||
if (this.DEBUG) {
|
||||
console.log('> ' + tools.ab2hexstr(data));
|
||||
}
|
||||
dataToSend = data;
|
||||
}
|
||||
|
||||
this.currentErrorCallback = onError;
|
||||
this.handle.send(dataToSend);
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError('No connection to peer');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
},
|
||||
|
||||
read: function (onSuccess, onError) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] reading from socket...');
|
||||
}
|
||||
|
||||
if (!this.handle) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] socket not created');
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError('Socket handle is invalid');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.isConnected(function (connected) {
|
||||
if (!connected) {
|
||||
this.currentResponseCallback = null;
|
||||
this.currentErrorCallback = null;
|
||||
|
||||
if (onError) {
|
||||
onError('No connection to peer');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
if (onSuccess) {
|
||||
this.currentResponseCallback = onSuccess;
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
this.currentErrorCallback = onError;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Data receiption callback
|
||||
* @private
|
||||
* @param event
|
||||
*/
|
||||
onDataReceived: function (event) {
|
||||
if (this.DEBUG) {
|
||||
console.log('[DEBUG] received data...');
|
||||
}
|
||||
|
||||
if (this.handle && event.data) {
|
||||
if (this.DEBUG) {
|
||||
console.log('< ' + event.data);
|
||||
}
|
||||
|
||||
if (this.currentResponseCallback) {
|
||||
this.currentResponseCallback(tools.str2ab(event.data));
|
||||
this.currentResponseCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
});
|
@ -0,0 +1,534 @@
|
||||
/*global define */
|
||||
define([
|
||||
'lib/stapes', 'views/MainView', 'models/Settings', 'views/SettingsView', 'views/EffectsView', 'views/TransformView', 'data/ServerControl', 'api/Socket', 'api/Network'
|
||||
], function (Stapes, MainView, Settings, SettingsView, EffectsView, TransformView, ServerControl, Socket, Network) {
|
||||
'use strict';
|
||||
var network = new Network();
|
||||
|
||||
return Stapes.subclass(/** @lends AppController.prototype */{
|
||||
/**
|
||||
* @type MainView
|
||||
*/
|
||||
mainView: null,
|
||||
/**
|
||||
* @type SettingsView
|
||||
*/
|
||||
settingsView: null,
|
||||
/**
|
||||
* @type EffectsView
|
||||
*/
|
||||
effectsView: null,
|
||||
/**
|
||||
* @type TransformView
|
||||
*/
|
||||
transformView: null,
|
||||
/**
|
||||
* @type Settings
|
||||
*/
|
||||
settings: null,
|
||||
/**
|
||||
* @type ServerControl
|
||||
*/
|
||||
serverControl: null,
|
||||
color: {
|
||||
r: 25,
|
||||
g: 25,
|
||||
b: 25
|
||||
},
|
||||
effects: [],
|
||||
transform: {},
|
||||
selectedServer: null,
|
||||
|
||||
/**
|
||||
* @class AppController
|