mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
integrated webserver ... (#697)
* initial commit of webconfig * update example config with webconfig and fix format of file update debian postinst script for install example config
This commit is contained in:
parent
d2f47251f5
commit
7dfb9f1967
@ -116,10 +116,10 @@ find_package(GitVersion)
|
|||||||
configure_file("${PROJECT_SOURCE_DIR}/HyperionConfig.h.in" "${PROJECT_BINARY_DIR}/HyperionConfig.h")
|
configure_file("${PROJECT_SOURCE_DIR}/HyperionConfig.h.in" "${PROJECT_BINARY_DIR}/HyperionConfig.h")
|
||||||
include_directories("${PROJECT_BINARY_DIR}")
|
include_directories("${PROJECT_BINARY_DIR}")
|
||||||
|
|
||||||
if(ENABLE_QT5)
|
if( NOT ENABLE_QT5)
|
||||||
ADD_DEFINITIONS ( -DENABLE_QT5 )
|
#ADD_DEFINITIONS ( -DENABLE_QT5 )
|
||||||
#find_package(Qt5Widgets)
|
#find_package(Qt5Widgets)
|
||||||
else()
|
#else()
|
||||||
# Add specific cmake modules to find qt4 (default version finds first available QT which might not be qt4)
|
# Add specific cmake modules to find qt4 (default version finds first available QT which might not be qt4)
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/qt4)
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/qt4)
|
||||||
endif()
|
endif()
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
// Define to enable profiler for development purpose
|
// Define to enable profiler for development purpose
|
||||||
#cmakedefine ENABLE_PROFILER
|
#cmakedefine ENABLE_PROFILER
|
||||||
|
#cmakedefine ENABLE_QT5
|
||||||
|
|
||||||
// the hyperion build id string
|
// the hyperion build id string
|
||||||
#define HYPERION_VERSION_ID "${HYPERION_VERSION_ID}"
|
#define HYPERION_VERSION_ID "${HYPERION_VERSION_ID}"
|
||||||
|
609
assets/webconfig/css/index.css
Normal file
609
assets/webconfig/css/index.css
Normal file
@ -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';
|
||||||
|
}
|
69
assets/webconfig/index.html
Normal file
69
assets/webconfig/index.html
Normal file
@ -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>
|
37
assets/webconfig/js/app/api/ChromeLocalStorage.js
Normal file
37
assets/webconfig/js/app/api/ChromeLocalStorage.js
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
57
assets/webconfig/js/app/api/ChromeNetwork.js
Normal file
57
assets/webconfig/js/app/api/ChromeNetwork.js
Normal file
@ -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);
|
||||||
|
});
|
307
assets/webconfig/js/app/api/ChromeTcpSocket.js
Normal file
307
assets/webconfig/js/app/api/ChromeTcpSocket.js
Normal file
@ -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);
|
||||||
|
});
|
49
assets/webconfig/js/app/api/LocalStorage.js
Normal file
49
assets/webconfig/js/app/api/LocalStorage.js
Normal file
@ -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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
57
assets/webconfig/js/app/api/Network.js
Normal file
57
assets/webconfig/js/app/api/Network.js
Normal file
@ -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);
|
||||||
|
});
|
68
assets/webconfig/js/app/api/Socket.js
Normal file
68
assets/webconfig/js/app/api/Socket.js
Normal file
@ -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);
|
||||||
|
});
|
229
assets/webconfig/js/app/api/WebSocket.js
Normal file
229
assets/webconfig/js/app/api/WebSocket.js
Normal file
@ -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);
|
||||||
|
});
|
534
assets/webconfig/js/app/controllers/AppController.js
Normal file
534
assets/webconfig/js/app/controllers/AppController.js
Normal file
@ -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
|
||||||
|
* @constructs
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
this.mainView = new MainView();
|
||||||
|
this.settingsView = new SettingsView();
|
||||||
|
this.effectsView = new EffectsView();
|
||||||
|
this.transformView = new TransformView();
|
||||||
|
|
||||||
|
this.settings = new Settings();
|
||||||
|
|
||||||
|
this.bindEventHandlers();
|
||||||
|
this.mainView.setColor(this.color);
|
||||||
|
|
||||||
|
if (!network.canDetectLocalAddress()) {
|
||||||
|
this.settingsView.enableDetectButton(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do initialization
|
||||||
|
*/
|
||||||
|
init: function () {
|
||||||
|
this.settings.load();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bindEventHandlers: function () {
|
||||||
|
this.settings.on({
|
||||||
|
'loaded': function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 0; i < this.settings.servers.length; i++) {
|
||||||
|
if (this.settings.servers[i].selected) {
|
||||||
|
this.selectedServer = this.settings.servers[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
|
||||||
|
if (!this.selectedServer) {
|
||||||
|
this.gotoArea('settings');
|
||||||
|
} else {
|
||||||
|
this.connectToServer(this.selectedServer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'error': function (message) {
|
||||||
|
this.showError(message);
|
||||||
|
},
|
||||||
|
'serverAdded': function (server) {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < this.settings.servers.length; i++) {
|
||||||
|
if (this.settings.servers[i].selected) {
|
||||||
|
this.selectedServer = this.settings.servers[i];
|
||||||
|
this.connectToServer(server);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
},
|
||||||
|
'serverChanged': function (server) {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < this.settings.servers.length; i++) {
|
||||||
|
if (this.settings.servers[i].selected) {
|
||||||
|
this.selectedServer = this.settings.servers[i];
|
||||||
|
this.connectToServer(server);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
this.connectToServer(server);
|
||||||
|
},
|
||||||
|
'serverRemoved': function () {
|
||||||
|
var i, removedSelected = true;
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
|
||||||
|
for (i = 0; i < this.settings.servers.length; i++) {
|
||||||
|
if (this.settings.servers[i].selected) {
|
||||||
|
removedSelected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedSelected) {
|
||||||
|
this.selectedServer = null;
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.disconnect();
|
||||||
|
}
|
||||||
|
this.effectsView.clear();
|
||||||
|
this.transformView.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.mainView.on({
|
||||||
|
'barClick': function (id) {
|
||||||
|
if (id !== 'settings') {
|
||||||
|
if (!this.selectedServer) {
|
||||||
|
this.showError('No server selected');
|
||||||
|
} else if (!this.serverControl) {
|
||||||
|
this.connectToServer(this.selectedServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.gotoArea(id);
|
||||||
|
},
|
||||||
|
'colorChange': function (color) {
|
||||||
|
this.color = color;
|
||||||
|
|
||||||
|
if (!this.selectedServer) {
|
||||||
|
this.showError('No server selected');
|
||||||
|
} else if (!this.serverControl) {
|
||||||
|
this.connectToServer(this.selectedServer, function () {
|
||||||
|
this.serverControl.setColor(color, this.selectedServer.duration);
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.serverControl.setColor(color, this.selectedServer.duration);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'clear': function () {
|
||||||
|
if (!this.selectedServer) {
|
||||||
|
this.showError('No server selected');
|
||||||
|
} else if (!this.serverControl) {
|
||||||
|
this.connectToServer(this.selectedServer, function () {
|
||||||
|
this.serverControl.clear();
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.serverControl.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'clearall': function () {
|
||||||
|
if (!this.selectedServer) {
|
||||||
|
this.showError('No server selected');
|
||||||
|
} else if (!this.serverControl) {
|
||||||
|
this.connectToServer(this.selectedServer, function () {
|
||||||
|
this.serverControl.clearall();
|
||||||
|
this.mainView.setColor({r: 0, g: 0, b: 0});
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.serverControl.clearall();
|
||||||
|
this.mainView.setColor({r: 0, g: 0, b: 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.settingsView.on({
|
||||||
|
'serverAdded': function (server) {
|
||||||
|
if (server.address && server.port) {
|
||||||
|
server.priority = server.priority || 50;
|
||||||
|
this.settings.addServer(server);
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid server data');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'serverAddCanceled': function () {
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
},
|
||||||
|
'serverEditCanceled': function () {
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
},
|
||||||
|
'serverSelected': function (index) {
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
this.settings.setSelectedServer(index);
|
||||||
|
},
|
||||||
|
'serverRemoved': function (index) {
|
||||||
|
this.settings.removeServer(index);
|
||||||
|
},
|
||||||
|
'serverChanged': function (data) {
|
||||||
|
if (data.server.address && data.server.port) {
|
||||||
|
data.server.priority = data.server.priority || 50;
|
||||||
|
this.settings.updateServer(data.index, data.server);
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid server data');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'editServer': function (index) {
|
||||||
|
var server = this.settings.servers[index];
|
||||||
|
this.settingsView.editServer({index: index, server: server});
|
||||||
|
},
|
||||||
|
'durationChanged': function (value) {
|
||||||
|
this.settings.duration = value;
|
||||||
|
this.settings.save();
|
||||||
|
},
|
||||||
|
'detect': function () {
|
||||||
|
this.lockSettingsView(true);
|
||||||
|
this.settingsView.showWaiting(true);
|
||||||
|
this.searchForServer(function (server) {
|
||||||
|
this.settings.addServer(server);
|
||||||
|
}.bind(this), function () {
|
||||||
|
this.lockSettingsView(false);
|
||||||
|
this.settingsView.showWaiting(false);
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.effectsView.on({
|
||||||
|
'effectSelected': function (effectId) {
|
||||||
|
if (!this.serverControl) {
|
||||||
|
this.connectToServer(this.selectedServer, function () {
|
||||||
|
this.serverControl.runEffect(this.effects[parseInt(effectId)]);
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.serverControl.runEffect(this.effects[parseInt(effectId)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.transformView.on({
|
||||||
|
'gamma': function (data) {
|
||||||
|
if (data.r) {
|
||||||
|
this.transform.gamma[0] = data.r;
|
||||||
|
} else if (data.g) {
|
||||||
|
this.transform.gamma[1] = data.g;
|
||||||
|
} else if (data.b) {
|
||||||
|
this.transform.gamma[2] = data.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.setTransform(this.transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'whitelevel': function (data) {
|
||||||
|
if (data.r) {
|
||||||
|
this.transform.whitelevel[0] = data.r;
|
||||||
|
} else if (data.g) {
|
||||||
|
this.transform.whitelevel[1] = data.g;
|
||||||
|
} else if (data.b) {
|
||||||
|
this.transform.whitelevel[2] = data.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.setTransform(this.transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'blacklevel': function (data) {
|
||||||
|
if (data.r) {
|
||||||
|
this.transform.blacklevel[0] = data.r;
|
||||||
|
} else if (data.g) {
|
||||||
|
this.transform.blacklevel[1] = data.g;
|
||||||
|
} else if (data.b) {
|
||||||
|
this.transform.blacklevel[2] = data.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.setTransform(this.transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'threshold': function (data) {
|
||||||
|
if (data.r) {
|
||||||
|
this.transform.threshold[0] = data.r;
|
||||||
|
} else if (data.g) {
|
||||||
|
this.transform.threshold[1] = data.g;
|
||||||
|
} else if (data.b) {
|
||||||
|
this.transform.threshold[2] = data.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.setTransform(this.transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'hsv': function (data) {
|
||||||
|
if (data.valueGain) {
|
||||||
|
this.transform.valueGain = data.valueGain;
|
||||||
|
} else if (data.saturationGain) {
|
||||||
|
this.transform.saturationGain = data.saturationGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverControl) {
|
||||||
|
this.serverControl.setTransform(this.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
gotoArea: function (id) {
|
||||||
|
this.mainView.scrollToArea(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param server
|
||||||
|
*/
|
||||||
|
connectToServer: function (server, onConnected) {
|
||||||
|
if (this.serverControl) {
|
||||||
|
if (this.serverControl.isConnecting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.serverControl.off();
|
||||||
|
this.serverControl.disconnect();
|
||||||
|
this.transformView.clear();
|
||||||
|
this.effectsView.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serverControl = new ServerControl(server, Socket);
|
||||||
|
this.serverControl.on({
|
||||||
|
connected: function () {
|
||||||
|
this.serverControl.getServerInfo();
|
||||||
|
},
|
||||||
|
serverInfo: function (info) {
|
||||||
|
var index;
|
||||||
|
if (!this.selectedServer.name || this.selectedServer.name.length === 0) {
|
||||||
|
this.selectedServer.name = info.hostname;
|
||||||
|
index = this.settings.indexOfServer(this.selectedServer);
|
||||||
|
this.settings.updateServer(index, this.selectedServer);
|
||||||
|
this.settingsView.fillServerList(this.settings.servers);
|
||||||
|
}
|
||||||
|
this.effects = info.effects;
|
||||||
|
this.transform = info.transform[0];
|
||||||
|
this.updateView();
|
||||||
|
this.showStatus('Connected to ' + this.selectedServer.name);
|
||||||
|
if (onConnected) {
|
||||||
|
onConnected();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (message) {
|
||||||
|
this.serverControl = null;
|
||||||
|
this.showError(message);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
this.serverControl.connect();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateView: function () {
|
||||||
|
var i, effects = [];
|
||||||
|
if (this.effects) {
|
||||||
|
for (i = 0; i < this.effects.length; i++) {
|
||||||
|
effects.push({id: i, name: this.effects[i].name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.effectsView.clear();
|
||||||
|
this.effectsView.fillList(effects);
|
||||||
|
|
||||||
|
this.transformView.clear();
|
||||||
|
this.transformView.fillList(this.transform);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the error text
|
||||||
|
* @param {string} error - Error message
|
||||||
|
*/
|
||||||
|
showError: function (error) {
|
||||||
|
this.mainView.showError(error);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a message
|
||||||
|
* @param {string} message - Text to show
|
||||||
|
*/
|
||||||
|
showStatus: function (message) {
|
||||||
|
this.mainView.showStatus(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param lock
|
||||||
|
*/
|
||||||
|
lockSettingsView: function (lock) {
|
||||||
|
if (network.canDetectLocalAddress()) {
|
||||||
|
this.settingsView.enableDetectButton(!lock);
|
||||||
|
}
|
||||||
|
this.settingsView.lockList(lock);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param onFound
|
||||||
|
* @param onEnd
|
||||||
|
*/
|
||||||
|
searchForServer: function (onFound, onEnd) {
|
||||||
|
network.getLocalInterfaces(function (ips) {
|
||||||
|
if (ips.length === 0) {
|
||||||
|
onEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInterface (localInterfaceAddress, ciOnFinished) {
|
||||||
|
var index, ipParts, addr;
|
||||||
|
|
||||||
|
index = 1;
|
||||||
|
ipParts = localInterfaceAddress.split('.');
|
||||||
|
ipParts[3] = index;
|
||||||
|
addr = ipParts.join('.');
|
||||||
|
|
||||||
|
function checkAddressRange (startAddress, count, carOnFinished) {
|
||||||
|
var ipParts, i, addr, cbCounter = 0, last;
|
||||||
|
|
||||||
|
function checkAddress (address, port, caOnFinished) {
|
||||||
|
var server = new ServerControl({'address': address, 'port': port}, Socket);
|
||||||
|
server.on({
|
||||||
|
'error': function () {
|
||||||
|
server.disconnect();
|
||||||
|
caOnFinished();
|
||||||
|
},
|
||||||
|
'connected': function () {
|
||||||
|
server.getServerInfo();
|
||||||
|
},
|
||||||
|
'serverInfo': function (result) {
|
||||||
|
var serverInfo = {
|
||||||
|
'address': address,
|
||||||
|
'port': port,
|
||||||
|
'priority': 50
|
||||||
|
};
|
||||||
|
server.disconnect();
|
||||||
|
|
||||||
|
if (result.hostname) {
|
||||||
|
serverInfo.name = result.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
caOnFinished(serverInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAddressDoneCb (serverInfo) {
|
||||||
|
var ipParts, nextAddr;
|
||||||
|
|
||||||
|
if (serverInfo && onFound) {
|
||||||
|
onFound(serverInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
cbCounter++;
|
||||||
|
if (cbCounter === count) {
|
||||||
|
ipParts = startAddress.split('.');
|
||||||
|
ipParts[3] = parseInt(ipParts[3]) + count;
|
||||||
|
nextAddr = ipParts.join('.');
|
||||||
|
carOnFinished(nextAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipParts = startAddress.split('.');
|
||||||
|
last = parseInt(ipParts[3]);
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
ipParts[3] = last + i;
|
||||||
|
addr = ipParts.join('.');
|
||||||
|
|
||||||
|
checkAddress(addr, 19444, checkAddressDoneCb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAddressRangeCb (nextAddr) {
|
||||||
|
var ipParts, count = 64, lastPart;
|
||||||
|
|
||||||
|
ipParts = nextAddr.split('.');
|
||||||
|
lastPart = parseInt(ipParts[3]);
|
||||||
|
if (lastPart === 255) {
|
||||||
|
ciOnFinished();
|
||||||
|
return;
|
||||||
|
} else if (lastPart + 64 > 254) {
|
||||||
|
count = 255 - lastPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAddressRange(nextAddr, count, checkAddressRangeCb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// do search in chunks because the dispatcher used in the ios socket plugin can handle only 64 threads
|
||||||
|
checkAddressRange(addr, 64, checkAddressRangeCb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInterfaceCb () {
|
||||||
|
if (ips.length === 0) {
|
||||||
|
onEnd();
|
||||||
|
} else {
|
||||||
|
checkInterface(ips.pop(), checkInterfaceCb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkInterface(ips.pop(), checkInterfaceCb);
|
||||||
|
|
||||||
|
}.bind(this), function (error) {
|
||||||
|
this.showError(error);
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
201
assets/webconfig/js/app/data/ServerControl.js
Normal file
201
assets/webconfig/js/app/data/ServerControl.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*global define */
|
||||||
|
define(['lib/stapes', 'utils/Tools'], function (Stapes, tools) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends ServerControl.prototype */{
|
||||||
|
/** @type Socket */
|
||||||
|
socket: null,
|
||||||
|
server: null,
|
||||||
|
connecting: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ServerControl
|
||||||
|
* @classdesc Interface for the hyperion server control. All commands are sent directly to hyperion's server.
|
||||||
|
* @constructs
|
||||||
|
* @param {object} server - Hyperion server parameter
|
||||||
|
* @param {string} server.address - Server address
|
||||||
|
* @param {number} server.port - Hyperion server port
|
||||||
|
* @param {function} Socket - constructor of the socket to use for communication
|
||||||
|
*
|
||||||
|
* @fires connected
|
||||||
|
* @fires error
|
||||||
|
* @fires serverInfo
|
||||||
|
* @fires cmdSent
|
||||||
|
*/
|
||||||
|
constructor: function (server, Socket) {
|
||||||
|
this.server = server;
|
||||||
|
this.socket = new Socket();
|
||||||
|
this.connecting = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to connect to the server
|
||||||
|
*/
|
||||||
|
connect: function () {
|
||||||
|
if (!this.server) {
|
||||||
|
this.emit('error', 'Missing server info');
|
||||||
|
} else {
|
||||||
|
this.connecting = true;
|
||||||
|
this.socket.create(function () {
|
||||||
|
this.socket.connect(this.server, function () {
|
||||||
|
this.emit('connected');
|
||||||
|
this.connecting = false;
|
||||||
|
}.bind(this), function (error) {
|
||||||
|
this.socket.close();
|
||||||
|
this.emit('error', error);
|
||||||
|
this.connecting = false;
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the server
|
||||||
|
*/
|
||||||
|
disconnect: function () {
|
||||||
|
this.socket.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the color command to the server
|
||||||
|
* @param {object} color - Color to set
|
||||||
|
* @param {number} color.r - Red value
|
||||||
|
* @param {number} color.g - Green value
|
||||||
|
* @param {number} color.b - Blue value
|
||||||
|
* @param {number} duration - Duration in seconds
|
||||||
|
*/
|
||||||
|
setColor: function (color, duration) {
|
||||||
|
var intColor, cmd;
|
||||||
|
|
||||||
|
intColor = [
|
||||||
|
Math.floor(color.r), Math.floor(color.g), Math.floor(color.b)
|
||||||
|
];
|
||||||
|
cmd = {
|
||||||
|
command: 'color',
|
||||||
|
color: intColor,
|
||||||
|
priority: this.server.priority
|
||||||
|
};
|
||||||
|
|
||||||
|
if (duration) {
|
||||||
|
cmd.duration = duration * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendCommand(cmd);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function () {
|
||||||
|
var cmd = {
|
||||||
|
command: 'clear',
|
||||||
|
priority: this.server.priority
|
||||||
|
};
|
||||||
|
this.sendCommand(cmd);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearall: function () {
|
||||||
|
var cmd = {
|
||||||
|
command: 'clearall'
|
||||||
|
};
|
||||||
|
this.sendCommand(cmd);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a command to rund specified effect
|
||||||
|
* @param {object} effect - Effect object
|
||||||
|
*/
|
||||||
|
runEffect: function (effect) {
|
||||||
|
var cmd;
|
||||||
|
|
||||||
|
if (!effect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = {
|
||||||
|
command: 'effect',
|
||||||
|
effect: {
|
||||||
|
name: effect.name,
|
||||||
|
args: effect.args
|
||||||
|
},
|
||||||
|
priority: this.server.priority
|
||||||
|
};
|
||||||
|
this.sendCommand(cmd);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a command for color transformation
|
||||||
|
* @param {object} transform
|
||||||
|
*/
|
||||||
|
setTransform: function (transform) {
|
||||||
|
var cmd;
|
||||||
|
|
||||||
|
if (!transform) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = {
|
||||||
|
'command': 'transform',
|
||||||
|
'transform': transform
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sendCommand(cmd);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param command
|
||||||
|
*/
|
||||||
|
sendCommand: function (command) {
|
||||||
|
var data;
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof command === 'string') {
|
||||||
|
data = command;
|
||||||
|
} else {
|
||||||
|
data = JSON.stringify(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.isConnected(function (connected) {
|
||||||
|
if (connected) {
|
||||||
|
this.socket.write(data + '\n', function () {
|
||||||
|
this.emit('cmdSent', command);
|
||||||
|
}.bind(this), function (error) {
|
||||||
|
this.emit('error', error);
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.emit('error', 'No server connection');
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the information about the hyperion server
|
||||||
|
*/
|
||||||
|
getServerInfo: function () {
|
||||||
|
var cmd = {command: 'serverinfo'};
|
||||||
|
|
||||||
|
this.socket.isConnected(function (connected) {
|
||||||
|
if (connected) {
|
||||||
|
this.socket.write(JSON.stringify(cmd) + '\n', function () {
|
||||||
|
this.socket.read(function (result) {
|
||||||
|
var dataobj, str = tools.ab2str(result);
|
||||||
|
dataobj = JSON.parse(str);
|
||||||
|
this.emit('serverInfo', dataobj.info);
|
||||||
|
}.bind(this), function (error) {
|
||||||
|
this.emit('error', error);
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this), function (error) {
|
||||||
|
this.emit('error', error);
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.emit('error', 'No server connection');
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
isConnecting: function () {
|
||||||
|
return this.connecting;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
150
assets/webconfig/js/app/main.js
Normal file
150
assets/webconfig/js/app/main.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*global require, requirejs */
|
||||||
|
|
||||||
|
requirejs.config({
|
||||||
|
baseUrl: 'js/app',
|
||||||
|
paths: {
|
||||||
|
'lib': '../vendor'
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
'controllers/AppController': {
|
||||||
|
'api/Socket': 'api/WebSocket'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerDownHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchstart', handler, false);
|
||||||
|
dom.addEventListener('mousedown', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.removePointerDownHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.removeEventListener('touchstart', handler, false);
|
||||||
|
dom.removeEventListener('mousedown', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerUpHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchend', handler, false);
|
||||||
|
dom.addEventListener('mouseup', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.removePointerUpHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.removeEventListener('touchend', handler, false);
|
||||||
|
dom.removeEventListener('mouseup', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerMoveHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchmove', handler, false);
|
||||||
|
dom.addEventListener('mousemove', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.removePointerMoveHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.removeEventListener('touchmove', handler, false);
|
||||||
|
dom.removeEventListener('mousemove', handler, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addClickHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
var toFire = false;
|
||||||
|
|
||||||
|
dom.addEventListener('touchstart', function (event) {
|
||||||
|
if (event.touches.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toFire = true;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('touchmove', function () {
|
||||||
|
toFire = false;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('touchend', function (event) {
|
||||||
|
var focused;
|
||||||
|
if (toFire) {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
|
||||||
|
focused = document.querySelector(':focus');
|
||||||
|
|
||||||
|
if (focused && event.target !== focused) {
|
||||||
|
focused.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.tagName !== 'INPUT') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('click', function () {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkInstallFirefoxOS() {
|
||||||
|
'use strict';
|
||||||
|
var manifest_url, installCheck;
|
||||||
|
|
||||||
|
manifest_url = [location.protocol, '//', location.host, location.pathname.replace('index.html',''), 'manifest.webapp'].join('');
|
||||||
|
installCheck = navigator.mozApps.checkInstalled(manifest_url);
|
||||||
|
|
||||||
|
installCheck.onerror = function() {
|
||||||
|
alert('Error calling checkInstalled: ' + installCheck.error.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
installCheck.onsuccess = function() {
|
||||||
|
var installLoc;
|
||||||
|
if(!installCheck.result) {
|
||||||
|
if (confirm('Do you want to install hyperion remote contorl on your device?')) {
|
||||||
|
installLoc = navigator.mozApps.install(manifest_url);
|
||||||
|
installLoc.onsuccess = function(data) {
|
||||||
|
};
|
||||||
|
installLoc.onerror = function() {
|
||||||
|
alert(installLoc.error.name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
require(['controllers/AppController'], function (AppController) {
|
||||||
|
'use strict';
|
||||||
|
var app = new AppController();
|
||||||
|
app.init();
|
||||||
|
if (navigator.mozApps && navigator.userAgent.indexOf('Mozilla/5.0 (Mobile;') !== -1) {
|
||||||
|
checkInstallFirefoxOS();
|
||||||
|
}
|
||||||
|
});
|
108
assets/webconfig/js/app/main_chrome.js
Normal file
108
assets/webconfig/js/app/main_chrome.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*global require, requirejs */
|
||||||
|
|
||||||
|
requirejs.config({
|
||||||
|
baseUrl: '../js/app',
|
||||||
|
paths: {
|
||||||
|
'lib': '../vendor'
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
'controllers/AppController': {
|
||||||
|
'api/Socket': 'api/ChromeTcpSocket',
|
||||||
|
'api/Network': 'api/ChromeNetwork'
|
||||||
|
},
|
||||||
|
'models/Settings': {
|
||||||
|
'api/LocalStorage': 'api/ChromeLocalStorage'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerDownHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchstart', function (event) {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
event.preventDefault();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('mousedown', function () {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerUpHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchend', function (event) {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
event.preventDefault();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('mouseup', function () {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addPointerMoveHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
dom.addEventListener('touchmove', function (event) {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
event.preventDefault();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('mousemove', function () {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} dom
|
||||||
|
* @param {function} handler
|
||||||
|
*/
|
||||||
|
window.addClickHandler = function (dom, handler) {
|
||||||
|
'use strict';
|
||||||
|
var toFire = false;
|
||||||
|
|
||||||
|
dom.addEventListener('touchstart', function (event) {
|
||||||
|
if (event.touches.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toFire = true;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('touchmove', function () {
|
||||||
|
toFire = false;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('touchend', function (event) {
|
||||||
|
if (toFire) {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
if (event.target.tagName !== 'INPUT') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
dom.addEventListener('click', function () {
|
||||||
|
handler.apply(this, arguments);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
require(['controllers/AppController'], function (AppController) {
|
||||||
|
'use strict';
|
||||||
|
var app = new AppController();
|
||||||
|
app.init();
|
||||||
|
});
|
124
assets/webconfig/js/app/models/Settings.js
Normal file
124
assets/webconfig/js/app/models/Settings.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*global define */
|
||||||
|
define(['lib/stapes', 'api/LocalStorage'], function (Stapes, LocalStorage) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends Settings.prototype */{
|
||||||
|
storage: null, servers: [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Settings
|
||||||
|
* @classdesc Local application settings
|
||||||
|
* @constructs
|
||||||
|
* @fires saved
|
||||||
|
* @fires loaded
|
||||||
|
* @fires error
|
||||||
|
* @fires serverAdded
|
||||||
|
* @fires serverChanged
|
||||||
|
* @fires serverRemoved
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
this.storage = new LocalStorage();
|
||||||
|
this.storage.on({
|
||||||
|
error: function (message) {
|
||||||
|
this.emit('error', message);
|
||||||
|
}, got: function (settings) {
|
||||||
|
if (settings) {
|
||||||
|
this.servers = settings.servers || [];
|
||||||
|
}
|
||||||
|
this.emit('loaded');
|
||||||
|
}, set: function () {
|
||||||
|
this.emit('saved');
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save current settings
|
||||||
|
*/
|
||||||
|
save: function () {
|
||||||
|
this.storage.set({
|
||||||
|
servers: this.servers
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads persistent settings
|
||||||
|
*/
|
||||||
|
load: function () {
|
||||||
|
this.storage.get();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server definition
|
||||||
|
* @param {object} server - Server information
|
||||||
|
*/
|
||||||
|
addServer: function (server) {
|
||||||
|
if (this.indexOfServer(server) === -1) {
|
||||||
|
if (this.servers.length === 0) {
|
||||||
|
server.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.servers.push(server);
|
||||||
|
this.save();
|
||||||
|
this.emit('serverAdded', server);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a server as a default server
|
||||||
|
* @param {number} index - Index of the server in the server list to set as default one
|
||||||
|
*/
|
||||||
|
setSelectedServer: function (index) {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < this.servers.length; i++) {
|
||||||
|
delete this.servers[i].selected;
|
||||||
|
}
|
||||||
|
this.servers[index].selected = true;
|
||||||
|
this.save();
|
||||||
|
this.emit('serverChanged', this.servers[index]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a server from the list
|
||||||
|
* @param {number} index - Index of the server in the list to remove
|
||||||
|
*/
|
||||||
|
removeServer: function (index) {
|
||||||
|
this.servers.splice(index, 1);
|
||||||
|
this.save();
|
||||||
|
this.emit('serverRemoved');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update server information
|
||||||
|
* @param {number} index - Index of the server to update
|
||||||
|
* @param {object} server - New server information
|
||||||
|
*/
|
||||||
|
updateServer: function (index, server) {
|
||||||
|
if (index >= 0 && index < this.servers.length) {
|
||||||
|
this.servers[index] = server;
|
||||||
|
this.save();
|
||||||
|
this.emit('serverChanged', server);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the server in the list.
|
||||||
|
* @param {object} server - Server to search index for
|
||||||
|
* @returns {number} - Index of the server in the list. -1 if server not found
|
||||||
|
*/
|
||||||
|
indexOfServer: function (server) {
|
||||||
|
var i, tmp;
|
||||||
|
|
||||||
|
for (i = 0; i < this.servers.length; i++) {
|
||||||
|
tmp = this.servers[i];
|
||||||
|
|
||||||
|
if (tmp.port === server.port && tmp.address === server.address) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
56
assets/webconfig/js/app/utils/Tools.js
Normal file
56
assets/webconfig/js/app/utils/Tools.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
define([], function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Convert a string to ArrayBuffer
|
||||||
|
* @param {string} str String to convert
|
||||||
|
* @returns {ArrayBuffer} Result
|
||||||
|
*/
|
||||||
|
str2ab: function (str) {
|
||||||
|
var i, buf = new ArrayBuffer(str.length), bufView = new Uint8Array(buf);
|
||||||
|
for (i = 0; i < str.length; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array to ArrayBuffer
|
||||||
|
* @param array
|
||||||
|
* @returns {ArrayBuffer} Result
|
||||||
|
*/
|
||||||
|
a2ab: function (array) {
|
||||||
|
return new Uint8Array(array).buffer;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert ArrayBuffer to string
|
||||||
|
* @param {ArrayBuffer} buffer Buffer to convert
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
ab2hexstr: function (buffer) {
|
||||||
|
var i, str = '', ua = new Uint8Array(buffer);
|
||||||
|
for (i = 0; i < ua.length; i++) {
|
||||||
|
str += this.b2hexstr(ua[i]);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert byte to hexstr.
|
||||||
|
* @param {number} byte Byte to convert
|
||||||
|
*/
|
||||||
|
b2hexstr: function (byte) {
|
||||||
|
return ('00' + byte.toString(16)).substr(-2);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} buffer
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
ab2str: function (buffer) {
|
||||||
|
return String.fromCharCode.apply(null, new Uint8Array(buffer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
64
assets/webconfig/js/app/views/EffectsView.js
Normal file
64
assets/webconfig/js/app/views/EffectsView.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* hyperion remote
|
||||||
|
* MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
define(['lib/stapes'], function (Stapes) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends EffectsView.prototype */{
|
||||||
|
/**
|
||||||
|
* @class EffectsView
|
||||||
|
* @constructs
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
this.bindEventHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bindEventHandlers: function () {
|
||||||
|
window.addClickHandler(document.querySelector('#effects ul'), function (event) {
|
||||||
|
var selected = event.target.parentNode.querySelector('.selected');
|
||||||
|
if (selected) {
|
||||||
|
selected.classList.remove('selected');
|
||||||
|
}
|
||||||
|
event.target.classList.add('selected');
|
||||||
|
this.emit('effectSelected', event.target.dataset.id);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the list
|
||||||
|
*/
|
||||||
|
clear: function () {
|
||||||
|
document.querySelector('#effects ul').innerHTML = '';
|
||||||
|
document.querySelector('#effects .info').classList.add('hidden');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the list
|
||||||
|
* @param {object} effects - Object containing effect information
|
||||||
|
*/
|
||||||
|
fillList: function (effects) {
|
||||||
|
var dom, el, i;
|
||||||
|
|
||||||
|
dom = document.createDocumentFragment();
|
||||||
|
|
||||||
|
for (i = 0; i < effects.length; i++) {
|
||||||
|
el = document.createElement('li');
|
||||||
|
el.innerHTML = effects[i].name;
|
||||||
|
el.dataset.id = effects[i].id;
|
||||||
|
dom.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#effects ul').appendChild(dom);
|
||||||
|
|
||||||
|
if (effects.length === 0) {
|
||||||
|
document.querySelector('#effects .info').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
396
assets/webconfig/js/app/views/MainView.js
Normal file
396
assets/webconfig/js/app/views/MainView.js
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
define([
|
||||||
|
'lib/stapes',
|
||||||
|
'lib/tinycolor',
|
||||||
|
'utils/Tools',
|
||||||
|
'views/Slider'
|
||||||
|
], function (Stapes, Tinycolor, tools, Slider) {
|
||||||
|
'use strict';
|
||||||
|
var timer;
|
||||||
|
|
||||||
|
function showMessageField (text, type) {
|
||||||
|
var dom, wrapper;
|
||||||
|
|
||||||
|
dom = document.querySelector('.work .msg');
|
||||||
|
|
||||||
|
if (!dom) {
|
||||||
|
dom = document.createElement('div');
|
||||||
|
dom.classList.add('msg');
|
||||||
|
dom.classList.add(type);
|
||||||
|
wrapper = document.createElement('div');
|
||||||
|
wrapper.classList.add('wrapper_msg');
|
||||||
|
wrapper.classList.add('invisible');
|
||||||
|
wrapper.appendChild(dom);
|
||||||
|
|
||||||
|
document.querySelector('.work').appendChild(wrapper);
|
||||||
|
setTimeout(function () {
|
||||||
|
wrapper.classList.remove('invisible');
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dom.classList.contains(type)) {
|
||||||
|
dom.className = 'msg';
|
||||||
|
dom.classList.add(type);
|
||||||
|
}
|
||||||
|
dom.innerHTML = text;
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
timer = setTimeout(function () {
|
||||||
|
var error = document.querySelector('.work .wrapper_msg');
|
||||||
|
if (error) {
|
||||||
|
error.parentNode.removeChild(error);
|
||||||
|
}
|
||||||
|
}, 1600);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends MainView.prototype*/{
|
||||||
|
pointer: null,
|
||||||
|
colorpicker: null,
|
||||||
|
slider: null,
|
||||||
|
sliderinput: null,
|
||||||
|
cpradius: 0,
|
||||||
|
cpcenter: 0,
|
||||||
|
drag: false,
|
||||||
|
color: null,
|
||||||
|
brightness: 1.0,
|
||||||
|
inputbox: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MainView
|
||||||
|
* @construct
|
||||||
|
* @fires barClick
|
||||||
|
* @fires colorChange
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
var ev;
|
||||||
|
this.pointer = document.querySelector('#colorpicker #pointer');
|
||||||
|
this.colorpicker = document.querySelector('#colorpicker #colorwheelbg');
|
||||||
|
this.slider = new Slider({
|
||||||
|
element: document.getElementById('brightness'),
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.02,
|
||||||
|
value: 1
|
||||||
|
});
|
||||||
|
this.inputbox = document.querySelector('#color input.value');
|
||||||
|
|
||||||
|
this.cpradius = this.colorpicker.offsetWidth / 2;
|
||||||
|
this.cpcenter = this.colorpicker.offsetLeft + this.cpradius;
|
||||||
|
|
||||||
|
this.bindEventHandlers();
|
||||||
|
|
||||||
|
ev = document.createEvent('Event');
|
||||||
|
ev.initEvent('resize', true, true);
|
||||||
|
window.dispatchEvent(ev);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bindEventHandlers: function () {
|
||||||
|
var cptouchrect;
|
||||||
|
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
var attrW, attrH, side, w = this.colorpicker.parentNode.clientWidth, h = this.colorpicker.parentNode.clientHeight;
|
||||||
|
|
||||||
|
attrW = this.colorpicker.getAttribute('width');
|
||||||
|
attrH = this.colorpicker.getAttribute('height');
|
||||||
|
side = attrW === 'auto' ? attrH : attrW;
|
||||||
|
if (w > h) {
|
||||||
|
if (attrH !== side) {
|
||||||
|
this.colorpicker.setAttribute('height', side);
|
||||||
|
this.colorpicker.setAttribute('width', 'auto');
|
||||||
|
}
|
||||||
|
} else if (attrW !== side) {
|
||||||
|
this.colorpicker.setAttribute('height', 'auto');
|
||||||
|
this.colorpicker.setAttribute('width', side);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cpradius = this.colorpicker.offsetWidth / 2;
|
||||||
|
this.cpcenter = this.colorpicker.offsetLeft + this.cpradius;
|
||||||
|
if (this.color) {
|
||||||
|
this.updatePointer();
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(document.querySelector('.footer'), function (event) {
|
||||||
|
this.emit('barClick', event.target.parentNode.dataset.area);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
|
||||||
|
this.slider.on('changeValue', function (value) {
|
||||||
|
this.brightness = value.value;
|
||||||
|
this.updateInput();
|
||||||
|
this.fireColorEvent();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.inputbox.addEventListener('input', function (event) {
|
||||||
|
var bright, rgb = new Tinycolor(event.target.value).toRgb();
|
||||||
|
|
||||||
|
if (rgb.r === 0 && rgb.g === 0 && rgb.b === 0) {
|
||||||
|
this.brightness = 0;
|
||||||
|
this.color = new Tinycolor({
|
||||||
|
r: 0xff,
|
||||||
|
g: 0xff,
|
||||||
|
b: 0xff
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bright = Math.max(rgb.r, rgb.g, rgb.b) / 256;
|
||||||
|
rgb.r = Math.round(rgb.r / bright);
|
||||||
|
rgb.g = Math.round(rgb.g / bright);
|
||||||
|
rgb.b = Math.round(rgb.b / bright);
|
||||||
|
this.brightness = bright;
|
||||||
|
this.color = new Tinycolor(rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireColorEvent();
|
||||||
|
this.updatePointer();
|
||||||
|
this.updateSlider();
|
||||||
|
}.bind(this), false);
|
||||||
|
|
||||||
|
this.inputbox.addEventListener('keydown', function (event) {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
case 16:
|
||||||
|
case 37:
|
||||||
|
case 38:
|
||||||
|
case 39:
|
||||||
|
case 40:
|
||||||
|
case 46:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (event.target.value.length >= 6 && (event.target.selectionEnd - event.target.selectionStart) === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else if (event.keyCode < 48 || event.keyCode > 71) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cptouchrect = document.querySelector('#colorpicker .touchrect');
|
||||||
|
window.addPointerDownHandler(cptouchrect, function (event) {
|
||||||
|
this.leaveInput();
|
||||||
|
this.drag = true;
|
||||||
|
this.handleEvent(event);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addPointerMoveHandler(cptouchrect, function (event) {
|
||||||
|
if (this.drag) {
|
||||||
|
this.handleEvent(event);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addPointerUpHandler(cptouchrect, function () {
|
||||||
|
this.drag = false;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(document.querySelector('#clear_button'), function () {
|
||||||
|
this.emit('clear');
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(document.querySelector('#clearall_button'), function () {
|
||||||
|
this.emit('clearall');
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
handleEvent: function (event) {
|
||||||
|
var point, x, y;
|
||||||
|
|
||||||
|
if (event.touches) {
|
||||||
|
x = event.touches[0].clientX;
|
||||||
|
y = event.touches[0].clientY;
|
||||||
|
} else {
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
}
|
||||||
|
point = this.getCirclePoint(x, y);
|
||||||
|
this.color = this.getColorFromPoint(point);
|
||||||
|
|
||||||
|
this.updatePointer();
|
||||||
|
this.updateSlider();
|
||||||
|
this.updateInput();
|
||||||
|
|
||||||
|
this.fireColorEvent();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @returns {{x: number, y: number}}
|
||||||
|
*/
|
||||||
|
getCirclePoint: function (x, y) {
|
||||||
|
var p = {
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
}, c = {
|
||||||
|
x: this.colorpicker.offsetLeft + this.cpradius,
|
||||||
|
y: this.colorpicker.offsetTop + this.cpradius
|
||||||
|
}, n;
|
||||||
|
|
||||||
|
n = Math.sqrt(Math.pow((x - c.x), 2) + Math.pow((y - c.y), 2));
|
||||||
|
|
||||||
|
if (n > this.cpradius) {
|
||||||
|
p.x = (c.x) + this.cpradius * ((x - c.x) / n);
|
||||||
|
p.y = (c.y) + this.cpradius * ((y - c.y) / n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {{x: number, y: number}} p
|
||||||
|
* @returns {Tinycolor}
|
||||||
|
*/
|
||||||
|
getColorFromPoint: function (p) {
|
||||||
|
var h, t, s, x, y;
|
||||||
|
x = p.x - this.colorpicker.offsetLeft - this.cpradius;
|
||||||
|
y = this.cpradius - p.y + this.colorpicker.offsetTop;
|
||||||
|
t = Math.atan2(y, x);
|
||||||
|
h = (t * (180 / Math.PI) + 360) % 360;
|
||||||
|
s = Math.min(Math.sqrt(x * x + y * y) / this.cpradius, 1);
|
||||||
|
|
||||||
|
return new Tinycolor({
|
||||||
|
h: h,
|
||||||
|
s: s,
|
||||||
|
v: 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param color
|
||||||
|
* @returns {{x: number, y: number}}
|
||||||
|
*/
|
||||||
|
getPointFromColor: function (color) {
|
||||||
|
var t, x, y, p = {};
|
||||||
|
|
||||||
|
t = color.h * (Math.PI / 180);
|
||||||
|
y = Math.sin(t) * this.cpradius * color.s;
|
||||||
|
x = Math.cos(t) * this.cpradius * color.s;
|
||||||
|
|
||||||
|
p.x = Math.round(x + this.colorpicker.offsetLeft + this.cpradius);
|
||||||
|
p.y = Math.round(this.cpradius - y + this.colorpicker.offsetTop);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
fireColorEvent: function () {
|
||||||
|
var rgb = this.color.toRgb();
|
||||||
|
rgb.r = Math.round(rgb.r * this.brightness);
|
||||||
|
rgb.g = Math.round(rgb.g * this.brightness);
|
||||||
|
rgb.b = Math.round(rgb.b * this.brightness);
|
||||||
|
this.emit('colorChange', rgb);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param rgb
|
||||||
|
*/
|
||||||
|
setColor: function (rgb) {
|
||||||
|
var bright;
|
||||||
|
|
||||||
|
if (rgb.r === 0 && rgb.g === 0 && rgb.b === 0) {
|
||||||
|
this.brightness = 0;
|
||||||
|
this.color = new Tinycolor({
|
||||||
|
r: 0xff,
|
||||||
|
g: 0xff,
|
||||||
|
b: 0xff
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bright = Math.max(rgb.r, rgb.g, rgb.b) / 256;
|
||||||
|
rgb.r = Math.round(rgb.r / bright);
|
||||||
|
rgb.g = Math.round(rgb.g / bright);
|
||||||
|
rgb.b = Math.round(rgb.b / bright);
|
||||||
|
this.brightness = bright;
|
||||||
|
this.color = new Tinycolor(rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatePointer();
|
||||||
|
this.updateSlider();
|
||||||
|
this.updateInput();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateSlider: function () {
|
||||||
|
this.slider.setValue(this.brightness);
|
||||||
|
this.slider.dom.style.backgroundImage = '-webkit-linear-gradient(left, #000000 0%, ' + this.color.toHexString() + ' 100%)';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updatePointer: function () {
|
||||||
|
var point = this.getPointFromColor(this.color.toHsv());
|
||||||
|
|
||||||
|
this.pointer.style.left = (point.x - this.pointer.offsetWidth / 2) + 'px';
|
||||||
|
this.pointer.style.top = (point.y - this.pointer.offsetHeight / 2) + 'px';
|
||||||
|
this.pointer.style.backgroundColor = this.color.toHexString();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateInput: function () {
|
||||||
|
var rgb = this.color.toRgb();
|
||||||
|
rgb.r = Math.round(rgb.r * this.brightness);
|
||||||
|
rgb.g = Math.round(rgb.g * this.brightness);
|
||||||
|
rgb.b = Math.round(rgb.b * this.brightness);
|
||||||
|
|
||||||
|
this.inputbox.value = tools.b2hexstr(rgb.r) + tools.b2hexstr(rgb.g) + tools.b2hexstr(rgb.b);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll to the specific tab content
|
||||||
|
* @param {string} id - Id of the tab to scroll to
|
||||||
|
*/
|
||||||
|
scrollToArea: function (id) {
|
||||||
|
var area, index;
|
||||||
|
document.querySelector('.footer .selected').classList.remove('selected');
|
||||||
|
document.querySelector('.footer .button[data-area =' + id + ']').classList.add('selected');
|
||||||
|
|
||||||
|
area = document.getElementById(id);
|
||||||
|
index = area.offsetLeft / area.clientWidth;
|
||||||
|
area.parentNode.style.left = (-index * 100) + '%';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a status message
|
||||||
|
* @param {string} message - Text to show
|
||||||
|
*/
|
||||||
|
showStatus: function (message) {
|
||||||
|
showMessageField(message, 'status');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the error text
|
||||||
|
* @param {string} error - Error message
|
||||||
|
*/
|
||||||
|
showError: function (error) {
|
||||||
|
showMessageField(error, 'error');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
leaveInput: function () {
|
||||||
|
this.inputbox.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
199
assets/webconfig/js/app/views/ServerList.js
Normal file
199
assets/webconfig/js/app/views/ServerList.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
define(['lib/stapes'], function (Stapes) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
function buildDom (params) {
|
||||||
|
var dom, label, ul, li, i, header;
|
||||||
|
|
||||||
|
dom = document.createElement('div');
|
||||||
|
dom.classList.add('grouplist');
|
||||||
|
dom.id = params.id;
|
||||||
|
|
||||||
|
header = document.createElement('div');
|
||||||
|
header.classList.add('header');
|
||||||
|
dom.appendChild(header);
|
||||||
|
label = document.createElement('div');
|
||||||
|
label.innerHTML = params.label;
|
||||||
|
label.classList.add('title');
|
||||||
|
header.appendChild(label);
|
||||||
|
label = document.createElement('div');
|
||||||
|
label.innerHTML = '';
|
||||||
|
label.classList.add('callout');
|
||||||
|
header.appendChild(label);
|
||||||
|
|
||||||
|
ul = document.createElement('ul');
|
||||||
|
dom.appendChild(ul);
|
||||||
|
if (params.list) {
|
||||||
|
for (i = 0; i < params.list.length; i++) {
|
||||||
|
li = createLine(params.list[i]);
|
||||||
|
ul.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLine (params) {
|
||||||
|
var dom, el, horiz, box, touch;
|
||||||
|
|
||||||
|
dom = document.createDocumentFragment();
|
||||||
|
|
||||||
|
horiz = document.createElement('div');
|
||||||
|
horiz.classList.add('horizontal');
|
||||||
|
dom.appendChild(horiz);
|
||||||
|
|
||||||
|
touch = document.createElement('div');
|
||||||
|
touch.classList.add('horizontal');
|
||||||
|
horiz.appendChild(touch);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('checkbox');
|
||||||
|
touch.appendChild(el);
|
||||||
|
|
||||||
|
box = document.createElement('div');
|
||||||
|
box.classList.add('titlebox');
|
||||||
|
touch.appendChild(box);
|
||||||
|
|
||||||
|
el = document.createElement('label');
|
||||||
|
el.classList.add('title');
|
||||||
|
el.innerHTML = params.title || '';
|
||||||
|
box.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('label');
|
||||||
|
el.classList.add('subtitle');
|
||||||
|
el.innerHTML = params.subtitle;
|
||||||
|
box.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('touchrect');
|
||||||
|
touch.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('edit_icon');
|
||||||
|
el.innerHTML = '';
|
||||||
|
horiz.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('delete_icon');
|
||||||
|
el.innerHTML = '';
|
||||||
|
horiz.appendChild(el);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends ServerList.prototype */{
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {HTMLElement}
|
||||||
|
*/
|
||||||
|
dom: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ServerList
|
||||||
|
* @constructs
|
||||||
|
* @param {object} params - List parameter
|
||||||
|
* @param {string} params.id - List id
|
||||||
|
* @param {string} params.label - List title
|
||||||
|
* @param {{title: string, subtitle: string, c}[]} [params.list] - List elements
|
||||||
|
*
|
||||||
|
* @fires add
|
||||||
|
* @fires remove
|
||||||
|
* @fires select
|
||||||
|
* @fires edit
|
||||||
|
*/
|
||||||
|
constructor: function (params) {
|
||||||
|
this.dom = buildDom(params || {});
|
||||||
|
this.bindEventHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bindEventHandlers: function () {
|
||||||
|
window.addClickHandler(this.dom.querySelector('.callout'), function () {
|
||||||
|
this.emit('add');
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(this.dom.querySelector('ul'), function (event) {
|
||||||
|
if (event.target.classList.contains('delete_icon')) {
|
||||||
|
this.emit('remove', event.target.parentNode.parentNode.id);
|
||||||
|
} else if (event.target.classList.contains('edit_icon')) {
|
||||||
|
this.emit('edit', event.target.parentNode.parentNode.id);
|
||||||
|
} else if (event.target.classList.contains('touchrect')) {
|
||||||
|
this.emit('select', event.target.parentNode.parentNode.parentNode.id);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DOM of the list
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
getDom: function () {
|
||||||
|
return this.dom;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a line
|
||||||
|
* @param id
|
||||||
|
* @param selected
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
append: function (id, selected, element) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
li.id = id;
|
||||||
|
if (selected) {
|
||||||
|
li.classList.add('selected');
|
||||||
|
}
|
||||||
|
li.appendChild(element);
|
||||||
|
this.dom.querySelector('ul').appendChild(li);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a line
|
||||||
|
* @param index
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
replace: function (index, element) {
|
||||||
|
var child = this.dom.querySelector('ul li:nth-child(' + (index + 1) + ')'), li;
|
||||||
|
if (child) {
|
||||||
|
li = document.createElement('li');
|
||||||
|
li.appendChild(element);
|
||||||
|
this.dom.querySelector('ul').replaceChild(li, child);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a line
|
||||||
|
* @param lineParam
|
||||||
|
*/
|
||||||
|
addLine: function (lineParam) {
|
||||||
|
var line = createLine(lineParam);
|
||||||
|
this.append(lineParam.id, lineParam.selected, line);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the list
|
||||||
|
*/
|
||||||
|
clear: function () {
|
||||||
|
this.dom.querySelector('ul').innerHTML = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide or show the Add button in the header
|
||||||
|
* @param {boolean} show - True to show, false to hide
|
||||||
|
*/
|
||||||
|
showAddButton: function (show) {
|
||||||
|
if (show) {
|
||||||
|
this.dom.querySelector('.callout').classList.remove('invisible');
|
||||||
|
} else {
|
||||||
|
this.dom.querySelector('.callout').classList.add('invisible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
259
assets/webconfig/js/app/views/SettingsView.js
Normal file
259
assets/webconfig/js/app/views/SettingsView.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
define(['lib/stapes', 'views/ServerList'], function (Stapes, ServerList) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createLabelInputLine (params) {
|
||||||
|
var dom, el;
|
||||||
|
|
||||||
|
dom = document.createElement('div');
|
||||||
|
dom.classList.add('inputline');
|
||||||
|
dom.id = params.id;
|
||||||
|
|
||||||
|
el = document.createElement('label');
|
||||||
|
el.innerHTML = params.label;
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('input');
|
||||||
|
if (typeof params.value === 'number') {
|
||||||
|
el.type = 'number';
|
||||||
|
}
|
||||||
|
el.value = params.value || '';
|
||||||
|
el.autocomplete='off';
|
||||||
|
el.autocorrect='off';
|
||||||
|
el.autocapitalize='off';
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createButtonLine (params) {
|
||||||
|
var dom, el;
|
||||||
|
|
||||||
|
dom = document.createElement('div');
|
||||||
|
dom.classList.add('inputline');
|
||||||
|
|
||||||
|
el = document.createElement('button');
|
||||||
|
el.innerHTML = params.label;
|
||||||
|
el.classList.add('OK');
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('button');
|
||||||
|
el.innerHTML = 'Cancel';
|
||||||
|
el.classList.add('CANCEL');
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDetectLine () {
|
||||||
|
var dom, el;
|
||||||
|
|
||||||
|
dom = document.createElement('div');
|
||||||
|
dom.classList.add('line');
|
||||||
|
|
||||||
|
el = document.createElement('button');
|
||||||
|
el.id = 'detect_button';
|
||||||
|
el.innerHTML = 'Detect';
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('spinner');
|
||||||
|
el.classList.add('hidden');
|
||||||
|
dom.appendChild(el);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends SettingsView.prototype */{
|
||||||
|
dom: null,
|
||||||
|
serverList: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SettingsView
|
||||||
|
* @classdesc View for the settings
|
||||||
|
* @constructs
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
var list = [], el;
|
||||||
|
|
||||||
|
this.dom = document.querySelector('#settings');
|
||||||
|
|
||||||
|
this.serverList = new ServerList({
|
||||||
|
id: 'serverList',
|
||||||
|
label: 'Server',
|
||||||
|
list: list
|
||||||
|
});
|
||||||
|
|
||||||
|
this.serverList.on({
|
||||||
|
add: function () {
|
||||||
|
var line, box;
|
||||||
|
|
||||||
|
this.enableDetectButton(false);
|
||||||
|
this.lockList(true);
|
||||||
|
|
||||||
|
box = document.createDocumentFragment();
|
||||||
|
|
||||||
|
line = createLabelInputLine({id: 'name', label: 'Name:'});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'address', label: 'Address:'});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'port', label: 'Port:', value: 19444});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'priority', label: 'Priority:', value: 50});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'duration', label: 'Duration (sec):', value: 0});
|
||||||
|
box.appendChild(line);
|
||||||
|
|
||||||
|
line = createButtonLine({label: 'Add'});
|
||||||
|
|
||||||
|
window.addClickHandler(line.firstChild, function (event) {
|
||||||
|
var server = {}, i, inputs = event.target.parentNode.parentNode.querySelectorAll('input');
|
||||||
|
|
||||||
|
for (i = 0; i < inputs.length; i++) {
|
||||||
|
server[inputs[i].parentNode.id] = inputs[i].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.port = parseInt(server.port);
|
||||||
|
server.priority = parseInt(server.priority);
|
||||||
|
server.duration = parseInt(server.duration);
|
||||||
|
|
||||||
|
this.emit('serverAdded', server);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(line.lastChild, function () {
|
||||||
|
this.emit('serverAddCanceled');
|
||||||
|
}.bind(this));
|
||||||
|
box.appendChild(line);
|
||||||
|
this.serverList.append(null, false, box);
|
||||||
|
},
|
||||||
|
select: function (id) {
|
||||||
|
if (!this.dom.classList.contains('locked')) {
|
||||||
|
this.emit('serverSelected', parseInt(id.replace('server_', '')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: function (id) {
|
||||||
|
this.emit('serverRemoved', parseInt(id.replace('server_', '')));
|
||||||
|
},
|
||||||
|
edit: function (id) {
|
||||||
|
this.emit('editServer', parseInt(id.replace('server_', '')));
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.dom.appendChild(this.serverList.getDom());
|
||||||
|
|
||||||
|
el = createDetectLine();
|
||||||
|
|
||||||
|
window.addClickHandler(el.querySelector('button'), function () {
|
||||||
|
this.emit('detect');
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.dom.appendChild(el);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the list of known servers
|
||||||
|
* @param {Array} servers
|
||||||
|
*/
|
||||||
|
fillServerList: function (servers) {
|
||||||
|
var i, server, params;
|
||||||
|
this.serverList.clear();
|
||||||
|
for (i = 0; i < servers.length; i++) {
|
||||||
|
server = servers[i];
|
||||||
|
params = {id: 'server_' + i, title: server.name, subtitle: server.address + ':' + server.port};
|
||||||
|
if (server.selected) {
|
||||||
|
params.selected = true;
|
||||||
|
}
|
||||||
|
this.serverList.addLine(params);
|
||||||
|
}
|
||||||
|
this.serverList.showAddButton(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the spinner as progress indicator
|
||||||
|
* @param {boolean} show True to show, false to hide
|
||||||
|
*/
|
||||||
|
showWaiting: function (show) {
|
||||||
|
if (show) {
|
||||||
|
this.dom.querySelector('.spinner').classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
this.dom.querySelector('.spinner').classList.add('hidden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the detect button
|
||||||
|
* @param {Boolean} enabled True to enable, false to disable
|
||||||
|
*/
|
||||||
|
enableDetectButton: function (enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
this.dom.querySelector('#detect_button').classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
this.dom.querySelector('#detect_button').classList.add('hidden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the list for editing/deleting
|
||||||
|
* @param {Boolean} lock True to lock, false to unlock
|
||||||
|
*/
|
||||||
|
lockList: function (lock) {
|
||||||
|
if (!lock) {
|
||||||
|
this.dom.classList.remove('locked');
|
||||||
|
this.serverList.showAddButton(true);
|
||||||
|
} else {
|
||||||
|
this.dom.classList.add('locked');
|
||||||
|
this.serverList.showAddButton(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
editServer: function (serverInfo) {
|
||||||
|
var line, box;
|
||||||
|
|
||||||
|
this.lockList(true);
|
||||||
|
this.enableDetectButton(false);
|
||||||
|
|
||||||
|
box = document.createDocumentFragment();
|
||||||
|
|
||||||
|
line = createLabelInputLine({id: 'name', label: 'Name:', value: serverInfo.server.name});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'address', label: 'Address:', value: serverInfo.server.address});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'port', label: 'Port:', value: serverInfo.server.port});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'priority', label: 'Priority:', value: serverInfo.server.priority});
|
||||||
|
box.appendChild(line);
|
||||||
|
line = createLabelInputLine({id: 'duration', label: 'Duration (sec):', value: serverInfo.server.duration});
|
||||||
|
box.appendChild(line);
|
||||||
|
|
||||||
|
line = createButtonLine({label: 'Done'});
|
||||||
|
|
||||||
|
window.addClickHandler(line.querySelector('button.OK'), function (event) {
|
||||||
|
var server = {}, i, inputs = event.target.parentNode.parentNode.querySelectorAll('input');
|
||||||
|
|
||||||
|
for (i = 0; i < inputs.length; i++) {
|
||||||
|
server[inputs[i].parentNode.id] = inputs[i].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.port = parseInt(server.port);
|
||||||
|
server.priority = parseInt(server.priority);
|
||||||
|
server.duration = parseInt(server.duration);
|
||||||
|
if (serverInfo.server.selected) {
|
||||||
|
server.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('serverChanged', {index: serverInfo.index, server: server});
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
window.addClickHandler(line.querySelector('button.CANCEL'), function () {
|
||||||
|
this.emit('serverEditCanceled');
|
||||||
|
}.bind(this));
|
||||||
|
box.appendChild(line);
|
||||||
|
|
||||||
|
window.addClickHandler(box.querySelector('input'), function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.serverList.replace(serverInfo.index, box);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
134
assets/webconfig/js/app/views/Slider.js
Normal file
134
assets/webconfig/js/app/views/Slider.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
define(['lib/stapes'], function (Stapes) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function syncView (slider) {
|
||||||
|
var left = (slider.value - slider.min) * 100 / (slider.max - slider.min);
|
||||||
|
slider.dom.lastElementChild.style.left = left + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEvent(event, slider) {
|
||||||
|
var x, left, ratio, value, steppedValue;
|
||||||
|
if (event.touches) {
|
||||||
|
x = event.touches[0].clientX;
|
||||||
|
} else {
|
||||||
|
x = event.clientX;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = x - slider.dom.getBoundingClientRect().left;
|
||||||
|
ratio = left / slider.dom.offsetWidth;
|
||||||
|
value = (slider.max - slider.min) * ratio;
|
||||||
|
|
||||||
|
steppedValue = (value - slider.min) % slider.step;
|
||||||
|
if (steppedValue <= slider.step / 2) {
|
||||||
|
value = value - steppedValue;
|
||||||
|
} else {
|
||||||
|
value = value + (slider.step - steppedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = Math.max(value, slider.min);
|
||||||
|
value = Math.min(value, slider.max);
|
||||||
|
slider.value = value;
|
||||||
|
slider.emit('changeValue', {
|
||||||
|
'value': value,
|
||||||
|
'target': slider.dom
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends Slider.prototype */{
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Element}
|
||||||
|
*/
|
||||||
|
dom: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
min: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
max: 100,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
value: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
step: 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Slider
|
||||||
|
* @constructs
|
||||||
|
* @fires change
|
||||||
|
*/
|
||||||
|
constructor: function (params) {
|
||||||
|
this.dom = params.element;
|
||||||
|
this.setValue(params.value);
|
||||||
|
this.setMin(params.min);
|
||||||
|
this.setMax(params.max);
|
||||||
|
this.setStep(params.step);
|
||||||
|
|
||||||
|
this.bindEventHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bindEventHandlers: function () {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
function pointerMoveEventHandler(event) {
|
||||||
|
if (that.drag) {
|
||||||
|
handleEvent(event, that);
|
||||||
|
syncView(that);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerUpEventHandler() {
|
||||||
|
that.drag = false;
|
||||||
|
syncView(that);
|
||||||
|
window.removePointerMoveHandler(document, pointerMoveEventHandler);
|
||||||
|
window.removePointerUpHandler(document, pointerUpEventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addPointerDownHandler(this.dom, function (event) {
|
||||||
|
this.drag = true;
|
||||||
|
handleEvent(event, this);
|
||||||
|
syncView(this);
|
||||||
|
window.addPointerMoveHandler(document, pointerMoveEventHandler);
|
||||||
|
window.addPointerUpHandler(document, pointerUpEventHandler);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
setValue: function(value) {
|
||||||
|
this.value = value || 0;
|
||||||
|
syncView(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
setMin: function(value) {
|
||||||
|
this.min = value || 0;
|
||||||
|
syncView(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
setMax: function(value) {
|
||||||
|
this.max = value || 100;
|
||||||
|
syncView(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
setStep: function(value) {
|
||||||
|
this.step = value || 1;
|
||||||
|
syncView(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
375
assets/webconfig/js/app/views/TransformView.js
Normal file
375
assets/webconfig/js/app/views/TransformView.js
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
/**
|
||||||
|
* hyperion remote
|
||||||
|
* MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'lib/stapes',
|
||||||
|
'views/Slider'
|
||||||
|
], function (Stapes, Slider) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function onHeaderClick (event) {
|
||||||
|
var list = event.target.parentNode.parentNode.querySelector('ul');
|
||||||
|
|
||||||
|
if (list.clientHeight === 0) {
|
||||||
|
list.style.maxHeight = list.scrollHeight + 'px';
|
||||||
|
event.target.parentNode.parentNode.setAttribute('collapsed', 'false');
|
||||||
|
} else {
|
||||||
|
list.style.maxHeight = 0;
|
||||||
|
event.target.parentNode.parentNode.setAttribute('collapsed', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLine (id, type, icon, caption, value, min, max) {
|
||||||
|
var dom, el, el2, label, wrapper;
|
||||||
|
|
||||||
|
dom = document.createElement('li');
|
||||||
|
dom.className = type;
|
||||||
|
|
||||||
|
label = document.createElement('label');
|
||||||
|
label.innerHTML = caption;
|
||||||
|
dom.appendChild(label);
|
||||||
|
|
||||||
|
wrapper = document.createElement('div');
|
||||||
|
wrapper.classList.add('wrapper');
|
||||||
|
wrapper.id = id;
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('icon');
|
||||||
|
el.innerHTML = icon;
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.classList.add('slider');
|
||||||
|
el2 = document.createElement('div');
|
||||||
|
el2.classList.add('track');
|
||||||
|
el.appendChild(el2);
|
||||||
|
el2 = document.createElement('div');
|
||||||
|
el2.classList.add('thumb');
|
||||||
|
el.appendChild(el2);
|
||||||
|
el.dataset.min = min;
|
||||||
|
el.dataset.max = max;
|
||||||
|
el.dataset.value = value;
|
||||||
|
el.dataset.step = 0.01;
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
|
el = document.createElement('input');
|
||||||
|
el.classList.add('value');
|
||||||
|
el.type = 'number';
|
||||||
|
el.min = min;
|
||||||
|
el.max = max;
|
||||||
|
el.step = 0.01;
|
||||||
|
el.value = parseFloat(Math.round(value * 100) / 100).toFixed(2);
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
|
dom.appendChild(wrapper);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGroup (groupInfo) {
|
||||||
|
var group, node, subnode, i, member;
|
||||||
|
group = document.createElement('div');
|
||||||
|
group.classList.add('group');
|
||||||
|
if (groupInfo.collapsed) {
|
||||||
|
group.setAttribute('collapsed', 'true');
|
||||||
|
}
|
||||||
|
group.id = groupInfo.id;
|
||||||
|
|
||||||
|
node = document.createElement('div');
|
||||||
|
node.classList.add('header');
|
||||||
|
group.appendChild(node);
|
||||||
|
subnode = document.createElement('label');
|
||||||
|
subnode.innerHTML = groupInfo.title;
|
||||||
|
node.appendChild(subnode);
|
||||||
|
subnode = document.createElement('label');
|
||||||
|
subnode.innerHTML = groupInfo.subtitle;
|
||||||
|
node.appendChild(subnode);
|
||||||
|
|
||||||
|
node = document.createElement('ul');
|
||||||
|
group.appendChild(node);
|
||||||
|
for (i = 0; i < groupInfo.members.length; i++) {
|
||||||
|
member = groupInfo.members[i];
|
||||||
|
subnode = createLine(member.id, member.type, member.icon, member.label, member.value, member.min,
|
||||||
|
member.max);
|
||||||
|
node.appendChild(subnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stapes.subclass(/** @lends TransformView.prototype */{
|
||||||
|
sliders: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class TransformView
|
||||||
|
* @constructs
|
||||||
|
*/
|
||||||
|
constructor: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the list
|
||||||
|
*/
|
||||||
|
clear: function () {
|
||||||
|
document.querySelector('#transform .values').innerHTML = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param change
|
||||||
|
*/
|
||||||
|
onSliderChange: function (event) {
|
||||||
|
var data = {}, idparts, value;
|
||||||
|
|
||||||
|
idparts = event.target.parentNode.id.split('_');
|
||||||
|
value = parseFloat(Math.round(parseFloat(event.value) * 100) / 100);
|
||||||
|
|
||||||
|
event.target.parentNode.querySelector('.value').value = value.toFixed(2);
|
||||||
|
|
||||||
|
data[idparts[1]] = value;
|
||||||
|
this.emit(idparts[0], data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param change
|
||||||
|
*/
|
||||||
|
onValueChange: function (event) {
|
||||||
|
var data = {}, idparts, value;
|
||||||
|
|
||||||
|
idparts = event.target.parentNode.id.split('_');
|
||||||
|
value = parseFloat(Math.round(parseFloat(event.target.value) * 100) / 100);
|
||||||
|
|
||||||
|
if (parseFloat(event.target.value) < parseFloat(event.target.min)) {
|
||||||
|
event.target.value = event.target.min;
|
||||||
|
} else if (parseFloat(event.target.value) > parseFloat(event.target.max)) {
|
||||||
|
event.target.value = event.target.max;
|
||||||
|
}
|
||||||
|
this.sliders[event.target.parentNode.id].setValue(value);
|
||||||
|
|
||||||
|
data[idparts[1]] = value;
|
||||||
|
this.emit(idparts[0], data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fill the list
|
||||||
|
* @param {object} transform - Object containing transform information
|
||||||
|
*/
|
||||||
|
fillList: function (transform) {
|
||||||
|
var dom, group, els, i, slider;
|
||||||
|
|
||||||
|
if (!transform) {
|
||||||
|
document.querySelector('#transform .info').classList.remove('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dom = document.createDocumentFragment();
|
||||||
|
|
||||||
|
group = createGroup({
|
||||||
|
collapsed: true,
|
||||||
|
id: 'HSV',
|
||||||
|
title: 'HSV',
|
||||||
|
subtitle: 'HSV color corrections',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'hsv_saturationGain',
|
||||||
|
type: 'saturation',
|
||||||
|
icon: '',
|
||||||
|
label: 'Saturation gain',
|
||||||
|
value: transform.saturationGain,
|
||||||
|
min: 0,
|
||||||
|
max: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hsv_valueGain',
|
||||||
|
type: 'value',
|
||||||
|
icon: '',
|
||||||
|
label: 'Value gain',
|
||||||
|
value: transform.valueGain,
|
||||||
|
min: 0,
|
||||||
|
max: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
dom.appendChild(group);
|
||||||
|
|
||||||
|
group = createGroup({
|
||||||
|
collapsed: true,
|
||||||
|
id: 'Gamma',
|
||||||
|
title: 'Gamma',
|
||||||
|
subtitle: 'Gamma correction',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'gamma_r',
|
||||||
|
type: 'red',
|
||||||
|
icon: '',
|
||||||
|
label: 'Red',
|
||||||
|
value: transform.gamma[0],
|
||||||
|
min: 0,
|
||||||
|
max: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gamma_g',
|
||||||
|
type: 'green',
|
||||||
|
icon: '',
|
||||||
|
label: 'Green',
|
||||||
|
value: transform.gamma[1],
|
||||||
|
min: 0,
|
||||||
|
max: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gamma_b',
|
||||||
|
type: 'blue',
|
||||||
|
icon: '',
|
||||||
|
label: 'Blue',
|
||||||
|
value: transform.gamma[2],
|
||||||
|
min: 0,
|
||||||
|
max: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
dom.appendChild(group);
|
||||||
|
|
||||||
|
group = createGroup({
|
||||||
|
collapsed: true,
|
||||||
|
id: 'Whitelevel',
|
||||||
|
title: 'Whitelevel',
|
||||||
|
subtitle: 'Value when RGB channel is fully on',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'whitelevel_r',
|
||||||
|
type: 'red',
|
||||||
|
icon: '',
|
||||||
|
label: 'Red',
|
||||||
|
value: transform.whitelevel[0],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'whitelevel_g',
|
||||||
|
type: 'green',
|
||||||
|
icon: '',
|
||||||
|
label: 'Green',
|
||||||
|
value: transform.whitelevel[1],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'whitelevel_b',
|
||||||
|
type: 'blue',
|
||||||
|
icon: '',
|
||||||
|
label: 'Blue',
|
||||||
|
value: transform.whitelevel[2],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
dom.appendChild(group);
|
||||||
|
|
||||||
|
group = createGroup({
|
||||||
|
collapsed: true,
|
||||||
|
id: 'Blacklevel',
|
||||||
|
title: 'Blacklevel',
|
||||||
|
subtitle: 'Value when RGB channel is fully off',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'blacklevel_r',
|
||||||
|
type: 'red',
|
||||||
|
icon: '',
|
||||||
|
label: 'Red',
|
||||||
|
value: transform.blacklevel[0],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'blacklevel_g',
|
||||||
|
type: 'green',
|
||||||
|
icon: '',
|
||||||
|
label: 'Green',
|
||||||
|
value: transform.blacklevel[1],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'blacklevel_b',
|
||||||
|
type: 'blue',
|
||||||
|
icon: '',
|
||||||
|
label: 'Blue',
|
||||||
|
value: transform.blacklevel[2],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
dom.appendChild(group);
|
||||||
|
|
||||||
|
group = createGroup({
|
||||||
|
collapsed: true,
|
||||||
|
id: 'Threshold',
|
||||||
|
title: 'Threshold',
|
||||||
|
subtitle: 'Threshold for a channel',
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
id: 'threshold_r',
|
||||||
|
type: 'red',
|
||||||
|
icon: '',
|
||||||
|
label: 'Red',
|
||||||
|
value: transform.threshold[0],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'threshold_g',
|
||||||
|
type: 'green',
|
||||||
|
icon: '',
|
||||||
|
label: 'Green',
|
||||||
|
value: transform.threshold[1],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'threshold_b',
|
||||||
|
type: 'blue',
|
||||||
|
icon: '',
|
||||||
|
label: 'Blue',
|
||||||
|
value: transform.threshold[2],
|
||||||
|
min: 0,
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
dom.appendChild(group);
|
||||||
|
|
||||||
|
els = dom.querySelectorAll('.slider');
|
||||||
|
for (i = 0; i < els.length; i++) {
|
||||||
|
slider = new Slider({
|
||||||
|
element: els[i],
|
||||||
|
min: els[i].dataset.min,
|
||||||
|
max: els[i].dataset.max,
|
||||||
|
step: els[i].dataset.step,
|
||||||
|
value: els[i].dataset.value
|
||||||
|
});
|
||||||
|
slider.on('changeValue', this.onSliderChange, this);
|
||||||
|
this.sliders[els[i].parentNode.id] = slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
els = dom.querySelectorAll('input');
|
||||||
|
for (i = 0; i < els.length; i++) {
|
||||||
|
els[i].addEventListener('input', this.onValueChange.bind(this), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
els = dom.querySelectorAll('.header');
|
||||||
|
for (i = 0; i < els.length; i++) {
|
||||||
|
window.addClickHandler(els[i], onHeaderClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#transform .info').classList.add('hidden');
|
||||||
|
document.querySelector('#transform .values').appendChild(dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
17
assets/webconfig/js/background.js
Normal file
17
assets/webconfig/js/background.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*global chrome */
|
||||||
|
chrome.app.runtime.onLaunched.addListener(function () {
|
||||||
|
'use strict';
|
||||||
|
chrome.app.window.create('index.html', {
|
||||||
|
'id': 'fakeIdForSingleton',
|
||||||
|
'innerBounds': {
|
||||||
|
'width': 320,
|
||||||
|
'height': 480,
|
||||||
|
'minWidth': 320,
|
||||||
|
'minHeight': 480
|
||||||
|
},
|
||||||
|
resizable: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
2054
assets/webconfig/js/vendor/require.js
vendored
Normal file
2054
assets/webconfig/js/vendor/require.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
594
assets/webconfig/js/vendor/stapes.js
vendored
Normal file
594
assets/webconfig/js/vendor/stapes.js
vendored
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
//
|
||||||
|
// ____ _ _
|
||||||
|
// / ___|| |_ __ _ _ __ ___ ___ (_)___ (*)
|
||||||
|
// \___ \| __/ _` | '_ \ / _ \/ __| | / __|
|
||||||
|
// ___) | || (_| | |_) | __/\__ \_ | \__ \
|
||||||
|
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
|
||||||
|
// |_| |__/
|
||||||
|
//
|
||||||
|
// (*) a (really) tiny Javascript MVC microframework
|
||||||
|
//
|
||||||
|
// (c) Hay Kranen < hay@bykr.org >
|
||||||
|
// Released under the terms of the MIT license
|
||||||
|
// < http://en.wikipedia.org/wiki/MIT_License >
|
||||||
|
//
|
||||||
|
// Stapes.js : http://hay.github.com/stapes
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var VERSION = "0.8.0";
|
||||||
|
|
||||||
|
// Global counter for all events in all modules (including mixed in objects)
|
||||||
|
var guid = 1;
|
||||||
|
|
||||||
|
// Makes _.create() faster
|
||||||
|
if (!Object.create) {
|
||||||
|
var CachedFunction = function(){};
|
||||||
|
}
|
||||||
|
|
||||||
|
// So we can use slice.call for arguments later on
|
||||||
|
var slice = Array.prototype.slice;
|
||||||
|
|
||||||
|
// Private attributes and helper functions, stored in an object so they
|
||||||
|
// are overwritable by plugins
|
||||||
|
var _ = {
|
||||||
|
// Properties
|
||||||
|
attributes : {},
|
||||||
|
|
||||||
|
eventHandlers : {
|
||||||
|
"-1" : {} // '-1' is used for the global event handling
|
||||||
|
},
|
||||||
|
|
||||||
|
guid : -1,
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
addEvent : function(event) {
|
||||||
|
// If we don't have any handlers for this type of event, add a new
|
||||||
|
// array we can use to push new handlers
|
||||||
|
if (!_.eventHandlers[event.guid][event.type]) {
|
||||||
|
_.eventHandlers[event.guid][event.type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push an event object
|
||||||
|
_.eventHandlers[event.guid][event.type].push({
|
||||||
|
"guid" : event.guid,
|
||||||
|
"handler" : event.handler,
|
||||||
|
"scope" : event.scope,
|
||||||
|
"type" : event.type
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
|
||||||
|
var eventMap = {},
|
||||||
|
scope;
|
||||||
|
|
||||||
|
if (typeof argTypeOrMap === "string") {
|
||||||
|
scope = argScope || false;
|
||||||
|
eventMap[ argTypeOrMap ] = argHandlerOrScope;
|
||||||
|
} else {
|
||||||
|
scope = argHandlerOrScope || false;
|
||||||
|
eventMap = argTypeOrMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var eventString in eventMap) {
|
||||||
|
var handler = eventMap[eventString];
|
||||||
|
var events = eventString.split(" ");
|
||||||
|
|
||||||
|
for (var i = 0, l = events.length; i < l; i++) {
|
||||||
|
var eventType = events[i];
|
||||||
|
_.addEvent.call(this, {
|
||||||
|
"guid" : this._guid || this._.guid,
|
||||||
|
"handler" : handler,
|
||||||
|
"scope" : scope,
|
||||||
|
"type" : eventType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addGuid : function(object, forceGuid) {
|
||||||
|
if (object._guid && !forceGuid) return;
|
||||||
|
|
||||||
|
object._guid = guid++;
|
||||||
|
|
||||||
|
_.attributes[object._guid] = {};
|
||||||
|
_.eventHandlers[object._guid] = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
// This is a really small utility function to save typing and produce
|
||||||
|
// better optimized code
|
||||||
|
attr : function(guid) {
|
||||||
|
return _.attributes[guid];
|
||||||
|
},
|
||||||
|
|
||||||
|
clone : function(obj) {
|
||||||
|
var type = _.typeOf(obj);
|
||||||
|
|
||||||
|
if (type === 'object') {
|
||||||
|
return _.extend({}, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'array') {
|
||||||
|
return obj.slice(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create : function(proto) {
|
||||||
|
if (Object.create) {
|
||||||
|
return Object.create(proto);
|
||||||
|
} else {
|
||||||
|
CachedFunction.prototype = proto;
|
||||||
|
return new CachedFunction();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createSubclass : function(props, includeEvents) {
|
||||||
|
props = props || {};
|
||||||
|
includeEvents = includeEvents || false;
|
||||||
|
|
||||||
|
var superclass = props.superclass.prototype;
|
||||||
|
|
||||||
|
// Objects always have a constructor, so we need to be sure this is
|
||||||
|
// a property instead of something from the prototype
|
||||||
|
var realConstructor = props.hasOwnProperty('constructor') ? props.constructor : function(){};
|
||||||
|
|
||||||
|
function constructor() {
|
||||||
|
// Be kind to people forgetting new
|
||||||
|
if (!(this instanceof constructor)) {
|
||||||
|
throw new Error("Please use 'new' when initializing Stapes classes");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this class has events add a GUID as well
|
||||||
|
if (this.on) {
|
||||||
|
_.addGuid( this, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
realConstructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeEvents) {
|
||||||
|
_.extend(superclass, Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.prototype = _.create(superclass);
|
||||||
|
constructor.prototype.constructor = constructor;
|
||||||
|
|
||||||
|
_.extend(constructor, {
|
||||||
|
extend : function() {
|
||||||
|
return _.extendThis.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
// We can't call this 'super' because that's a reserved keyword
|
||||||
|
// and fails in IE8
|
||||||
|
'parent' : superclass,
|
||||||
|
|
||||||
|
proto : function() {
|
||||||
|
return _.extendThis.apply(this.prototype, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
subclass : function(obj) {
|
||||||
|
obj = obj || {};
|
||||||
|
obj.superclass = this;
|
||||||
|
return _.createSubclass(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy all props given in the definition to the prototype
|
||||||
|
for (var key in props) {
|
||||||
|
if (key !== 'constructor' && key !== 'superclass') {
|
||||||
|
constructor.prototype[key] = props[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return constructor;
|
||||||
|
},
|
||||||
|
|
||||||
|
emitEvents : function(type, data, explicitType, explicitGuid) {
|
||||||
|
explicitType = explicitType || false;
|
||||||
|
explicitGuid = explicitGuid || this._guid;
|
||||||
|
|
||||||
|
// #30: make a local copy of handlers to prevent problems with
|
||||||
|
// unbinding the event while unwinding the loop
|
||||||
|
var handlers = slice.call(_.eventHandlers[explicitGuid][type]);
|
||||||
|
|
||||||
|
for (var i = 0, l = handlers.length; i < l; i++) {
|
||||||
|
// Clone the event to prevent issue #19
|
||||||
|
var event = _.extend({}, handlers[i]);
|
||||||
|
var scope = (event.scope) ? event.scope : this;
|
||||||
|
|
||||||
|
if (explicitType) {
|
||||||
|
event.type = explicitType;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.scope = scope;
|
||||||
|
event.handler.call(event.scope, data, event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Extend an object with more objects
|
||||||
|
extend : function() {
|
||||||
|
var args = slice.call(arguments);
|
||||||
|
var object = args.shift();
|
||||||
|
|
||||||
|
for (var i = 0, l = args.length; i < l; i++) {
|
||||||
|
var props = args[i];
|
||||||
|
for (var key in props) {
|
||||||
|
object[key] = props[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
},
|
||||||
|
|
||||||
|
// The same as extend, but uses the this value as the scope
|
||||||
|
extendThis : function() {
|
||||||
|
var args = slice.call(arguments);
|
||||||
|
args.unshift(this);
|
||||||
|
return _.extend.apply(this, args);
|
||||||
|
},
|
||||||
|
|
||||||
|
// from http://stackoverflow.com/a/2117523/152809
|
||||||
|
makeUuid : function() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAttribute : function(keys, silent) {
|
||||||
|
silent = silent || false;
|
||||||
|
|
||||||
|
// Split the key, maybe we want to remove more than one item
|
||||||
|
var attributes = _.trim(keys).split(" ");
|
||||||
|
|
||||||
|
// Actually delete the item
|
||||||
|
for (var i = 0, l = attributes.length; i < l; i++) {
|
||||||
|
var key = _.trim(attributes[i]);
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
delete _.attr(this._guid)[key];
|
||||||
|
|
||||||
|
// If 'silent' is set, do not throw any events
|
||||||
|
if (!silent) {
|
||||||
|
this.emit('change', key);
|
||||||
|
this.emit('change:' + key);
|
||||||
|
this.emit('remove', key);
|
||||||
|
this.emit('remove:' + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeEventHandler : function(type, handler) {
|
||||||
|
var handlers = _.eventHandlers[this._guid];
|
||||||
|
|
||||||
|
if (type && handler) {
|
||||||
|
// Remove a specific handler
|
||||||
|
handlers = handlers[type];
|
||||||
|
if (!handlers) return;
|
||||||
|
|
||||||
|
for (var i = 0, l = handlers.length, h; i < l; i++) {
|
||||||
|
h = handlers[i].handler;
|
||||||
|
if (h && h === handler) {
|
||||||
|
handlers.splice(i--, 1);
|
||||||
|
l--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type) {
|
||||||
|
// Remove all handlers for a specific type
|
||||||
|
delete handlers[type];
|
||||||
|
} else {
|
||||||
|
// Remove all handlers for this module
|
||||||
|
_.eventHandlers[this._guid] = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setAttribute : function(key, value, silent) {
|
||||||
|
silent = silent || false;
|
||||||
|
|
||||||
|
// We need to do this before we actually add the item :)
|
||||||
|
var itemExists = this.has(key);
|
||||||
|
var oldValue = _.attr(this._guid)[key];
|
||||||
|
|
||||||
|
// Is the value different than the oldValue? If not, ignore this call
|
||||||
|
if (value === oldValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually add the item to the attributes
|
||||||
|
_.attr(this._guid)[key] = value;
|
||||||
|
|
||||||
|
// If 'silent' flag is set, do not throw any events
|
||||||
|
if (silent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw a generic event
|
||||||
|
this.emit('change', key);
|
||||||
|
|
||||||
|
// And a namespaced event as well, NOTE that we pass value instead of
|
||||||
|
// key here!
|
||||||
|
this.emit('change:' + key, value);
|
||||||
|
|
||||||
|
// Throw namespaced and non-namespaced 'mutate' events as well with
|
||||||
|
// the old value data as well and some extra metadata such as the key
|
||||||
|
var mutateData = {
|
||||||
|
"key" : key,
|
||||||
|
"newValue" : value,
|
||||||
|
"oldValue" : oldValue || null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.emit('mutate', mutateData);
|
||||||
|
this.emit('mutate:' + key, mutateData);
|
||||||
|
|
||||||
|
// Also throw a specific event for this type of set
|
||||||
|
var specificEvent = itemExists ? 'update' : 'create';
|
||||||
|
|
||||||
|
this.emit(specificEvent, key);
|
||||||
|
|
||||||
|
// And a namespaced event as well, NOTE that we pass value instead of key
|
||||||
|
this.emit(specificEvent + ':' + key, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
trim : function(str) {
|
||||||
|
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
typeOf : function(val) {
|
||||||
|
if (val === null || typeof val === "undefined") {
|
||||||
|
// This is a special exception for IE, in other browsers the
|
||||||
|
// method below works all the time
|
||||||
|
return String(val);
|
||||||
|
} else {
|
||||||
|
return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAttribute : function(key, fn, silent) {
|
||||||
|
var item = this.get(key);
|
||||||
|
|
||||||
|
// In previous versions of Stapes we didn't have the check for object,
|
||||||
|
// but still this worked. In 0.7.0 it suddenly doesn't work anymore and
|
||||||
|
// we need the check. Why? I have no clue.
|
||||||
|
var type = _.typeOf(item);
|
||||||
|
|
||||||
|
if (type === 'object' || type === 'array') {
|
||||||
|
item = _.clone(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newValue = fn.call(this, item, key);
|
||||||
|
_.setAttribute.call(this, key, newValue, silent || false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Can be mixed in later using Stapes.mixinEvents(object);
|
||||||
|
var Events = {
|
||||||
|
emit : function(types, data) {
|
||||||
|
data = (typeof data === "undefined") ? null : data;
|
||||||
|
|
||||||
|
var splittedTypes = types.split(" ");
|
||||||
|
|
||||||
|
for (var i = 0, l = splittedTypes.length; i < l; i++) {
|
||||||
|
var type = splittedTypes[i];
|
||||||
|
|
||||||
|
// First 'all' type events: is there an 'all' handler in the
|
||||||
|
// global stack?
|
||||||
|
if (_.eventHandlers[-1].all) {
|
||||||
|
_.emitEvents.call(this, "all", data, type, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch all events for this type?
|
||||||
|
if (_.eventHandlers[-1][type]) {
|
||||||
|
_.emitEvents.call(this, type, data, type, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this._guid === 'number') {
|
||||||
|
// 'all' event for this specific module?
|
||||||
|
if (_.eventHandlers[this._guid].all) {
|
||||||
|
_.emitEvents.call(this, "all", data, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, normal events :)
|
||||||
|
if (_.eventHandlers[this._guid][type]) {
|
||||||
|
_.emitEvents.call(this, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
off : function() {
|
||||||
|
_.removeEventHandler.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
on : function() {
|
||||||
|
_.addEventHandler.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_.Module = function() {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
_.Module.prototype = {
|
||||||
|
each : function(fn, ctx) {
|
||||||
|
var attr = _.attr(this._guid);
|
||||||
|
for (var key in attr) {
|
||||||
|
var value = attr[key];
|
||||||
|
fn.call(ctx || this, value, key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
extend : function() {
|
||||||
|
return _.extendThis.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
filter : function(fn) {
|
||||||
|
var filtered = [];
|
||||||
|
var attributes = _.attr(this._guid);
|
||||||
|
|
||||||
|
for (var key in attributes) {
|
||||||
|
if ( fn.call(this, attributes[key], key)) {
|
||||||
|
filtered.push( attributes[key] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
},
|
||||||
|
|
||||||
|
get : function(input) {
|
||||||
|
if (typeof input === "string") {
|
||||||
|
return this.has(input) ? _.attr(this._guid)[input] : null;
|
||||||
|
} else if (typeof input === "function") {
|
||||||
|
var items = this.filter(input);
|
||||||
|
return (items.length) ? items[0] : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getAll : function() {
|
||||||
|
return _.clone( _.attr(this._guid) );
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllAsArray : function() {
|
||||||
|
var arr = [];
|
||||||
|
var attributes = _.attr(this._guid);
|
||||||
|
|
||||||
|
for (var key in attributes) {
|
||||||
|
var value = attributes[key];
|
||||||
|
|
||||||
|
if (_.typeOf(value) === "object" && !value.id) {
|
||||||
|
value.id = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
|
||||||
|
has : function(key) {
|
||||||
|
return (typeof _.attr(this._guid)[key] !== "undefined");
|
||||||
|
},
|
||||||
|
|
||||||
|
map : function(fn, ctx) {
|
||||||
|
var mapped = [];
|
||||||
|
this.each(function(value, key) {
|
||||||
|
mapped.push( fn.call(ctx || this, value, key) );
|
||||||
|
}, ctx || this);
|
||||||
|
return mapped;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Akin to set(), but makes a unique id
|
||||||
|
push : function(input, silent) {
|
||||||
|
if (_.typeOf(input) === "array") {
|
||||||
|
for (var i = 0, l = input.length; i < l; i++) {
|
||||||
|
_.setAttribute.call(this, _.makeUuid(), input[i], silent || false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_.setAttribute.call(this, _.makeUuid(), input, silent || false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
remove : function(input, silent) {
|
||||||
|
if (typeof input === 'undefined') {
|
||||||
|
// With no arguments, remove deletes all attributes
|
||||||
|
_.attributes[this._guid] = {};
|
||||||
|
this.emit('change remove');
|
||||||
|
} else if (typeof input === "function") {
|
||||||
|
this.each(function(item, key) {
|
||||||
|
if (input(item)) {
|
||||||
|
_.removeAttribute.call(this, key, silent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// nb: checking for exists happens in removeAttribute
|
||||||
|
_.removeAttribute.call(this, input, silent || false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
set : function(objOrKey, valueOrSilent, silent) {
|
||||||
|
if (typeof objOrKey === "object") {
|
||||||
|
for (var key in objOrKey) {
|
||||||
|
_.setAttribute.call(this, key, objOrKey[key], valueOrSilent || false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_.setAttribute.call(this, objOrKey, valueOrSilent, silent || false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
size : function() {
|
||||||
|
var size = 0;
|
||||||
|
var attr = _.attr(this._guid);
|
||||||
|
|
||||||
|
for (var key in attr) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
|
||||||
|
update : function(keyOrFn, fn, silent) {
|
||||||
|
if (typeof keyOrFn === "string") {
|
||||||
|
_.updateAttribute.call(this, keyOrFn, fn, silent || false);
|
||||||
|
} else if (typeof keyOrFn === "function") {
|
||||||
|
this.each(function(value, key) {
|
||||||
|
_.updateAttribute.call(this, key, keyOrFn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var Stapes = {
|
||||||
|
"_" : _, // private helper functions and properties
|
||||||
|
|
||||||
|
"extend" : function() {
|
||||||
|
return _.extendThis.apply(_.Module.prototype, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
"mixinEvents" : function(obj) {
|
||||||
|
obj = obj || {};
|
||||||
|
|
||||||
|
_.addGuid(obj);
|
||||||
|
|
||||||
|
return _.extend(obj, Events);
|
||||||
|
},
|
||||||
|
|
||||||
|
"on" : function() {
|
||||||
|
_.addEventHandler.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
"subclass" : function(obj, classOnly) {
|
||||||
|
classOnly = classOnly || false;
|
||||||
|
obj = obj || {};
|
||||||
|
obj.superclass = classOnly ? function(){} : _.Module;
|
||||||
|
return _.createSubclass(obj, !classOnly);
|
||||||
|
},
|
||||||
|
|
||||||
|
"version" : VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
// This library can be used as an AMD module, a Node.js module, or an
|
||||||
|
// old fashioned global
|
||||||
|
if (typeof exports !== "undefined") {
|
||||||
|
// Server
|
||||||
|
if (typeof module !== "undefined" && module.exports) {
|
||||||
|
exports = module.exports = Stapes;
|
||||||
|
}
|
||||||
|
exports.Stapes = Stapes;
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
// AMD
|
||||||
|
define(function() {
|
||||||
|
return Stapes;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Global scope
|
||||||
|
window.Stapes = Stapes;
|
||||||
|
}
|
||||||
|
})();
|
1107
assets/webconfig/js/vendor/tinycolor.js
vendored
Normal file
1107
assets/webconfig/js/vendor/tinycolor.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
assets/webconfig/manifest.json
Normal file
32
assets/webconfig/manifest.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "hyperion remote control",
|
||||||
|
"short_name": "hyperion remote",
|
||||||
|
"description": "Client side app for controlling the hyperion server over the local network.",
|
||||||
|
"author": {
|
||||||
|
"name": "Daniel Wiese",
|
||||||
|
"email": "gamadril.dev@gmail.com"
|
||||||
|
},
|
||||||
|
"manifest_version": 2,
|
||||||
|
"version": "0.6.0",
|
||||||
|
"minimum_chrome_version": "36",
|
||||||
|
"permissions": [
|
||||||
|
"storage", "system.network"
|
||||||
|
],
|
||||||
|
"app": {
|
||||||
|
"background": {
|
||||||
|
"scripts": ["js/background.js"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sockets": {
|
||||||
|
"tcp": {
|
||||||
|
"connect": "*"
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"send": "*",
|
||||||
|
"bind": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"128": "res/icon_128.png"
|
||||||
|
}
|
||||||
|
}
|
14
assets/webconfig/manifest.webapp
Normal file
14
assets/webconfig/manifest.webapp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "hyperion remote control",
|
||||||
|
"description": "Client side app for controlling the hyperion server over the local network.",
|
||||||
|
"launch_path": "/hyperion-remote/index.html",
|
||||||
|
"icons": {
|
||||||
|
"128": "/hyperion-remote/res/icon_128.png"
|
||||||
|
},
|
||||||
|
"developer": {
|
||||||
|
"name": "Daniel Wiese",
|
||||||
|
"email": "gamadril.dev@gmail.com",
|
||||||
|
"url": "https://github.com/Gamadril"
|
||||||
|
},
|
||||||
|
"default_locale": "en"
|
||||||
|
}
|
BIN
assets/webconfig/res/colorwheel.png
Normal file
BIN
assets/webconfig/res/colorwheel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 487 KiB |
BIN
assets/webconfig/res/fontello.ttf
Normal file
BIN
assets/webconfig/res/fontello.ttf
Normal file
Binary file not shown.
BIN
assets/webconfig/res/fontello.woff
Normal file
BIN
assets/webconfig/res/fontello.woff
Normal file
Binary file not shown.
BIN
assets/webconfig/res/icon_128.png
Normal file
BIN
assets/webconfig/res/icon_128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@ -20,7 +20,7 @@ install_file()
|
|||||||
echo "--- hyperion ambilight postinstall ---"
|
echo "--- hyperion ambilight postinstall ---"
|
||||||
echo "- install configuration template"
|
echo "- install configuration template"
|
||||||
mkdir -p /etc/hyperion
|
mkdir -p /etc/hyperion
|
||||||
install_file /usr/share/hyperion/config/hyperion.config.json /etc/hyperion/hyperion.config.json
|
install_file /usr/share/hyperion/config/hyperion.config.json.example /etc/hyperion/hyperion.config.json
|
||||||
|
|
||||||
|
|
||||||
HYPERION_RUNNING=false
|
HYPERION_RUNNING=false
|
||||||
|
@ -154,19 +154,23 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// The configuration of the effect engine, contains the following items:
|
/// The configuration of the effect engine, contains the following items:
|
||||||
/// * paths : An array with absolute location(s) of directories with effects
|
/// * paths : An array with absolute/relative location(s) of directories with effects
|
||||||
/// * color : Set static color after boot -> set effect to "" (empty) and input the values [R,G,B] and set duration_ms NOT to 0 (use 1) instead
|
|
||||||
/// * effect : The effect selected as 'boot sequence'
|
|
||||||
/// * duration_ms : The duration of the selected effect (0=endless)
|
|
||||||
/// * priority : The priority of the selected effect/static color (default=990) HINT: lower value result in HIGHER priority!
|
|
||||||
"effects" :
|
"effects" :
|
||||||
{
|
{
|
||||||
"paths" :
|
"paths" :
|
||||||
[
|
[
|
||||||
"/opt/hyperion/effects"
|
"/usr/share/hyperion/effects"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Boot sequence configuration. Start effect / set color at startup of hyperion
|
||||||
|
/// HINT inital background color is not shown, when any other grabber is active
|
||||||
|
/// * color : Set initial background color on startup -> set effect to "" (empty) and input the values [R,G,B] and set duration_ms NOT to 0 (use 1) instead
|
||||||
|
/// * effect : The effect is shown when hyperion starts
|
||||||
|
/// * duration_ms : The duration of the selected effect (0=endless)
|
||||||
|
/// * priority : The priority of the selected effect/initial background color (default=990, if duration is 0)
|
||||||
|
/// when duration > 0 => priority is set to 0, otherwise priority is set to configured value
|
||||||
|
/// HINT: lower value result in HIGHER priority!
|
||||||
"bootsequence" :
|
"bootsequence" :
|
||||||
{
|
{
|
||||||
"color" : [0,0,0],
|
"color" : [0,0,0],
|
||||||
@ -175,11 +179,22 @@
|
|||||||
"priority" : 990
|
"priority" : 990
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Configuration of webserver integrated in hyperion.
|
||||||
|
/// * enable : enable the server or not
|
||||||
|
/// * document_root : path to hyperion webapp files
|
||||||
|
/// * port : the port where hyperion webapp is accasible
|
||||||
|
"webConfig" :
|
||||||
|
{
|
||||||
|
"enable" : true,
|
||||||
|
"document_root" : "/usr/share/hyperion/webconfig",
|
||||||
|
"port" : 8080
|
||||||
|
},
|
||||||
|
|
||||||
/// The configuration of the Json/Proto forwarder. Forward messages to multiple instances of Hyperion on same and/or other hosts
|
/// The configuration of the Json/Proto forwarder. Forward messages to multiple instances of Hyperion on same and/or other hosts
|
||||||
/// 'proto' is mostly used for video streams and 'json' for effects
|
/// 'proto' is mostly used for video streams and 'json' for effects
|
||||||
/// * proto : Proto server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19447"] or more instances to forward ["127.0.0.1:19447","192.168.0.24:19449"]
|
/// * proto : Proto server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19447"] or more instances to forward ["127.0.0.1:19447","192.168.0.24:19449"]
|
||||||
/// * json : Json server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19446"] or more instances to forward ["127.0.0.1:19446","192.168.0.24:19448"]
|
/// * json : Json server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19446"] or more instances to forward ["127.0.0.1:19446","192.168.0.24:19448"]
|
||||||
/// HINT: If you redirect to "127.0.0.1" (localhost) you could start a second hyperion with another device/led config!
|
/// HINT:If you redirect to "127.0.0.1" (localhost) you could start a second hyperion with another device/led config!
|
||||||
/// Be sure your client(s) is/are listening on the configured ports. The second Hyperion (if used) also needs to be configured! (HyperCon -> External -> Json Server/Proto Server)
|
/// Be sure your client(s) is/are listening on the configured ports. The second Hyperion (if used) also needs to be configured! (HyperCon -> External -> Json Server/Proto Server)
|
||||||
"forwarder" :
|
"forwarder" :
|
||||||
{
|
{
|
||||||
@ -240,7 +255,7 @@
|
|||||||
|
|
||||||
/// The configuration of the boblight server which enables the boblight remote interface
|
/// The configuration of the boblight server which enables the boblight remote interface
|
||||||
/// * port : Port at which the boblight server is started
|
/// * port : Port at which the boblight server is started
|
||||||
/// * priority: Priority of the boblight server (Default=900) HINT: lower value result in HIGHER priority!
|
/// * priority : Priority of the boblight server (Default=900) HINT: lower value result in HIGHER priority!
|
||||||
"boblightServer" :
|
"boblightServer" :
|
||||||
{
|
{
|
||||||
"port" : 19333,
|
"port" : 19333,
|
||||||
|
27
include/webconfig/WebConfig.h
Normal file
27
include/webconfig/WebConfig.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef WEBCONFIG_H
|
||||||
|
#define WEBCONFIG_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class StaticFileServing;
|
||||||
|
|
||||||
|
class WebConfig : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WebConfig (std::string baseUrl, quint16 port, QObject * parent = NULL);
|
||||||
|
virtual ~WebConfig (void);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QObject * _parent;
|
||||||
|
QString _baseUrl;
|
||||||
|
quint16 _port;
|
||||||
|
StaticFileServing * _server;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WEBCONFIG_H
|
||||||
|
|
@ -19,3 +19,7 @@ add_subdirectory(utils)
|
|||||||
add_subdirectory(xbmcvideochecker)
|
add_subdirectory(xbmcvideochecker)
|
||||||
add_subdirectory(effectengine)
|
add_subdirectory(effectengine)
|
||||||
add_subdirectory(grabber)
|
add_subdirectory(grabber)
|
||||||
|
|
||||||
|
if(ENABLE_QT5)
|
||||||
|
add_subdirectory(webconfig)
|
||||||
|
endif()
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "hyperion/ImageProcessorFactory.h"
|
#include "hyperion/ImageProcessorFactory.h"
|
||||||
#include "hyperion/ImageProcessor.h"
|
#include "hyperion/ImageProcessor.h"
|
||||||
#include "utils/ColorRgb.h"
|
#include "utils/ColorRgb.h"
|
||||||
|
#include "HyperionConfig.h"
|
||||||
|
|
||||||
// project includes
|
// project includes
|
||||||
#include "BoblightClientConnection.h"
|
#include "BoblightClientConnection.h"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
// effect engine includes
|
// effect engine includes
|
||||||
#include <effectengine/EffectEngine.h>
|
#include <effectengine/EffectEngine.h>
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
#include "HyperionConfig.h"
|
||||||
|
|
||||||
EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectConfig) :
|
EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectConfig) :
|
||||||
_hyperion(hyperion),
|
_hyperion(hyperion),
|
||||||
|
53
libsrc/webconfig/CMakeLists.txt
Normal file
53
libsrc/webconfig/CMakeLists.txt
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
# Define the current source locations
|
||||||
|
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webconfig)
|
||||||
|
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webconfig)
|
||||||
|
|
||||||
|
# Group the headers that go through the MOC compiler
|
||||||
|
set(WebConfig_QT_HEADERS
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpClientWrapper.h
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpHeader.h
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpReply.h
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpRequest.h
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpServer.h
|
||||||
|
${CURRENT_SOURCE_DIR}/StaticFileServing.h
|
||||||
|
${CURRENT_HEADER_DIR}/WebConfig.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(WebConfig_HEADERS
|
||||||
|
)
|
||||||
|
|
||||||
|
set(WebConfig_SOURCES
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpClientWrapper.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpHeader.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpReply.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpRequest.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/QtHttpServer.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/StaticFileServing.cpp
|
||||||
|
${CURRENT_SOURCE_DIR}/WebConfig.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(ENABLE_QT5)
|
||||||
|
qt5_wrap_cpp(WebConfig_HEADERS_MOC ${WebConfig_QT_HEADERS})
|
||||||
|
else()
|
||||||
|
qt4_wrap_cpp(WebConfigr_HEADERS_MOC ${WebConfig_QT_HEADERS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(webconfig
|
||||||
|
${WebConfig_HEADERS}
|
||||||
|
${WebConfig_QT_HEADERS}
|
||||||
|
${WebConfig_SOURCES}
|
||||||
|
${WebConfig_HEADERS_MOC}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(ENABLE_QT5)
|
||||||
|
qt5_use_modules(webconfig Widgets Network)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(webconfig
|
||||||
|
hyperion
|
||||||
|
hyperion-utils
|
||||||
|
${QT_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
221
libsrc/webconfig/QtHttpClientWrapper.cpp
Normal file
221
libsrc/webconfig/QtHttpClientWrapper.cpp
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
|
||||||
|
#include "QtHttpClientWrapper.h"
|
||||||
|
#include "QtHttpRequest.h"
|
||||||
|
#include "QtHttpReply.h"
|
||||||
|
#include "QtHttpServer.h"
|
||||||
|
#include "QtHttpHeader.h"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QStringBuilder>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
|
||||||
|
|
||||||
|
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
|
||||||
|
: QObject (parent)
|
||||||
|
, m_guid ("")
|
||||||
|
, m_parsingStatus (AwaitingRequest)
|
||||||
|
, m_sockClient (sock)
|
||||||
|
, m_currentRequest (Q_NULLPTR)
|
||||||
|
, m_serverHandle (parent)
|
||||||
|
{
|
||||||
|
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtHttpClientWrapper::getGuid (void) {
|
||||||
|
if (m_guid.isEmpty ()) {
|
||||||
|
m_guid = QString::fromLocal8Bit (
|
||||||
|
QCryptographicHash::hash (
|
||||||
|
QByteArray::number ((quint64) (this)),
|
||||||
|
QCryptographicHash::Md5
|
||||||
|
).toHex ()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return m_guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||||
|
if (m_sockClient != Q_NULLPTR) {
|
||||||
|
while (m_sockClient->bytesAvailable ()) {
|
||||||
|
QByteArray line = m_sockClient->readLine ();
|
||||||
|
switch (m_parsingStatus) { // handle parsing steps
|
||||||
|
case AwaitingRequest: { // "command url version" × 1
|
||||||
|
QString str = QString::fromUtf8 (line).trimmed ();
|
||||||
|
QStringList parts = str.split (SPACE, QString::SkipEmptyParts);
|
||||||
|
if (parts.size () == 3) {
|
||||||
|
QString command = parts.at (0);
|
||||||
|
QString url = parts.at (1);
|
||||||
|
QString version = parts.at (2);
|
||||||
|
if (version == QtHttpServer::HTTP_VERSION) {
|
||||||
|
//qDebug () << "Debug : HTTP"
|
||||||
|
// << "command :" << command
|
||||||
|
// << "url :" << url
|
||||||
|
// << "version :" << version;
|
||||||
|
m_currentRequest = new QtHttpRequest (m_serverHandle);
|
||||||
|
m_currentRequest->setUrl (QUrl (url));
|
||||||
|
m_currentRequest->setCommand (command);
|
||||||
|
m_parsingStatus = AwaitingHeaders;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parsingStatus = ParsingError;
|
||||||
|
//qWarning () << "Error : unhandled HTTP version :" << version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parsingStatus = ParsingError;
|
||||||
|
//qWarning () << "Error : incorrect HTTP command line :" << line;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AwaitingHeaders: { // "header: value" × N (until empty line)
|
||||||
|
QByteArray raw = line.trimmed ();
|
||||||
|
if (!raw.isEmpty ()) { // parse headers
|
||||||
|
int pos = raw.indexOf (COLON);
|
||||||
|
if (pos > 0) {
|
||||||
|
QByteArray header = raw.left (pos).trimmed ();
|
||||||
|
QByteArray value = raw.mid (pos +1).trimmed ();
|
||||||
|
//qDebug () << "Debug : HTTP"
|
||||||
|
// << "header :" << header
|
||||||
|
// << "value :" << value;
|
||||||
|
m_currentRequest->addHeader (header, value);
|
||||||
|
if (header == QtHttpHeader::ContentLength) {
|
||||||
|
int len = -1;
|
||||||
|
bool ok = false;
|
||||||
|
len = value.toInt (&ok, 10);
|
||||||
|
if (ok) {
|
||||||
|
m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parsingStatus = ParsingError;
|
||||||
|
qWarning () << "Error : incorrect HTTP headers line :" << line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // end of headers
|
||||||
|
//qDebug () << "Debug : HTTP end of headers";
|
||||||
|
if (m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt () > 0) {
|
||||||
|
m_parsingStatus = AwaitingContent;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parsingStatus = RequestParsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AwaitingContent: { // raw data × N (until EOF ??)
|
||||||
|
m_currentRequest->appendRawData (line);
|
||||||
|
//qDebug () << "Debug : HTTP"
|
||||||
|
// << "content :" << m_currentRequest->getRawData ().toHex ()
|
||||||
|
// << "size :" << m_currentRequest->getRawData ().size ();
|
||||||
|
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ()) {
|
||||||
|
//qDebug () << "Debug : HTTP end of content";
|
||||||
|
m_parsingStatus = RequestParsed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: { break; }
|
||||||
|
}
|
||||||
|
switch (m_parsingStatus) { // handle parsing status end/error
|
||||||
|
case RequestParsed: { // a valid request has ben fully parsed
|
||||||
|
QtHttpReply reply (m_serverHandle);
|
||||||
|
connect (&reply, &QtHttpReply::requestSendHeaders,
|
||||||
|
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
|
||||||
|
connect (&reply, &QtHttpReply::requestSendData,
|
||||||
|
this, &QtHttpClientWrapper::onReplySendDataRequested);
|
||||||
|
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
|
||||||
|
m_parsingStatus = sendReplyToClient (&reply);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ParsingError: { // there was an error durin one of parsing steps
|
||||||
|
m_sockClient->readAll (); // clear remaining buffer to ignore content
|
||||||
|
QtHttpReply reply (m_serverHandle);
|
||||||
|
reply.setStatusCode (QtHttpReply::BadRequest);
|
||||||
|
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
|
||||||
|
reply.appendRawData (CRLF);
|
||||||
|
m_parsingStatus = sendReplyToClient (&reply);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpClientWrapper::onReplySendHeadersRequested (void) {
|
||||||
|
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
|
||||||
|
if (reply != Q_NULLPTR) {
|
||||||
|
QByteArray data;
|
||||||
|
// HTTP Version + Status Code + Status Msg
|
||||||
|
data.append (QtHttpServer::HTTP_VERSION);
|
||||||
|
data.append (SPACE);
|
||||||
|
data.append (QByteArray::number (reply->getStatusCode ()));
|
||||||
|
data.append (SPACE);
|
||||||
|
data.append (QtHttpReply::getStatusTextForCode (reply->getStatusCode ()));
|
||||||
|
data.append (CRLF);
|
||||||
|
// Header name: header value
|
||||||
|
if (reply->useChunked ()) {
|
||||||
|
static const QByteArray & CHUNKED = QByteArrayLiteral ("chunked");
|
||||||
|
reply->addHeader (QtHttpHeader::TransferEncoding, CHUNKED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply->addHeader (QtHttpHeader::ContentLength, QByteArray::number (reply->getRawDataSize ()));
|
||||||
|
}
|
||||||
|
const QList<QByteArray> & headersList = reply->getHeadersList ();
|
||||||
|
foreach (const QByteArray & header, headersList) {
|
||||||
|
data.append (header);
|
||||||
|
data.append (COLON);
|
||||||
|
data.append (SPACE);
|
||||||
|
data.append (reply->getHeader (header));
|
||||||
|
data.append (CRLF);
|
||||||
|
}
|
||||||
|
// empty line
|
||||||
|
data.append (CRLF);
|
||||||
|
m_sockClient->write (data);
|
||||||
|
m_sockClient->flush ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpClientWrapper::onReplySendDataRequested (void) {
|
||||||
|
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
|
||||||
|
if (reply != Q_NULLPTR) {
|
||||||
|
// content raw data
|
||||||
|
QByteArray data = reply->getRawData ();
|
||||||
|
if (reply->useChunked ()) {
|
||||||
|
data.prepend (QByteArray::number (data.size (), 16) % CRLF);
|
||||||
|
data.append (CRLF);
|
||||||
|
reply->resetRawData ();
|
||||||
|
}
|
||||||
|
// write to socket
|
||||||
|
m_sockClient->write (data);
|
||||||
|
m_sockClient->flush ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHttpReply * reply) {
|
||||||
|
if (reply != Q_NULLPTR) {
|
||||||
|
if (!reply->useChunked ()) {
|
||||||
|
reply->appendRawData (CRLF);
|
||||||
|
// send all headers and all data in one shot
|
||||||
|
reply->requestSendHeaders ();
|
||||||
|
reply->requestSendData ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// last chunk
|
||||||
|
m_sockClient->write ("0" % CRLF % CRLF);
|
||||||
|
m_sockClient->flush ();
|
||||||
|
}
|
||||||
|
if (m_currentRequest != Q_NULLPTR) {
|
||||||
|
static const QByteArray & CLOSE = QByteArrayLiteral ("close");
|
||||||
|
if (m_currentRequest->getHeader (QtHttpHeader::Connection).toLower () == CLOSE) {
|
||||||
|
// must close connection after this request
|
||||||
|
m_sockClient->close ();
|
||||||
|
}
|
||||||
|
m_currentRequest->deleteLater ();
|
||||||
|
m_currentRequest = Q_NULLPTR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AwaitingRequest;
|
||||||
|
}
|
51
libsrc/webconfig/QtHttpClientWrapper.h
Normal file
51
libsrc/webconfig/QtHttpClientWrapper.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef QTHTTPCLIENTWRAPPER_H
|
||||||
|
#define QTHTTPCLIENTWRAPPER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class QTcpSocket;
|
||||||
|
|
||||||
|
class QtHttpRequest;
|
||||||
|
class QtHttpReply;
|
||||||
|
class QtHttpServer;
|
||||||
|
|
||||||
|
class QtHttpClientWrapper : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent);
|
||||||
|
|
||||||
|
static const char SPACE = ' ';
|
||||||
|
static const char COLON = ':';
|
||||||
|
static const QByteArray & CRLF;
|
||||||
|
|
||||||
|
enum ParsingStatus {
|
||||||
|
ParsingError = -1,
|
||||||
|
AwaitingRequest = 0,
|
||||||
|
AwaitingHeaders = 1,
|
||||||
|
AwaitingContent = 2,
|
||||||
|
RequestParsed = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
QString getGuid (void);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onClientDataReceived (void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ParsingStatus sendReplyToClient (QtHttpReply * reply);
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void onReplySendHeadersRequested (void);
|
||||||
|
void onReplySendDataRequested (void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_guid;
|
||||||
|
ParsingStatus m_parsingStatus;
|
||||||
|
QTcpSocket * m_sockClient;
|
||||||
|
QtHttpRequest * m_currentRequest;
|
||||||
|
QtHttpServer * m_serverHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTHTTPCLIENTWRAPPER_H
|
32
libsrc/webconfig/QtHttpHeader.cpp
Normal file
32
libsrc/webconfig/QtHttpHeader.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
#include "QtHttpHeader.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
const QByteArray & QtHttpHeader::Server = QByteArrayLiteral ("Server");
|
||||||
|
const QByteArray & QtHttpHeader::Date = QByteArrayLiteral ("Date");
|
||||||
|
const QByteArray & QtHttpHeader::Host = QByteArrayLiteral ("Host");
|
||||||
|
const QByteArray & QtHttpHeader::Accept = QByteArrayLiteral ("Accept");
|
||||||
|
const QByteArray & QtHttpHeader::Cookie = QByteArrayLiteral ("Cookie");
|
||||||
|
const QByteArray & QtHttpHeader::ContentType = QByteArrayLiteral ("Content-Type");
|
||||||
|
const QByteArray & QtHttpHeader::ContentLength = QByteArrayLiteral ("Content-Length");
|
||||||
|
const QByteArray & QtHttpHeader::Connection = QByteArrayLiteral ("Connection");
|
||||||
|
const QByteArray & QtHttpHeader::UserAgent = QByteArrayLiteral ("User-Agent");
|
||||||
|
const QByteArray & QtHttpHeader::AcceptCharset = QByteArrayLiteral ("Accept-Charset");
|
||||||
|
const QByteArray & QtHttpHeader::AcceptEncoding = QByteArrayLiteral ("Accept-Encoding");
|
||||||
|
const QByteArray & QtHttpHeader::AcceptLanguage = QByteArrayLiteral ("Accept-Language");
|
||||||
|
const QByteArray & QtHttpHeader::Authorization = QByteArrayLiteral ("Authorization");
|
||||||
|
const QByteArray & QtHttpHeader::CacheControl = QByteArrayLiteral ("Cache-Control");
|
||||||
|
const QByteArray & QtHttpHeader::ContentMD5 = QByteArrayLiteral ("Content-MD5");
|
||||||
|
const QByteArray & QtHttpHeader::ProxyAuthorization = QByteArrayLiteral ("Proxy-Authorization");
|
||||||
|
const QByteArray & QtHttpHeader::Range = QByteArrayLiteral ("Range");
|
||||||
|
const QByteArray & QtHttpHeader::ContentEncoding = QByteArrayLiteral ("Content-Encoding");
|
||||||
|
const QByteArray & QtHttpHeader::ContentLanguage = QByteArrayLiteral ("Content-Language");
|
||||||
|
const QByteArray & QtHttpHeader::ContentLocation = QByteArrayLiteral ("Content-Location");
|
||||||
|
const QByteArray & QtHttpHeader::ContentRange = QByteArrayLiteral ("Content-Range");
|
||||||
|
const QByteArray & QtHttpHeader::Expires = QByteArrayLiteral ("Expires");
|
||||||
|
const QByteArray & QtHttpHeader::LastModified = QByteArrayLiteral ("Last-Modified");
|
||||||
|
const QByteArray & QtHttpHeader::Location = QByteArrayLiteral ("Location");
|
||||||
|
const QByteArray & QtHttpHeader::SetCookie = QByteArrayLiteral ("Set-Cookie");
|
||||||
|
const QByteArray & QtHttpHeader::TransferEncoding = QByteArrayLiteral ("Transfer-Encoding");
|
||||||
|
const QByteArray & QtHttpHeader::ContentDisposition = QByteArrayLiteral ("Content-Disposition");
|
37
libsrc/webconfig/QtHttpHeader.h
Normal file
37
libsrc/webconfig/QtHttpHeader.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef QTHTTPHEADER_H
|
||||||
|
#define QTHTTPHEADER_H
|
||||||
|
|
||||||
|
class QByteArray;
|
||||||
|
|
||||||
|
class QtHttpHeader {
|
||||||
|
public:
|
||||||
|
static const QByteArray & Server;
|
||||||
|
static const QByteArray & Date;
|
||||||
|
static const QByteArray & Host;
|
||||||
|
static const QByteArray & Accept;
|
||||||
|
static const QByteArray & ContentType;
|
||||||
|
static const QByteArray & ContentLength;
|
||||||
|
static const QByteArray & Connection;
|
||||||
|
static const QByteArray & Cookie;
|
||||||
|
static const QByteArray & UserAgent;
|
||||||
|
static const QByteArray & AcceptCharset;
|
||||||
|
static const QByteArray & AcceptEncoding;
|
||||||
|
static const QByteArray & AcceptLanguage;
|
||||||
|
static const QByteArray & Authorization;
|
||||||
|
static const QByteArray & CacheControl;
|
||||||
|
static const QByteArray & ContentMD5;
|
||||||
|
static const QByteArray & ProxyAuthorization;
|
||||||
|
static const QByteArray & Range;
|
||||||
|
static const QByteArray & ContentEncoding;
|
||||||
|
static const QByteArray & ContentLanguage;
|
||||||
|
static const QByteArray & ContentLocation;
|
||||||
|
static const QByteArray & ContentRange;
|
||||||
|
static const QByteArray & Expires;
|
||||||
|
static const QByteArray & LastModified;
|
||||||
|
static const QByteArray & Location;
|
||||||
|
static const QByteArray & SetCookie;
|
||||||
|
static const QByteArray & TransferEncoding;
|
||||||
|
static const QByteArray & ContentDisposition;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTHTTPHEADER_H
|
75
libsrc/webconfig/QtHttpReply.cpp
Normal file
75
libsrc/webconfig/QtHttpReply.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
#include "QtHttpReply.h"
|
||||||
|
#include "QtHttpHeader.h"
|
||||||
|
#include "QtHttpServer.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
QtHttpReply::QtHttpReply (QtHttpServer * parent)
|
||||||
|
: QObject (parent)
|
||||||
|
, m_useChunked (false)
|
||||||
|
, m_statusCode (Ok)
|
||||||
|
, m_data (QByteArray ())
|
||||||
|
, m_serverHandle (parent)
|
||||||
|
{
|
||||||
|
// set some additional headers
|
||||||
|
addHeader (QtHttpHeader::Date, QDateTime::currentDateTimeUtc ().toString ("ddd, dd MMM yyyy hh:mm:ss t").toUtf8 ());
|
||||||
|
addHeader (QtHttpHeader::Server, m_serverHandle->getServerName ().toUtf8 ());
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtHttpReply::getRawDataSize (void) const {
|
||||||
|
return m_data.size ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtHttpReply::useChunked (void) const {
|
||||||
|
return m_useChunked;
|
||||||
|
}
|
||||||
|
|
||||||
|
QtHttpReply::StatusCode QtHttpReply::getStatusCode (void) const {
|
||||||
|
return m_statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray QtHttpReply::getRawData (void) const {
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> QtHttpReply::getHeadersList (void) const {
|
||||||
|
return m_headersHash.keys ();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray QtHttpReply::getHeader (const QByteArray & header) const {
|
||||||
|
return m_headersHash.value (header, QByteArray ());
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray QtHttpReply::getStatusTextForCode (QtHttpReply::StatusCode statusCode) {
|
||||||
|
switch (statusCode) {
|
||||||
|
case Ok: return QByteArrayLiteral ("OK.");
|
||||||
|
case BadRequest: return QByteArrayLiteral ("Bad request !");
|
||||||
|
case Forbidden: return QByteArrayLiteral ("Forbidden !");
|
||||||
|
case NotFound: return QByteArrayLiteral ("Not found !");
|
||||||
|
default: return QByteArrayLiteral ("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpReply::setUseChunked (bool chunked){
|
||||||
|
m_useChunked = chunked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpReply::setStatusCode (QtHttpReply::StatusCode statusCode) {
|
||||||
|
m_statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpReply::appendRawData (const QByteArray & data) {
|
||||||
|
m_data.append (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpReply::addHeader (const QByteArray & header, const QByteArray & value) {
|
||||||
|
QByteArray key = header.trimmed ();
|
||||||
|
if (!key.isEmpty ()) {
|
||||||
|
m_headersHash.insert (key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpReply::resetRawData (void) {
|
||||||
|
m_data.clear ();
|
||||||
|
}
|
55
libsrc/webconfig/QtHttpReply.h
Normal file
55
libsrc/webconfig/QtHttpReply.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#ifndef QTHTTPREPLY_H
|
||||||
|
#define QTHTTPREPLY_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
class QtHttpServer;
|
||||||
|
|
||||||
|
class QtHttpReply : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_ENUMS (StatusCode)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtHttpReply (QtHttpServer * parent);
|
||||||
|
|
||||||
|
enum StatusCode {
|
||||||
|
Ok = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
InternalError = 502,
|
||||||
|
};
|
||||||
|
|
||||||
|
int getRawDataSize (void) const;
|
||||||
|
bool useChunked (void) const;
|
||||||
|
StatusCode getStatusCode (void) const;
|
||||||
|
QByteArray getRawData (void) const;
|
||||||
|
QList<QByteArray> getHeadersList (void) const;
|
||||||
|
|
||||||
|
QByteArray getHeader (const QByteArray & header) const;
|
||||||
|
|
||||||
|
static const QByteArray getStatusTextForCode (StatusCode statusCode);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setUseChunked (bool chunked = false);
|
||||||
|
void setStatusCode (StatusCode statusCode);
|
||||||
|
void appendRawData (const QByteArray & data);
|
||||||
|
void addHeader (const QByteArray & header, const QByteArray & value);
|
||||||
|
void resetRawData (void);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestSendHeaders (void);
|
||||||
|
void requestSendData (void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_useChunked;
|
||||||
|
StatusCode m_statusCode;
|
||||||
|
QByteArray m_data;
|
||||||
|
QtHttpServer * m_serverHandle;
|
||||||
|
QHash<QByteArray, QByteArray> m_headersHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTHTTPREPLY_H
|
60
libsrc/webconfig/QtHttpRequest.cpp
Normal file
60
libsrc/webconfig/QtHttpRequest.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
#include "QtHttpRequest.h"
|
||||||
|
#include "QtHttpHeader.h"
|
||||||
|
#include "QtHttpServer.h"
|
||||||
|
|
||||||
|
QtHttpRequest::QtHttpRequest (QtHttpServer * parent)
|
||||||
|
: QObject (parent)
|
||||||
|
, m_url (QUrl ())
|
||||||
|
, m_command (QString ())
|
||||||
|
, m_data (QByteArray ())
|
||||||
|
, m_serverHandle (parent)
|
||||||
|
{
|
||||||
|
// set some additional headers
|
||||||
|
addHeader (QtHttpHeader::ContentLength, QByteArrayLiteral ("0"));
|
||||||
|
addHeader (QtHttpHeader::Connection, QByteArrayLiteral ("Keep-Alive"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl QtHttpRequest::getUrl (void) const {
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtHttpRequest::getCommand (void) const {
|
||||||
|
return m_command;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QtHttpRequest::getRawDataSize (void) const {
|
||||||
|
return m_data.size ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray QtHttpRequest::getRawData (void) const {
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> QtHttpRequest::getHeadersList (void) const {
|
||||||
|
return m_headersHash.keys ();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray QtHttpRequest::getHeader (const QByteArray & header) const {
|
||||||
|
return m_headersHash.value (header, QByteArray ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpRequest::setUrl (const QUrl & url) {
|
||||||
|
m_url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpRequest::setCommand (const QString & command) {
|
||||||
|
m_command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpRequest::addHeader (const QByteArray & header, const QByteArray & value) {
|
||||||
|
QByteArray key = header.trimmed ();
|
||||||
|
if (!key.isEmpty ()) {
|
||||||
|
m_headersHash.insert (key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpRequest::appendRawData (const QByteArray & data) {
|
||||||
|
m_data.append (data);
|
||||||
|
}
|
40
libsrc/webconfig/QtHttpRequest.h
Normal file
40
libsrc/webconfig/QtHttpRequest.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef QTHTTPREQUEST_H
|
||||||
|
#define QTHTTPREQUEST_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
class QtHttpServer;
|
||||||
|
|
||||||
|
class QtHttpRequest : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtHttpRequest (QtHttpServer * parent);
|
||||||
|
|
||||||
|
int getRawDataSize (void) const;
|
||||||
|
QUrl getUrl (void) const;
|
||||||
|
QString getCommand (void) const;
|
||||||
|
QByteArray getRawData (void) const;
|
||||||
|
QList<QByteArray> getHeadersList (void) const;
|
||||||
|
|
||||||
|
QByteArray getHeader (const QByteArray & header) const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setUrl (const QUrl & url);
|
||||||
|
void setCommand (const QString & command);
|
||||||
|
void addHeader (const QByteArray & header, const QByteArray & value);
|
||||||
|
void appendRawData (const QByteArray & data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUrl m_url;
|
||||||
|
QString m_command;
|
||||||
|
QByteArray m_data;
|
||||||
|
QtHttpServer * m_serverHandle;
|
||||||
|
QHash<QByteArray, QByteArray> m_headersHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTHTTPREQUEST_H
|
66
libsrc/webconfig/QtHttpServer.cpp
Normal file
66
libsrc/webconfig/QtHttpServer.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
#include "QtHttpServer.h"
|
||||||
|
#include "QtHttpRequest.h"
|
||||||
|
#include "QtHttpReply.h"
|
||||||
|
#include "QtHttpClientWrapper.h"
|
||||||
|
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
|
||||||
|
|
||||||
|
QtHttpServer::QtHttpServer (QObject * parent)
|
||||||
|
: QObject (parent)
|
||||||
|
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
|
||||||
|
{
|
||||||
|
m_sockServer = new QTcpServer (this);
|
||||||
|
connect (m_sockServer, &QTcpServer::newConnection, this, &QtHttpServer::onClientConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString QtHttpServer::getServerName (void) const {
|
||||||
|
return m_serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpServer::start (quint16 port) {
|
||||||
|
if (m_sockServer->listen (QHostAddress::Any, port)) {
|
||||||
|
emit started (m_sockServer->serverPort ());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit error (m_sockServer->errorString ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpServer::stop (void) {
|
||||||
|
if (m_sockServer->isListening ()) {
|
||||||
|
m_sockServer->close ();
|
||||||
|
emit stopped ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpServer::setServerName (const QString & serverName) {
|
||||||
|
m_serverName = serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpServer::onClientConnected (void) {
|
||||||
|
while (m_sockServer->hasPendingConnections ()) {
|
||||||
|
QTcpSocket * sockClient = m_sockServer->nextPendingConnection ();
|
||||||
|
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sockClient, this);
|
||||||
|
connect (sockClient, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
|
||||||
|
m_socksClientsHash.insert (sockClient, wrapper);
|
||||||
|
emit clientConnected (wrapper->getGuid ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHttpServer::onClientDisconnected (void) {
|
||||||
|
QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ());
|
||||||
|
if (sockClient) {
|
||||||
|
QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR);
|
||||||
|
if (wrapper) {
|
||||||
|
emit clientDisconnected (wrapper->getGuid ());
|
||||||
|
wrapper->deleteLater ();
|
||||||
|
m_socksClientsHash.remove (sockClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
libsrc/webconfig/QtHttpServer.h
Normal file
48
libsrc/webconfig/QtHttpServer.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef QTHTTPSERVER_H
|
||||||
|
#define QTHTTPSERVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
class QTcpSocket;
|
||||||
|
class QTcpServer;
|
||||||
|
|
||||||
|
class QtHttpRequest;
|
||||||
|
class QtHttpReply;
|
||||||
|
class QtHttpClientWrapper;
|
||||||
|
|
||||||
|
class QtHttpServer : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
|
||||||
|
|
||||||
|
static const QString & HTTP_VERSION;
|
||||||
|
|
||||||
|
const QString getServerName (void) const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void start (quint16 port = 0);
|
||||||
|
void stop (void);
|
||||||
|
void setServerName (const QString & serverName);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void started (quint16 port);
|
||||||
|
void stopped (void);
|
||||||
|
void error (const QString & msg);
|
||||||
|
void clientConnected (const QString & guid);
|
||||||
|
void clientDisconnected (const QString & guid);
|
||||||
|
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onClientConnected (void);
|
||||||
|
void onClientDisconnected (void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_serverName;
|
||||||
|
QTcpServer * m_sockServer;
|
||||||
|
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTHTTPSERVER_H
|
87
libsrc/webconfig/StaticFileServing.cpp
Normal file
87
libsrc/webconfig/StaticFileServing.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
#include "StaticFileServing.h"
|
||||||
|
|
||||||
|
#include <QStringBuilder>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
StaticFileServing::StaticFileServing (QString baseUrl, quint16 port, QObject * parent)
|
||||||
|
: QObject (parent)
|
||||||
|
, m_baseUrl (baseUrl)
|
||||||
|
{
|
||||||
|
m_mimeDb = new QMimeDatabase;
|
||||||
|
|
||||||
|
m_server = new QtHttpServer (this);
|
||||||
|
m_server->setServerName (QStringLiteral ("Qt Static HTTP File Server"));
|
||||||
|
|
||||||
|
connect (m_server, &QtHttpServer::started, this, &StaticFileServing::onServerStarted);
|
||||||
|
connect (m_server, &QtHttpServer::stopped, this, &StaticFileServing::onServerStopped);
|
||||||
|
connect (m_server, &QtHttpServer::error, this, &StaticFileServing::onServerError);
|
||||||
|
connect (m_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply);
|
||||||
|
|
||||||
|
m_server->start (port);
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticFileServing::~StaticFileServing ()
|
||||||
|
{
|
||||||
|
m_server->stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileServing::onServerStarted (quint16 port)
|
||||||
|
{
|
||||||
|
qDebug () << "QtHttpServer started on port" << port << m_server->getServerName ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileServing::onServerStopped () {
|
||||||
|
qDebug () << "QtHttpServer stopped" << m_server->getServerName ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileServing::onServerError (QString msg)
|
||||||
|
{
|
||||||
|
qDebug () << "QtHttpServer error :" << msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void printErrorToReply (QtHttpReply * reply, QString errorMessage)
|
||||||
|
{
|
||||||
|
reply->addHeader ("Content-Type", QByteArrayLiteral ("text/plain"));
|
||||||
|
reply->appendRawData (errorMessage.toLocal8Bit ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply)
|
||||||
|
{
|
||||||
|
QString command = request->getCommand ();
|
||||||
|
if (command == QStringLiteral ("GET"))
|
||||||
|
{
|
||||||
|
QString path = request->getUrl ().path ();
|
||||||
|
if ( path == "/" || path.isEmpty() || ! QFile::exists(m_baseUrl % "/" % path) )
|
||||||
|
path = "index.html";
|
||||||
|
|
||||||
|
QFile file (m_baseUrl % "/" % path);
|
||||||
|
if (file.exists ())
|
||||||
|
{
|
||||||
|
QMimeType mime = m_mimeDb->mimeTypeForFile (file.fileName ());
|
||||||
|
if (file.open (QFile::ReadOnly)) {
|
||||||
|
QByteArray data = file.readAll ();
|
||||||
|
reply->addHeader ("Content-Type", mime.name ().toLocal8Bit ());
|
||||||
|
reply->appendRawData (data);
|
||||||
|
file.close ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printErrorToReply (reply, "Requested file " % m_baseUrl % "/" % path % " couldn't be open for reading !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printErrorToReply (reply, "Requested file " % path % " couldn't be found !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printErrorToReply (reply, "Unhandled HTTP/1.1 method " % command % " on static file server !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
libsrc/webconfig/StaticFileServing.h
Normal file
31
libsrc/webconfig/StaticFileServing.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef STATICFILESERVING_H
|
||||||
|
#define STATICFILESERVING_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
|
#include "QtHttpServer.h"
|
||||||
|
#include "QtHttpRequest.h"
|
||||||
|
#include "QtHttpReply.h"
|
||||||
|
#include "QtHttpHeader.h"
|
||||||
|
|
||||||
|
class StaticFileServing : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit StaticFileServing (QString baseUrl, quint16 port, QObject * parent = NULL);
|
||||||
|
virtual ~StaticFileServing (void);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onServerStopped (void);
|
||||||
|
void onServerStarted (quint16 port);
|
||||||
|
void onServerError (QString msg);
|
||||||
|
void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_baseUrl;
|
||||||
|
QtHttpServer * m_server;
|
||||||
|
QMimeDatabase * m_mimeDb;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STATICFILESERVING_H
|
33
libsrc/webconfig/WebConfig.cpp
Normal file
33
libsrc/webconfig/WebConfig.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "webconfig/webconfig.h"
|
||||||
|
#include "StaticFileServing.h"
|
||||||
|
|
||||||
|
WebConfig::WebConfig(std::string baseUrl, quint16 port, QObject * parent) :
|
||||||
|
_parent(parent),
|
||||||
|
_baseUrl(QString::fromStdString(baseUrl)),
|
||||||
|
_port(port),
|
||||||
|
_server(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WebConfig::~WebConfig()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WebConfig::start()
|
||||||
|
{
|
||||||
|
if ( _server == nullptr )
|
||||||
|
_server = new StaticFileServing (_baseUrl, _port, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebConfig::stop()
|
||||||
|
{
|
||||||
|
if ( _server != nullptr )
|
||||||
|
{
|
||||||
|
delete _server;
|
||||||
|
_server = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -11,6 +11,9 @@ target_link_libraries(hyperiond
|
|||||||
boblightserver
|
boblightserver
|
||||||
protoserver
|
protoserver
|
||||||
)
|
)
|
||||||
|
if (ENABLE_QT5)
|
||||||
|
target_link_libraries(hyperiond webconfig)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (ENABLE_DISPMANX)
|
if (ENABLE_DISPMANX)
|
||||||
target_link_libraries(hyperiond dispmanx-grabber)
|
target_link_libraries(hyperiond dispmanx-grabber)
|
||||||
@ -40,4 +43,4 @@ install ( TARGETS hyperiond DESTINATION "bin" COMPONENT ambilight )
|
|||||||
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/effects DESTINATION "share/hyperion/" COMPONENT ambilight )
|
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/effects DESTINATION "share/hyperion/" COMPONENT ambilight )
|
||||||
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT ambilight )
|
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT ambilight )
|
||||||
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/config DESTINATION "share/hyperion/" COMPONENT ambilight )
|
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/config DESTINATION "share/hyperion/" COMPONENT ambilight )
|
||||||
|
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/assets/webconfig DESTINATION "share/hyperion/" COMPONENT ambilight )
|
||||||
|
@ -58,8 +58,16 @@
|
|||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// JsonServer includes
|
// network servers
|
||||||
#include <jsonserver/JsonServer.h>
|
#include <jsonserver/JsonServer.h>
|
||||||
|
#include <protoserver/ProtoServer.h>
|
||||||
|
#include <boblightserver/BoblightServer.h>
|
||||||
|
#include <webconfig/WebConfig.h>
|
||||||
|
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
using namespace vlofgren;
|
||||||
|
|
||||||
// ProtoServer includes
|
// ProtoServer includes
|
||||||
#include <protoserver/ProtoServer.h>
|
#include <protoserver/ProtoServer.h>
|
||||||
@ -187,7 +195,11 @@ void startXBMCVideoChecker(const Json::Value &config, XBMCVideoChecker* &xbmcVid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_QT5
|
||||||
|
void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonServer* &jsonServer, ProtoServer* &protoServer, BoblightServer* &boblightServer, WebConfig* &webConfig, XBMCVideoChecker* &xbmcVideoChecker, QObject* parent)
|
||||||
|
#else
|
||||||
void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonServer* &jsonServer, ProtoServer* &protoServer, BoblightServer* &boblightServer, XBMCVideoChecker* &xbmcVideoChecker)
|
void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonServer* &jsonServer, ProtoServer* &protoServer, BoblightServer* &boblightServer, XBMCVideoChecker* &xbmcVideoChecker)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// Create Json server if configuration is present
|
// Create Json server if configuration is present
|
||||||
unsigned int jsonPort = 19444;
|
unsigned int jsonPort = 19444;
|
||||||
@ -218,6 +230,24 @@ void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonSer
|
|||||||
}
|
}
|
||||||
std::cout << "INFO: Proto server created and started on port " << protoServer->getPort() << std::endl;
|
std::cout << "INFO: Proto server created and started on port " << protoServer->getPort() << std::endl;
|
||||||
|
|
||||||
|
#ifdef ENABLE_QT5
|
||||||
|
// webconfig server
|
||||||
|
std::string webconfigPath = "/usr/share/hyperion/webconfig";
|
||||||
|
quint16 webconfigPort = 80;
|
||||||
|
bool webconfigEnable = true;
|
||||||
|
if (config.isMember("webConfig"))
|
||||||
|
{
|
||||||
|
const Json::Value & webconfigConfig = config["webConfig"];
|
||||||
|
webconfigEnable = webconfigConfig.get("enable", true).asBool();
|
||||||
|
webconfigPort = webconfigConfig.get("port", 80).asUInt();
|
||||||
|
webconfigPath = webconfigConfig.get("document_root", "/usr/share/hyperion/webconfig").asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
webConfig = new WebConfig(webconfigPath, webconfigPort, parent);
|
||||||
|
if ( webconfigEnable )
|
||||||
|
webConfig->start();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_ZEROCONF
|
#ifdef ENABLE_ZEROCONF
|
||||||
const Json::Value & deviceConfig = config["device"];
|
const Json::Value & deviceConfig = config["device"];
|
||||||
const std::string deviceName = deviceConfig.get("name", "").asString();
|
const std::string deviceName = deviceConfig.get("name", "").asString();
|
||||||
@ -492,7 +522,12 @@ int main(int argc, char** argv)
|
|||||||
JsonServer * jsonServer = nullptr;
|
JsonServer * jsonServer = nullptr;
|
||||||
ProtoServer * protoServer = nullptr;
|
ProtoServer * protoServer = nullptr;
|
||||||
BoblightServer * boblightServer = nullptr;
|
BoblightServer * boblightServer = nullptr;
|
||||||
|
#ifdef ENABLE_QT5
|
||||||
|
WebConfig * webConfig = nullptr;
|
||||||
|
startNetworkServices(config, hyperion, jsonServer, protoServer, boblightServer, webConfig, xbmcVideoChecker, &app);
|
||||||
|
#else
|
||||||
startNetworkServices(config, hyperion, jsonServer, protoServer, boblightServer, xbmcVideoChecker);
|
startNetworkServices(config, hyperion, jsonServer, protoServer, boblightServer, xbmcVideoChecker);
|
||||||
|
#endif
|
||||||
|
|
||||||
// ---- grabber -----
|
// ---- grabber -----
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user